Skip to content

feat(errors): classify provider failures and enrich telemetry evidence - #879

Merged
seonghobae merged 40 commits into
mainfrom
fix/provider-error-taxonomy-telemetry
Aug 29, 2026
Merged

feat(errors): classify provider failures and enrich telemetry evidence#879
seonghobae merged 40 commits into
mainfrom
fix/provider-error-taxonomy-telemetry

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes three customer-visible gaps in one slice: (1) every upstream model/provider failure surfaced as a generic internal_error; (2) OpenTelemetry spans carried no per-call evidence; (3) orchestration traces omitted which model/provider served each step.

Problem

  • _send_with_retry / _send_raw_with_retry collapsed provider HTTP errors into bare RuntimeErrors and the server catch-all mapped everything to 500 internal_error, so callers could not distinguish a throttled model from an auth failure.
  • Provider spans recorded only transport metadata — no token counts, latency, served model, finish reason, or classified cause on failure.
  • Streamed route traces had no latency_ms; routed/conducted steps omitted model/provider.

Approach

  • Typed taxonomy (contextual_orchestrator/provider_errors.py): upstream HTTP status (RFC 9110), network, TLS, and transport failures classify into OpenAI-compatible codes (rate_limit_exceeded, authentication_error, model_not_found, provider_timeout, …) with client status, retryability, and one bounded redacted message (CWE-209). Only JSON error.message fields pass through; URLs/secrets never do.
  • Transport wiring: chat, passthrough, stream, and batch transports surface the classified cause. Agent failover still runs, but a fully-failed pool raises the final classified ProviderUpstreamError instead of an opaque collapse.
  • Server surface: all four HTTP-method handlers map ProviderUpstreamError to its client status/code with structured detail (agent_id, model, provider_status, retryable, transport) plus actionable next-step guidance per failure family.
  • Telemetry evidence: spans record gen_ai.usage.input_tokens/output_tokens/total_tokens from provider-reported counts, served gen_ai.response.model, gen_ai.response.finish_reasons, request latency, and classified error.type + upstream status on failure (replacing exception-class labels).
  • Trace enrichment: streamed/batched/routed/conducted steps carry model, provider, latency_ms alongside usage.

Validation

  • Full suite locally on this exact head (a62e1ca3): 2279 passed.
  • Docstring coverage 100% (interrogate, org gate).
  • Statement coverage 100% on provider_errors.py and telemetry.py.
  • End-to-end server test asserts a throttled model answers 429 rate_limit_exceeded with guidance — never internal_error.

Open in Devin Review

Summary by CodeRabbit

  • 새로운 기능
    • --production 인증 게이트와 공용 바인드 보호가 추가되었습니다.
    • 지원 에이전트의 스트리밍 응답에서 사용량 정보가 제공됩니다.
    • 비동기 비디오 작업의 상태 확인과 콘텐츠 다운로드 연결이 개선되었습니다.
    • 실험적 CEFR 기준 관찰 게이트웨이가 추가되었습니다.
  • 개선 사항
    • 제공자 오류가 원인별 코드, 상태 및 재시도 가능 여부로 반환됩니다.
    • 제공자 장애 시 안전한 오류 안내와 failover가 적용됩니다.
    • 모델, 지연 시간, 종료 사유 및 토큰 사용량이 텔레메트리에 기록됩니다.
    • 추적 정보 권한 검증과 민감 정보 보호가 강화되었습니다.
    • 함수 도구 설명이 허용된 요청 본문 한도까지 지원됩니다.

Every upstream model failure used to collapse into one generic
internal_error: _send_with_retry/_send_raw_with_retry raised bare
RuntimeErrors and the server catch-all mapped everything to 500.
Telemetry spans recorded only transport metadata, and orchestration
traces omitted which model served a step.

- add contextual_orchestrator.provider_errors: upstream HTTP status,
  network, TLS, and transport failures classify into OpenAI-compatible
  codes with client status, retryability, and bounded redacted messages
  (CWE-209); chat/passthrough/stream/batch transports surface the
  classification; agent failover preserves the final classified cause;
  server error payloads gain next-step guidance per failure family.
- telemetry spans now record GenAI semantic-convention usage counts,
  served response model, finish reason, request latency, and classified
  error.type plus upstream status on failures.
