如果你今天还在用"给LLM加几个工具调用"来描述你的Agent,那我们需要认真谈谈了。

2026年的AI工程现实是:绝大多数Agent项目死在了从Demo到生产的路上。不是因为模型不够强,而是因为工程没跟上。本文会系统梳理Agent工程化的核心路径,从基础架构到可观测性,从错误处理到成本控制,给你一份可直接参考的生产级指南。

为什么Demo能跑、生产就崩

先说痛点。一个典型的Agent Demo通常长这样:一个System Prompt,几个工具函数,一个while循环,完事。在测试集上跑得飞起,一到真实用户手里就各种翻车。

原因其实很明确:

输入分布偏移。测试时你知道用户会问什么,生产环境用户问的是你完全没预料到的东西。Agent的规划能力在面对奇怪输入时会快速退化。

工具调用失败的传导效应。一个工具返回了意外格式,Agent不知道怎么处理,然后开始幻觉,然后调用下一个错误的工具,雪球越滚越大。

上下文窗口的隐形炸弹。多轮对话跑久了,工具调用的结果积累在上下文里,Token越来越贵,模型注意力越来越分散,最后答非所问。

缺少可观测性。你不知道Agent在哪个步骤出问题,不知道为什么它做了那个决策,出了问题只能瞪着日志发呆。

生产级Agent的架构基础

明确区分协调层和执行层

这是最关键的架构决策。协调层(Orchestrator)负责规划、决策、工具选择;执行层(Executor)负责具体的工具调用和结果处理。两者不要混在一起。

class AgentOrchestrator:
    """协调层:只负责规划和决策"""
        
            def __init__(self, llm_client, tool_registry):
                    self.llm = llm_client
                            self.tools = tool_registry
                                    self.plan_history = []
                                        
                                            async def plan_next_action(self, goal: str, context: dict) -> Action:
                                                    """根据目标和当前上下文,规划下一步行动"""
                                                            prompt = self._build_planning_prompt(goal, context)
                                                                    response = await self.llm.complete(prompt)
                                                                            return self._parse_action(response)
                                                                                
                                                                                    def _build_planning_prompt(self, goal: str, context: dict) -> str:
                                                                                            # 包含:目标、已完成步骤、可用工具、当前状态
                                                                                                    available_tools = self.tools.list_tools()
                                                                                                            completed_steps = context.get("completed_steps", [])
                                                                                                                    
                                                                                                                            return f"""
                                                                                                                            你是一个任务规划器。你的目标是:{goal}
已完成的步骤:
{self._format_steps(completed_steps)}

可用工具:
{self._format_tools(available_tools)}

基于当前状态,请规划下一步行动。如果任务已完成,返回 DONE。
以JSON格式返回:{{"action": "tool_name", "params": {{...}}, "reasoning": "..."}}
"""


