Skip to content

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 apply_patch instead of edit_file when filesystem tools are available.

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 None to disable it.

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.

reasoning property

reasoning: ReasoningLevel

Active reasoning level for the interactive model.

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_stream(question: ChatInput) -> AsyncIterator[ChatEvent]

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 apply_patch instead of edit_file.

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

output class-attribute instance-attribute

output: OutputSpec = Field(default_factory=OutputSpec)

usage class-attribute instance-attribute

usage: Usage | None = None

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

outcome class-attribute instance-attribute

outcome: ToolCallOutcome | None = None

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'

usage instance-attribute

usage: Usage

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'

result instance-attribute

result: ChatResult

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 registry.

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 "Classify this job: {{ description }}".

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 record_type is not a Pydantic model class or contains unsupported column types.

ValueError

If record_type is empty or a root model, or input columns, the template, or inline media are invalid.

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 extract calls on this instance.

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. "Source: <title> (<url>)") surfaced in every chunk's <context> block, so provenance/framing reaches each excerpt without the caller threading it through instruction.

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 record_type, in chunk order. Empty if the document is blank

list[_EntityT]

or contains no matching records.

Raises:

Type Description
TypeError

If record_type is not a Pydantic model class.

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

usage

usage() -> Usage

Return model usage accumulated by uncached summary generation.

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// URL. A dataset with multiple configs/subsets requires one, named as .../viewer/ (optionally .../viewer//).

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 parameters argument to the LLM.

enable_refresh

Whether to expose the refresh argument to the LLM.

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; None explicitly disables the timeout.

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

metrics

metrics() -> RunQueryToolMetrics

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]

exec_result instance-attribute

exec_result: ExecResult

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 parameters argument to the LLM.

False
enable_refresh bool

Whether to expose the refresh argument to the LLM. When True, the agent can request a connector schema refresh after DDL.

False
enable_media bool

Whether to expose inline result-cell media inspection.

False
enable_max_cell_chars bool

Whether to expose max_cell_chars so the agent can expand textual result cells.

False
timeout int | None | object

Query timeout in seconds. When omitted, use each connector's default; None explicitly disables the timeout.

_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

metrics

metrics() -> RunQueryToolMetrics

Return aggregated metrics across all aliases.

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 refresh parameter in the tool schema sent to the LLM. When False (default), the parameter is hidden from the LLM entirely.

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 None when schemas are not applicable.

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

metrics

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

metrics

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 refresh parameter to the LLM.

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

metrics

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 refresh parameter to the LLM.

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 None when schemas are not applicable.

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

metrics

Return aggregated metrics across all aliases.

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

metrics

Return aggregated metrics across all aliases.

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 refresh to the LLM tool signature.

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 run_query.

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 run_query (for example S3). To write a complete table, first run SELECT * FROM <table> without LIMIT, then write that result.

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 to create a new table, append to add rows, replace_rows to replace rows while preserving the table definition, or replace_table to recreate the table.

'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 enable_run_query_tool=True so the per-row subagent can query any registered data source. If omitted, that flag is unavailable.

None
message_store MessageStore | None

Optional workspace-backed message store. When provided, every browser tool return is mirrored here and tagged with a [message_id=M<n>] marker so the agent can reference it. For non-leaf subagents (enable_nested_subagents=True) that also have a registry, oversized prompts and tool returns are additionally replaced with head+tail snippets, and the subagent gets a registry-backed run_query tool to read the full content back from workspace._internal.messages. Without a store, browser returns are neither mirrored nor tagged.

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 _subagent_exception and _subagent_trajectory columns back to the target table after each row. _subagent_exception is NULL on success and a "<ExceptionType>: <message>" string on failure.

False
trajectory_log_dir Path | None

If set, each per-row subagent trajectory is written as <dir>/<call_id>/row-<N>.md after the row finishes. Nested subagent instances inherit the same directory so their own call_ids appear alongside the parent's. Independent of store_metadata: this is a filesystem sink for local debugging; store_metadata writes to the target table.

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

