The problem
A model asks for a tool by writing a tool call: a JSON (JavaScript Object Notation) object between tags, in the middle of its text.
I will look it up.
<tool_call>
{"name": "search",
"arguments": {"query": "OLMo 3"}}
</tool_call>
The program around the model must not trust what it finds there: the JSON can be broken, the tool can be made up, the arguments can be wrong. You will write the function that finds the calls and checks them, so that only valid calls are run and every problem becomes an error message.
The registry and the checks
registry describes the available tools: {name: {"required": [...], "optional": [...]}}.
Find every <tool_call>...</tool_call> block, in order, and check each, in this order:
- The content must be valid JSON, and an object with a string
"name"and an object"arguments": otherwise the error is"bad json: <i>", where<i>is the index of the block, from 0. - The tool must exist in
registry: otherwise"unknown tool: <name>". - Every required argument must be present: otherwise
"missing argument: <name>.<arg>"for the first missing one (in registry order). - Every argument must be required or optional: otherwise
"unexpected argument: <name>.<arg>"for the first unexpected one (in the call's order).
A valid block becomes a (name, arguments) pair in calls. An invalid block adds one error string to errors and is skipped. Return (calls, errors).
Example.
registry = {"search": {"required": ["query"], "optional": ["limit"]}}and a text with five blocks:
block content result 0 searchwithquery"OLMo 3"valid: ("search", {"query": "OLMo 3"})1 weatherwith no arguments"unknown tool: weather"2 {"name": "search", "arguments": {"query": "x"(cut off)"bad json: 2"3 searchwith no arguments"missing argument: search.query"4 searchwithqueryanddepth"unexpected argument: search.depth"The result is
calls = [("search", {"query": "OLMo 3"})]and the four errors, in block order.
What to implement
parse_tool_calls(text, registry): return (calls, errors).
Requirements
- Standard library only (
re,json). Use a non-greedy pattern withre.DOTALL: a block can span lines.
Hint
re.findall(r"<tool_call>(.*?)</tool_call>", text, re.DOTALL) returns the contents of all blocks. The ? makes .* stop at the first closing tag, and re.DOTALL lets . match newlines.