🇬🇧 Read the English version of this article on tanhdev.com

Answer-first: Trong năm 2026, Kỹ thuật Nền tảng (Platform Engineering) cho AI không còn nằm ở việc chọn đúng LLM framework. Những câu hỏi thực sự là: Ai kiểm soát chi phí token? Ai định tuyến lưu lượng truy cập thông minh đến đúng GPU pod? Trạng thái của tác nhân (agent state) đi về đâu sau một sự cố crash? Ba dự án CNCF — Envoy AI Gateway, K8s Gateway API Inference Extension, và Dapr Agents — đang hội tụ để trả lời những câu hỏi đó ở tầng cơ sở hạ tầng, giúp mã nguồn ứng dụng (application code) không phải gánh vác.


Lớp 1 — FinOps: Envoy AI Gateway Kiểm soát Token như Ngân sách Tài chính

Answer-first: Envoy AI Gateway (v1.0+, giữa 2026) đặt một QuotaPolicy (Chính sách hạn mức) ngay tại tầng hạ tầng. Thay vì đo lường số request mỗi giây, nó đo lường mức tiêu thụ token thực tế và bắn lỗi HTTP 429 như một cầu dao tài chính (financial circuit breaker) trước khi vòng lặp của một tác nhân (agent loop) có thể đốt sạch toàn bộ ngân sách.

Vấn đề cốt lõi: các gateway truyền thống (NGINX, vanilla Kong) không thể kiểm tra phần thân (body) của một yêu cầu LLM. Chúng hoàn toàn mù tịt về việc liệu một request đang tiêu thụ 50 token hay 50.000 token. Các đội ngũ nền tảng chỉ phát hiện ra họ đã làm thủng ngân sách khi hóa đơn cuối tháng gửi về.

QuotaPolicy: Ngân sách Token cho từng Tenant

Envoy AI Gateway giải quyết vấn đề này bằng cách tích hợp một bộ lọc ext_proc (External Processing) vào bên trong Envoy để phân tích cú pháp phần thân phản hồi của LLM và trích xuất số lượng token thực tế từ metadata của phản hồi.

apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIGatewayRoute
metadata:
  name: tenant-a-route
spec:
  rules:
  - matches:
    - headers:
      - name: x-tenant-id
        value: "team-backend"
    backendRefs:
    - name: openai-backend
      weight: 100
    - name: azure-openai-fallback   # tự động dự phòng sang provider khác
      weight: 0
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: QuotaPolicy
spec:
  limits:
  - tokenBudget: 500000          # 500K tokens/ngày cho mỗi team
    window: 24h
    selector:
      header: "x-tenant-id"
    costs:
      inputToken: 1
      outputToken: 3              # CEL expression: output đắt gấp 3 lần
      reasoningToken: 5           # thinking tokens là đắt đỏ nhất

Khi team-backend vượt quá mốc 500K tokens, gateway sẽ trả về lỗi 429 Too Many Requests trước khi request đó kịp chạm tới OpenAI. Không một token nào bị lãng phí.

Ảo hóa Mô hình (Model Virtualization): Code không cần biết nó đang xài Provider nào

Tính năng ít được chú ý nhưng quan trọng nhất về mặt chiến lược đối với các team lớn: Ảo hóa Mô hình. Ứng dụng gọi model: "gpt-production", và gateway ánh xạ nó sang gpt-4o hoặc claude-sonnet-4 tùy theo chính sách — không cần phải deploy lại ứng dụng khi chuyển đổi provider.

Kết hợp với LiteLLM: Nhiều team sử dụng LiteLLM ở tầng ứng dụng để chuẩn hóa lời gọi cho hơn 100+ provider, trong khi sử dụng Envoy AI Gateway ở tầng hạ tầng để thi hành FinOps ở cấp độ token. Hai công cụ này không hề loại trừ lẫn nhau.


Lớp 2 — Định tuyến Thông minh: K8s Gateway API Inference Extension Đưa Định tuyến GPU vào Tiêu chuẩn CNCF

