Skip to content

[Platform] Normalize finish_reason metadata across all bridges#2290

Open
chr-hertel wants to merge 2 commits into
symfony:mainfrom
chr-hertel:finish-reason-all-bridges
Open

[Platform] Normalize finish_reason metadata across all bridges#2290
chr-hertel wants to merge 2 commits into
symfony:mainfrom
chr-hertel:finish-reason-all-bridges

Conversation

@chr-hertel

Copy link
Copy Markdown
Member
Q A
Bug fix? no
New feature? yes
Docs? yes
Issues Fix #2194
License MIT

Follow-up to #2276, which exposed the raw streamed finish_reason for the OpenAI-compatible bridges only.

  • Adds FinishReason\FinishReason + FinishReason\FinishReasonCase, normalizing the divergent provider vocabularies (length / max_tokens / MAX_TOKENS / max_output_tokens) into STOP, LENGTH, TOOL_CALL, CONTENT_FILTER, STOP_SEQUENCE, OTHER while getRaw() keeps the provider value.
  • Exposes it as finish_reason result metadata for buffered and streamed results across OpenAI, Anthropic, Gemini, VertexAI, Bedrock, Azure, Ollama, Cohere, Mistral, DeepSeek, Cerebras, Scaleway, Docker Model Runner, Perplexity, MiniMax, amazee.ai and Generic.
  • Streams now emit the reason as the final MetadataDelta, after the deltas of the chunk that carried it (providers such as Mistral bundle the last content token with finish_reason).
$finishReason = $result->getMetadata()->get('finish_reason');

if ($finishReason?->is(FinishReasonCase::LENGTH)) {
    // truncated by the output token limit
}

Docs in docs/components/platform.rst, runnable example in examples/misc/finish-reason.php.

Known gap: the OpenAI Responses API signals a truncated stream with response.incomplete, which the bridge raises as an exception, so there is no result left to carry the reason. Left as-is; changing it is a behavior change beyond this PR.

@chr-hertel chr-hertel requested a review from OskarStark as a code owner July 9, 2026 01:35
@carsonbot carsonbot added Status: Needs Review Feature New feature Platform Issues & PRs about the AI Platform component labels Jul 9, 2026
@chr-hertel chr-hertel force-pushed the finish-reason-all-bridges branch from b22c4b5 to b97a6a4 Compare July 10, 2026 23:09
…acing LENGTH

Some providers (Anthropic among them) throw MaxOutputTokensException mid-stream
on an output token limit truncation rather than emitting a LENGTH finish reason,
so the same max_tokens case differs between buffered and streamed results.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New feature Platform Issues & PRs about the AI Platform component Status: Reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Platform][Generic] Streamed finish_reason (stop/length/content_filter) is never surfaced to consumers

3 participants