Skip to content

FEAT: Capture API response stop reason on MessagePiece metadata - #2340

Open
varunj-msft wants to merge 1 commit into
microsoft:mainfrom
varunj-msft:varunj-msft/10481-Capture-API-Response-Metadata
Open

FEAT: Capture API response stop reason on MessagePiece metadata#2340
varunj-msft wants to merge 1 commit into
microsoft:mainfrom
varunj-msft:varunj-msft/10481-Capture-API-Response-Metadata

Conversation

@varunj-msft

Copy link
Copy Markdown
Contributor

Description

Targets already recorded token usage but discarded why generation stopped, and dropped
usage entirely on content-filtered responses. This captures the provider's stop reason
alongside token usage on MessagePiece.prompt_metadata:

  • finish_reason — Chat Completions, Completions, LiteLLM
  • status + incomplete_reason — Responses API

A base no-op hook OpenAITarget._capture_response_metadata is called from
_handle_content_filter_response, so a filtered response now records the tokens it consumed.

These keys are reserved for the provider. construct_response_from_request merges the
request's prompt_metadata into every response piece, so a caller-supplied finish_reason
would otherwise be indistinguishable from the real one. All reserved keys are cleared from
every piece before each capture writes back what its own API reported; token_usage_* is
cleared by prefix for the same reason.

Two bugs fixed along the way:

  • OpenAICompletionTarget captured neither usage nor finish_reason.
  • With n>1, every piece got choice 0's finish_reason; each piece now gets its own.

Not breaking: _METADATA_PREFIXTOKEN_USAGE_METADATA_PREFIX was private with no
external callers.

Tests and Documentation

  • Full tests/unit: 14,701 passed / 0 failed. Diff coverage 95% (gate is 90%).
    pre-commit run --all-files clean, including ty.
  • New tests cover each target's capture path, reserved-key clearing across all pieces,
    per-choice finish_reason with n>1, content-filtered responses, "not reported" cases
    (missing/empty/non-string omitted rather than stored as zeros), and a SQLite round-trip.
  • Verified live against Azure OpenAI: finish_reason=stop; a truncated reasoning request →
    status=incomplete + incomplete_reason=max_output_tokens; and a forged caller-supplied
    finish_reason correctly cleared.
  • No doc/notebook changes (metadata only, no public API surface), so JupyText N/A.

Targets already recorded token usage but discarded why generation stopped,
and dropped usage entirely on content-filtered responses.

Capture the provider's stop reason alongside token usage: finish_reason for
Chat Completions, Completions and LiteLLM; status plus incomplete_reason for
the Responses API. A base no-op hook on OpenAITarget is called from
_handle_content_filter_response, so a filtered response now records the
tokens it consumed instead of returning bare metadata.

These keys are reserved for the provider. construct_response_from_request
merges the request's metadata into every response piece, so all of them are
cleared before a capture writes back the subset its own API reports.
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.

1 participant