Answer-first: InferencePool + InferenceModel là hai CRD mới từ K8s SIG Network (Quý 1/2026) cho phép một Endpoint Picker (EPP) định tuyến lưu lượng AI dựa trên trạng thái KV cache thời gian thực và độ sâu hàng đợi (queue depth) của GPU — thay vì chia bài vòng tròn (round-robin) mù quáng của Kubernetes Service tiêu chuẩn.

Khi bạn dùng một Kubernetes Service cơ bản để load balance các vLLM pods, lưu lượng được phân phối đồng đều. Nhưng tính toán LLM (LLM inference) vốn dĩ là không đồng đều: một pod có thể đang nắm giữ sẵn KV cache cho chính xác tiền tố prompt (prompt prefix) mà request mới cần, trong khi pod khác sẽ phải tính toán lại từ con số không. Hậu quả: TTFT (Time To First Token - Thời gian tới Token đầu tiên) tăng cao không cần thiết và xảy ra hiện tượng “nghẽn cổ chai” (GPU hotspotting).

InferencePool và Endpoint Picker: Định tuyến Thông minh

apiVersion: inference.networking.k8s.io/v1alpha2
kind: InferencePool
metadata:
  name: llama-3-pool
spec:
  targetPortNumber: 8000
  selector:
    app: vllm-llama3
  extensionRef:
    name: endpoint-picker-epp    # "bộ não" định tuyến
---
apiVersion: inference.networking.k8s.io/v1alpha2
kind: InferenceModel
metadata:
  name: llama-3-70b
spec:
  modelName: "meta-llama/Meta-Llama-3-70B-Instruct"
  criticality: Standard          # Low | Standard | Critical
  poolRef:
    name: llama-3-pool

Endpoint Picker (EPP) là một plugin sử dụng giao thức ext_proc để truy vấn mỗi pod về:

  • Mức độ sử dụng KV cache hiện tại
  • Độ sâu hàng đợi GPU (các request đang chờ)
  • Các LoRA adapters đã được tải lên bộ nhớ

EPP sẽ định tuyến mỗi request tới pod có tỷ lệ cache hit cao nhất tương ứng với tiền tố prompt của request đó → giảm TTFT đáng kể, triệt tiêu hotspot.

llm-d: Suy luận Phân tách — Dự án CNCF Sandbox

llm-d (Red Hat/Google/IBM/NVIDIA, CNCF Sandbox 2026) tiến xa hơn một bước: nó chia tách prefill (xử lý prompt — ngốn compute) và decode (tạo ra token — ngốn băng thông bộ nhớ) thành hai pool GPU hoàn toàn riêng biệt.

  • Prefill pool: GPU A100 với mức độ song song tensor (tensor-parallelism) cao — xử lý prompt chớp nhoáng
  • Decode pool: Các GPU sở hữu bộ nhớ HBM lớn — duy trì thông lượng (throughput) tạo token ổn định

Kết hợp với InferencePool và định tuyến nhận thức KV cache (KV cache-aware routing), llm-d định tuyến các request chia sẻ chung một tiền tố prompt về cùng một decode worker → tối đa hóa cache hit rate, gia tăng thông lượng lên tới 30–50% theo các benchmark nội bộ của dự án.

Ghi chú trưởng thành: llm-d vẫn đang là dự án CNCF Sandbox (chưa Tốt nghiệp). Thích hợp cho các team muốn đi trước đón đầu, nhưng để dùng cho production cần phải đánh giá cực kỳ cẩn thận.


Lớp 3 — Độ tin cậy: Dapr Agents GA — Tác nhân là một Workflow, không phải là một Pod Phi trạng thái

Answer-first: Dapr Agents v1.0 đã đạt GA vào tháng 3/2026 tại sự kiện KubeCon Europe. Lớp trừu tượng DurableAgent biến một tác nhân AI thành một Dapr Workflow: mỗi bước Suy nghĩ (Thought) → Quan sát (Observation) → Hành động (Action) đều được lưu điểm kiểm tra (checkpointed) xuống Redis/Postgres. Khi một pod dính lỗi OOMKilled, workflow sẽ tự phục hồi ngay tại đúng vị trí nó bị gián đoạn — không có một ngữ cảnh nào bị thất thoát.

