Repository navigation
fix(telemetry): keep client token usage when the last response has none - #7561
Somay-kousis wants to merge 1 commit into
Conversation
record_client_token_usage read usage only from the last response of a model call and recorded nothing when that response had no usage_metadata. A LiteLlm stream cut off by max_tokens inside a tool call yields the text with the usage and then an error response without it, so the call's tokens never reached gen_ai.client.token.usage. Take the newest usage report with TokenUsage.from_llm_responses, the same helper the per-agent token totals already use.
There was a problem hiding this comment.
Code Review
This pull request updates token usage tracking in streaming responses to use TokenUsage.from_llm_responses(responses) instead of relying solely on the last response chunk, which may lack metadata if the stream is cut off. A unit test was added to verify this behavior. The reviewer pointed out that responses[-1].model_version might also be missing in trailing chunks, and suggested searching backwards through the responses to find the first non-null model version.
| input_token_count = token_usage.input_tokens or 0 | ||
| output_token_count = token_usage.output_tokens or 0 | ||
| response_model = last_response.model_version or llm_request.model | ||
| response_model = responses[-1].model_version or llm_request.model |
There was a problem hiding this comment.
Since the last response in a stream can be a trailing chunk or error response that lacks metadata (which is the core issue this PR addresses), it is highly likely that responses[-1].model_version will also be None. To ensure we don't lose the resolved model version, we should retrieve the model version from the latest response chunk that actually populated it, falling back to llm_request.model only if none of the responses have it.
| response_model = responses[-1].model_version or llm_request.model | |
| response_model = next((r.model_version for r in reversed(responses) if r.model_version), None) or llm_request.model |
There was a problem hiding this comment.
Kept this as it was on purpose. Main already reads model_version from the last response, and in the LiteLlm stream this PR is about, the text and tool-call responses get the same stream-level model version, so the last one isn't missing it. I'd rather keep this PR to the usage fix and leave the model attribute unchanged.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
record_client_token_usagereads usage only from the last response of a model call. If that response has nousage_metadata, it records nothing forgen_ai.client.token.usage, even when an earlier response of the same call reported the usage.LiteLlmstreaming does this when the model hitsmax_tokensinside a tool call. It yields the text with the usage, clears the usage, then yields an error response without it (lite_llm.py:4313-4330). The call's tokens never reach the metric.Solution:
Use
TokenUsage.from_llm_responses, which takes the newest usage report.record_inference_telemetryalready uses it for the per-agent token totals a few lines earlier, so the metric and the totals now read the same report.gen_ai.response.modelstill comes from the last response, as before.When the last response does have usage,
from_llm_responsespicks that same report, so nothing changes for calls that already recorded. One edge moves: a last response whose usage has neither prompt nor candidate counts used to record a 0/0 point, and now it's skipped with the same warning, which matches the per-agent totals.Testing Plan
Unit Tests:
New test
test_record_client_token_usage_keeps_usage_before_trailing_responseintests/unittests/telemetry/test_metrics.py: a response with usage, then aMAX_TOKENSresponse without it.Before the fix:
After the fix:
pre-commit run --fileson both files passes.mypyon_metrics.pyreports one error at the"compaction"feature list, and main without this change reports the same error (from d01d5d8). This change adds none.Manual End-to-End (E2E) Tests:
The script from the issue runs an
LlmAgentonLiteLlmwith a fake client that replays a stream cut off bymax_tokensinside a tool call, in SSE mode, and reads the metric from anInMemoryMetricReader.Before:
After:
Checklist
Additional context
N/A