Errors¶
Symptoms and fixes: Troubleshooting.
Every error the adapter raises subclasses AdapterError. Catch the narrow type where
you can act on it. The five most caught (AdapterError, ToolError, ApprovalDenied,
ApprovalPending, MissingDependency) come from agent_adapter. Every other error, and
ErrorInfo, comes from agent_adapter.errors.
flowchart LR
AE["AdapterError"] --> DEF["definition<br/>ToolDefinitionError · DuplicateTool<br/>SchemaNotPortable · ConfigError"]
AE --> CALL["call<br/>ToolNotFound · ToolArgsInvalid · ToolUsageError<br/>ToolError · ToolTimeout · ToolFailed<br/>RunLimitExceeded · GuardrailBlocked"]
AE --> HITL["approval and budget<br/>ApprovalDenied · ApprovalPending<br/>ApprovalNotFound · ApprovalAlreadyDecided<br/>ApprovalStoreCorrupt · BudgetExceeded"]
AE --> FLOW["flows and plans<br/>PlanInvalid · FlowError<br/>FlowDefinitionError · FlowCompileError<br/>FlowLoopExceeded"]
AE --> MOD["models<br/>ModelError and 7 subclasses"]
AE --> OTHER["other<br/>MissingDependency · IntegrationError<br/>AuthError · Unauthorized · Forbidden<br/>OptimiserError"]
kind and retryable¶
Each class has two class attributes. kind is a stable kebab-case id used in
ErrorInfo, HTTP problem types and events. retryable tells the executor and
FallbackModel that another attempt may succeed. Errors survive pickling.
| Error | kind |
Retryable | Raised when |
|---|---|---|---|
AdapterError |
internal-error |
base class | |
ToolDefinitionError |
tool-definition-invalid |
the callable or schema cannot become a tool (startup) | |
DuplicateTool |
duplicate-tool |
the name is already registered (startup) | |
SchemaNotPortable |
schema-not-portable |
a provider would reject the schema; .problems, .tool |
|
ToolNotFound |
tool-not-found |
unknown name in get, remove or subset(names=) |
|
ToolArgsInvalid |
tool-args-invalid |
the arguments fail validation; .tool, .errors |
|
ToolUsageError (also TypeError) |
tool-usage-error |
host code misused an API | |
MissingDependency (also ImportError) |
missing-dependency |
an optional extra is missing | |
ToolError |
tool-error |
raised by tool code; the message is shown to the model; .details |
|
ToolTimeout (also TimeoutError) |
tool-timeout |
yes | a call ran past its timeout |
ToolFailed |
tool-failed |
a tool raised a foreign exception; .cause_type |
|
RunLimitExceeded |
run-limit-exceeded |
once_per_run or max_calls refused the call |
|
GuardrailBlocked |
guardrail-blocked |
a guardrail rejected args, a result or an LLM payload | |
ApprovalDenied / ApprovalPending |
approval-denied / approval-pending |
a reviewer said no / will decide later | |
ApprovalNotFound / ApprovalAlreadyDecided |
approval-not-found / approval-already-decided |
approval store lookups | |
ApprovalStoreCorrupt |
internal-error |
a durable approval store read a corrupt or tampered row (fails closed) | |
BudgetExceeded |
budget-exceeded |
spend reached the limit | |
PlanInvalid |
plan-invalid |
a step plan has problems; .problems |
|
FlowError and subclasses |
flow-error, flow-definition-invalid, flow-compile-failed, flow-loop-exceeded |
flow runs and definitions | |
ModelError |
model-error |
a provider call failed; .provider, .model, .status_code, .retry_after |
|
ModelRateLimited, ModelUnavailable, ModelTimeout |
model-rate-limited, model-unavailable, model-timeout |
yes | 429, 5xx / overload / connection, timeout |
ModelAuthError, ModelBadRequest, ModelContentFiltered, ModelOutputInvalid |
model-auth-failed, model-bad-request, model-content-filtered, model-output-invalid |
credentials, bad request, safety filter, schema mismatch | |
ModelContextOverflow (a ModelBadRequest) |
model-context-overflow |
too many tokens for the model; see Context compression | |
ConfigError (also ValueError) |
config-invalid |
invalid configuration | |
IntegrationError |
integration-error |
a framework integration failed or is unknown | |
AuthError, Unauthorized, Forbidden |
auth-error, unauthorized, forbidden |
HTTP server authentication | |
OptimiserError |
optimiser-error |
the optimiser could not complete |
Retry rule. An exception is retried when it is not control flow and either is not an
AdapterError or its class has retryable = True.
Who raises what¶
| Errors | Raised by |
|---|---|
ToolTimeout, ToolFailed, RunLimitExceeded, ApprovalDenied, ApprovalPending, PlanInvalid |
executor |
BudgetExceeded, GuardrailBlocked, approval store errors |
built-in hooks, approval stores |
ModelError family |
model ports |
FlowError family |
flow runner and the LangGraph / ADK compilers |
AuthError family |
HTTP server |
Send argument errors back to the model¶
ToolArgsInvalid.errors is a list of JSON-safe dicts with type, loc, msg and,
when present, a stringified ctx. Input values are never kept, so it is safe to log and
safe to return to the model so it can retry.
try:
await Executor(registry).run("add", {"a": 1, "b": "two", "c": 3})
except ToolArgsInvalid as exc:
# JSON-safe and free of input values: safe to log or return to the model.
reply = {"tool": exc.tool, "errors": exc.errors}
print(json.dumps(reply))
{"tool": "add", "errors": [
{"type": "int_parsing", "loc": ["b"], "msg": "Input should be a valid integer, unable to parse string as an integer"},
{"type": "extra_forbidden", "loc": ["c"], "msg": "Extra inputs are not permitted"}
]}
ErrorInfo.from_exception(exc) gives the same safety for any error; see
Context.
Fail at startup, not mid-request¶
try:
registry.wrap(tag_counts) # dict[str, int] is an open map
except SchemaNotPortable as exc:
print(exc.tool, exc.problems)
try:
registry.wrap(no_docstring) # no description anywhere
except ToolDefinitionError as exc:
print(exc)
tag_counts ['$.counts: open map (dict) is not portable']
tool 'no_docstring' needs a description or a docstring
Catch by category¶
try:
registry.get("subtract")
except ToolNotFound as exc:
print(exc)
except AdapterError: # every adapter error shares this base
raise
ToolUsageError is also a TypeError, MissingDependency an ImportError,
ToolTimeout a TimeoutError and ConfigError a ValueError, so existing handlers for
those keep working.
Logging¶
The adapter logs with the standard logging module, under agent_adapter.* loggers.
It never configures handlers; your app does.
Failures are logged with the exception type at WARNING / ERROR; exception text,
guardrail reasons and tracebacks only at DEBUG. A test scans the source for this rule. To see why a plugin failed to load, enable DEBUG on
agent_adapter.integrations.discovery.
| Logger | Level | When |
|---|---|---|
agent_adapter.core.registry |
DEBUG |
a tool is registered or removed |
agent_adapter.core.tool |
WARNING |
pydantic cannot describe a return type, so result_schema() is None |
agent_adapter.core.imported |
DEBUG |
an imported schema is not portable |
agent_adapter.core.context, agent_adapter.tracing.events |
WARNING |
an event sink raised |
agent_adapter.hooks.dispatch |
ERROR |
a hook raised (it is re-raised) |
agent_adapter.integrations.discovery |
WARNING |
a plugin entry point failed to load (skipped) |
agent_adapter.cache.tool_cache |
WARNING |
a cache backend failed (treated as a miss) |
agent_adapter.runtime.loop_local |
WARNING |
closing a loop-local value failed |
This page's code is examples/errors.py. The test suite runs it.