---
title: "Dùng Claude API trong sản phẩm của bạn: gọi hàm, hiện chữ dần và tiết kiệm tiền"
url: https://phatdev.net/vi/blog/claude-api-tool-use-streaming-caching
lang: vi
updated: 2026-10-21
alternate:
  en: https://phatdev.net/en/blog/claude-api-tool-use-streaming-caching
markdown: https://phatdev.net/vi/blog/claude-api-tool-use-streaming-caching.md
---

# Dùng Claude API trong sản phẩm của bạn: gọi hàm, hiện chữ dần và tiết kiệm tiền

> Cho AI gọi hàm của bạn, cho chữ hiện dần thay vì màn hình trống, và đừng trả tiền nhiều lần cho cùng một đoạn. Model đúng tháng 10/2026, kèm code Python và TypeScript.

Chuyên mục: AI | Đăng: 2026-10-19 | Cấp độ: Trung cấp | 8 phút đọc

Tags: claude, anthropic, tool-use, streaming, prompt-caching

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.

![Vòng lặp gồm mô hình, công cụ và kết quả nối nhau bằng mũi tên, bên phải là một dòng khối nhỏ chảy ra như luồng streaming](https://phatdev.net/uploads/blog/claude-api-tool-use-streaming-caching/tool-loop-1920.webp)

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"`.

![Chồng các tấm isometric, ba tấm dưới ổn định sáng lên như phần được cache, tấm mỏng trên cùng đổi màu như phần thay đổi](https://phatdev.net/uploads/blog/claude-api-tool-use-streaming-caching/cache-prefix-1920.webp)

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):

```python
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ữ:

```python
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)
```

```typescript
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](https://phatdev.net/vi/blog/agentic-workflow-orchestrator). 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](https://phatdev.net/vi/blog/prompt-engineering-for-developers).

## Nguồn tham khảo

- [Claude docs: Models overview](https://platform.claude.com/docs/en/about-claude/models/overview)
- [Claude docs: Tool use overview](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview)
- [Claude docs: Streaming Messages](https://platform.claude.com/docs/en/build-with-claude/streaming)
- [Claude docs: Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
- [Claude docs: Pricing](https://platform.claude.com/docs/en/about-claude/pricing)
- [Claude docs: Errors](https://platform.claude.com/docs/en/api/errors)
- [anthropic-sdk-python (GitHub)](https://github.com/anthropics/anthropic-sdk-python)
- [anthropic-sdk-typescript (GitHub)](https://github.com/anthropics/anthropic-sdk-typescript)

## Bài liên quan

- [Viết yêu cầu cho AI sao cho nó làm đúng và không bịa](https://phatdev.net/vi/blog/prompt-engineering-for-developers.md)
- [Giao việc lớn cho nhiều AI cùng làm: cách chia việc để không hỏng](https://phatdev.net/vi/blog/agentic-workflow-orchestrator.md)
- [Claude, Codex và Gemini: chọn trợ lý AI viết code nào cho việc gì](https://phatdev.net/vi/blog/claude-codex-gemini-for-coding.md)
