Bạn đã:
- Execute tool function với Claude's input params
- Build tool_result block đúng format
- Match tool_use_id giữa request và result
- Gửi multiple tool results trong 1 message
- Handle tool error (is_error=True)
Mở đầu: Đóng vòng lặp
- Viết tool function (6.32)
- Viết schema (6.31)
- Nhận response multi-block (6.33)
Bước cuối — execute function + gửi kết quả về Claude để Claude tạo final response.
Execute tool function
Từ ToolUseBlock, extract và call:
tool_call = response.content[-1] # giả định block cuối là ToolUse
name = tool_call.name # "get_current_datetime"
input_data = tool_call.input # {"date_format": "%H:%M:%S"}
result = get_current_datetime(**input_data)
**input_data unpack dict thành keyword arguments.
Tool result block structure
Sau khi có result, build tool_result block:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_call.id, # ← Match với ToolUseBlock.id
"content": str(result), # ← Convert to string
"is_error": False # ← True nếu function raise
}
Critical: tool_use_id phải match. Nếu sai → Claude không biết result cho request nào.
Wrap trong user message
Tool result đi trong user message (dù không phải user gửi):
messages.append({
"role": "user",
"content": [tool_result_block]
})
Tại sao là user? Vì Anthropic API alternating user/assistant. Assistant đã request → user (bạn) provide data.
Flow đầy đủ (1 tool)
messages = []
add_user_message(messages, "What time is it in HH:MM:SS?")
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
tool_call = next(b for b in response.content if b.type == "tool_use")
result = get_current_datetime(**tool_call.input) # "14:30:25"
add_user_message(messages, [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": result,
"is_error": False
}])
final = chat(messages, tools=[get_current_datetime_schema])
print(final.content[0].text)
Pass tools= cả ở final call** — Claude cần schema để hiểu tool_result trong history.
Handle tool error
Nếu tool raise exception:
try:
result = run_tool(tool_call.name, tool_call.input)
is_error = False
except Exception as e:
result = str(e)
is_error = True
tool_result = {
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": result,
"is_error": is_error
}
Khi is_error=True:
- Claude "thấy" có lỗi
- Có thể retry với params khác
- Hoặc acknowledge trong response tới user
Multiple tool calls trong 1 response
Claude có thể request nhiều tool cùng lúc:
[
TextBlock(text="Let me calculate both."),
ToolUseBlock(id="t1", name="add", input={"a": 10, "b": 10}),
ToolUseBlock(id="t2", name="add", input={"a": 30, "b": 30})
]
Loop qua, build list tool_results:
tool_calls = [b for b in response.content if b.type == "tool_use"]
tool_results = []
for tc in tool_calls:
try:
result = run_tool(tc.name, tc.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": str(result),
"is_error": False
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": str(e),
"is_error": True
})
add_user_message(messages, tool_results)
Claude xử lý tất cả results → tạo final response.
Dispatcher pattern
TOOL_REGISTRY = {
"get_weather": get_weather,
"get_current_datetime": get_current_datetime,
"add_duration_to_datetime": add_duration_to_datetime,
}
def run_tool(name: str, input_data: dict) -> str:
"""Dispatch to registered function."""
if name not in TOOL_REGISTRY:
raise ValueError(f"Unknown tool: {name}")
func = TOOL_REGISTRY[name]
return func(**input_data)
def run_tools(tool_uses: list) -> list:
"""Run multiple tools, return tool_result blocks."""
results = []
for tu in tool_uses:
try:
output = run_tool(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": f"Error: {str(e)}",
"is_error": True
})
return results
Reusable cho mọi turn tool use.
Content types cho tool_result
content có thể là:
String (simple)
{"content": "72°F, sunny"}
Multi-part content (advanced)
{"content": [
{"type": "text", "text": "Weather data:"},
{"type": "image", "source": {...}} # Image in result!
]}
Ví dụ: tool render_chart return cả text description + chart image.
JSON string
import json
result_dict = {"temp": 72, "condition": "sunny", "humidity": 65}
{"content": json.dumps(result_dict)}
Claude parse được JSON, nhưng formatted string thường clearer.
Ví dụ đầy đủ từ scratch
import json
from datetime import datetime
from anthropic import Anthropic
from anthropic.types import Message
client = Anthropic()
model = "claude-sonnet-4-6-20260205"
def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
if not date_format:
raise ValueError("date_format cannot be empty")
return datetime.now().strftime(date_format)
get_current_datetime_schema = {
"name": "get_current_datetime",
"description": "Get current date/time in specified strftime format. Use when user asks for current time.",
"input_schema": {
"type": "object",
"properties": {
"date_format": {
"type": "string",
"description": "Python strftime format",
"default": "%Y-%m-%d %H:%M:%S"
}
},
"required": []
}
}
def add_user_message(messages, content):
if isinstance(content, Message):
content = content.content
messages.append({"role": "user", "content": content})
def add_assistant_message(messages, content):
if isinstance(content, Message):
content = content.content
messages.append({"role": "assistant", "content": content})
def chat(messages, tools=None):
params = {
"model": model, "max_tokens": 1000, "messages": messages
}
if tools:
params["tools"] = tools
return client.messages.create(**params)
messages = []
add_user_message(messages, "What's the current time in HH:MM?")
response = chat(messages, tools=[get_current_datetime_schema])
print(f"Stop: {response.stop_reason}") # "tool_use"
add_assistant_message(messages, response)
tool_call = next(b for b in response.content if b.type == "tool_use")
result = get_current_datetime(**tool_call.input)
print(f"Tool result: {result}") # "14:30"
add_user_message(messages, [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": result,
"is_error": False
}])
final = chat(messages, tools=[get_current_datetime_schema])
print(f"Final: {final.content[0].text}")
Chạy → thấy full flow.
Anti-patterns
Quên tool_use_id
{"type": "tool_result", "content": "..."} # thiếu id
Fix: tool_use_id bắt buộc.
Sai ID (mismatch)
tool_call.id = "toolu_ABC"
tool_result.tool_use_id = "toolu_XYZ" # khác!
Fix: Copy chính xác từ tool_call.id.
Content không phải string
"content": {"result": "..."} # dict, not string
Fix: str(result) hoặc json.dumps(result).
Quên pass tools= ở final call
Claude sẽ confused, không biết tool schema cho context.
Fix: Pass tools ở mọi call trong conversation.
Handle single, không multi
Code assume 1 tool call per response.
Fix: Loop qua all ToolUseBlocks.
Áp dụng ngay
Bài tập 1: Complete flow 1 tool (20 phút)
Ship full example ở trên. Verify:
- Claude request tool đúng
- Bạn execute đúng
- Final response có dùng tool result
Bài tập 2: Handle multi-tool (20 phút)
User: "What's time now + add 100 days"
Claude có thể request 2 tool:
get_current_datetimeadd_duration_to_datetime
Viết code handle. Có thể Claude request sequential (Module 6) hoặc parallel. Code phải robust.
Tóm tắt bài học
- Tool result block:
{type, tool_use_id, content, is_error}. - Wrap trong user message, append vào history.
tool_use_idmatch với ToolUseBlock.id — critical cho tracking.- Multi-tool: loop, build list of results, 1 user message với tất cả.
is_error=Truekhi function raise → Claude có thể retry.