-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(usage): Normalize None token details on Usage initialization #2034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ef862d7 to
c1a9802
Compare
|
FYI I'm aware it could be a bikeshed discussion if these fields are really optional or not. for example, they are more or less implied or explicitly required in the latest openai openapi yaml. However, there is defensive code around other token fields, that coerce to zero. Plus the impact is tricky to workaround otherwise even if PRs are raised everywhere. So, hoping we can add similar defense here |
seratch
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'e fine to deal with this reality, but can you update the code comment a bit more for future maintainers?
Some providers don't populate optional token detail fields, resulting in None values that bypass Pydantic validation. This fix adds a __post_init__ method to normalize None to 0 for cached_tokens and reasoning_tokens on any Usage object creation. This defensive approach handles the issue at the boundary we control, regardless of how providers construct their response objects. Signed-off-by: Adrian Cole <adrian@tetrate.io>
c1a9802 to
376530d
Compare
seratch
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
Extends openai#2034 to handle providers that return None for entire input_tokens_details and output_tokens_details objects (not just the fields within them). This affects non-streaming responses. Related to openai#1179 (which fixed the streaming case). Some providers like llama-stack return null for these optional fields in their JSON responses. The OpenAI SDK maps these to None in Python. Previously, passing None to the Usage constructor would fail Pydantic validation before __post_init__ could normalize them. This PR uses Pydantic's BeforeValidator to normalize None values at the field level, before Pydantic's type validation runs. Signed-off-by: Adrian Cole <adrian@tetrate.io>
Extends openai#2034 to handle providers that return None for entire input_tokens_details and output_tokens_details objects (not just the fields within them). This affects non-streaming responses. Related to openai#1179 (which fixed the streaming case). Some providers like llama-stack return null for these optional fields in their JSON responses. The OpenAI SDK maps these to None in Python. Previously, passing None to the Usage constructor would fail Pydantic validation before __post_init__ could normalize them. This PR uses Pydantic's BeforeValidator to normalize None values at the field level, before Pydantic's type validation runs. Signed-off-by: Adrian Cole <adrian@tetrate.io>
|
ps looks like there was another case like this that popped up. Hopefully #2141 is a more sustainable fix, lemme know if you agree |
Extends openai#2034 to handle providers that return None for entire input_tokens_details and output_tokens_details objects (not just the fields within them). This affects non-streaming responses. Related to openai#1179 (which fixed the streaming case). Some providers like llama-stack return null for these optional fields in their JSON responses. The OpenAI SDK maps these to None in Python. Previously, passing None to the Usage constructor would fail Pydantic validation before __post_init__ could normalize them. This PR uses Pydantic's BeforeValidator to normalize None values at the field level, before Pydantic's type validation runs. The validators also convert Chat Completions API types (PromptTokensDetails, CompletionTokensDetails) to Responses API types (InputTokensDetails, OutputTokensDetails).
Extends openai#2034 to handle providers that return None for entire input_tokens_details and output_tokens_details objects (not just the fields within them). This affects non-streaming responses. Related to openai#1179 (which fixed the streaming case). Some providers like llama-stack return null for these optional fields in their JSON responses. The OpenAI SDK maps these to None in Python. Previously, passing None to the Usage constructor would fail Pydantic validation before __post_init__ could normalize them. This PR uses Pydantic's BeforeValidator to normalize None values at the field level, before Pydantic's type validation runs. The validators also convert Chat Completions API types (PromptTokensDetails, CompletionTokensDetails) to Responses API types (InputTokensDetails, OutputTokensDetails).
Extends openai#2034 to handle providers that return None for entire input_tokens_details and output_tokens_details objects (not just the fields within them). This affects non-streaming responses. Related to openai#1179 (which fixed the streaming case). Some providers like llama-stack return null for these optional fields in their JSON responses. The OpenAI SDK maps these to None in Python. Previously, passing None to the Usage constructor would fail Pydantic validation before __post_init__ could normalize them. This PR uses Pydantic's BeforeValidator to normalize None values at the field level, before Pydantic's type validation runs. The validators also convert Chat Completions API types (PromptTokensDetails, CompletionTokensDetails) to Responses API types (InputTokensDetails, OutputTokensDetails).
Some openai API clones (such as ollama) don't populate new token detail fields, resulting in None values that bypass Pydantic validation. This results in agent crashes like this:
This fix adds a
__post_init__method to normalize None to 0 for "cached_tokens" and "reasoning_tokens" on any Usage object creation. This defensive approach handles the issue at the boundary we control (just after the stainless generated code), allowing absence of these newer fields to not break agents.