Đây là sự chuyển đổi tư duy lớn nhất: thay vì cố ép các tác nhân LLM trở thành “phi trạng thái và lũy đẳng” (stateless and idempotent - điều gần như bất khả thi với lập luận đa lượt / multi-turn reasoning), Dapr Agents chấp nhận sự thật rằng các tác nhân vốn dĩ là có trạng thái (stateful by nature) và cung cấp hạ tầng để quản lý trạng thái đó một cách bền bỉ.

DurableAgent: Phục hồi Crash Đích thực

from dapr_agents import DurableAgent, tool

class ResearchAgent(DurableAgent):
    """Một tác nhân có khả năng sống sót qua các lần khởi động lại pod."""

    @tool
    async def search_web(self, query: str) -> str:
        """Search — kết quả được lưu trữ bền bỉ."""
        ...

    @tool
    async def analyze_document(self, url: str) -> dict:
        """Mỗi tool call = 1 Dapr Activity = được checkpoint."""
        ...

Mọi lời gọi @tool bên trong một DurableAgent đều được engine của Dapr Workflow xử lý như một Activity (Hoạt động): kết quả của nó được lưu trữ lại sau khi thực thi xong. Khi workflow được tua lại (replay) do crash hoặc restart, trình điều phối (orchestrator) sẽ đọc lấy kết quả đã lưu trong cache thay vì gọi lại LLM hay gọi một API bên ngoài.

Quy tắc chí tử: Các lời gọi LLM bắt buộc phải nằm bên trong một Activity, tuyệt đối không được nằm trong Orchestrator. Orchestrator phải mang tính tất định (deterministic) — cùng một đầu vào phải luôn cho ra cùng một đầu ra. Nhưng LLM thì không như vậy. Vi phạm quy tắc này sẽ phá hỏng hoàn toàn khả năng tua lại (replay) chuẩn xác của Dapr Workflow.

Mô hình Virtual Actor: Tự động Scale, Không cần Khóa (Locks)

Mỗi instance (bản thể) của DurableAgent là một Dapr Virtual Actor — một đơn vị có trạng thái, có địa chỉ rõ ràng kèm theo một ID độc nhất. Runtime của Dapr sẽ tự xử lý:

  • Di trú (Migration): khi một pod bị đuổi (evicted), actor sẽ tự động “di cư” sang một pod khác.
  • Đồng thời theo lượt (Turn-based concurrency): mỗi actor xử lý từng tin nhắn một tuần tự → không cần xài mutex, không xảy ra race conditions.
  • Kích hoạt lười (Lazy activation): các actor chỉ được tải vào bộ nhớ khi cần thiết → hàng ngàn tác nhân có thể tồn tại đồng thời mà không ngốn một byte bộ nhớ nào khi đang nhàn rỗi (idle).

Tích hợp MCP: Khám phá Công cụ Zero-Config

Dapr v1.18 đã giới thiệu MCPServer Resource — sidecar sẽ tự động khám phá các công cụ MCP và phơi bày chúng cho các tác nhân:

apiVersion: dapr.io/v1alpha1
kind: MCPServer
metadata:
  name: internal-tools
spec:
  tools:
    - name: database-query
      rbac:
        allowedAgents: ["research-agent", "audit-agent"]
      piiRedaction: true          # tự động che giấu thông tin PII khỏi log
      auditLog: true

Không cần viết code khám phá (discovery logic) trong tác nhân. Sidecar sẽ lo toàn bộ.

Nâng cấp Bảo mật Dapr v1.18 (Tháng 6/2026): Bắt buộc cho Đa Tenant

