CodeRigor

LESSON

A response can request a tool or finish

Parse a small explicit response protocol, distinguish a call request from a final answer, and retain call identity without executing anything.

A tool-using application needs to distinguish what the model proposes from what the application actually does. Start with a small response protocol that has two branches: a final text answer, or a request to call a named tool with arguments. The model does not execute the tool merely by mentioning its name.

Our exercises use ordinary JSON-shaped dictionaries so the mechanics are deterministic and independent of any provider. A final response contains kind and text. A call contains kind, id, name and arguments. These exact keys are a teaching contract, not a copy of an API’s message format. A real adapter must translate its provider’s documented format into the application’s chosen representation.

A proposal crosses several boundaries
start / 3
Parsing checks shape; dispatch and permissions still decide whether a call can execute.
A proposal crosses several boundaries
Parsing checks shape; dispatch and permissions still decide whether a call can execute.

Reject missing, extra and wrongly typed fields deliberately. A string that looks like JSON is not an argument object. A whitespace-only call ID cannot correlate useful results. A valid name is preserved exactly; the parser should not guess that a misspelled tool refers to some other function.

The parser returns an independent copy. JSON arguments may contain nested lists or dictionaries, so copying only the outer response leaves aliases behind. A later mutation should not rewrite the evidence of what arrived at the boundary.

Python
import copy
raw = {'kind': 'call', 'id': 'c1', 'name': 'read_note', 'arguments': {'ids': ['a']}}
accepted = copy.deepcopy(raw)
accepted['arguments']['ids'].append('b')
print(raw['arguments']['ids'])

Call IDs and document IDs serve different purposes. The call ID links a tool result to one request. A document ID identifies content that a tool may read. Neither is automatically a path or a permission. Cross-turn reuse of a call ID is checked by the later loop, not by this stateless parser.

Likewise, a well-formed final response is still just text. It may be wrong. Envelope validation establishes the shape of data that the next layer can inspect; it does not verify a natural-language answer or grant access to any resource.

Identity has two separate jobs
start / 3
A request ID correlates messages; an argument ID selects a note only after permission checks.
Identity has two separate jobs
A request ID correlates messages; an argument ID selects a note only after permission checks.
Python
call = {'id': 'c1', 'arguments': {'id': 'n1'}}
result = {'id': call['id'], 'ok': True, 'value': {'id': call['arguments']['id']}}
print(result['id'], result['value']['id'])

Build the parser and check both accepted branches, blank identities, a string argument field, extra keys and a nested-copy mutation. Keep these tests when a real model adapter is added. They pin an application-level interface that can remain stable even when provider message formats change.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Watch the tool-and-feedback discussion. Our exercises use a deterministic teaching protocol, not a vendor SDK.