mirror of
https://github.com/pydantic/pydantic-ai-harness.git
synced 2026-07-21 02:45:34 +00:00
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:
co-authored by
Claude Fable 5
parent
e34c30ae54
commit
05ae737182
@@ -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
@@ -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]
|
||||
|
||||
@@ -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}}',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user