Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 4 — Tự viết agent loop cho Anthropic tool use từ A đến Z

Đã đọc 0/9 mục · còn khoảng 4 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Bài 4 — Tự viết agent loop cho Anthropic tool use

OpenAI Agents SDK giấu vòng lặp đi. Với Anthropic Messages API, bạn tự viết — và đây là kỹ năng quan trọng nhất, vì nó cho bạn quyền kiểm soát tuyệt đối (log, retry, ngắt, giới hạn số vòng).

Sơ đồ vòng lặp

messages = [user question]
lặp tối đa N vòng:
    resp = client.messages.create(model, tools, messages, max_tokens)
    thêm resp (role=assistant) vào messages
    nếu resp.stop_reason != "tool_use":
        return text cuối     # xong
    với mỗi block tool_use trong resp.content:
        kết quả = chạy_tool(block.name, block.input)
        gom vào 1 message role=user chứa các tool_result (khớp id)
    thêm message tool_result vào messages

Chìa khóa: sau mỗi lượt tool_use, bạn gửi một message role=user chứa tất cả tool_result của lượt đó, mỗi cái khớp tool_use_id.

Code đầy đủ

import anthropic, json

client = anthropic.Anthropic()

def chay_tool(name, args):
    if name == "tra_cuu_don_hang":
        return f"Đơn {args['order_id']}: đang giao"
    return f"Lỗi: không có tool {name}"

def run_agent(cau_hoi, tools, max_vong=6):
    messages = [{"role": "user", "content": cau_hoi}]
    for _ in range(max_vong):
        resp = client.messages.create(
            model="claude-sonnet-4-5", max_tokens=1024,
            tools=tools, messages=messages,
        )
        messages.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":
            return "".join(b.text for b in resp.content if b.type == "text")

        ket_qua = []
        for block in resp.content:
            if block.type == "tool_use":
                out = chay_tool(block.name, block.input)
                ket_qua.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,   # PHẢI khớp
                    "content": out,
                })
        messages.append({"role": "user", "content": ket_qua})
    return "Đã đạt giới hạn số vòng, dừng an toàn."

Các bước triển khai

  • Khởi tạo messages với câu hỏi người dùng.
  • Gọi model, luôn append nguyên resp.content với role="assistant" — giữ đủ ngữ cảnh cho vòng sau.
  • Kiểm tra stop_reason: khác "tool_use" nghĩa là model đã trả lời xong → return.
  • Duyệt từng block tool_use, chạy tool tương ứng, thu tool_result khớp id.
  • Gửi lại toàn bộ tool_result trong một message role="user".
  • Giới hạn max_vong để không lặp vô hạn.

Xử lý lỗi tool ngay trong loop

try:
    out = chay_tool(block.name, block.input)
except Exception as e:
    ket_qua.append({
        "type": "tool_result", "tool_use_id": block.id,
        "content": f"Lỗi khi chạy tool: {e}",
        "is_error": True,
    })
    continue

Đặt is_error: True để Claude hiểu đây là lỗi và cân nhắc thử cách khác thay vì tin đó là dữ liệu hợp lệ.

Checklist một agent loop đúng

  • [ ] Append nguyên resp.content với role=assistant trước khi gửi tool_result.
  • [ ] Mỗi tool_result khớp đúng tool_use_id của block tương ứng.
  • [ ] Gom mọi tool_result của một lượt vào MỘT message role=user.
  • [ ] Có max_vong và dừng an toàn khi chạm trần.
  • [ ] Lỗi tool được trả về với is_error=True, không nuốt im lặng.
  • [ ] Log tên tool, input, output rút gọn ở mỗi lượt.

Vì sao phải append nguyên resp.content

Một chi tiết dễ bỏ sót nhưng gây lỗi khó hiểu: sau mỗi lượt, bạn phải append nguyên resp.content (danh sách block, gồm cả text lẫn tool_use) vào messages với role="assistant", rồi mới thêm message tool_result. Nếu bạn chỉ nhặt block tool_use hoặc chỉ nhặt text, lịch sử hội thoại gửi lên API sẽ không hợp lệ, vì Anthropic yêu cầu mỗi tool_result phải theo sau một assistant message chứa tool_use tương ứng. Cấu trúc đúng của messages sau một lượt tool là: [user(câu hỏi), assistant(text + tool_use), user(tool_result)]. Giữ đúng thứ tự và đầy đủ nội dung này là điều kiện để vòng sau chạy được.

Khi nào một tool nên trả nhiều tool_use

Trong một lượt, Claude có thể yêu cầu gọi nhiều tool cùng lúc (ví dụ tra 3 đơn hàng song song). Vòng lặp của bạn phải duyệt HẾT các block tool_use trong resp.content, chạy từng cái, và gom tất cả tool_result vào một message role="user" duy nhất. Đừng gửi mỗi kết quả thành một message riêng — điều đó phá vỡ cặp ghép tool_use ↔ tool_result và khiến API báo lỗi hoặc Claude hiểu nhầm ngữ cảnh.

Sai lầm thường gặp

  • Quên append assistant content trước khi gửi tool_result → API báo message không hợp lệ.
  • Gộp sai tool_use_id → Claude ghép kết quả nhầm tool.
  • Trả nhiều tool_result thành nhiều message thay vì một message gộp → cấu trúc hội thoại hỏng.
  • Không giới hạn số vòng → agent kẹt lặp, đốt token.
  • Nuốt exception im lặng → model tưởng tool thành công, trả lời sai; hãy báo is_error.
  • Không log → khi lỗi không biết vòng nào, tool nào gây ra.
Giờ bạn kiểm soát được loop thủ công. Bài 5 chuyển sang handoff — điều phối nhiều agent chuyên biệt.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi