Agents
Build stateful chat agents with structured results and streaming events, or use tools, document extraction, data enrichment, and data-source summarization in your own workflows.
Chat sessions
Import ChatSession and ChatInput from tabulaflow.agents.
A session runs one turn at a time. run(...) returns a ChatResult;
run_stream(...) yields semantic events and ends with TurnFinished on
normal completion. Failures and cancellation propagate as exceptions.
ChatSession is an async context manager and also exposes aclose(). It does
not close the registry or connectors supplied by the application.
For interruption, cancel and await the task consuming the stream before
starting another turn. reset_conversation() clears conversation context
while keeping connectors and stored outputs. Automatic context compaction
is enabled by default; pass compaction=None to disable it.
ChatSession
ChatSession(
registry: DataConnectorRegistry,
*,
model: str,
reasoning: ReasoningLevel,
service_tier: ServiceTier | None = None,
subagent_model: str = DEFAULT_SUBAGENT_MODEL,
subagent_reasoning: ReasoningLevel = DEFAULT_SUBAGENT_REASONING,
fanout_concurrency: int = 200,
use_apply_patch: bool = False,
extra_instructions: str | None = None,
trajectory_log_dir: Path | None = None,
workspace: SQLConnector | None = None,
project_dir: Path | None = None,
scratch_dir: Path | None = None,
data_dir: Path | None = None,
data_source_definitions: Sequence[DataSourceDefinition]
| None = None,
data_source_connector_configs: DataSourceConnectorConfigs
| None = None,
compaction: CompactionConfig
| None = CompactionConfig(),
)
Stateful runtime for one interactive data conversation.
A session owns conversation history, tools, outputs, and model state and runs
one turn at a time. Call :meth:aclose when the session is no longer needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
Data sources available to the conversation. |
required |
model
|
str
|
Provider-qualified model identifier for the interactive agent. |
required |
reasoning
|
ReasoningLevel
|
Provider-neutral reasoning level for the main model. |
required |
service_tier
|
ServiceTier | None
|
Optional provider service tier. |
None
|
subagent_model
|
str
|
Model used by fan-out and extraction helpers. |
DEFAULT_SUBAGENT_MODEL
|
subagent_reasoning
|
ReasoningLevel
|
Reasoning level for helper models. |
DEFAULT_SUBAGENT_REASONING
|
use_apply_patch
|
bool
|
Use |
False
|
extra_instructions
|
str | None
|
Instructions appended to the fixed baseline prompt. |
None
|
trajectory_log_dir
|
Path | None
|
Optional directory for conversation trajectories. |
None
|
workspace
|
SQLConnector | None
|
Writable SQL scratch database for derived data and message spill. |
None
|
project_dir
|
Path | None
|
Host project directory; enables filesystem tools. |
None
|
scratch_dir
|
Path | None
|
Transient directory exposed to shell workflows. |
None
|
data_dir
|
Path | None
|
Directory where connected file sources are materialized. |
None
|
data_source_definitions
|
Sequence[DataSourceDefinition] | None
|
Curated data sources available to the connection tool. |
None
|
compaction
|
CompactionConfig | None
|
Automatic context-compaction policy, or |
CompactionConfig()
|
output_store
property
output_store: OutputStore
The live output store — results the agent's answers reference.
last_usage
property
last_usage: Usage | None
Latest turn usage, including partial usage after interruption, or None.
model
property
model: str
Active interactive model; change it through :meth:activate_llm_profile.
subagent_model
property
subagent_model: str
Active model for fan-out and extraction helpers.
subagent_reasoning
property
subagent_reasoning: ReasoningLevel
Active reasoning level for helper models.
run
async
run(question: ChatInput) -> ChatResult
Non-streaming convenience: run a turn and return its ChatResult.
Equivalent to draining run_stream and taking the terminal TurnFinished
payload — for callers (tests, batch jobs) that want the result, not the live
events. Cancellation and the one-turn-at-a-time guard behave as in
run_stream.
run_stream
async
Run the agent on a user question, yielding progress as ChatEvents.
The stream ends with exactly one TurnFinished (carrying the ChatResult)
on normal completion. Failures propagate as exceptions. To interrupt, cancel
the task iterating this generator: it raises CancelledError and the
agent's message history / last_usage are left reflecting the partial run.
The agent loop runs as a background task (_run_to_queue) that pushes
events onto a queue; this is what lets fan-out tools' progress callbacks
(which fire deep inside tool execution, not at a yield) reach the
consumer live. The producer signals end-of-stream with a None sentinel.
A ChatSession runs one turn at a time — its conversation state is mutable,
so calling this while a turn is already in flight raises RuntimeError
rather than silently corrupting history. Run separate conversations on
separate ChatSession instances.
activate_llm_profile
activate_llm_profile(
*,
model: str,
reasoning: ReasoningLevel,
subagent_model: str,
subagent_reasoning: ReasoningLevel,
use_apply_patch: bool,
) -> tuple[str | None, str | None]
Atomically activate main and subagent LLM profiles.
Conversation, query, tool, and message-store state remain attached to
this ChatSession. Provider runtimes are prepared before the live
profile is changed, so a construction failure leaves the old profile
usable. A main-model change is recorded in the conversation history
(see _note_profile_change). Returns the API keys resolved during
preparation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
New interactive model identifier. |
required |
reasoning
|
ReasoningLevel
|
New interactive reasoning level. |
required |
subagent_model
|
str
|
New helper model identifier. |
required |
subagent_reasoning
|
ReasoningLevel
|
New helper reasoning level. |
required |
use_apply_patch
|
bool
|
Whether to use |
required |
Returns:
| Type | Description |
|---|---|
tuple[str | None, str | None]
|
Raw main and subagent API keys, when their resolved clients use keys. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If a turn is active. |
note_event
note_event(description: str) -> None
Make the agent aware of a host/app event (typically a user action — e.g.
connecting a data source, uploading a file) by appending it to the
conversation. The caller supplies description in its own domain terms;
the agent owns how it enters the conversation: a system-tagged turn in the
message history.
Events go in the message history, not the system instructions, on purpose: the instructions stay static so the model's large prompt prefix is fully prompt-cached, and each event is a pure append to the history tail — itself cache-friendly. The message also gives the agent temporal awareness (it knows the event just happened).
reset_conversation
reset_conversation() -> None
Start a fresh conversation while preserving the session environment.
aclose
async
aclose() -> None
Release session-scoped resources, including active shell jobs.
ChatInput
module-attribute
ChatInput: TypeAlias = str | Sequence[str | BinaryContent]
CompactionConfig
dataclass
CompactionConfig(
trigger_tokens: int = 240000, target_tokens: int = 32000
)
User-facing controls for automatic conversation compaction.
trigger_tokens starts checkpointing and target_tokens bounds the rewritten
history.
trigger_tokens
class-attribute
instance-attribute
trigger_tokens: int = 240000
target_tokens
class-attribute
instance-attribute
target_tokens: int = 32000
Events and turn results
These types are also available from tabulaflow.agents.chat.
ToolStarted identifies a tool call, AnswerDelta carries answer text, and
TurnFinished carries the complete result. Other events report narration,
tool progress, usage, and context compaction.
The chat ⇄ frontend event contract.
ChatSession.run_stream() yields a stream of these events; any frontend (the TUI, a
browser client, a CLI logger, a test harness) consumes the stream and decides how
to render each one. Events are semantic — they carry the data of what the
agent did, never pre-rendered presentation — so a frontend renders / words /
truncates however it wants. Rendering is deliberately the frontend's job; this
module ships no summarizers.
They're pydantic models forming a discriminated union on kind (consistent
with the other serialized schema models), which gives a frontend free, robust, two-way
wire (de)serialization:
raw = event.model_dump_json() # produce (server)
event = TypeAdapter(ChatEvent).validate_json(raw) # consume (client)
The stream ends with exactly one TurnFinished (carrying the result) on normal
completion. Failures propagate as exceptions; an interrupted run raises
CancelledError and the agent's message history / usage reflect the partial run.
ChatEvent
module-attribute
ChatEvent: TypeAlias = Annotated[
Union[
AnswerDelta,
NarrationDelta,
ThinkingDelta,
ToolStarted,
ToolFinished,
ToolProgress,
UsageUpdated,
CompactionStarted,
CompactionFinished,
TurnFinished,
],
Field(discriminator="kind"),
]
ChatResult
Bases: BaseModel
Logical result of one chat turn.
text
instance-attribute
text: str
AnswerDelta
Bases: _ChatEvent
A chunk of the assistant's streaming final answer (the user-facing reply).
kind
class-attribute
instance-attribute
kind: Literal['answer_delta'] = 'answer_delta'
content
instance-attribute
content: str
NarrationDelta
Bases: _ChatEvent
A chunk of mid-turn narration — text the model emits while working, before its
final answer. Distinct from AnswerDelta so a frontend can drop or dim it
separately (the TUI drops it; a webapp might show it greyed).
kind
class-attribute
instance-attribute
kind: Literal['narration_delta'] = 'narration_delta'
content
instance-attribute
content: str
ThinkingDelta
Bases: _ChatEvent
A chunk of the model's reasoning summary (reasoning models only). Distinct
from AnswerDelta so a frontend can show / collapse it separately from the answer.
kind
class-attribute
instance-attribute
kind: Literal['thinking_delta'] = 'thinking_delta'
content
instance-attribute
content: str
ToolStarted
Bases: _ChatEvent
The agent invoked a tool. args is the raw tool-call arguments (lossless,
so a frontend can show the full query / spec, or render its own compact line).
kind
class-attribute
instance-attribute
kind: Literal['tool_started'] = 'tool_started'
tool_call_id
instance-attribute
tool_call_id: str
name
instance-attribute
name: str
args
instance-attribute
args: dict[str, Any]
ToolFinished
Bases: _ChatEvent
A tool call returned. outcome is structured so a frontend can reword it;
None means plain completion with no suffix-worthy fact. The full result
(if any) arrives later in TurnFinished.result.
kind
class-attribute
instance-attribute
kind: Literal['tool_finished'] = 'tool_finished'
tool_call_id
instance-attribute
tool_call_id: str
name
instance-attribute
name: str
ToolProgress
Bases: _ChatEvent
Progress within a long-running / fan-out tool (e.g. per-row subagents).
tool_call_id identifies which in-flight tool when several run at once;
None means "the current fan-out" for simple single-tool cases.
kind
class-attribute
instance-attribute
kind: Literal['tool_progress'] = 'tool_progress'
completed
instance-attribute
completed: int
total
instance-attribute
total: int | None
unit
class-attribute
instance-attribute
unit: str | None = None
stage
class-attribute
instance-attribute
stage: str | None = None
tool_call_id
class-attribute
instance-attribute
tool_call_id: str | None = None
UsageUpdated
Bases: _ChatEvent
Cumulative token/cost usage so far this turn (for a live cost readout).
kind
class-attribute
instance-attribute
kind: Literal['usage_updated'] = 'usage_updated'
CompactionStarted
Bases: _ChatEvent
The session started compacting context before the pending user turn.
kind
class-attribute
instance-attribute
kind: Literal['compaction_started'] = 'compaction_started'
CompactionFinished
Bases: _ChatEvent
Context compaction ended and normal processing is resuming.
kind
class-attribute
instance-attribute
kind: Literal["compaction_finished"] = "compaction_finished"
TurnFinished
Bases: _ChatEvent
The turn completed normally; carries the full result. The only terminal event — failures and interrupts surface as exceptions on the iterator, not here.
kind
class-attribute
instance-attribute
kind: Literal['turn_finished'] = 'turn_finished'
DataFrame enrichment
Add typed columns using each row's supplied content. See the job enrichment example.
DataFrameEnricher
DataFrameEnricher(
*,
llm: str | Model = "openai:gpt-5.6-luna",
model_settings: ModelSettings | None = None,
max_concurrency: int = 200,
enable_browser_tools: bool = False,
enable_run_query_tool: bool = False,
registry: DataConnectorRegistry | None = None,
)
Enrich DataFrame rows with typed fields using an LLM.
Rows are processed concurrently using their supplied content and optional browser or database tools. Each row produces a record validated by the supplied Pydantic model, preserving required fields, defaults, constraints, and nullability.
Initialize the enricher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
str | Model
|
LLM identifier or model object used for row enrichment. |
'openai:gpt-5.6-luna'
|
model_settings
|
ModelSettings | None
|
Optional settings passed to each agent. |
None
|
max_concurrency
|
int
|
Maximum concurrent row tasks across all calls on this instance. |
200
|
enable_browser_tools
|
bool
|
Give each row agent its own browser tools. |
False
|
enable_run_query_tool
|
bool
|
Give row agents a tool to query and modify
registered data sources. Requires |
False
|
registry
|
DataConnectorRegistry | None
|
Data sources available to the optional query tool. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If concurrency is not positive or query tools lack a registry. |
enrich
async
enrich(
df: DataFrame,
*,
record_type: type[BaseModel],
instruction: str,
) -> DataFrame
Return a copy with the model's fields added or replaced as columns.
Input order, index (including duplicate labels), and other columns are preserved. Scalar outputs use nullable pandas dtypes; dates and datetimes remain Python objects. Literal and enum outputs are categorical.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Input rows with unique string column names. Images and PDFs in supported inline forms are attached to the row's prompt. |
required |
record_type
|
type[BaseModel]
|
Pydantic model class defining a non-empty, flat record. Fields may use str, int, float, bool, date, datetime, scalar Literal choices or enums, and nullable forms of those types. Model field names become column names, regardless of aliases. |
required |
instruction
|
str
|
Jinja2 template referencing input columns, such as
|
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A new DataFrame with one output row per input row. An empty input |
DataFrame
|
returns an empty frame with the declared output columns. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
RuntimeError
|
If any agent fails or aborts. Pending row tasks are cancelled and awaited; the input DataFrame is never modified. |
Extraction and summarization
These services can be used directly without a chat session. Import
EntityExtractor from tabulaflow.agents.extraction. Pass a Pydantic model class
to extract(..., record_type=Place, instruction=...) to receive a list[Place].
EntityExtractor
EntityExtractor(
*,
llm: str | Model = "openai:gpt-5.6-luna",
model_settings: ModelSettings | None = None,
max_concurrency: int = 200,
chunk_target: int = DEFAULT_TARGET_CHARS,
chunk_max: int = DEFAULT_MAX_CHARS,
)
Extract structured entities from document text or media with an LLM.
Long documents are split into chunks and processed concurrently. Each result is an instance of the supplied model, preserving its validation and defaults. Records are returned in chunk order; duplicates across chunks are not removed. The concurrency limit is shared across calls, while each call owns its record type and agent.
Initialize the extractor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
str | Model
|
LLM identifier or model object used by per-chunk extraction subagents. |
'openai:gpt-5.6-luna'
|
model_settings
|
ModelSettings | None
|
Optional pydantic-ai model settings passed to each subagent run. |
None
|
max_concurrency
|
int
|
Maximum number of chunk subagents to run concurrently
across all |
200
|
chunk_target
|
int
|
Soft per-chunk size the splitter packs toward — tunes density for many-small-entity documents. |
DEFAULT_TARGET_CHARS
|
chunk_max
|
int
|
Hard per-chunk ceiling; the only size at which a single block is split. A larger entity stays whole up to this. |
DEFAULT_MAX_CHARS
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a concurrency or chunk size argument is not positive. |
llm
instance-attribute
llm = llm
model_settings
instance-attribute
model_settings = model_settings
chunk_target
instance-attribute
chunk_target = chunk_target
chunk_max
instance-attribute
chunk_max = chunk_max
apply_llm_profile
apply_llm_profile(
*,
llm: str | Model,
model_settings: ModelSettings | None,
) -> None
Apply the LLM profile used by per-chunk extraction subagents.
extract
async
extract(
content: str | BinaryContent | Sequence[BinaryContent],
*,
record_type: type[_EntityT],
instruction: str,
doc_context: str | None = None,
on_chunk_complete: Callable[
[int, AgentRunResult[list[_EntityT]]], None
]
| None = None,
) -> list[_EntityT]
Extract validated records from one document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
str | BinaryContent | Sequence[BinaryContent]
|
Document text, validated image/PDF media, or an ordered collection of validated image/PDF media. |
required |
record_type
|
type[_EntityT]
|
Pydantic model class describing one record. Required values, defaults, constraints, and nullability are preserved. |
required |
instruction
|
str
|
Natural-language description of what one entity is and how to populate the declared fields. |
required |
doc_context
|
str | None
|
Optional document-level context (e.g. |
None
|
on_chunk_complete
|
Callable[[int, AgentRunResult[list[_EntityT]]], None] | None
|
Optional callback invoked as each chunk finishes, with its 1-based index and agent run result (records, messages, and usage). Called in completion order. Callback errors propagate to the caller. |
None
|
Returns:
| Type | Description |
|---|---|
list[_EntityT]
|
Instances of |
list[_EntityT]
|
or contains no matching records. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
DataSourceSummarizer
DataSourceSummarizer(
llm: str = "openai:gpt-5.6-sol",
reasoning: ReasoningLevel | None = "high",
max_words: int = 4000,
model_settings: ModelSettings | None = None,
)
Produce schema-sensitive, cached Markdown summaries for data connectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
str
|
Model used to generate non-trivial summaries. |
'openai:gpt-5.6-sol'
|
reasoning
|
ReasoningLevel | None
|
Provider-neutral reasoning level. |
'high'
|
max_words
|
int
|
Requested summary length ceiling. |
4000
|
model_settings
|
ModelSettings | None
|
Additional Pydantic AI model settings. |
None
|
llm
instance-attribute
llm = llm
reasoning
instance-attribute
reasoning = reasoning
max_words
instance-attribute
max_words = max_words
model_settings
instance-attribute
model_settings = model_settings
summarize
async
summarize(connector: DataConnector) -> str
Return a Markdown summary, loading or writing the semantic disk cache.
Data tools
ConnectDataSourceTool
ConnectDataSourceTool(
registry: DataConnectorRegistry,
data_dir: Path,
*,
definitions: Sequence[
DataSourceDefinition
] = DEFAULT_DATA_SOURCE_DEFINITIONS,
configs: DataSourceConnectorConfigs | None = None,
)
Connect an existing file, connector URL, or HuggingFace dataset as a read-only source.
name
class-attribute
instance-attribute
name: ClassVar = 'connect_data_source'
__call__
async
__call__(source: str, alias: str) -> str
Connect an existing data source as a read-only queryable source, for data that already exists in finished form and should be queried as-is.
Accepts one of:
- Curated source — for example wikidata.
- Local data file — a path ending in .csv, .tsv, .json, .parquet, .xlsx, or .xls.
- Local database file — a path ending in .sqlite, .sqlite3, .db, or .duckdb.
- Connector URL — e.g. postgresql://, mysql://, bigquery://, snowflake://,
neo4j+s://, bolt://, or sparql+https://. For Neo4j, preserve the exact
deployment-provided scheme because it determines routing, TLS, and
certificate verification.
A source needing a password that isn't in the URL is deferred to the user.
- HuggingFace dataset — a https://huggingface.co/datasets/
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
The source to connect, in one of the forms above. |
required |
alias
|
str
|
The name to register the source under, used verbatim — letters, digits, and underscores only, and not already in use by another source. |
required |
execute
async
execute(source: str, alias: str) -> str
Connect and register one external data source.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RunQueryTool
RunQueryTool(
connector: DataConnector,
*,
enable_params: bool = False,
enable_refresh: bool = False,
enable_media: bool = False,
enable_max_cell_chars: bool = False,
timeout: int | None | object = _UNSET,
max_visible_rows: int = 20,
max_cell_width: int = 200,
floatfmt: str = ".8g",
)
Execute a query against the database and return formatted results.
Supports any registered data connector, including SQL, Cypher, and SPARQL sources.
When enable_params=True, the tool schema exposed to the LLM includes
a parameters argument for parameterized queries.
When False (the default), the argument is omitted.
When enable_refresh=True, the tool schema also includes a refresh
flag that, when set by the LLM, re-introspects the connector's schema
after the query runs. Use this when the agent is allowed to issue DDL
(CREATE / DROP / ALTER) and the connector's cached schema
must reflect the new state for subsequent get_table_schema calls.
When enable_max_cell_chars=True, the tool schema includes a
max_cell_chars argument for expanding textual result cells beyond the
normal compact preview.
Attributes:
| Name | Type | Description |
|---|---|---|
connector |
Data connector to execute queries against. |
|
enable_params |
Whether to expose the |
|
enable_refresh |
Whether to expose the |
|
enable_media |
Whether to expose inline result-cell media inspection. |
|
enable_max_cell_chars |
Whether to expose expanded text-cell output. |
|
timeout |
Query timeout in seconds. When omitted, use the connector default;
|
|
max_visible_rows |
Maximum rows shown in the formatted output. |
|
max_cell_width |
Default maximum character width per cell in the formatted output. |
|
floatfmt |
Float format string passed to tabulate. |
name
class-attribute
instance-attribute
name: ClassVar = 'run_query'
connector
instance-attribute
connector = connector
enable_params
instance-attribute
enable_params = enable_params
enable_refresh
instance-attribute
enable_refresh = enable_refresh
enable_media
instance-attribute
enable_media = enable_media
enable_max_cell_chars
instance-attribute
enable_max_cell_chars = enable_max_cell_chars
timeout
instance-attribute
timeout = timeout
max_visible_rows
instance-attribute
max_visible_rows = max_visible_rows
max_cell_width
instance-attribute
max_cell_width = max_cell_width
floatfmt
instance-attribute
floatfmt = floatfmt
execute
async
execute(
query: str,
parameters: list[LLMParameter] | None = None,
refresh: bool = False,
*,
include_media: bool = False,
max_cell_chars: int | None = None,
) -> QueryExecution
Execute one query and return both agent-facing output and recorded query data.
__call__
async
__call__(
query: str,
parameters: list[LLMParameter] | None = None,
refresh: bool = False,
include_media: bool = False,
max_cell_chars: int | None = None,
) -> ToolReturn
Execute a query against the database and return formatted results.
Returning large result sets is safe: displayed output is truncated while the complete execution result remains available to the host.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
The SQL, Cypher, or SPARQL query to execute. |
required |
parameters
|
list[LLMParameter] | None
|
Values for named query placeholders. Exposed only when parameterized queries are enabled. |
None
|
refresh
|
bool
|
Whether to refresh connector schema after execution. Exposed only when schema refresh is enabled. |
False
|
include_media
|
bool
|
Whether to attach inline images and PDFs from result cells to the model for inspection. Audio and video remain available for artifact display but are not attached to the model. Does not fetch paths, URLs, or object-store URIs. |
False
|
max_cell_chars
|
int | None
|
Maximum characters to show in each text cell. |
None
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
QueryExecution
dataclass
QueryExecution(
output: str,
query: str,
parameter_values: dict[str, Any],
exec_result: ExecResult,
media_content: tuple[UserContent, ...] = (),
)
Result of one run-query invocation.
output
instance-attribute
output: str
query
instance-attribute
query: str
parameter_values
instance-attribute
parameter_values: dict[str, Any]
media_content
class-attribute
instance-attribute
media_content: tuple[UserContent, ...] = ()
LLMParameter
Bases: BaseModel
Named query parameter supplied through a model tool call.
parameter_name
class-attribute
instance-attribute
parameter_name: str = Field(
description="The parameter name that corresponds to the placeholder in the query (e.g. :name in SQL, $name in Cypher)."
)
parameter_value
class-attribute
instance-attribute
parameter_value: int | float | str = Field(
description="The intended value of the parameter."
)
RegistryRunQueryTool
RegistryRunQueryTool(
registry: DataConnectorRegistry,
*,
enable_params: bool = False,
enable_refresh: bool = False,
enable_media: bool = False,
enable_max_cell_chars: bool = False,
timeout: int | None | object = _UNSET,
max_visible_rows: int = 20,
max_cell_width: int = 200,
floatfmt: str = ".8g",
output_store: OutputStore | None = None,
)
Execute a query against any registered data source.
The agent specifies which connector to target via connector_alias. The tool
resolves the alias through a DataConnectorRegistry and delegates execution to a
per-alias RunQueryTool instance.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
The connector registry. |
required |
enable_params
|
bool
|
Whether to expose the |
False
|
enable_refresh
|
bool
|
Whether to expose the |
False
|
enable_media
|
bool
|
Whether to expose inline result-cell media inspection. |
False
|
enable_max_cell_chars
|
bool
|
Whether to expose |
False
|
timeout
|
int | None | object
|
Query timeout in seconds. When omitted, use each connector's
default; |
_UNSET
|
max_visible_rows
|
int
|
Maximum rows shown in the formatted output. |
20
|
max_cell_width
|
int
|
Maximum character width per cell in the formatted output. |
200
|
floatfmt
|
str
|
Float format string passed to tabulate. |
'.8g'
|
output_store
|
OutputStore | None
|
Optional shared output store. If not provided, the tool creates its own in-memory output_store. |
None
|
name
class-attribute
instance-attribute
name: ClassVar = 'run_query'
registry
instance-attribute
registry = registry
enable_params
instance-attribute
enable_params = enable_params
enable_refresh
instance-attribute
enable_refresh = enable_refresh
enable_media
instance-attribute
enable_media = enable_media
enable_max_cell_chars
instance-attribute
enable_max_cell_chars = enable_max_cell_chars
timeout
instance-attribute
timeout = timeout
max_visible_rows
instance-attribute
max_visible_rows = max_visible_rows
max_cell_width
instance-attribute
max_cell_width = max_cell_width
floatfmt
instance-attribute
floatfmt = floatfmt
__call__
async
__call__(
connector_alias: str,
query: str,
parameters: list[LLMParameter] | None = None,
refresh: bool = False,
include_media: bool = False,
max_cell_chars: int | None = None,
) -> ToolReturn
Execute a query against a registered data source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the target connector. |
required |
query
|
str
|
The SQL, Cypher, or SPARQL query to execute. |
required |
parameters
|
list[LLMParameter] | None
|
Values for named query placeholders. Exposed only when parameterized queries are enabled. |
None
|
refresh
|
bool
|
Whether to refresh connector schema after execution. Exposed only when schema refresh is enabled. |
False
|
include_media
|
bool
|
Whether to attach inline images and PDFs from result cells to the model for inspection. Audio and video remain available for artifact display but are not attached to the model. Does not fetch paths, URLs, or object-store URIs. |
False
|
max_cell_chars
|
int | None
|
Maximum characters to show in each text cell. |
None
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
GetTableSchemaTool
GetTableSchemaTool(
connector: SQLConnector,
formatter: SQLSchemaFormatter,
*,
include_descriptions: bool = True,
max_columns: int | None = 50,
enable_refresh: bool = False,
)
Tool that retrieves the full schema definition for a specified table.
Looks up a table by schema name and table name, then formats the table schema using the configured formatter.
Attributes:
| Name | Type | Description |
|---|---|---|
connector |
SQL connector providing live schema access and refresh. |
|
formatter |
The formatter used to render table schema as text. |
|
include_descriptions |
Whether to include column descriptions in output. |
|
max_columns |
If set, reject requests whose resulting columns exceed this limit, prompting the agent to use column_offset/column_limit or column_regex_filter to narrow down. |
|
enable_refresh |
If True, expose and honour the |
name
class-attribute
instance-attribute
name: ClassVar = 'get_table_schema'
connector
instance-attribute
connector = connector
formatter
instance-attribute
formatter = formatter
include_descriptions
instance-attribute
include_descriptions = include_descriptions
max_columns
instance-attribute
max_columns = max_columns
execute
async
execute(
schema_name: str | None,
table_name: str,
*,
refresh: bool = False,
column_regex_filter: str | None = None,
column_offset: int = 0,
column_limit: int | None = None,
) -> TableSchemaExecution
Render the table schema and return output plus the selected-column count.
__call__
async
__call__(
schema_name: str | None,
table_name: str,
refresh: bool = False,
column_offset: int = 0,
column_limit: int | None = None,
column_regex_filter: str | None = None,
) -> str
Get a table schema with optional column filtering and pagination.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema_name
|
str | None
|
Schema containing the table, or |
required |
table_name
|
str
|
Name of the table. |
required |
refresh
|
bool
|
Whether to re-introspect the table before returning. Exposed only when schema refresh is enabled. |
False
|
column_offset
|
int
|
Number of columns to skip. |
0
|
column_limit
|
int | None
|
Maximum number of columns to return. |
None
|
column_regex_filter
|
str | None
|
Case-insensitive regex used to select columns. |
None
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
TableSchemaExecution
dataclass
TableSchemaExecution(output: str, n_columns: int | None)
Result of one get-table-schema invocation.
output
instance-attribute
output: str
n_columns
instance-attribute
n_columns: int | None
GetColumnJsonSchemaTool
GetColumnJsonSchemaTool(
schema: SQLSchema,
include_examples: bool = True,
max_example_chars: int = _DEFAULT_MAX_EXAMPLE_CHARS,
)
Tool that retrieves the JSON schema of a specific column.
Looks up a column by schema name, table name, and column name, then returns its full JSON schema if available. This is useful for exploring the internal structure of JSON/VARIANT columns that contain nested objects, arrays, etc.
Attributes:
| Name | Type | Description |
|---|---|---|
schema |
The physical SQL schema containing all available tables. |
|
include_examples |
Whether to include example values in the output. |
|
max_example_chars |
Character budget for example values appended to the output. At least one example is always included. |
name
class-attribute
instance-attribute
name: ClassVar = 'get_column_json_schema'
schema
instance-attribute
schema = schema
include_examples
instance-attribute
include_examples = include_examples
max_example_chars
instance-attribute
max_example_chars = max_example_chars
__call__
async
__call__(
schema_name: str | None,
table_name: str,
column_name: str,
path: str | None = None,
) -> str
Get the JSON schema of a column, describing its internal structure (nested objects, arrays, etc.).
Useful for semi-structured column types such as VARIANT, OBJECT, ARRAY, JSON, and JSONB that store nested or complex data.
When called without a path, returns a shallow overview of the schema (top-level fields and one level of nesting). To drill into a specific sub-structure, provide a dot-separated path (e.g. "product", "transaction.currencyCode"). Arrays are traversed automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema_name
|
str | None
|
The name of the schema, or None if schema is not applicable. |
required |
table_name
|
str
|
The name of the table. |
required |
column_name
|
str
|
The name of the column. |
required |
path
|
str | None
|
Optional dot-separated path to a nested sub-schema. When provided, returns the full details of that sub-path instead of a shallow overview of the entire schema. |
None
|
execute
async
execute(
schema_name: str | None,
table_name: str,
column_name: str,
path: str | None = None,
) -> str
Resolve and render one column's JSON schema.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RegistryGetSchemaTool
RegistryGetSchemaTool(
registry: DataConnectorRegistry,
*,
enable_refresh: bool = False,
max_chars: int = _DEFAULT_MAX_CHARS,
)
Retrieve the full schema of any registered data source.
Automatically dispatches to the appropriate formatter based on the tagged
schema model. Large schemas are truncated to max_chars.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
The connector registry. |
required |
enable_refresh
|
bool
|
If True, expose the |
False
|
max_chars
|
int
|
Maximum characters in the returned schema text. Output exceeding this limit is truncated with a notice. |
_DEFAULT_MAX_CHARS
|
name
class-attribute
instance-attribute
name: ClassVar = 'get_schema'
registry
instance-attribute
registry = registry
enable_refresh
instance-attribute
enable_refresh = enable_refresh
max_chars
instance-attribute
max_chars = max_chars
execute
async
execute(connector_alias: str, refresh: bool = False) -> str
Render a registered data-source schema as agent-facing text.
__call__
async
__call__(
connector_alias: str, refresh: bool = False
) -> ToolReturn
Get the full schema of a registered data source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the target connector. |
required |
refresh
|
bool
|
Whether to refresh connector schema before rendering. Exposed only when schema refresh is enabled. |
False
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RegistryGetTableSchemaTool
RegistryGetTableSchemaTool(
registry: DataConnectorRegistry,
formatter: SQLSchemaFormatter,
*,
include_descriptions: bool = True,
max_columns: int | None = 50,
enable_refresh: bool = False,
)
Retrieve the schema of a table from any registered SQL database.
The agent specifies which connector to target via connector_alias. The tool
resolves the alias through a DataConnectorRegistry and delegates to a per-alias
GetTableSchemaTool instance.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
The connector registry. |
required |
formatter
|
SQLSchemaFormatter
|
The formatter used to render table schema as text. |
required |
include_descriptions
|
bool
|
Whether to include column descriptions in output. |
True
|
max_columns
|
int | None
|
If set, reject requests whose resulting columns exceed this limit. |
50
|
enable_refresh
|
bool
|
If True, expose the |
False
|
name
class-attribute
instance-attribute
name: ClassVar = 'get_table_schema'
registry
instance-attribute
registry = registry
formatter
instance-attribute
formatter = formatter
include_descriptions
instance-attribute
include_descriptions = include_descriptions
max_columns
instance-attribute
max_columns = max_columns
enable_refresh
instance-attribute
enable_refresh = enable_refresh
__call__
async
__call__(
connector_alias: str,
schema_name: str | None,
table_name: str,
refresh: bool = False,
column_offset: int = 0,
column_limit: int | None = None,
column_regex_filter: str | None = None,
) -> ToolReturn
Get a table schema from a registered database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the target connector. |
required |
schema_name
|
str | None
|
Schema containing the table, or |
required |
table_name
|
str
|
Name of the table. |
required |
refresh
|
bool
|
Whether to re-introspect the table before returning. Exposed only when schema refresh is enabled. |
False
|
column_offset
|
int
|
Number of columns to skip. |
0
|
column_limit
|
int | None
|
Maximum number of columns to return. |
None
|
column_regex_filter
|
str | None
|
Case-insensitive regex used to select columns. |
None
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RegistryGetColumnJsonSchemaTool
RegistryGetColumnJsonSchemaTool(
registry: DataConnectorRegistry,
*,
include_examples: bool = True,
max_example_chars: int = 1000,
)
Retrieve the JSON schema of a column from any registered SQL database.
The agent specifies which connector to target via connector_alias. The tool
resolves the alias through a DataConnectorRegistry and delegates to a per-alias
GetColumnJsonSchemaTool instance.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
The connector registry. |
required |
include_examples
|
bool
|
Whether to include example values in the output. |
True
|
max_example_chars
|
int
|
Character budget for example values. |
1000
|
name
class-attribute
instance-attribute
name: ClassVar = 'get_column_json_schema'
registry
instance-attribute
registry = registry
include_examples
instance-attribute
include_examples = include_examples
max_example_chars
instance-attribute
max_example_chars = max_example_chars
__call__
async
__call__(
connector_alias: str,
schema_name: str | None,
table_name: str,
column_name: str,
path: str | None = None,
) -> ToolReturn
Get the JSON schema of a column, describing its internal structure.
Useful for semi-structured column types such as VARIANT, OBJECT, ARRAY, JSON, and JSONB that store nested or complex data.
When called without a path, returns a shallow overview of the schema. To drill into a specific sub-structure, provide a dot-separated path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the target connector. |
required |
schema_name
|
str | None
|
The name of the schema, or None if not applicable. |
required |
table_name
|
str
|
The name of the table. |
required |
column_name
|
str
|
The name of the column. |
required |
path
|
str | None
|
Optional dot-separated path to a nested sub-schema. |
None
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RegistryGetDataSourceDocumentTool
RegistryGetDataSourceDocumentTool(
registry: DataConnectorRegistry,
*,
summarizer_cls: Callable[..., Any],
summarizer_llm: str = "openai:gpt-5.6-sol",
summary_max_words: int = 2000,
min_items_for_summary: int = 10,
enable_refresh: bool = False,
model_settings: ModelSettings | None = None,
)
Retrieve a human-readable source document for any registered data source.
By default this calls DataSourceSummarizer to generate the source document.
Optionally, small sources can skip summarization and return a direct
formatted schema document.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
Registry containing available connectors. |
required |
summarizer_cls
|
Callable[..., Any]
|
Data-source summarizer implementation. |
required |
summarizer_llm
|
str
|
LLM ID used for generated source documents. |
'openai:gpt-5.6-sol'
|
summary_max_words
|
int
|
Target maximum words for generated summaries. |
2000
|
min_items_for_summary
|
int
|
Minimum number of schema items required to run LLM summarization. SQL uses table count; property-graph uses node-label count + relationship-pattern count. If the count is lower, returns a direct formatted schema document. |
10
|
enable_refresh
|
bool
|
If True, expose |
False
|
model_settings
|
ModelSettings | None
|
Optional pydantic-ai model settings passed to summarizer agents. |
None
|
name
class-attribute
instance-attribute
name: ClassVar = 'get_data_source_document'
registry
instance-attribute
registry = registry
summarizer_llm
instance-attribute
summarizer_llm = summarizer_llm
model_settings
instance-attribute
model_settings = model_settings
summary_max_words
instance-attribute
summary_max_words = summary_max_words
min_items_for_summary
instance-attribute
min_items_for_summary = min_items_for_summary
enable_refresh
instance-attribute
enable_refresh = enable_refresh
apply_llm_profile
apply_llm_profile(
*, llm: str, model_settings: ModelSettings | None
) -> None
Apply the LLM profile used by generated source summaries.
execute
async
execute(connector_alias: str, refresh: bool = False) -> str
Render a registered data-source document as agent-facing text.
__call__
async
__call__(
connector_alias: str, refresh: bool = False
) -> ToolReturn
Get a connector-aware data-source document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the target connector. |
required |
refresh
|
bool
|
Whether to refresh connector schema before rendering. |
False
|
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
WriteResultTableTool
WriteResultTableTool(
registry: DataConnectorRegistry,
output_store: OutputStore,
)
Write a fixed query result into a target SQL table.
This tool resolves a prior run_query result and writes its DataFrame into
the session workspace or another writable registered SQL database.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DataConnectorRegistry
|
The connector registry. |
required |
output_store
|
OutputStore
|
Shared output store used by |
required |
name
class-attribute
instance-attribute
name: ClassVar = 'write_result_table'
registry
instance-attribute
registry = registry
__call__
async
__call__(
source_id: str,
target_alias: str,
target_schema: str | None,
target_table: str,
mode: TableWriteMode = "create",
) -> ToolReturn
Write a fixed run_query result into a SQL target table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_id
|
str
|
Source ID from |
required |
target_alias
|
str
|
Destination database alias. |
required |
target_schema
|
str | None
|
Optional destination schema name. |
required |
target_table
|
str
|
Destination table name. |
required |
mode
|
TableWriteMode
|
|
'create'
|
execute
async
execute(
source_id: str,
target_alias: str,
target_schema: str | None,
target_table: str,
mode: TableWriteMode = "create",
) -> str
Write one fixed query result into a registered SQL target.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
Extraction and enrichment tools
RunSubagentForEachRowTool
RunSubagentForEachRowTool(
connector: SQLConnector,
*,
registry: DataConnectorRegistry | None = None,
message_store: MessageStore | None = None,
subagent_llm: str | Model = "openai:gpt-5.6-luna",
model_settings: ModelSettings | None = None,
max_concurrency: int = 200,
store_metadata: bool = False,
trajectory_log_dir: Path | None = None,
)
Enrich table rows with concurrent agents and write structured results back.
Each agent emits one value per configured output column, with types derived from the target table. Agents use their supplied row content by default; browser, database, and nested-subagent tools can be enabled as needed.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector
|
SQLConnector
|
SQL connector for the table being updated (used for per-row write-back). |
required |
registry
|
DataConnectorRegistry | None
|
Optional data-source registry. Required only when callers
pass |
None
|
message_store
|
MessageStore | None
|
Optional workspace-backed message store. When
provided, every browser tool return is mirrored here and tagged
with a |
None
|
subagent_llm
|
str | Model
|
LLM identifier or model object used by per-row subagent runs. |
'openai:gpt-5.6-luna'
|
model_settings
|
ModelSettings | None
|
Optional pydantic-ai model settings passed to each subagent run. |
None
|
max_concurrency
|
int
|
Maximum number of row subagents to run concurrently. |
200
|
store_metadata
|
bool
|
If True, write |
False
|
trajectory_log_dir
|
Path | None
|
If set, each per-row subagent trajectory is
written as |
None
|
name
class-attribute
name: str = 'run_subagent_for_each_row'
connector
instance-attribute
connector = connector
registry
instance-attribute
registry = registry
message_store
instance-attribute
message_store = message_store
subagent_llm
instance-attribute
subagent_llm = subagent_llm
model_settings
instance-attribute
model_settings = model_settings
max_concurrency
instance-attribute
max_concurrency = max_concurrency
store_metadata
instance-attribute
store_metadata = store_metadata
trajectory_log_dir
instance-attribute
trajectory_log_dir = trajectory_log_dir
apply_llm_profile
apply_llm_profile(
*, llm: str, model_settings: ModelSettings | None
) -> None
Apply the LLM profile used by per-row subagents.
apply_execution_limits
apply_execution_limits(*, max_concurrency: int) -> None
Apply the concurrency limit used by subsequent calls.
__call__
async
__call__(
ctx: RunContext[Any],
schema_name: str | None,
table_name: str,
*,
task_query: str,
task_instruction: str,
key_columns: list[str],
output_columns: list[str],
enable_browser_tools: bool = False,
enable_nested_subagents: bool = False,
enable_run_query_tool: bool = False,
) -> str
Run an LLM subagent on each row, concurrently.
Use this tool to process many similar, independent sub-tasks in parallel:
lay the sub-tasks out as rows of a table and each row gets its own subagent.
task_query and task_instruction serve two roles. Row selection:
task_query is a free-form SELECT whose result rows become the tasks (one
subagent per row). Prompt construction: each subagent's prompt is
task_instruction rendered with that row's task_query columns — which
may include joined or computed columns, not just the table's own.
By default the subagent has no tools: it reads its prompt and emits one value
per column in output_columns (via a structured submit_answer output), and
this tool writes them back to that row in a single UPDATE. Any field may be
emitted as NULL — no placeholder strings like "N/A" are needed. Set
enable_browser_tools=True to grant web-browsing tools (plus the
extract_rows_from_documents and add_canonical_name tools, so a row
that browses can mine pages into structured rows and unify entity variants),
or enable_run_query_tool=True to grant a run_query tool that can query
and modify any registered data source. Set enable_nested_subagents=True to
give the subagent this same tool so it can fan out its own row-wise sub-tasks;
this does not propagate — each deeper level must set the flag again to nest
further.
Images, PDFs, and ordered collections that may mix them are attached to that row's prompt automatically. Path-backed media is not fetched; download and import its bytes first, or omit the column. Other binary values are rejected before any subagents run.
Safe to call multiple times in parallel in one turn.
Every subagent has a built-in abort_task(message: str) tool for
rows it can't complete; aborted rows are recorded in
_subagent_exception and output_columns are left unwritten.
Do not instruct it to emit sentinel strings like "NOT_COMPLETED" —
describe the successful output only and let it abort otherwise.
This is also the execution primitive for semantic operators beyond standard
SQL — tasks where the predicate, join condition, or transformation requires
natural-language understanding rather than exact SQL expressions. Prefer this
tool over fuzzy regex matching or LIKE-based SQL for these tasks. Common
patterns:
- Semantic filter: Classify a free-text column against a natural-language
predicate (e.g., "is this review positive or negative?").
- Semantic extraction: Extract structured values from unstructured text
(e.g., extract sentiment, topic, or named entities from a comment).
- Semantic join: Match rows across tables where there is no shared key
and no syntactic overlap between join columns (e.g., abbreviations to
full names, or matching product names across different naming conventions).
Two approaches:
(a) (preferred when the lookup space is large) Add a foreign-key column
to one table and have the subagent resolve the match against the
other table at runtime via run_query — set
enable_run_query_tool=True. Avoid embedding a large vocabulary
in the task instruction.
(b) Add a standardized column to both tables and have the subagent
normalize each side to a canonical form (e.g., IATA airport code)
independently. No run_query access needed.
After the tool completes, a standard SQL JOIN on the new column(s)
produces the final result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema_name
|
str | None
|
Schema containing |
required |
table_name
|
str
|
Target table name. Used as the write-back target; per-row
updates locate rows here via |
required |
task_query
|
str
|
SELECT query producing one row per subagent task. Free-form:
may join tables, compute new columns, etc. The result columns
become the variables available to |
required |
task_instruction
|
str
|
A Jinja2 template rendered per-row as the subagent
prompt. Use |
required |
key_columns
|
list[str]
|
Columns used in the WHERE clause to locate each row in
|
required |
output_columns
|
list[str]
|
One or more columns to update on |
required |
enable_browser_tools
|
bool
|
If True, the per-row subagent gets web-browsing
tools (navigate, click, type, etc.). Each row browses in its own
isolated tabs (cookies/logins shared). A process-wide tab cap
throttles this automatically, so fan out freely — no need to
limit parallelism for browser load. The subagent also receives the
When the task hands the subagent a deep link to a results page
on a large consumer site (Google Flights/Maps, Amazon, booking
sites) — e.g. |
False
|
enable_nested_subagents
|
bool
|
If True, each per-row subagent additionally
receives this |
False
|
enable_run_query_tool
|
bool
|
If True, the per-row subagent additionally
receives a registry-backed
|
False
|
execute
async
execute(
schema_name: str | None,
table_name: str,
*,
task_query: str,
task_instruction: str,
key_columns: list[str],
output_columns: list[str],
enable_browser_tools: bool = False,
enable_nested_subagents: bool = False,
enable_run_query_tool: bool = False,
tool_call_id: str | None = None,
) -> str
Run row-wise subagents without requiring an agent run context.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
Return pydantic-ai Tool wrapper.
ExtractRowsFromDocumentsTool
ExtractRowsFromDocumentsTool(
connector: SQLConnector,
*,
subagent_llm: str | Model = "openai:gpt-5.6-luna",
model_settings: ModelSettings | None = None,
max_concurrency: int = 200,
chunk_target: int = DEFAULT_TARGET_CHARS,
chunk_max: int = DEFAULT_MAX_CHARS,
trajectory_log_dir: Path | None = None,
)
Extract a list of entities from each source document and append them as table rows.
This is the row-expansion dual of run_subagent_for_each_row (which expands
columns): one source document in, many entity rows out. task_query yields
one row per document — its content column holds text, media, or an ordered media collection, and the remaining
columns feed the task_instruction Jinja template. task_query must project
the document as a column named content; any other columns feed the
template. Text and PDFs are chunked, while each image is one excerpt; a leaf subagent extracts entities from each
chunk; every chunk's rows are appended to table_name. Deduplication is
intentionally out of scope (handle it downstream with full semantic context).
The DB-free extraction engine lives in :class:EntityExtractor; this class is the
database adapter around it (read documents with SQL, write extracted rows back).
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector
|
SQLConnector
|
SQL connector that both evaluates |
required |
subagent_llm
|
str | Model
|
LLM identifier or model object used by per-chunk extraction subagents. |
'openai:gpt-5.6-luna'
|
model_settings
|
ModelSettings | None
|
Optional pydantic-ai model settings passed to each subagent run. |
None
|
max_concurrency
|
int
|
Maximum number of chunk subagents to run concurrently across all documents. |
200
|
chunk_target
|
int
|
Soft per-chunk size the splitter packs toward. |
DEFAULT_TARGET_CHARS
|
chunk_max
|
int
|
Hard per-chunk ceiling; the only size at which a block is split. |
DEFAULT_MAX_CHARS
|
trajectory_log_dir
|
Path | None
|
If set, each per-chunk subagent trajectory is written
as |
None
|
name
class-attribute
name: str = 'extract_rows_from_documents'
connector
instance-attribute
connector = connector
subagent_llm
instance-attribute
subagent_llm = subagent_llm
model_settings
instance-attribute
model_settings = model_settings
max_concurrency
instance-attribute
max_concurrency = max_concurrency
chunk_target
instance-attribute
chunk_target = chunk_target
chunk_max
instance-attribute
chunk_max = chunk_max
trajectory_log_dir
instance-attribute
trajectory_log_dir = trajectory_log_dir
apply_llm_profile
apply_llm_profile(
*, llm: str, model_settings: ModelSettings | None
) -> None
Apply the LLM profile used by per-chunk extraction subagents.
apply_execution_limits
apply_execution_limits(*, max_concurrency: int) -> None
Apply the concurrency limit used by subsequent calls.
__call__
async
__call__(
ctx: RunContext[Any],
schema_name: str | None,
table_name: str,
*,
task_query: str,
task_instruction: str,
output_columns: list[str],
) -> str
Extract entities from documents and append them as new rows.
Use this tool to turn unstructured documents into structured rows — the
row-expansion counterpart to run_subagent_for_each_row. Where that tool
runs one subagent per input row and writes one value back, this tool reads
one document per input row and appends many extracted entity rows (e.g.
mining a long web page into a table of entities). It is the LLM-based
extraction path: use it when the target data is irregularly formatted,
requires semantic understanding to extract, or when regex parsing is
unreliable.
Internally, text is split into structure-aware chunks, PDFs into bounded page ranges, and each image is treated as one excerpt. A media collection may mix images and PDFs and is processed in order. A subagent extracts entities from every excerpt concurrently; every excerpt's rows are appended (no dedup), so documents far larger than one LLM context are handled.
task_query selects the source documents: one result row per document, with
its text, inline image/PDF media, or ordered media collection projected as a
column named content; path-backed media is not fetched, so download the
referenced file and import its bytes before projecting it as content. Any
other columns are available to task_instruction. The common case is a page
the agent already browsed, which was offloaded to _internal.messages
(already has a content column)::
SELECT content FROM _internal.messages WHERE message_id = 'M7'
Entities extracted from each document are appended to table_name (one row
per entity, populating output_columns). Any field may be emitted as NULL —
no placeholder strings like "N/A" are needed.
This tool does not deduplicate. The same entity may appear in multiple rows,
and different documents commonly emit variants of the same real-world entity
(e.g. "Microsoft", "MSFT", "Microsoft Corp"). Plan to follow up with a
canonicalization step.
Safe to call multiple times in parallel in one turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema_name
|
str | None
|
Schema containing |
required |
table_name
|
str
|
Existing target table to append rows into. All
|
required |
task_query
|
str
|
SELECT producing one row per source document. Must project
document text, inline image/PDF media, or a media collection as a
column named |
required |
task_instruction
|
str
|
A Jinja2 template rendered once per source document
describing what one entity is and how to populate |
required |
output_columns
|
list[str]
|
Columns each extracted entity populates. Must be
non-empty. All must already exist on |
required |
execute
async
execute(
schema_name: str | None,
table_name: str,
*,
task_query: str,
task_instruction: str,
output_columns: list[str],
tool_call_id: str | None = None,
) -> str
Extract and append document rows without requiring an agent run context.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
Return pydantic-ai Tool wrapper.
AddCanonicalNameTool
AddCanonicalNameTool(
*,
subagent_llm: str | Model = "openai:gpt-5.6-luna",
model_settings: ModelSettings | None = None,
max_concurrency: int = 200,
trajectory_log_dir: Path | None = None,
)
Cluster same-entity variants in input_column and write one canonical name per cluster.
Per-value SAME-judgment (with cross-row evidence via run_query) builds a SAME-edge
graph over the distinct values; connected components are the clusters; one picker call
per cluster produces the canonical name; cross-cluster name collisions are resolved by
an LLM-disambiguation pass that knows the already-claimed canonicals and is re-prompted
with feedback on validation failure (no synthetic suffix fallback — if disambiguation
fails after retries, the call returns a hard error). The value→canonical mapping is
applied to all rows in one SQL UPDATE.
Guarantee: every cluster gets a globally unique canonical name within one call.
The validator on the disambiguation step enforces this; if the LLM cannot produce
distinct names after retries, the call hard-fails rather than silently emitting
duplicates. Downstream joins on canonical_column and the merge_duplicates
post-step both rely on this invariant.
For row-independent transformations — per-value normalization with no cross-row
evidence, or resolving values against a separate reference table — use
run_subagent_for_each_row with task_query="SELECT DISTINCT col FROM tbl" and
key_columns=[col] instead. This tool owns only the cross-row clustering case.
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subagent_llm
|
str | Model
|
LLM identifier or model object used by per-value and per-cluster subagents. |
'openai:gpt-5.6-luna'
|
model_settings
|
ModelSettings | None
|
Optional pydantic-ai settings passed to subagent runs. |
None
|
max_concurrency
|
int
|
Maximum number of per-value subagents running concurrently across one call. |
200
|
trajectory_log_dir
|
Path | None
|
If set, each subagent trajectory is persisted as
|
None
|
name
class-attribute
name: str = 'add_canonical_name'
subagent_llm
instance-attribute
subagent_llm = subagent_llm
model_settings
instance-attribute
model_settings = model_settings
max_concurrency
instance-attribute
max_concurrency = max_concurrency
trajectory_log_dir
instance-attribute
trajectory_log_dir = trajectory_log_dir
attach_connector
attach_connector(connector: SQLConnector) -> None
Bind the workspace connector after construction (mirrors OutputStore).
apply_llm_profile
apply_llm_profile(
*, llm: str, model_settings: ModelSettings | None
) -> None
Apply the LLM profile used by canonicalization subagents.
apply_execution_limits
apply_execution_limits(*, max_concurrency: int) -> None
Apply the concurrency limit used by subsequent calls.
__call__
async
__call__(
ctx: RunContext[Any],
schema_name: str | None,
table_name: str,
*,
canonical_column: str,
instruction: str,
input_column: str,
merge_duplicates: bool = False,
) -> str
Cluster variants in input_column and populate canonical_column per cluster.
Operates on SELECT DISTINCT input_column — rows sharing an input_column
value always receive the same canonical. If two distinct entities can share
that value (e.g. two "John Smith" rows), pre-derive a discriminating column
and pass that as input_column.
input_column can be the row's own identifier or a foreign attribute (e.g.
"school" on a students table); in the latter case only the named column
gets canonicalized, the row entity is untouched.
Use this tool when variants of the same entity exist within the same column and
need to be unified (e.g. "Microsoft", "MSFT", "Microsoft Corp" →
"Microsoft Corporation"). For per-value normalization with no cross-row
evidence, or for matching values against a separate reference table, use
run_subagent_for_each_row instead.
Safe to call multiple times in parallel in one turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema_name
|
str | None
|
Schema containing |
required |
table_name
|
str
|
Table containing both |
required |
canonical_column
|
str
|
Existing column to populate. Set equal to |
required |
instruction
|
str
|
What makes two values refer to the same real-world entity, plus
any style guidance for the canonical form. If collisions are likely
(common surface names like |
required |
input_column
|
str
|
The column being canonicalized. |
required |
merge_duplicates
|
bool
|
After populating, collapse rows sharing a canonical into
one via per-column coalesce (most-frequent non-null). Safe because
every cluster gets a globally unique canonical, so the groupby
collapses one entity at a time. In place — originals are lost;
copy first if needed. Only set when |
False
|
execute
async
execute(
schema_name: str | None,
table_name: str,
*,
canonical_column: str,
instruction: str,
input_column: str,
merge_duplicates: bool = False,
tool_call_id: str | None = None,
) -> str
Canonicalize one table column without requiring an agent run context.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
Return pydantic-ai Tool wrapper.
Output tools
CreateParameterizedArtifactSourceTool
CreateParameterizedArtifactSourceTool(
registry: DataConnectorRegistry,
output_store: OutputStore,
*,
timeout: int | None | object = _UNSET,
default_max_warm_variants: int = 10,
)
Create an artifact source whose query is controlled by output parameters.
name
class-attribute
instance-attribute
name: ClassVar = 'create_parameterized_source'
timeout
instance-attribute
timeout = timeout
__call__
async
__call__(
connector_alias: str,
parameters: list[ParameterSpec],
query_template: str,
max_warm_variants: int | None = None,
) -> ToolReturn
Create a parameterized source and warm its default or small finite choice grid.
Example:
create_parameterized_source(
connector_alias="workspace",
parameters=[
{
"kind": "choice",
"id": "metric",
"label": "Ranking metric",
"choices": [
{"id": "revenue", "label": "Revenue"},
{"id": "profit", "label": "Profit"},
{"id": "orders", "label": "Order count"},
],
},
{
"kind": "number",
"id": "min_spend",
"label": "Minimum spend",
"min": 0,
"max": 100000,
"step": 5000,
"default": 10000,
"unit": "USD",
},
],
query_template='''
SELECT customer,
{% if metric == "revenue" %} SUM(revenue_usd) AS value
{% elif metric == "profit" %} SUM(profit_usd) AS value
{% elif metric == "orders" %} COUNT(*) AS value
{% endif %}
FROM orders
GROUP BY customer
HAVING SUM(revenue_usd) >= {{ min_spend }}
ORDER BY value DESC
''',
)
Use not_applicable(reason) when a source intentionally does not apply
for a parameter branch; no SQL is run for that selection, and dependent
artifacts render the reason as a not-applicable message.
Example:
create_parameterized_source(
connector_alias="workspace",
parameters=[
{
"kind": "choice",
"id": "metric",
"label": "Metric",
"choices": [
{"id": "revenue", "label": "Revenue"},
{"id": "orders", "label": "Orders"},
],
},
],
query_template='''
{% if metric != "revenue" %}
{{ not_applicable("Revenue detail only applies when metric is Revenue") }}
{% endif %}
SELECT customer, revenue_usd
FROM customer_revenue
ORDER BY revenue_usd DESC
''',
)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the connector that executes rendered queries. |
required |
parameters
|
list[ParameterSpec]
|
Choice or number parameters referenced by the Jinja query template. For choice parameters, the first choice is the default. |
required |
query_template
|
str
|
Jinja template rendered with validated parameter values.
It may call |
required |
max_warm_variants
|
int | None
|
Maximum finite choice combinations to precompute. If omitted, the session default is used. Numeric parameters are fixed at their defaults while choice combinations are warmed up to this cap. |
None
|
execute
async
execute(
connector_alias: str,
parameters: list[ParameterSpec],
query_template: str,
max_warm_variants: int | None = None,
) -> CreatedParameterizedArtifactSource
Validate, create, and warm a parameterized result source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_alias
|
str
|
Alias of the connector that executes rendered queries. |
required |
parameters
|
list[ParameterSpec]
|
Parameters referenced by |
required |
query_template
|
str
|
Jinja query template rendered for each warmed selection. |
required |
max_warm_variants
|
int | None
|
Maximum finite choice combinations to precompute. |
None
|
Returns:
| Type | Description |
|---|---|
CreatedParameterizedArtifactSource
|
The created source and a model-facing summary of its warmed results. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If configuration is invalid or a warmed query fails. |
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
CreatedParameterizedArtifactSource
dataclass
CreatedParameterizedArtifactSource(
output: str,
artifact_source: ParameterizedArtifactSource,
)
Created artifact-source metadata returned by programmatic execution.
output
instance-attribute
output: str
RenderChartTool
RenderChartTool(output_store: OutputStore | None = None)
Create a standalone chart artifact from a result or family source.
Validates the spec against the source DataFrame(s) and stores it as a citable
clean chart ArtifactSpec. Simple x/y specs also get a terminal (plotext) preview;
richer specs render in the browser via the full Vega runtime.
name
class-attribute
instance-attribute
name: ClassVar = 'render_chart'
__call__
async
__call__(source_id: str, *, vegalite_spec: str) -> str
Create a Vega-Lite chart from a result or result-lookup source source.
Accepts any Vega-Lite spec — single or multi-view: bar, line, point, area, arc/pie, heatmap, stacked/grouped bars via a color encoding, faceting, transforms, etc. Simple x/y charts preview in the terminal; richer charts open in the browser at full fidelity.
Specs may bind inputs (e.g. a range slider via params/bind) or
selections for interactive filtering and zoom in the browser.
A dark theme is applied by the viewer, so leave colors unset unless the user asked for specific ones.
For stable categorical colors across parameter selections, set the
complete ordered category list in color.scale.domain.
In a layered spec where any layer is colored by a field, every layer
must declare a color: {"datum": "<series name>"} gives an overlay
(e.g. a total line) its own legend entry and palette color;
{"value": "<css color>"} sets a fixed color.
Example spec
{"mark": "bar", "encoding": {"x": {"field": "status", "type": "nominal"}, "y": {"field": "count", "type": "quantitative"}}, "title": "Schools by Status"}
Returns the new chart id (CHART1, CHART2, …) to cite in the answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_id
|
str
|
Output-store source ID, such as |
required |
vegalite_spec
|
str
|
A Vega-Lite JSON specification string. |
required |
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RenderMapTool
RenderMapTool(output_store: OutputStore | None = None)
Create a declarative map artifact from one or more sources.
name
class-attribute
instance-attribute
name: ClassVar = 'render_map'
__call__
async
__call__(*, map_spec: str) -> str
Create a map from one or more sources.
The spec is a JSON string containing an object with a non-empty
layers list. Each column/geojson layer names the source it
reads from via source_id; layers with different source_id values
overlay data from multiple sources on one map (e.g. GeoJSON
boundaries from one source and point markers from another).
Full public grammar:
- Top level:
title: optional string.
view: optional object with fit bool, center as
[lat, lng], zoom number, and maxZoom number.
layers: required non-empty list.
- Common layer fields:
source_id: output-store source id the layer reads from (e.g.
"S3"). Required for column and geojson layers; omit for inline
points.
label: optional field name for the short feature identity.
tooltip: optional field name, list of field names, or true;
shown as popup body fields on hover and click. Popup titles use
label when present. String values that are full http(s) URLs
render as links.
color: optional {"field":"status"} or
{"field":"status","domain":[...]}; the browser pane chooses the
palette. An explicit ordered domain keeps category colors fixed across
parameter selections.
- points layer:
Column mode: {"type":"points","source_id":"S3","lat":"lat","lng":"lng"}.
Inline mode:
{"type":"points","points":[{"lat":37.7,"lng":-122.4,"label":"Destination"}]}.
Add optional label, tooltip, color, marker, and
size. Inline label, tooltip, color, and size
reference inline point property names.
marker is optional; omit it to use fixed-size pins, the preferred
default for ordinary locations. Use {"type":"circle"} for circle
markers. size is supported only for circles; when size is
present and marker is omitted, circles are selected automatically.
Add "domain":[0,1000] only when known bounds should keep sizes
comparable across updates.
- geojson layer:
{"type":"geojson","source_id":"S3","geojson":"geom_geojson"} plus
optional label, tooltip, and color. geojson is a
column name or inline WGS84 GeoJSON object. If the database has
native geometry, convert it in SQL first (e.g.
ST_AsGeoJSON(ST_Transform(geom, 4326)) AS geom_geojson) and
reference that column.
Minimal examples:
{"layers":[{"type":"points","source_id":"S3","lat":"lat","lng":"lng","label":"name","tooltip":["status"]}]}
{"layers":[{"type":"points","points":[{"lat":37.7,"lng":-122.4,"label":"Destination"}],"label":"label"}]}
{"layers":[{"type":"geojson","source_id":"S3","geojson":"geom_geojson","label":"name","tooltip":["status"]}]}
Multi-source overlay:
{"layers":[{"type":"geojson","source_id":"S1","geojson":"area_geojson","label":"area"},{"type":"points","source_id":"S2","lat":"lat","lng":"lng","label":"name"}]}
Prefer defaults unless the user asks for styling or a fixed viewport.
If the database has native geometry, convert it to WGS84 GeoJSON in SQL
before calling this tool, using the database's spatial functions. For
example, PostGIS:
ST_AsGeoJSON(ST_Transform(geom, 4326)) AS geom_geojson; DuckDB
spatial: ST_AsGeoJSON(ST_Transform(geom, 'EPSG:4326')) AS geom_geojson.
Returns the new map id (MAP1, MAP2, …) to cite in the answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
map_spec
|
str
|
Declarative map specification as a JSON string. GeoJSON coordinates must be WGS84 longitude/latitude. |
required |
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
RenderGraphTool
RenderGraphTool(output_store: OutputStore | None = None)
Create a node-link graph artifact from query results or inline data.
name
class-attribute
instance-attribute
name: ClassVar = 'render_graph'
__call__
async
__call__(*, graph_spec: str) -> str
Create a graph from one or more query results. Use this when the source is not Neo4j or the graph needs to be customized.
The spec is a JSON string containing an object with nodes and
edges. Each column source names the query result it reads from via
source_id. Nodes may be supplied in one result while edges come
from another; every edge endpoint id must match a declared node id.
Full public grammar:
- Top level:
title: optional string.
layout: optional force, layered, or tree.
group_domain: optional ordered list of all node group values;
fixes their colors across parameter selections.
nodes: required list of node sources.
edges: optional list of edge sources; omit it for node-only graphs.
- Node source:
Column mode:
{"source_id":"S1","id":"id","label":"name","group":"type"}.
Inline mode:
{"data":[{"id":"a","name":"A"}],"id":"id","label":"name"}.
Node id values are global across all sources: equal ids are
the same node (the first source wins) and every edge endpoint
must match one, so id spaces that overlap across types must be
disambiguated (e.g. prefixed) in the query.
group is the node's categorical type (not an identifier);
nodes are colored one color per distinct group value. It is a
column name, or {"value":"Customer"} when all nodes from
the source share one type; omit it for untyped nodes.
Optional tooltip is a field name, list of field names, or
true (all row fields). Explicit tooltip lists define body
fields; node titles use label or id.
- Edge source:
Column mode:
{"source_id":"S2","source":"from_id","target":"to_id","label":"rel"}.
Inline mode:
{"data":[{"from":"a","to":"b"}],"source":"from","target":"to"}.
label is drawn along the edge (typically the relationship
type): a column name, or {"value":"PURCHASED"} when all
edges from the source share one type.
Optional directed defaults to true. Optional tooltip is a
field name, list of field names, or true (all row fields).
Explicit tooltip lists define body fields; edge titles use label
when present.
Minimal examples:
{"nodes":[{"source_id":"S1","id":"src"},{"source_id":"S1","id":"dst"}],"edges":[{"source_id":"S1","source":"src","target":"dst","label":"rel"}]}
{"layout":"layered","nodes":[{"source_id":"S1","id":"id","label":"name"}],"edges":[{"source_id":"S2","source":"from_id","target":"to_id"}]}
{"nodes":[{"data":[{"id":"a"},{"id":"b"}],"id":"id"}],"edges":[{"data":[{"from":"a","to":"b"}],"source":"from","target":"to"}]}
{"nodes":[{"source_id":"S1","id":"customer","group":{"value":"Customer"}},{"source_id":"S1","id":"product","group":{"value":"Product"}}],"edges":[{"source_id":"S1","source":"customer","target":"product","label":{"value":"PURCHASED"}}]}
Returns the new graph id (GRAPH1, GRAPH2, …) to cite in the answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph_spec
|
str
|
Declarative graph specification as a JSON string. |
required |
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
ShowArtifactsTool
ShowArtifactsTool(output_store: OutputStore)
Declare which sources or artifacts a turn shows, each with a display label.
name
class-attribute
instance-attribute
name: ClassVar = 'show_artifacts'
__call__
async
__call__(artifacts: Artifacts) -> ToolReturn
Show the user a set of results, each as a labelled card.
Ids come from the tools that produced them: S* from a query or source,
CHART*, MAP* and GRAPH* from render tools. Cards appear in the
order given, the first one open. Parameter controls are inferred from the
selected sources and artifacts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
artifacts
|
Artifacts
|
The results to show, in display order. |
required |
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
ArtifactRef
Bases: BaseModel
User-facing label for one source or rendered artifact identifier.
id
class-attribute
instance-attribute
id: str = Field(
min_length=1,
description="Id of a source or artifact to show: S*, CHART*, MAP* or GRAPH*.",
)
label
class-attribute
instance-attribute
label: str = Field(
min_length=1,
description="Short human-readable name for the card, never the id itself.",
)
ArtifactBundle
dataclass
ArtifactBundle(artifacts: tuple[ArtifactRef, ...])
The artifacts one show_artifacts call declared, in display order.
Travels on the tool return's metadata — host-facing, never sent to the
model — so the turn's cards need no state on the tool itself.
Artifacts
module-attribute
Artifacts: TypeAlias = Annotated[
list[ArtifactRef], Field(max_length=20)
]
Browser and filesystem tools
WebBrowserTool
WebBrowserTool(
manager: WebBrowserManager | None = None,
isolated: bool = False,
max_tabs: int = 10,
)
Per-agent stateful browser tool with multi-tab support.
One instance can manage many tabs within a shared (or private)
BrowserContext. Each browser_navigate call opens a NEW tab; the
LLM addresses subsequent actions via tab="t1" to refer back. Tabs
auto-close if the agent doesn't interact with them on the next turn
(turn boundary detected via a pydantic-ai before_model_request hook
— register it by calling tool.lifecycle_capability() and passing
the result to Agent(capabilities=[...])).
Initialize the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manager
|
WebBrowserManager | None
|
BrowserManager instance to use. If None, the process-wide singleton is used (recommended). |
None
|
isolated
|
bool
|
If True, this tool gets its own private BrowserContext instead of sharing the manager's default context. Use when an agent needs cookie/storage isolation from peers. |
False
|
max_tabs
|
int
|
Cap on simultaneously-open tabs for this tool. Returns an error if exceeded; idle tabs auto-close at the next turn boundary. |
10
|
name
class-attribute
instance-attribute
name: ClassVar = 'web_browser'
browser_navigate
async
browser_navigate(
url: str, tab: str | None = None
) -> str | ToolReturn
Navigate to url and return the post-load snapshot.
By default (tab=None) opens a NEW tab. Pass an existing tab
id (e.g. "t3") to navigate that tab in place instead — useful
when the tab cap is reached, or when you want the tab's id and
back-history to stay stable across the URL change.
The snapshot is markdown rendered from the page's accessibility
tree. Every interactive element appears as a self-contained
single-line atom followed by [ref=eN]::
[text](url) [ref=eN] link
button "name" [ref=eN] button (also: clickable "text" [ref=eN])
role "name" = "value" [ref=eN] form controls (textbox/combobox/...)
![alt]() [ref=eN] img
option "name" [ref=eN] live listbox option
Pass that ref id to browser_click / browser_type /
browser_select to act on the element. Atoms may appear
mid-line.
Refs are per-snapshot, NOT stable element ids. Every tool response for a tab renumbers them from scratch — even on a visually unchanged page, DOM mutations or AJAX can shift the assignment. Only refs from the tab's MOST RECENT response are valid; never reuse an earlier ref.
Each new-tab call opens a fresh tab — previously-opened tabs remain
open. Use the tab id from the response (e.g., "t3") in
subsequent action calls (browser_click, etc.) to interact with
this tab. Tabs auto-close if not interacted with on the next agent
turn.
Issue multiple navigates in parallel within one turn to scan several URLs concurrently. Direct image and PDF responses are returned as native model content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
An |
required |
tab
|
str | None
|
If set, navigate this existing tab in place instead of opening a new one. |
None
|
browser_screenshot
async
browser_screenshot(
tab: str, ref: str | None = None
) -> str | ToolReturn
Capture the current viewport or one referenced element as an image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to capture, e.g. |
required |
ref
|
str | None
|
An optional ref from the tab's most recent snapshot. When omitted, captures the current viewport. |
None
|
browser_click
async
browser_click(tab: str, ref: str) -> str
Click an interactive element on a specific tab.
Downloads are disabled — clicking a download link succeeds but produces no page change; don't retry the same ref.
Clicks on comboboxes, menu buttons, date pickers, and similar
controls open popups that stay visible in subsequent snapshots
(shown as [expanded] on the trigger). Either act inside the
popup (select an option, type into the search box that appeared)
or dismiss it with browser_press(key="Escape") before targeting
other controls — some sites trap focus while the popup is open,
which can shadow sibling form fields from the next snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to act on, e.g. |
required |
ref
|
str
|
The ref string of the target element, e.g. |
required |
browser_type
async
browser_type(
tab: str, ref: str, text: str, submit: bool = False
) -> str
Type text into an editable element on a specific tab.
Always types one character at a time so per-keystroke handlers fire —
the reliable shape for autocompletes, comboboxes, and live-search
widgets that listen for input events. press_sequentially uses
zero inter-key delay so the overhead is small (~5-10ms/char) for the
typical short inputs agents send (search terms, names, URLs).
Works on combobox refs directly — typing auto-focuses the control,
so no preceding browser_click is needed to enter the field.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to act on, e.g. |
required |
ref
|
str
|
The ref string of the target element, e.g. |
required |
text
|
str
|
The text to type. Replaces existing content. |
required |
submit
|
bool
|
If True, press Enter after typing. Without submit the response is a short ack since the page state hasn't changed beyond the input field's value (which the agent already knows). |
False
|
browser_scroll
async
browser_scroll(
tab: str,
direction: Literal["up", "down", "top", "bottom"],
) -> str
Scroll a specific tab.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to scroll, e.g. |
required |
direction
|
Literal['up', 'down', 'top', 'bottom']
|
One of |
required |
browser_back
async
browser_back(tab: str) -> str
Navigate back in a specific tab's history.
Use this whenever a previous action replaced the tab's page and you still need the prior content. Triggers include:
browser_navigatewithtab=<id>(explicit in-place navigation).browser_clickon a link, form submit, or JS-driven nav element.browser_typewithsubmit=Truecausing a form post or search redirect.
A page replacement discards the prior page's DOM, JS state, and text
content entirely; browser_back is the only way to recover it without
re-navigating to the URL by hand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to navigate back on, e.g. |
required |
browser_press
async
browser_press(tab: str, key: str) -> str
Press a keyboard key on a tab (no specific element required).
Most useful for dismissing modals ("Escape"), submitting forms
("Enter"), and tab navigation ("Tab"). Operates on whichever
element currently has focus, or at page level for keys like Escape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to act on, e.g. |
required |
key
|
str
|
A Playwright key name — e.g. |
required |
browser_select
async
browser_select(tab: str, ref: str, option: str) -> str
Select an option from a native <select> dropdown.
For native HTML <select> elements (combobox role). Use this
instead of click+click — Playwright's select_option handles
native dropdowns reliably across browsers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to act on, e.g. |
required |
ref
|
str
|
The ref string of the target element, e.g. |
required |
option
|
str
|
The option to choose, matched by visible label or by value attribute (Playwright tries both). |
required |
browser_wait
async
browser_wait(
tab: str,
seconds: float = 3.0,
text: str | None = None,
text_gone: str | None = None,
) -> str
Wait for a condition (or a fixed time), then re-snapshot the tab.
Prefer text / text_gone over a fixed sleep — they return as
soon as the condition holds. Reach for seconds when no stable
text exists to key off.
Reach for this when the last snapshot looks under-hydrated: many
button [ref=eXXX] without names, bare [] icons, bare # /
## headings, or stripped combobox labels. 1-3s is usually enough.
Don't call after every action — nav/mutation tools already apply a
small post-load settle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tab
|
str
|
The id of the tab to re-snapshot afterward, e.g. |
required |
seconds
|
float
|
Fixed sleep when neither |
3.0
|
text
|
str | None
|
Wait until this text appears. |
None
|
text_gone
|
str | None
|
Wait until this text disappears. |
None
|
tick
async
tick() -> None
Advance the turn counter and close idle tabs.
Called by the lifecycle capability before each model request.
Cleanup only runs on turns that follow browser activity. If the previous turn had no browser actions (agent was doing SQL, planning, etc.), all tabs are preserved — the agent can return to its browser context later. When the agent does interact with the browser again, the normal "tabs untouched in the past 2 turns get closed" rule kicks back in.
suspend
async
suspend() -> None
Drop all tabs and make new tab opens fail until :meth:resume.
Called around a fan-out the agent triggers while it can also browse. The
agent's open tabs hold page permits from the shared budget that aren't
released until the next turn boundary; a fan-out whose rows need those
permits would deadlock, waiting for a turn that can't end until the
fan-out returns. Suspending drops the tabs (releasing the permits) and
makes _open_new_tab refuse to take one until resume, so the tool
provably holds zero permits across the fan-out await — even if a sibling
browser_* call lands in the same (concurrently executed) turn; that
call fails fast and the agent can retry in a later turn.
Reentrant: nested/concurrent suspends stack and lift in kind.
resume
resume() -> None
Lift one :meth:suspend; tab opens are allowed again once depth hits zero.
close
async
close() -> None
Close all tabs and (if isolated) the private context.
as_pydantic_ai_tools
as_pydantic_ai_tools() -> list[Tool]
Return this tool's browse actions as pydantic-ai Tool objects.
All actions share this instance's tab/snapshot state. Splat into
Agent(tools=[..., *web_browser.as_pydantic_ai_tools()]) and pair
with capabilities=[web_browser.lifecycle_capability()] so idle
tabs are cleaned up at turn boundaries.
lifecycle_capability
lifecycle_capability() -> Hooks[None]
Return a pydantic-ai Hooks capability that ticks this tool.
The returned capability subscribes to before_model_request and
invokes tick() on each model turn boundary. Pass it to
Agent(capabilities=[...]).
WebBrowserManager
WebBrowserManager(
headless: bool = True, max_pages: int | None = None
)
Own one Chromium process, shared context, and process-wide page budget.
The agent runtime owns the default manager. Direct construction is reserved for tests and callers that need a separate browser lifecycle.
acquire_page
async
acquire_page(*, block: bool) -> bool
Acquire a page permit, optionally waiting for capacity.
release_page
async
release_page() -> None
Release one previously acquired page permit.
shared_context
async
shared_context() -> BrowserContext
Return the lazily created process-wide browser context.
new_isolated_context
async
new_isolated_context() -> BrowserContext
Create a browser context with isolated cookies and storage.
close
async
close() -> None
Close the context, Chromium process, and Playwright runtime.
FilesystemRoot
dataclass
FilesystemRoot(
name: str, path: str | Path, writable: bool = True
)
A filesystem root that a host file tool may access.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human-readable root label used in error messages. |
required |
path
|
str | Path
|
Root directory path. |
required |
writable
|
bool
|
Whether mutating commands may write under this root. |
True
|
name
instance-attribute
name: str
path
instance-attribute
path: str | Path
writable
class-attribute
instance-attribute
writable: bool = True
ViewTool
ViewTool(
working_dir: str,
allowed_roots: Sequence[FilesystemRoot]
| None
| _DefaultAllowedRoots = _DEFAULT_ALLOWED_ROOTS,
)
View files and directories.
By default, access is scoped to working_dir. Pass explicit
allowed_roots to grant access to additional directories, or pass
allowed_roots=None for unrestricted filesystem access. Relative paths
always resolve against working_dir.
name
class-attribute
instance-attribute
name: ClassVar = 'view'
__call__
async
__call__(
path: str = ".", view_range: list[int] | None = None
) -> str | ToolReturn
View a file or list a directory.
Images and PDFs are returned as native model content. For text files and
directories, view_range selects an inclusive 1-indexed range. For PDFs,
it selects an inclusive physical page range. Images do not accept a range.
In restricted mode, paths must resolve under one of the configured filesystem roots. In unrestricted mode, absolute paths are allowed. Relative paths always resolve against the working directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Relative path to the file or directory. |
'.'
|
view_range
|
list[int] | None
|
Optional |
None
|
execute
async
execute(
path: str = ".", view_range: list[int] | None = None
) -> str | _ViewedMedia
View one filesystem path.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
EditFileTool
EditFileTool(
working_dir: str,
allowed_roots: Sequence[FilesystemRoot]
| None
| _DefaultAllowedRoots = _DEFAULT_ALLOWED_ROOTS,
)
Write files or replace exact text within them.
name
class-attribute
instance-attribute
name: ClassVar = 'edit_file'
__call__
async
__call__(
command: Literal["write", "replace"],
path: str,
new_text: str,
old_text: str | None = None,
replace_all: bool = False,
) -> str
Write a text file or replace exact text within one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command
|
Literal['write', 'replace']
|
|
required |
path
|
str
|
Path to the text file. |
required |
new_text
|
str
|
Complete file content for |
required |
old_text
|
str | None
|
Exact, whitespace-sensitive text to find for |
None
|
replace_all
|
bool
|
Replace every occurrence instead of requiring a unique
match. Only valid for |
False
|
execute
async
execute(
command: Literal["write", "replace"],
path: str,
new_text: str,
old_text: str | None = None,
replace_all: bool = False,
) -> str
Execute one structured file edit.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
ApplyPatchTool
ApplyPatchTool(
working_dir: str,
allowed_roots: Sequence[FilesystemRoot]
| None
| _DefaultAllowedRoots = _DEFAULT_ALLOWED_ROOTS,
)
Apply multi-file text patches.
name
class-attribute
instance-attribute
name: ClassVar = 'apply_patch'
__call__
async
__call__(patch: str) -> str
Apply a multi-file text patch. The preferred tool for editing files.
The patch must use the V4A envelope format with *** Begin Patch and
*** End Patch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
patch
|
str
|
Patch text containing one or more add, update, delete, or move operations. |
required |
execute
async
execute(patch: str) -> str
Apply one V4A patch.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
ExecuteBashTool
ExecuteBashTool(
working_dir: str | PathLike[str] | None = None,
*,
job_dir: str | PathLike[str] | None = None,
wait_timeout: float = _DEFAULT_WAIT_TIMEOUT_SECONDS,
max_output_chars: int = _DEFAULT_MAX_OUTPUT_CHARS,
env_overrides: Mapping[str, str] | None = None,
command_filter: Callable[[str], str | None]
| None = None,
)
Run independent Bash jobs concurrently.
Initialize the Bash job runner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
working_dir
|
str | PathLike[str] | None
|
Directory in which every command starts. |
None
|
job_dir
|
str | PathLike[str] | None
|
Scratch directory for bounded job logs. A private temporary directory is created when omitted. |
None
|
wait_timeout
|
float
|
Default wall-clock wait budget for waiting modes. |
_DEFAULT_WAIT_TIMEOUT_SECONDS
|
max_output_chars
|
int
|
Maximum retained command-output characters per job. |
_DEFAULT_MAX_OUTPUT_CHARS
|
env_overrides
|
Mapping[str, str] | None
|
Values merged over a snapshot of the launch environment. |
None
|
command_filter
|
Callable[[str], str | None] | None
|
Optional guard returning an error reason for blocked
commands and |
None
|
name
class-attribute
instance-attribute
name: ClassVar = 'execute_bash'
execute
async
execute(
command: str,
mode: BashMode = "detach_on_timeout",
wait_timeout: WaitTimeout | None = None,
) -> str
Execute a Bash command and return agent-facing output text.
__call__
async
__call__(
command: str,
mode: BashMode = "detach_on_timeout",
wait_timeout: WaitTimeout | None = None,
) -> str
Run a Bash command as an independent non-PTY job.
Calls may execute concurrently. Every command starts in the configured
project directory with a snapshot of TabulaFlow's launch environment;
shell state such as cd and export does not persist across calls.
Waiting commands use a wall-clock budget. detach_on_timeout preserves
work and returns its job id, process-group id, and bounded scratch log when
that budget expires. kill_on_timeout terminates the whole process group
instead. background returns the same job metadata immediately and does
not accept wait_timeout. Detached jobs remain owned by this session and
are terminated when it closes.
Inspect a running job with tail <log>. Stop it with
kill -TERM -- -<process_group>. The final line of a completed log
records its exit status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command
|
str
|
Bash source to execute. |
required |
mode
|
BashMode
|
Whether a waiting timeout kills or detaches the job, or whether to return it immediately in the background. |
'detach_on_timeout'
|
wait_timeout
|
WaitTimeout | None
|
Wall-clock seconds to wait. Omit to use the session
default. Not valid in |
None
|
close
async
close() -> None
Terminate all active jobs and release tool-owned resources.
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool
BashMode
module-attribute
BashMode: TypeAlias = Literal[
"kill_on_timeout", "detach_on_timeout", "background"
]
WaitTimeout
module-attribute
WaitTimeout: TypeAlias = Annotated[
float, Field(gt=0, allow_inf_nan=False)
]
Usage and traces
Usage
Bases: BaseModel
Aggregated model requests, tokens, and estimated API cost.
Adding usage from different models sets llm to "MULTI".
llm
instance-attribute
llm: str | Literal['MULTI'] | None
api_requests
instance-attribute
api_requests: int
input_tokens
instance-attribute
input_tokens: int
output_tokens
instance-attribute
output_tokens: int
api_cost_usd
instance-attribute
api_cost_usd: Decimal
cache_read_tokens
class-attribute
instance-attribute
cache_read_tokens: int = 0
cache_write_tokens
class-attribute
instance-attribute
cache_write_tokens: int = 0
create
classmethod
create(
llm: str | None = None,
api_requests: int = 0,
input_tokens: int = 0,
cache_read_tokens: int = 0,
cache_write_tokens: int = 0,
output_tokens: int = 0,
api_cost_usd: float | Decimal | None = None,
) -> Usage
Build usage, calculating cost when it is not supplied.
from_pydantic_ai_usage
classmethod
from_pydantic_ai_usage(
usage: RunUsage | RequestUsage, llm: str
) -> Usage
Convert Pydantic AI run or request usage for one model.
Trajectory
Bases: BaseModel
Serializable, provider-neutral record of one agent conversation or run.
id
class-attribute
instance-attribute
id: str = 'TRJY'
from_pydantic_ai_messages
classmethod
from_pydantic_ai_messages(
messages: list[ModelMessage], id: str = "TRJY"
) -> Trajectory
Normalize Pydantic AI request/response messages into a trajectory.
to_markdown
to_markdown() -> str
Render the complete trajectory as navigable Markdown.
Message
module-attribute
Message = Annotated[
Union[
AssistantMessage,
ToolResponse,
UserMessage,
SystemMessage,
],
Field(discriminator="role"),
]
SystemMessage
Bases: BaseModel
Normalized system message in a saved trajectory.
role
class-attribute
instance-attribute
role: Literal['system'] = 'system'
content
instance-attribute
content: str
UserMessage
Bases: BaseModel
Normalized user message in a saved trajectory.
role
class-attribute
instance-attribute
role: Literal['user'] = 'user'
content
instance-attribute
content: str
AssistantMessage
Bases: BaseModel
Normalized assistant text, thinking, and tool calls.
role
class-attribute
instance-attribute
role: Literal['assistant'] = 'assistant'
thinking
class-attribute
instance-attribute
thinking: str | None = None
content
instance-attribute
content: str
tool_calls
class-attribute
instance-attribute
tool_calls: list[ToolCall] = Field(default_factory=list)
ToolCall
Bases: BaseModel
Normalized assistant tool call and its decoded arguments.
arguments is None when the model generated invalid JSON.
tool_call_id
instance-attribute
tool_call_id: str
name
instance-attribute
name: str
arguments
instance-attribute
arguments: dict[str, Any] | None
ToolResponse
Bases: BaseModel
Normalized tool result or automatic retry prompt.
role
class-attribute
instance-attribute
role: Literal['tool'] = 'tool'
tool_call_id
instance-attribute
tool_call_id: str
response
instance-attribute
response: str
is_retry_prompt
class-attribute
instance-attribute
is_retry_prompt: bool = False
compute_api_cost
compute_api_cost(
llm: str,
input_tokens: int,
output_tokens: int,
*,
cache_read_tokens: int = 0,
cache_write_tokens: int = 0,
) -> Decimal
Estimate token cost, returning zero when the model has no known price.
instrument_agents
instrument_agents() -> None
Enable Pydantic AI instrumentation once for this process.
Tool metrics
Tool metrics properties expose these per-tool counters. Research-specific
counters are documented with the research tools.
ToolMetrics
module-attribute
ToolMetrics: TypeAlias = BaseModel
sum_tool_metrics
sum_tool_metrics(
metrics_iter: Iterable[_M], cls: type[_M]
) -> _M
Sum numeric fields across multiple metrics instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics_iter
|
Iterable[_M]
|
Iterable of metrics objects to aggregate. |
required |
cls
|
type[_M]
|
The metrics class to instantiate for the result. |
required |
RunQueryToolMetrics
Bases: BaseModel
Query call and failure counters.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
error_timeout
class-attribute
instance-attribute
error_timeout: int = 0
error_query_failed
class-attribute
instance-attribute
error_query_failed: int = 0
error_read_only_violation
class-attribute
instance-attribute
error_read_only_violation: int = 0
GetTableSchemaToolMetrics
Bases: BaseModel
Table-schema lookup, filtering, and limit counters.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
max_columns_exceeded
class-attribute
instance-attribute
max_columns_exceeded: int = 0
error_invalid_column_regex_filter
class-attribute
instance-attribute
error_invalid_column_regex_filter: int = 0
error_table_not_found
class-attribute
instance-attribute
error_table_not_found: int = 0
GetColumnJsonSchemaToolMetrics
Bases: BaseModel
Lookup and path-resolution counters for JSON-schema inspection.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
error_table_not_found
class-attribute
instance-attribute
error_table_not_found: int = 0
error_column_not_found
class-attribute
instance-attribute
error_column_not_found: int = 0
error_no_json_schema
class-attribute
instance-attribute
error_no_json_schema: int = 0
error_path_not_found
class-attribute
instance-attribute
error_path_not_found: int = 0
RegistryGetSchemaToolMetrics
Bases: BaseModel
Registry schema lookup and truncation counters.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
error_unknown_alias
class-attribute
instance-attribute
error_unknown_alias: int = 0
truncated
class-attribute
instance-attribute
truncated: int = 0
RegistryGetDataSourceDocumentToolMetrics
Bases: BaseModel
Metrics for RegistryGetDataSourceDocumentTool.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
error_unknown_alias
class-attribute
instance-attribute
error_unknown_alias: int = 0
truncated
class-attribute
instance-attribute
truncated: int = 0
WebBrowserToolMetrics
Bases: BaseModel
Browser action, error, and lifecycle counters.
num_navigates
class-attribute
instance-attribute
num_navigates: int = 0
num_screenshots
class-attribute
instance-attribute
num_screenshots: int = 0
num_clicks
class-attribute
instance-attribute
num_clicks: int = 0
num_types
class-attribute
instance-attribute
num_types: int = 0
num_scrolls
class-attribute
instance-attribute
num_scrolls: int = 0
num_backs
class-attribute
instance-attribute
num_backs: int = 0
num_presses
class-attribute
instance-attribute
num_presses: int = 0
num_selects
class-attribute
instance-attribute
num_selects: int = 0
num_waits
class-attribute
instance-attribute
num_waits: int = 0
num_errors
class-attribute
instance-attribute
num_errors: int = 0
num_popups_adopted
class-attribute
instance-attribute
num_popups_adopted: int = 0
num_tabs_auto_closed
class-attribute
instance-attribute
num_tabs_auto_closed: int = 0
num_clicks_dispatched_through_overlay
class-attribute
instance-attribute
num_clicks_dispatched_through_overlay: int = 0
ViewToolMetrics
Bases: BaseModel
Invocation and error counters for the filesystem viewer tool.
num_view
class-attribute
instance-attribute
num_view: int = 0
error_count
class-attribute
instance-attribute
error_count: int = 0
EditFileToolMetrics
Bases: BaseModel
Command and error counters for structured file editing.
num_write
class-attribute
instance-attribute
num_write: int = 0
num_replace
class-attribute
instance-attribute
num_replace: int = 0
error_count
class-attribute
instance-attribute
error_count: int = 0
ApplyPatchToolMetrics
Bases: BaseModel
Invocation and error counts for the apply-patch tool.
num_apply_patch
class-attribute
instance-attribute
num_apply_patch: int = 0
error_count
class-attribute
instance-attribute
error_count: int = 0
BashToolMetrics
Bases: BaseModel
Execution and lifecycle counters for the shell tool.
num_calls
class-attribute
instance-attribute
num_calls: int = 0
num_background_calls
class-attribute
instance-attribute
num_background_calls: int = 0
num_detached_calls
class-attribute
instance-attribute
num_detached_calls: int = 0
num_timeouts
class-attribute
instance-attribute
num_timeouts: int = 0
num_errors
class-attribute
instance-attribute
num_errors: int = 0
Runtime and model configuration
Initialize process-wide policies before creating model or browser resources.
Use make_agent to construct a Pydantic AI agent with TabulaFlow's shared
model throttling.
AgentRuntimeConfig
Bases: BaseSettings
Immutable process-wide policy for shared agent resources.
Explicit values override TABULAFLOW_* environment variables, which
override defaults. Optional limits use None for unlimited capacity.
Attributes:
| Name | Type | Description |
|---|---|---|
cache_dir |
Path
|
Root for persistent preprocessing caches. |
preprocessing_cache_mode |
AgentCacheMode
|
Read/write policy for derived agent inputs. |
max_llm_concurrency |
PositiveInt | None
|
Maximum simultaneous model requests. |
max_llm_requests_per_minute |
PositiveInt | None
|
Process-wide model request rate. |
max_embedding_concurrency |
PositiveInt | None
|
Maximum simultaneous embedding requests. |
max_embedding_requests_per_minute |
PositiveInt | None
|
Process-wide embedding request rate. |
browser_max_tabs |
PositiveInt | None
|
Process-wide open-page limit. |
browser_headless |
bool
|
Whether the shared Chromium process is headless. |
cache_dir
class-attribute
instance-attribute
cache_dir: Path = DEFAULT_CACHE_DIR
preprocessing_cache_mode
class-attribute
instance-attribute
preprocessing_cache_mode: AgentCacheMode = 'off'
max_llm_concurrency
class-attribute
instance-attribute
max_llm_concurrency: PositiveInt | None = 64
max_llm_requests_per_minute
class-attribute
instance-attribute
max_llm_requests_per_minute: PositiveInt | None = 600
max_embedding_concurrency
class-attribute
instance-attribute
max_embedding_concurrency: PositiveInt | None = 16
max_embedding_requests_per_minute
class-attribute
instance-attribute
max_embedding_requests_per_minute: PositiveInt | None = 150
browser_max_tabs
class-attribute
instance-attribute
browser_max_tabs: PositiveInt | None = 20
browser_headless
class-attribute
instance-attribute
browser_headless: bool = True
AgentCacheMode
module-attribute
AgentCacheMode: TypeAlias = Literal[
"off", "read_write", "refresh", "cache_only"
]
initialize_agent_runtime
initialize_agent_runtime(
config: AgentRuntimeConfig,
) -> None
Initialize the process-wide runtime before any agent capability uses it.
Initialization is optional; otherwise defaults and environment values resolve
lazily. A second initialization, including after lazy creation, raises
RuntimeError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AgentRuntimeConfig
|
Fully resolved immutable runtime policy. |
required |
make_agent
make_agent(
model: str | Model,
*,
output_type: type[_OutputT],
instructions: str | None = None,
tools: Sequence[Any] = (),
model_settings: Any = None,
retries: int = 3,
**kwargs: Any,
) -> Agent[object, _OutputT]
make_agent(
model: str | Model,
*,
output_type: ToolOutput[_OutputT],
instructions: str | None = None,
tools: Sequence[Any] = (),
model_settings: Any = None,
retries: int = 3,
**kwargs: Any,
) -> Agent[object, _OutputT]
make_agent(
model: str | Model,
*,
instructions: str | None = None,
tools: Sequence[Any] = (),
model_settings: Any = None,
retries: int = 3,
**kwargs: Any,
) -> Agent[object, str]
make_agent(
model: str | Model,
*,
output_type: Any = str,
instructions: str | None = None,
tools: Sequence[Any] = (),
model_settings: Any = None,
retries: int = 3,
**kwargs: Any,
) -> Agent[Any, Any]
Build a pydantic-ai Agent wired with tabulaflow's defaults.
The model is wrapped with throttling and vertex-claude resolution, and runs
default to no request limit. Every
tabulaflow Agent should be built via this. The named params are the
commonly-used ones (for discovery + type-checking); any other keyword accepted
by :class:pydantic_ai.Agent (e.g. capabilities, deps_type) flows
through **kwargs.
make_model_settings
make_model_settings(
*,
model: str,
reasoning: ThinkingLevel | None = None,
service_tier: ServiceTier | None = None,
timeout: float | None = None,
) -> ModelSettings
Build pydantic-ai model settings from provider-neutral LLM config.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Provider-qualified model identifier (e.g. |
required |
reasoning
|
ThinkingLevel | None
|
Unified thinking level, translated per provider. |
None
|
service_tier
|
ServiceTier | None
|
Provider service tier, for providers that expose one. |
None
|
timeout
|
float | None
|
Per-request timeout in seconds. On timeout the provider SDK retries the request automatically, so this doubles as a hang watchdog for non-streaming calls. |
None
|
ReasoningLevel
module-attribute
ReasoningLevel: TypeAlias = bool | ThinkingEffort
Type alias for thinking/reasoning configuration values.
True: Enable thinking with the provider's default effort.False: Disable thinking (silently ignored on always-on models).'minimal'/'low'/'medium'/'high'/'xhigh': Enable thinking at a specific effort level.
Not all providers support all levels. When a level is not natively supported,
it maps to the closest available value (e.g. 'xhigh' -> 'high' on providers
that don't support it, 'minimal' -> 'low' on providers without a minimal level).
ReasoningEffort
module-attribute
ReasoningEffort: TypeAlias = Literal[
"minimal", "low", "medium", "high", "xhigh"
]
The string effort levels for thinking/reasoning configuration.
ServiceTier
module-attribute
ServiceTier: TypeAlias = Literal[
"auto", "default", "flex", "priority"
]
model_label
model_label(model: str) -> str
Remove provider namespaces and a trailing release date from a model identifier.
Examples:
openai:gpt-5.6-sol becomes gpt-5.6-sol.
openai:gpt-5-2025-08-07 becomes gpt-5.
anthropic:claude-sonnet-4-5-20250929 becomes
claude-sonnet-4-5.
fireworks:accounts/fireworks/models/kimi-k3 becomes kimi-k3.
embedding_throttle
async
embedding_throttle() -> AsyncIterator[None]
Throttle an embedding call (concurrency + RPM from config).
Tool contracts
Tools expose a model-facing adapter through __call__ and
as_pydantic_ai_tool(). Tools with an execute(...) method also support direct
programmatic use. Registry adapters resolve connector aliases and may return
ToolReturn objects carrying display metadata. Check each tool's return type;
the adapters do not all return the same shape.
AgentTool
Bases: Protocol
Protocol for single-action agent tools.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Identifier exposed to the LLM as the tool's function name. |
name
class-attribute
name: str
__call__
__call__(*args: Any, **kwargs: Any) -> Any
as_pydantic_ai_tool
as_pydantic_ai_tool() -> Tool | ToolOutput[Any]
ToolCallOutcome
dataclass
ToolCallOutcome(
count: int | None = None,
unit: str | None = None,
error: bool = False,
)
Facts about one completed tool call, for the host's display.
Attached as pydantic_ai.ToolReturn.metadata by the tool's LLM-facing
entrypoints, so it rides the call's own return — never sent to the model.
Attributes:
| Name | Type | Description |
|---|---|---|
count |
int | None
|
Units of work the call returned (e.g. result rows). |
unit |
str | None
|
Noun for the count (e.g. |
error |
bool
|
Whether the call failed. |
count
class-attribute
instance-attribute
count: int | None = None
unit
class-attribute
instance-attribute
unit: str | None = None
error
class-attribute
instance-attribute
error: bool = False
ToolProgressUpdate
dataclass
ToolProgressUpdate(
completed: int,
total: int | None = None,
unit: str | None = None,
stage: str | None = None,
tool_call_id: str | None = None,
)
A progress tick from a long-running tool.
Attributes:
| Name | Type | Description |
|---|---|---|
completed |
int
|
Units of work finished so far. |
total |
int | None
|
Denominator, or |
unit |
str | None
|
Optional noun for the count (e.g. |
stage |
str | None
|
Optional named phase within the tool (e.g. |
tool_call_id |
str | None
|
Routes the tick to the right step when several tool calls run concurrently. |
completed
instance-attribute
completed: int
total
class-attribute
instance-attribute
total: int | None = None
unit
class-attribute
instance-attribute
unit: str | None = None
stage
class-attribute
instance-attribute
stage: str | None = None
tool_call_id
class-attribute
instance-attribute
tool_call_id: str | None = None
ProgressReportingTool
Bases: Protocol
Tool that reports progress ticks through its on_progress slot.
LLMProfileTool
Bases: Protocol
Tool with an internal LLM profile supplied by its host.
apply_llm_profile
apply_llm_profile(
*, llm: str, model_settings: ModelSettings | None
) -> None
Media inputs
These APIs prepare binary values for model input. Core media detection is documented in the Core reference.
to_binary_content
to_binary_content(
value: object,
*,
media_type: str | None = None,
decode_plain_base64: bool = False,
) -> BinaryContent
Convert a binary-like value into validated Pydantic AI content.
select_pdf_pages
select_pdf_pages(
data: bytes, page_range: tuple[int, int] | None = None
) -> PdfSelection
Validate a PDF and optionally select a 1-indexed inclusive page range.
PdfSelection
dataclass
PdfSelection(
data: bytes,
first_page: int,
last_page: int,
total_pages: int,
)
A validated PDF containing a selected range of physical pages.
data
instance-attribute
data: bytes
first_page
instance-attribute
first_page: int
last_page
instance-attribute
last_page: int
total_pages
instance-attribute
total_pages: int
inspect_inline_media
inspect_inline_media(
value: object,
) -> tuple[InlineMediaItem, ...] | None
Inspect a scalar media value or a top-level collection of media values.
Collections may mix supported media representations and retain their source order. Arbitrary nested structures are intentionally not traversed.
InlineMediaItem
dataclass
InlineMediaItem(
candidate: InlineMediaCandidate,
index: int | None = None,
)
One scalar item found in an inline media value or collection.
index
class-attribute
instance-attribute
index: int | None = None
InlineMediaCandidate
dataclass
InlineMediaCandidate(
value: object,
declared_type: str | None,
estimated_size: int | None,
)
A binary-like value inspected without decoding its contents.
value
instance-attribute
value: object
declared_type
instance-attribute
declared_type: str | None
estimated_size
instance-attribute
estimated_size: int | None
materialize_inline_media
materialize_inline_media(
candidate: InlineMediaCandidate, *, max_bytes: int
) -> BinaryContent
Convert a bounded inline candidate into a validated image or PDF.
UnrecognizedMediaError
Bases: ValueError
Raised when an inline binary value has no identifiable media type.
UnsupportedModelMediaError
Bases: ValueError
Raised when media cannot be attached to the active model.
Message storage
MessageStore persists user prompts and tool responses in a writable SQL
connector. Scoped stores attach an agent provenance tag to writes.
MessageStore
MessageStore(connector: SQLConnector | None = None)
Append-only mirror of user prompts and tool responses in workspace DuckDB.
Successfully stored entries receive sequential M1, M2, ... ids. Model-
visible marking and truncation remain the caller's responsibility.
add
async
add(
*,
kind: MessageKind,
content: str,
tool_name: str | None = None,
tool_call_id: str | None = None,
agent_id: str | None = None,
) -> str | None
Persist one message and return its id, or None if storage is unavailable.
agent_id is a provenance tag (e.g. "main", "subagent:<call>:<row>")
— not an access scope. Use :meth:scoped to bind it once and avoid threading
the value through every call site.
scoped
scoped(agent_id: str) -> ScopedMessageStore
Return a thin handle that pins agent_id on every add call.
ScopedMessageStore
dataclass
A thin handle over :class:MessageStore that pins agent_id on writes.
Provenance tagging only — does not restrict reads in any way. Construct via
:meth:MessageStore.scoped rather than instantiating directly.
agent_id
instance-attribute
agent_id: str
add
async
add(
*,
kind: MessageKind,
content: str,
tool_name: str | None = None,
tool_call_id: str | None = None,
) -> str | None
MessageKind
module-attribute
MessageKind = Literal['user_prompt', 'tool_return']