Troubleshoot tool calls
Most tool-calling problems fall into a handful of patterns. Find the symptom below, check the likely causes in order, and apply the fix. When you change a definition or prompt, re-run the cases that failed and the rest of your test set.
The request is rejected
| Symptom | Likely cause | Fix |
|---|---|---|
upstream_error with a 4xx after you send tool results | A tool message has no matching tool_call_id, or the assistant message with tool_calls was changed or dropped | Send back the assistant turn unmodified, then one tool message per call with the same id. |
| Same error on the second round only | You sent tool messages without the preceding assistant message | The assistant message must come immediately before its tool results. |
413 too_many_messages or payload_too_large in long loops | History and tool results keep growing | Trim old results; see Tool results and context. |
| Error after you trimmed history | A tool call or its result was removed without the other | Remove call and result together, or neither. |
The model does not call the tool
| Likely cause | Fix |
|---|---|
| The description does not say when to use it | Add “Use this whenever the user asks about …” to the description. |
| The system message tells the model to answer directly | Say instead: “Use the tools to look things up before answering.” |
| Another tool looks like a better fit | Rename or describe the two so their purposes do not overlap. |
tool_choice is "none" | Use "auto", or name the function to force it. |
| The question can plausibly be answered from general knowledge | Tell the model it must not answer from memory for this kind of question. |
The model calls the wrong tool
- Check names and descriptions side by side: if a newcomer could confuse them, so can the model.
- Add “do not use this for …” sentences that point to the right neighbour.
- Reduce the tool list to what the current step needs.
- Add a test case for each confusion you see, and keep it in the set.
Arguments are wrong or malformed
| Symptom | Fix |
|---|---|
arguments is not valid JSON | Parse inside try; on failure return {"error": "arguments were not valid JSON"} as the tool result. The model usually corrects itself. |
| Wrong format (dates, ids, units) | Put the format and an example in the property’s description, and a pattern or format in the schema. |
| Invented values for required fields | Tell the model to ask the user for missing information instead of guessing, and validate in code. |
| Values outside the allowed set | Use enum in the schema and check it in code. |
Server-side schema enforcement is planned; see Strict tool arguments.
The model calls tools in a loop
- The result does not answer the question. Return clearer results, including “no results” explicitly:
{"results": [], "note": "nothing matched; try a broader query"}. - Errors are vague. Say what was wrong and what to try instead.
- No stopping rule. Add a round limit in your loop (for example 8 rounds) and tell the model in the system message how many lookups are reasonable.
- The same call repeats. Detect repeated identical calls in your loop and return “You already called this with the same arguments; use the earlier result.”
The final answer ignores the tool result
- Make results easy to read: field names that explain themselves, units included.
- Tell the model to base its answer on tool results: “Answer using the tool results; if they do not contain the answer, say so.”
- Check that you sent the result as a
toolmessage, not as ausermessage.
Parallel calls cause confusion
When the model asks for several tools at once, return one tool message per call. Order them in the same order as the calls, and include enough context in each result (for example the id that was looked up) so each one stands on its own. See Parallel tool calls.
Log every round of a tool loop with the request id (x-syntic-request-id), the tool calls and a truncated copy of each result. Most tool bugs are obvious once you can read the sequence.