Sending tool results — Gửi kết quả về Claude

5 — Tool UseTrung cấp20 phút

Bạn đã: - Viết tool function (6.32) - Viết schema (6.31) - Nhận response multi-block (6.33)

Bạn sẽ học được
  • 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)

Execute tool function

Từ ToolUseBlock, extract và call:

**input_data unpack dict thành keyword arguments.

tool_call = response.content[-1]  # giả định block cuối là ToolUse

# Extract
name = tool_call.name  # "get_current_datetime"
input_data = tool_call.input  # {"date_format": "%H:%M:%S"}

# Call function
result = get_current_datetime(**input_data)
# "14:30:25"

Tool result block structure

Sau khi có result, build tool_result block:

Critical: tool_use_id phải match. Nếu sai → Claude không biết result cho request nào.

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
}

Wrap trong user message

Tool result đi trong user message (dù không phải user gửi):

Tại sao là user? Vì Anthropic API alternating user/assistant. Assistant đã request → user (bạn) provide data.

messages.append({
    "role": "user",
    "content": [tool_result_block]
})

Flow đầy đủ (1 tool)

Lưu ý: Pass tools= cả ở final call — Claude cần schema để hiểu tool_result trong history.

# 1. User hỏi
messages = []
add_user_message(messages, "What time is it in HH:MM:SS?")

# 2. Claude request tool
response = chat(messages, tools=[get_current_datetime_schema])

# 3. Append response với ToolUseBlock
add_assistant_message(messages, response)

# 4. Execute tool
tool_call = next(b for b in response.content if b.type == "tool_use")
result = get_current_datetime(**tool_call.input)  # "14:30:25"

# 5. Build tool_result + append
add_user_message(messages, [{
    "type": "tool_result",
    "tool_use_id": tool_call.id,
    "content": result,
    "is_error": False
}])

# 6. Final call với full history
final = chat(messages, tools=[get_current_datetime_schema])
print(final.content[0].text)
# "The current time is 14:30:25."

Handle tool error

Nếu tool raise exception:

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
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
}

Multiple tool calls trong 1 response

Claude có thể request nhiều tool cùng lúc:

Loop qua, build list tool_results:

# User: "What's 10+10 and 30+30?"
# response.content:
[
    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})
]

Multiple tool calls trong 1 response (tiếp)

Claude xử lý tất cả results → tạo final response.

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
        })

# Gửi tất cả results trong 1 user message
add_user_message(messages, tool_results)

Dispatcher pattern

Reusable cho mọi turn tool use.

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

Content types cho tool_result

content có thể là:

String (simple)

Multi-part content (advanced)

{"content": "72°F, sunny"}

Multi-part content (advanced)

Ví dụ: tool render_chart return cả text description + chart image.

JSON string

{"content": [
    {"type": "text", "text": "Weather data:"},
    {"type": "image", "source": {...}}  # Image in result!
]}

JSON string

Claude parse được JSON, nhưng formatted string thường clearer.

import json
result_dict = {"temp": 72, "condition": "sunny", "humidity": 65}
{"content": json.dumps(result_dict)}

Ví dụ đầy đủ từ scratch

Chạy → thấy full flow.

import json
from datetime import datetime
from anthropic import Anthropic
from anthropic.types import Message

client = Anthropic()
model = "claude-sonnet-5-20260205"

# === Tool definition ===
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": []
    }
}

# === Helpers ===
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)

# === Main flow ===
messages = []
add_user_message(messages, "What's the current time in HH:MM?")

# Turn 1
response = chat(messages, tools=[get_current_datetime_schema])
print(f"Stop: {response.stop_reason}")  # "tool_use"
add_assistant_message(messages, response)

# Execute
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"

# Turn 2 — send result
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}")
# "The current time is 14:30."

Anti-patterns

❌ Quên tool_use_id

Fix: tool_use_id bắt buộc.

❌ Sai ID (mismatch)

{"type": "tool_result", "content": "..."}  # thiếu id

❌ Sai ID (mismatch)

Fix: Copy chính xác từ tool_call.id.

❌ Content không phải string

tool_call.id = "toolu_ABC"
tool_result.tool_use_id = "toolu_XYZ"  # khác!

❌ Content không phải 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.

"content": {"result": "..."}  # dict, not string

Áp dụng ngay

Bài tập 1: Complete flow 1 tool (20 phút)

Ship full example ở trên. Verify:

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:

Viết code handle. Có thể Claude request sequential (Module 6) hoặc parallel. Code phải robust.

  • Claude request tool đúng
  • Bạn execute đúng
  • Final response có dùng tool result
  • get_current_datetime
  • add_duration_to_datetime

Tóm tắt

🎯 Tool result block: {type, tool_use_id, content, is_error}.

🎯 Wrap trong user message, append vào history.

🎯 tool_use_id match với ToolUseBlock.id — critical cho tracking.

🎯 Multi-tool: loop, build list of results, 1 user message với tất cả.

🎯 is_error=True khi function raise → Claude có thể retry.

Nội dung này có hữu ích không?