Trước v1.18, bất kỳ app nào trong cùng một trust domain cũng có thể lên lịch (schedule) hoặc chấm dứt (terminate) các workflow của một app khác. Trong môi trường nhiều team dùng chung một cluster, đây là một lỗ hổng bảo mật chết người.

WorkflowAccessPolicy CRD đã khắc phục điều này:

apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
  name: research-agent-policy
spec:
  targetWorkflows:
    - "research-agent-workflow"
  allowedCallers:
    - appId: "orchestration-service"
    - appId: "admin-dashboard"

Các tính năng nâng cấp khác của v1.18:

  • Phát hiện Can thiệp (Tamper Detection): lịch sử workflow được ký bằng mật mã → phát hiện ngay lập tức bất kỳ sự can thiệp trạng thái nào khi hệ thống tải lên.
  • Giới hạn Đồng thời (Concurrency Limits): maxConcurrentWorkflows: 50 trên mỗi namespace → ngăn chặn các swarm tác nhân tiêu tốn tài nguyên cluster.

Tích hợp Go Backend (Kratos + Dapr)

Answer-first: Bọc Dapr Go SDK phía sau một interface trong tầng internal/data, và bơm (inject) thông qua Wire vào tầng internal/biz. Logic nghiệp vụ (biz) tuyệt đối không bao giờ được biết đến sự tồn tại của Dapr — nó chỉ được phép tương tác thông qua Go interface.

// internal/biz/agent.go — tầng biz hoàn toàn không biết gì về Dapr
type AgentWorkflow interface {
    StartResearch(ctx context.Context, req *ResearchRequest) (string, error)
    GetStatus(ctx context.Context, instanceID string) (*WorkflowStatus, error)
}

// internal/data/dapr_workflow.go — tầng data implement Go interface bằng Dapr SDK
type daprWorkflowRepo struct {
    client dapr.Client
}

func (r *daprWorkflowRepo) StartResearch(ctx context.Context, req *ResearchRequest) (string, error) {
    resp, err := r.client.StartWorkflow(ctx, &dapr.StartWorkflowRequest{
        WorkflowComponent: "dapr",
        WorkflowName:      "research-agent-workflow",
        Input:             req,
    })
    return resp.InstanceID, err
}

Những cạm bẫy thực tế (đúc rút từ kinh nghiệm production tại tanhdev.com):

  1. Tuyệt đối đừng tạo ra một gói “Kratos-native Dapr transport” — Các công cụ AI thường xuyên ảo giác (hallucinate) ra cái package này. Nó không hề tồn tại. Hãy chỉ dùng duy nhất thư viện dapr/go-sdk chính thức.
  2. Lan truyền traceparent: truyền cái header này thông qua context.Context dọc theo toàn bộ chuỗi gọi (call chain) để duy trì khả năng truy vết xuyên suốt (end-to-end tracing) giữa Kratos handler và các hoạt động của Dapr.
  3. ETags cho Actor state: sử dụng cơ chế xử lý ETag minh bạch khi cập nhật Actor state đồng thời để tránh làm mất dữ liệu cập nhật (lost updates).
  4. Mock interface trong unit test: không bao giờ test thư viện Dapr runtime bên trong các bài test của tầng biz — hãy chỉ mock cái interface.

KEDA + vLLM: FinOps Autoscaling theo Độ sâu Hàng đợi

Answer-first: GPU pods không scale tốt dựa trên các số liệu (metrics) CPU/memory — bởi vì GPU thường nhảy lên ngưỡng 100% ngay lập tức. KEDA ScaledObject dùng thông số vllm:num_requests_waiting từ Prometheus để kích hoạt scaling. Hãy giữ minReplicaCount: 1 cho các tác nhân giao tiếp trực tiếp (interactive agents) — vLLM cold start ngốn từ 40–90 giây nếu bạn scale từ con số 0.

Mảnh ghép cuối cùng của bức tranh: Hệ thống tự động mở rộng (autoscaling) GPU pod không được phụ thuộc vào CPU/memory metrics. Hãy sử dụng một KEDA ScaledObject được điều khiển bằng số liệu Prometheus bắn ra từ vLLM:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-scaler
spec:
  scaleTargetRef:
    name: vllm-deployment
  minReplicaCount: 1             # giữ lại 1 pod nóng cho traffic interactive
  maxReplicaCount: 10
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus:9090
      metricName: vllm_num_requests_waiting
      query: sum(vllm:num_requests_waiting)
      threshold: "5"             # scale khi có > 5 requests đang đợi

Scale về số 0 với minReplicaCount: 0 chỉ phù hợp cho các luồng xử lý bất đồng bộ/lô (async/batch workloads). Đối với các tác nhân interactive (như chatbot, phân tích thời gian thực): vLLM khởi động lạnh (cold start) sẽ ngốn từ 40–90 giây (để tải weights + chạy torch.compile + vẽ CUDA graph + làm nóng KV cache). Hãy giữ lại ít nhất một pod nóng.


FAQ

Dapr Agents vs LangGraph — Tôi nên sử dụng cái nào?

Đây là một câu hỏi sai vì hai công cụ này hoạt động ở hai lớp hoàn toàn khác nhau. LangGraph là “bộ não” của tác nhân — nó quản lý state machine, vòng lặp suy luận, và human-in-the-loop (HITL). Dapr Agents là “cơ thể” vật lý — phụ trách độ bền (durability), khả năng chịu lỗi (fault tolerance), và khả năng scale trên K8s. Mô hình được khuyến nghị trong năm 2026 là lai tạo (hybrid): sử dụng DaprCheckpointer để LangGraph có thể lưu giữ trạng thái đồ thị (graph state) của nó vào một Dapr state store. Nếu bạn đã chạy sẵn Dapr trên K8s và không cần những logic đồ thị phức tạp — thì một DurableAgent thuần túy là quá đủ.

Tại sao tôi không thể nhét thẳng các lệnh gọi LLM vào Dapr Workflow Orchestrator?

Bởi vì Orchestrator bắt buộc phải là một cỗ máy tất định (deterministic) để Dapr có thể tua lại (replay) workflow sau một sự cố crash. Phản hồi của LLM vốn dĩ là không tất định (non-deterministic). Mẫu thiết kế chuẩn xác: LLM call đi vào trong một Dapr Activity — nó chỉ chạy một lần, kết quả được lưu trữ lại, khi replay nó sẽ lôi kết quả từ cache ra đọc.

InferencePool có thực sự cần thiết không?

Không phải lúc nào cũng cần. Nếu bạn chỉ có một deployment vLLM duy nhất với lưu lượng traffic đồng đều, một Kubernetes Service là đã đủ xài. InferencePool chỉ cần thiết khi: bạn triển khai nhiều phiên bản mô hình hoặc nhiều LoRA adapters; bạn muốn giảm TTFT bằng thuật toán định tuyến nhận thức KV cache (KV cache-aware routing); xuất hiện hiện tượng GPU hotspotting (một pod bị nghẽn trong khi pod khác ngồi chơi); hoặc khi mức độ tận dụng GPU (GPU utilization) duy trì vượt quá ngưỡng 40%.

Làm sao để phát hiện một “vòng lặp tác nhân mất kiểm soát” (runaway agent loop) trên production?

Bạn cần 3 lớp phòng thủ: (1) thiết lập max_iterations trong framework của tác nhân — bắt buộc cho mọi tác nhân; (2) một cầu dao kiểm soát vận tốc token tại AI Gateway — nếu tác nhân xài lố X tokens/phút trong một phiên, hãy bắn lỗi 429 ngay lập tức; (3) phân tích độ tương đồng ngữ nghĩa (semantic similarity analysis) — phát hiện khi các output liên tiếp của tác nhân gần như giống hệt nhau (kẹt vòng lặp logic). Giới hạn đồng thời (Concurrency Limits) của Dapr Workflow là lớp chốt chặn thứ tư và cuối cùng.

🔗 Đọc thêm các chuyên đề liên quan: