Product Management
Đăng nhập
ESC

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

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

Bài 3 — Đóng vòng lặp: nối tool call vào agent loop

Đã đọc 0/8 mục · còn khoảng 3 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 2 dừng ở chỗ LLM yêu cầu gọi tool. Bài này ta thực thi tool, trả kết quả về, và lặp — biến một lời gọi API thành agent thật sự.

Bước 1: Viết bộ điều phối (dispatcher)

Dispatcher ánh xạ tên tool → hàm Python thật:

def read_file(path):
    with open(path, "r", encoding="utf-8") as f:
        return f.read()

def run_command(command):
    import subprocess
    r = subprocess.run(command, shell=True, capture_output=True,
                       text=True, timeout=60)
    return f"exit={r.returncode}\nSTDOUT:\n{r.stdout}\nSTDERR:\n{r.stderr}"

TOOL_FUNCS = {"read_file": read_file, "run_command": run_command}

def run_tool(name, args):
    try:
        return TOOL_FUNCS[name](**args)
    except Exception as e:
        return f"ERROR: {type(e).__name__}: {e}"   # trả lỗi, KHÔNG raise

Chú ý dòng cuối: khi tool lỗi, ta trả chuỗi lỗi cho LLM thay vì để chương trình crash. Đây là mấu chốt của vòng lặp tự sửa: LLM cần đọc được lỗi để phản ứng.

Bước 2: Vòng lặp hoàn chỉnh

messages = [{"role": "user", "content": user_task}]
for step in range(25):                     # giới hạn 25 vòng
    resp = client.messages.create(
        model=MODEL, max_tokens=2048,
        tools=TOOLS, messages=messages)
    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason != "tool_use":
        print(final_text(resp)); break     # xong

    results = []
    for block in resp.content:
        if block.type == "tool_use":
            out = run_tool(block.name, block.input)
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": out,
            })
    messages.append({"role": "user", "content": results})
else:
    print("Đã đạt giới hạn vòng lặp — dừng an toàn.")

Bước 3: Hiểu "khớp ID" của tool result

Mỗi tool_use block có id. Khi trả kết quả, bạn phải gắn đúng tool_use_id tương ứng. Nếu LLM gọi 3 tool cùng lúc, bạn trả về 3 tool_result khớp 3 id. Sai id ⇒ API báo lỗi hoặc LLM hiểu nhầm kết quả thuộc về tool nào.

Ví dụ chạy thực

Task: "Có bao nhiêu dòng trong app.py?"

  • Vòng 1 — LLM gọi run_command("wc -l app.py").
  • Agent chạy → trả exit=0\nSTDOUT:\n42 app.py.
  • Vòng 2 — LLM đọc kết quả, không gọi tool nữa, trả lời "File có 42 dòng." → break.
Hai vòng, đúng một tool. Đơn giản nhưng đã là một agent thực thụ.

Khung tư duy: 3 bất biến của vòng lặp

  • Mọi assistant message đều được append trước khi xử lý tool — để history nhất quán.
  • Mọi tool_use đều sinh đúng một tool_result khớp id — không thiếu, không thừa.
  • Luôn có lối thoát: điều kiện dừng (không tool) và trần vòng lặp (max steps).

Checklist

  • [ ] Dispatcher bắt exception và trả chuỗi lỗi, không raise.
  • [ ] Tool result gắn đúng tool_use_id.
  • [ ] Có max_steps và nhánh xử lý khi chạm trần.
  • [ ] Append cả assistant message lẫn tool result vào history.
  • [ ] run_command có timeout để tránh treo vô hạn.

Sai lầm thường gặp

  • Để tool raise exception làm sập agent. Một lệnh sai cú pháp không nên giết cả phiên; hãy trả lỗi cho LLM xử lý.
  • Trả thiếu tool_result khi LLM gọi nhiều tool song song. API sẽ từ chối message tiếp theo.
  • Không append assistant content chứa tool_use trước khi gửi tool_result — phá vỡ thứ tự hội thoại.
  • Quên timeout ở run_command. Một lệnh npm install hoặc server chạy nền có thể treo agent mãi mãi.
  • Đặt max_steps quá thấp (2–3). Task thật thường cần 8–15 vòng; trần quá thấp khiến agent bỏ dở.
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