Skip to content

Populate ResponseUpdate.FinishReason for the Anthropic provider from stop_reason - #596

Merged
Quim Muntal (qmuntal) merged 2 commits into
microsoft:mainfrom
PratikDhanaveFork:anthropic-finish-reason
Jul 27, 2026
Merged

Populate ResponseUpdate.FinishReason for the Anthropic provider from stop_reason#596
Quim Muntal (qmuntal) merged 2 commits into
microsoft:mainfrom
PratikDhanaveFork:anthropic-finish-reason

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

The Anthropic provider never populated ResponseUpdate.FinishReason, so collected responses had an empty FinishReason even though the Messages API returns stop_reason on both the non-streaming response and the streaming message_delta event.

This adds a package-level mapStopReason helper that maps the SDK stop_reason to the canonical FinishReason values shared across providers:

  • end_turn, stop_sequence, pause_turn -> stop

  • max_tokens -> length

  • tool_use -> tool_calls

  • refusal -> content_filter

  • empty / unknown -> ""

  • Non-streaming: sets FinishReason: mapStopReason(resp.StopReason) on the update.

  • Streaming: captures the stop reason from MessageDeltaEvent and sets it on the terminal update, guarding against a later empty chunk overwriting a value already captured.

Why

Brings the Anthropic provider to parity with the other providers, which already surface a finish reason: openaiprovider/chat.go sets finishReason = choice.FinishReason and copilotprovider/copilot.go sets update.FinishReason. This mirrors the .NET ChatFinishReason semantics so downstream consumers can reason about why generation stopped regardless of provider.

Tests

  • TestMapStopReason: table test asserting each SDK stop_reason constant maps to the expected canonical string (including empty/unknown -> "").
  • TestNonStreamingFinishReason: fake-transport run returning stop_reason: tool_use asserts the collected Response.FinishReason == tool_calls.
  • TestStreamingFinishReason: streaming run emitting a message_delta with stop_reason: max_tokens asserts Response.FinishReason == length.

go build ./..., go vet ./provider/anthropicprovider/..., and go test ./provider/anthropicprovider/... all pass.

Copilot AI review requested due to automatic review settings July 23, 2026 02:16
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner July 23, 2026 02:16

Copilot AI 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.

Pull request overview

This PR brings the Anthropic provider to parity with other providers by populating ResponseUpdate.FinishReason based on Anthropic’s stop_reason for both non-streaming and streaming (via message_delta) flows.

Changes:

  • Add mapStopReason(anthropic.StopReason) string helper to translate Anthropic stop_reason values to canonical finish reasons.
  • Populate FinishReason on non-streaming responses using resp.StopReason.
  • Capture streaming stop_reason from MessageDeltaEvent and emit it on the terminal update, avoiding overwriting with later empty stop reasons.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
provider/anthropicprovider/agent.go Adds stop-reason mapping and wires FinishReason into non-streaming and streaming terminal updates.
provider/anthropicprovider/agent_test.go Adds integration-style tests asserting collected FinishReason values for non-streaming and streaming runs.
provider/anthropicprovider/stopreason_internal_test.go Adds table test for mapStopReason covering known, empty, and unknown reasons.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +215 to +218
"event: message_delta\n" +
`data: {"type":"message_delta","delta":{"stop_reason":"max_tokens","stop_sequence":null},"usage":{"output_tokens":5}}` + "\n\n" +
"event: message_stop\n" +
`data: {"type":"message_stop"}` + "\n\n"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 3f03a5e: added TestStreamingFinishReasonNotClobbered, which streams a max_tokens message_delta followed by a second message_delta with stop_reason:null and asserts FinishReason stays "length", validating the guard against an empty stop_reason overwriting a captured value.

@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the anthropic-finish-reason branch 4 times, most recently from 1c873d6 to 559950b Compare July 24, 2026 01:42
@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

The Anthropic provider never set FinishReason, so collected responses lacked
the reason generation ended even though the API returns stop_reason. Add a
mapStopReason helper that maps the SDK stop_reason to the canonical values
used by the OpenAI and Copilot providers (stop/length/tool_calls/content_filter),
set it on the non-streaming update from resp.StopReason, and capture it from the
streaming message_delta event for the terminal update, guarding against a later
empty chunk clobbering the value.
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review ✅

Scope: Internal implementation fix — mapStopReason is unexported; only ResponseUpdate.FinishReason population behavior changed (was always empty, now populated).

No exported API surface changed. The public-api-change label is not warranted.

Cross-repo alignment confirmed:

  • Python (_types.py): FinishReason uses "stop", "length", "tool_calls", "content_filter" — exact same canonical strings mapped here.
  • .NET: The PR description cites .NET ChatFinishReason semantics; the ChatClientAgent-based pipeline delegates FinishReason from the underlying Microsoft.Extensions.AI ChatResponse, which uses identical string values.
  • Other Go providers (OpenAI, Copilot): already set FinishReason with the same values — this PR brings the Anthropic provider to parity with them.

The mapping table (end_turn/stop_sequence/pause_turnstop, max_tokenslength, tool_usetool_calls, refusalcontent_filter) is semantically aligned with all three SDKs. No divergence found.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 48.7 AIC · ⌖ 3.97 AIC · ⊞ 5.9K ·

@qmuntal
Quim Muntal (qmuntal) added this pull request to the merge queue Jul 27, 2026
Merged via the queue into microsoft:main with commit 62f0b4f Jul 27, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants