Multi-Turn Conversations with Tools
On this page
Multi-turn conversations with function calling
In a real conversation, the model may need to call tools multiple times throughout the exchange. Each turn can involve new calls, results that trigger other calls, or requests for clarification from the user before using a tool.
The max_turns parameter
The max_turns parameter limits the number of assistant/tool turns server-side in a single request. It applies only to built-in tools (web_search, code_interpreter, etc.).
response = client.responses.create(
model="grok-4.5",
input="Analyze 2026 tech trends",
tools=[{"type": "web_search"}],
max_turns=5 # Maximum 5 web searches before responding
)
Important point: client-side tool invocations reset the counter. When you send back a function_call_output, a new max_turns allocation begins.
Client-side multi-turn loop
To manage conversations where the model calls tools multiple times, implement a loop:
import json
def multi_turn_chat(user_message, tools, max_iterations=10):
messages = [{"role": "user", "content": user_message}]
for i in range(max_iterations):
response = client.chat.completions.create(
model="grok-4.5",
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
# If no tool_call, the conversation is over
if not message.tool_calls:
return message.content
# Execute all requested tools
for tool_call in message.tool_calls:
args = json.loads(tool_call.function.arguments)
result = execute_function(tool_call.function.name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
return "Maximum number of iterations reached"
The max_iterations variable is an essential safeguard to prevent infinite loops. In production, set a reasonable limit (5–10 turns).
Multi-turn context with the Responses API
The Responses API offers two mechanisms to maintain context:
1. Remote storage
# First message
r1 = client.responses.create(
model="grok-4.5",
input="What's the weather like in Paris?",
tools=tools,
store_messages=True
)
# Tool result
r2 = client.responses.create(
model="grok-4.5",
input=[{
"type": "function_call_output",
"call_id": r1.output[0].call_id,
"output": {temp: 18}
}],
previous_response_id=r1.id,
store_messages=True
)
# Follow-up question (context is preserved)
r3 = client.responses.create(
model="grok-4.5",
input="What about tomorrow?",
previous_response_id=r2.id,
tools=tools,
store_messages=True
)
The advantage: you don’t have to manage the message history. xAI stores and retrieves it automatically via previous_response_id.
2. Client-side encrypted content
response = client.responses.create(
model="grok-4.5",
input="Search for flight prices to Nice",
tools=tools,
use_encrypted_content=True
)
With use_encrypted_content=True, you receive reasoning and tool outputs in encrypted form. You send them back as-is in subsequent requests, without being able to read them. This is useful when you don’t want data to pass in clear text through your server.
Multi-turn conversation patterns
Pattern: Search then action
Turn 1 : User → "Find a cheap Paris-Nice flight"
Model → tool_call: search_flights(from="CDG", to="NCE")
Client → result : [{flight1}, {flight2}, {flight3}]
Turn 2 : Model → "I found 3 flights. The cheapest is €89. Do you want to book it?"
User → "Yes, book the €89 flight"
Turn 3 : Model → tool_call: book_flight(flight_id="FL123", passengers=1)
Pattern: Data aggregation
Turn 1 : Model → tool_call: get_sales(month="march")
Turn 2 : Model → tool_call: get_expenses(month="march")
Turn 3 : Model → tool_call: get_headcount(month="march")
Turn 4 : Model → "Here is the monthly report: sales +12%, expenses -3%, headcount stable."
Pattern: Progressive refinement
Turn 1 : Model → tool_call: search_products(query="running shoes")
Result → 150 products
Turn 2 : Model → "I found 150 products. What size and budget?"
User → "42, max €120"
Turn 3 : Model → tool_call: search_products(query="running shoes", size=42, max_price=120)
Result → 8 products
Memory management
In long conversations, history—which includes every tool_call and every tool result—grows quickly, and with it the cost of each turn. Three strategies are available to you, ranging from manual to fully delegated. Periodic summarization condenses older turns into a paragraph injected into the system prompt: you keep the substance of past exchanges for a fraction of their token weight, at the cost of some detail loss. The sliding window is more blunt but simpler: keep only the last N turns, accepting that the model will forget the beginning of the conversation—suitable when older exchanges no longer have operational importance. Remote storage, finally, delegates the problem to xAI with store_messages=True: the history lives server-side, and your application carries only a reference. The right choice depends on what your application needs to remember, and for how long.
Key takeaways
max_turnslimits server-side tool turns—client tools reset the counter- Implement a loop with
max_iterationsto prevent infinite loops previous_response_idchains conversations without resending historystore_messages=Truedelegates context storage to xAIuse_encrypted_content=Trueprotects data in transit- Manage memory in long conversations (summarization, sliding window)