Skip to content

Instantly share code, notes, and snippets.

@tonydzi
Created September 23, 2026 16:50
Show Gist options
  • Select an option

  • Save tonydzi/1992ce6cf95340ea000e67cdb33a4600 to your computer and use it in GitHub Desktop.

Select an option

Save tonydzi/1992ce6cf95340ea000e67cdb33a4600 to your computer and use it in GitHub Desktop.
pydantic-ai #8551: where the 'no usage object' signal dies (models/openai.py L5171-5173), and whether RequestUsage.details can carry it
"""Where does pydantic-ai lose the "provider sent no usage object" signal?
Context: pydantic/pydantic-ai#8551. Measured on pydantic-ai-slim 2.48.0,
openai 3.19.0, opentelemetry-sdk 1.44.0, Python 3.12, macOS.
Part 1 hits the real production mapper (pydantic_ai.models.openai._map_usage)
with three responses; C is the positive control and must differ from A.
Part 2 asks whether RequestUsage.details -- a field that already exists --
survives accumulation into RunUsage.
Part 3 asks whether it reaches the OpenTelemetry span attributes unaided.
The one-line change, at models/openai.py L5173:
- return usage.RequestUsage()
+ return usage.RequestUsage(details={'usage_missing_requests': 1})
Observed with that line applied to the installed package, then reverted:
stock 2.48.0 A == B -> True (unknown indistinguishable from free)
patched A == B -> False (control A == C still False)
reverted A == B -> True
"""
import dataclasses, json
from openai.types import chat, CompletionUsage
from pydantic_ai.models.openai import _map_usage
from pydantic_ai import usage as u
def completion(usage_obj):
return chat.ChatCompletion(
id='x', model='m', object='chat.completion', created=0,
choices=[chat.chat_completion.Choice(
finish_reason='stop', index=0,
message=chat.ChatCompletionMessage(role='assistant', content='hi'))],
usage=usage_obj)
cases = {
'A provider sent no usage object': None,
'B provider sent a genuine zero ': CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
'C control: real tokens (must differ)': CompletionUsage(prompt_tokens=12, completion_tokens=8, total_tokens=20),
}
print('=== part 1: recording edge, pydantic_ai.models.openai._map_usage ===')
out = {}
for name, uo in cases.items():
ru = _map_usage(completion(uo), provider='openai', provider_url='https://api.openai.com/v1', model='m')
out[name] = ru
print(f'{name:38} in={ru.input_tokens:3} out={ru.output_tokens:3} details={ru.details} repr_eq_to_A={ru == out["A provider sent no usage object"]}')
A, B, C = out.values()
print()
print('A == B (unknown indistinguishable from free):', A == B)
print('A == C (control; must be False): ', A == C)
print()
print('=== part 2: does RequestUsage.details survive accumulation into RunUsage? ===')
run = u.RunUsage()
run.incr(u.RequestUsage(input_tokens=12, output_tokens=8)) # normal request
run.incr(u.RequestUsage(details={'usage_missing_requests': 1})) # marked unknown
run.incr(u.RequestUsage(details={'usage_missing_requests': 1})) # marked unknown
print('run.requests =', run.requests)
print('run.input_tokens =', run.input_tokens)
print('run.details =', run.details)
print('marker summed to 2:', run.details.get('usage_missing_requests') == 2)
clean = u.RunUsage()
clean.incr(u.RequestUsage(input_tokens=12, output_tokens=8))
print('control run (no marker) details:', clean.details, '-> absent:', 'usage_missing_requests' not in clean.details)
print()
print('=== part 3: does the marker reach the span attributes for free? ===')
print('marked :', run.opentelemetry_attributes())
print('control :', clean.opentelemetry_attributes())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment