bumping pydantic ai (#280)

* bumping pydantic ai

* Fix breakage from pydantic-ai 1.107: capability toolsets no longer cross runs

All three failures came from pydantic-ai #5230 (on-demand capabilities):

- SubAgents inherit_tools transplanted capability-contributed toolsets into
  sub-agent runs where the owning capability is not registered, which now
  fails CapabilityOwnedToolset's ownership resolution. Inherit only the
  parent's own toolsets; capability sharing is shared_capabilities' job.
  This also drops the delegate tool, replacing the name-based filter.
- Tool search discovery moved from message-scanning to
  RunContext.discovered_tool_names, so the code_mode test now derives it
  via parse_discovered_tools like the agent graph does.
- ToolDefinition gained capability_id, refreshing the logfire snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Test latest pydantic-ai in CI and set the floor to the verified minimum

The lock-resolved test matrix never exercises pydantic-ai releases newer
than the lock, and pydantic-ai's harness-compat job only covers harness
code that exists when a pydantic-ai PR merges. Harness code merged after
a core change (SubAgents, ManagedPrompt vs core #5230) was therefore
never tested against it until a manual bump. The new test-latest job
re-locks pydantic-ai-slim/pydantic-graph to the newest published
versions on every run, so release breakage surfaces immediately.

With latest covered by CI, the pydantic-ai-slim floor no longer needs to
chase releases: set it to 1.105.0, the first release with capability
ownership semantics (core #5230) that the harness now relies on, and
verified against the full suite via the test-floor resolution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Mirror pydantic-ai's lowest-versions matrix and unfreeze its resolution

The test-floor job was not testing the floor: under the workflow-level
UV_FROZEN=1, `uv sync --resolution lowest-direct` installs the locked
(latest) versions, so the job duplicated the regular matrix while
claiming lowest-version coverage. pydantic-ai's test-lowest-versions job
sets UV_FROZEN=0 on the sync step for exactly this reason; do the same,
and adopt the rest of its shape (full Python matrix, fail-fast off, job
timeouts) so floor regressions that only reproduce on some Python
version get caught.

A genuinely-lowest resolution surfaced one over-coupled snapshot: the
`logfire.metrics` span attribute only exists on logfire releases newer
than the extra's 4.31.0 floor, so treat it as volatile in the
ManagedPrompt span snapshots instead of raising the floor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Replace UV_FROZEN with UV_LOCKED in CI

Frozen mode installs the lockfile blindly: lock drift goes unnoticed and
resolution flags become silent no-ops, which is how the lowest-versions
job ended up testing the locked versions. Locked mode validates the
lockfile against pyproject.toml and fails loudly on any mismatch, so a
job that cannot do what it claims turns red instead of green. The two
jobs that deviate from the lock on purpose (lowest-versions resolution,
latest pydantic-ai upgrade) opt out per step, each with a comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep filtering the delegate tool when SubAgentToolset is used directly

The capability-ownership filter only drops the delegate tool when the
toolset arrives via the SubAgents capability, since that is the path that
wraps it in CapabilityOwnedToolset. SubAgentToolset is publicly exported,
and registered directly in Agent(toolsets=[...]) nothing wraps it, so
inherit_tools=True forwarded delegate_task to sub-agents and re-enabled
recursive delegation. Restore the name filter on top of the capability
filter so both registration paths stay non-recursive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Adapt to per-delegate run controls from main

PR #283 made limits and call_counts required on SubAgentToolset, which
broke the direct-construction test from the previous commit once CI
merged the branch with main. The merge also stranded the
CapabilityOwnedToolset import below the new SubAgentLimits dataclass;
moved it back into the import block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Aditya Vardhan
2026-06-12 16:55:22 +05:30
committed by GitHub
co-authored by Claude Fable 5
parent e34c30ae54
commit 05ae737182
8 changed files with 252 additions and 43 deletions
+59 -8
View File
@@ -8,8 +8,14 @@ on:
- 'v*'
pull_request: {}
# UV_LOCKED (not UV_FROZEN): both keep uv from rewriting the lockfile, but
# frozen installs the lock blindly while locked validates it against
# pyproject.toml first and fails the job on drift. Frozen also turns
# resolution flags into silent no-ops, which left the lowest-versions job
# testing the locked (latest) versions for months. Jobs that intentionally
# deviate from the lock opt out per step with `UV_LOCKED: '0'` or `env -u`.
env:
UV_FROZEN: '1'
UV_LOCKED: '1'
permissions:
contents: read
@@ -28,7 +34,7 @@ jobs:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-suffix: lint
- run: uv sync --frozen --all-groups --all-extras
- run: uv sync --locked --all-groups --all-extras
- uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0
with:
@@ -40,6 +46,7 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
@@ -62,7 +69,7 @@ jobs:
python-version: ${{ matrix.python-version }}
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- run: uv sync --frozen --group dev ${{ matrix.install.extras }}
- run: uv sync --locked --group dev ${{ matrix.install.extras }}
- run: mkdir -p coverage-data
- run: uv run coverage run -m pytest
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
@@ -82,7 +89,44 @@ jobs:
# `harness compat` job (see https://github.com/pydantic/pydantic-ai/blob/main/.github/workflows/harness-compat.yml).
test-floor:
runs-on: ubuntu-latest
name: test on lowest direct-dep versions
name: test on ${{ matrix.python-version }} (lowest-versions)
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
python-version: ${{ matrix.python-version }}
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-suffix: floor
# The lock validation must be off here: this job deviates from the lock
# on purpose, and under UV_LOCKED=1 the lowest-direct resolution fails
# because uv refuses to write the lowest-resolved lockfile it produces.
- run: uv sync --resolution lowest-direct --group dev --all-extras
env:
UV_LOCKED: '0'
- run: uv run --no-sync pytest
# Verify the harness works against the latest published pydantic-ai packages,
# not just the lock. The `test` matrix resolves against the lock, so harness
# code merged after the last lock refresh is never exercised against newer
# pydantic-ai releases until someone bumps. pydantic-ai's `harness compat`
# job doesn't cover that window either: it tests harness main as it exists
# when a pydantic-ai PR merges, not harness code added afterwards. With this
# job, the pydantic-ai-slim `>=` floor in pyproject.toml can stay an honest,
# test-verified floor (exercised by `test-floor`) instead of chasing the
# latest release.
test-latest:
runs-on: ubuntu-latest
name: test on latest pydantic-ai
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -92,9 +136,16 @@ jobs:
with:
python-version: '3.14'
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-suffix: floor
cache-suffix: latest
- run: uv sync --resolution lowest-direct --group dev --all-extras
# `env -u`: under the workflow-level `UV_LOCKED=1`, `uv lock` asserts the
# lockfile is up to date instead of applying the upgrade, so the moment a
# newer pydantic-ai exists this step would fail rather than test it.
- run: |
env -u UV_LOCKED uv lock \
--upgrade-package pydantic-ai-slim \
--upgrade-package pydantic-graph
- run: uv sync --locked --group dev --all-extras
- run: uv run --no-sync pytest
coverage:
@@ -116,13 +167,13 @@ jobs:
path: coverage-data
merge-multiple: true
- run: uv sync --frozen --group dev --all-extras
- run: uv sync --locked --group dev --all-extras
- run: uv run coverage combine coverage-data
- run: uv run coverage report
check:
if: always()
needs: [lint, test, test-floor, coverage]
needs: [lint, test, test-floor, test-latest, coverage]
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
@@ -59,7 +59,7 @@ print(result.output)
- **Deps are forwarded.** The parent run's `deps` are passed to each sub-agent, so sub-agents share the parent's `AgentDepsT` (enforced by the type signature -- every sub-agent is an `AbstractAgent[AgentDepsT, Any]`).
- **Usage is shared by default.** The parent's `usage` is passed to each sub-agent run, so token usage aggregates and a parent `usage_limits` applies across the whole agent tree. Set `forward_usage=False` to give each sub-agent run its own accounting.
- **Tools can be inherited.** With `inherit_tools=True`, the parent agent's tools are added to each sub-agent run (on top of the sub-agent's own). The delegate tool itself is filtered out, so a sub-agent can't recurse into further delegation. Off by default.
- **Tools can be inherited.** With `inherit_tools=True`, the parent agent's own tools (registered directly or via `toolsets`) are added to each sub-agent run, on top of the sub-agent's own. Tools contributed by the parent's capabilities are not inherited: they are bound to capability instances registered in the parent run, and would arrive without the hooks and instructions they depend on. Use `shared_capabilities` to give sub-agents a capability. This also excludes the delegate tool itself, so a sub-agent can't recurse into further delegation. Off by default.
- **Capabilities can be shared.** `shared_capabilities` are applied to every sub-agent run -- e.g. give all sub-agents a common guardrail, memory, or planning capability without rebuilding each `Agent`.
- **Sub-agent events can be streamed.** Pass an `event_stream_handler` and it's forwarded to each sub-agent run, so the sub-agent's model-streaming and tool events surface to the caller (the handler receives the sub-agent's own `RunContext`).
@@ -112,7 +112,7 @@ SubAgents(
agents={}, # Mapping[str, AbstractAgent[AgentDepsT, Any]] -- name -> agent
descriptions=None, # optional per-name description overrides for the prompt listing
forward_usage=True, # share the parent's usage with sub-agent runs
inherit_tools=False, # expose the parent's tools to sub-agents (delegate tool excluded)
inherit_tools=False, # expose the parent's own tools to sub-agents (capability tools excluded)
shared_capabilities=(),# capabilities applied to every sub-agent run
event_stream_handler=None, # forwarded to each sub-agent run to stream its events
tool_name='delegate_task',
@@ -12,6 +12,10 @@ from pydantic_ai.capabilities import AgentCapability
from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UsageLimitExceeded
from pydantic_ai.tools import AgentDepsT, RunContext
from pydantic_ai.toolsets import AbstractToolset, FunctionToolset
# Private import: pydantic-ai has no public way to tell capability-contributed
# toolsets apart from the agent's own in `agent.toolsets`.
from pydantic_ai.toolsets._capability_owned import CapabilityOwnedToolset
from pydantic_ai.usage import UsageLimits
@@ -50,6 +54,19 @@ class SubAgentLimits:
instead of raising a parent `ModelRetry`."""
def _is_capability_contributed(toolset: AbstractToolset[AgentDepsT]) -> bool:
"""Whether `toolset`'s tree contains a `CapabilityOwnedToolset`."""
found = False
def visit(node: AbstractToolset[AgentDepsT]) -> None:
nonlocal found
if isinstance(node, CapabilityOwnedToolset):
found = True
toolset.apply(visit)
return found
class SubAgentToolset(FunctionToolset[AgentDepsT]):
"""Exposes one delegate tool that dispatches a task to a named sub-agent.
@@ -87,11 +104,31 @@ class SubAgentToolset(FunctionToolset[AgentDepsT]):
self.add_function(self.delegate_task, name=tool_name)
def _inherited_toolsets(self, ctx: RunContext[AgentDepsT]) -> list[AbstractToolset[AgentDepsT]] | None:
"""The parent's toolsets, with the delegate tool filtered out (no recursion)."""
"""The parent agent's own toolsets, excluding capability-contributed ones.
Capability toolsets are bound to capability instances registered in the
parent run; carrying them into the sub-agent's run (where their owner is
not registered) fails `CapabilityOwnedToolset`'s ownership resolution, and
the tools would arrive without the hooks and instructions that make them
work. Use `shared_capabilities` to share a capability with sub-agents.
The delegate tool itself is also filtered out by name, so delegation
cannot recurse. When this toolset was registered via the `SubAgents`
capability the capability filter already drops it; the name filter covers
direct registration in `Agent(toolsets=[...])`, where nothing wraps it in
`CapabilityOwnedToolset`.
"""
agent = ctx.agent
if agent is None: # pragma: no cover - the running agent is always set during a run
return None
return [toolset.filtered(lambda _ctx, tool_def: tool_def.name != self._tool_name) for toolset in agent.toolsets]
# Capability toolsets surface as `CombinedToolset(CapabilityOwnedToolset(...))`
# entries, so ownership is detected by walking each tree. Only core's capability
# assembly constructs `CapabilityOwnedToolset`, so a tree containing one is
# capability-contributed in its entirety.
return [
toolset.filtered(lambda _ctx, tool_def: tool_def.name != self._tool_name)
for toolset in agent.toolsets
if not _is_capability_contributed(toolset)
]
def _budget_exhausted(self, ctx: RunContext[AgentDepsT], agent_name: str, max_calls: int) -> bool:
"""Increment this run's delegation count for `agent_name` and report whether it is over budget.
+2 -2
View File
@@ -29,7 +29,7 @@ classifiers = [
]
dependencies = [
"httpx>=0.28.1",
'pydantic-ai-slim>=1.95.1',
"pydantic-ai-slim>=1.105.0",
]
[project.optional-dependencies]
@@ -48,7 +48,7 @@ dbos = [
]
logfire = [
'logfire>=4.31.0',
'pydantic-ai-slim[spec]>=1.95.1',
"pydantic-ai-slim[spec]>=1.105.0",
]
[project.urls]
+16 -14
View File
@@ -1805,8 +1805,8 @@ class TestToolSearchIntegration:
async def test_tool_search_toolset_discovered_tool_in_run_code(self) -> None:
"""End-to-end: once `search_tools` has discovered the deferred tool, it folds into `run_code`."""
from pydantic_ai.messages import ModelRequest, ToolSearchReturnPart
from pydantic_ai.toolsets._tool_search import ToolSearchToolset
from pydantic_ai.messages import ModelMessage, ModelRequest, ToolSearchReturnPart
from pydantic_ai.toolsets._tool_search import ToolSearchToolset, parse_discovered_tools
def later(x: int) -> str:
"""A deferred-loading tool."""
@@ -1815,23 +1815,25 @@ class TestToolSearchIntegration:
base = FunctionToolset[None](tools=[Tool(add), Tool(later, defer_loading=True)])
code_mode = CodeModeToolset(wrapped=ToolSearchToolset(wrapped=base), tool_selector='all')
messages: list[ModelMessage] = [
ModelRequest(
parts=[
ToolSearchReturnPart(
content={'discovered_tools': [{'name': 'later', 'description': 'A deferred-loading tool.'}]},
tool_call_id='search-1',
)
]
)
]
ctx = RunContext[None](
deps=None,
model=TestModel(),
usage=RunUsage(),
prompt=None,
messages=[
ModelRequest(
parts=[
ToolSearchReturnPart(
content={
'discovered_tools': [{'name': 'later', 'description': 'A deferred-loading tool.'}]
},
tool_call_id='search-1',
)
]
)
],
messages=messages,
# The agent graph reconstructs `discovered_tool_names` from history each step;
# mirror that here since the test drives `get_tools` without a real run.
discovered_tool_names=parse_discovered_tools(messages),
run_step=1,
)
tools = await code_mode.get_tools(ctx)
@@ -253,6 +253,78 @@ class TestDelegation:
assert 'parent_tool' in offered # the parent's tool is inherited by the sub-agent
assert 'delegate_task' not in offered # the delegate tool is filtered out, so no recursion
async def test_directly_registered_toolset_still_filters_delegate_tool(self) -> None:
"""`SubAgentToolset` used without the `SubAgents` capability must not recurse.
Registered directly in `Agent(toolsets=[...])` it is not wrapped in
`CapabilityOwnedToolset`, so only the name filter keeps `delegate_task`
out of inherited toolsets.
"""
offered: list[str] = []
def worker_fn(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
offered.extend(tool.name for tool in info.function_tools)
return ModelResponse(parts=[TextPart('sub done')])
worker = Agent(FunctionModel(worker_fn), name='worker')
toolset: SubAgentToolset[None] = SubAgentToolset(
agents={'worker': worker},
forward_usage=True,
inherit_tools=True,
shared_capabilities=[],
event_stream_handler=None,
tool_name='delegate_task',
limits={},
call_counts={},
)
parent: Agent[None, str] = Agent(_delegate_then_finish('worker'), toolsets=[toolset])
@parent.tool_plain
def parent_tool() -> str: # pyright: ignore[reportUnusedFunction]
return 'PT' # pragma: no cover - listed but not called in this test
result = await parent.run('go')
assert result.output == 'all done'
assert 'parent_tool' in offered # the parent's own tool is still inherited
assert 'delegate_task' not in offered # the delegate tool is filtered by name
async def test_inherit_tools_excludes_capability_contributed_tools(self) -> None:
"""Tools contributed by the parent's capabilities stay out of sub-agent runs.
They are bound to capability instances registered in the parent run; sharing
them is `shared_capabilities`' job (see the `_inherited_toolsets` docstring).
"""
from pydantic_ai.toolsets import FunctionToolset
@dataclass
class _ToolCapability(AbstractCapability[None]):
def get_toolset(self) -> Any:
def cap_tool() -> str:
return 'CT' # pragma: no cover - never offered to the sub-agent
return FunctionToolset[None](tools=[cap_tool])
offered: list[str] = []
def worker_fn(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
offered.extend(tool.name for tool in info.function_tools)
return ModelResponse(parts=[TextPart('sub done')])
worker = Agent(FunctionModel(worker_fn), name='worker')
parent: Agent[None, str] = Agent(
_delegate_then_finish('worker'),
capabilities=[SubAgents(agents={'worker': worker}, inherit_tools=True), _ToolCapability()],
)
@parent.tool_plain
def parent_tool() -> str: # pyright: ignore[reportUnusedFunction]
return 'PT' # pragma: no cover - listed but not called in this test
result = await parent.run('go')
assert result.output == 'all done'
assert 'parent_tool' in offered
assert 'cap_tool' not in offered
async def test_shared_capabilities_applied_to_subagent(self) -> None:
cap: _RecordingCapability[None] = _RecordingCapability()
worker = Agent(TestModel(custom_output_text='W'), name='worker')
@@ -62,11 +62,14 @@ def instructions_seen(result_messages: list[ModelMessage]) -> list[str]:
# make snapshots non-deterministic. `attributes` here is the literal key Logfire emits
# on the resolve span containing the serialized targeting attributes -- it shadows the
# enclosing span attributes dict by name, so the pop targets the inner one.
# `logfire.metrics` only appears on logfire versions newer than the extra's floor,
# so keeping it would make the snapshots depend on the resolved logfire version.
_VOLATILE_SPAN_ATTRIBUTES = (
'attributes',
'code.lineno',
'gen_ai.conversation.id',
'gen_ai.agent.call.id',
'logfire.metrics',
)
@@ -246,7 +249,7 @@ async def test_baggage_propagates_to_run_and_child_spans(capfire: CaptureLogfire
'gen_ai.provider.name': 'test',
'gen_ai.system': 'test',
'gen_ai.request.model': 'test',
'model_request_parameters': '{"function_tools":[{"name":"noop","parameters_json_schema":{"additionalProperties":false,"properties":{},"type":"object"},"description":null,"outer_typed_dict_key":null,"strict":null,"sequential":false,"kind":"function","metadata":null,"timeout":null,"defer_loading":false,"unless_native":null,"with_native":null,"tool_kind":null,"return_schema":null,"include_return_schema":null}],"native_tools":[],"output_mode":"text","output_object":null,"output_tools":[],"prompted_output_template":null,"allow_text_output":true,"allow_image_output":false,"instruction_parts":[{"content":"You are a helpful assistant.","dynamic":true,"part_kind":"instruction"}],"thinking":null}',
'model_request_parameters': '{"function_tools":[{"name":"noop","parameters_json_schema":{"additionalProperties":false,"properties":{},"type":"object"},"description":null,"outer_typed_dict_key":null,"strict":null,"sequential":false,"kind":"function","metadata":null,"timeout":null,"defer_loading":false,"unless_native":null,"with_native":null,"tool_kind":null,"return_schema":null,"include_return_schema":null,"capability_id":null}],"native_tools":[],"output_mode":"text","output_object":null,"output_tools":[],"prompted_output_template":null,"allow_text_output":true,"allow_image_output":false,"instruction_parts":[{"content":"You are a helpful assistant.","dynamic":true,"part_kind":"instruction"}],"thinking":null}',
'gen_ai.agent.name': 'agent',
'gen_ai.tool.definitions': '[{"type":"function","name":"noop","parameters":{"additionalProperties":false,"properties":{},"type":"object"}}]',
'logfire.span_type': 'span',
@@ -283,7 +286,7 @@ async def test_baggage_propagates_to_run_and_child_spans(capfire: CaptureLogfire
'gen_ai.provider.name': 'test',
'gen_ai.system': 'test',
'gen_ai.request.model': 'test',
'model_request_parameters': '{"function_tools":[{"name":"noop","parameters_json_schema":{"additionalProperties":false,"properties":{},"type":"object"},"description":null,"outer_typed_dict_key":null,"strict":null,"sequential":false,"kind":"function","metadata":null,"timeout":null,"defer_loading":false,"unless_native":null,"with_native":null,"tool_kind":null,"return_schema":null,"include_return_schema":null}],"native_tools":[],"output_mode":"text","output_object":null,"output_tools":[],"prompted_output_template":null,"allow_text_output":true,"allow_image_output":false,"instruction_parts":[{"content":"You are a helpful assistant.","dynamic":true,"part_kind":"instruction"}],"thinking":null}',
'model_request_parameters': '{"function_tools":[{"name":"noop","parameters_json_schema":{"additionalProperties":false,"properties":{},"type":"object"},"description":null,"outer_typed_dict_key":null,"strict":null,"sequential":false,"kind":"function","metadata":null,"timeout":null,"defer_loading":false,"unless_native":null,"with_native":null,"tool_kind":null,"return_schema":null,"include_return_schema":null,"capability_id":null}],"native_tools":[],"output_mode":"text","output_object":null,"output_tools":[],"prompted_output_template":null,"allow_text_output":true,"allow_image_output":false,"instruction_parts":[{"content":"You are a helpful assistant.","dynamic":true,"part_kind":"instruction"}],"thinking":null}',
'gen_ai.agent.name': 'agent',
'gen_ai.tool.definitions': '[{"type":"function","name":"noop","parameters":{"additionalProperties":false,"properties":{},"type":"object"}}]',
'logfire.span_type': 'span',
@@ -314,7 +317,6 @@ async def test_baggage_propagates_to_run_and_child_spans(capfire: CaptureLogfire
'pydantic_ai.all_messages': '[{"role":"user","parts":[{"type":"text","content":"hello"}]},{"role":"assistant","parts":[{"type":"tool_call","id":"pyd_ai_tool_call_id__noop","name":"noop","arguments":{}}]},{"role":"user","parts":[{"type":"tool_call_response","id":"pyd_ai_tool_call_id__noop","name":"noop","result":"ok"}]},{"role":"assistant","parts":[{"type":"text","content":"{\\"noop\\":\\"ok\\"}"}]}]',
'gen_ai.system_instructions': '[{"type": "text", "content": "You are a helpful assistant."}]',
'logfire.json_schema': '{"type":"object","properties":{"pydantic_ai.all_messages":{"type":"array"},"gen_ai.system_instructions":{"type":"array"},"final_result":{"type":"object"}}}',
'logfire.metrics': '{"gen_ai.client.token.usage": {"details": [{"attributes": {"gen_ai.operation.name": "chat", "gen_ai.provider.name": "test", "gen_ai.request.model": "test", "gen_ai.response.model": "test", "gen_ai.system": "test", "gen_ai.token.type": "input"}, "total": 103}, {"attributes": {"gen_ai.operation.name": "chat", "gen_ai.provider.name": "test", "gen_ai.request.model": "test", "gen_ai.response.model": "test", "gen_ai.system": "test", "gen_ai.token.type": "output"}, "total": 8}], "total": 111}}',
},
},
]
Generated
+57 -12
View File
@@ -7,6 +7,14 @@ resolution-markers = [
"python_full_version < '3.13'",
]
[options]
[options.exclude-newer-package]
pydantic-ai-slim = false
pydantic-graph = false
pydantic-ai = false
pydantic-evals = false
[[package]]
name = "annotated-doc"
version = "0.0.4"
@@ -431,15 +439,15 @@ wheels = [
[[package]]
name = "genai-prices"
version = "0.0.59"
version = "0.0.66"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "httpx2" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cd/c8/b61a028b8d8ee286ffab3f9b9f1c9229087184e7d543cea4e349e11375b0/genai_prices-0.0.59.tar.gz", hash = "sha256:3e1c7dcd9b38163589c8cf4a9bcfd286c52ea57a3becdc062a2cbaa8295b08c4", size = 67406, upload-time = "2026-05-07T12:08:40.475Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0e/7c/c50fc8d18b283e9b56ff625d45dbe9577c676e1d16636c1011967182d813/genai_prices-0.0.66.tar.gz", hash = "sha256:f087dfe56da28a4c3933dcf846cf2b7111ba733cef674c0cbc66de80212bcd6b", size = 71130, upload-time = "2026-06-09T21:51:14.561Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/f9/4693c127f9fab0a8d39c47c198e378ecafcb043463e6dd73df205eacbc13/genai_prices-0.0.59-py3-none-any.whl", hash = "sha256:88fd8818e6807374e5a5c03f293b574ade5f18a3060622080cdd94a03cf43115", size = 70509, upload-time = "2026-05-07T12:08:39.075Z" },
{ url = "https://files.pythonhosted.org/packages/5c/87/1a36166f91906e47f430f6d4150ec6bfc0001032a363e49fc389021c0609/genai_prices-0.0.66-py3-none-any.whl", hash = "sha256:86b83f107c1cf04bb449a120cd8d4439ceb6843660d9128cde560eb511686d7b", size = 73745, upload-time = "2026-06-09T21:51:13.523Z" },
]
[[package]]
@@ -539,6 +547,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpcore2"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/34/18f1c596e677962f040284246f393b10a1f8ce440b3a7e69c637d0f1c7ad/httpcore2-2.3.0.tar.gz", hash = "sha256:07327e251560960eea8e969d92d4c6a325feb13cca39e25340731336c3baf924", size = 64300, upload-time = "2026-06-01T13:15:02.998Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl", hash = "sha256:477e9e334f74e5240dcac002e890580f36a57d40ff0fb14cc9655731d23b8415", size = 80024, upload-time = "2026-06-01T13:15:00.001Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
@@ -554,6 +575,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "httpx2"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "httpcore2" },
{ name = "idna" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/9a/cca0b9145f13d8ae34b885ae28d403a1469a433abc78e0f94f4ce94e650b/httpx2-2.3.0.tar.gz", hash = "sha256:227e7c41d95a76d4077a52640564132777215fc3394e07b66a3116c33d668fa9", size = 81115, upload-time = "2026-06-01T13:15:04.324Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl", hash = "sha256:6f393663bdf6dbe7fe90118e3eb5b2bd024a675cae0390ac08cec9198812d8b7", size = 74538, upload-time = "2026-06-01T13:15:01.566Z" },
]
[[package]]
name = "idna"
version = "3.15"
@@ -1023,9 +1059,9 @@ lint = [
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "logfire", marker = "extra == 'logfire'", specifier = ">=4.31.0" },
{ name = "pydantic-ai-slim", specifier = ">=1.95.1" },
{ name = "pydantic-ai-slim", specifier = ">=1.105.0" },
{ name = "pydantic-ai-slim", extras = ["dbos"], marker = "extra == 'dbos'" },
{ name = "pydantic-ai-slim", extras = ["spec"], marker = "extra == 'logfire'", specifier = ">=1.95.1" },
{ name = "pydantic-ai-slim", extras = ["spec"], marker = "extra == 'logfire'", specifier = ">=1.105.0" },
{ name = "pydantic-ai-slim", extras = ["temporal"], marker = "extra == 'temporal'" },
{ name = "pydantic-monty", marker = "extra == 'code-mode'", specifier = ">=0.0.16" },
{ name = "pydantic-monty", marker = "extra == 'codemode'", specifier = ">=0.0.16" },
@@ -1053,7 +1089,7 @@ lint = [
[[package]]
name = "pydantic-ai-slim"
version = "1.95.1"
version = "1.107.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
@@ -1065,9 +1101,9 @@ dependencies = [
{ name = "pydantic-graph" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/b0/43a3adca6d596eae78712eb4d7c82a094441997578bcee9c149174371b6c/pydantic_ai_slim-1.95.1.tar.gz", hash = "sha256:126c9925c340eb27d4bf8655019f157026be39fbb52b86a4f0663636e0af48ba", size = 696384, upload-time = "2026-05-13T18:58:16.751Z" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/26/ced63dfaabbc77f3beb86d59689cdea748e7ccffb6b419dbaf4780f211e8/pydantic_ai_slim-1.107.0.tar.gz", hash = "sha256:4616f689a92fcfecfecf2a7af27aca22f139a873cf6d7a8929eaeee9c0eedbb4", size = 779902, upload-time = "2026-06-10T14:53:10.574Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/f9/b433442c2f1f97addc994bf8cf27c76ad02da006501cdeeec4b6b2554e09/pydantic_ai_slim-1.95.1-py3-none-any.whl", hash = "sha256:1a6d57c56881bd7140e8c9b9b3d668d373482e06c3b134246fa906203b223e04", size = 867298, upload-time = "2026-05-13T18:58:07.91Z" },
{ url = "https://files.pythonhosted.org/packages/15/57/71044e17f931b08cc3930bc0fe5a1e1fd37fa474ae826be004729ef1cb4a/pydantic_ai_slim-1.107.0-py3-none-any.whl", hash = "sha256:1af49bbae06a6c598f72c54d4734ba377100cac493c9a05fa8e089bebeae0da6", size = 964046, upload-time = "2026-06-10T14:53:03.333Z" },
]
[package.optional-dependencies]
@@ -1200,7 +1236,7 @@ wheels = [
[[package]]
name = "pydantic-graph"
version = "1.95.1"
version = "1.107.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -1208,9 +1244,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9b/3e/bf0fdcb6f8cf3f099d5eabc189795e856eb09b8dff2a57e6428b1c172c0f/pydantic_graph-1.95.1.tar.gz", hash = "sha256:1d370ea634623b28c97edbe17ac8b16f9b5724bbffa8a878caf5b4d5ef25613c", size = 59247, upload-time = "2026-05-13T18:58:19.442Z" }
sdist = { url = "https://files.pythonhosted.org/packages/dd/c3/6e8c2d13b8701041f1b3eac5deb41f25d4dbfa479a190d5c6becc23f2a49/pydantic_graph-1.107.0.tar.gz", hash = "sha256:278dd89b3e33f3a2963ac949f27a53aef705c5d883a8ce5d06d23e6e3cfbd972", size = 62564, upload-time = "2026-06-10T14:53:13.366Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/61/1f91e2797b7667c2ef70657fcb8b8a517890269a413a5cdc2d9a06dce4c7/pydantic_graph-1.95.1-py3-none-any.whl", hash = "sha256:612efc7e3458f12fbc44f7d484e166419883b3567e3005e48283899519423938", size = 73049, upload-time = "2026-05-13T18:58:11.728Z" },
{ url = "https://files.pythonhosted.org/packages/fc/72/621556e3f5068400d43a0375d38e5963de30256eaa5a702aba12e82ed0ff/pydantic_graph-1.107.0-py3-none-any.whl", hash = "sha256:71add94fe7e14c703977a895117c475aae6c0b02a774a036c4d00d9a63c78b00", size = 80106, upload-time = "2026-06-10T14:53:06.543Z" },
]
[[package]]
@@ -1732,6 +1768,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/93/dab25dc87ac48da0fe0f6419e07d0bfd98799bed4e05e7b9e0f85a1a4b4b/trio-0.33.0-py3-none-any.whl", hash = "sha256:3bd5d87f781d9b0192d592aef28691f8951d6c2e41b7e1da4c25cde6c180ae9b", size = 510294, upload-time = "2026-02-14T18:40:53.313Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "typer"
version = "0.25.1"