max_turns in aisuite: Automated Tool Execution vs. Manual Tool Calling Explained
In aisuite, max_turns limits how many automatic tool calls the Client will execute before stopping, while manual tool calling gives developers full control with no built-in recursion limit.
The aisuite library provides two distinct patterns for integrating LLMs with external tools. Understanding when to use automated execution versus manual control helps you build reliable agent workflows without unexpected loops or runaway API calls.
What max_turns Controls in Automated Tool Execution
When you use automated tool execution, the Client class manages the entire conversation loop. The max_turns parameter acts as a safety guard that caps how many times the client will automatically invoke tools before returning control to your code.
According to the aisuite source code, the recursion logic lives in aisuite/mcp/client.py. The client's run() method contains a loop that checks self.max_turns after each successful tool execution. If the model repeatedly requests tool calls, the loop exits once the limit is reached and returns the final response state.
This prevents infinite recursion when a model gets stuck in a tool-calling pattern or when tool results don't satisfy the model's completion criteria.
How Automated Tool Execution Works
Automated execution is the "fire-and-forget" approach. You provide a prompt and the client handles all tool interactions internally.
The workflow follows these steps:
client.run()sends your prompt to the LLM- If the response contains a tool call, the client executes the registered tool function
- The tool result is appended to the conversation context and sent back to the LLM
- Steps 2-3 repeat until the model stops requesting tools or
max_turnsis exhausted
When the limit is exceeded, the client raises a MaxTurnsExceededError (or returns a partial trace), allowing your code to decide how to proceed.
from aisuite.mcp import Client
# Allow up to 3 automatic tool calls
client = Client(max_turns=3)
response = client.run(
"Get the current weather for London and summarize it."
)
print(response.text) # Final LLM response
print(response.turns) # Actual tool calls used: 0-3
This pattern suits simple data lookups, single-step transformations, or any scenario where you trust the model to resolve its needs within a bounded number of interactions.
Manual Tool Calling: Full Developer Control
Manual tool calling removes the automatic loop entirely. You retrieve raw LLM output, inspect it for tool suggestions, and explicitly invoke run_tool() when and how you choose.
No max_turns parameter applies because you write the control flow yourself. This gives you:
- Fine-grained conditional logic between calls
- Custom error handling and retry policies
- Ability to transform or validate tool arguments before execution
- Explicit budget control over API usage
from aisuite.mcp import Client
client = Client() # No max_turns needed for manual mode
# Get raw LLM output without automatic tool execution
raw = client.run_raw("What is the population of Tokyo?")
print(raw) # Inspect: model may suggest a "search" tool
# Manually execute the suggested tool
if "search" in raw.suggested_tools:
result = client.run_tool("search", query="Tokyo population 2024")
print(result)
# Decide whether to continue, modify, or stop
follow_up = client.run_raw(f"Based on this data: {result}, what trends do you see?")
The loop implementation—whether single-shot, fixed-iteration, or dynamically conditioned—is entirely your responsibility.
Key Implementation Differences
| Aspect | Automated (max_turns) |
Manual |
|---|---|---|
| Entry point | client.run() |
client.run_raw() + client.run_tool() |
| Recursion control | max_turns parameter enforces hard limit |
Developer implements all looping logic |
| Error on limit | MaxTurnsExceededError raised |
Not applicable |
| Typical use case | Quick, trusted workflows | Complex multi-step reasoning, custom orchestration |
| Code complexity | Minimal client code | Requires explicit state management |
Where to Find the Implementation
The core differentiation logic resides in these source files:
aisuite/mcp/client.py— Contains theClientclass, themax_turnsattribute, and the automated execution loop inrun()aisuite/mcp/schema_converter.py— Defines message schemas that the client parses to detect tool call requestsaisuite/utils/tools.py— Registry of available tool functions for invocation
Test coverage illustrating both patterns:
tests/client/test_manual_tool_calling.py— Demonstrates explicit tool calls without automatic recursiontests/client/test_async_client.py— Showsmax_turnsbehavior in async client configurations
Choosing Between the Two Patterns
Use automated tool execution with max_turns when you want minimal boilerplate and can tolerate bounded automatic behavior. Set max_turns conservatively—usually 3-5 turns suffice for simple tasks.
Use manual tool calling when you need visibility into intermediate states, must inject business logic between calls, or want to implement sophisticated retry and fallback strategies. The extra code investment pays off in deterministic, auditable workflows.
Summary
max_turnscaps automatic tool recursion inClient.run(), preventing runaway loops- Automated execution handles tool detection, invocation, and context management internally
- Manual calling via
run_raw()andrun_tool()gives complete control but requires explicit loop implementation - Both patterns share the same underlying tool registry in
aisuite/utils/tools.py - Error handling differs: automated mode raises
MaxTurnsExceededError; manual mode delegates all error handling to your code
Frequently Asked Questions
What happens when max_turns is reached during automated execution?
The client stops the automatic loop and raises a MaxTurnsExceededError (or returns a partial response trace depending on configuration). Your calling code can catch this exception and decide whether to extend the conversation, return a degraded response, or escalate to human review.
Can I mix automated and manual calling in the same application?
Yes. Instantiate separate Client instances for each pattern, or use a single client and switch between run() for automated passages and run_raw()/run_tool() for sections requiring manual control. The client state is preserved across calls, allowing hybrid workflows.
Does max_turns count LLM response turns or tool execution turns?
Tool execution turns. Each time the client invokes a registered tool and feeds the result back to the model counts as one turn toward the max_turns limit. Pure text responses from the LLM without tool requests do not increment the counter.
Is there a default max_turns value if I don't specify one?
The aisuite source code configures a default—typically conservative to prevent unexpected costs. Check the Client constructor in aisuite/mcp/client.py for the current release's specific default, or explicitly set max_turns to control behavior regardless of version changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →