Agent Tool Use and Skill Libraries
Define, validate, dispatch and secure tool calls. Store skills as code, retrieve, compose and refine them.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
The function calling loop#
Tool schema#
{"type": "function", "function": { "name": "get_weather", "description": "Get current weather for a city.",a prompt for tool selection "parameters": {"type": "object",Anthropic: input_schema "properties": {"city": {"type": "string"}, "units": {"enum": ["celsius", "fahrenheit"]}}, "required": ["city"]}}}missing field: error observationProviders side by side#
| Factor | OpenAI | Anthropic | |
|---|---|---|---|
| Tools parameter | tools | tools | function_declarations |
| Call format | tool_calls[].function | content[].type= | functionCall |
| Schema field | function.parameters | input_schema | JSON Schema |
| Forced call | tool_choice= | tool_choice= | function_calling_config |
| Result goes back as | {"role": "tool", | type: "tool_result" in a user message | Not in the lesson table |
Tasks in code#
| Task | Code |
|---|---|
| Register a tool | reg.register(ToolDef(...)) |
| Show the catalog to the model | reg.catalog() |
| Run calls of one turn | reg.dispatch_many(calls) |
| Check args against a schema | validate(args, |
| Run the loop with a cap | run_function_calling_loop(user_message, |
| Find skills for a task | lib.search("I need a pickaxe") |
| Run a skill with its deps | lib.execute("craft_iron_pickaxe") |
| Run the demo | python3 code/ |
Registry with structured errors#
reg = ToolRegistry()reg.register(ToolDef(name="classify", description="<when to use>", input_schema={"type": "object", "required": ["status"], "properties": {"status": {"type": "string", "enum": ["open", "closed", "pending"]}}}, executor=classify))timeout_s defaults to 5.0calls = [ToolCall("u02", "multiply", {"a": "4", "b": 5}),"4" coerces ToolCall("u03", "classify", {"status": "in_progress"})]not in enumfor result in reg.dispatch_many(calls): result.ok, result.contenterrors are strings, never raisedSkill library loop#
lib = SkillLibrary()lib.register(Skill(name="craft_iron_pickaxe", description="<what it does>", code="mine_ore(); place_table(); craft('iron_pickaxe')", fn=_craft_iron_pick_v1, tags=("craft", "tool"), depends_on=("mine_ore", "place_crafting_table")))v1lib.search("I need a pickaxe")token overlap, top_k=3ctx = lib.execute("craft_iron_pickaxe")deps first, topo_orderctx["failed"]True: need 2 stick, have 0lib.register(Skill(..., fn=_craft_iron_pick_v2))same name: v2Numbers that matter#
- BFCL V4
- 40% agentic, 30% multi-turn, 10% live, 10% non-live, 10% hallucination.
- Parallel calls
- cut round trips from 2 to 1.
- Parallel latency
- 60-80% less for 5-10 tool calls per query.
- Call budget
- 10-20 calls per conversation is reasonable.
- Voyager
- 3.3x more unique items, 8.5x faster stone tools, 6.4x faster iron tools.
- Toolformer
- keep a tool call only when its result reduces next-token loss.
Voyager refinement loop#
Validation failures and fixes#
| Symptom | Cause | Fix |
|---|---|---|
error: unknown tool 'subtract' | Model calls a tool that does not exist | Return the error. Add a no-op tool to refuse |
missing required: <name> | Required field absent | Send the error back. The model retries |
'in_progress' not in [...] | Value outside the enum | Reject with a descriptive error |
| Wrong tool picked | Bad tool description | Say what the tool does and when to use it |
| Results routed to wrong calls | Swapped tool_use_id values | Correlate each result by its ID |
| Retrieval gets worse | Library past a few hundred skills | Add tag filters and hard constraints |
Defaults in the lesson code#
| Setting | Default | Note |
|---|---|---|
timeout_s | 5.0 | Per tool in ToolDef |
version | 1 | New Skill |
dedup | True | Same name refines the skill |
top_k | 3 | Skill search results |
tag_filter | None | Set it to limit search to one tag |
max_iterations | 5 | run_function_calling_loop |
Security rules#
- Never pass model-generated SQL directly to a database. Parameterize it.
- Allowlist functions. Expose only the tools the user needs.
- Validate every argument for type, range and format.
- Sanitize tool results. Remove API keys, PII and internal errors.
run_codeparses the code withast.parseand blocks imports and dunder access.- Run untrusted code in a separate process or container.
Questions#
- Who executes the tool?
- Your code. The model only decides what to call.
- Structured outputs or function calling?
- Structured outputs for data extraction. Function calling to act on external systems.
- Why return errors as results?
- Models self-correct from structured errors. They fail on empty or generic errors.
- What is a skill?
- A named chunk of code plus a description, retrievable by similarity.
- Why version skills?
- A parent pinned to v1 does not pick up v3 of a child.