Skip to content

fix(mistralai): standardize API error handling - #2518

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
jinx-calculus-guzzlers
Open

rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
jinx-calculus-guzzlers

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports livekit/agents#7308 to standardize Mistral API error handling across LLM, STT, and TTS.

  • Translate Mistral SDK and validation errors into APIStatusError.
  • Preserve status codes, raw response bodies, and x-request-id values.
  • Translate SDK and realtime handshake timeouts into APITimeoutError.
  • Stop retrying LLM streams after output has begun.

Validation

  • pnpm test plugins/mistralai (4 passed, 3 provider-key tests skipped)
  • pnpm test agents (2,710 passed, 5 skipped; 3 unrelated environment-specific failures in agents/src/telemetry/loop_monitor.test.ts because this runtime reports process CPU scope instead of thread CPU scope)
  • pnpm build (40/40 workspace builds passed)
  • pnpm --filter @livekit/agents lint (passed with existing warnings)
  • pnpm --filter @livekit/agents-plugin-mistralai lint
  • pnpm --filter @livekit/agents typecheck
  • pnpm --filter @livekit/agents-plugin-mistralai exec tsc --noEmit
  • pnpm format:check

Repository-wide pnpm lint remains blocked by an unrelated existing @typescript-eslint/no-misused-promises error in plugins/openai/src/ws/llm.ts:127. API Extractor also reports unrelated stale base-branch API entries (SpeechStream.terminalError and typed-emitter imports); this PR includes only the intentional APIError.body report update.

Source diff coverage

Source diff coverage

  • Adapted livekit-plugins/livekit-plugins-mistralai/livekit/plugins/mistralai/llm.py to plugins/mistralai/src/llm.ts and the shared target-specific translator in plugins/mistralai/src/errors.ts. Uses the TypeScript SDK error classes, preserves status/body/request ID, maps SDK timeouts, and retains stream retryability after output.
  • Adapted livekit-plugins/livekit-plugins-mistralai/livekit/plugins/mistralai/stt.py to plugins/mistralai/src/stt.ts and plugins/mistralai/src/errors.ts. Applies the same SDK translation to batch and realtime STT; additionally maps the TypeScript SDK realtime handshake timeout wrapper because it has no Python counterpart class.
  • Adapted livekit-plugins/livekit-plugins-mistralai/livekit/plugins/mistralai/tts.py to plugins/mistralai/src/tts.ts and plugins/mistralai/src/errors.ts. Applies standardized status, timeout, request ID, and raw-body handling.
  • Adapted tests/test_plugin_mistralai_llm.py into the existing plugins/mistralai/src/llm.test.ts. Ports exactly the source scenarios for HTTP 400, HTTP 422 detail preservation, and failure after the first streamed chunk; assertions use the target framework error-event contract.
  • Target infrastructure agents/src/_exceptions.ts and agents/etc/agents.api.md widen APIError.body to accept raw strings, which is required to preserve the Mistral SDK body exactly as the source change does.
  • Not applicable: none.

Ported from livekit/agents#7308

Original PR description

Summary

Standardize Mistral error handling across LLM, STT, and TTS while preserving provider error details and retry semantics.

Motivation

With an invalid LLM model such as foo, Mistral returns HTTP 400 with error code 3003.

Model foo is currently not in use.

The LLM plugin previously handled Mistral's SDKError as a generic exception, turning this non-retryable API response into a retryable APIConnectionError. One user turn therefore produced four misleading Connection error events, the
initial request plus three retries.

Changes

  • Translate Mistral SDKError and HTTPValidationError into APIStatusError across LLM, STT, and TTS.
  • Preserve the provider status, body, and x-request-id.
  • Translate actual asyncio/httpx timeouts into APITimeoutError.
  • Preserve LLM stream retryability so errors after output begins are not retried, avoiding duplicated partial output.

Ordinary 4xx responses are now non-retryable, while transient statuses continue to follow APIConnectOptions.

Tests

  • HTTP 400 is attempted once and remains an APIStatusError.
  • HTTP 422 preserves its status, body, and request ID.
  • An error after the first streamed chunk is not retried.
uv run pytest tests/test_plugin_mistralai_llm.py -q

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner September 17, 2026 00:36
@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 85f7dad

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@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 1 potential issue.

Devin Review

return new APITimeoutError({ message: error.message, options: { retryable } });
}

if (error instanceof SDKError || error instanceof HTTPValidationError) {

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.

🟡 Malformed validation responses trigger retries

A malformed 422 response makes mistralAPIError ignore ResponseValidationError. Callers retry it as a connection failure and lose its status, body, and request ID.

Learn more

The Mistral SDK wraps a response that fails its expected schema in ResponseValidationError. That class extends MistralError and retains statusCode, headers, and body, but it is neither SDKError nor HTTPValidationError. The LLM and TTS endpoints can produce it when a 422 body is valid JSON but does not match HTTPValidationError; the SDK's response matcher constructs it before this helper runs. The helper returns undefined, so each caller falls back to a retryable APIConnectionError.

Example: Mistral returns HTTP 422 with body {"unexpected":"shape"}. The SDK raises ResponseValidationError with status 422. The plugin retries it as a connection error instead of returning a non-retryable APIStatusError containing the provider details.

Recommended fix: Match the SDK's MistralError base class, or explicitly include ResponseValidationError, and copy the same status, request ID, and body fields into APIStatusError. Add a malformed-422 test for an endpoint using the validation matcher.

Devin Review

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

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.

0 participants