- route/stream/batch/conduct trace steps carry model, provider, and
  latency_ms alongside usage.

Full suite: 2279 passed. Docstrings 100% (interrogate); statement
coverage 100% on provider_errors.py and telemetry.py.
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9371620f-beb6-49c8-89f5-1ea6c320fa88

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1829a and 2bd84b7.

📒 Files selected for processing (11)
  • CHANGELOG.d/provider-error-taxonomy-evidence.md
  • CHANGELOG.md
  • contextual_orchestrator/orchestrator.py
  • contextual_orchestrator/provider_errors.py
  • contextual_orchestrator/server.py
  • tests/test_orchestrator_client_boundaries.py
  • tests/test_passthrough_provider_failover.py
  • tests/test_provider_error_merge_regressions.py
  • tests/test_provider_error_taxonomy.py
  • tests/test_provider_reliability.py
  • tests/test_true_streaming.py

📝 Walkthrough

Walkthrough

제공자 오류를 ProviderUpstreamError로 분류하고 Chat, streaming, passthrough 및 batch 경로에 전달합니다. Provider 응답의 usage, 모델, 종료 사유 및 지연 시간을 텔레메트리에 기록합니다. Trace 접근 제어와 failover 동작을 갱신합니다.

Changes

제공자 신뢰성 및 관측성

