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
Posted on: 4/15/2026 12:11:58 AM
Table of contents
- 1. Vì sao mọi multi-agent đều cần một router ở tầng đầu
- 2. Phân loại router — từ rule đơn giản tới bandit học online
- 3. Semantic classifier — trái tim của router hiện đại
- 4. Cascade routing — khi routing đơn thuần chưa đủ
- 5. Bandit feedback — router tự học từ kết quả thật
- 6. Redis làm state store cho router — tại sao đây là lựa chọn đúng
- 7. ClickHouse — backbone analytics cho router
- 8. Pipeline hoàn chỉnh — ghép các mảng vào một request thực tế
- 9. Token budget & cost governance — chặng cuối cùng
- 10. Router cho Claude Code và multi-agent framework khác
- 11. Drift, retraining và lifecycle của router
- 12. So sánh các framework router mã nguồn mở 2026
- 13. Checklist đưa Agent Router vào production
- 14. Tổng kết
- Nguồn tham khảo
Một trong những nghịch lý lớn nhất của kiến trúc multi-agent năm 2026 là ở chỗ: chi phí LLM không tăng vì người dùng hỏi nhiều hơn, mà vì mọi câu hỏi đều được gửi tới mô hình to nhất. Một câu "chào buổi sáng" tốn cùng số token tới Claude Opus 4.6 như một yêu cầu phân tích log phức tạp. Một truy vấn FAQ lặp đi lặp lại vẫn đi qua cả pipeline reasoning. Đây là lý do một lớp kiến trúc mới — Agent Router — đã trở thành thành phần gần như bắt buộc trong mọi hệ thống LLM sản xuất: nó là "bộ não đứng trước bộ não", chọn đúng mô hình, đúng độ sâu suy luận, đúng ngân sách token cho từng request. Bài viết đi sâu vào kiến trúc router hiện đại năm 2026 — từ semantic classifier, multi-armed bandit, cascade routing, cho tới cách đặt Redis làm state store, ClickHouse làm backbone telemetry và đóng gói toàn bộ thành một layer có thể đặt trước bất kỳ multi-agent framework nào.
1. Vì sao mọi multi-agent đều cần một router ở tầng đầu
Khi hệ thống chỉ chạy một mô hình duy nhất, câu chuyện rất đơn giản: mọi request đều đi thẳng xuống mô hình đó. Nhưng kiến trúc sản xuất năm 2026 gần như không còn dự án nào chỉ dùng một LLM. Một team điển hình kết hợp Claude Opus 4.6 cho reasoning phức tạp, Claude Sonnet 4.6 cho tác vụ thông thường, Claude Haiku 4.5 cho latency cực thấp, GPT-4o cho đa phương tiện, một mô hình open-source (Llama 3.3, Mistral) cho các luồng nhạy cảm về dữ liệu, và vài mô hình embedding chuyên biệt. Không có router, toàn bộ traffic đổ về một mô hình — hoặc tệ hơn: nhân viên tự chọn thủ công.
Điểm đau thực sự lộ ra khi nhìn vào phân phối request. Phân tích log LLM của các đội engineering chuyên nghiệp cho thấy một pattern rất ổn định: khoảng 60-70% request đơn giản tới mức mô hình rẻ nhất cũng trả lời hoàn hảo, 20-25% cần reasoning trung bình, và chỉ 5-15% thực sự cần mô hình đầu bảng. Nếu bạn đang gửi tất cả tới Opus, bạn đang đốt 80% ngân sách cho 15% lợi ích.
graph LR
Users["User requests
100%"] --> Router["Agent Router"]
Router -->|"~65%
greeting, FAQ, format"| Cheap["Haiku 4.5
$0.25/1M in"]
Router -->|"~25%
general chat, code edit"| Mid["Sonnet 4.6
$3/1M in"]
Router -->|"~10%
deep reasoning, research"| Big["Opus 4.6
$15/1M in"]
Cheap --> Metrics["Telemetry
Redis + ClickHouse"]
Mid --> Metrics
Big --> Metrics
Metrics -.->|"reward signal"| Router
style Router fill:#e94560,stroke:#fff,color:#fff
style Metrics fill:#f39c12,stroke:#fff,color:#fff
style Cheap fill:#4CAF50,stroke:#fff,color:#fff
style Mid fill:#0f3460,stroke:#fff,color:#fff
style Big fill:#6610f2,stroke:#fff,color:#fff
Router không chỉ cắt chi phí. Nó cắt latency cho các truy vấn dễ (Haiku trả lời trong vài trăm mili-giây trong khi Opus mất vài giây), cắt rủi ro rate-limit trên mô hình đầu bảng (traffic đơn giản không bao giờ chạm tới đó), và trở thành điểm kiểm soát chính sách: một chỗ duy nhất để quyết định ai được dùng mô hình nào, theo budget nào, qua chặn nào.
2. Phân loại router — từ rule đơn giản tới bandit học online
Các router không sinh ra bình đẳng. Có ít nhất năm lớp tiếp cận đang song song tồn tại trong sản xuất năm 2026, và chúng hợp lý trong những ngữ cảnh khác nhau:
| Lớp router | Cơ chế quyết định | Training data | Độ trễ | Phù hợp |
|---|---|---|---|---|
| Rule-based | Regex, keyword, length threshold | Không cần | <0.5 ms | MVP, luồng có format biết trước |
| Embedding k-NN | Tìm prompt gần nhất, mượn nhãn model | Vài trăm cặp (prompt → model thắng) | 1-3 ms | Bắt đầu nhanh, không có dataset preference |
| BERT/ModernBERT classifier | Fine-tune classifier phân loại 2-N lớp | Vài nghìn đến vài chục nghìn label | 5-15 ms (GPU) | Sản xuất ổn định, domain tương đối cố định |
| Preference-trained (RouteLLM) | Học từ preference data, điều khiển bằng cost threshold | Chatbot Arena + augmentation | 2-10 ms | Team có preference log hoặc chấp nhận public data |
| Bandit / Neural UCB | Cập nhật online dựa trên reward thực tế | Không cần cố định; học liên tục | 3-8 ms | Traffic lớn, nhu cầu thích nghi nhanh với model mới |
Trên thực tế, hầu hết team sản xuất chín chắn dùng kiến trúc pha trộn: rule đi đầu để bắt các case rõ ràng (từ khóa nhạy cảm, độ dài thấp, API call cho analytics), embedding classifier làm xương sống cho phần lớn traffic, và một lớp bandit học online ngồi phía sau để tinh chỉnh lựa chọn khi môi trường thay đổi (model mới được release, giá thay đổi, phân phối traffic dịch chuyển).
Vì sao không có một router duy nhất chiến thắng mọi kịch bản
Rule-based quá cứng nhưng cực nhanh và dễ audit. BERT classifier cân bằng tốt nhưng phải retrain mỗi khi domain thay đổi. Bandit tự học nhưng cần đủ traffic để hội tụ. Pha trộn cho phép mỗi lớp làm tốt thứ nó mạnh, và router trở thành một pipeline nhiều tầng chứ không phải một mô hình duy nhất.
3. Semantic classifier — trái tim của router hiện đại
Trong hầu hết kiến trúc router sản xuất năm 2026, thành phần chịu trách nhiệm chính là một semantic classifier nhẹ, thường là ModernBERT hoặc một encoder-only fine-tune được train để gán nhãn "class" cho prompt — với mỗi class tương ứng một mô hình đích. Quy trình lấy cảm hứng từ các hệ thống routing của vLLM Semantic Router: prompt đi qua embedding model, vector được đưa vào một classifier head, classifier trả về phân phối xác suất trên các model, router chọn model có kỳ vọng lợi ích cao nhất sau khi tính đến cost.
import torch
from transformers import AutoTokenizer, AutoModel
class ModernBertRouter:
LABELS = ["haiku", "sonnet", "opus", "open-source"]
PRICES = {"haiku": 0.25, "sonnet": 3.0, "opus": 15.0, "open-source": 0.1}
def __init__(self, model_path: str):
self.tok = AutoTokenizer.from_pretrained(model_path)
self.encoder = AutoModel.from_pretrained(model_path).eval()
self.head = torch.load(f"{model_path}/router_head.pt").eval()
@torch.no_grad()
def classify(self, prompt: str) -> dict[str, float]:
x = self.tok(prompt, truncation=True, max_length=256, return_tensors="pt")
h = self.encoder(**x).last_hidden_state[:, 0]
logits = self.head(h).squeeze(0)
probs = torch.softmax(logits, dim=-1).tolist()
return dict(zip(self.LABELS, probs))
def choose(self, prompt: str, cost_weight: float = 1.0) -> str:
probs = self.classify(prompt)
# Expected utility: prob * quality - cost_weight * price
# Quality prior: Opus 1.0, Sonnet 0.9, Haiku 0.75, open-source 0.7
quality = {"haiku": 0.75, "sonnet": 0.9, "opus": 1.0, "open-source": 0.7}
scores = {
m: probs[m] * quality[m] - cost_weight * self.PRICES[m] / 15.0
for m in self.LABELS
}
return max(scores, key=scores.get)
Điểm tinh tế của đoạn code trên không phải classifier, mà là cost_weight. Nó là núm vặn duy nhất cho phép runtime điều chỉnh vị trí trên đường cong cost-quality: cost_weight = 0 biến router thành "luôn chọn mô hình tốt nhất theo xác suất"; cost_weight cao biến router thành "chọn rẻ bất cứ khi nào không chắc chắn". Trong sản xuất, cost_weight thường được đặt theo loại request: free tier nhận cost_weight cao, enterprise tier nhận thấp hơn, internal debugging nhận gần 0.
4. Cascade routing — khi routing đơn thuần chưa đủ
Routing giải quyết câu hỏi "model nào cho request này", nhưng vẫn có bug cơ bản: classifier có thể đoán sai, và khi nó đoán sai, một prompt khó bị gửi tới Haiku và nhận một câu trả lời tệ. Cascade routing khắc phục bằng cách biến quá trình routing thành một chuỗi thử nhiều bước — bắt đầu ở model rẻ nhất, chỉ leo thang nếu kết quả không đạt một ngưỡng chất lượng nào đó.
graph TB
Req["Request"] --> Stage1["Stage 1: Haiku 4.5
try first"]
Stage1 --> Check1{"Chất lượng
đủ?"}
Check1 -->|"đủ
~65%"| Done1["Trả về user"]
Check1 -->|"không đủ"| Stage2["Stage 2: Sonnet 4.6"]
Stage2 --> Check2{"Chất lượng
đủ?"}
Check2 -->|"đủ
~25%"| Done2["Trả về user"]
Check2 -->|"không đủ"| Stage3["Stage 3: Opus 4.6
~10% cuối"]
Stage3 --> Done3["Trả về user"]
style Stage1 fill:#4CAF50,stroke:#fff,color:#fff
style Stage2 fill:#0f3460,stroke:#fff,color:#fff
style Stage3 fill:#6610f2,stroke:#fff,color:#fff
style Check1 fill:#f39c12,stroke:#fff,color:#fff
style Check2 fill:#f39c12,stroke:#fff,color:#fff
Câu hỏi then chốt của cascade là: làm sao biết kết quả ở một stage là "đủ tốt"? Có bốn phương pháp chính được dùng trong sản xuất:
- Log-probability threshold: nếu mô hình trả lời với độ tự tin thấp (entropy cao hoặc logprob thấp), leo thang. Đơn giản, không cần thêm call, hoạt động tốt với các task có câu trả lời rõ ràng (JSON, phân loại, extraction).
- Self-verification: cho mô hình hỏi ngược "bạn có chắc chắn không" hoặc "liệt kê các giả định". Tốn thêm một lượt nhưng không phải gọi mô hình đắt hơn.
- Small verifier model: một mô hình nhỏ (DeBERTa, ModernBERT) kiểm tra câu trả lời có hợp lý với prompt không. Rẻ, nhanh, và có thể fine-tune riêng.
- LLM-as-judge ở tier thấp hơn: dùng Haiku để judge câu trả lời của Haiku. Trái với trực giác, điều này hoạt động tốt hơn mong đợi vì việc đánh giá thường dễ hơn sinh.
Nghiên cứu gần đây (OpenReview 2024, "A Unified Approach to Routing and Cascading for LLMs") chỉ ra rằng kết hợp routing với cascading đạt cost-quality tradeoff tốt hơn 14% so với dùng một trong hai. Ý tưởng: router chọn starting stage dựa trên độ khó dự đoán của prompt, cascade leo thang nếu cần. Pattern này gần như đã trở thành tiêu chuẩn de facto trong sản xuất cuối 2025 - đầu 2026.
Pattern tiết kiệm nhất
Gộp routing và cascade thành một pipeline hai bước: (1) semantic classifier chọn starting stage — phần lớn request bắt đầu ở Haiku, phần khó bắt đầu ở Sonnet, chỉ phần cực khó hoặc đã từng thất bại mới bắt đầu ở Opus; (2) một log-probability check nhẹ quyết định leo thang hay không. Trong đo lường của Mavik Labs và PremAI, pattern này đạt 47-65% giảm chi phí trên traffic thật mà chất lượng đầu ra bằng hoặc cao hơn so với "tất cả vào Opus".
5. Bandit feedback — router tự học từ kết quả thật
Rule và classifier đều là phương pháp "đóng băng": một khi deploy, chúng không học thêm. Nhưng môi trường LLM năm 2026 không đứng yên — giá thay đổi hàng quý, model mới xuất hiện hàng tháng, phân phối prompt của user dịch chuyển liên tục. Đây là sân chơi của multi-armed bandit: một lớp router học online, cập nhật chính sách mỗi khi có feedback thật.
Mô hình chuẩn: mỗi request tạo thành một "arm pull". Router chọn một model (arm), quan sát reward (chất lượng câu trả lời), cập nhật niềm tin về arm đó. Công thức cân bằng khai thác và khám phá quen thuộc — UCB, Thompson sampling, epsilon-greedy — đều hoạt động, với hai điều chỉnh quan trọng cho ngữ cảnh LLM:
import math
from dataclasses import dataclass
@dataclass
class ArmState:
pulls: int = 0
reward_sum: float = 0.0
cost_sum: float = 0.0
class ContextualUcbRouter:
def __init__(self, models: list[str], prices: dict[str, float],
cost_weight: float = 0.3):
self.models = models
self.prices = prices
self.cost_weight = cost_weight
self.arms: dict[tuple, dict[str, ArmState]] = {}
def _context_key(self, prompt_emb: list[float]) -> tuple:
# Quantize embedding to bucket similar prompts into same context
return tuple(round(x, 1) for x in prompt_emb[:16])
def choose(self, prompt_emb: list[float]) -> str:
ctx = self._context_key(prompt_emb)
if ctx not in self.arms:
self.arms[ctx] = {m: ArmState() for m in self.models}
arms = self.arms[ctx]
total = sum(a.pulls for a in arms.values()) + 1
best, best_score = None, -math.inf
for m, a in arms.items():
if a.pulls == 0:
return m # cold start — explore
avg_reward = a.reward_sum / a.pulls
ucb = avg_reward + math.sqrt(2 * math.log(total) / a.pulls)
score = ucb - self.cost_weight * self.prices[m]
if score > best_score:
best, best_score = m, score
return best
def update(self, prompt_emb: list[float], model: str,
reward: float, cost: float):
ctx = self._context_key(prompt_emb)
a = self.arms[ctx][model]
a.pulls += 1
a.reward_sum += reward
a.cost_sum += cost
Hai nét đáng chú ý. Thứ nhất, context key biến một bandit phẳng thành bandit ngữ cảnh: router học riêng cho từng "vùng prompt" thay vì một chính sách toàn cục. Thứ hai, reward = quality - cost_weight * price — bandit không tối ưu chỉ chất lượng, nó tối ưu chất lượng có trừ chi phí. Điều này khớp đúng với cái team sản xuất thực sự quan tâm: ROI, không phải accuracy tuyệt đối.
Cạm bẫy cold-start
Bandit chỉ hoạt động tốt khi đã có đủ feedback. Với traffic thấp hoặc sau khi thêm một model mới, các arm rỗng có thể dẫn tới quyết định tệ. Pattern sản xuất hay dùng: khởi tạo prior bằng kết quả từ classifier đã train sẵn, rồi để bandit cập nhật dần. Đây là ý tưởng của NeuralUCB và các biến thể "warm-started bandit" gần đây.
6. Redis làm state store cho router — tại sao đây là lựa chọn đúng
Một router có ba loại state cần giữ cực nhanh: (1) bảng arm của bandit — cần đọc/ghi sub-ms vì mỗi request đều chạm, (2) semantic cache — lưu các prompt đã được route trước đó để trả lời lại ngay mà không cần chạy classifier, (3) token budget counter — đếm chi phí theo tenant/user/API key cho rate limiting.
Không có nhiều lựa chọn có thể làm cả ba với cùng một data store. Redis là ứng cử viên tự nhiên. Với Redis 8, ba mẫu hình sau khớp sát vào ba thành phần của router:
graph TB
Router["Agent Router"] --> RedisHash["Redis HASH
bandit:arms:{ctx}"]
Router --> RedisVS["Redis 8 Vector Set
router:cache"]
Router --> RedisStream["Redis Stream
router:events"]
Router --> RedisCounter["Redis INCRBYFLOAT
budget:{tenant}:{day}"]
RedisHash -->|"HINCRBYFLOAT pulls,reward"| BandStore["Per-arm statistics
contextual UCB"]
RedisVS -->|"VSIM embedding → model"| SemCache["Semantic cache hit
skip classifier"]
RedisStream -->|"XADD request"| Ingest["Batch → ClickHouse
via consumer group"]
RedisCounter -->|"check vs cap"| Budget["Tenant budget
throttle / downgrade"]
style Router fill:#e94560,stroke:#fff,color:#fff
style RedisHash fill:#dc3545,stroke:#fff,color:#fff
style RedisVS fill:#dc3545,stroke:#fff,color:#fff
style RedisStream fill:#dc3545,stroke:#fff,color:#fff
style RedisCounter fill:#dc3545,stroke:#fff,color:#fff
Chi tiết các pattern:
- HASH cho bandit arms:
HINCRBYFLOAT bandit:arms:{ctx}:{model} pulls 1vàHINCRBYFLOAT ... reward 0.87cho phép cập nhật atomic dưới 0.5 ms. Không cần lock, không cần transaction. - Vector Set cho semantic cache của router:
VADD router:cache VALUES 1536 ... req:123 SETATTR '{"model":"sonnet","reward":0.9}'— lần sau khi prompt tương tự xuất hiện,VSIM router:cache ... COUNT 1có thể trả về ngay "model=sonnet" mà không chạy classifier BERT. Thường hit rate 30-50% trên traffic có tính lặp lại cao. - Stream cho event log:
XADD router:events * req_id 123 model sonnet latency 420 reward 0.9. Consumer group batch pull và đẩy vào ClickHouse mỗi 500 ms. - Counter cho budget:
INCRBYFLOAT budget:tenant42:2026-04-14 0.0034(chi phí USD ước lượng của request). MộtGETnhanh để so với cap; vượt cap thì downgrade xuống model rẻ hoặc trả 429.
Lý do Redis thắng ở đây không phải "nhanh nhất" — nhiều KV store cũng sub-ms. Lý do là bốn pattern này nằm cùng một cluster. Không cần join, không cần sync, không cần hai lớp cache. Với router cần chạm 5-6 key/hash/set khác nhau cho mỗi request, việc tất cả nằm cùng cluster (và thường cùng một pipeline TCP) là khác biệt giữa overhead 2 ms và overhead 20 ms.
7. ClickHouse — backbone analytics cho router
Redis là nơi lưu state "sống"; ClickHouse là nơi lưu lịch sử và trả lời các câu hỏi phân tích. Mỗi event router sinh ra được stream từ Redis Stream vào ClickHouse qua Kafka Engine hoặc qua một consumer Python nhỏ. Schema điển hình đủ để trả lời mọi câu hỏi kinh doanh về hiệu quả router:
CREATE TABLE router_events (
ts DateTime64(3),
request_id String,
tenant_id LowCardinality(String),
user_id String,
api_key_hash FixedString(16),
prompt_tokens UInt32,
response_tokens UInt32,
prompt_hash UInt64,
prompt_class LowCardinality(String), -- 'chat', 'code', 'research', 'sql'
classifier_score Float32, -- độ tự tin của classifier
chosen_model LowCardinality(String), -- 'haiku', 'sonnet', 'opus', 'llama-70b'
chosen_reason LowCardinality(String), -- 'classifier', 'cache_hit', 'cascade', 'bandit'
cascade_depth UInt8, -- 1, 2, 3 (số model đã thử)
cost_usd Float64,
latency_ms UInt32,
ttft_ms UInt32, -- time to first token
reward Float32, -- quality signal (thumbs up/down, judge score)
was_downgraded UInt8, -- đã bị force xuống model rẻ vì budget
cost_weight Float32 -- giá trị cost_weight tại thời điểm route
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (tenant_id, chosen_model, ts)
TTL ts + INTERVAL 90 DAY;Với bảng này, một dashboard duy nhất trả lời được các câu hỏi quyết định hàng ngày: tenant nào đang chi nhiều nhất? Classifier nhầm model nào thường xuyên nhất (cascade_depth > 1)? Tỷ lệ cache_hit ở giờ cao điểm có đang tụt không? Cost per request trung bình theo phân lớp prompt? Cost_weight bao nhiêu thì đạt điểm tối ưu reward/cost?
Vẻ đẹp của pattern này là nó khép vòng feedback. ClickHouse không chỉ là nơi xem dashboard — nó là nơi router đọc reward để cập nhật bandit offline, nơi team đo cost_weight tối ưu, nơi phát hiện drift phân phối prompt. Router và ClickHouse tạo thành một hệ thống học liên tục: ClickHouse lưu tín hiệu, Redis lưu trạng thái, router vận hành inference, event pipeline nối chúng lại.
8. Pipeline hoàn chỉnh — ghép các mảng vào một request thực tế
Đến đây, hãy xem pipeline đầy đủ của một request đi qua router sản xuất. Từ khi user nhấn enter tới khi nhận được token đầu tiên, có bảy mốc cần đo và kiểm soát:
sequenceDiagram
participant U as User
participant GW as API Gateway
participant R as Agent Router
participant RS as Redis
participant CLS as Classifier
participant M as Model (Haiku/Sonnet/Opus)
participant CH as ClickHouse
U->>GW: prompt + api_key
GW->>R: forward + tenant_id
R->>RS: GET budget:{tenant}:{day}
RS-->>R: 0.85 (85% used)
R->>RS: VSIM router:cache (semantic cache)
RS-->>R: nearest={model:sonnet,score:0.94}
alt cache hit (score > 0.93)
R->>M: call sonnet
else cache miss
R->>CLS: classify(prompt)
CLS-->>R: probs={haiku:0.7, sonnet:0.25, opus:0.05}
R->>RS: HGETALL bandit:arms:{ctx}
RS-->>R: arm stats
R->>R: compute UCB - cost_weight * price
R->>M: call haiku (cheapest with acceptable UCB)
end
M-->>R: response + logprobs
R->>R: check confidence threshold
alt confidence low
R->>M: call sonnet (cascade up)
M-->>R: response
end
R->>RS: XADD router:events ...
R->>RS: HINCRBYFLOAT bandit:arms ...
R->>RS: INCRBYFLOAT budget:{tenant}:{day} ...
R->>RS: VADD router:cache ...
R-->>GW: response
GW-->>U: stream tokens
RS-->>CH: consumer batch (async)
Quan sát ba điều quan trọng về pipeline này. Một, mọi tương tác với Redis đều được gộp vào một pipeline TCP — GET budget + VSIM cache + HGETALL arms được gửi đi cùng lúc, chỉ chờ một round-trip. Hai, classifier được skip hoàn toàn khi cache hit, và cache hit chiếm 30-50% traffic trong đa số ứng dụng có user-facing. Ba, cập nhật bandit và cache xảy ra sau khi phản hồi tới user, nên không cản trở latency end-to-end.
Kết quả điển hình: p50 overhead của router ~5 ms, p99 ~15 ms. Trên latency tổng 2-4 giây của một LLM call, đây là lớp "miễn phí" về mặt UX.
9. Token budget & cost governance — chặng cuối cùng
Một router hoàn chỉnh không chỉ chọn model; nó còn từ chối chọn nếu ngân sách đã hết. Trong các tổ chức lớn, nơi hàng chục team cùng chia sẻ một cluster LLM, đây là tính năng sống còn. Pattern chuẩn gồm ba lớp:
- Lớp tenant: mỗi tenant có daily cap (ví dụ 50 USD/ngày).
INCRBYFLOAT budget:{tenant}:2026-04-14 costsau mỗi call;EXPIREvề lúc nửa đêm. Vượt cap → downgrade về model rẻ nhất hoặc trả 429. - Lớp API key: trong tenant, mỗi API key có quota riêng — quan trọng khi một service misbehave làm cạn ngân sách.
- Lớp global: kill-switch để nếu tổng chi phí giờ vừa qua vượt ngưỡng bất thường, router chuyển toàn bộ traffic không-critical về model rẻ.
Kỹ thuật: dùng sorted set Redis để lưu quota theo slot thời gian (ZADD budget:tenant42 {unix_ts} {cost}), sau đó ZRANGEBYSCORE để tính tổng chi phí trong 1 giờ/24 giờ qua. Chi phí kiểm tra: một lệnh Redis, dưới 1 ms.
Graceful downgrade thay vì hard cap
Hard cap (vượt cap là trả 429) rất tệ về UX. Pattern tốt hơn: khi ngân sách đạt 80%, cost_weight tự động nhân 2x; đạt 95%, nhân 5x. Router sẽ chọn model rẻ hơn cho tất cả request "không bắt buộc phải dùng model lớn". User không bao giờ thấy lỗi, chỉ thấy câu trả lời hơi khác. Đây là cách các platform như Cursor, Perplexity và Phind áp dụng trong sản xuất.
10. Router cho Claude Code và multi-agent framework khác
Câu hỏi thực tế: router có áp dụng cho Claude Code không? Câu trả lời là có — và đây là một chỗ thú vị vì Claude Code chạy nhiều sub-agent, mỗi sub-agent có thể chọn model riêng qua cấu hình. Một router sản xuất đặt trước Claude Code (hoặc đặt trong qua custom sub-agent dispatcher) cho phép:
- Sub-agent
Exploredùng Haiku vì phần lớn thời gian chỉ duyệt file và grep — reasoning nhẹ. - Sub-agent
Plandùng Sonnet vì cần reasoning trung bình về thiết kế. - Agent chính dùng Opus khi cần tổng hợp nhiều nguồn thông tin hoặc khi cascade đã leo lên.
- Các tool call nội bộ (git status, test runner) không cần LLM nào cả — router phát hiện pattern lặp lại, trả từ cache.
Pattern tương tự áp dụng cho LangGraph, OpenAI Swarm, CrewAI, AutoGen. Thay vì để framework trực tiếp cài model hardcoded, team đặt một layer gateway ở giữa — thường là LLM Gateway (xem bài về LLM Gateway trên anhtu.dev) — và router là một middleware trong gateway đó. Kết quả: bạn có thể thay model, thay giá, thay chính sách routing mà không phải chỉnh code agent.
11. Drift, retraining và lifecycle của router
Router không phải thứ "deploy xong là xong". Ba nguồn drift chính cần theo dõi và xử lý:
- Prompt distribution drift: user hỏi kiểu khác sau khi launch feature mới. Classifier bị "ngoài phân phối" và đoán sai nhiều hơn. Theo dõi:
classifier_scoretrung bình theo ngày — rơi xuống là dấu hiệu. - Model quality drift: provider update model, đôi khi tốt lên, đôi khi tệ đi. Theo dõi:
rewardtrung bình theo model theo tuần. - Price drift: giá token thay đổi hàng quý. Reload bảng giá vào router, cost_weight tự dịch theo.
Lịch trình thường thấy: retrain classifier 4-8 tuần/lần, refresh bandit online liên tục, cập nhật bảng giá ngay khi provider thông báo, review cost_weight hàng tháng theo KPI tài chính. Team nên có sẵn một pipeline ClickHouse → Parquet → training notebook, không phải dựng từ đầu mỗi lần.
Shadow routing — cách retrain an toàn
Khi deploy classifier mới, đừng switch traffic ngay. Chạy shadow: router cũ quyết định model, router mới cũng quyết định nhưng không được thực thi, cả hai quyết định được log vào ClickHouse. So sánh dự đoán, đo hiệu quả giả lập bằng reward mà router cũ thu được. Nếu router mới khớp hoặc vượt trên tập control, switch. Pattern này tránh mọi "big bang" và cho phép rollback tức thì.
12. So sánh các framework router mã nguồn mở 2026
Năm 2026 có vài framework router trưởng thành đủ để team có thể chọn thay vì tự viết từ đầu:
| Framework | Cơ chế chính | Điểm mạnh | Hạn chế |
|---|---|---|---|
| vLLM Semantic Router | ModernBERT classifier + HNSW semantic cache (Redis/Milvus) | Envoy ExtProc, hỗ trợ jailbreak detect, PII check, phân loại 14 MMLU categories | Khớp sâu với vLLM stack, cần hạ tầng Envoy |
| RouteLLM (LMSYS) | 4 router types: SW ranking, BERT, causal LLM, matrix factorization | Preference-data driven, benchmark rất kỹ, có pretrained model | Thiết kế cho pair 2 model, mở rộng N model cần retrain |
| NVIDIA AI Blueprints llm-router | Rule + fine-tuned classifier + cascade | Gắn sẵn với NeMo Guardrails, có blueprint sản xuất | Nặng nghiêng về stack NVIDIA |
| Aurelio Labs semantic-router | Embedding similarity + rule | Đơn giản, dễ hiểu, rất nhẹ | Không có cascade, không bandit |
| Martian Router | Proprietary; routing theo cost-quality learned từ traffic | Ngắm sản xuất enterprise, API OpenAI-compat | Closed source, phụ thuộc vendor |
Lựa chọn thực dụng: bắt đầu với Aurelio semantic-router hoặc tự viết một rule + embedding k-NN trong vài trăm dòng Python để có baseline chạy được. Khi traffic vượt vài trăm nghìn request/ngày và chi phí LLM vượt 5000 USD/tháng, di cư sang vLLM Semantic Router hoặc tự build với ModernBERT + RouteLLM preference dataset. Bandit là bước cuối — chỉ cần khi traffic đủ lớn để việc khám phá online có ý nghĩa thống kê (thường >100k request/ngày cho bandit hội tụ nhanh).
13. Checklist đưa Agent Router vào production
- Đã phân tích log 4 tuần gần nhất để biết phân phối thực tế của prompt theo độ khó — không đoán, phải đo.
- Đã chọn cơ chế router phù hợp với quy mô (rule → embedding → BERT → bandit) và không chọn lớn quá tầm.
- Đã có ground truth nhỏ (50-200 mẫu) để đo classifier accuracy trước khi deploy lần đầu.
- Cost_weight được cấu hình qua flag runtime, không hardcode — để tinh chỉnh mà không deploy.
- Redis cluster có đủ RAM cho bandit state, semantic cache, token budget; và có replica cho VSIM.
- ClickHouse đã có schema router_events, partition theo tháng, TTL 60-90 ngày.
- Dashboard Grafana theo dõi: p95 latency router overhead, cost per request trung bình, cascade depth, classifier confidence, hit rate semantic cache, budget theo tenant.
- Có shadow routing pipeline để test classifier mới mà không ảnh hưởng sản xuất.
- Có kill-switch để trong sự cố có thể tạm dừng router và đổ toàn bộ traffic về một model duy nhất.
- Tài liệu rõ policy downgrade khi budget sắp cạn — user tier nào sẽ bị ảnh hưởng thế nào.
14. Tổng kết
Agent Router là thành phần mà ít ai nhắc tới khi nói về "kiến trúc AI" nhưng lại quyết định kinh tế của mọi hệ thống multi-agent sản xuất năm 2026. Nó trả lời đúng một câu hỏi duy nhất — model nào, stage nào, cho request nào, dưới budget gì — nhưng trả lời đúng câu đó cắt giảm 47-80% chi phí LLM và giảm latency trung bình đáng kể. Trái tim của router hiện đại là semantic classifier nhẹ, đệm bằng semantic cache trên Redis Vector Set, leo thang qua cascade khi cần, học online qua bandit, và đo đạc mọi quyết định trong ClickHouse để khép vòng feedback.
Điểm đáng lưu ý nhất là router không thay thế các thành phần khác của stack AI — LLM Gateway, Agent Memory, Semantic Cache, Guardrails, Observability vẫn còn đó. Router là lớp điều phối đặt giữa chúng, biến một tập mô hình phân tán thành một "tổ hợp mô hình" hoạt động như một hệ thống duy nhất. Với team engineering đang xây agent năm 2026, câu hỏi không còn là "có nên thêm router không" mà là "router của tôi đang vận hành ở lớp nào — rule, embedding, hay bandit — và có còn là bottleneck của cost không". Nếu trả lời của bạn cho câu thứ hai vẫn là "chưa biết", đây là nơi ROI cao nhất trong stack AI để bắt đầu đào tiếp.
Nguồn tham khảo
- vLLM Blog, vLLM Semantic Router v0.1 Iris: The First Major Release — blog.vllm.ai/2026/01/05/vllm-sr-iris
- vLLM Semantic Router, Open-Source LLM Router for Mixture-of-Models — vllm-semantic-router.com
- Red Hat Developer, Getting started with the vLLM Semantic Router project's Athena release — developers.redhat.com athena release
- Ong et al., RouteLLM: Learning to Route LLMs with Preference Data, arXiv:2406.18665 — arxiv.org/abs/2406.18665
- LMSYS Blog, RouteLLM: An Open-Source Framework for Cost-Effective LLM Routing — lmsys.org/blog/2024-07-01-routellm
- OpenReview, A Unified Approach to Routing and Cascading for LLMs — openreview.net unified routing cascading
- arXiv 2510.07429, Learning to Route LLMs from Bandit Feedback: One Policy, Many Trade-offs — arxiv.org/abs/2510.07429
- arXiv 2502.02743, LLM Bandit: Cost-Efficient LLM Generation via Preference-Conditioned Dynamic Routing — arxiv.org/abs/2502.02743
- Mavik Labs, LLM Cost Optimization in 2026: Routing, Caching, and Batching — maviklabs.com/blog/llm-cost-optimization-2026
- PremAI Blog, LLM Cost Optimization: 8 Strategies That Cut API Spend by 80% — blog.premai.io llm-cost-optimization
- NVIDIA AI Blueprints, llm-router GitHub repository — github.com/NVIDIA-AI-Blueprints/llm-router
- Redis Blog, LLMOps Guide 2026: Build Fast, Cost-Effective LLM Apps — redis.io/blog/llmops-guide
Prompt Caching & Context Caching 2026 - Kiến trúc Tái sử dụng KV Cache Provider-level cho Claude, OpenAI, Gemini với Redis Edge và ClickHouse Analytics
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
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.