on_progress instance-attribute

on_progress: Callable[[ToolProgressUpdate], None] | None = (
    None
)

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 table_name (None if unqualified).

required
table_name str

Target table name. Used as the write-back target; per-row updates locate rows here via key_columns.

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 task_instruction. Must include all key_columns. Pass SELECT * FROM <table_name> as a default. Example::

SELECT r.review_id,
       r.product_name,
       m.content AS review_text
FROM reviews r
JOIN _internal.messages m ON r.msg_ref = m.message_id
WHERE r.sentiment IS NULL
required
task_instruction str

A Jinja2 template rendered per-row as the subagent prompt. Use {{ column_name }} to interpolate values from the task_query result; standard Jinja control flow ({% for %}, {% if %}) is available. For consistency, state in the instruction how missing information should be handled — abort_task (row fails, nothing written) or a NULL field (row succeeds with that field null). For JSON columns, extract the field or cast to an array in task_query using the dialect's JSON functions rather than relying on the template — driver materialization varies (string vs structure) and only structured projections iterate reliably. Example: "Classify the sentiment of: {{ review_text }}".

required
key_columns list[str]

Columns used in the WHERE clause to locate each row in table_name for write-back. Must be real columns of table_name (not computed/joined-only), must appear in the task_query result, and together must form a unique, non-null key — one task_query row per target row.

required
output_columns list[str]

One or more columns to update on table_name; the subagent emits a value for each in a single submit_answer output. They need not appear in the task_query projection. All must already exist on table_name and must be scalar, text, or date columns — each value is stored as that column's type (numeric/boolean/ date → native values; text → text). For list/nested values, target a text column holding a JSON string (DuckDB JSON columns work too). Array, struct, map, and binary columns are not valid targets.

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 extract_rows_from_documents and add_canonical_name tools (the document-mining + canonicalization toolchain), wired to the workspace connector — so a browsing row can turn pages into clean structured rows end to end.

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. .../flights/search?tfs=... — it can load the generic landing page with no results, because the results RPC is gated behind in-page interaction. If that happens, have the subagent fall back to opening the entry page and submitting the search form rather than giving up on the deep link.

False
enable_nested_subagents bool

If True, each per-row subagent additionally receives this run_subagent_for_each_row tool, allowing it to fan out further row-wise tasks of its own. The flag does not propagate automatically — each nested level must opt in explicitly.

False
enable_run_query_tool bool

If True, the per-row subagent additionally receives a registry-backed run_query tool that can query and modify any registered data source (the subagent specifies connector_alias per call). Enable it for tasks where row-local context isn't enough:

  • Computing a large output via SQL. When the value is too large to round-trip through submit_answer, have the subagent UPDATE the target column itself with run_query, and set output_columns to a separate small acknowledgment column for submit_answer to fill. The task_instruction must give the subagent the connector_alias, table_name, and key columns for its WHERE.
  • Reads or writes beyond the row. The subagent reads auxiliary tables for context, or writes to other tables (INSERTs, DDL).
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 task_query and receives the appended rows (same database).

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 <dir>/<call_id>/doc-<D>-chunk-<N>.md. A filesystem sink for local debugging, mirroring run_subagent_for_each_row.

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

on_progress instance-attribute

on_progress: Callable[[ToolProgressUpdate], None] | None = (
    None
)

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 table_name (None if unqualified).

required
table_name str

Existing target table to append rows into. All output_columns must already exist on it; other columns are left NULL/default.

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 content (alias it if needed, e.g. SELECT body AS content, url FROM ...). Path-backed media is not fetched; import its bytes before projecting it as content. Any other columns are variables available to task_instruction (content itself is NOT available to the template). Column order does not matter.

required
task_instruction str

A Jinja2 template rendered once per source document describing what one entity is and how to populate output_columns. It may reference any column of task_query other than content (e.g. {{ url }}); the document content itself is not available to the template. Example: "Extract every product mentioned. For each, capture name and price_usd."

