User hỏi: "What day is 103 days from today?" Claude cần:
- Hiểu pattern multi-turn tool: Claude có thể call nhiều tool liên tiếp
- Thấy vì sao single-turn không đủ cho task phức tạp
- Plan refactoring helper functions cho multi-turn
- Sketch agent conversation loop
Mở đầu: 1 tool không giải quyết được mọi thứ
- Biết hôm nay là ngày nào → gọi
get_current_datetime - Cộng 103 ngày → gọi
add_duration_to_datetime - Format & trả lời user
Đây là sequential tool calls — output tool 1 là input tool 2.
Single-turn (1 tool, 1 response) không đủ. Cần loop.
Multi-turn flow
User: "What day is 103 days from today?"
│
▼
Claude (Turn 1): "I need to know current date first"
+ ToolUse(get_current_datetime)
│
▼
You run: get_current_datetime() → "2026-04-20"
│
▼
Send tool_result to Claude
│
▼
Claude (Turn 2): "Now I add 103 days"
+ ToolUse(add_duration_to_datetime,
input={"datetime_str": "2026-04-20", "duration": 103, "unit": "days"})
│
▼
You run: add_duration(...) → "2026-08-01"
│
▼
Send tool_result
│
▼
Claude (Turn 3): "103 days from today is August 1, 2026 (Saturday)."
→ stop_reason: "end_turn"
│
▼
Done.
3 turns, 2 tool calls. Claude tự orchestrate sequence.
Conversation loop pattern
def run_conversation(initial_message: str, tools: list) -> str:
"""Loop until Claude done with tools."""
messages = []
add_user_message(messages, initial_message)
while True:
# Ask Claude
response = chat(messages, tools=tools)
add_assistant_message(messages, response)
# Check stop reason
if response.stop_reason == "end_turn":
# Claude done, return final text
return text_from_message(response)
if response.stop_reason == "tool_use":
# Execute tools, send results back
tool_calls = [b for b in response.content if b.type == "tool_use"]
tool_results = run_tools(tool_calls)
add_user_message(messages, tool_results)
continue # Loop again
if response.stop_reason == "max_tokens":
raise ValueError("Response truncated, increase max_tokens")
raise ValueError(f"Unexpected stop_reason: {response.stop_reason}")
Loop continues cho đến khi Claude end_turn (không cần tool nữa).
Helpers cần refactor
Before (single-turn)
def chat(messages):
msg = client.messages.create(
model=model,
max_tokens=1000,
messages=messages
)
return msg.content[0].text # ← just text
After (multi-turn ready)
def chat(messages, system=None, tools=None, temperature=1.0, stop_sequences=None):
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
}
if system:
params["system"] = system
if tools:
params["tools"] = tools
if stop_sequences:
params["stop_sequences"] = stop_sequences
return client.messages.create(**params) # ← full Message
def text_from_message(message):
"""Extract combined text from multi-block message."""
return "\n".join(
b.text for b in message.content if b.type == "text"
)
Return full Message để caller check stop_reason, inspect blocks.
Ví dụ đầy đủ
tools_schemas = [
get_current_datetime_schema,
add_duration_to_datetime_schema,
]
TOOL_REGISTRY = {
"get_current_datetime": get_current_datetime,
"add_duration_to_datetime": add_duration_to_datetime,
}
def run_tools(tool_uses):
results = []
for tu in tool_uses:
try:
output = TOOL_REGISTRY[tu.name](**tu.input)
results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": str(output),
"is_error": False
})
except Exception as e:
results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": str(e),
"is_error": True
})
return results
def run_conversation(user_msg, tools):
messages = []
add_user_message(messages, user_msg)
max_turns = 10 # safety limit
for turn in range(max_turns):
response = chat(messages, tools=tools)
add_assistant_message(messages, response)
print(f"\n--- Turn {turn + 1} ---")
print(f"Stop: {response.stop_reason}")
if response.stop_reason == "end_turn":
return text_from_message(response)
if response.stop_reason == "tool_use":
tool_uses = [b for b in response.content if b.type == "tool_use"]
for tu in tool_uses:
print(f"Calling: {tu.name}({tu.input})")
results = run_tools(tool_uses)
add_user_message(messages, results)
for r in results:
print(f"Result: {r['content']}")
continue
raise ValueError(f"Unexpected: {response.stop_reason}")
raise RuntimeError(f"Exceeded {max_turns} turns")
final = run_conversation(
"What day is 103 days from today? Include day of week.",
tools_schemas
)
print(f"\n=== Final ===\n{final}")
Output dự kiến
--- Turn 1 ---
Stop: tool_use
Calling: get_current_datetime({'date_format': '%Y-%m-%d'})
Result: 2026-04-20
--- Turn 2 ---
Stop: tool_use
Calling: add_duration_to_datetime({'datetime_str': '2026-04-20T00:00:00', 'duration': 103, 'unit': 'days'})
Result: 2026-08-01T00:00:00
--- Turn 3 ---
Stop: end_turn
=== Final ===
103 days from today (2026-04-20) is 2026-08-01, which falls on a Saturday.
Safety: max_turns
Always có limit. Nếu Claude stuck in loop (bug) → app crash controlled, không burn tokens.
MAX_TURNS = 10 # adjust based on expected task complexity
for turn in range(MAX_TURNS):
...
raise RuntimeError(f"Agent exceeded {MAX_TURNS} turns — possible loop")
Typical budget:
- Simple task: 2-5 turns
- Medium (research, multi-step): 5-15
- Complex (agentic coding, multi-tool): 20-50
Nếu vượt thường do:
- Tool fail, Claude retry vô hạn
- Circular reasoning
- Task quá phức tạp — cần break down
Agent = multi-turn tool use
"Agent" đơn giản là multi-turn tool use với nhiều tools.
Claude Code:
- Tools: Read, Edit, Bash, Grep, WebFetch, ...
- Multi-turn loop: plan → read → analyze → edit → test → commit
- Max turns: rất cao (>100 cho complex task)
Computer Use:
- Tools: screenshot, click, type
- Multi-turn loop: see → plan → act → see → plan → act
- Each "act" là 1 tool call
Bạn xây agent = viết tools + conversation loop. Đó là cốt lõi.
Chi tiết architecture agent ở Module 9.
Optimization: Context trimming
Conversation dài → token cost scale. Sau N turn, trim:
def trim_conversation(messages, max_history=20):
"""Keep last N messages + initial user msg."""
if len(messages) <= max_history:
return messages
return [messages[0]] + messages[-(max_history - 1):]
Hoặc summarize old turns (ở Module 6 prompt caching có cách tốt hơn).
Anti-patterns
Không có max_turns
Infinite loop → burn $100 mà không xong task.
Fix: Always have safety limit.
Quên pass tools ở mọi call
Turn 2 không pass tools → Claude không thấy schema → confused.
Fix: tools= parameter ở mọi chat() trong loop.
Xử lý tool result đồng bộ khi có thể async
Nếu có 5 tool calls parallel, asyncio.gather speed up 5x.
Fix: Async execution khi tool calls independent.
Hardcoded tool dispatcher
if name == "get_weather":
...
elif name == "get_time":
...
Adds 1 tool = modify 2 chỗ.
Fix: Dict registry (như pattern ở trên).
Áp dụng ngay
Bài tập 1: Implement run_conversation (30 phút)
Copy code ở trên, test với:
run_conversation("What's 30 days from tomorrow?", tools_schemas)
Quan sát turn sequence.
Bài tập 2: Add safety + logging (20 phút)
Mở rộng:
def run_conversation(user_msg, tools, max_turns=10, verbose=True):
"""
- Log mỗi turn (tool name, latency, result)
- Limit max_turns
- Handle timeout per tool (5 seconds)
- Track total tokens
"""
...
Production-grade conversation loop.
Tóm tắt bài học
- Multi-turn tool use = Claude chain tool calls cho task phức tạp.
- Loop pattern: chat → check stop_reason → if tool_use, execute, continue.
- Always có max_turns safety limit.
- Refactor helpers — chat return full Message, text extraction tách riêng.
- Agent = multi-turn tool use scaled up — đây là mô hình cho Module 9.