Agent
Optional install
The agent module is not part of the core aitaem install. See
Getting Started for install instructions and a
two-line quick start for QueryBot and DefinitionBot.
Primitives
Bot
aitaem.agent.base.Bot
Bases: ABC
Abstract base class for all aitaem agent bots.
Subclasses implement _build_agent() to return a configured pydantic-ai Agent. Bots are constructed per user/session; conversation state (message history and result store) lives on the bot instance.
The standard pattern for subclass construction
class MyBot(Bot): def init(self, my_resource, kwargs): self._my_resource = my_resource # set BEFORE super().init() super().init(kwargs) # triggers _build_agent()
def _build_agent(self):
# self._my_resource is already available here
from pydantic_ai.toolsets import FunctionToolset
toolset = FunctionToolset()
toolset.add_function(my_default_tool)
for tool in self._tools:
_register_tool(toolset, tool)
self._toolset = toolset # REQUIRED — see contract below
return pydantic_ai.Agent(..., toolsets=[self._toolset])
Tool composition contract: _build_agent() MUST build a FunctionToolset, register self._tools onto it (via the module-level _register_tool() helper), and assign it to self._toolset before returning the Agent. add_tool() mutates self._toolset in place, so it has nothing to mutate otherwise. Bot.init raises TypeError immediately after _build_agent() returns if self._toolset is still None, naming the offending subclass.
Context-window management: for long-running sessions where history may exceed the model's context limit, pass a history processor via the capabilities argument when constructing the Agent in _build_agent():
from pydantic_ai.capabilities import ProcessHistory, ReinjectSystemPrompt
def _build_agent(self):
return Agent(
model=self._model,
capabilities=[
ReinjectSystemPrompt(replace_existing=True),
ProcessHistory(trim_old_messages),
],
)
The processor callable receives the full message list before each model request (including mid-tool-call-loop steps) and returns a modified list. IMPORTANT: only trim complete tool-call pairs (ToolCallPart + matching ToolReturnPart) as a unit. Dropping a ToolReturnPart without its ToolCallPart violates provider API constraints. See pydantic-ai issue #2050. No built-in trimmer is provided by pydantic-ai; implement one in Phase 2+.
Source code in aitaem/agent/base.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
chat
async
Send a message and accumulate history (multi-turn entry point).
Source code in aitaem/agent/base.py
ask
async
Send a single-turn message without accumulating history.
Source code in aitaem/agent/base.py
add_tool
Add a tool to this bot's persistent tool set at runtime.
Takes effect on the next chat()/ask() call. Mutations during an in-progress run() are undefined.
Source code in aitaem/agent/base.py
add_bot
as_tool
get_result
dump_history
Serialize conversation history and result artifacts to a JSON-safe dict.
The returned dict is JSON-serializable (suitable for json.dumps). Ibis refs are not serialized; only Arrow artifacts are preserved. Reload with load_history() to restore history with Arrow artifacts available via get_result(), but get_ibis() will return None on restored entries.
Tools added at runtime via add_tool() are NOT restored by load_history() — their names are recorded in the bundle so load_history() can warn if they're missing after reload, but the callables themselves aren't portably serializable. Pass them again via tools=[...] or call add_tool() on the reloaded bot to restore them.
Source code in aitaem/agent/base.py
load_history
classmethod
Construct a new bot pre-loaded with a serialized history bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
Bundle returned by dump_history() on a prior bot instance. |
required |
**kwargs
|
Any
|
Constructor arguments for the concrete Bot subclass. |
{}
|
Returns:
| Type | Description |
|---|---|
Bot
|
A new bot instance with _message_history and store populated from |
Bot
|
data. The bot's _agent is rebuilt fresh from **kwargs. |
Warns (UserWarning) if the bundle references tools added via add_tool() on the original bot that are not present on the reloaded bot — pass them again via tools=[...] in **kwargs, or call add_tool() after reload, to silence the warning.
Source code in aitaem/agent/base.py
BotResponse
aitaem.agent.response.BotResponse
Bases: BaseModel, Generic[PayloadT]
The response returned by every Bot method — status, narrative, trace, and an optional typed payload.
Source code in aitaem/agent/response.py
Status
aitaem.agent.response.Status
Bases: str, Enum
Outcome of a bot response: ok, empty, refused, or error.
refused is the general "no spec precisely answers — don't substitute an
approximation" outcome (the Metric Precision Rule), distinct from error
(a tool or the run itself failed) and empty (ran fine, no results).
Source code in aitaem/agent/trace.py
RunTrace
aitaem.agent.trace.RunTrace
Bases: BaseModel
Aggregated, eval-friendly trace of one bot run — tool calls, usage, and timing.
Source code in aitaem/agent/trace.py
ToolCall
aitaem.agent.trace.ToolCall
Bases: BaseModel
One tool invocation within a run, with its arguments, outcome, and an
LLM-facing summary (never the raw result — see result_id for that).
Source code in aitaem/agent/trace.py
Usage
aitaem.agent.trace.Usage
Bases: BaseModel
Token and tool-call usage counters for one bot run.
Source code in aitaem/agent/trace.py
ResultStore
aitaem.agent.store.ResultStore
Session-scoped store for computation results.
Supports two entry kinds: tabular (Arrow + Ibis ref) and text (string + content_type). Use store_tabular() / get_tabular() for computation results; store_text() / get_text() for serialized artifacts such as validated YAML specs. The generic get() returns the union and is useful when the caller handles either kind.
Source code in aitaem/agent/store.py
ResultEntry
TabularEntry
aitaem.agent.store.TabularEntry
Bases: _EntryBase
Tabular result entry holding an Arrow artifact and optional live ibis ref.
Source code in aitaem/agent/store.py
TextEntry
aitaem.agent.store.TextEntry
WrongEntryKindError
aitaem.agent.store.WrongEntryKindError
QueryBot
QueryBot
aitaem.agent.query_bot.QueryBot
Bases: Bot
Convenience bot for answering natural-language questions against a metric catalog.
Tools create a MetricCompute instance per call from the held spec_cache and connection_manager. Artifacts are written to the bot's ResultStore; callers dereference via get_result(result_id).
Construction
bot = QueryBot( model="anthropic:claude-sonnet-4-6", spec_cache=my_spec_cache, connection_manager=my_connection_manager, ) response = await bot.chat("What was Q4 revenue by region?")
Multi-provider
Use model strings supported by pydantic-ai, e.g. "openai:gpt-4o". For testing, pass a FunctionModel or TestModel instance directly.
tenant_id
Optional per-tenant identifier for OpenAI prompt-cache routing. When omitted, a fingerprint of the spec_cache's visible catalog is used, which naturally separates RBAC-differentiated permission sets.
Source code in aitaem/agent/query_bot.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 | |
chat
async
Send a message in multi-turn mode. Accumulates history on the bot.
Always returns a QueryResponse — exceptions from the agent run are caught and surfaced as status=error rather than propagated raw.
Source code in aitaem/agent/query_bot.py
ask
async
Send a single-turn message. Does NOT accumulate history.
Always returns a QueryResponse — exceptions from the agent run are caught and surfaced as status=error rather than propagated raw.
Source code in aitaem/agent/query_bot.py
QueryResponse
aitaem.agent.query_bot.QueryResponse
Bases: BotResponse[QueryPayload]
Concrete response type for QueryBot — narrows BotResponse's generic payload.
Source code in aitaem/agent/query_bot.py
QueryPayload
aitaem.agent.query_types.QueryPayload
Bases: BaseModel
Metadata assembled by QueryBot from QueryOutput and the turn trace.
Source code in aitaem/agent/query_types.py
MetricIntent
aitaem.agent.query_types.MetricIntent
dataclass
Structured interpretation of one metric the user is asking about.
Produced by record_intent and stored in QueryDeps.intents. One intent per metric; multi-metric questions produce multiple intents.
Source code in aitaem/agent/query_types.py
ResolvedSpec
aitaem.agent.query_types.ResolvedSpec
dataclass
Validated compute parameters keyed by spec_token in QueryDeps.spec_registry.
Constructed by resolve_intent when SpecResolver confirms an exact match. Consumed by compute_metrics(spec_token) to reconstruct MetricCompute arguments.
Source code in aitaem/agent/query_types.py
ExactMatch
aitaem.agent.query_types.ExactMatch
NearMiss
aitaem.agent.query_types.NearMiss
Bases: BaseModel
A catalog entry that resolve_intent considered but rejected, with the reason why.
Source code in aitaem/agent/query_types.py
SpecMatchResult
aitaem.agent.query_types.SpecMatchResult
Bases: BaseModel
Returned to the LLM by resolve_intent.
If exact_match is not None: the LLM proceeds to compute_metrics(spec_token). If exact_match is None: the LLM must produce status=refused and cite near_misses.
Source code in aitaem/agent/query_types.py
RecordIntentResult
aitaem.agent.query_types.RecordIntentResult
ResolveIntentResult
aitaem.agent.query_types.ResolveIntentResult
Bases: BaseModel
Returned by resolve_intent. Wraps SpecMatchResult for the LLM.
Source code in aitaem/agent/query_types.py
SpecResolver
aitaem.agent.resolver.SpecResolver
Deterministic v0 catalog validator.
v0 → v1 swap point: the interface (resolve method signature and return type) is stable. Only the body changes in v1 (dict lookup → RAG retrieval + deterministic filter).
Source code in aitaem/agent/resolver.py
resolve
resolve(intent: MetricIntent, proposed_metric_name: str, proposed_slices: list[str], proposed_segment: str | None, spec_cache: Any) -> SpecMatchResult
Validate the proposed names against the catalog.
Returns SpecMatchResult with exact_match set if all validations pass. The spec_token inside exact_match is left empty (""); the caller (resolve_intent tool) mints and fills the token after this method returns.
Source code in aitaem/agent/resolver.py
DefinitionBot
DefinitionBot
aitaem.agent.definition_bot.DefinitionBot
Bases: Bot
Convenience bot for defining MetricSpec, SliceSpec, and SegmentSpec.
Uses a 4-step token-gated workflow: record_definition_intent → list_tables / describe_table → draft_spec → validate_spec loop → DefinitionOutput.
The bot is primarily a single-turn bot — use ask() for each new spec. chat() is provided for cross-turn context but multi-turn revision re-drafts from scratch (drafts are ephemeral per run(); see ND-10).
Construction
bot = DefinitionBot( model="anthropic:claude-sonnet-4-6", connection_manager=my_connection_manager, spec_cache=my_spec_cache, ) response = await bot.ask("Define a metric for weekly active users on the events table.")
Source code in aitaem/agent/definition_bot.py
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | |
ask
async
Send a single-turn message. Does NOT accumulate history.
Each ask() call is fully independent — drafts from prior ask() calls are not accessible. Use chat() for cross-turn context.
Source code in aitaem/agent/definition_bot.py
chat
async
Send a message in multi-turn mode. Accumulates history on the bot.
Drafts from prior turns are not accessible across runs — cross-turn revision re-drafts from scratch. For specs whose YAML exceeds 800 chars, cross-turn recovery may be lossy (ND-10).
Source code in aitaem/agent/definition_bot.py
DefinitionResponse
aitaem.agent.definition_bot.DefinitionResponse
Bases: BotResponse[DefinitionPayload]
Concrete response type for DefinitionBot — narrows BotResponse's generic payload.
Source code in aitaem/agent/definition_bot.py
DefinitionPayload
aitaem.agent.definition_types.DefinitionPayload
Bases: BaseModel
Assembled bot payload returned in DefinitionResponse.payload.
Source code in aitaem/agent/definition_types.py
DefinitionIntent
aitaem.agent.definition_types.DefinitionIntent
dataclass
Records what the user wants to define. Set once per run by record_definition_intent.
Source code in aitaem/agent/definition_types.py
SpecDraft
aitaem.agent.definition_types.SpecDraft
dataclass
Server-side storage for a not-yet-validated YAML string. Keyed by draft_id.
Source code in aitaem/agent/definition_types.py
ColumnInfo
aitaem.agent.definition_types.ColumnInfo
ListTablesResult
aitaem.agent.definition_types.ListTablesResult
Bases: BaseModel
Returned by list_tables. Both fields may be non-empty on partial success.
Source code in aitaem/agent/definition_types.py
DescribeTableResult
aitaem.agent.definition_types.DescribeTableResult
Bases: BaseModel
Returned by describe_table.
Source code in aitaem/agent/definition_types.py
DraftSpecResult
aitaem.agent.definition_types.DraftSpecResult
Bases: BaseModel
Returned by draft_spec.
Source code in aitaem/agent/definition_types.py
ValidateSpecResult
aitaem.agent.definition_types.ValidateSpecResult
Bases: BaseModel
Returned by validate_spec — the anti-hallucination gate.