required
output_columns list[str]

Columns each extracted entity populates. Must be non-empty. All must already exist on table_name and must be scalar, text, or date columns — each value is stored as that column's type (numeric/boolean/date → native values; text → text). For list/nested values, target a text column holding a JSON string (DuckDB JSON columns work too). Array, struct, map, and binary columns are not valid targets.

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 <dir>/<call_id>/<role>-<idx>.md where role is one of resolve, picker, or disambiguate. Useful for debugging clustering and disambiguation decisions.

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

on_progress instance-attribute

on_progress: Callable[[ToolProgressUpdate], None] | None = (
    None
)

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 table_name. Pass None for unqualified tables.

required
table_name str

Table containing both input_column and canonical_column.

required
canonical_column str

Existing column to populate. Set equal to input_column to canonicalize in place.

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 "Bob Smith" or "Acme Corp"), include a rule for how the canonical should extend on collision — e.g. "append a parenthetical city, like 'Bob Smith (Chicago)'".

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 input_column identifies the row's own entity; never on a foreign attribute.

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 not_applicable(reason) to declare that the source intentionally does not apply for the active selection.

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 query_template.

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

artifact_source instance-attribute

artifact_source: ParameterizedArtifactSource

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 "S1".

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 instance-attribute

artifacts: tuple[ArtifactRef, ...]

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 http:// or https:// URL.

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. "t1".

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. "t1" (from a previous response).

required
ref str

The ref string of the target element, e.g. "e15". Must come from the tab's MOST RECENT response — refs are per-snapshot.

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. "t1".

required
ref str

The ref string of the target element, e.g. "e15". Must come from the tab's MOST RECENT response — refs are per-snapshot.

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. "t1".

required
direction Literal['up', 'down', 'top', 'bottom']

One of "up", "down", "top", "bottom".

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_navigate with tab=<id> (explicit in-place navigation).
  • browser_click on a link, form submit, or JS-driven nav element.
  • browser_type with submit=True causing 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. "t1".

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. "t1".

required
key str

A Playwright key name — e.g. "Escape", "Enter", "Tab", "ArrowDown", "PageDown", "Backspace", or a chord like "Control+a" / "Meta+v".

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. "t1".

required
ref str

The ref string of the target element, e.g. "e15". Must come from the tab's MOST RECENT response — refs are per-snapshot.

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. "t1".

required
seconds float

Fixed sleep when neither text nor text_gone is given; otherwise the timeout.

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=[...]).

metrics

metrics() -> WebBrowserToolMetrics

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 [start, end] for view (1-indexed). For files, selects a line range; for directories, an entry range for pagination; for PDFs, a physical page range.

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

metrics

metrics() -> ViewToolMetrics

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']

write to create or overwrite a file, or replace to replace text in an existing file.

required
path str

Path to the text file.

required
new_text str

Complete file content for write; replacement text for replace.

required
old_text str | None

Exact, whitespace-sensitive text to find for replace.

None
replace_all bool

Replace every occurrence instead of requiring a unique match. Only valid for replace.

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

metrics

metrics() -> EditFileToolMetrics

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

metrics

metrics() -> ApplyPatchToolMetrics

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 for allowed commands.

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 background mode.

None

close async

close() -> None

Terminate all active jobs and release tool-owned resources.

as_pydantic_ai_tool

as_pydantic_ai_tool() -> Tool

metrics

metrics() -> BashToolMetrics

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'

messages instance-attribute

messages: list[Message]

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. anthropic:claude-...).

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]

metrics

metrics() -> ToolMetrics

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. "rows", "columns").

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 None for an open-ended running count.

unit str | None

Optional noun for the count (e.g. "rows").

stage str | None

Optional named phase within the tool (e.g. "canonicalize").

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.

on_progress instance-attribute

on_progress: Callable[[ToolProgressUpdate], None] | None

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.

candidate instance-attribute

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']