class ToolExecutor:
    """执行层:只负责工具调用和错误处理"""
        
            def __init__(self, tools: dict):
                    self.tools = tools
                            self.retry_config = RetryConfig(max_retries=3, backoff_factor=2)
                                
                                    async def execute(self, action: Action) -> ToolResult:
                                            tool = self.tools.get(action.name)
                                                    if not tool:
                                                                return ToolResult.error(f"Unknown tool: {action.name}")
                                                                        
                                                                                for attempt in range(self.retry_config.max_retries):
                                                                                            try:
                                                                                                            result = await tool.call(action.params)
                                                                                                                            return ToolResult.success(result)
                                                                                                                                        except ToolTimeout:
                                                                                                                                                        if attempt == self.retry_config.max_retries - 1:
                                                                                                                                                                            return ToolResult.error("Tool timeout after retries")
                                                                                                                                                                                            await asyncio.sleep(self.retry_config.backoff_factor ** attempt)
                                                                                                                                                                                                        except ToolError as e:
                                                                                                                                                                                                                        return ToolResult.error(str(e))  # 不重试业务错误
                                                                                                                                                                                                                        ```
这种分离让你可以独立优化每一层,也让测试变得更简单。

### 状态管理:不只是记录对话历史

生产级Agent需要显式的状态管理,而不是把所有东西都塞进对话历史。

```python
@dataclass
class AgentState:
    session_id: str
        goal: str
            status: Literal["planning", "executing", "waiting", "done", "failed"]
                
                    # 执行进度
                        completed_steps: list[StepResult] = field(default_factory=list)
                            current_step: Optional[Step] = None
                                
                                    # 上下文(严格控制大小)
                                        working_memory: dict = field(default_factory=dict)  # 当前任务相关的临时数据
                                            
                                                # 统计
                                                    token_usage: int = 0
                                                        tool_call_count: int = 0
                                                            start_time: float = field(default_factory=time.time)
                                                                
                                                                    @property
                                                                        def elapsed_seconds(self) -> float:
                                                                                return time.time() - self.start_time
                                                                                    
                                                                                        def add_step_result(self, result: StepResult):
                                                                                                self.completed_steps.append(result)
                                                                                                        # 自动摘要:只保留最近N步的完整结果,更早的压缩成摘要
                                                                                                                if len(self.completed_steps) > 10:
                                                                                                                            self._compress_early_steps()
                                                                                                                                
                                                                                                                                    def _compress_early_steps(self):
                                                                                                                                            """把早期步骤压缩成摘要,避免上下文无限增长"""
                                                                                                                                                    early_steps = self.completed_steps[:5]
                                                                                                                                                            summary = f"已完成{len(early_steps)}个早期步骤:" + ";".join(
                                                                                                                                                                        s.summary for s in early_steps
                                                                                                                                                                                )
                                                                                                                                                                                        self.completed_steps = [StepResult.summary(summary)] + self.completed_steps[5:]
                                                                                                                                                                                        ```
### 工具契约:防御性设计

每个工具都应该有明确的输入输出契约,并且在边界处做好防御:

```python
from pydantic import BaseModel, validator

class SearchToolInput(BaseModel):
    query: str
        max_results: int = 5
            
                @validator("query")
                    def query_not_empty(cls, v):
                            if not v.strip():
                                        raise ValueError("搜索词不能为空")
                                                return v.strip()[:500]  # 截断过长的查询
                                                    
                                                        @validator("max_results")
                                                            def results_in_range(cls, v):
                                                                    return max(1, min(v, 20))  # 强制限制范围

class SearchToolOutput(BaseModel):
    results: list[SearchResult]
        total_found: int
            search_took_ms: int
                
                    # 提供给Agent的结构化摘要
                        def to_agent_context(self) -> str:
                                if not self.results:
                                            return "搜索未找到相关结果"
                                                    return f"找到{self.total_found}条结果,以下是前{len(self.results)}条:\n" + \
                                                                   "\n".join(f"- {r.title}: {r.snippet}" for r in self.results)
                                                                   ```
## 可观测性:让Agent不再是黑盒

没有可观测性,你就是在盲飞。生产级Agent必须能回答这几个问题:

- 这次任务Agent做了哪些决策,理由是什么?
- - 哪个步骤花了最多时间和Token?
- - 失败是在哪里发生的?
### Trace设计

```python
import uuid
from contextlib import asynccontextmanager

