chore(types): coerce dict server_tool_use to ServerToolUse in Usage.__init__

The usage-only recovery path added in #31035 builds a Usage whose
server_tool_use is a plain dict; on stable/1.86.x Usage.__init__ assigned it
verbatim, so attribute access on the recovered usage failed. Replicate the
staging Usage.__init__ coercion so server tool-use requests are typed and
priced. This dependency has no discrete commit to cherry-pick; it predates
internal staging's squashed-root history, so it is carried as a small
verbatim copy of current staging behavior.

The same coercion makes server_tool_use a ServerToolUse on the pre-existing
calculate_usage round-trip (Usage(**model_dump())), so the matching staging
test assertion update is carried too: test_stream_chunk_builder_anthropic_web_search
now asserts attribute access (and isinstance ServerToolUse) instead of dict
subscript, matching staging and the typed representation.
This commit is contained in:
Yuneng Jiang 2026-06-23 21:11:51 -07:00
parent 8b969db626
commit a7a625a8e0
No known key found for this signature in database
2 changed files with 8 additions and 2 deletions

View file

@ -1554,7 +1554,7 @@ class Usage(SafeAttributeModel, CompletionUsage):
completion_tokens_details: Optional[
Union[CompletionTokensDetailsWrapper, dict]
] = None,
server_tool_use: Optional[ServerToolUse] = None,
server_tool_use: Optional[Union[ServerToolUse, dict]] = None,
cost: Optional[float] = None,
**params,
):
@ -1655,6 +1655,9 @@ class Usage(SafeAttributeModel, CompletionUsage):
prompt_tokens_details=_prompt_tokens_details or None,
)
if isinstance(server_tool_use, dict):
server_tool_use = ServerToolUse(**server_tool_use)
if server_tool_use is not None:
self.server_tool_use = server_tool_use
else: # maintain openai compatibility in usage object if possible

View file

@ -520,7 +520,10 @@ def test_stream_chunk_builder_anthropic_web_search():
assert usage.prompt_tokens == 50
assert usage.completion_tokens == 27
assert usage.total_tokens == 77
assert usage.server_tool_use["web_search_requests"] == 2
# server_tool_use must be a ServerToolUse pydantic so downstream cost-calc
# (which uses attribute access) works. See issue #26153.
assert isinstance(usage.server_tool_use, ServerToolUse)
assert usage.server_tool_use.web_search_requests == 2
def test_sort_chunks_handles_dict_hidden_params_created_at():