Repository navigation
fix(ai): report OpenAI Agents cached tokens - #1102
marandaneto merged 3 commits into
Conversation
The Agents SDK nests cached input and reasoning tokens under the usage details fields: input_tokens_details/output_tokens_details on generation span usage and on the Responses usage object of response spans. The processor read flat keys that the SDK never sets, so both were always dropped. Read the nested fields, keeping the flat keys for custom models.
dustinbyrne
left a comment
There was a problem hiding this comment.
Nested token extraction is verified on both Agents model paths. One cache-accounting issue remains for OpenAI-compatible Claude requests: the new cache count increases estimated input cost without an inclusive-reporting flag. See the inline reproduction.
AI-assisted review.
|
and thank you for the pull request @mosafariuk! 🙌 |
OpenAI-shaped usage counts cached tokens inside input_tokens. Declare $ai_cache_reporting_exclusive: False when the cache count comes from the nested usage details, as the Node Agents processor does, so the cost backend does not infer exclusive reporting from a Claude model name served through an OpenAI-compatible endpoint. Flat custom-model keys keep their own convention, and a caller-supplied value still takes precedence.
|
@dustinbyrne the inclusive-reporting fix is in 4de76ae, together with your regression test. Ready for another look when you have a moment. |
dustinbyrne
left a comment
There was a problem hiding this comment.
The inclusive cache-accounting correction is verified, including the compatible-Claude case. One remaining interaction affects Gemini through OpenRouter: reasoning is already included in the completion total, but forwarding it separately causes the backend to count it again. See the inline base/head reproduction.
AI-assisted review.
OpenAI-compatible providers such as OpenRouter count reasoning inside completion_tokens, while the cost backend adds reasoning on top for Gemini model names, so forwarding the nested count priced reasoning twice. Keep the original flat reasoning_tokens key only; cached tokens are unchanged.
marandaneto
left a comment
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
💡 Motivation and Context
PostHogTracingProcessornever reports cached input tokens or reasoning tokens for real OpenAI Agents runs, on either model path:OpenAIChatCompletionsModel): the Agents SDK buildsspan_data.usagewith the counts nested underinput_tokens_details.cached_tokensandoutput_tokens_details.reasoning_tokens(seeagents.usage.model_usage_to_span_usageandopenai_chatcompletions.py). The processor reads flatusage["cache_read_input_tokens"]andusage["reasoning_tokens"], which the SDK never sets.OpenAIResponsesModel, the default): the processor reads onlyinput_tokensandoutput_tokensfromresponse.usage, and ignores itsinput_tokens_detailsandoutput_tokens_details.So
$ai_cache_read_input_tokensand$ai_reasoning_tokensare always missing. Cached input is then costed at the full input rate, and reasoning usage is invisible.This PR reads the nested fields on both paths, and keeps the flat keys for custom models that report them that way. The values are mapped the same way as the Python OpenAI wrapper's
extract_openai_usage_from_response:cached_tokens→$ai_cache_read_input_tokensandreasoning_tokens→$ai_reasoning_tokens, each only when greater than zero. It's the Python counterpart of the cache-read part of PostHog/posthog-js#5056. Cache writes are left out, because the Python OpenAI wrapper doesn't report them either.💚 How did you test it?
Runner.runwithOpenAIChatCompletionsModeland withOpenAIResponsesModel, against anhttpx.MockTransportreturning usage with 100 input, 60 cached, 50 output and 40 reasoning tokens, and the PostHog processor registered. Onmain, both runs capture$ai_cache_read_input_tokens: nulland$ai_reasoning_tokens: null. With this change, both capture 60 and 40.posthog/test/ai/openai_agents/test_processor.py:test_generation_span_reads_nested_cache_and_reasoning_tokensbuilds the span usage with the SDK's ownmodel_usage_to_span_usage(Usage(...)).test_response_span_reads_cache_and_reasoning_tokensuses a realopenai.types.responses.Response.mainwithKeyError: '$ai_cache_read_input_tokens'and pass with this change. The existing flat-key test (test_generation_span_with_reasoning_tokens) still passes.ruff format --check,ruff check,mypy | mypy-baseline filterandmake public_api_checkare clean.📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
@posthog/aifixes with the Python SDK and reading the Agents SDK's span-usage serialization. It drafted the fix and tests. I reviewed and ran all of it.