Sổ tay kỹ thuật

Claude API – tool use loop bằng Python SDK

how-to#claude-api#python#agents· cập nhật 2026-11-06

Dùng khi cần tự viết agent loop gọi tool (ping, show) bằng Messages API và package anthropic, không qua framework.

Sửa trang này
Mục lục

Tóm tắt: Khai báo tools (name, description, input_schema) trong client.messages.create. Khi stop_reason == "tool_use", đọc các block tool_use, chạy hàm tương ứng, gửi lại một message user chứa block tool_result (cùng tool_use_id), lặp đến khi stop_reason == "end_turn". Đây chính là vòng ReAct mà LangGraph đóng gói sẵn.

Luồng hoạt động

sequenceDiagram participant A as Code của bạn participant C as Claude API participant R as Router A->>C: messages + tools C-->>A: stop_reason=tool_use (ping 198.51.100.11) A->>R: chạy ping R-->>A: kết quả A->>C: tool_result (tool_use_id) C-->>A: stop_reason=end_turn, câu trả lời

Cấu hình

Cài đặt

uv add anthropic
export ANTHROPIC_API_KEY='***'

Agent loop đầy đủ

agent.py
import ipaddress
import json
import subprocess
 
import anthropic
 
client = anthropic.Anthropic()  # đọc ANTHROPIC_API_KEY từ env
MODEL = "claude-sonnet-5-5"
MAX_TURNS = 10
 
TOOLS = [
    {
        "name": "ping",
        "description": "Ping một địa chỉ IP từ máy quản trị. Dùng để kiểm tra reachability. Chỉ nhận IP, không nhận hostname.",
        "input_schema": {
            "type": "object",
            "properties": {
                "target": {"type": "string", "description": "IPv4/IPv6, vd 198.51.100.11"},
                "count": {"type": "integer", "minimum": 1, "maximum": 10, "default": 3},
            },
            "required": ["target"],
        },
    },
    {
        "name": "get_device",
        "description": "Tra inventory theo tên thiết bị, trả về IP quản trị, role, OS.",
        "input_schema": {
            "type": "object",
            "properties": {"name": {"type": "string", "description": "vd pe1-hni"}},
            "required": ["name"],
        },
    },
]
 
INVENTORY = {"pe1-hni": {"mgmt": "198.51.100.11", "role": "pe", "os": "iosxr"}}
 
 
def ping(target: str, count: int = 3) -> str:
    ip = ipaddress.ip_address(target)
    out = subprocess.run(["ping", "-c", str(count), "-W", "1", str(ip)],
                         capture_output=True, text=True, timeout=30)
    return out.stdout[-1500:] or out.stderr
 
 
def get_device(name: str) -> str:
    return json.dumps(INVENTORY[name])
 
 
HANDLERS = {"ping": ping, "get_device": get_device}
 
 
def run(question: str) -> str:
    messages = [{"role": "user", "content": question}]
    for _ in range(MAX_TURNS):
        resp = client.messages.create(
            model=MODEL,
            max_tokens=2048,
            system="Bạn là trợ lý NOC. Chỉ dùng tool để đọc trạng thái, không đoán.",
            tools=TOOLS,
            messages=messages,
        )
        # luôn giữ nguyên content của assistant (gồm cả block tool_use)
        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")
 
        results = []
        for block in resp.content:
            if block.type != "tool_use":
                continue
            try:
                output = HANDLERS[block.name](**block.input)
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
            except Exception as e:  # trả lỗi cho model tự sửa, không crash loop
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": f"{type(e).__name__}: {e}", "is_error": True})
        # mọi tool_result của một lượt nằm chung trong MỘT message user
        messages.append({"role": "user", "content": results})
    raise RuntimeError("Vượt MAX_TURNS, có thể agent đang lặp")
 
 
if __name__ == "__main__":
    print(run("pe1-hni có ping được không?"))

Tham số hay dùng

Tham số Dùng khi
tool_choice={"type": "auto"} Mặc định, model tự quyết có gọi tool
tool_choice={"type": "any"} Bắt buộc gọi ít nhất một tool
tool_choice={"type": "tool", "name": "ping"} Ép gọi đúng tool này (vd trích xuất có cấu trúc)
tool_choice={"type": "none"} Cấm gọi tool ở lượt này
system= Vai trò, quy tắc an toàn (chỉ đọc, không commit)
resp.usage Đếm input_tokens / output_tokens để theo dõi chi phí
stop_reason Ý nghĩa Xử lý
tool_use Model muốn gọi tool Chạy tool, gửi tool_result
end_turn Trả lời xong Lấy text, thoát loop
max_tokens Hết max_tokens giữa chừng Tăng max_tokens hoặc yêu cầu trả lời ngắn
stop_sequence Gặp chuỗi trong stop_sequences Tuỳ logic

SDK có tool runner (beta) tự chạy vòng lặp này. Viết tay như trên khi cần kiểm soát: xác nhận người dùng trước tool ghi, log audit, giới hạn số lượt.

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
400 ... tool_use ids were found without tool_result blocks Thiếu tool_result cho một tool_use, hoặc chèn text trước nó Trả đủ kết quả cho mọi tool_use.id, block tool_result đặt đầu content
400 ... unexpected tool_use_id Không append message assistant trước khi gửi tool_result Append resp.content nguyên vẹn
Model không gọi tool, tự bịa Description mơ hồ, system prompt không yêu cầu Viết description rõ "dùng khi nào", thêm chỉ dẫn trong system
Loop không dừng Tool trả lỗi giống nhau, model thử lại mãi MAX_TURNS, trả lỗi rõ ràng kèm gợi ý sửa
401 authentication_error Thiếu/sai ANTHROPIC_API_KEY echo ${ANTHROPIC_API_KEY:+set}
429 / 529 overloaded Rate limit / quá tải SDK đã retry mặc định; tăng max_retries trong Anthropic(max_retries=5)

Tham khảo