mirror of
https://github.com/pydantic/pydantic-ai-harness.git
synced 2026-07-21 02:45:34 +00:00
refactor: graduate capabilities out of experimental (ACP excepted) (#347)
* refactor: graduate capabilities out of `experimental` (ACP excepted) The `experimental` label suppressed the try->fail->report->improve loop we want from real usage, so drop it from the harness. Ten capabilities move to top-level submodules; ACP stays experimental (it may still be reshaped or moved to core, and can't be generic across agents). - Old `pydantic_ai_harness.experimental.<name>` paths keep working as DeprecationWarning shims (via `warn_moved`), so existing imports don't break. - Renames to match capability names: authoring -> runtime_authoring, overflow -> overflowing_tool_output. - Capabilities stay in individual submodules with no top-level re-export, so importing the root package never pulls in a capability's optional deps. - Docs drop the experimental framing and the "may be removed in any release" language in favor of "API subject to change". * docs: tell capability authors to ask the user when unsure about a name Per PR review: a name is a public commitment once shipped, so ask rather than guess.
This commit is contained in:
@@ -44,9 +44,12 @@ Before implementing or reviewing a capability change:
|
||||
signatures when needed. Do not assume a contributor's local checkout layout.
|
||||
5. Use `pydantic_ai_harness.code_mode` as the exemplar for capability shape,
|
||||
docs, tests, and public exports until another capability becomes a better
|
||||
example. `code_mode` is a released top-level capability; new capabilities
|
||||
start under `pydantic_ai_harness.experimental` (see
|
||||
`agent_docs/capability-authoring.md`, "Experimental Vs Released Exports").
|
||||
example. Capabilities live in their own top-level submodule
|
||||
`pydantic_ai_harness/<name>/` (module name = capability name; one module per
|
||||
capability or strategy) and are not re-exported from the root `__init__.py`,
|
||||
so each keeps its own optional dependencies. The `experimental` tier is
|
||||
retired; ACP is the sole remaining experimental capability (see
|
||||
`agent_docs/capability-authoring.md`, "Capability Submodules And Exports").
|
||||
|
||||
## Capabilities API reference
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ With it, the model writes one Python script in which each sub-agent is an async
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import DynamicWorkflow
|
||||
from pydantic_ai_harness.dynamic_workflow import DynamicWorkflow
|
||||
|
||||
reviewer = Agent('anthropic:claude-sonnet-4-6', name='reviewer', description='Reviews code for bugs.')
|
||||
summarizer = Agent('anthropic:claude-sonnet-4-6', name='summarizer', description='Summarizes findings.')
|
||||
@@ -132,9 +132,9 @@ It composes with the rest of the harness:
|
||||
- **Budgets**: `max_agent_calls` is an exact, host-enforced ceiling on sub-agent runs (it holds even under concurrent fan-out), and by default the whole tree's token spend lands on the parent run's `usage`.
|
||||
- **On-demand**: `defer_loading=True` keeps the catalog out of the prompt until the model loads the capability, and `reveal()` adds a sub-agent mid-run without disturbing the prompt cache.
|
||||
|
||||
`DynamicWorkflow` ships under `experimental` while planned extensions (structured sub-agent inputs, durable workflows) settle the call contract; importing it emits a `HarnessExperimentalWarning`.
|
||||
`DynamicWorkflow`'s API is subject to change while planned extensions (structured sub-agent inputs, durable workflows) settle the call contract. Breaking changes ship deprecation warnings where practical.
|
||||
|
||||
[Full tutorial →](pydantic_ai_harness/experimental/dynamic_workflow/)
|
||||
[Full tutorial →](pydantic_ai_harness/dynamic_workflow/)
|
||||
|
||||
## Capability matrix
|
||||
|
||||
@@ -148,25 +148,25 @@ We studied leading coding agents, agent frameworks, and Claw-style assistants to
|
||||
| | **Tool search** | Progressive tool discovery for large tool sets | :white_check_mark: [Pydantic AI](https://pydantic.dev/docs/ai/tools-toolsets/toolsets/#deferred-loading) | |
|
||||
| | **File system** | Read, write, edit, search files with path traversal prevention | :white_check_mark: [Docs](pydantic_ai_harness/filesystem/) | [pydantic-ai-backend](https://github.com/vstorm-co/pydantic-ai-backend) (vstorm‑co) |
|
||||
| | **Shell** | Execute commands with allowlists, denylists, and timeouts | :white_check_mark: [Docs](pydantic_ai_harness/shell/) | [pydantic-ai-backend](https://github.com/vstorm-co/pydantic-ai-backend) (vstorm‑co) |
|
||||
| | **Repo context injection** | Auto-load CLAUDE.md/AGENTS.md and repo structure | :white_check_mark: [Docs](pydantic_ai_harness/experimental/context/) (experimental) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Docs lookup** | On-demand `read_pyai_docs` tool for Pydantic AI docs | :white_check_mark: [Docs](pydantic_ai_harness/experimental/docs/) (experimental) | |
|
||||
| | **Repo context injection** | Auto-load CLAUDE.md/AGENTS.md and repo structure | :white_check_mark: [Docs](pydantic_ai_harness/context/) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Docs lookup** | On-demand `read_pyai_docs` tool for Pydantic AI docs | :white_check_mark: [Docs](pydantic_ai_harness/docs/) | |
|
||||
| | **Verification loop** | Run tests after edits, auto-fix failures | :construction: [PR #169](https://github.com/pydantic/pydantic-ai-harness/pull/169) | |
|
||||
| **Editor integration** | **ACP** | Serve an agent to editors (Zed, etc.) over the [Agent Client Protocol](https://agentclientprotocol.com) -- streamed text, diff-rendered edits, tool approval | :white_check_mark: [Docs](pydantic_ai_harness/experimental/acp/) (experimental) | |
|
||||
| **Prompt management** | **Managed prompt** | Back an agent's instructions with a [Logfire](https://pydantic.dev/logfire)-managed prompt, editable without shipping code | :white_check_mark: [Docs](pydantic_ai_harness/logfire/) | |
|
||||
| **Context management** | **Sliding window** | Trim conversation history to stay within token limits | :white_check_mark: [Docs](pydantic_ai_harness/experimental/compaction/) (experimental) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Context compaction** | LLM-powered summarization of older messages | :white_check_mark: [Docs](pydantic_ai_harness/experimental/compaction/) (experimental) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Limit warnings** | Warn agent before hitting context/iteration limits | :white_check_mark: [Docs](pydantic_ai_harness/experimental/compaction/) (experimental) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Tool output management** | Truncate, summarize, or spill large tool outputs | :white_check_mark: [Docs](pydantic_ai_harness/experimental/overflow/) (experimental) | |
|
||||
| **Context management** | **Sliding window** | Trim conversation history to stay within token limits | :white_check_mark: [Docs](pydantic_ai_harness/compaction/) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Context compaction** | LLM-powered summarization of older messages | :white_check_mark: [Docs](pydantic_ai_harness/compaction/) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Limit warnings** | Warn agent before hitting context/iteration limits | :white_check_mark: [Docs](pydantic_ai_harness/compaction/) | [summarization-pydantic-ai](https://github.com/vstorm-co/summarization-pydantic-ai) (vstorm‑co) |
|
||||
| | **Tool output management** | Truncate, summarize, or spill large tool outputs | :white_check_mark: [Docs](pydantic_ai_harness/overflowing_tool_output/) | |
|
||||
| | **System reminders** | Inject periodic reminders to counteract instruction drift | :construction: [PR #181](https://github.com/pydantic/pydantic-ai-harness/pull/181) | |
|
||||
| **Memory & persistence** | **Memory** | Persistent key-value memory across sessions | :construction: [PR #179](https://github.com/pydantic/pydantic-ai-harness/pull/179) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Session persistence** | Save and restore full conversation state | :white_check_mark: [Docs](pydantic_ai_harness/experimental/step_persistence/) (experimental) | |
|
||||
| | **Checkpointing** | Snapshot, resume (`continue_run`), and fork (`fork_run`) a run | :white_check_mark: [Docs](pydantic_ai_harness/experimental/step_persistence/) (experimental) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Media externalization** | Offload large `BinaryContent` to content-addressed stores (building blocks) | :white_check_mark: [Docs](pydantic_ai_harness/experimental/media/) (experimental) | |
|
||||
| **Agent orchestration** | **Sub-agents** | Delegate subtasks to specialized child agents | :white_check_mark: [Docs](pydantic_ai_harness/experimental/subagents/) (experimental) | [subagents-pydantic-ai](https://github.com/vstorm-co/subagents-pydantic-ai) (vstorm‑co) |
|
||||
| | **Dynamic workflow** | Orchestrate sub-agents from a model-written Python script -- fan-out, chaining, voting in one tool call | :white_check_mark: [Docs](pydantic_ai_harness/experimental/dynamic_workflow/) (experimental) | |
|
||||
| | **Session persistence** | Save and restore full conversation state | :white_check_mark: [Docs](pydantic_ai_harness/step_persistence/) | |
|
||||
| | **Checkpointing** | Snapshot, resume (`continue_run`), and fork (`fork_run`) a run | :white_check_mark: [Docs](pydantic_ai_harness/step_persistence/) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Media externalization** | Offload large `BinaryContent` to content-addressed stores (building blocks) | :white_check_mark: [Docs](pydantic_ai_harness/media/) | |
|
||||
| **Agent orchestration** | **Sub-agents** | Delegate subtasks to specialized child agents | :white_check_mark: [Docs](pydantic_ai_harness/subagents/) | [subagents-pydantic-ai](https://github.com/vstorm-co/subagents-pydantic-ai) (vstorm‑co) |
|
||||
| | **Dynamic workflow** | Orchestrate sub-agents from a model-written Python script -- fan-out, chaining, voting in one tool call | :white_check_mark: [Docs](pydantic_ai_harness/dynamic_workflow/) | |
|
||||
| | **Skills** | Progressive tool loading -- search, activate, deactivate | :construction: [PR #183](https://github.com/pydantic/pydantic-ai-harness/pull/183) | [pydantic-ai-skills](https://github.com/DougTrajano/pydantic-ai-skills) (DougTrajano), [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| | **Planning** | Break complex tasks into structured plans before execution | :white_check_mark: [Docs](pydantic_ai_harness/experimental/planning/) (experimental) | |
|
||||
| | **Runtime authoring** | Let an agent author, validate, and load real capabilities at runtime | :white_check_mark: [Docs](pydantic_ai_harness/experimental/authoring/) (experimental) | |
|
||||
| | **Planning** | Break complex tasks into structured plans before execution | :white_check_mark: [Docs](pydantic_ai_harness/planning/) | |
|
||||
| | **Runtime authoring** | Let an agent author, validate, and load real capabilities at runtime | :white_check_mark: [Docs](pydantic_ai_harness/runtime_authoring/) | |
|
||||
| | **Task tracking** | Track tasks, subtasks, and dependencies | :memo: [#65](https://github.com/pydantic/pydantic-ai-harness/issues/65) | [pydantic-ai-todo](https://github.com/vstorm-co/pydantic-ai-todo) (vstorm‑co) |
|
||||
| | **Teams** | Multi-agent teams with shared state and message bus | :memo: [#195](https://github.com/pydantic/pydantic-ai-harness/issues/195) | [pydantic-deep](https://github.com/vstorm-co/pydantic-deepagents) (vstorm‑co) |
|
||||
| **Safety & guardrails** | **Input guardrails** | Validate user input before the agent run starts | :white_check_mark: [Docs](pydantic_ai_harness/guardrails/) | [pydantic-ai-shields](https://github.com/vstorm-co/pydantic-ai-shields) (vstorm‑co) |
|
||||
|
||||
@@ -31,21 +31,35 @@ Each capability package should normally have:
|
||||
The root `pydantic_ai_harness/__init__.py` should re-export stable public
|
||||
capabilities. Keep implementation helpers private unless users need them.
|
||||
|
||||
### Experimental Vs Released Exports
|
||||
### Capability Submodules And Exports
|
||||
|
||||
New capabilities land under `pydantic_ai_harness/experimental/`. Each
|
||||
experimental package calls `warn_experimental('<name>')` in its `__init__.py` so
|
||||
importing it emits `HarnessExperimentalWarning`. Anything under `experimental`
|
||||
may change or be removed in any release, without a deprecation period.
|
||||
The `experimental` tier is retired. ACP is the sole remaining experimental
|
||||
capability (`pydantic_ai_harness/experimental/acp/`); do not add new capabilities
|
||||
there.
|
||||
|
||||
Promote a capability to a top-level package and a top-level re-export in
|
||||
`pydantic_ai_harness/__init__.py` only when its API is stable.
|
||||
New capabilities land as a top-level submodule `pydantic_ai_harness/<name>/`.
|
||||
They are not re-exported from the root `pydantic_ai_harness/__init__.py`: each
|
||||
capability keeps its own optional dependencies, so importing the root package
|
||||
must not pull in a capability's extras. Users import a capability from its
|
||||
submodule (`from pydantic_ai_harness.<name> import ...`).
|
||||
|
||||
Top-level exports in `pydantic_ai_harness/__init__.py` are the intended public
|
||||
surface. Once an export has shipped in a published release it is a
|
||||
backward-compatibility commitment: do not move, rename, or break it. (`CodeMode`
|
||||
is a shipped, released example.) Do not relocate an already-top-level capability
|
||||
into `experimental`.
|
||||
Naming: the module name is the capability name, one module per capability or
|
||||
strategy. Prefer a longer descriptive name over a terse one (e.g.
|
||||
`overflowing_tool_output`, not `overflow`). A known term is fine as-is (e.g.
|
||||
`compaction`). If you are unsure what to name a capability, ask the user (via the
|
||||
ask-user tool) rather than guessing -- a name is a public commitment once shipped.
|
||||
|
||||
When a capability's module path changes, keep the old path working as a
|
||||
`DeprecationWarning` shim so existing imports do not break.
|
||||
|
||||
Top-level re-exports in `pydantic_ai_harness/__init__.py` (`CodeMode`,
|
||||
`FileSystem`, `Shell`, `ManagedPrompt`) are the exception, not the rule. Once an
|
||||
export has shipped in a published release it is a backward-compatibility
|
||||
commitment: do not move, rename, or break it. Do not add new top-level
|
||||
re-exports.
|
||||
|
||||
APIs are subject to change between releases; breaking changes ship deprecation
|
||||
warnings where practical.
|
||||
|
||||
## API Design
|
||||
|
||||
|
||||
@@ -36,18 +36,33 @@ Do **not** use this skill for:
|
||||
`CodeMode` has a full reference below; it is the flagship capability and the one this skill goes deep on.
|
||||
The rest ship today and each has its own README with API and examples.
|
||||
|
||||
**Stable** (imported from `pydantic_ai_harness`):
|
||||
Each capability lives in its own submodule and is imported from there
|
||||
(`from pydantic_ai_harness.<module> import ...`). Capabilities are not importable from the top-level
|
||||
`pydantic_ai_harness` package by design, so each one keeps its own optional dependencies isolated.
|
||||
`CodeMode`, `FileSystem`, `Shell`, and `ManagedPrompt` also have top-level re-exports (importable directly
|
||||
from `pydantic_ai_harness`).
|
||||
|
||||
| Capability | Description | Reference |
|
||||
APIs are subject to change between releases; breaking changes ship deprecation warnings where practical.
|
||||
|
||||
| Capability | Module | Description |
|
||||
|---|---|---|
|
||||
| `CodeMode` | Wraps eligible tools into a single sandboxed `run_code` tool so the model orchestrates them in Python | [Code Mode](./references/CODE-MODE.md) |
|
||||
| `FileSystem` | Read, write, edit, and search files under a root directory, with traversal prevention | [README](https://github.com/pydantic/pydantic-ai-harness/tree/main/pydantic_ai_harness/filesystem) |
|
||||
| `Shell` | Run commands in a subprocess with allowlists, a default denylist, timeouts, and env masking | [README](https://github.com/pydantic/pydantic-ai-harness/tree/main/pydantic_ai_harness/shell) |
|
||||
| `ManagedPrompt` | Back an agent's instructions with a Logfire-managed prompt | [README](https://github.com/pydantic/pydantic-ai-harness/tree/main/pydantic_ai_harness/logfire) |
|
||||
| `CodeMode` | `pydantic_ai_harness.code_mode` (also top-level) | Wraps eligible tools into a single sandboxed `run_code` tool so the model orchestrates them in Python -- see [Code Mode](./references/CODE-MODE.md) |
|
||||
| `FileSystem` | `pydantic_ai_harness.filesystem` (also top-level) | Read, write, edit, and search files under a root directory, with traversal prevention |
|
||||
| `Shell` | `pydantic_ai_harness.shell` (also top-level) | Run commands in a subprocess with allowlists, a default denylist, timeouts, and env masking |
|
||||
| `ManagedPrompt` | `pydantic_ai_harness.logfire` (also top-level) | Back an agent's instructions with a Logfire-managed prompt |
|
||||
| `SubAgents` | `pydantic_ai_harness.subagents` | Delegate subtasks to specialized child agents |
|
||||
| `DynamicWorkflow` | `pydantic_ai_harness.dynamic_workflow` | Orchestrate sub-agents from a model-written Python script |
|
||||
| `Planning` | `pydantic_ai_harness.planning` | Break complex tasks into structured plans before execution |
|
||||
| compaction family (`SlidingWindow`, `SummarizingCompaction`, ...) | `pydantic_ai_harness.compaction` | Trim or summarize conversation history to stay within token limits |
|
||||
| `OverflowingToolOutput` | `pydantic_ai_harness.overflowing_tool_output` | Truncate, summarize, or spill large tool outputs |
|
||||
| `RepoContext` | `pydantic_ai_harness.context` | Auto-load CLAUDE.md/AGENTS.md and repo structure |
|
||||
| `StepPersistence` | `pydantic_ai_harness.step_persistence` | Save, restore, resume, and fork run state |
|
||||
| `PyaiDocs` | `pydantic_ai_harness.docs` | On-demand `read_pyai_docs` tool for Pydantic AI docs |
|
||||
| `RuntimeAuthoring` | `pydantic_ai_harness.runtime_authoring` | Let an agent author, validate, and load real capabilities at runtime |
|
||||
| media externalization | `pydantic_ai_harness.media` | Offload large `BinaryContent` to content-addressed stores |
|
||||
|
||||
**Experimental** (imported from `pydantic_ai_harness.experimental`, may change in any release): `SubAgents`,
|
||||
`Planning`, a compaction family (`SlidingWindow`, `SummarizingCompaction`, ...), `OverflowingToolOutput`,
|
||||
`RepoContext`, `StepPersistence`, `PyaiDocs`, `RuntimeAuthoring`, and an ACP server adapter.
|
||||
Still experimental: an ACP server adapter, imported from `pydantic_ai_harness.experimental.acp`. Importing it
|
||||
emits a `HarnessExperimentalWarning`.
|
||||
|
||||
The full, current list with links and status is in the
|
||||
[capability matrix](https://github.com/pydantic/pydantic-ai-harness#capability-matrix).
|
||||
|
||||
+7
-18
@@ -1,23 +1,13 @@
|
||||
# Compaction capabilities
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** These capabilities live under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import them from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import these capabilities from their submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.compaction import TieredCompaction
|
||||
> from pydantic_ai_harness.compaction import TieredCompaction
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
A menu of strategies for keeping an agent's conversation history within a model's context
|
||||
window. Each is a Pydantic AI `Capability` that runs in the `before_model_request` hook; edits
|
||||
@@ -62,7 +52,7 @@ low-entropy repetition, so a head/tail slice loses little.
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import ClampOversizedMessages
|
||||
from pydantic_ai_harness.compaction import ClampOversizedMessages
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
@@ -89,7 +79,7 @@ user input should not be silently rewritten, and oversized tool returns are the
|
||||
Use it as the first tier of `TieredCompaction`, before `ClearToolResults`:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.compaction import (
|
||||
from pydantic_ai_harness.compaction import (
|
||||
ClampOversizedMessages,
|
||||
ClearToolResults,
|
||||
TieredCompaction,
|
||||
@@ -113,7 +103,7 @@ that is not enough -- which is exactly what `TieredCompaction` encodes:
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import (
|
||||
from pydantic_ai_harness.compaction import (
|
||||
ClearToolResults,
|
||||
DeduplicateFileReads,
|
||||
SummarizingCompaction,
|
||||
@@ -168,7 +158,6 @@ call is not a file read:
|
||||
```python
|
||||
from pydantic_ai.messages import ToolCallPart
|
||||
|
||||
|
||||
def my_file_key(call: ToolCallPart) -> str | None:
|
||||
if call.tool_name != 'read_file':
|
||||
return None
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Compaction capabilities: keep an agent's conversation history within the context window.
|
||||
|
||||
Each capability lives in its own module; shared utilities (token estimation, the
|
||||
`CompactionStrategy` protocol, tool-pair-safe cutoffs, in-place clearing) live in `_shared`.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.compaction._clamp_oversized_messages import ClampOversizedMessages
|
||||
from pydantic_ai_harness.compaction._clear_tool_results import ClearToolResults
|
||||
from pydantic_ai_harness.compaction._deduplicate_file_reads import DeduplicateFileReads
|
||||
from pydantic_ai_harness.compaction._limit_warner import LimitWarner, WarningKind
|
||||
from pydantic_ai_harness.compaction._shared import CompactionStrategy, estimate_token_count
|
||||
from pydantic_ai_harness.compaction._sliding_window import SlidingWindow
|
||||
from pydantic_ai_harness.compaction._summarizing_compaction import SummarizingCompaction
|
||||
from pydantic_ai_harness.compaction._tiered_compaction import TieredCompaction
|
||||
|
||||
__all__ = [
|
||||
'ClampOversizedMessages',
|
||||
'ClearToolResults',
|
||||
'CompactionStrategy',
|
||||
'DeduplicateFileReads',
|
||||
'LimitWarner',
|
||||
'SlidingWindow',
|
||||
'SummarizingCompaction',
|
||||
'TieredCompaction',
|
||||
'WarningKind',
|
||||
'estimate_token_count',
|
||||
]
|
||||
+2
-3
@@ -17,12 +17,11 @@ from pydantic_ai.messages import (
|
||||
)
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import compact_with_span, estimate_text_tokens
|
||||
from pydantic_ai_harness.compaction._shared import compact_with_span, estimate_text_tokens
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai.models import ModelRequestContext
|
||||
|
||||
|
||||
_CLAMP_MARKER = '\n[clamped: removed {removed} of {original} characters]\n'
|
||||
"""Inserted between the head and tail slices of a clamped part. ``{removed}`` and ``{original}``
|
||||
are filled with character counts."""
|
||||
@@ -71,7 +70,7 @@ class ClampOversizedMessages(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import ClampOversizedMessages
|
||||
from pydantic_ai_harness.compaction import ClampOversizedMessages
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
+2
-2
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.messages import ModelMessage
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
compact_with_span,
|
||||
estimate_token_count,
|
||||
exceeds,
|
||||
@@ -43,7 +43,7 @@ class ClearToolResults(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import ClearToolResults
|
||||
from pydantic_ai_harness.compaction import ClearToolResults
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
+2
-4
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.messages import ModelMessage, ToolCallPart
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
compact_with_span,
|
||||
exceeds,
|
||||
iter_tool_pairs,
|
||||
@@ -39,8 +39,7 @@ class DeduplicateFileReads(AbstractCapability[AgentDepsT]):
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai.messages import ToolCallPart
|
||||
from pydantic_ai_harness.experimental.compaction import DeduplicateFileReads
|
||||
|
||||
from pydantic_ai_harness.compaction import DeduplicateFileReads
|
||||
|
||||
def file_key(call: ToolCallPart) -> str | None:
|
||||
if call.tool_name != 'read_file':
|
||||
@@ -48,7 +47,6 @@ class DeduplicateFileReads(AbstractCapability[AgentDepsT]):
|
||||
args = call.args_as_dict()
|
||||
return args.get('path')
|
||||
|
||||
|
||||
agent = Agent('openai:gpt-4o', capabilities=[DeduplicateFileReads(file_key=file_key)])
|
||||
```
|
||||
"""
|
||||
+2
-2
@@ -10,7 +10,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.messages import ModelMessage, ModelRequest, SystemPromptPart, UserPromptPart
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import estimate_token_count
|
||||
from pydantic_ai_harness.compaction._shared import estimate_token_count
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai.models import ModelRequestContext
|
||||
@@ -43,7 +43,7 @@ class LimitWarner(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import LimitWarner
|
||||
from pydantic_ai_harness.compaction import LimitWarner
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
-1
@@ -331,7 +331,6 @@ def prepend_first_user_message(
|
||||
# Tool-pair inspection and in-place clearing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
_CLEARED_TOOL_ARGS = '{}'
|
||||
"""Replacement for cleared tool-call arguments.
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.messages import ModelMessage
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
compact_with_span,
|
||||
exceeds,
|
||||
find_safe_cutoff,
|
||||
@@ -37,7 +37,7 @@ class SlidingWindow(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import SlidingWindow
|
||||
from pydantic_ai_harness.compaction import SlidingWindow
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
+2
-2
@@ -20,7 +20,7 @@ from pydantic_ai.messages import (
|
||||
)
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
compact_with_span,
|
||||
exceeds,
|
||||
find_first_user_message,
|
||||
@@ -151,7 +151,7 @@ class SummarizingCompaction(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import SummarizingCompaction
|
||||
from pydantic_ai_harness.compaction import SummarizingCompaction
|
||||
|
||||
agent = Agent(
|
||||
'openai:gpt-4o',
|
||||
+2
-2
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.messages import ModelMessage
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
CompactionStrategy,
|
||||
compact_with_span,
|
||||
estimate_token_count,
|
||||
@@ -36,7 +36,7 @@ class TieredCompaction(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.compaction import (
|
||||
from pydantic_ai_harness.compaction import (
|
||||
ClearToolResults,
|
||||
SummarizingCompaction,
|
||||
TieredCompaction,
|
||||
+5
-15
@@ -1,23 +1,13 @@
|
||||
# RepoContext
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.context import RepoContext
|
||||
> from pydantic_ai_harness.context import RepoContext
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Discover and load a repo's accumulated coding-assistant context engineering (CE).
|
||||
|
||||
@@ -37,7 +27,7 @@ idea the rest of the setup exists, so it can neither honor it nor translate it.
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.context import RepoContext
|
||||
from pydantic_ai_harness.context import RepoContext
|
||||
|
||||
agent = Agent(
|
||||
'anthropic:claude-sonnet-4-6',
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Context capability: discover and load a repo's accumulated context engineering."""
|
||||
|
||||
from pydantic_ai_harness.context._capability import RepoContext
|
||||
from pydantic_ai_harness.context._inventory import AgentContextInventory, AssetRoot
|
||||
from pydantic_ai_harness.context._loader import ContextFile
|
||||
from pydantic_ai_harness.context._toolset import RepoContextToolset
|
||||
|
||||
__all__ = ['AgentContextInventory', 'AssetRoot', 'ContextFile', 'RepoContext', 'RepoContextToolset']
|
||||
+3
-4
@@ -12,19 +12,18 @@ from pydantic_ai.messages import ToolCallPart
|
||||
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.context._loader import (
|
||||
from pydantic_ai_harness.context._loader import (
|
||||
ContextFile,
|
||||
discover_instruction_files,
|
||||
find_dir_context_file,
|
||||
render_context_file,
|
||||
render_context_files,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.context._toolset import RepoContextToolset
|
||||
from pydantic_ai_harness.context._toolset import RepoContextToolset
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai._instructions import AgentInstructions
|
||||
|
||||
|
||||
_INVENTORY_HINT = (
|
||||
'Call `{tool_name}` to map where this repo keeps its coding-assistant setup '
|
||||
'(instruction dirs, skills, sub-agents, and hooks) so you can read and translate it.'
|
||||
@@ -63,7 +62,7 @@ class RepoContext(AbstractCapability[AgentDepsT]):
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.context import RepoContext
|
||||
from pydantic_ai_harness.context import RepoContext
|
||||
|
||||
agent = Agent(
|
||||
'anthropic:claude-sonnet-4-6',
|
||||
+1
-1
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from pydantic_ai.tools import AgentDepsT
|
||||
from pydantic_ai.toolsets import FunctionToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.context._inventory import AgentContextInventory, scan_assets
|
||||
from pydantic_ai_harness.context._inventory import AgentContextInventory, scan_assets
|
||||
|
||||
|
||||
class RepoContextToolset(FunctionToolset[AgentDepsT]):
|
||||
+5
-15
@@ -1,23 +1,13 @@
|
||||
# PyaiDocs
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.docs import PyaiDocs
|
||||
> from pydantic_ai_harness.docs import PyaiDocs
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Give an agent a tool that locates and returns Pydantic AI documentation on demand.
|
||||
|
||||
@@ -40,7 +30,7 @@ Topics: `capabilities`, `hooks`, `tools`, `tools-advanced`, `toolsets`, `agent`.
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.docs import PyaiDocs
|
||||
from pydantic_ai_harness.docs import PyaiDocs
|
||||
|
||||
agent = Agent(
|
||||
'anthropic:claude-sonnet-4-6',
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Docs capability: an on-demand tool that locates Pydantic AI documentation."""
|
||||
|
||||
from pydantic_ai_harness.docs._capability import PyaiDocs
|
||||
from pydantic_ai_harness.docs._toolset import PyaiDocsToolset, PyaiDocsTopic
|
||||
|
||||
__all__ = ['PyaiDocs', 'PyaiDocsToolset', 'PyaiDocsTopic']
|
||||
+2
-2
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.tools import AgentDepsT
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.docs._toolset import PyaiDocsToolset, PyaiDocsTopic
|
||||
from pydantic_ai_harness.docs._toolset import PyaiDocsToolset, PyaiDocsTopic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai._instructions import AgentInstructions
|
||||
@@ -46,7 +46,7 @@ class PyaiDocs(AbstractCapability[AgentDepsT]):
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.docs import PyaiDocs
|
||||
from pydantic_ai_harness.docs import PyaiDocs
|
||||
|
||||
agent = Agent(
|
||||
'anthropic:claude-sonnet-4-6',
|
||||
+10
-18
@@ -2,20 +2,16 @@
|
||||
|
||||
Let one agent coordinate a whole team of sub-agents by writing a small Python script.
|
||||
|
||||
> **Experimental**
|
||||
>
|
||||
> Importing `pydantic_ai_harness.experimental.dynamic_workflow` emits a
|
||||
> `HarnessExperimentalWarning`. The API can still change in any release. The extensions planned in
|
||||
> [What is coming](#what-is-coming), structured sub-agent inputs and durable workflows, touch the
|
||||
> sub-agent call contract itself, so the capability stays experimental until they settle. Once you
|
||||
> have accepted that, you can silence the warning:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> from pydantic_ai_harness.dynamic_workflow import DynamicWorkflow
|
||||
> ```
|
||||
>
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
> The extensions planned in [What is coming](#what-is-coming), structured sub-agent inputs and durable
|
||||
> workflows, touch the sub-agent call contract itself.
|
||||
|
||||
## The idea
|
||||
|
||||
@@ -81,7 +77,7 @@ Let's build the smallest thing that works. Two sub-agents, one orchestrator.
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import DynamicWorkflow
|
||||
from pydantic_ai_harness.dynamic_workflow import DynamicWorkflow
|
||||
|
||||
reviewer = Agent('openai:gpt-5', name='reviewer', description='Reviews code for bugs.')
|
||||
summarizer = Agent('openai:gpt-5', name='summarizer', description='Summarizes findings.')
|
||||
@@ -192,7 +188,7 @@ from pydantic import BaseModel
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai.usage import UsageLimits
|
||||
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import DynamicWorkflow
|
||||
from pydantic_ai_harness.dynamic_workflow import DynamicWorkflow
|
||||
|
||||
# With Logfire configured, the trace shows the orchestrator turn, the run_workflow call (including
|
||||
# the exact script the model wrote), and every sub-agent run nested underneath it.
|
||||
@@ -201,12 +197,10 @@ logfire.instrument_pydantic_ai()
|
||||
|
||||
MODEL = 'anthropic:claude-sonnet-4-6' # or 'anthropic:claude-opus-4-8'
|
||||
|
||||
|
||||
class Score(BaseModel):
|
||||
value: int # 0-10
|
||||
reason: str
|
||||
|
||||
|
||||
drafter = Agent(
|
||||
MODEL,
|
||||
name='drafter',
|
||||
@@ -237,7 +231,6 @@ orchestrator = Agent(
|
||||
capabilities=[DynamicWorkflow(agents=[drafter, critic, editor])],
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
result = await orchestrator.run(
|
||||
'Explain, for a new hire, why our service uses idempotency keys on payment requests.',
|
||||
@@ -245,7 +238,6 @@ async def main() -> None:
|
||||
)
|
||||
logfire.info('done', answer=result.output, requests=result.usage.requests)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
@@ -401,7 +393,7 @@ want a different name or a different description for one particular workflow, wi
|
||||
agent itself. Wrap it in a `WorkflowAgent`:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import WorkflowAgent
|
||||
from pydantic_ai_harness.dynamic_workflow import WorkflowAgent
|
||||
|
||||
DynamicWorkflow(
|
||||
agents=[
|
||||
@@ -0,0 +1,10 @@
|
||||
"""Dynamic workflow capability: orchestrate sub-agents from a sandboxed Python script."""
|
||||
|
||||
from pydantic_ai_harness.dynamic_workflow._capability import DynamicWorkflow
|
||||
from pydantic_ai_harness.dynamic_workflow._toolset import (
|
||||
DynamicWorkflowToolset,
|
||||
WorkflowAgent,
|
||||
WorkflowResourceLimits,
|
||||
)
|
||||
|
||||
__all__ = ['DynamicWorkflow', 'DynamicWorkflowToolset', 'WorkflowAgent', 'WorkflowResourceLimits']
|
||||
+2
-2
@@ -11,7 +11,7 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.tools import AgentDepsT
|
||||
from pydantic_ai.usage import UsageLimits
|
||||
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow._toolset import (
|
||||
from pydantic_ai_harness.dynamic_workflow._toolset import (
|
||||
DynamicWorkflowToolset,
|
||||
WorkflowAgent,
|
||||
WorkflowResourceLimits,
|
||||
@@ -30,7 +30,7 @@ class DynamicWorkflow(AbstractCapability[AgentDepsT]):
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import DynamicWorkflow
|
||||
from pydantic_ai_harness.dynamic_workflow import DynamicWorkflow
|
||||
|
||||
reviewer = Agent('openai:gpt-5', name='reviewer', description='Review code for bugs.')
|
||||
summarizer = Agent('openai:gpt-5', name='summarizer', description='Summarize findings.')
|
||||
@@ -38,3 +38,18 @@ def warn_experimental(feature: str) -> None:
|
||||
category=HarnessExperimentalWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
def warn_moved(old: str, new: str) -> None:
|
||||
"""Emit a `DeprecationWarning` that `experimental.old` now lives at top-level `new`.
|
||||
|
||||
Left behind at each old `pydantic_ai_harness.experimental.<name>` path when a capability
|
||||
graduates out of `experimental`, so existing imports keep working with a clear pointer to
|
||||
the new location.
|
||||
"""
|
||||
warnings.warn(
|
||||
f'`pydantic_ai_harness.experimental.{old}` has moved to `pydantic_ai_harness.{new}`. '
|
||||
f'Update your imports; this compatibility shim will be removed in a future release.',
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
"""Runtime capability authoring: let an agent write, validate, and register real capabilities."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.runtime_authoring`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.authoring._capability import RuntimeAuthoring
|
||||
from pydantic_ai_harness.experimental.authoring._store import AuthoredCapability, CapabilityStore
|
||||
from pydantic_ai_harness.experimental.authoring._toolset import AuthoringToolset
|
||||
from pydantic_ai_harness.experimental.authoring._validate import (
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.runtime_authoring` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.runtime_authoring import (
|
||||
AuthoredCapability,
|
||||
AuthoringToolset,
|
||||
CapabilityStore,
|
||||
CapabilityValidationError,
|
||||
RuntimeAuthoring,
|
||||
load_capability_instance,
|
||||
validate_capability_file,
|
||||
)
|
||||
|
||||
warn_experimental('authoring')
|
||||
warn_moved('authoring', 'runtime_authoring')
|
||||
|
||||
__all__ = [
|
||||
'AuthoredCapability',
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
"""Compaction capabilities: keep an agent's conversation history within the context window.
|
||||
"""Deprecated import location for `pydantic_ai_harness.compaction`.
|
||||
|
||||
Each capability lives in its own module; shared utilities (token estimation, the
|
||||
`CompactionStrategy` protocol, tool-pair-safe cutoffs, in-place clearing) live in `_shared`.
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.compaction` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.compaction._clamp_oversized_messages import ClampOversizedMessages
|
||||
from pydantic_ai_harness.experimental.compaction._clear_tool_results import ClearToolResults
|
||||
from pydantic_ai_harness.experimental.compaction._deduplicate_file_reads import DeduplicateFileReads
|
||||
from pydantic_ai_harness.experimental.compaction._limit_warner import LimitWarner, WarningKind
|
||||
from pydantic_ai_harness.experimental.compaction._shared import CompactionStrategy, estimate_token_count
|
||||
from pydantic_ai_harness.experimental.compaction._sliding_window import SlidingWindow
|
||||
from pydantic_ai_harness.experimental.compaction._summarizing_compaction import SummarizingCompaction
|
||||
from pydantic_ai_harness.experimental.compaction._tiered_compaction import TieredCompaction
|
||||
from pydantic_ai_harness.compaction import (
|
||||
ClampOversizedMessages,
|
||||
ClearToolResults,
|
||||
CompactionStrategy,
|
||||
DeduplicateFileReads,
|
||||
LimitWarner,
|
||||
SlidingWindow,
|
||||
SummarizingCompaction,
|
||||
TieredCompaction,
|
||||
WarningKind,
|
||||
estimate_token_count,
|
||||
)
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
|
||||
warn_experimental('compaction')
|
||||
warn_moved('compaction', 'compaction')
|
||||
|
||||
__all__ = [
|
||||
'ClampOversizedMessages',
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
"""Context capability: discover and load a repo's accumulated context engineering."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.context`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.context._capability import RepoContext
|
||||
from pydantic_ai_harness.experimental.context._inventory import AgentContextInventory, AssetRoot
|
||||
from pydantic_ai_harness.experimental.context._loader import ContextFile
|
||||
from pydantic_ai_harness.experimental.context._toolset import RepoContextToolset
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.context` instead.
|
||||
"""
|
||||
|
||||
warn_experimental('context')
|
||||
from pydantic_ai_harness.context import (
|
||||
AgentContextInventory,
|
||||
AssetRoot,
|
||||
ContextFile,
|
||||
RepoContext,
|
||||
RepoContextToolset,
|
||||
)
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
|
||||
__all__ = ['AgentContextInventory', 'AssetRoot', 'ContextFile', 'RepoContext', 'RepoContextToolset']
|
||||
warn_moved('context', 'context')
|
||||
|
||||
__all__ = [
|
||||
'AgentContextInventory',
|
||||
'AssetRoot',
|
||||
'ContextFile',
|
||||
'RepoContext',
|
||||
'RepoContextToolset',
|
||||
]
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
"""Docs capability: an on-demand tool that locates Pydantic AI documentation."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.docs`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.docs._capability import PyaiDocs
|
||||
from pydantic_ai_harness.experimental.docs._toolset import PyaiDocsToolset, PyaiDocsTopic
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.docs` instead.
|
||||
"""
|
||||
|
||||
warn_experimental('docs')
|
||||
from pydantic_ai_harness.docs import (
|
||||
PyaiDocs,
|
||||
PyaiDocsToolset,
|
||||
PyaiDocsTopic,
|
||||
)
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
|
||||
__all__ = ['PyaiDocs', 'PyaiDocsToolset', 'PyaiDocsTopic']
|
||||
warn_moved('docs', 'docs')
|
||||
|
||||
__all__ = [
|
||||
'PyaiDocs',
|
||||
'PyaiDocsToolset',
|
||||
'PyaiDocsTopic',
|
||||
]
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
"""Dynamic workflow capability: orchestrate sub-agents from a sandboxed Python script."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.dynamic_workflow`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow._capability import DynamicWorkflow
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow._toolset import (
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.dynamic_workflow` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.dynamic_workflow import (
|
||||
DynamicWorkflow,
|
||||
DynamicWorkflowToolset,
|
||||
WorkflowAgent,
|
||||
WorkflowResourceLimits,
|
||||
)
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
|
||||
warn_experimental('dynamic_workflow')
|
||||
warn_moved('dynamic_workflow', 'dynamic_workflow')
|
||||
|
||||
__all__ = ['DynamicWorkflow', 'DynamicWorkflowToolset', 'WorkflowAgent', 'WorkflowResourceLimits']
|
||||
__all__ = [
|
||||
'DynamicWorkflow',
|
||||
'DynamicWorkflowToolset',
|
||||
'WorkflowAgent',
|
||||
'WorkflowResourceLimits',
|
||||
]
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
"""Content-addressed media stores for offloading large binary parts.
|
||||
"""Deprecated import location for `pydantic_ai_harness.media`.
|
||||
|
||||
Used by `pydantic_ai_harness.experimental.step_persistence` to keep snapshots small when
|
||||
messages carry `BinaryContent` payloads. A forthcoming `MediaExternalizer`
|
||||
capability will reuse these stores for in-flight wire-payload reduction
|
||||
(rewriting `BinaryContent` to URL parts before the model sees them).
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.media` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.media._s3 import S3MediaStore
|
||||
from pydantic_ai_harness.experimental.media._store import (
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.media import (
|
||||
DiskMediaStore,
|
||||
KeyStrategy,
|
||||
MediaContext,
|
||||
MediaStore,
|
||||
PublicUrlResolver,
|
||||
S3MediaStore,
|
||||
SqliteMediaStore,
|
||||
default_key_strategy,
|
||||
externalize_media,
|
||||
make_static_public_url,
|
||||
media_uri_for,
|
||||
parse_media_uri,
|
||||
restore_media,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.media._walker import externalize_media, restore_media
|
||||
|
||||
warn_experimental('media')
|
||||
warn_moved('media', 'media')
|
||||
|
||||
__all__ = [
|
||||
'DiskMediaStore',
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
"""Overflow capability: reduce oversized tool returns at production time.
|
||||
"""Deprecated import location for `pydantic_ai_harness.overflowing_tool_output`.
|
||||
|
||||
`OverflowingToolOutput` intercepts a tool return when it is produced and reduces it --
|
||||
truncating, spilling to a queryable file, or summarizing -- so an oversized payload does
|
||||
not persist in history and get re-sent on every later model request. Combine the three
|
||||
modes through an ordered list of size `bands`.
|
||||
|
||||
Spilled payloads are read back on demand through the registered `read_tool_result` tool;
|
||||
the `OverflowStore` protocol is the seam for a durable backend (the local-file default
|
||||
ships for single-process runs).
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.overflowing_tool_output` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.overflow._bands import (
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.overflowing_tool_output import (
|
||||
READ_TOOL_NAME,
|
||||
Action,
|
||||
Band,
|
||||
LocalFileStore,
|
||||
OverflowingToolOutput,
|
||||
OverflowStore,
|
||||
Passthrough,
|
||||
Spill,
|
||||
Summarize,
|
||||
SummarizeFunc,
|
||||
Truncate,
|
||||
TruncationStrategy,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._capability import READ_TOOL_NAME, OverflowingToolOutput
|
||||
from pydantic_ai_harness.experimental.overflow._payload import TruncationStrategy
|
||||
from pydantic_ai_harness.experimental.overflow._store import LocalFileStore, OverflowStore
|
||||
|
||||
warn_experimental('overflow')
|
||||
warn_moved('overflow', 'overflowing_tool_output')
|
||||
|
||||
__all__ = [
|
||||
'READ_TOOL_NAME',
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
"""Planning capability: model-owned, cache-friendly task planning for agents."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.planning`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.planning._capability import Planning
|
||||
from pydantic_ai_harness.experimental.planning._toolset import PlanItem, PlanningToolset, TaskStatus
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.planning` instead.
|
||||
"""
|
||||
|
||||
warn_experimental('planning')
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.planning import (
|
||||
PlanItem,
|
||||
Planning,
|
||||
PlanningToolset,
|
||||
TaskStatus,
|
||||
)
|
||||
|
||||
__all__ = ['PlanItem', 'Planning', 'PlanningToolset', 'TaskStatus']
|
||||
warn_moved('planning', 'planning')
|
||||
|
||||
__all__ = [
|
||||
'PlanItem',
|
||||
'Planning',
|
||||
'PlanningToolset',
|
||||
'TaskStatus',
|
||||
]
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
"""Step-event persistence: append-only event log, continuable snapshots, tool-effect ledger."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.step_persistence`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.step_persistence._capability import StepPersistence
|
||||
from pydantic_ai_harness.experimental.step_persistence._helpers import (
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.step_persistence` instead.
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.step_persistence import (
|
||||
ContinuableSnapshot,
|
||||
EventKind,
|
||||
FileStepStore,
|
||||
InMemoryStepStore,
|
||||
RunRecord,
|
||||
SqliteStepStore,
|
||||
StepEvent,
|
||||
StepPersistence,
|
||||
StepStore,
|
||||
ToolEffectRecord,
|
||||
ToolEffectStatus,
|
||||
annotate_tool_effect,
|
||||
continue_run,
|
||||
fork_run,
|
||||
is_provider_valid,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.step_persistence._store import (
|
||||
FileStepStore,
|
||||
InMemoryStepStore,
|
||||
SqliteStepStore,
|
||||
StepStore,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.step_persistence._types import (
|
||||
ContinuableSnapshot,
|
||||
EventKind,
|
||||
RunRecord,
|
||||
StepEvent,
|
||||
ToolEffectRecord,
|
||||
ToolEffectStatus,
|
||||
)
|
||||
|
||||
warn_experimental('step_persistence')
|
||||
warn_moved('step_persistence', 'step_persistence')
|
||||
|
||||
__all__ = [
|
||||
'ContinuableSnapshot',
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
"""Sub-agent capability: delegate self-contained tasks to named child agents."""
|
||||
"""Deprecated import location for `pydantic_ai_harness.subagents`.
|
||||
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
from pydantic_ai_harness.experimental.subagents._capability import SubAgents, ToolResolver
|
||||
from pydantic_ai_harness.experimental.subagents._disk import AgentOverride
|
||||
from pydantic_ai_harness.experimental.subagents._effort import MINIMUM_EFFORT_FLOOR, clamp_effort
|
||||
from pydantic_ai_harness.experimental.subagents._toolset import SubAgent, SubAgentToolset
|
||||
This capability graduated out of `experimental`; importing from here still works but
|
||||
emits a `DeprecationWarning`. Import from `pydantic_ai_harness.subagents` instead.
|
||||
"""
|
||||
|
||||
warn_experimental('subagents')
|
||||
from pydantic_ai_harness.experimental._warn import warn_moved
|
||||
from pydantic_ai_harness.subagents import (
|
||||
MINIMUM_EFFORT_FLOOR,
|
||||
AgentOverride,
|
||||
SubAgent,
|
||||
SubAgents,
|
||||
SubAgentToolset,
|
||||
ToolResolver,
|
||||
clamp_effort,
|
||||
)
|
||||
|
||||
warn_moved('subagents', 'subagents')
|
||||
|
||||
__all__ = [
|
||||
'MINIMUM_EFFORT_FLOOR',
|
||||
|
||||
+14
-11
@@ -1,16 +1,19 @@
|
||||
# Media
|
||||
|
||||
> **Experimental.** Imported from `pydantic_ai_harness.experimental.media`, this module can change or be removed in any release. Importing it emits a `HarnessExperimentalWarning`.
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.media import (
|
||||
DiskMediaStore,
|
||||
S3MediaStore,
|
||||
SqliteMediaStore,
|
||||
externalize_media,
|
||||
restore_media,
|
||||
)
|
||||
```
|
||||
> [!NOTE]
|
||||
> Import these helpers from their submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.media import (
|
||||
> DiskMediaStore,
|
||||
> S3MediaStore,
|
||||
> SqliteMediaStore,
|
||||
> externalize_media,
|
||||
> restore_media,
|
||||
> )
|
||||
> ```
|
||||
>
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Content-addressed stores and walker helpers that move large binary payloads out of message history and put them back on demand.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Content-addressed media stores for offloading large binary parts.
|
||||
|
||||
Used by `pydantic_ai_harness.step_persistence` to keep snapshots small when
|
||||
messages carry `BinaryContent` payloads. A forthcoming `MediaExternalizer`
|
||||
capability will reuse these stores for in-flight wire-payload reduction
|
||||
(rewriting `BinaryContent` to URL parts before the model sees them).
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.media._s3 import S3MediaStore
|
||||
from pydantic_ai_harness.media._store import (
|
||||
DiskMediaStore,
|
||||
KeyStrategy,
|
||||
MediaContext,
|
||||
MediaStore,
|
||||
PublicUrlResolver,
|
||||
SqliteMediaStore,
|
||||
default_key_strategy,
|
||||
make_static_public_url,
|
||||
media_uri_for,
|
||||
parse_media_uri,
|
||||
)
|
||||
from pydantic_ai_harness.media._walker import externalize_media, restore_media
|
||||
|
||||
__all__ = [
|
||||
'DiskMediaStore',
|
||||
'KeyStrategy',
|
||||
'MediaContext',
|
||||
'MediaStore',
|
||||
'PublicUrlResolver',
|
||||
'S3MediaStore',
|
||||
'SqliteMediaStore',
|
||||
'default_key_strategy',
|
||||
'externalize_media',
|
||||
'make_static_public_url',
|
||||
'media_uri_for',
|
||||
'parse_media_uri',
|
||||
'restore_media',
|
||||
]
|
||||
@@ -22,7 +22,7 @@ from datetime import datetime, timezone
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import quote
|
||||
|
||||
from pydantic_ai_harness.experimental.media._store import (
|
||||
from pydantic_ai_harness.media._store import (
|
||||
_EMPTY_CONTEXT, # pyright: ignore[reportPrivateUsage]
|
||||
KeyStrategy,
|
||||
MediaContext,
|
||||
@@ -36,7 +36,6 @@ from pydantic_ai_harness.experimental.media._store import (
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
|
||||
|
||||
_ALGORITHM = 'AWS4-HMAC-SHA256'
|
||||
_SERVICE = 's3'
|
||||
# Conservative subset for x-amz-meta-* keys: ASCII letters/digits/dash; lowercase.
|
||||
@@ -61,7 +61,6 @@ class MediaContext:
|
||||
|
||||
_EMPTY_CONTEXT = MediaContext()
|
||||
|
||||
|
||||
PublicUrlResolver = Callable[[str, MediaContext], 'str | None | Awaitable[str | None]']
|
||||
"""User-supplied callable that turns a `media+sha256://<hex>` URI into a fetchable URL.
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ from __future__ import annotations
|
||||
import base64
|
||||
from typing import TypeGuard
|
||||
|
||||
from pydantic_ai_harness.experimental.media._store import MediaContext, MediaStore
|
||||
from pydantic_ai_harness.media._store import MediaContext, MediaStore
|
||||
|
||||
_EXTERNAL_MARKER = '__harness_external_media__'
|
||||
|
||||
+5
-15
@@ -1,23 +1,13 @@
|
||||
# Overflow capability
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.overflow import OverflowingToolOutput
|
||||
> from pydantic_ai_harness.overflowing_tool_output import OverflowingToolOutput
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
A tool can return a payload large enough to dominate the context window. Tool returns
|
||||
persist in history as `ToolReturnPart`s, so an oversized one is re-sent on every later
|
||||
@@ -60,7 +50,7 @@ that fits wins; anything below the smallest threshold passes through.
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.overflow import (
|
||||
from pydantic_ai_harness.overflowing_tool_output import (
|
||||
Band,
|
||||
OverflowingToolOutput,
|
||||
Spill,
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Overflow capability: reduce oversized tool returns at production time.
|
||||
|
||||
`OverflowingToolOutput` intercepts a tool return when it is produced and reduces it --
|
||||
truncating, spilling to a queryable file, or summarizing -- so an oversized payload does
|
||||
not persist in history and get re-sent on every later model request. Combine the three
|
||||
modes through an ordered list of size `bands`.
|
||||
|
||||
Spilled payloads are read back on demand through the registered `read_tool_result` tool;
|
||||
the `OverflowStore` protocol is the seam for a durable backend (the local-file default
|
||||
ships for single-process runs).
|
||||
"""
|
||||
|
||||
from pydantic_ai_harness.overflowing_tool_output._bands import (
|
||||
Action,
|
||||
Band,
|
||||
Passthrough,
|
||||
Spill,
|
||||
Summarize,
|
||||
SummarizeFunc,
|
||||
Truncate,
|
||||
)
|
||||
from pydantic_ai_harness.overflowing_tool_output._capability import READ_TOOL_NAME, OverflowingToolOutput
|
||||
from pydantic_ai_harness.overflowing_tool_output._payload import TruncationStrategy
|
||||
from pydantic_ai_harness.overflowing_tool_output._store import LocalFileStore, OverflowStore
|
||||
|
||||
__all__ = [
|
||||
'READ_TOOL_NAME',
|
||||
'Action',
|
||||
'Band',
|
||||
'LocalFileStore',
|
||||
'OverflowStore',
|
||||
'OverflowingToolOutput',
|
||||
'Passthrough',
|
||||
'Spill',
|
||||
'Summarize',
|
||||
'SummarizeFunc',
|
||||
'Truncate',
|
||||
'TruncationStrategy',
|
||||
]
|
||||
+1
-1
@@ -16,7 +16,7 @@ from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic_ai_harness.experimental.overflow._payload import TruncationStrategy
|
||||
from pydantic_ai_harness.overflowing_tool_output._payload import TruncationStrategy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai.models import Model
|
||||
+4
-4
@@ -14,7 +14,7 @@ from pydantic_ai.messages import ToolCallPart, ToolReturn, ToolReturnContent, Us
|
||||
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition, ToolSelector, matches_tool_selector
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.overflow._bands import (
|
||||
from pydantic_ai_harness.overflowing_tool_output._bands import (
|
||||
Action,
|
||||
Band,
|
||||
Passthrough,
|
||||
@@ -22,7 +22,7 @@ from pydantic_ai_harness.experimental.overflow._bands import (
|
||||
Summarize,
|
||||
Truncate,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._payload import (
|
||||
from pydantic_ai_harness.overflowing_tool_output._payload import (
|
||||
is_binary,
|
||||
json_sketch,
|
||||
measure,
|
||||
@@ -31,7 +31,7 @@ from pydantic_ai_harness.experimental.overflow._payload import (
|
||||
to_text,
|
||||
truncate_text,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._store import LocalFileStore, OverflowStore
|
||||
from pydantic_ai_harness.overflowing_tool_output._store import LocalFileStore, OverflowStore
|
||||
|
||||
READ_TOOL_NAME = 'read_tool_result'
|
||||
"""Name of the registered read-back tool. Its own returns are exempt from reduction."""
|
||||
@@ -96,7 +96,7 @@ class OverflowingToolOutput(AbstractCapability[AgentDepsT]):
|
||||
Example:
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.overflow import (
|
||||
from pydantic_ai_harness.overflowing_tool_output import (
|
||||
Band,
|
||||
OverflowingToolOutput,
|
||||
Spill,
|
||||
+1
-1
@@ -15,7 +15,7 @@ from typing import TypeGuard
|
||||
from pydantic_ai.messages import ModelMessage, ModelRequest, SystemPromptPart
|
||||
from pydantic_core import to_json
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction._shared import estimate_token_count
|
||||
from pydantic_ai_harness.compaction._shared import estimate_token_count
|
||||
|
||||
|
||||
class TruncationStrategy(str, Enum):
|
||||
+6
-16
@@ -1,23 +1,13 @@
|
||||
# Planning
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.planning import Planning
|
||||
> from pydantic_ai_harness.planning import Planning
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Give an agent a structured, self-updating task plan -- without ever invalidating the prompt cache.
|
||||
|
||||
@@ -36,7 +26,7 @@ So the plan stays current in the model's view while the cached prefix is never i
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.planning import Planning
|
||||
from pydantic_ai_harness.planning import Planning
|
||||
|
||||
agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[Planning()])
|
||||
|
||||
@@ -109,7 +99,7 @@ capabilities:
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.planning import Planning
|
||||
from pydantic_ai_harness.planning import Planning
|
||||
|
||||
agent = Agent.from_file('agent.yaml', custom_capability_types=[Planning])
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Planning capability: model-owned, cache-friendly task planning for agents."""
|
||||
|
||||
from pydantic_ai_harness.planning._capability import Planning
|
||||
from pydantic_ai_harness.planning._toolset import PlanItem, PlanningToolset, TaskStatus
|
||||
|
||||
__all__ = ['PlanItem', 'Planning', 'PlanningToolset', 'TaskStatus']
|
||||
+2
-3
@@ -10,14 +10,13 @@ from pydantic_ai.messages import CachePoint, ModelRequest, ModelResponse, UserPr
|
||||
from pydantic_ai.tools import AgentDepsT, RunContext
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.planning._toolset import PlanningToolset, PlanState, render_plan
|
||||
from pydantic_ai_harness.planning._toolset import PlanningToolset, PlanState, render_plan
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai._instructions import AgentInstructions
|
||||
from pydantic_ai.capabilities.abstract import WrapModelRequestHandler
|
||||
from pydantic_ai.models import ModelRequestContext
|
||||
|
||||
|
||||
_DEFAULT_GUIDANCE = (
|
||||
'You have a planning tool, `write_plan`. For multi-step work, call it first to lay out the '
|
||||
'steps, then call it again to update statuses as you start and finish each step. Pass the '
|
||||
@@ -41,7 +40,7 @@ class Planning(AbstractCapability[AgentDepsT]):
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.planning import Planning
|
||||
from pydantic_ai_harness.planning import Planning
|
||||
|
||||
agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[Planning()])
|
||||
```
|
||||
+5
-15
@@ -1,23 +1,13 @@
|
||||
# RuntimeAuthoring
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.authoring import RuntimeAuthoring
|
||||
> from pydantic_ai_harness.runtime_authoring import RuntimeAuthoring
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Let an agent author, validate, and persist real pydantic-ai capabilities at runtime.
|
||||
|
||||
@@ -50,7 +40,7 @@ authoring a hook means authoring a capability that overrides one lifecycle metho
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.authoring import RuntimeAuthoring
|
||||
from pydantic_ai_harness.runtime_authoring import RuntimeAuthoring
|
||||
|
||||
authoring = RuntimeAuthoring(directory=Path('.authored'))
|
||||
agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[authoring])
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Runtime capability authoring: let an agent write, validate, and register real capabilities."""
|
||||
|
||||
from pydantic_ai_harness.runtime_authoring._capability import RuntimeAuthoring
|
||||
from pydantic_ai_harness.runtime_authoring._store import AuthoredCapability, CapabilityStore
|
||||
from pydantic_ai_harness.runtime_authoring._toolset import AuthoringToolset
|
||||
from pydantic_ai_harness.runtime_authoring._validate import (
|
||||
CapabilityValidationError,
|
||||
load_capability_instance,
|
||||
validate_capability_file,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AuthoredCapability',
|
||||
'AuthoringToolset',
|
||||
'CapabilityStore',
|
||||
'CapabilityValidationError',
|
||||
'RuntimeAuthoring',
|
||||
'load_capability_instance',
|
||||
'validate_capability_file',
|
||||
]
|
||||
+3
-4
@@ -10,13 +10,12 @@ from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.tools import AgentDepsT
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.authoring._store import CapabilityStore
|
||||
from pydantic_ai_harness.experimental.authoring._toolset import AuthoringToolset
|
||||
from pydantic_ai_harness.runtime_authoring._store import CapabilityStore
|
||||
from pydantic_ai_harness.runtime_authoring._toolset import AuthoringToolset
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai._instructions import AgentInstructions
|
||||
|
||||
|
||||
_DEFAULT_GUIDANCE = (
|
||||
'You can author new pydantic-ai capabilities at runtime with `author_capability(name, code)`. '
|
||||
'A capability is a subclass of `pydantic_ai.capabilities.AbstractCapability` that constructs with '
|
||||
@@ -48,7 +47,7 @@ class RuntimeAuthoring(AbstractCapability[AgentDepsT]):
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.authoring import RuntimeAuthoring
|
||||
from pydantic_ai_harness.runtime_authoring import RuntimeAuthoring
|
||||
|
||||
authoring = RuntimeAuthoring(directory=Path('.authored'))
|
||||
agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[authoring])
|
||||
+1
-1
@@ -19,7 +19,7 @@ from typing import Literal
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
from pydantic_ai_harness.experimental.authoring._validate import (
|
||||
from pydantic_ai_harness.runtime_authoring._validate import (
|
||||
CapabilityValidationError,
|
||||
load_capability_instance,
|
||||
validate_capability_file,
|
||||
+1
-1
@@ -6,7 +6,7 @@ from pydantic_ai.exceptions import ModelRetry
|
||||
from pydantic_ai.tools import AgentDepsT
|
||||
from pydantic_ai.toolsets import FunctionToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.authoring._store import CapabilityStore
|
||||
from pydantic_ai_harness.runtime_authoring._store import CapabilityStore
|
||||
|
||||
|
||||
class AuthoringToolset(FunctionToolset[AgentDepsT]):
|
||||
+14
-23
@@ -1,24 +1,15 @@
|
||||
# StepPersistence
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** `StepPersistence` and the `media` stores live under
|
||||
> `pydantic_ai_harness.experimental` and may change or be removed in any release, without a
|
||||
> deprecation period. Import them from the experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import `StepPersistence` and the `media` stores from their submodules -- there is no top-level
|
||||
> `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.step_persistence import StepPersistence
|
||||
> from pydantic_ai_harness.experimental.media import S3MediaStore
|
||||
> from pydantic_ai_harness.step_persistence import StepPersistence
|
||||
> from pydantic_ai_harness.media import S3MediaStore
|
||||
> ```
|
||||
>
|
||||
> Importing either package emits a `HarnessExperimentalWarning`. Silence **all** harness
|
||||
> experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
`StepPersistence` records what an agent did at each boundary, separate from
|
||||
whether the run can be safely resumed. It is the persistence substrate for
|
||||
@@ -52,7 +43,7 @@ snapshots, and graph-node resume are out of scope and tracked separately
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.step_persistence import StepPersistence, InMemoryStepStore
|
||||
from pydantic_ai_harness.step_persistence import StepPersistence, InMemoryStepStore
|
||||
|
||||
store = InMemoryStepStore()
|
||||
librarian = Agent(
|
||||
@@ -128,7 +119,7 @@ context". `StepPersistence` does not introduce a parallel mechanism -- it
|
||||
exposes one helper that loads the most recent provider-valid snapshot:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.step_persistence import continue_run
|
||||
from pydantic_ai_harness.step_persistence import continue_run
|
||||
|
||||
# Earlier: tag the first turn with a conversation id so the follow-up can find it.
|
||||
await librarian.run(
|
||||
@@ -262,7 +253,7 @@ write external state should annotate their in-flight `ToolEffectRecord`
|
||||
via `annotate_tool_effect`:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.step_persistence import annotate_tool_effect
|
||||
from pydantic_ai_harness.step_persistence import annotate_tool_effect
|
||||
|
||||
@orchestrator.tool
|
||||
async def set_label(ctx: RunContext[Deps], issue: int, label: str) -> str:
|
||||
@@ -330,8 +321,8 @@ original bytes.
|
||||
Override the destination by passing your own `MediaStore`:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.step_persistence import FileStepStore
|
||||
from pydantic_ai_harness.experimental.media import S3MediaStore
|
||||
from pydantic_ai_harness.step_persistence import FileStepStore
|
||||
from pydantic_ai_harness.media import S3MediaStore
|
||||
|
||||
store = FileStepStore(
|
||||
'runs',
|
||||
@@ -379,7 +370,7 @@ without re-encoding bytes into the request body.
|
||||
Static base URL (public R2 bucket, CDN):
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.media import S3MediaStore, make_static_public_url
|
||||
from pydantic_ai_harness.media import S3MediaStore, make_static_public_url
|
||||
|
||||
store = S3MediaStore(
|
||||
bucket='my-bucket',
|
||||
@@ -395,7 +386,7 @@ Presigned / rotating-signature URL -- pass any async callable that takes
|
||||
`(uri, MediaContext)`:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.media import MediaContext, S3MediaStore
|
||||
from pydantic_ai_harness.media import MediaContext, S3MediaStore
|
||||
|
||||
async def presign(uri: str, ctx: MediaContext) -> str:
|
||||
key = 'media/' + uri.removeprefix('media+sha256://') + '.bin'
|
||||
@@ -444,7 +435,7 @@ primary key is the digest, so a user-chosen key would either break
|
||||
dedup or be a no-op):
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.media import DiskMediaStore, MediaContext
|
||||
from pydantic_ai_harness.media import DiskMediaStore, MediaContext
|
||||
|
||||
def by_media_type(uri: str, ctx: MediaContext) -> str:
|
||||
digest = uri.removeprefix('media+sha256://')
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Step-event persistence: append-only event log, continuable snapshots, tool-effect ledger."""
|
||||
|
||||
from pydantic_ai_harness.step_persistence._capability import StepPersistence
|
||||
from pydantic_ai_harness.step_persistence._helpers import (
|
||||
annotate_tool_effect,
|
||||
continue_run,
|
||||
fork_run,
|
||||
is_provider_valid,
|
||||
)
|
||||
from pydantic_ai_harness.step_persistence._store import (
|
||||
FileStepStore,
|
||||
InMemoryStepStore,
|
||||
SqliteStepStore,
|
||||
StepStore,
|
||||
)
|
||||
from pydantic_ai_harness.step_persistence._types import (
|
||||
ContinuableSnapshot,
|
||||
EventKind,
|
||||
RunRecord,
|
||||
StepEvent,
|
||||
ToolEffectRecord,
|
||||
ToolEffectStatus,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'ContinuableSnapshot',
|
||||
'EventKind',
|
||||
'FileStepStore',
|
||||
'InMemoryStepStore',
|
||||
'RunRecord',
|
||||
'SqliteStepStore',
|
||||
'StepEvent',
|
||||
'StepPersistence',
|
||||
'StepStore',
|
||||
'ToolEffectRecord',
|
||||
'ToolEffectStatus',
|
||||
'annotate_tool_effect',
|
||||
'continue_run',
|
||||
'fork_run',
|
||||
'is_provider_valid',
|
||||
]
|
||||
+7
-7
@@ -15,10 +15,10 @@ from pydantic_ai.models import ModelRequestContext
|
||||
from pydantic_ai.run import AgentRunResult
|
||||
from pydantic_ai.tools import AgentDepsT, RunContext, ToolDefinition
|
||||
|
||||
from pydantic_ai_harness.experimental.step_persistence._context import current_run_id, snapshot_saved
|
||||
from pydantic_ai_harness.experimental.step_persistence._helpers import is_provider_valid
|
||||
from pydantic_ai_harness.experimental.step_persistence._store import InMemoryStepStore, StepStore
|
||||
from pydantic_ai_harness.experimental.step_persistence._types import (
|
||||
from pydantic_ai_harness.step_persistence._context import current_run_id, snapshot_saved
|
||||
from pydantic_ai_harness.step_persistence._helpers import is_provider_valid
|
||||
from pydantic_ai_harness.step_persistence._store import InMemoryStepStore, StepStore
|
||||
from pydantic_ai_harness.step_persistence._types import (
|
||||
ContinuableSnapshot,
|
||||
EventKind,
|
||||
RunRecord,
|
||||
@@ -49,7 +49,7 @@ class StepPersistence(AbstractCapability[AgentDepsT]):
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.step_persistence import StepPersistence, InMemoryStepStore
|
||||
from pydantic_ai_harness.step_persistence import StepPersistence, InMemoryStepStore
|
||||
|
||||
store = InMemoryStepStore()
|
||||
librarian = Agent(
|
||||
@@ -124,12 +124,12 @@ class StepPersistence(AbstractCapability[AgentDepsT]):
|
||||
if backend == 'memory':
|
||||
return cls(store=InMemoryStepStore(), **kwargs)
|
||||
if backend == 'file':
|
||||
from pydantic_ai_harness.experimental.step_persistence._store import FileStepStore
|
||||
from pydantic_ai_harness.step_persistence._store import FileStepStore
|
||||
|
||||
directory = kwargs.pop('directory', '.step-persistence')
|
||||
return cls(store=FileStepStore(directory), **kwargs)
|
||||
if backend == 'sqlite':
|
||||
from pydantic_ai_harness.experimental.step_persistence._store import SqliteStepStore
|
||||
from pydantic_ai_harness.step_persistence._store import SqliteStepStore
|
||||
|
||||
database = kwargs.pop('database', '.step-persistence.db')
|
||||
return cls(store=SqliteStepStore(database=database), **kwargs)
|
||||
+2
-2
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from contextvars import ContextVar
|
||||
|
||||
current_run_id: ContextVar[str | None] = ContextVar(
|
||||
'pydantic_ai_harness.experimental.step_persistence.current_run_id',
|
||||
'pydantic_ai_harness.step_persistence.current_run_id',
|
||||
default=None,
|
||||
)
|
||||
"""Async-context-local pointer to the active `StepPersistence` `run_id`.
|
||||
@@ -20,7 +20,7 @@ import.
|
||||
"""
|
||||
|
||||
snapshot_saved: ContextVar[bool] = ContextVar(
|
||||
'pydantic_ai_harness.experimental.step_persistence.snapshot_saved',
|
||||
'pydantic_ai_harness.step_persistence.snapshot_saved',
|
||||
default=False,
|
||||
)
|
||||
"""Async-context-local flag: did `after_node_run` already save a snapshot this run?
|
||||
+3
-3
@@ -13,9 +13,9 @@ from pydantic_ai.messages import (
|
||||
)
|
||||
from pydantic_ai.tools import RunContext
|
||||
|
||||
from pydantic_ai_harness.experimental.step_persistence._context import current_run_id
|
||||
from pydantic_ai_harness.experimental.step_persistence._store import StepStore
|
||||
from pydantic_ai_harness.experimental.step_persistence._types import ToolEffectRecord
|
||||
from pydantic_ai_harness.step_persistence._context import current_run_id
|
||||
from pydantic_ai_harness.step_persistence._store import StepStore
|
||||
from pydantic_ai_harness.step_persistence._types import ToolEffectRecord
|
||||
|
||||
|
||||
def is_provider_valid(messages: list[ModelMessage]) -> bool:
|
||||
+2
-2
@@ -14,14 +14,14 @@ import anyio.to_thread
|
||||
from pydantic import TypeAdapter
|
||||
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
|
||||
|
||||
from pydantic_ai_harness.experimental.media import (
|
||||
from pydantic_ai_harness.media import (
|
||||
DiskMediaStore,
|
||||
MediaStore,
|
||||
SqliteMediaStore,
|
||||
externalize_media,
|
||||
restore_media,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.step_persistence._types import (
|
||||
from pydantic_ai_harness.step_persistence._types import (
|
||||
ContinuableSnapshot,
|
||||
EventKind,
|
||||
RunRecord,
|
||||
+8
-18
@@ -1,23 +1,13 @@
|
||||
# SubAgents
|
||||
|
||||
> [!WARNING]
|
||||
> **Experimental.** This capability lives under `pydantic_ai_harness.experimental` and may
|
||||
> change or be removed in any release, without a deprecation period. Import it from the
|
||||
> experimental path -- there is no top-level export:
|
||||
> [!NOTE]
|
||||
> Import this capability from its submodule -- there is no top-level `pydantic_ai_harness` re-export:
|
||||
>
|
||||
> ```python
|
||||
> from pydantic_ai_harness.experimental.subagents import SubAgent, SubAgents
|
||||
> from pydantic_ai_harness.subagents import SubAgent, SubAgents
|
||||
> ```
|
||||
>
|
||||
> Importing any experimental capability emits a `HarnessExperimentalWarning`. Silence **all**
|
||||
> harness experimental warnings with a single filter (no per-capability lines needed):
|
||||
>
|
||||
> ```python
|
||||
> import warnings
|
||||
> from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
>
|
||||
> warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
> ```
|
||||
> The API may change between releases. Where practical, breaking changes ship with a deprecation warning.
|
||||
|
||||
Let an agent delegate self-contained tasks to named child agents.
|
||||
|
||||
@@ -31,7 +21,7 @@ A single agent that does everything accumulates a large tool set and a long cont
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.subagents import SubAgent, SubAgents
|
||||
from pydantic_ai_harness.subagents import SubAgent, SubAgents
|
||||
|
||||
researcher = Agent('anthropic:claude-sonnet-4-6', name='researcher', description='Researches a topic and reports findings')
|
||||
writer = Agent('anthropic:claude-sonnet-4-6', name='writer', description='Turns notes into polished prose')
|
||||
@@ -71,7 +61,7 @@ Each `SubAgent` carries its own budgets, so one delegate's controls do not touch
|
||||
|
||||
```python
|
||||
from pydantic_ai.usage import UsageLimits
|
||||
from pydantic_ai_harness.experimental.subagents import SubAgent, SubAgents
|
||||
from pydantic_ai_harness.subagents import SubAgent, SubAgents
|
||||
|
||||
# reproducer and librarian are Agent instances, as in the example above.
|
||||
orchestrator = Agent(
|
||||
@@ -115,7 +105,7 @@ A repo's markdown agent definitions become delegates without writing any `Agent`
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.subagents import SubAgents
|
||||
from pydantic_ai_harness.subagents import SubAgents
|
||||
|
||||
orchestrator = Agent(
|
||||
'anthropic:claude-opus-4-7',
|
||||
@@ -155,7 +145,7 @@ Frontmatter is read by a small, dependency-free parser limited to those keys (`p
|
||||
Disk agents inherit the parent run's model by default. Per agent, the caller can override the model and set a thinking/effort level via `agent_overrides`, keyed by the agent's name:
|
||||
|
||||
```python
|
||||
from pydantic_ai_harness.experimental.subagents import AgentOverride, SubAgents
|
||||
from pydantic_ai_harness.subagents import AgentOverride, SubAgents
|
||||
|
||||
SubAgents(
|
||||
agent_folders='agents',
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Sub-agent capability: delegate self-contained tasks to named child agents."""
|
||||
|
||||
from pydantic_ai_harness.subagents._capability import SubAgents, ToolResolver
|
||||
from pydantic_ai_harness.subagents._disk import AgentOverride
|
||||
from pydantic_ai_harness.subagents._effort import MINIMUM_EFFORT_FLOOR, clamp_effort
|
||||
from pydantic_ai_harness.subagents._toolset import SubAgent, SubAgentToolset
|
||||
|
||||
__all__ = [
|
||||
'MINIMUM_EFFORT_FLOOR',
|
||||
'AgentOverride',
|
||||
'SubAgent',
|
||||
'SubAgentToolset',
|
||||
'SubAgents',
|
||||
'ToolResolver',
|
||||
'clamp_effort',
|
||||
]
|
||||
+4
-4
@@ -14,14 +14,14 @@ from pydantic_ai.settings import ModelSettings
|
||||
from pydantic_ai.tools import AgentDepsT, RunContext
|
||||
from pydantic_ai.toolsets import AgentToolset
|
||||
|
||||
from pydantic_ai_harness.experimental.subagents._disk import (
|
||||
from pydantic_ai_harness.subagents._disk import (
|
||||
AgentOverride,
|
||||
ParsedAgent,
|
||||
parse_agent_markdown,
|
||||
resolve_folders,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.subagents._effort import clamp_effort
|
||||
from pydantic_ai_harness.experimental.subagents._toolset import SubAgent, SubAgentToolset
|
||||
from pydantic_ai_harness.subagents._effort import clamp_effort
|
||||
from pydantic_ai_harness.subagents._toolset import SubAgent, SubAgentToolset
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic_ai._instructions import AgentInstructions
|
||||
@@ -66,7 +66,7 @@ class SubAgents(AbstractCapability[AgentDepsT]):
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai_harness.experimental.subagents import SubAgent, SubAgents
|
||||
from pydantic_ai_harness.subagents import SubAgent, SubAgents
|
||||
|
||||
researcher = Agent('anthropic:claude-sonnet-4-6', name='researcher', description='Researches topics')
|
||||
writer = Agent('anthropic:claude-sonnet-4-6', name='writer', description='Writes prose')
|
||||
+9
-9
@@ -1,4 +1,4 @@
|
||||
"""Tests for pydantic_ai_harness.experimental.compaction capabilities."""
|
||||
"""Tests for pydantic_ai_harness.compaction capabilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -22,7 +22,7 @@ from pydantic_ai.models import Model, ModelRequestContext, ModelRequestParameter
|
||||
from pydantic_ai.models.test import TestModel
|
||||
from pydantic_ai.usage import RunUsage
|
||||
|
||||
from pydantic_ai_harness.experimental.compaction import (
|
||||
from pydantic_ai_harness.compaction import (
|
||||
ClampOversizedMessages,
|
||||
ClearToolResults,
|
||||
DeduplicateFileReads,
|
||||
@@ -32,11 +32,11 @@ from pydantic_ai_harness.experimental.compaction import (
|
||||
TieredCompaction,
|
||||
estimate_token_count,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.compaction._clamp_oversized_messages import (
|
||||
from pydantic_ai_harness.compaction._clamp_oversized_messages import (
|
||||
_CLAMP_ARGS_KEY,
|
||||
_CLAMP_MARKER,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
from pydantic_ai_harness.compaction._shared import (
|
||||
_history_changed,
|
||||
_is_safe_cutoff,
|
||||
compact_with_span,
|
||||
@@ -46,7 +46,7 @@ from pydantic_ai_harness.experimental.compaction._shared import (
|
||||
iter_tool_pairs,
|
||||
prepend_first_user_message,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.compaction._summarizing_compaction import (
|
||||
from pydantic_ai_harness.compaction._summarizing_compaction import (
|
||||
_SUMMARY_PREFIX,
|
||||
_extract_previous_summary,
|
||||
_extract_system_prompts,
|
||||
@@ -699,9 +699,9 @@ class TestExtractSystemPrompts:
|
||||
|
||||
|
||||
class TestExports:
|
||||
def test_exposed_under_experimental_only(self):
|
||||
def test_exposed_under_submodule_only(self):
|
||||
import pydantic_ai_harness
|
||||
import pydantic_ai_harness.experimental.compaction as compaction
|
||||
import pydantic_ai_harness.compaction as compaction
|
||||
|
||||
names = [
|
||||
'SlidingWindow',
|
||||
@@ -712,7 +712,7 @@ class TestExports:
|
||||
'TieredCompaction',
|
||||
]
|
||||
for name in names:
|
||||
# Available from the experimental package...
|
||||
# Available from the capability submodule...
|
||||
assert hasattr(compaction, name)
|
||||
# ...and deliberately NOT from the top-level namespace.
|
||||
assert not hasattr(pydantic_ai_harness, name)
|
||||
@@ -1796,7 +1796,7 @@ class TestSummarizingCompactionModel:
|
||||
assert mock_agent_instance.run.call_args.kwargs['usage'] is ctx.usage
|
||||
|
||||
def test_default_prompt_has_structured_sections(self):
|
||||
from pydantic_ai_harness.experimental.compaction._summarizing_compaction import _DEFAULT_SUMMARY_PROMPT
|
||||
from pydantic_ai_harness.compaction._summarizing_compaction import _DEFAULT_SUMMARY_PROMPT
|
||||
|
||||
for heading in (
|
||||
'## Intent',
|
||||
@@ -12,14 +12,14 @@ from pydantic_ai.models.test import TestModel
|
||||
from pydantic_ai.tools import ToolDefinition
|
||||
from pydantic_ai.usage import RunUsage
|
||||
|
||||
from pydantic_ai_harness.experimental.context import (
|
||||
from pydantic_ai_harness.context import (
|
||||
AgentContextInventory,
|
||||
ContextFile,
|
||||
RepoContext,
|
||||
RepoContextToolset,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.context._inventory import scan_assets
|
||||
from pydantic_ai_harness.experimental.context._loader import (
|
||||
from pydantic_ai_harness.context._inventory import scan_assets
|
||||
from pydantic_ai_harness.context._loader import (
|
||||
discover_instruction_files,
|
||||
find_dir_context_file,
|
||||
render_context_files,
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
@@ -12,8 +11,7 @@ from pydantic_ai import Agent
|
||||
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart, ToolCallPart, ToolReturnPart
|
||||
from pydantic_ai.models.function import AgentInfo, FunctionModel
|
||||
|
||||
from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
from pydantic_ai_harness.experimental.docs import PyaiDocs, PyaiDocsToolset, PyaiDocsTopic
|
||||
from pydantic_ai_harness.docs import PyaiDocs, PyaiDocsToolset, PyaiDocsTopic
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
@@ -175,9 +173,3 @@ class TestThroughAgent:
|
||||
if isinstance(part, ToolReturnPart) and part.tool_name == 'read_pyai_docs'
|
||||
]
|
||||
assert returns == ['# Capabilities doc']
|
||||
|
||||
|
||||
def test_import_emits_experimental_warning() -> None:
|
||||
module = importlib.import_module('pydantic_ai_harness.experimental.docs')
|
||||
with pytest.warns(HarnessExperimentalWarning, match='docs'):
|
||||
importlib.reload(module)
|
||||
+2
-2
@@ -29,7 +29,7 @@ from pydantic_ai.usage import RunUsage, UsageLimits
|
||||
from pydantic_core import core_schema
|
||||
|
||||
from pydantic_ai_harness.code_mode import CodeMode
|
||||
from pydantic_ai_harness.experimental.dynamic_workflow import (
|
||||
from pydantic_ai_harness.dynamic_workflow import (
|
||||
DynamicWorkflow,
|
||||
DynamicWorkflowToolset,
|
||||
WorkflowAgent,
|
||||
@@ -995,7 +995,7 @@ async def test_sandbox_panic_after_budget_exhaustion_returns_terminal_result(
|
||||
await self.dispatch('counted', {'task': 'second'})
|
||||
raise PanicException('sandbox panic')
|
||||
|
||||
monkeypatch.setattr('pydantic_ai_harness.experimental.dynamic_workflow._toolset.MontyExecutor', FakeExecutor)
|
||||
monkeypatch.setattr('pydantic_ai_harness.dynamic_workflow._toolset.MontyExecutor', FakeExecutor)
|
||||
ts = DynamicWorkflowToolset[object](agents=[_wf_agent('counted-result', 'counted')], max_agent_calls=1)
|
||||
out = await _run_script(ts, '1 + 1')
|
||||
assert isinstance(out, dict)
|
||||
@@ -0,0 +1,41 @@
|
||||
"""The old `experimental.<name>` paths still import, with a `DeprecationWarning` to the new path.
|
||||
|
||||
Each capability that graduated out of `experimental` leaves a compatibility shim at its former
|
||||
path. Importing the shim must keep working (re-exporting the moved capability) and must warn.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
# (old experimental subpackage, new top-level module, a public symbol to check re-export)
|
||||
_SHIMS = [
|
||||
('authoring', 'runtime_authoring', 'RuntimeAuthoring'),
|
||||
('overflow', 'overflowing_tool_output', 'OverflowingToolOutput'),
|
||||
('compaction', 'compaction', 'TieredCompaction'),
|
||||
('context', 'context', 'RepoContext'),
|
||||
('docs', 'docs', 'PyaiDocs'),
|
||||
('dynamic_workflow', 'dynamic_workflow', 'DynamicWorkflow'),
|
||||
('media', 'media', 'S3MediaStore'),
|
||||
('planning', 'planning', 'Planning'),
|
||||
('step_persistence', 'step_persistence', 'StepPersistence'),
|
||||
('subagents', 'subagents', 'SubAgents'),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('old, new, symbol', _SHIMS)
|
||||
def test_shim_warns_and_reexports(old: str, new: str, symbol: str) -> None:
|
||||
# Import once quietly so the deprecation fires on the reload we assert on, even if
|
||||
# another test already imported the shim.
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
shim = importlib.import_module(f'pydantic_ai_harness.experimental.{old}')
|
||||
|
||||
with pytest.warns(DeprecationWarning, match=rf'experimental\.{old}` has moved'):
|
||||
importlib.reload(shim)
|
||||
|
||||
new_module = importlib.import_module(f'pydantic_ai_harness.{new}')
|
||||
assert getattr(shim, symbol) is getattr(new_module, symbol)
|
||||
@@ -1,42 +0,0 @@
|
||||
"""Tests for the experimental-capability warning convention."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
||||
from pydantic_ai_harness.experimental._warn import warn_experimental
|
||||
|
||||
|
||||
class TestExperimentalWarning:
|
||||
def test_message_names_feature_and_carries_silence_snippet(self) -> None:
|
||||
with pytest.warns(HarnessExperimentalWarning) as rec:
|
||||
warn_experimental('compaction')
|
||||
assert len(rec) == 1
|
||||
msg = str(rec[0].message)
|
||||
assert '`pydantic_ai_harness.experimental.compaction`' in msg
|
||||
# The message must hand the user the exact, category-wide silence line.
|
||||
assert "warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)" in msg
|
||||
|
||||
def test_one_filter_silences_every_capability(self) -> None:
|
||||
# A single category filter mutes all experimental warnings — no per-capability lines.
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('error') # baseline: any warning is an error
|
||||
warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
|
||||
warn_experimental('compaction')
|
||||
warn_experimental('some_future_capability') # also silenced, same filter
|
||||
|
||||
@pytest.mark.parametrize('feature', ['compaction', 'subagents', 'dynamic_workflow'])
|
||||
def test_importing_a_capability_warns(self, feature: str) -> None:
|
||||
module = importlib.import_module(f'pydantic_ai_harness.experimental.{feature}')
|
||||
with pytest.warns(HarnessExperimentalWarning):
|
||||
importlib.reload(module)
|
||||
|
||||
@pytest.mark.parametrize('feature', ['step_persistence', 'media'])
|
||||
def test_importing_step_persistence_or_media_warns(self, feature: str) -> None:
|
||||
module = importlib.import_module(f'pydantic_ai_harness.experimental.{feature}')
|
||||
with pytest.warns(HarnessExperimentalWarning, match=feature):
|
||||
importlib.reload(module)
|
||||
@@ -24,7 +24,6 @@ import pytest
|
||||
if TYPE_CHECKING:
|
||||
from vcr.request import Request as VcrRequest # pyright: ignore[reportMissingTypeStubs]
|
||||
|
||||
|
||||
# Public placeholders baked into the committed cassettes. Tests pass
|
||||
# these *exact* values when constructing `S3MediaStore`, so the replay
|
||||
# URI matches the recorded URI even though both were sanitised.
|
||||
@@ -194,7 +193,7 @@ def s3_store(s3_credentials: dict[str, str]) -> Any:
|
||||
The key prefix is part of the URL path that lands in the cassette, so
|
||||
keep it stable across re-records.
|
||||
"""
|
||||
from pydantic_ai_harness.experimental.media import S3MediaStore
|
||||
from pydantic_ai_harness.media import S3MediaStore
|
||||
|
||||
return S3MediaStore(
|
||||
bucket=s3_credentials['bucket'],
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for `pydantic_ai_harness.experimental.media`: stores + walker + SigV4 + S3 store."""
|
||||
"""Tests for `pydantic_ai_harness.media`: stores + walker + SigV4 + S3 store."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
from httpx import AsyncClient, MockTransport, Request, Response
|
||||
|
||||
from pydantic_ai_harness.experimental.media import (
|
||||
from pydantic_ai_harness.media import (
|
||||
DiskMediaStore,
|
||||
MediaContext,
|
||||
MediaStore,
|
||||
@@ -22,7 +22,7 @@ from pydantic_ai_harness.experimental.media import (
|
||||
parse_media_uri,
|
||||
restore_media,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.media._s3 import sign_request
|
||||
from pydantic_ai_harness.media._s3 import sign_request
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
@@ -387,7 +387,7 @@ class TestS3MediaStoreWithMockTransport:
|
||||
Otherwise the signed canonical path and the path S3 receives diverge ->
|
||||
SignatureDoesNotMatch. The wire `raw_path` must equal `_canonical_uri(path)`.
|
||||
"""
|
||||
from pydantic_ai_harness.experimental.media._s3 import _canonical_uri # pyright: ignore[reportPrivateUsage]
|
||||
from pydantic_ai_harness.media._s3 import _canonical_uri # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
captured: list[Request] = []
|
||||
|
||||
@@ -24,7 +24,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pydantic_ai_harness.experimental.media import MediaContext, S3MediaStore, media_uri_for
|
||||
from pydantic_ai_harness.media import MediaContext, S3MediaStore, media_uri_for
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
"""Tests for pydantic_ai_harness.experimental.overflow."""
|
||||
"""Tests for pydantic_ai_harness.overflowing_tool_output."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -26,7 +26,7 @@ from pydantic_ai.models.test import TestModel
|
||||
from pydantic_ai.tools import ToolDefinition
|
||||
from pydantic_ai.usage import RunUsage
|
||||
|
||||
from pydantic_ai_harness.experimental.overflow import (
|
||||
from pydantic_ai_harness.overflowing_tool_output import (
|
||||
Band,
|
||||
LocalFileStore,
|
||||
OverflowingToolOutput,
|
||||
@@ -36,7 +36,7 @@ from pydantic_ai_harness.experimental.overflow import (
|
||||
Truncate,
|
||||
TruncationStrategy,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._capability import (
|
||||
from pydantic_ai_harness.overflowing_tool_output._capability import (
|
||||
READ_TOOL_NAME,
|
||||
_build_spill_preview,
|
||||
_handle_key,
|
||||
@@ -46,7 +46,7 @@ from pydantic_ai_harness.experimental.overflow._capability import (
|
||||
_Unit,
|
||||
_with_handles,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._payload import (
|
||||
from pydantic_ai_harness.overflowing_tool_output._payload import (
|
||||
is_binary,
|
||||
json_sketch,
|
||||
measure,
|
||||
@@ -55,7 +55,7 @@ from pydantic_ai_harness.experimental.overflow._payload import (
|
||||
to_text,
|
||||
truncate_text,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.overflow._store import _safe_segment
|
||||
from pydantic_ai_harness.overflowing_tool_output._store import _safe_segment
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -19,8 +19,8 @@ from pydantic_ai.models import ModelRequestContext, ModelRequestParameters
|
||||
from pydantic_ai.models.function import AgentInfo, FunctionModel
|
||||
from pydantic_ai.models.test import TestModel
|
||||
|
||||
from pydantic_ai_harness.experimental.planning import PlanItem, Planning, PlanningToolset, TaskStatus
|
||||
from pydantic_ai_harness.experimental.planning._toolset import PlanState, render_plan
|
||||
from pydantic_ai_harness.planning import PlanItem, Planning, PlanningToolset, TaskStatus
|
||||
from pydantic_ai_harness.planning._toolset import PlanState, render_plan
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
+2
-11
@@ -15,13 +15,13 @@ from pydantic_ai.exceptions import ModelRetry
|
||||
from pydantic_ai.messages import ToolReturnPart
|
||||
from pydantic_ai.models.test import TestModel
|
||||
|
||||
from pydantic_ai_harness.experimental.authoring import (
|
||||
from pydantic_ai_harness.runtime_authoring import (
|
||||
AuthoringToolset,
|
||||
CapabilityStore,
|
||||
CapabilityValidationError,
|
||||
RuntimeAuthoring,
|
||||
)
|
||||
from pydantic_ai_harness.experimental.authoring._validate import (
|
||||
from pydantic_ai_harness.runtime_authoring._validate import (
|
||||
load_capability_instance,
|
||||
validate_capability_file,
|
||||
)
|
||||
@@ -41,7 +41,6 @@ from dataclasses import dataclass
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
from pydantic_ai.toolsets import FunctionToolset
|
||||
|
||||
|
||||
class _MarkerToolset(FunctionToolset):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -50,7 +49,6 @@ class _MarkerToolset(FunctionToolset):
|
||||
async def marker(self) -> str:
|
||||
return 'marker-result'
|
||||
|
||||
|
||||
@dataclass
|
||||
class MarkerCapability(AbstractCapability):
|
||||
def get_instructions(self):
|
||||
@@ -67,12 +65,10 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class CapA(AbstractCapability):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class CapB(AbstractCapability):
|
||||
pass
|
||||
@@ -83,7 +79,6 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class NeedsArg(AbstractCapability):
|
||||
value: int
|
||||
@@ -94,7 +89,6 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class BadGetter(AbstractCapability):
|
||||
def get_toolset(self):
|
||||
@@ -111,7 +105,6 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class {class_name}(AbstractCapability):
|
||||
def get_instructions(self):
|
||||
@@ -124,7 +117,6 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class BadInstructions(AbstractCapability):
|
||||
def get_instructions(self):
|
||||
@@ -136,7 +128,6 @@ from dataclasses import dataclass
|
||||
|
||||
from pydantic_ai.capabilities import AbstractCapability
|
||||
|
||||
|
||||
@dataclass
|
||||
class BadModelSettings(AbstractCapability):
|
||||
def get_model_settings(self):
|
||||
+2
-2
@@ -25,8 +25,8 @@ from pydantic_ai.messages import (
|
||||
)
|
||||
from pydantic_ai.models.test import TestModel
|
||||
|
||||
from pydantic_ai_harness.experimental.media import DiskMediaStore, SqliteMediaStore
|
||||
from pydantic_ai_harness.experimental.step_persistence import (
|
||||
from pydantic_ai_harness.media import DiskMediaStore, SqliteMediaStore
|
||||
from pydantic_ai_harness.step_persistence import (
|
||||
ContinuableSnapshot,
|
||||
FileStepStore,
|
||||
RunRecord,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user