Tuần trước tôi debug một agent LangGraph chạy production. Agent cứ loop vô tận — không error, không timeout, chỉ đơn giản là không dừng. Log đẹp, trace sạch, nhưng bill Anthropic API cuối tháng tăng gấp đôi. Sau khi đào sâu, tôi phát hiện ra một điều: production agents thất bại theo những cách hoàn toàn khác với prototype agents.
Theo survey mới nhất của LangChain (State of Agent Engineering 2026), 57% tổ chức đã có agents chạy production. Nhưng chưa đến 25% scale được thành công. Gap này không phải do model chưa đủ tốt — mà do chúng ta đang áp dụng mental model sai.
Stack của Agent khác Stack của Chatbot
Đây là điều đầu tiên cần internalize. Một chatbot cần: inference + RAG là xong. Nhưng một production agent cần:
- State management qua multi-step execution (không phải stateless request-response)
- Tool access với permission boundaries rõ ràng
- Memory persist across sessions (không phải context window tạm thời)
- Guardrails kiểm soát hành động thực tế, không chỉ text output
- Evaluation continuous, không phải one-time testing
Tôi đã thấy team deploy agent bằng cách lấy chatbot code và add thêm tool calls. Họ gặp đủ thứ: race condition trong state updates, tool calls không có idempotency, memory leak vì session không được cleanup. Đây không phải vấn đề model — đây là vấn đề kiến trúc.
Những Cạm Bẫy Thực Tế
1. LangGraph Loop Vô Tận
# Code ngây thơ — không có termination guard
def build_agent():
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("tools", tool_node)
graph.add_conditional_edges(
"agent",
should_continue, # Cái này có thể không bao giờ return False
)
return graph.compile()
Vấn đề: should_continue logic phụ thuộc vào model output. Nếu model bị stuck ở một reasoning loop — ví dụ cố gắng call một tool bị rate-limited rồi retry mãi — graph sẽ không có cơ chế thoát.
# Production-safe: thêm iteration guard
class AgentState(TypedDict):
messages: list
iteration_count: int # Thêm field này
def should_continue(state: AgentState):
if state["iteration_count"] > 20: # Hard limit
return "end"
if len(state["messages"]) == 0:
return "end"
last_message = state["messages"][-1]
if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
return "tools"
return "end"
def call_model(state: AgentState):
# Increment counter mỗi iteration
return {
**state,
"iteration_count": state.get("iteration_count", 0) + 1,
"messages": [...]
}
Tôi đặt hard limit ở 20 iterations cho most workflows, 50 cho research agents có nhiều tool calls. Chưa bao giờ có workflow hợp lệ nào cần hơn con số đó.
2. CrewAI Output Schema Không Nhất Quán
CrewAI dễ prototype nhưng output format là ác mộng ở production. Agent trả về free-form text thay vì structured data, và bạn chỉ phát hiện khi downstream system crash.
# Nguy hiểm — output là string tùy ý
researcher_task = Task(
description="Analyze this document and extract key metrics",
agent=researcher,
expected_output="A list of metrics with values"
)
# Production-safe — enforce schema
from pydantic import BaseModel
from typing import List
class MetricResult(BaseModel):
name: str
value: float
unit: str
confidence: float
class AnalysisOutput(BaseModel):
metrics: List[MetricResult]
summary: str
data_quality_score: float
researcher_task = Task(
description="Analyze this document and extract key metrics",
agent=researcher,
expected_output="Structured metrics data",
output_pydantic=AnalysisOutput # Enforce này
)
Khi output không match schema, CrewAI sẽ retry tự động (tốn thêm token). Nếu sau 3 lần vẫn không match, task fail với error rõ ràng — tốt hơn là silently trả về garbage data.
3. Evaluation Gap Là Rủi Ro Lớn Nhất
32% teams trong survey nói quality là production killer số 1. Và nguyên nhân chính: most prototypes have zero eval.
Tôi từng tự tin deploy một document processing agent vì nó hoạt động tốt trên 20 test cases. Production users tìm ra failure cases trong ngày đầu tiên. Vấn đề là tôi đang eval trên distribution quá hẹp.
Framework eval tôi hiện đang dùng:
# Eval framework đơn giản nhưng effective
import json
from dataclasses import dataclass
from typing import Callable, List
@dataclass
class EvalCase:
input: dict
expected_output: dict
category: str # "happy_path", "edge_case", "adversarial"
def run_eval(
agent_fn: Callable,
eval_cases: List[EvalCase],
scorer: Callable
) -> dict:
results = {"passed": 0, "failed": 0, "by_category": {}}
for case in eval_cases:
actual = agent_fn(case.input)
score = scorer(actual, case.expected_output)
if score > 0.8:
results["passed"] += 1
else:
results["failed"] += 1
cat = case.category
if cat not in results["by_category"]:
results["by_category"][cat] = {"passed": 0, "failed": 0}
if score > 0.8:
results["by_category"][cat]["passed"] += 1
else:
results["by_category"][cat]["failed"] += 1
return results
Quan trọng là categorize eval cases. Edge cases và adversarial inputs thường reveal vấn đề mà happy path tests che giấu.
4. Guardrails Cần Được Rethink Hoàn Toàn
Năm 2024, guardrails là input/output filter. Năm 2026, agent của bạn call tools, spend tiền, và thực hiện hành động. Guardrails bây giờ phải cover:
- Authorization: Agent có quyền call tool này không, trong context này không?
- Rate limiting: Bao nhiêu API calls trong 1 phút? Tổng spend limit/ngày?
- Action validation: Trước khi commit một action irreversible, có human-in-loop không?
- Audit trail: Log đủ chi tiết để reproduce và debug bất kỳ decision nào
class AgentGuardrails:
def __init__(self, config: dict):
self.max_api_calls_per_minute = config.get("rate_limit", 60)
self.max_daily_spend_usd = config.get("spend_limit", 10.0)
self.requires_human_approval = config.get("human_approval_tools", [])
self._call_count = 0
self._daily_spend = 0.0
def before_tool_call(self, tool_name: str, args: dict) -> bool:
"""Returns True if call should proceed"""
if self._call_count >= self.max_api_calls_per_minute:
raise RateLimitExceeded(f"Agent exceeded rate limit")
if tool_name in self.requires_human_approval:
approved = self._request_human_approval(tool_name, args)
if not approved:
return False
self._call_count += 1
return True
def after_tool_call(self, tool_name: str, cost_usd: float):
self._daily_spend += cost_usd
if self._daily_spend > self.max_daily_spend_usd:
raise SpendLimitExceeded(f"Daily spend limit reached: ${self._daily_spend:.2f}")
Điều Thực Sự Quan Trọng Ở Production
Sau khi debug đủ thứ, tôi đúc kết ra một nguyên tắc: Production agent không cần phải perfect — nó cần phải fail safely và be observable.
Cụ thể:
- Blast radius control: Limit what agent can do. Nếu agent bị compromised hoặc loop, thiệt hại tối đa là bao nhiêu?
- Detect uncertainty early: Khi agent không confident, route về human hoặc fail explicitly — đừng để nó “guess and hope”
- Continuous eval: Set up automated regression tests. Sau mỗi model update hoặc prompt change, chạy eval suite trước khi deploy
Khó nhất trong 3 cái này là “detect uncertainty early” — model thường không biết nó không biết gì. Trick tôi hay dùng là thêm một “confidence scoring” step sau reasoning, nơi model tự đánh giá confidence level của output trước khi commit action.
Kết Luận
Nếu bạn đang build agent production:
- Dùng LangGraph: Powerful nhưng phải add iteration guards và explicit termination conditions
- Dùng Pydantic models cho mọi agent output — đừng trust free-form text
- Build eval pipeline trước khi deploy, không phải sau khi user complain
- Redesign workflow thay vì layer agent lên legacy process
Agents không thất bại vì model kém — chúng thất bại vì chúng ta mang assumptions từ chatbot world vào agent world. Đó là hai vấn đề kỹ thuật khác nhau hoàn toàn.