A tool call by itself isn't an answer β the model needs the result handed back before it can actually respond.
1The Round-Trip Back to the Model Is Not Optional
Executing a tool call answers your server's question, not the user's. The model still has to be told what happened β by appending a role: "tool" message containing the result and calling the model again β before it can generate the sentence a user actually reads. Skip that step and the conversation just ends on an executed side effect with no answer.
2Why the tool_call_id Has to Match
When a model can call multiple tools in one turn, each tool_calls entry carries its own id. The role: "tool" message you send back must include that same tool_call_id, so the model can correctly associate each result with the specific call that produced it β exactly the same matching problem the request id solved back in Module 1's JSON-RPC lesson.
3Step-by-Step Breakdown
The Full Loop. Here's every piece from this module, assembled: send the user's message with your tools; if the model returns a tool_calls entry, parse and execute it locally; append the result as a new message with role "tool"; call the model again so it can see that result and produce a real final answer. Without that last round-trip, the model never learns what your tool actually returned.
Trigger a Real Stateful Tool Call. This time offer create_task instead of a read-only tool. Ask the model to create a task β watch it produce a tool_calls entry naming create_task with a title argument it wrote itself, based only on your instruction in plain English.
After your server executes create_task locally and gets a real result, why must that result be sent back to the model as a new message before the conversation can end with a real answer?
- βThe model has no automatic awareness of what happened outside itself β it only knows a tool's outcome if that outcome is explicitly sent back to it as new context in the conversation.
- βIt isn't actually required; the conversation can end immediately after any tool call executes.
A Real Loop, End to End. You've now seen the complete round-trip: a real model choosing a tool, your server executing it, and the model incorporating the real result into its final answer. What's missing is safety β right now nothing stops a model from asking your server to read a file it shouldn't. Next module: hardening DevAssist before it ships.
Level Up π
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Show Loading State Between the Tool Call and the Final Answer
The round-trip to execute a tool and call the model again takes real time β a chat UI should show a distinct 'running tool' state rather than leaving the screen static with no feedback.
<span>Running list_filesβ¦</span>SEO Implications
- 1
Target 'OpenAI function calling multi-turn example' and 'MCP tool result message format' separately
Developers wiring up this exact loop search for the full-conversation pattern and the specific tool-result message shape independently.
Best Practices
Cap the Number of Tool-Call Round-Trips Per Conversation Turn
Without a limit, a model that keeps deciding to call more tools can loop far longer (and cost far more) than intended β enforce a max number of tool rounds per user turn.
Frequent Bugs
Ending the conversation right after executing the tool, without calling the model again.
The user never receives an actual answer β only the raw tool result, or nothing at all, because the model was never given a chance to respond to it.
Real-World Examples
Multi-Step Assistant Turns
A single user request like 'check my open tasks and create one if I don't have any urgent ones' can require two full round-trips β list_tasks, then conditionally create_task β before the model gives its final answer.
while response.tool_calls: response = run_tool_round(response)