Repository navigation
fix(strix): retry transient OpenRouter upstream 502s #1359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c63edc2
31594fc
f779ba3
3913b4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2869,7 +2869,7 @@ PY | |||||||||||||||||||||||||||||||||||||||
| echo "Strix report artifacts emitted warning/fatal/denied/timeout output; failing closed." | tee -a "$STRIX_LOG" >&2 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if [ "$report_failure_signal" -eq 1 ] || has_detected_infrastructure_error; then | ||||||||||||||||||||||||||||||||||||||||
| if [ "$report_failure_signal" -eq 1 ] || has_detected_infrastructure_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| INFRA_ERROR_DETECTED=1 | ||||||||||||||||||||||||||||||||||||||||
| if [ "$rc" -eq 0 ] && provider_signal_fail_closed_enabled; then | ||||||||||||||||||||||||||||||||||||||||
| echo "Strix run emitted provider infrastructure or failure-signal output; failing closed." >&2 | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2915,13 +2915,72 @@ is_llm_api_connection_error() { | |||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| is_openrouter_upstream_502_error() { | ||||||||||||||||||||||||||||||||||||||||
| local model="${1-}" | ||||||||||||||||||||||||||||||||||||||||
| case "$model" in | ||||||||||||||||||||||||||||||||||||||||
| openrouter/*) ;; | ||||||||||||||||||||||||||||||||||||||||
| *) return 1 ;; | ||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Parse only the JSON record attached to LiteLLM's OpenRouter APIError. | ||||||||||||||||||||||||||||||||||||||||
| # Whole-log regexes can borrow an unrelated target 502, while JSON parsing | ||||||||||||||||||||||||||||||||||||||||
| # also keeps metadata key order and nested values irrelevant. | ||||||||||||||||||||||||||||||||||||||||
| python3 - "$STRIX_LOG" <<'PY' | ||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| api_error = re.compile(r"litellm(?:\.exceptions)?\.APIError:.*OpenrouterException", re.I) | ||||||||||||||||||||||||||||||||||||||||
| lines = Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines() | ||||||||||||||||||||||||||||||||||||||||
| for index, line in enumerate(lines): | ||||||||||||||||||||||||||||||||||||||||
| match = api_error.search(line) | ||||||||||||||||||||||||||||||||||||||||
| if match is None: | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
| inline_start = line.find("{", match.end()) | ||||||||||||||||||||||||||||||||||||||||
| fragments = [line[inline_start:]] if inline_start >= 0 else lines[index + 1 : index + 33] | ||||||||||||||||||||||||||||||||||||||||
| if not fragments or not fragments[0].lstrip().startswith("{"): | ||||||||||||||||||||||||||||||||||||||||
|
seonghobae marked this conversation as resolved.
Comment on lines
+2940
to
+2942
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win inline JSON 레코드의 후속 줄을 함께 누적하십시오. Line 2941은 APIError 줄에 수정 예시- fragments = [line[inline_start:]] if inline_start >= 0 else lines[index + 1 : index + 33]
+ fragments = (
+ [line[inline_start:]] + lines[index + 1 : index + 33]
+ if inline_start >= 0
+ else lines[index + 1 : index + 33]
+ )이 형식의 다중 줄 JSON 회귀 테스트도 추가하십시오. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
| record = "" | ||||||||||||||||||||||||||||||||||||||||
| for fragment in fragments: | ||||||||||||||||||||||||||||||||||||||||
| record += fragment.strip() | ||||||||||||||||||||||||||||||||||||||||
| if len(record) > 65536: | ||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||
| value = json.loads(record) | ||||||||||||||||||||||||||||||||||||||||
| except json.JSONDecodeError: | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
| error = value.get("error", {}) if isinstance(value, dict) else {} | ||||||||||||||||||||||||||||||||||||||||
| metadata = error.get("metadata", {}) if isinstance(error, dict) else {} | ||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||
| type(error.get("code")) is int | ||||||||||||||||||||||||||||||||||||||||
| and error["code"] == 502 | ||||||||||||||||||||||||||||||||||||||||
| and isinstance(metadata, dict) | ||||||||||||||||||||||||||||||||||||||||
| and isinstance(metadata.get("provider_name"), str) | ||||||||||||||||||||||||||||||||||||||||
| and metadata["provider_name"].strip() | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2953
to
+2961
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Malformed error JSON crashes the OpenRouter 502 classifier When the OpenRouter error JSON has a non-object
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||
| raise SystemExit(0) | ||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||
| raise SystemExit(1) | ||||||||||||||||||||||||||||||||||||||||
| PY | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| is_llm_service_unavailable_error() { | ||||||||||||||||||||||||||||||||||||||||
| local model="${1-}" | ||||||||||||||||||||||||||||||||||||||||
| if grep -Eiq 'litellm(\.exceptions)?\.ServiceUnavailableError' "$STRIX_LOG" && | ||||||||||||||||||||||||||||||||||||||||
| grep -Eiq '(GeminiException|Nvidia_nimException|nvidia[_ -]?nim|VertexAI|Vertex_ai|vertex\.ai|openai|anthropic|LLM CONNECTION FAILED|Could not establish connection to the language model)' "$STRIX_LOG" && | ||||||||||||||||||||||||||||||||||||||||
| grep -Eiq '("status"[[:space:]]*:[[:space:]]*"UNAVAILABLE"|(^|[^0-9])503([^0-9]|$)|high demand|temporarily overloaded|Service Unavailable)' "$STRIX_LOG"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # OpenRouter's dynamic free route can surface an upstream provider 502 as | ||||||||||||||||||||||||||||||||||||||||
| # APIError rather than ServiceUnavailableError. Require both LiteLLM's | ||||||||||||||||||||||||||||||||||||||||
| # OpenRouter exception and OpenRouter's provider metadata so target-app 502 | ||||||||||||||||||||||||||||||||||||||||
| # output cannot independently trigger a provider retry. | ||||||||||||||||||||||||||||||||||||||||
| if is_openrouter_upstream_502_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2965,10 +3024,14 @@ is_transient_same_model_retry_error() { | |||||||||||||||||||||||||||||||||||||||
| if is_timeout_error; then | ||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if grep -Eiq 'Vulnerabilities[[:space:]]+[1-9][0-9]*' "$STRIX_LOG" || | ||||||||||||||||||||||||||||||||||||||||
| has_blocking_vulnerability_reports; then | ||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
seonghobae marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
| if is_llm_api_connection_error; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error; then | ||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if is_rate_limit_error; then | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3037,7 +3100,7 @@ run_strix_with_transient_retry() { | |||||||||||||||||||||||||||||||||||||||
| retry_reason="rate limit" | ||||||||||||||||||||||||||||||||||||||||
| elif is_llm_api_connection_error; then | ||||||||||||||||||||||||||||||||||||||||
| retry_reason="LLM API connection" | ||||||||||||||||||||||||||||||||||||||||
| elif is_llm_service_unavailable_error; then | ||||||||||||||||||||||||||||||||||||||||
| elif is_llm_service_unavailable_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| retry_reason="LLM service unavailable" | ||||||||||||||||||||||||||||||||||||||||
| elif is_midstream_fallback_error; then | ||||||||||||||||||||||||||||||||||||||||
| retry_reason="midstream fallback" | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3269,6 +3332,7 @@ is_llm_token_limit_error() { | |||||||||||||||||||||||||||||||||||||||
| # was interrupted or incomplete. Used as a guard to prevent the | ||||||||||||||||||||||||||||||||||||||||
| # below-threshold override from silently passing an aborted scan. | ||||||||||||||||||||||||||||||||||||||||
| has_detected_infrastructure_error() { | ||||||||||||||||||||||||||||||||||||||||
| local model="${1-}" | ||||||||||||||||||||||||||||||||||||||||
| if grep -Eiq '(^|[^[:alpha:]])(Fatal|Denied|Warn|Warning)([^[:alpha:]]|$)' "$STRIX_LOG"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3293,7 +3357,7 @@ has_detected_infrastructure_error() { | |||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error; then | ||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -4190,7 +4254,7 @@ is_model_retryable_error() { | |||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error; then | ||||||||||||||||||||||||||||||||||||||||
| if is_llm_service_unavailable_error "$model"; then | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.