Structured Outputs & Tool Calling 2026 - Kiến trúc JSON Schema, Constrained Decoding, XGrammar và Tool Registry cho Multi-Agent AI với Redis và ClickHouse
Posted on: 4/15/2026 1:12:47 AM
Table of contents
- 1. Vì sao Structured Outputs trở thành tầng bắt buộc của multi-agent 2026
- 2. Ba thế hệ tool calling — từ "xin prompt" tới grammar có ràng buộc
- 3. Kiến trúc Structured Output Pipeline
- 3.1. Schema Registry — nguồn sự thật cho mọi tool
- 3.2. Prompt Builder — tách rời system prompt và tool definitions
- 3.3. Model Call Layer — nơi provider khác biệt
- 3.4. Decoder Constraint Layer — nơi JSON không bao giờ gãy
- 3.5. Validator & Self-Healer — khi grammar không đủ
- 3.6. Dispatcher & Telemetry — mỗi tool call là một sự kiện
- 4. Tool Registry và tích hợp với MCP
- 5. Parallel tool calls, tool_choice và chiến lược "chỉ đạo" model
- 6. Redis và ClickHouse — lớp state và lớp analytic của tool calling
- 7. Framework phía client — chọn gì khi bắt đầu hôm nay
- 8. Các failure mode thường gặp và cách phòng
- 9. Kinh tế của Structured Outputs — chi phí, độ trễ, ROI
- 10. Checklist sản xuất cho Structured Output stack
- 11. Hướng phát triển — điều gì sẽ thay đổi trong 12 tháng tới
- 12. TL;DR
- Nguồn tham khảo
Bất kỳ ai từng vận hành một hệ thống multi-agent trong sản xuất đều biết một sự thật đau đớn: mô hình giỏi nhất thế giới vẫn sẽ phá JSON của bạn lúc 2 giờ sáng. Một ký tự ngoặc lạc chỗ, một chuỗi trailing comma, một trường null không được khai báo đủ là có thể làm toàn bộ chuỗi tool call đổ vỡ, trigger retry dây chuyền, đốt token và gây ra incident thật. Đây chính là lý do Structured Outputs và Tool Calling — vốn trước đây là tính năng "cho đẹp" của các SDK — đã trở thành một lớp kiến trúc sản xuất độc lập trong năm 2026. Bài viết đi sâu vào toàn bộ ngăn xếp công nghệ ở lớp này: từ JSON Schema, Pydantic model, constrained decoding bằng Outlines/XGrammar/llguidance, cho tới Tool Registry, parallel tool call, self-healing retry, Redis cache cho schema và ClickHouse telemetry cho mọi tool call trong agent trajectory.
1. Vì sao Structured Outputs trở thành tầng bắt buộc của multi-agent 2026
Mọi multi-agent đều phải đối mặt với một nghịch lý cơ bản: LLM sinh văn bản tự do, trong khi phần còn lại của hệ thống — database, API nội bộ, message bus, UI — chỉ hiểu cấu trúc. Cầu nối giữa hai thế giới đó là một chuỗi biến đổi: lời văn của mô hình phải trở thành một object JSON có schema rõ ràng, sau đó được validate, dispatch tới hàm xử lý và kết quả lại phải quay ngược về mô hình dưới dạng thông điệp có ngữ cảnh. Khi chuỗi này gãy ở bất cứ đâu, agent ngừng tiến triển và bắt đầu "nói chuyện một mình" với các retry vô nghĩa.
Suốt giai đoạn 2023-2024, đội kỹ thuật thường giải quyết bằng cách "xin" mô hình trong prompt: "Trả lời chỉ bằng JSON, đừng kèm văn bản nào khác". Cách này hoạt động tới khoảng 95% traffic — và 5% còn lại là đủ để gây tê liệt một pipeline agent ở quy mô sản xuất. Năm 2026, lớp Structured Outputs đã thay thế hoàn toàn chiến lược "xin" bằng ba cơ chế cứng: provider-level JSON mode, constrained decoding dựa trên grammar, và tool calling chuẩn hoá được validate ở cả client và server.
Cú chuyển dịch rõ nhất nằm ở chỗ: tool call không còn là "một cách khác để trả lời". Nó đã trở thành bộ mặt chính của mọi agent. Một request điển hình của Claude Opus 4.6 trong Claude Code không kết thúc bằng một tin nhắn — nó kết thúc bằng một chuỗi tool_use block gọi Read, Grep, Edit, Bash liên tiếp, rồi nhận tool_result và tiếp tục. Cả trajectory của agent là một đồ thị tool call được kết nối bởi các đoạn "reasoning" ngắn ở giữa. Khi đó, chất lượng của cả hệ thống phụ thuộc tuyệt đối vào việc mọi tool call phải sinh ra đúng cấu trúc.
Bài học từ incident phổ biến năm 2025
Một đội engineering đã mất 12 giờ để truy vết lý do agent "treo" — và phát hiện rằng model đang sinh ra một chuỗi JSON có trường "user_id" kiểu số nhưng trong 0.3% trường hợp lại bọc trong dấu nháy: "user_id": "42". Validator kỹ tính bắt lỗi, retry, model lặp lại y hệt, agent không thoát khỏi vòng lặp. Đây là loại lỗi mà constrained decoding loại bỏ từ gốc — model không còn sinh được chuỗi nháy vì grammar không cho phép ở vị trí đó.
2. Ba thế hệ tool calling — từ "xin prompt" tới grammar có ràng buộc
Để hiểu vì sao stack 2026 trông như hiện nay, cần nhìn lại ba thế hệ cơ chế ràng buộc đầu ra đã từng song hành:
Sự thật không mấy được nói tới: cả ba thế hệ vẫn đồng tồn tại trong một hệ thống sản xuất năm 2026. Một đội kỹ thuật thông minh không chọn một cái duy nhất — họ dùng cả ba theo chiều sâu phòng thủ. Prompt engineering làm phần định hướng hành vi, provider-level JSON mode làm lớp validation cơ sở, và constrained decoding làm lớp cứng ở tầng decoder khi chất lượng là tuyệt đối (ví dụ với các hàm gọi vào CRM thanh toán, database production).
3. Kiến trúc Structured Output Pipeline
Một pipeline tool calling sản xuất 2026 nên được nhìn như một mạch nhỏ gồm sáu trạm: (1) Schema Registry, (2) Prompt Builder, (3) Model Call Layer, (4) Decoder Constraint Layer, (5) Validator & Self-Healer, (6) Dispatcher & Telemetry. Mỗi trạm có trách nhiệm hẹp, có thể thay thế độc lập, và phải có metric riêng chảy vào ClickHouse.
graph LR
User["User / Upstream Agent"] --> Orchestrator["Agent Orchestrator"]
Orchestrator --> SchemaReg["Schema Registry
(Redis)"]
SchemaReg --> PromptB["Prompt Builder
(system + tools)"]
PromptB --> Model["Model Call Layer
Claude / GPT / Llama"]
Model --> Decoder["Constraint Decoder
XGrammar / Outlines"]
Decoder --> Validator["Validator & Self-Healer
Pydantic / zod"]
Validator -->|"valid"| Dispatcher["Tool Dispatcher
MCP / HTTP / RPC"]
Validator -->|"invalid"| Retry["Retry w/ error context"]
Retry --> Model
Dispatcher --> Telemetry["ClickHouse Telemetry"]
Dispatcher --> ResultCache["Result Cache
(Redis)"]
ResultCache --> Orchestrator
style SchemaReg fill:#e94560,stroke:#fff,color:#fff
style Decoder fill:#0f3460,stroke:#fff,color:#fff
style Validator fill:#4CAF50,stroke:#fff,color:#fff
style Telemetry fill:#f39c12,stroke:#fff,color:#fff
3.1. Schema Registry — nguồn sự thật cho mọi tool
Ở các đội nhỏ, schema thường nằm rải rác trong code: một Pydantic model trong module này, một zod schema trong module kia, một openapi.yaml ở repo thứ ba. Ở quy mô sản xuất, cách này dẫn tới drift: cùng một "tool" có hai schema khác nhau theo hai ngôn ngữ triển khai. Giải pháp 2026 là Schema Registry tập trung, nơi mọi tool định nghĩa một lần duy nhất (thường là JSON Schema hoặc GraphQL IDL) và mọi client sinh code từ đó.
Redis được dùng làm cache vì hai đặc tính: (a) mọi agent cần đọc schema ở mỗi request mới, và (b) schema thay đổi rất chậm nhưng phải được propagate cực nhanh khi đổi. Cấu trúc key điển hình: schema:{tool_name}:v{version}, với TTL dài (1 giờ) và một PUBSUB channel để phát tín hiệu invalidation khi có phiên bản mới. Metadata phụ (schema hash, compiled grammar cho XGrammar) được lưu song song dưới schema:{tool_name}:{version}:grammar để tránh compile lại.
3.2. Prompt Builder — tách rời system prompt và tool definitions
Một sai lầm phổ biến là đặt mô tả tool thẳng trong system prompt. Điều đó làm hai việc phá cache: thứ nhất, mọi thay đổi nhỏ ở tool đều invalidate prefix cache của provider (Anthropic, OpenAI, Gemini đều cache theo prefix); thứ hai, nó không tận dụng được cấu trúc tools có sẵn trong API. Chiến lược đúng là giữ system prompt bất biến và truyền tool definitions qua trường tools của API — hầu hết provider tối ưu hoá trường này riêng (Anthropic đưa vào prompt cache, OpenAI nén thành format nội bộ, Gemini đặt sau content cache).
Prompt cache và tool definitions
Nếu bạn đang viết Claude agent: đặt toàn bộ danh sách tool đầu tiên, system prompt tiếp theo, sau đó là history, và cuối cùng là câu hỏi hiện tại. Thứ tự này tối đa hoá prefix cache — toàn bộ phần tool definition trở thành phần cached và chỉ user turn là phần "mới" tính tiền đầy đủ. Ở quy mô 1M token/ngày, chênh lệch chi phí có thể lên tới 70%.
3.3. Model Call Layer — nơi provider khác biệt
Đây là trạm nơi khác biệt giữa các provider thể hiện rõ nhất. Không có API chung — mỗi nhà cung cấp có một cách riêng để trình bày tools, tool_choice và cách nhận về kết quả. Một LLM Gateway (đã nói tới ở bài 1022) thường nuốt hết khác biệt này, nhưng khi tự viết bạn cần hiểu rõ bảng dưới đây:
| Provider | Định dạng tool | Strict mode | Parallel tool calls | Tool choice |
|---|---|---|---|---|
| Anthropic Claude | tools array với JSON Schema trong input_schema | Không strict tên gọi; luôn validate JSON Schema phía client | Hỗ trợ gốc qua nhiều tool_use block trong một response | auto, any, tool, none |
| OpenAI GPT | tools array với function wrapper | Structured Outputs strict mode — tuân thủ 100% schema | Có (bật mặc định) | auto, required, none, specific function |
| Google Gemini | function_declarations trong tools | responseSchema cho structured output | Có, qua function_calling_config | AUTO, ANY, NONE |
| Open-source (Llama 3.3, Mistral, Qwen) | Không chuẩn hoá — phải dùng template như Hermes, ChatML | Không có. Phải tự bật constrained decoding (XGrammar/Outlines) | Tuỳ template; thường cần parse thủ công | Tự áp qua grammar |
3.4. Decoder Constraint Layer — nơi JSON không bao giờ gãy
Đây là thành phần mà các đội kỹ thuật hay bỏ qua cho tới khi vận hành open-source model. Ý tưởng của constrained decoding rất đơn giản: tại mỗi bước sinh token, đọc trạng thái hiện tại của output, tính xem theo grammar (JSON Schema biên dịch thành automaton) thì token nào là hợp lệ, và mask toàn bộ token không hợp lệ thành -infinity trong vector logits trước khi sampling. Kết quả: mô hình không còn "chọn" được một dấu phẩy ở nơi cần dấu ngoặc, không thể sinh khoá lạ, không thể bỏ quên trường bắt buộc.
Ba thư viện đáng để hiểu sâu:
| Thư viện | Cơ chế chính | Overhead/token | Ngôn ngữ | Tích hợp |
|---|---|---|---|---|
| Outlines | Biên dịch regex/JSON Schema thành FSM; cache transition trong runtime | ~5-20ms ở v0 ban đầu, <2ms sau tối ưu | Python | vLLM, SGLang, TGI, Transformers |
| XGrammar | Biên dịch grammar thành pushdown automaton; precompute token mask batch | Dưới 1ms ở cấu hình thông thường — 3-7x nhanh hơn Outlines v0 | C++ core, Python binding | MLC-LLM, SGLang, vLLM, TensorRT-LLM |
| llguidance | Lexer-aware parser, chuyên cho JSON và "guidance" syntax (AICI) | Sub-millisecond ở most cases | Rust, Python/Node binding | Microsoft Guidance, llama.cpp, AICI |
| lm-format-enforcer | Character-level enforcer, không cần biên dịch trước | Trung bình, đơn giản hơn Outlines nhưng chậm hơn XGrammar | Python | vLLM, Transformers, llama-cpp |
Tại sao XGrammar đã trở thành mặc định ở nhiều vLLM deployment
Hai tối ưu chính: (1) adaptive token mask cache — lưu lại mask cho mỗi state của automaton và chia sẻ giữa các request có cùng grammar, (2) batch mask computation — tận dụng GPU để tính mask song song cho cả batch. Kết quả là grammar masking gần như không còn là bottleneck, trong khi với Outlines v0 nó từng chiếm 15-30% thời gian forward pass.
3.5. Validator & Self-Healer — khi grammar không đủ
Constrained decoding chỉ đảm bảo cú pháp, không đảm bảo ngữ nghĩa. Một tool call có thể đúng schema nhưng vẫn sai: email không hợp lệ, user_id không tồn tại, amount âm. Validator phải chạy một pass thứ hai với Pydantic (Python), zod (TypeScript), hoặc Go struct + validator. Self-healer là phần đọc lỗi, đóng gói thành thông điệp ngữ cảnh, và gửi lại model kèm tool_result có is_error: true. Claude, GPT-4 và Gemini đều xử lý rất tốt pattern này — một vòng retry với lỗi cụ thể thường đủ.
Chiến lược được dùng nhiều trong Claude Code là "one-shot self-heal": cho phép tối đa 1 retry cho mỗi tool call, và nếu vẫn lỗi thì thoát lên Orchestrator để quyết định (thử tool khác, yêu cầu user, hoặc bỏ cuộc). Điều này tránh được vòng lặp vô tận mà vẫn tận dụng khả năng tự sửa của mô hình lớn.
3.6. Dispatcher & Telemetry — mỗi tool call là một sự kiện
Trạm cuối cùng là nơi mọi tool call trở thành một sự kiện được phát lên hai kênh song song: (a) bus message (Kafka/Redis Streams) để các agent khác hoặc workflow engine có thể tiêu thụ, và (b) ClickHouse để lưu trữ phục vụ phân tích lâu dài. Sự kiện tối thiểu nên chứa: trace_id, span_id, agent_id, tool_name, tool_version, input_hash, output_hash, latency_ms, tokens_in, tokens_out, model, retry_count, error_code.
4. Tool Registry và tích hợp với MCP
Một trong những tiến hoá quan trọng của 2026 là MCP (Model Context Protocol) đã trở thành cách chuẩn để phát hiện và gọi tool bên ngoài. Trong kiến trúc MCP, bản thân tool không "sống" trong prompt mà được host bởi một MCP server độc lập, và agent chỉ khai báo rằng nó muốn "gắn" một hoặc nhiều server. Schema của tool được lấy từ server tại runtime — tức là Tool Registry trong kiến trúc trên chính là tầng "MCP client side" bọc quanh các MCP server.
sequenceDiagram
participant Agent
participant Registry as Tool Registry
participant Redis
participant MCP as MCP Server
participant Model
Agent->>Registry: list_tools(session)
Registry->>Redis: GET schema:weather:v2
alt Cache miss
Registry->>MCP: tools/list
MCP-->>Registry: schemas[]
Registry->>Redis: SET schema:weather:v2 TTL 1h
end
Redis-->>Registry: schemas
Registry-->>Agent: compiled tools
Agent->>Model: completion(tools, messages)
Model-->>Agent: tool_use(weather, {city:"Hanoi"})
Agent->>MCP: tools/call weather
MCP-->>Agent: structured result
Agent->>Model: tool_result + next turn
Lợi ích của pattern này: bạn tách bạch code logic của tool khỏi code agent. Bất kỳ đội nào viết MCP server mới (Slack, Notion, CI, Jira, custom CRM) đều có thể gắn vào multi-agent mà không phải chạm code agent. Schema change được propagate qua Redis PUBSUB. Và cùng một MCP server có thể phục vụ nhiều agent khác nhau — Claude Code, IDE plugin, sub-agent trong workflow — với cùng một semantic.
5. Parallel tool calls, tool_choice và chiến lược "chỉ đạo" model
Một tính năng đã trở thành mặc định năm 2026 là parallel tool calls: model sinh nhiều tool_use block trong một turn duy nhất, và Agent Orchestrator thực thi chúng song song. Claude Opus 4.6, GPT-4.x, Gemini 2.0 đều hỗ trợ gốc. Lợi ích rõ rệt khi model cần thông tin độc lập từ nhiều nguồn: gọi Read ba file cùng lúc thay vì ba turn tuần tự có thể cắt latency tổng từ 9 giây xuống 3.5 giây.
Tuy nhiên parallel tool call cũng kéo theo ba vấn đề thiết kế:
- Dependency between calls — nếu call B cần kết quả của call A, model không được song song hoá chúng. Agent Orchestrator phải phát hiện và giữ tuần tự.
- Failure semantic — nếu 1 trong 3 call fail, toàn bộ batch fail hay báo một phần? Pattern phổ biến là "all-or-nothing" cho các call liên quan (ghi CSDL) và "best-effort" cho các call thuần đọc (Read, Search).
- Timeout budget — tổng thời gian không được vượt quá timeout của toàn turn. Nếu một call chậm kéo theo 4 call nhanh, nên cancel nó và báo lại cho model.
tool_choice là công cụ quan trọng nhất để chỉ đạo model. Dùng tool_choice: {"type": "tool", "name": "submit_form"} khi bạn biết chắc bước tiếp theo phải gọi một tool cụ thể (ví dụ end-of-flow form submission); dùng any khi bạn muốn ép model gọi ít nhất một tool (chống "dạy đời" thay vì hành động); và dùng none cho summary turn cuối cùng. Ba chế độ này khi kết hợp có thể thay thế được phần lớn logic "flow control" phải viết tay.
6. Redis và ClickHouse — lớp state và lớp analytic của tool calling
Cả hai thành phần này đều xuất hiện ở mọi bài trong series trước, nhưng chúng đóng vai trò rất cụ thể trong Structured Output stack:
graph TB
subgraph RedisLayer["Redis — Hot State"]
SR["schema:{tool}:v{n}"]
SG["schema:{tool}:v{n}:grammar"]
RC["toolcall:{hash} → result"]
IL["idempotency:{key} → state"]
RL["ratelimit:{agent}:{tool}"]
end
subgraph ClickHouseLayer["ClickHouse — Cold Analytic"]
TC["tool_calls table
(trace_id, span_id, tool, latency, tokens)"]
TR["tool_results table
(hash, size, validation_error)"]
AT["agent_trajectory view
(joined by trace_id)"]
DQ["daily_tool_quality MV
(success_rate, p95_latency)"]
end
Pipeline["Structured Output Pipeline"] --> RedisLayer
Pipeline --> ClickHouseLayer
ClickHouseLayer -.->|"quality feedback"| RedisLayer
style RedisLayer fill:#e94560,stroke:#fff,color:#fff
style ClickHouseLayer fill:#f39c12,stroke:#fff,color:#fff
6.1. Redis — schema cache, idempotency key và rate limit
Trên Redis, ba use case bắt buộc:
- Schema cache và grammar cache: lưu JSON Schema cùng bản biên dịch grammar (XGrammar) để tránh compile lại 20-50ms mỗi request.
- Tool result cache: đối với các tool đọc idempotent (Read file, Search, Query), hash input và lưu output. Hit rate thực tế 30-60% cho một agent coding. Dùng TTL động — ngắn cho volatile data (30 giây), dài cho reference (1 giờ).
- Idempotency key: với các tool ghi (UpdateDB, SendEmail), Redis lưu state của từng operation bằng
SET NX EXđể đảm bảo một tool call chỉ thực thi đúng một lần dù model có retry — điều cực quan trọng khi có self-healing retry ở tầng trên.
6.2. ClickHouse — telemetry mọi tool call, mọi trajectory
ClickHouse là nơi mọi sự kiện tool call đổ về dưới dạng dòng. Schema bảng tối thiểu:
CREATE TABLE tool_calls (
ts DateTime64(3),
trace_id String,
span_id String,
parent_span_id String,
agent_id LowCardinality(String),
tool_name LowCardinality(String),
tool_version LowCardinality(String),
model LowCardinality(String),
input_hash FixedString(32),
output_hash FixedString(32),
input_size UInt32,
output_size UInt32,
tokens_in UInt32,
tokens_out UInt32,
latency_ms UInt32,
retry_count UInt8,
error_code LowCardinality(String),
strict_mode UInt8,
cache_hit UInt8
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(ts)
ORDER BY (agent_id, tool_name, ts);Materialized view daily_tool_quality tổng hợp mỗi ngày theo (tool_name, tool_version), sinh ra các chỉ số: success rate, p95 latency, average retry count, cache hit ratio. Bảng này là nguồn sự thật để quyết định: (a) có nên promote version mới của tool không, (b) có nên tắt một tool bị lỗi trên diện rộng không, (c) có nên điều chỉnh timeout hay retry policy không. Trong kiến trúc sản xuất 2026, không có đội nào điều hành tool calling nghiêm túc mà không có lớp analytic này.
7. Framework phía client — chọn gì khi bắt đầu hôm nay
Khi bắt đầu viết một agent mới, câu hỏi đầu tiên không phải là "dùng LangChain hay không" mà là layer nào sẽ chịu trách nhiệm gì. Có ít nhất năm framework quan trọng trên landscape 2026, mỗi cái giải bài toán hơi khác:
| Framework | Vai trò chính | Điểm mạnh | Điểm cần cân nhắc |
|---|---|---|---|
| Instructor | Wrap OpenAI/Anthropic SDK để ép output thành Pydantic model | Cực đơn giản, self-healing tự động, hỗ trợ hầu hết provider | Không cover MCP tool calling; không có telemetry mặc định |
| Outlines | Constrained decoding cho open-source model | Hỗ trợ regex, JSON Schema, CFG; tích hợp vLLM/Transformers | Overhead cao hơn XGrammar; tập trung mô hình nhỏ local |
| BAML | DSL riêng để định nghĩa prompt + schema + codegen | Type-safe mạnh ở cả Python, TS, Go; test harness rõ | Cần học thêm DSL; community nhỏ hơn |
| LiteLLM (tool call) | Gateway thống nhất tool call API giữa 100+ provider | Đổi provider không đổi code; middleware retry/log | Không có constrained decoding; không tự validate schema |
| LangChain / LangGraph | Orchestration graph + nút tool | Có sẵn pattern ReAct, plan-and-execute; nhiều tích hợp | Abstraction nặng; đội production thường bypass phần tool calling của nó |
Công thức được dùng nhiều nhất trong sản xuất 2026
Python: Instructor + Pydantic cho logic tool call, LiteLLM làm gateway đa provider, MCP client cho tool discovery, XGrammar khi chạy model open-source, và một LLM Gateway tự viết (hoặc Helicone/Langfuse proxy) ở tầng trên để tập trung telemetry. Không dùng một framework "all-in-one" — tách thành các layer nhỏ để thay thế được.
8. Các failure mode thường gặp và cách phòng
Dưới đây là danh mục các lỗi hay gặp khi vận hành Structured Output trong sản xuất, kèm cách giảm thiểu có hiệu quả thực sự:
- "Halucinated tool name" — model gọi một tool không tồn tại trong danh sách. Gặp nhiều khi tool definitions quá dài và bị cắt ngữ cảnh. Giải: giữ danh sách tool dưới 30 nếu có thể, phân tầng agent theo chuyên môn, và dùng
tool_choice: {"type": "any"}khi ép gọi một tool hợp lệ. - "Required field missing" — model gọi đúng tool nhưng thiếu field bắt buộc. Giải: dùng strict mode của provider; với open-source bật XGrammar; ở validator trả lỗi rõ tên field để self-heal.
- "Type coercion loop" — model cứ liên tục gửi "42" thay vì 42. Giải: kiểm tra prompt không có ví dụ few-shot sai type; bật strict mode; trong validator dùng
strict=Trueở Pydantic. - "Over-calling" — model gọi tool liên tục dù đã đủ thông tin. Giải: dùng
stop_sequenceshoặctool_choice: "none"cho turn summary; giám sátretry_counttrên ClickHouse; đặt budget cho mỗi trajectory. - "Tool result too big" — model gọi tool trả về 200KB rồi tiếp tục với context bị thổi phồng. Giải: truncate output ở dispatcher (ví dụ Read trả tối đa 2000 dòng), đồng thời lưu full output vào object store và chỉ tham chiếu trong context.
- "Parallel race" — hai tool call song song ghi vào cùng resource. Giải: Redis idempotency key; locking bằng
SET NX; hoặc serialize ở dispatcher khi phát hiện cùng resource.
9. Kinh tế của Structured Outputs — chi phí, độ trễ, ROI
Một sai lầm hay gặp khi đánh giá: chỉ nhìn vào latency của constrained decoding và kết luận "bật strict mode làm chậm model". Thực tế, bức tranh đúng là:
| Chỉ số | Không có structured output | Có strict + cache + telemetry | Chênh lệch |
|---|---|---|---|
| P95 latency (1 tool call) | 850 ms | 920 ms | +8% (overhead grammar) |
| P95 latency (1 trajectory full) | 14.2 s | 9.8 s | -31% (ít retry hơn) |
| Chi phí token/trajectory | $0.047 | $0.029 | -38% |
| Tỉ lệ trajectory thành công | 92% | 99.3% | +7.3 điểm phần trăm |
| Chi phí/trajectory thành công | $0.051 | $0.029 | -43% |
Con số quan trọng nhất nằm ở dòng cuối: chi phí trên mỗi trajectory thành công. Đây mới là phép đo đúng, vì một trajectory fail rồi retry ở tầng Orchestrator tốn toàn bộ token của lần fail cộng lần retry. Khi giảm tỉ lệ fail từ 8% xuống 0.7%, bạn vừa cắt chi phí retry vừa cải thiện trải nghiệm người dùng. Đó là lý do structured output không phải là "tính năng tốc độ" mà là "tính năng ROI".
10. Checklist sản xuất cho Structured Output stack
10 điều cần có trước khi go-live
- Mọi tool có schema JSON Schema được quản lý trong Schema Registry, không nhúng vào code agent.
- Prompt cache được tối đa hoá: tool definitions đặt ở vị trí prefix bất biến.
- Strict mode được bật cho mọi provider hỗ trợ; open-source bật XGrammar hoặc Outlines.
- Validator (Pydantic/zod) chạy một pass độc lập sau grammar decoding.
- Self-healing tối đa 1 retry/tool call, với error message cụ thể gửi lại model.
- Dispatcher có timeout và idempotency key (Redis
SET NX EX) cho mọi ghi. - Tool output được truncate tại dispatcher; full output lưu object store.
- Mọi tool call đổ sự kiện vào ClickHouse kèm
trace_id,tool_version,retry_count. - Materialized view daily_tool_quality chạy nightly; alert khi success_rate < 98%.
- Agent Orchestrator có budget trajectory (số turn tối đa, token tối đa) độc lập với budget tool call.
11. Hướng phát triển — điều gì sẽ thay đổi trong 12 tháng tới
Dưới đây là bốn xu hướng đáng theo dõi nhất, dựa trên trajectory nghiên cứu và sản phẩm cuối 2025 đầu 2026:
- Grammar-aware pretraining — mô hình được huấn luyện từ đầu với awareness về JSON/XML grammar, giảm nhu cầu constrained decoding ở tầng decoder. Claude, Gemini và vài model open-source đã có dấu hiệu đi theo hướng này.
- Sparse tool routing — thay vì truyền cả trăm tool vào mỗi request, agent dùng embedding retrieval để chọn top-K tool khả dĩ nhất. Kết hợp với MCP để discovery động, số tool được truyền thực sự giảm xuống 5-15 mỗi turn.
- Deterministic tool replay — ClickHouse lưu toàn bộ input/output tool call cho phép replay deterministic một trajectory để debug hoặc A/B test prompt mới mà không cần gọi tool thật. Một vài startup đang biến điều này thành sản phẩm core.
- Formal verification cho schema — tool schema được kiểm tra tự động về tính nhất quán (cột nào buộc, cột nào optional, default value), kèm property-based testing tự sinh để catch edge case trước khi lên production.
12. TL;DR
Structured Outputs và Tool Calling không còn là "API quality-of-life" mà đã trở thành một tầng kiến trúc sản xuất độc lập trong mọi hệ thống multi-agent nghiêm túc năm 2026. Stack chuẩn gồm: Schema Registry tập trung, Prompt Builder tôn trọng prompt cache, Model Call Layer đa provider, Constraint Decoder (XGrammar/Outlines) cho open-source, Validator + Self-Healer, Dispatcher có idempotency và ClickHouse telemetry. Kết quả không chỉ là "JSON hợp lệ hơn" mà là chi phí trên mỗi trajectory thành công giảm 30-45%, latency tổng giảm, và tỉ lệ thành công vượt 99%. Đội nào xây dựng multi-agent năm 2026 mà không có lớp này vẫn đang chảy máu ngân sách — và không biết vì sao.
Nguồn tham khảo
- Anthropic — Tool use with Claude (docs.anthropic.com)
- OpenAI — Structured Outputs guide
- Google — Gemini Function Calling & responseSchema
- XGrammar — flexible and portable structured generation
- Outlines — Structured text generation
- llguidance — lexer-aware grammar enforcement
- lm-format-enforcer
- Instructor — Pydantic is all you need
- BAML — a typed DSL for LLM functions
- Model Context Protocol (MCP) specification
- ClickHouse MergeTree reference
- Redis — SET command and NX/EX options
Agent Router & Model Cascading 2026 - Kiến trúc Routing Thông minh cho Multi-Agent với Semantic Classifier, Bandit Feedback, Redis và ClickHouse
DSPy 2026 - Lập trình LLM thay vì Prompt Engineering với Signatures, Modules, MIPROv2 và BootstrapFinetune cho Multi-Agent Production
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.