Layer / File(s) Summary
제공자 오류 taxonomy
contextual_orchestrator/provider_errors.py, contextual_orchestrator/orchestrator.py, tests/test_provider_error_taxonomy.py
HTTP, TLS, DNS, 연결 및 기타 예외를 제한된 메시지와 OpenAI 호환 오류 코드로 분류합니다.
전송 경로 및 failover 전파
contextual_orchestrator/orchestrator.py, tests/test_orchestrator_client_boundaries.py, tests/test_provider_reliability.py, tests/test_passthrough_provider_failover.py, tests/test_true_streaming.py
Chat, streaming, passthrough 및 batch 실패를 보존합니다. request-size 오류와 마지막 provider 오류를 failover 결과에 반영합니다.
에이전트 상태 및 라우팅 계약
contextual_orchestrator/orchestrator.py
stream_usage_supported, ZDR 정책, 파일 replica, capability 및 psychometric state를 에이전트 저장과 후보 선택에 연결합니다.
서버 오류 응답 및 trace 접근 제어
contextual_orchestrator/server.py, tests/test_chat_include_orchestration_trace_http_honesty.py
HTTP와 SSE 응답에 분류된 오류 정보를 포함합니다. Trace 공개를 요청 플래그와 인증 상태에 따라 판정하고 감사 이벤트를 기록합니다.
Provider 및 workflow 텔레메트리
contextual_orchestrator/telemetry.py, contextual_orchestrator/orchestrator.py, tests/test_telemetry.py, CHANGELOG.md
Provider 응답의 usage, 모델, 종료 사유 및 지연 시간을 기록합니다. 종료 사유 배열은 최대 128개 항목으로 제한합니다. Workflow trace에 모델과 provider를 추가합니다.
통합 검증 및 릴리스 기록
tests/*, CHANGELOG.md, CHANGELOG.d/provider-error-taxonomy-evidence.md, admin_ui/src/stories/Configure.mdx
오류 분류, failover, streaming, batch trace, trace 공개 및 릴리스 기록을 검증합니다. UI JSX의 후행 공백을 제거합니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟡 Moderate · up to 2b182

This PR improves provider error responses and trace evidence, but several current request paths can still misclassify failures, lose actionable provider details, or crash instead of returning the intended contract, while trace labels and audit coverage remain inconsistent. The change is not merge-ready until these bounded correctness and observability issues are addressed or explicitly accepted.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant ModelClient
  participant Provider
  participant Telemetry
  Client->>Server: Chat 또는 Responses 요청
  Server->>ModelClient: provider 호출
  ModelClient->>Provider: 요청 전송
  Provider-->>ModelClient: 응답 또는 provider 실패
  ModelClient->>Telemetry: usage, 모델, 종료 사유, 지연 시간 기록
  ModelClient-->>Server: 응답 또는 ProviderUpstreamError
  Server-->>Client: HTTP 또는 SSE 응답
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.65% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 102 functions across 10 files. (3 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 제공자 오류 분류와 텔레메트리 증거 확장이라는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 67.65% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 102 functions across 10 files. (3 skipped: 2 unsupported, 1 too large.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/provider-error-taxonomy-telemetry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae
seonghobae enabled auto-merge (squash) August 26, 2026 11:37
# Conflicts:
#	CHANGELOG.md
#	contextual_orchestrator/orchestrator.py
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@opencode-agent
opencode-agent Bot disabled auto-merge August 26, 2026 12:45
@seonghobae
seonghobae enabled auto-merge (squash) August 26, 2026 12:53
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@opencode-agent
opencode-agent Bot disabled auto-merge August 26, 2026 16:08
@seonghobae
seonghobae enabled auto-merge (squash) August 26, 2026 16:09
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Exact-head remediation at 6482cb97: initialized both retry/exhaustion state variables consistently; empty passthrough pools now fail with the stable no-eligible-provider error; SSE telemetry now retains provider-reported model and terminal finish reasons through the shared recorder. The retryability table was verified as aligned with the existing transient-status policy. Focused provider-error, telemetry, streaming, and passthrough set: 79 passed; all four threads resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@opencode-agent
opencode-agent Bot disabled auto-merge August 26, 2026 17:00

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 7 new potential issues.

Devin Review

Comment thread contextual_orchestrator/orchestrator.py
Comment thread contextual_orchestrator/telemetry.py
Comment on lines 1986 to 1987
def proxy_get_json(self, agent: ModelAgent, endpoint: str, *, max_response_bytes: int) -> dict[str, Any]:
"""Retrieve provider JSON from the exact agent that owns an async job."""

@devin-ai-integration devin-ai-integration Bot Aug 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Provider-affine errors remain specialized

File and video transports retain route-specific public errors. Applying the general taxonomy there would erase established resource semantics.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1179 to +1183
if isinstance(current, ProviderUpstreamError):
if current.provider_status in (
_PASSTHROUGH_UNAVAILABLE_STATUS | TRANSIENT_HTTP_STATUS
):
return True

@devin-ai-integration devin-ai-integration Bot Aug 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Ambiguous passthrough remains single-shot

Statusless timeouts and connection failures do not authorize cross-provider replay. Only explicit upstream statuses prove safe passthrough failover.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +75 to +86
def provider_error_body(exc: urllib.error.HTTPError) -> bytes:
"""Read and cache one bounded upstream error body for all classifiers."""
cache_key = "_contextual_orchestrator_provider_error_body"
cached = getattr(exc, cache_key, None)
if isinstance(cached, bytes):
return cached
body = exc.read(MAX_PROVIDER_ERROR_BODY_BYTES + 1)[:MAX_PROVIDER_ERROR_BODY_BYTES]
try:
setattr(exc, cache_key, body)
except (AttributeError, TypeError): # pragma: no cover - HTTPError is mutable
pass
return body

@devin-ai-integration devin-ai-integration Bot Aug 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Shared error bodies remain reusable

Retry, tool-limit, and public-message classifiers reuse one bounded cached body. Classifier order no longer consumes evidence needed downstream.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1 to +18
"""Provider failure taxonomy: typed, caller-actionable model errors.

Every upstream provider/model failure is classified into one OpenAI-compatible
error surface so callers learn *which* model failed, *why*, and *whether to
retry* — instead of receiving one opaque ``internal_error`` for every cause.

The classification derives from the upstream HTTP status (RFC 9110 semantics)
and the OpenAI error-code conventions used across compatible providers.
Provider response bodies are never surfaced raw: only a bounded, control-free
message field is kept, because provider diagnostics can embed secrets,
prompts, or internal topology (CWE-209).

References
----------
- Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics*
(RFC 9110). IETF. https://doi.org/10.17487/RFC9110
- OpenAI. (2026). *Error codes*. https://platform.openai.com/docs/guides/error-codes
"""

@devin-ai-integration devin-ai-integration Bot Aug 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Research artifact is missing

This substantive gateway feature adds no academic paper or linked summary. AGENTS.md requires research grounding for substantive feature PRs.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread contextual_orchestrator/provider_errors.py Outdated
@seonghobae
seonghobae enabled auto-merge (squash) August 29, 2026 10:38
@opencode-agent
opencode-agent Bot disabled auto-merge August 29, 2026 10:55
@seonghobae
seonghobae enabled auto-merge (squash) August 29, 2026 10:56
@opencode-agent
opencode-agent Bot disabled auto-merge August 29, 2026 10:57
@seonghobae
seonghobae enabled auto-merge (squash) August 29, 2026 10:58
@opencode-agent
opencode-agent Bot disabled auto-merge August 29, 2026 10:58
github-advanced-security[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

Devin Review

Comment on lines +2007 to +2010
except Exception as exc: # noqa: BLE001 - classify provider transport failures
raise classify_provider_failure(
exc, agent_id=agent.id, model=agent.model, transport="passthrough"
) from None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Binary size rejections damage provider health

When a binary provider returns 413, classify_provider_failure runs before size detection. The request penalizes provider health and loses size-exhaustion semantics.

Prompt for agents
Preserve request-size semantics for binary capability calls. proxy_send_bytes now classifies raw HTTP 413 into ProviderUpstreamError, but _is_request_too_large_error does not recognize that typed representation. Make typed request_too_large/provider_status 413 failures detectable without weakening existing exception-chain limits, then test binary proxy_capability failover and all-candidate exhaustion. Verify 413 attempts do not update group-router health.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

401: (401, "authentication_error", False),
402: (402, "payment_required", False),
403: (403, "permission_error", False),
404: (404, "model_not_found", False),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing batch resources become missing models

Any 404 from batch polling or output retrieval becomes model_not_found. Missing batch resources tell callers to replace a valid model.

Prompt for agents
Make 404 classification transport or operation aware. The global PROVIDER_STATUS_SURFACES entry cannot distinguish a missing chat model from a missing batch, file, job, or passthrough resource. Preserve model_not_found for provider calls where 404 identifies the requested model, and use an accurate resource/API failure for batch lifecycle and non-model endpoints. Add tests for 404 during batch creation/polling/output-file retrieval as well as chat model lookup.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +668 to +671
guidance = _PROVIDER_FAILURE_GUIDANCE.get(
exc.error_code, "Review the request or contact the operator."
)
return f"Model '{exc.model}' via agent '{exc.agent_id}': {exc}. {guidance}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Provider wording precedes gateway guidance

Safe upstream text remains in caller messages before fixed guidance. Contradictory provider wording can make the human-readable recommendation ambiguous.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae merged commit 5f2753a into main Aug 29, 2026
23 of 26 checks passed
@seonghobae
seonghobae deleted the fix/provider-error-taxonomy-telemetry branch August 29, 2026 12:59
seonghobae pushed a commit that referenced this pull request Aug 30, 2026
Two of this PR's own tests hardcoded assumptions that main invalidated
during the merge above (32 commits, base was e7618a3 -> 5f2753a):

- tests/test_nim_benchmark_workflow_contract.py read
  .github/workflows/tests.yml, which main renamed to ci.yml in
  9b0a356 ("use conventional workflow filename"); the
  nim_benchmark_quality job content the tests check for is present and
  intact under the new name. Point both reads at ci.yml.
- tests/test_nim_benchmark_release_acceptance.py::
  test_budgeted_client_fallback_and_transport_errors matched the old
  error string "provider .* request failed". Main's
  contextual_orchestrator/provider_errors.py (PR #879, now on main)
  reclassifies provider HTTP failures through ProviderUpstreamError
  (still a RuntimeError subclass) with the new fixed message "provider
  rejected the request with HTTP {status}"; updated the match regex to
  the new message.

One more failure, tests/test_nim_benchmark_release_acceptance.py::
test_smoke_manifest_cannot_authorize_production_routing, is NOT caused
by this merge: it fails identically (same 1280-vs-1283 token-budget
numbers on task trick_arithmetic_lily_pads/conduct_bounded) on this
PR's own unmerged head b0167b0, contradicting the PR description's
claimed "112 passed" NIM-focused run. Left untouched rather than
loosening the equal-budget assertion or the 30/0.9 evidence thresholds
without the author's input on why token usage grew by 3 tokens for that
one locked task; flagged in the gap baseline for follow-up.

Full suite after both fixes: 2797 passed, 2 failed (the pre-existing
token-budget gap above, plus tests/test_psychometric_routing.py needing
the private fast-mlsirm package that is unreachable in this sandbox,
same documented blocker as PR #917).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants