Skip to content

Agent

Agent is the high-level interface for running model interactions with optional tools, files, sessions, and structured output.

Responsibilities

  • Detect provider from model name (_detect_provider).
  • Load tools from multiple formats via ToolLoader.
  • Transform media inputs with MediaTransformer.
  • Route execution between function-calling and structured-output code paths.
  • Normalize provider responses to AgentResult.
  • Optionally track detailed per-run costs.

Initialization model

Primary constructor parameters include:

  • Core: model, instructions, max_iterations, max_tokens, temperature (sent only to models whose registry row lists it — see Sampling knobs). max_iterations bounds the tool rounds of a run. result.error reports "Maximum completed tool calling loop iterations (N) reached" when the rounds ran out before a finished answer: the model was still calling tools, or, on Gemini, its last turn was cut off (at max_tokens, say) or came back empty or thought-only after a tool round, with no round left to ask again. A run that answers on its last allowed round succeeds, on every provider (Gemini fixed in 0.5.1)
  • Tooling: tools, tool_choice, strict_tools (Anthropic; each request stays within Anthropic's per-request strict limits, see Strict tool schemas on Anthropic), built_in_tools, defer_tools (see Tool search and deferred tools), max_tool_result_chars (see Tool-result size cap), tool_result_preview_chars (default 500: how much of each tool result the tool_call_result stream and hook events carry; 0 = the whole result plus result_data. It never changes what the model is sent; see Tool results in events), truncated_result_read_chars (default None, off: how many more characters of a truncated tool result the model may read back with the built-in read_truncated_result tool; see Letting the model read past the cut)
  • Reasoning: thinking, plus provider-specific kwargs
  • Steering: hooks — an awaited control channel that can veto a tool call, inject a message, pause, or abort (see Hooks and Steering); emit_request_start (default False) adds the opt-in request_start event on both channels
  • Prompt-part policy: on_unsupported_part — "drop" (default, unchanged behaviour) or "raise" (see Unsupported parts are now loud)
  • Pre-flight estimate: estimate_input_tokens — "lazy" (the new default), True or False (see The pre-flight estimate no longer calls the provider)
  • Files/media: code_execution_output_dir (default: a fresh per-run folder; a name the run already wrote is written as name (2).ext, a file an earlier run left there is overwritten — see Output files), per-run files, tool_files, media_resolution_list
  • Persistence: media_policy, media_store, media_max_inline_bytes — what a saved session does with media and where externalised bytes live (see Media in a checkpoint)
  • Anthropic code execution container reuse (per-run or session-scoped): anthropic_container_id, reuse_anthropic_container (session-only). See Built-in tools recipe for examples.
  • Session/cost: auto_inject_memory_instructions, auto_inject_skills, detailed_costs, cost_output_dir
  • Context management: compaction, compaction_config, session_history_config (see Compaction and Sessions)
  • Per-token cost levers: prompt_caching (Anthropic opt-in — see Prompt Caching), service_tier ("priority" or "flex" on OpenAI and Google, "ultrafast" on gpt-6-astra — see Service Tiers)
  • Transport: openai_transport="websocket" runs an OpenAI agent's tool loop over one WebSocket instead of one HTTP request per step (opt-in, same results and billing — see OpenAI WebSocket Transport)
  • Anthropic thinking-prefix controls: anthropic_block_binding, strict_prefix_binding, prefix_audit, prefix_strip_fallback (see Thinking)
  • Credentials: api_key (optional per-Agent override of the provider's environment variable)
  • Logging: logger, log_level, log_file, and log_to_file (default True). With no logger= the Agent builds its own logger that also writes ./logs/<logger-name>_<timestamp>.log; log_to_file=False — or DPT_AGENT_LOG_TO_FILE=0 for the whole process — makes it console-only, never creates ./logs/, and ignores log_file= with a warning. A logger= you pass is used as-is either way. In a YAML config, logging.to_file: false (or a log_to_file override to Agent.from_config) does the same.
  • Recipe: to_config() returns the data half of the constructor as the YAML shape from_config reads (model, instructions, constraints, the tool and media policies, thinking, compaction, the history mode, logging, provider overrides, every tool given as data); Python callables, hooks, a custom media store, a logger and the api key are listed under unserialisable, never dropped silently. So are a pre-built {"session": ...} entry and the {"type": "fastmcp", ...} dict form, by name, since 0.5.1 (they hold a live object), and since 0.5.3 the env values of an mcp_command / mcp_script entry (an API key may be among them): the entry is emitted without env and the variable names go under unserialisable.tool_env; a path-like cwd is emitted as a string. session_history_config is a YAML key too. See Best practices.

Provider-specific keywords are forwarded as given — file_active_timeout_s, file_poll_interval_s and file_poll_backoff reach the Google entry points this way, for example. A keyword the target provider's function does not declare is dropped at request time, so check Providers for the spelling. Construction now catches the mistake early: a keyword that is neither an Agent parameter nor one the provider's parameter mapping or entry points accept — or one the Agent supplies itself, such as system_prompt — logs one WARNING naming every such keyword, with the Agent parameter to use where there is an obvious one (system_prompt= → instructions=). It never raises, and the keywords are still forwarded unchanged, so code that passes provider keywords deliberately keeps working.

Reasoning in a schema field

When your output_schema has a field named internal_reasoning, thoughts or reasoning (case-insensitive), every provider appends the model's reasoning text to that field of output, after whatever the model wrote there, under a ***Internal Reasoning:*** heading. The same text is always on result.reasoning_content, which is the canonical location — so such a run carries the reasoning twice.

The merge stays the default for clients that read it from output. DPT_MERGE_REASONING_INTO_SCHEMA=off stops the copy on all four providers: the field then holds exactly what the model wrote, and the reasoning is on reasoning_content only.

Scheduled change in 0.5

The merge is scheduled to be retired in 0.5: the default will become off, as a breaking change with its own release note. Read reasoning from reasoning_content now, and set DPT_MERGE_REASONING_INTO_SCHEMA=off to see today what your schema field will hold then.

Client lifecycle

By default an Agent borrows its provider client from a process-level cache, so many agents on the same provider share one SDK client and one connection pool. await agent.close() disconnects local MCP servers and drops the Agent's references, but deliberately leaves a shared client open — closing it would break every other live Agent using it. The MCP server configs survive close(), so the Agent reconnects them on its next run (see Tools).

Passing api_key=... changes that: the Agent builds its own client outside the shared cache, and its close() closes that client. Use it when one process must talk to a provider under more than one credential.

To close the shared clients at process exit, call doc_processing_toolkit.utils.shutdown_clients(), or await shutdown_clients_async() from code already running on an event loop. Full details, including event-loop affinity for async clients and the loop-teardown hook that makes an explicit call rarely necessary, are in Async Clients.

The pre-flight estimate no longer calls the provider

Agent(estimate_input_tokens=...) controls how the pre-flight input-token figure is produced. That figure is read by exactly one thing — the cost record a provider writes when a stream fails mid-flight — and it used to be a call to the provider's own token-counting API before every request. On Google that call uploads every media part to the Files API, polls it and deletes it again, so a run carrying one image uploaded those bytes twice and the counted copy could never be reused.

Value What happens
"lazy" The new default. A purely local approximation: roughly four characters per token for text, plus a fixed figure per non-text part (agent.core.LAZY_MEDIA_TOKEN_ESTIMATES). No API call, and on Google no Files API upload.
True The previous behaviour verbatim — the provider's exact count, upload included.
False No estimate at all; the figure is 0, which is what the old code already did whenever the count raised.

This is a default change

On "lazy" the failure-path cost estimate becomes a local approximation instead of an exact provider count. It feeds a record that was already an estimate and never a billed total, and the media figures are deliberately coarse order-of-magnitude numbers rather than vendor-published ones. Pass estimate_input_tokens=True if you want the old exactness back on that one path. Anything other than True, False or "lazy" raises at construction (agent.core.ESTIMATE_INPUT_TOKENS_MODES is the vocabulary).

Google Files API uploads on the request path are reused as well: identical bytes sent under the same credential upload once and are reused while the handle is ACTIVE and not about to expire. Reuse is keyed on credential and content and MIME type, so a handle can never cross API keys, and one the cleanup path deletes is dropped from the cache immediately (providers.google.GOOGLE_UPLOAD_REUSE_MIN_TTL_S, 60 seconds of remaining lifetime).

A run's cleanup deletes only the handles that run uploaded itself. A handle it reused belongs to whoever uploaded it and stays in the cache. Before this release a no-tools run that reused a handle an AgentSession had uploaded on an earlier turn deleted it on the way out, and the session's stored file_uri then failed on every later turn (403 / "File is not in an ACTIVE state"). The cache is also bounded: at most providers.google.GOOGLE_UPLOAD_CACHE_MAX_ENTRIES (256) entries, evicting the least recently used, so a long-lived process such as an MCP server or a worker no longer grows it for its whole life. There is no age-based sweep: a cache hit is re-validated with the Files API before it is reused, which is what catches a handle Google has already expired.

Run loop behavior

  • run(...) is async and is the canonical execution path.
  • run_sync(...) wraps the async path and handles nested event loop scenarios; MCP servers are connected and released inside the loop it creates.
  • Local MCP, client-side remote MCP, and local tools route to function-calling APIs.
  • Provider-native remote MCP-only setups are handled provider-natively via mapped params.
  • MCP sessions are opened once per event loop and reused across runs and session turns, so an Agent with local or client-side remote MCP tools serves many runs.

Typical run call:

result = await agent.run(
    "Summarize this document",
    files=["./sample.pdf"],
    output_schema=MySchema,
)

Result fields commonly used:

  • output
  • tools_used
  • built_in_tools_used — the built-in tools configured on the request (a config echo, not proof of use) / built_in_tools_invoked — the subset with observed evidence they actually ran. A provider-executed remote MCP call adds the neutral "remote_mcp" on every provider that has one (Anthropic also keeps the MCP tool's own name beside it), so "remote_mcp" in result.built_in_tools_invoked is one check everywhere
  • input_tokens / output_tokens / total_tokens (their sum)
  • reasoning_tokens / reasoning_content — the thinking share of output_tokens, and the thinking text or summary, on every provider and every run shape. reasoning_content is the canonical place to read reasoning; see Reasoning in a schema field
  • iteration_metrics — one entry per provider request, each with a purpose (see Cost Tracking)
  • cached_input_tokens / cache_write_tokens — prompt-cache accounting
  • request_cost_records — per-request cost breakdown for the run
  • tier_downgraded — some request in the run was not served at the tier it asked for; service_tier_served / priority_request_count give the run-level answer
  • prefix_binding_mismatches / model_binding_mismatches / prefix_binding_retry — replayed Anthropic thinking blocks the API dropped, and whether a binding 400 was recovered from; 0 / 0 / False everywhere else
  • distinct_urls — grounding URLs, now populated on the Google tool-calling path too; on OpenAI the URLs each web search visited as well as the ones the answer cites, so JSON and structured answers carry them too (details)
  • dropped_parts — every prompt part this run's model could not accept, with kind, index, reason, provider and model; populated under both on_unsupported_part policies. A restored session reports the same on session.dropped_parts for the history it loaded
  • media_uploads — every Google Files API upload this run made (part_index, uri, name, mime_type, size, sha256, uploaded_at, expiration_time); [] elsewhere
  • deferred_tools — the client tool names actually hidden behind the provider's tool search this run, in wire order; [] whenever deferral did not apply
  • cost / cost_estimated
  • tool_reported_cost / tool_reported_costs — spend the run's tools reported through the _dpt_spend marker, already in cost (see Cost Tracking)
  • retry_count / retry_cost / retry_input_tokens / retry_output_tokens — spend on failed-and-retried attempts, already included in the totals above
  • error

A stream the caller closes before its complete event returns no AgentResult. Its spend is kept on the Agent instead: agent.last_abandoned_stream holds that run's result dict with "abandoned": True, and is reset to None when the next stream starts (see Stopping a stream early).

Builder pattern

AgentBuilder provides a fluent interface for constructing agents step-by-step:

agent = (
    Agent.builder()
    .model("claude-sonnet-4-5")
    .instructions("You are helpful.")
    .add_tool(my_tool)
    .max_iterations(10)
    .build()
)

This is equivalent to passing all parameters directly to the Agent constructor but can be more readable for complex configurations.

New/advanced features reflected in code

  • Flexible tool orchestration across local tools, local MCP sessions, and remote MCP configs.
  • Session-aware execution with multi-turn memory and metadata accumulation.
  • Optional structured-output mode with schema-validated responses.
  • Optional detailed per-run telemetry for cost, tokens, iterations, and tool usage.
  • Provider-aware parameter mapping that keeps one high-level Agent API.