Handling message blocks — Multi-block responses

5 — Tool UseTrung cấp15 phút

Ở bài 6.6 bạn học: message.content[0].text — response là 1 text block.

Bạn sẽ học được
  • Hiểu multi-block response khi Claude dùng tool
  • Phân biệt TextBlock và ToolUseBlock
  • Append multi-block message vào history đúng cách
  • Update helper functions để handle multi-block

Gọi API với tools

tools = list of schemas (bài 6.31).

messages = [{"role": "user", "content": "What's the exact time in HH:MM:SS?"}]

response = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    tools=[get_current_datetime_schema],  # ← thêm tools param
)

Anatomy multi-block response

3 loại block thường thấy:

  • TextBlock — text Claude muốn nói
  • ToolUseBlock — request tool call
  • ThinkingBlock — (Module 6) reasoning process
response.content = [
    TextBlock(
        type="text",
        text="I'll get the current time for you."
    ),
    ToolUseBlock(
        type="tool_use",
        id="toolu_01ABC...",
        name="get_current_datetime",
        input={"date_format": "%H:%M:%S"}
    )
]

response.stop_reason = "tool_use"  # ← Claude dừng vì muốn tool

ToolUseBlock có gì?

  • id — match với tool_result sau (critical nếu có multi-tool)
  • name — function nào
  • input — dict của arguments
ToolUseBlock(
    type="tool_use",
    id="toolu_01XYZ...",         # Unique ID cho tool call
    name="get_current_datetime",  # Function name
    input={"date_format": "%H:%M:%S"}  # Arguments
)

Check stop_reason

stop_reason="tool_use" là signal bạn cần process tool call.

if response.stop_reason == "tool_use":
    # Claude muốn call tool → bạn execute
    ...
elif response.stop_reason == "end_turn":
    # Claude done, không cần tool nữa
    text = response.content[0].text
elif response.stop_reason == "max_tokens":
    # Bị cắt
    ...

Append multi-block vào messages

Quan trọng: phải append toàn bộ response.content (không chỉ text):

Lý do: Claude cần thấy full history. Nếu quên ToolUseBlock → turn tiếp theo Claude không biết mình đã request tool.

# ✅ Đúng
messages.append({
    "role": "assistant",
    "content": response.content  # ← Entire list of blocks
})

# ❌ Sai — mất ToolUseBlock
messages.append({
    "role": "assistant",
    "content": response.content[0].text  # chỉ text
})

Extract specific block

Helper function:

# Find ToolUseBlock
tool_calls = [b for b in response.content if b.type == "tool_use"]

# Find TextBlocks
text_blocks = [b for b in response.content if b.type == "text"]

# Combine text
full_text = "\n".join(b.text for b in text_blocks)

Extract specific block (tiếp)

def get_tool_calls(response):
    return [b for b in response.content if b.type == "tool_use"]

def text_from_message(response):
    return "\n".join(b.text for b in response.content if b.type == "text")

Update helper functions

add_user_message cũ chỉ nhận string. Update để flexible:

Giờ có thể pass full response hoặc plain text.

from anthropic.types import Message

def add_user_message(messages, content):
    """Accept string, list of blocks, or Message object."""
    if isinstance(content, str):
        messages.append({"role": "user", "content": content})
    elif isinstance(content, Message):
        messages.append({"role": "user", "content": content.content})
    else:
        # Assume list of content blocks
        messages.append({"role": "user", "content": content})


def add_assistant_message(messages, content):
    if isinstance(content, str):
        messages.append({"role": "assistant", "content": content})
    elif isinstance(content, Message):
        messages.append({"role": "assistant", "content": content.content})
    else:
        messages.append({"role": "assistant", "content": content})

Update chat function

Thay vì return .content[0].text, return full message. Gọi side extract text nếu cần.

def chat(messages, system=None, temperature=1.0, stop_sequences=None, tools=None):
    params = {
        "model": model,
        "max_tokens": 1000,
        "messages": messages,
        "temperature": temperature,
    }
    if system:
        params["system"] = system
    if stop_sequences:
        params["stop_sequences"] = stop_sequences
    if tools:
        params["tools"] = tools
    
    return client.messages.create(**params)  # Return full Message

Ví dụ đầy đủ — 1 tool call

messages = []
add_user_message(messages, "What is the exact time, formatted HH:MM:SS?")

# Step 1: Claude request tool
response = chat(messages, tools=[get_current_datetime_schema])
print(f"Stop reason: {response.stop_reason}")  # "tool_use"

# Step 2: Inspect content blocks
for block in response.content:
    print(f"Block type: {block.type}")
    if block.type == "text":
        print(f"Text: {block.text}")
    elif block.type == "tool_use":
        print(f"Tool: {block.name}, Input: {block.input}")

# Step 3: Append response to history
add_assistant_message(messages, response)
# messages giờ có: [user msg, assistant msg with 2 blocks]

# Bước 4: Execute tool (bài 6.34)
# ...

Ví dụ: Multiple tool calls trong 1 response

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

Loop qua tất cả:

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

Ví dụ: Multiple tool calls trong 1 response (tiếp)

Chi tiết xử lý ở bài 6.34.

tool_calls = [b for b in response.content if b.type == "tool_use"]
for tc in tool_calls:
    result = run_tool(tc.name, tc.input)
    # tracked với tc.id

Anti-patterns

❌ Assume response.content[0] luôn là text

Với tool use, content[0] có thể là TextBlock HOẶC ToolUseBlock.

Fix: Dùng block.type check.

❌ Quên append ToolUseBlock vào history

Turn tiếp Claude không thấy đã call tool → re-request.

Fix: messages.append({"role": "assistant", "content": response.content}) — FULL list.

❌ Modify response.content

Không thêm/xóa block từ response.

Fix: Xem response là immutable.

❌ Hardcode block index

Fix: Filter by type.

tool_call = response.content[1]  # assume always index 1

Áp dụng ngay

Bài tập 1: Inspect tool response (15 phút)

Gọi Claude với tool, print chi tiết content:

Bài tập 2: Update helpers (15 phút)

Update add_user_message, add_assistant_message, chat theo code ở trên. Test với:

  • Plain string input
  • Message object input
  • List of blocks input
response = chat([{"role": "user", "content": "Time now?"}], 
                tools=[get_current_datetime_schema])

for i, block in enumerate(response.content):
    print(f"Block {i}: type={block.type}")
    if block.type == "tool_use":
        print(f"  name: {block.name}")
        print(f"  input: {block.input}")
        print(f"  id: {block.id}")
    elif block.type == "text":
        print(f"  text: {block.text}")

Tóm tắt

🎯 Tool use → multi-block response. TextBlock + ToolUseBlock.

🎯 Check stop_reason="tool_use" để biết Claude muốn tool.

🎯 Append FULL response.content vào history — không chỉ text.

🎯 Update helpers flexible với string/Message/list.

🎯 Filter block by type — [b for b in content if b.type == "tool_use"].

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