Agent工程2026:从提示词堆砌到生产级智能体的完整跃迁路径
·
如果你今天还在用"给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工具调用、可观测性、成本控制*
更多推荐



所有评论(0)