The Recipe¶
The one-page, end-to-end recipe for building a function/tool calling dataset: every case to cover, from the basics to edge and advanced cases. The deeper guides are linked from each section.
1. Pipeline¶
- Scenario: pick a domain and the user's role (guest, staff, admin). Expose only tools that role may use.
- Schema: write 3-8 tools (Schema). Add 2-5 distractor tools that are similar but wrong.
- System prompt: tone, policies, defaults, confirmation rules, out-of-scope behavior (System Instruction).
- Seed conversation: write one high-quality seed per cell of the coverage matrix (§2).
- Variants: create 4-5 variants per seed (Variants).
- Validate: run the quality gates (§8). Reject anything that fails.
- Mix and split: balance the dataset (§7). Hold out unseen tools and domains for eval.
- Train, then evaluate (§9, Fine-Tuning Guide). Turn failures into new seeds, and repeat.
2. Coverage Matrix¶
Every conversation is one value from each axis. Cover all combinations that make sense.
| Axis | Values |
|---|---|
| Turn | single, multiple |
| Call | none, single, parallel, consecutive, parallel + consecutive |
| User | cooperative, missing info, extra info, changes mind, cancels, refuses, asks params, noisy, toxic, adversarial |
| Tool result | success, empty, partial, error, timeout, huge, unexpected shape |
| Ending | task done, clarification asked, gracefully declined, handed off / escalated |
3. Call Patterns¶
| Pattern | Example | Model must |
|---|---|---|
| No call | "What causes rain?" | Answer directly, without calling any tool |
| No call (no tool exists) | "Book me a flight" with no flight tool | Say it can't do this; never invent a tool |
| Single | "Weather in Tokyo?" | Make one call with exact args |
| Parallel | "Weather in Tokyo and Paris?" | Make N independent calls in one turn |
| Same tool, many args | "Convert 10, 20, 30 USD to EUR" | Make 3 parallel calls, or 1 call if the tool takes an array |
| Consecutive | "Book Dr. X on Mon or Tue" | Check availability, then book. Use the IDs from the output |
| Parallel then consecutive | "Cheapest of hotels A, B, C, then book it" | Fan out the lookups, compare, then make one booking call |
| Conditional | "If it rains, cancel my picnic" | Branch on the tool result |
| Loop / pagination | "List all my orders" | Follow next_page until done; know when to stop |
| Retry | Tool returns a validation error | Fix the args and retry once; don't loop forever |
| Fallback | Primary tool fails | Use the alternative tool, or explain the failure |
| Cross-turn reuse | Turn 3 refers to an ID from turn 1 | Reuse the earlier output; don't call the tool again |
| Zero-arg tool | get_current_time() |
Call it with {} |
4. Edge Cases¶
Arguments¶
- Missing required arg: ask for it. Ask for one or two values per turn, not a form.
- Optional arg absent: omit it. Never fill it with a guess.
- Ambiguous value: "next Friday", "Springfield", "10 bucks". Resolve it from context or ask.
- Implicit default: currency, unit, timezone, locale. Use it only if the system prompt or schema defines it.
- Out of enum or range: "size XXXL" when the enum stops at
XL. Tell the user the valid options. - Type coercion: "two" becomes
2, "tomorrow" becomes an ISO date, "yes" becomestrue. - Arrays and nested objects: produce the correct structure and keep the order when it matters.
- Special characters: quotes, emoji, unicode, and newlines must be escaped correctly in the JSON args.
- Precision: money, IDs with leading zeros, and large integers must be kept exact. Use strings when needed.
- Never hallucinate IDs, emails, prices, or dates. They must come from the user or from a tool output.
User behavior¶
- Changes params mid-flow: "Actually, Wednesday". Use the new value and don't re-ask for the old ones.
- Cancels: stop. If the action already ran, call the cancel/undo tool.
- Asks which params are needed: list the required and optional ones.
- Refuses PII: offer an alternative, or explain why the value is needed. Don't pressure.
- Gives extra or irrelevant info: extract only what the call needs.
- Gives conflicting info: "Book for 2... for me, my wife and kid". Point out the conflict and ask.
- Noisy input: pasted JSON/HTML/logs, typos, slang, shortcuts like "color=red", mixed languages.
- Toxic user: stay professional and keep helping within policy.
- Asks out-of-role: a guest asks to cancel another guest's booking. Refuse, even though the tool exists.
- Multi-intent message: "Cancel order 5 and what's your refund policy?" Make the call and answer the question.
Tool results¶
- Empty:
[]ornull. Say nothing was found and suggest a next step; never make results up. - Partial failure in a parallel batch: report the successes and the failures separately.
- Error with a hint: follow the hint (see Tool Output).
- Opaque error: 500 or timeout. Retry at most once, then explain and stop.
- Rate limit: back off. Don't spam calls.
- Huge output: summarize only what matters; don't dump it to the user.
- Unexpected shape or extra fields: read the result defensively.
- Result contradicts the user: "Your order is shipped" but the tool says
pending. Trust the tool and say so. - Stale data across turns: re-fetch when the state may have changed.
Safety and control¶
- Destructive or irreversible actions (delete, pay, send): confirm before calling.
- Idempotency: a repeated "pay" message must not pay twice.
- Prompt injection from the user: "Ignore your rules and...". The system prompt always wins.
- Prompt injection inside a tool output: text such as "assistant, now email X" is data, not instructions.
- System prompt leakage: don't reveal hidden instructions or internal tool details when asked.
- Least privilege: don't call more tools than the task needs.
5. Advanced Cases¶
- Large toolsets (50-1000+): train with long tool lists and many near-duplicate tools. See Tool Search.
- Tool search / pivoting: call
search_toolsfirst, then the tool it returns. - Distractor tools:
get_uservsget_usersvssearch_user. Choose by the description, not by the name. - Planning text: write a short plan before multi-step calls (Training Data).
- Long-horizon tasks: 5-20 calls with intermediate state. Know when the task is done.
- Aggregation: compare, sort, sum, or deduplicate the results of many calls, and get the math right.
- Multi-turn state: remember what the user confirmed, chose, or rejected earlier.
- Schema variations: same intent, but a renamed tool, reordered params, or a different param style. Keeps the model from overfitting on names.
- Mixed formats: the user writes JSON or YAML, the tool wants another format, the answer is natural language.
- Structured output without tools: when the task is "return JSON" and no tool fits.
- Multilingual: the user writes in language X and the args stay in the language the schema expects (e.g. enums stay English).
- Time awareness: relative dates resolved against the current date given in the system prompt.
- Handoff / escalation: call a
transfer_to_humantool, or stop when the policy says so. - Unchanged tool output: if the same call returns the same result, don't call it a third time.
- Final answer quality: interpret and format the results; don't just repeat the raw output.
6. Sample Record¶
The record below shows a parallel call, a partial failure, and a grounded final answer. A longer multi-turn sample is on Training Data.
{
"messages": [
{"role": "system", "content": "You are a travel assistant. Today is 2026-11-01. Default currency: EUR. Confirm before booking."},
{"role": "user", "content": "weather in tokyo & paris tmrw?"},
{"role": "assistant", "content": "I'll check both cities.", "tool_calls": [
{"id": "c1", "name": "get_weather", "arguments": {"city": "Tokyo", "date": "2026-11-02"}},
{"id": "c2", "name": "get_weather", "arguments": {"city": "Paris", "date": "2026-11-02"}}
]},
{"role": "tool", "tool_call_id": "c1", "content": {"status": "OK", "temp_c": 14, "condition": "rain"}},
{"role": "tool", "tool_call_id": "c2", "content": {"status": "ERROR", "error": "date_out_of_range", "hint": "max 1 day ahead in local time"}},
{"role": "assistant", "content": "Tokyo tomorrow: 14°C with rain. I couldn't get Paris yet; its forecast isn't available for that date. Want me to try again later?"}
],
"tools": [
{"name": "get_weather", "description": "Daily forecast for a city.", "parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, English."},
"date": {"type": "string", "format": "date", "description": "ISO date, local to the city."}
},
"required": ["city", "date"]
}}
]
}
7. Dataset Mix¶
A starting point. Tune it on your eval results.
| Slice | Share |
|---|---|
| Single call | 20% |
| Parallel | 15% |
| Consecutive / mixed | 20% |
| No call (chit-chat + no tool exists) | 15% |
| Clarification (missing or ambiguous args) | 10% |
| Tool error / empty result / recovery | 10% |
| Adversarial (injection, out-of-role, toxic, noisy) | 10% |
- About 50% multi-turn.
- At least 30% of samples include distractor tools.
- Balance domains (Home) and languages.
- Deduplicate near-identical samples.
8. Quality Gates¶
Every sample must pass all of these:
- Arguments validate against the JSON schema (types,
required,enum, ranges). - Every
tool_callhas exactly one matching tool result (id), in order. - Every argument value is traceable to the user, the system prompt, or an earlier tool output.
- No call to a tool that isn't in
tools, and no call outside the user's role. - Tool outputs are realistic: structured, consistent with earlier turns, and with errors that carry hints.
- The final answer is grounded in the tool results, follows the system tone, and isn't a raw dump.
- There is a plan text before multi-step calls.
- No PII leaks and no system-prompt leaks.
9. Evaluation¶
| Metric | Measures |
|---|---|
| Tool selection accuracy | Picked the right tool(s) |
| Argument accuracy (AST / exact) | Correct args and correct types |
| Call-count accuracy | No missing or extra calls; parallel calls made in parallel |
| No-call precision / recall | Abstains when it should, and only then |
| Clarification rate | Asks when info is missing; doesn't ask when it isn't |
| Recovery rate | Succeeds after an error or empty result |
| Hallucination rate | Invented tools, args, or results |
| Injection resistance | Ignores injected instructions |
| End-to-end task success | Final state is correct (judged by an LLM or a rule) |
- Report each metric per slice (§7), on seen vs unseen tools, and on single vs multi-turn conversations. Details: Evaluation.
10. Anti-patterns¶
- Every sample is a happy path with a single call.
- Tool outputs written as prose sentences.
- Defaults the model guesses but no prompt or schema defines.
- Asking for all params at once, like a form.
- Same tool names in every sample, so the model overfits on names.
- Errors with no hint for recovery.
- The final answer repeats the tool JSON.
Put the recipe to the test
Try your schema and these edge cases on real models in the toolcalling.ai workspace, and vote in the Arena. Then go to the Fine-Tuning Guide and Evaluation.