class AgentTracer:
    def __init__(self, backend):  # backend可以是Langfuse、自建系统等
            self.backend = backend
                
                    @asynccontextmanager
                        async def trace_session(self, session_id: str, goal: str):
                                trace = Trace(
                                            id=session_id,
                                                        goal=goal,
                                                                    start_time=time.time()
                                                                            )
                                                                                    try:
                                                                                                yield trace
                                                                                                            trace.status = "success"
                                                                                                                    except Exception as e:
                                                                                                                                trace.status = "failed"
                                                                                                                                            trace.error = str(e)
                                                                                                                                                        raise
                                                                                                                                                                finally:
                                                                                                                                                                            trace.end_time = time.time()
                                                                                                                                                                                        await self.backend.save(trace)
                                                                                                                                                                                            
                                                                                                                                                                                                @asynccontextmanager
                                                                                                                                                                                                    async def trace_step(self, trace: Trace, step_name: str, **metadata):
                                                                                                                                                                                                            step = Step(
                                                                                                                                                                                                                        id=str(uuid.uuid4()),
                                                                                                                                                                                                                                    name=step_name,
                                                                                                                                                                                                                                                metadata=metadata,
                                                                                                                                                                                                                                                            start_time=time.time()
                                                                                                                                                                                                                                                                    )
                                                                                                                                                                                                                                                                            try:
                                                                                                                                                                                                                                                                                        yield step
                                                                                                                                                                                                                                                                                                    step.status = "success"
                                                                                                                                                                                                                                                                                                            except Exception as e:
                                                                                                                                                                                                                                                                                                                        step.status = "failed"
                                                                                                                                                                                                                                                                                                                                    step.error = str(e)
                                                                                                                                                                                                                                                                                                                                                raise
                                                                                                                                                                                                                                                                                                                                                        finally:
                                                                                                                                                                                                                                                                                                                                                                    step.end_time = time.time()
                                                                                                                                                                                                                                                                                                                                                                                trace.add_step(step)
                                                                                                                                                                                                                                                                                                                                                                                ```
实际使用时,每个规划决策和工具调用都包在trace里:

```python
async with tracer.trace_session(session_id, goal) as trace:
    while not done:
            async with tracer.trace_step(trace, "planning", 
                                                  context_size=len(state.completed_steps)) as step:
                                                              action = await orchestrator.plan_next_action(goal, state)
                                                                          step.record_llm_call(tokens=action.tokens_used, model=action.model)
                                                                                  
                                                                                          async with tracer.trace_step(trace, f"tool:{action.name}",
                                                                                                                                params=action.params) as step:
                                                                                                                                            result = await executor.execute(action)
                                                                                                                                                        step.record_result(result)
                                                                                                                                                        ```
## 成本控制:让Agent可持续

Token费用是Agent上生产后的第一个噩梦。几个实用策略:

**上下文压缩策略**。不要把每次工具调用的完整响应都放进上下文。设计一个摘要函数,把长结果压缩成关键信息:

```python
class ContextManager:
    MAX_CONTEXT_TOKENS = 8000  # 为规划留出足够空间
        
            def build_context(self, state: AgentState) -> str:
                    context_parts = []
                            
                                    # 目标始终保留
                                            context_parts.append(f"目标:{state.goal}")
                                                    
                                                            # 已完成步骤:最近3步保留完整,更早的只保留摘要
                                                                    recent = state.completed_steps[-3:]
                                                                            earlier = state.completed_steps[:-3]
                                                                                    
                                                                                            if earlier:
                                                                                                        summaries = [s.summary for s in earlier]
                                                                                                                    context_parts.append(f"早期步骤(已摘要):{'; '.join(summaries)}")
                                                                                                                            
                                                                                                                                    for step in recent:
                                                                                                                                                context_parts.append(f"步骤 {step.name}{step.result_text}")
                                                                                                                                                        
                                                                                                                                                                context = "\n\n".join(context_parts)
                                                                                                                                                                        
                                                                                                                                                                                # Token超限时进一步压缩
                                                                                                                                                                                        if self._estimate_tokens(context) > self.MAX_CONTEXT_TOKENS:
                                                                                                                                                                                                    context = self._emergency_compress(context)
                                                                                                                                                                                                            
                                                                                                                                                                                                                    return context
                                                                                                                                                                                                                    ```
**工具调用缓存**。同样的工具调用不要重复执行:

```python
class CachedToolExecutor:
    def __init__(self, executor: ToolExecutor, cache_ttl: int = 300):
            self.executor = executor
                    self.cache = {}
                            self.cache_ttl = cache_ttl
                                
                                    async def execute(self, action: Action) -> ToolResult:
                                            # 只缓存幂等的工具(搜索、查询等),不缓存写操作
                                                    if not action.is_cacheable:
                                                                return await self.executor.execute(action)
                                                                        
                                                                                cache_key = f"{action.name}:{json.dumps(action.params, sort_keys=True)}"
                                                                                        
                                                                                                if cache_key in self.cache:
                                                                                                            entry = self.cache[cache_key]
                                                                                                                        if time.time() - entry["time"] < self.cache_ttl:
                                                                                                                                        return entry["result"]
                                                                                                                                                
                                                                                                                                                        result = await self.executor.execute(action)
                                                                                                                                                                self.cache[cache_key] = {"result": result, "time": time.time()}
                                                                                                                                                                        return result
                                                                                                                                                                        ```
## 错误处理:给Agent一个降级策略

Agent在生产环境会遇到各种意外情况,必须为每种失败模式设计明确的处理策略:

```python
class AgentRunner:
    
        async def run(self, goal: str, session_id: str) -> AgentResult:
                state = AgentState(session_id=session_id, goal=goal)
                        
                                async with self.tracer.trace_session(session_id, goal):
                                            while True:
                                                            # 安全边界检查
                                                                            if state.tool_call_count > 50:
                                                                                                return AgentResult.failed(
                                                                                                                        reason="exceeded_tool_limit",
                                                                                                                                                partial_result=self._extract_partial_result(state)
                                                                                                                                                                    )
                                                                                                                                                                                    
                                                                                                                                                                                                    if state.elapsed_seconds > 300:  # 5分钟超时
                                                                                                                                                                                                                        return AgentResult.failed(
                                                                                                                                                                                                                                                reason="timeout",
                                                                                                                                                                                                                                                                        partial_result=self._extract_partial_result(state)
                                                                                                                                                                                                                                                                                            )
                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                            try:
                                                                                                                                                                                                                                                                                                                                                action = await self.orchestrator.plan_next_action(goal, state)
                                                                                                                                                                                                                                                                                                                                                                except LLMError as e:
                                                                                                                                                                                                                                                                                                                                                                                    # LLM调用失败:等待后重试,最多3次
                                                                                                                                                                                                                                                                                                                                                                                                        if state.llm_errors < 3:
                                                                                                                                                                                                                                                                                                                                                                                                                                state.llm_errors += 1
                                                                                                                                                                                                                                                                                                                                                                                                                                                        await asyncio.sleep(5)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                continue
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    return AgentResult.failed(reason="llm_unavailable")
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    if action.is_done:
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        return AgentResult.success(state)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        result = await self.executor.execute(action)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        # 工具失败:通知Agent,让它决策如何继续
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        if not result.ok:
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            state.add_step_result(StepResult(
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    step=action,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            result=f"工具调用失败:{result.error}。请考虑替代方案。"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                else:
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    state.add_step_result(StepResult(step=action, result=result.data))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ```
## 从这里开始的实践建议

1. **先跑通最简单的任务,建立基线**。不要一上来就构建通用Agent,先找一个具体场景,把它做到生产可靠。
2. **从第一天就接入可观测性**。Langfuse是个不错的选择,开源可自建。不要等到出问题再加。
3. **设置硬性的安全边界**。工具调用上限、时间上限、Token上限,每一个都要有,不能让Agent无限跑。
4. **测试要包含对抗性输入**。专门设计一批会让Agent迷惑的输入,纳入你的回归测试集。
5. **记录每一次生产失败**。建立一个失败案例库,每次新的异常都要分析根因,更新你的错误处理策略。
Agent工程化不是一天就能完成的事,但架构方向对了,后面每一步优化都会积累成真正的竞争壁垒。

---

*本文关键词:Agent工程、生产级AI、LLM工具调用、可观测性、成本控制*

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