Bạn muốn gắn một trợ lý AI vào web bán hàng: khách gõ mã đơn, trợ lý tra giúp tình trạng giao hàng. Bản thử chạy ngon. Rồi lên thật thì có ba chuyện nảy ra. Khách phải nhìn màn hình trống mười mấy giây mới thấy chữ. Trợ lý không tự tra được đơn, vì nó không biết kho dữ liệu của bạn. Và hoá đơn API cuối tháng cao hơn bạn tưởng, vì lần nào bạn cũng gửi lại cả cuốn hướng dẫn dài.
Đó là ba việc của bài này, gồm cách để AI gọi hàm của bạn, cách cho chữ hiện dần, và cách đừng trả tiền nhiều lần cho cùng một đoạn chữ. Mình dùng Claude API. Tên model, tham số và cú pháp bên dưới mình đối chiếu với tài liệu SDK chính thức hiện hành (tháng 10/2026), không viết theo trí nhớ.
Cách API này hoạt động
Mọi thứ đi qua một cửa duy nhất. Bạn gửi một danh sách tin nhắn (lần lượt của người dùng và của AI), có thể kèm lời dặn hệ thống và danh sách công cụ. API trả về một danh sách khối nội dung: chữ, yêu cầu gọi công cụ, tóm tắt suy nghĩ. API không nhớ gì giữa hai lần gọi, nên mỗi lần bạn gửi lại toàn bộ cuộc trò chuyện.
Cài thư viện chính thức bằng pip install anthropic (Python) hoặc npm install @anthropic-ai/sdk (JavaScript). Khoá API để trong biến môi trường ANTHROPIC_API_KEY, đừng ghi vào code. Thư viện tự đọc biến này.
Chọn model nào
| Model | ID | Hợp với | Giá vào / ra mỗi triệu token |
|---|---|---|---|
| Claude Opus 5.5 | claude-opus-5-5 |
việc khó, code, agent; chọn mặc định | 4 USD / 20 USD |
| Claude Sonnet 5.5 | claude-sonnet-5-5 |
khối lượng lớn, cần nhanh và rẻ hơn | 2 USD / 10 USD |
| Claude Haiku 5.5 | claude-haiku-5-5 |
phân loại, trích xuất đơn giản | 0,10 USD / 0,50 USD (prompt tới 100K token) |
Ghi đúng chuỗi ID như trong bảng, đừng thêm ngày tháng phía sau. Giá có thể đổi, nên đối chiếu trang giá chính thức khi lập ngân sách.
Ba điều khác với đời model cũ hay làm code bị lỗi 400. Trên Opus 5.5, AI luôn có bước "suy nghĩ" và không tắt được; bỏ trống tham số thinking là xong, còn gửi {"type": "disabled"} hay budget_tokens sẽ bị từ chối. Muốn nhanh và rẻ hơn thì hạ output_config={"effort": "low"}, mặc định của đời này là medium. Thứ hai, đừng gửi temperature, top_p, top_k khác mặc định. Thứ ba, bắt AI phải gọi một công cụ cụ thể bằng tool_choice kiểu any hoặc tool cũng bị từ chối trên Opus 5.5 và Sonnet 5.5; dùng auto và nói rõ trong lời dặn.
Để AI gọi hàm của bạn (tool use)
Nghĩ như thế này. AI là một nhân viên giỏi nhưng ngồi trong phòng kín. Muốn tra đơn hàng, nó viết giấy nhờ bạn: "tra giúp đơn HD-1001". Bạn tra, rồi đưa kết quả qua khe cửa. Nó đọc và trả lời khách. AI không bao giờ tự chạy hàm của bạn. Nó chỉ nhờ, bạn chạy.
Về kỹ thuật, bạn mô tả công cụ bằng tên, mô tả và khuôn dữ liệu. AI trả về một khối tool_use kèm tham số, bạn chạy hàm, rồi gửi kết quả lại bằng khối tool_result. Vòng này lặp tới khi AI không nhờ nữa.

Ba điều cần nhớ khi làm. Luôn kiểm dữ liệu AI gửi trước khi chạy hàm, đừng giả định nó đúng khuôn. Nếu hàm lỗi, trả is_error: true kèm lời giải thích để AI tự sửa, đừng im lặng bỏ qua. Và nếu AI nhờ nhiều việc một lượt, chạy hết rồi gửi tất cả kết quả trong một tin nhắn duy nhất; tách ra nhiều tin nhắn sẽ làm AI dần ngừng nhờ song song.
Cho chữ hiện dần (streaming)
Bình thường API làm xong cả câu trả lời rồi mới gửi. Với câu dài, khách nhìn màn hình trống. Streaming là gửi từng mảnh chữ ngay khi nó được sinh ra, giống nhìn người ta gõ. Thư viện cũng yêu cầu dùng streaming khi bạn cho phép câu trả lời rất dài.
Có một chi tiết: trên các model mới, phần "suy nghĩ" mặc định không được trả về. Nếu muốn hiện tóm tắt cho khách, đặt thinking={"type": "adaptive", "display": "summarized"}.
Đừng trả tiền nhiều lần cho cùng một đoạn (prompt caching)
Nếu mỗi lần gọi bạn gửi lại cùng một cuốn hướng dẫn dài, hãy đánh dấu để API ghi nhớ nó. Lần sau phần đã ghi nhớ chỉ tính một phần nhỏ giá thường (khoảng một phần mười, hoặc rẻ hơn tuỳ model). Lần đầu ghi hơi đắt hơn một chút, khoảng 1,25 lần.
Quy tắc cần nhớ là cache khớp theo phần đầu. Đổi một chữ ở đầu là mất cache từ chỗ đó trở đi. Thứ tự gửi là công cụ, lời dặn hệ thống, rồi tới tin nhắn, nên hãy đặt phần ít đổi ở trước và phần hay đổi ở sau cùng. Nhớ rằng phần ghi nhớ phải đủ dài (tài liệu ghi ngưỡng tối thiểu khoảng 512 token tuỳ model, hãy đối chiếu lại); ngắn quá thì API không báo lỗi, chỉ đơn giản là không cache. Mặc định ghi nhớ 5 phút, có thể đặt "ttl": "1h".

Cách biết cache có chạy không: xem số liệu dùng (usage). Lần đầu cache_creation_input_tokens lớn, các lần sau cache_read_input_tokens lớn. Nếu số đọc luôn bằng 0, có "kẻ phá thầm lặng" nào đó: giờ hiện tại nằm trong lời dặn, một mã ngẫu nhiên, hay danh sách công cụ đổi thứ tự mỗi lần.
Cho bạn nào viết code
Vòng lặp gọi công cụ bằng Python (bản TypeScript có cùng cấu trúc):
TOOLS = [{
"name": "get_order_status",
"description": "Look up an order by its id. Use only when the user gives an order id.",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "Order id like HD-12345"}},
"required": ["order_id"],
"additionalProperties": False,
},
}]
def run_with_tools(client, user_input, max_turns=8):
messages = [{"role": "user", "content": user_input}]
for _ in range(max_turns):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=16000, tools=TOOLS, messages=messages)
if response.stop_reason in ("refusal", "max_tokens"):
raise RuntimeError(f"Stopped early: {response.stop_reason}")
if response.stop_reason == "pause_turn": # only with server-side tools
messages.append({"role": "assistant", "content": response.content})
continue
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
content, is_error = execute_tool(block.name, block.input)
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": content, "is_error": is_error})
messages.append({"role": "user", "content": results}) # ALL results in ONE message
raise RuntimeError("Too many tool turns")
Streaming và cache, cùng một ý ở cả hai ngôn ngữ:
with client.messages.stream(
model="claude-sonnet-5-5", max_tokens=64000,
messages=[{"role": "user", "content": prompt}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message() # full Message, including usage and stop_reason
response = client.messages.create(
model="claude-sonnet-5-5", max_tokens=16000,
system=[{"type": "text", "text": guidelines, # stable part first
"cache_control": {"type": "ephemeral"}}],
messages=[{"role": "user", "content": f"Review this diff:\n{diff}"}], # changing part last
)
u = response.usage
print(u.cache_creation_input_tokens, u.cache_read_input_tokens, u.input_tokens)
const stream = client.messages
.stream({ model: "claude-sonnet-5-5", max_tokens: 64000,
messages: [{ role: "user", content: prompt }] })
.on("text", (text) => process.stdout.write(text));
const final = await stream.finalMessage();
Bộ lọc an toàn của Opus 5.5 đôi khi từ chối một yêu cầu (stop_reason: "refusal"). Có tham số beta fallbacks: "default" kèm header server-side-fallback-2026-07-01 để API tự chạy lại yêu cầu đó trên model thay thế. Mình chỉ kiểm hàm này bằng trình biên dịch TypeScript và máy chủ giả, chưa gọi thật.
Lỗi hay gặp
Lỗi hay nhất là bỏ qua stop_reason. Nếu là max_tokens, câu trả lời bị cắt giữa chừng; nếu là refusal, bộ lọc đã từ chối và content có thể rỗng. Hãy kiểm trước khi đọc nội dung. Một lỗi nữa là bắt lỗi chung chung một lần. Nên tách: RateLimitError và lỗi 5xx thì thử lại (thư viện đã tự thử lại hai lần), lỗi 4xx thì đừng thử lại vì gửi lại vẫn sai. Và đừng để khoá API trong repo hay trong log.
Mình đã kiểm gì, chưa kiểm gì
Máy mình không có khoá API, nên chưa có lời gọi thật nào. Hôm 21/10/2026 mình chạy lại ba việc. Một là biên dịch file Python (python -m py_compile, cài anthropic bản mới nhất 1.17.0 trong thư mục thử). Hai là tsc --noEmit với @anthropic-ai/sdk 0.135.0, đúng kiểu và đúng tham số, kể cả fallbacks: "default". Ba là chạy cả hai bản với một máy chủ Messages giả cục bộ: vòng công cụ gửi đúng một tool_use rồi đúng một tool_result trong một tin nhắn, streaming ghép đúng chữ, và usage đọc đúng số cache. Đây là kiểm cách code của mình và thư viện xử lý phản hồi, không chứng minh hành vi thật của API. Trước khi dùng cho việc tính tiền, hãy chạy một lần với khoá của bạn.
Làm gì tiếp theo
Bắt đầu nhỏ: một lần gọi đơn giản với claude-opus-5-5 hoặc claude-sonnet-5-5, bật streaming, rồi mới thêm công cụ. Khi có công cụ chạy ổn, sắp lời dặn dài lên đầu và đánh dấu cache, rồi nhìn usage để xem tiết kiệm thật hay không.
Nếu bạn cần cả một đội AI thay vì một cuộc gọi lẻ, xem Giao việc lớn cho nhiều AI cùng làm: cách chia việc để không hỏng. Cách viết lời dặn cho từng lần gọi nằm ở Viết yêu cầu cho AI sao cho nó làm đúng và không bịa.



