引言:演示到生产之间,隔着一整个架构
最近技术圈有个现象:自主编程 Agent 的热度在回落。去年大家还在惊呼"AI 自己写完了一个项目",今年却开始讨论"为什么我们的 Agent 总在第三步崩溃"。这种落差不是模型能力不足,而是架构设计没跟上。
一个典型的自主编程 Agent 演示是这样的:给它一个需求描述,它自动拆解任务、读写文件、执行命令、运行测试,最后交付可运行的代码。演示视频里一切行云流水。但当你真的把它接到项目里,问题就开始了------上下文越滚越长、工具调用不受控、中间状态丢失、一个报错就让整个流程卡死。
这些问题的根源不在模型,而在 Agent 框架的架构选择。本文从四个核心维度拆解:执行循环、状态管理、工具治理、错误恢复,帮你理解从"能演示"到"能上线"到底差在哪。
一、执行循环:从无限递归到有界状态机
大多数 Agent 框架的执行循环长这样:
python
# ❌ 危险的无限循环模式
def run_agent(task):
messages = [{"role": "user", "content": task}]
while True:
response = llm.chat(messages)
if response.finish_reason == "stop":
break
# 执行工具调用
for tool_call in response.tool_calls:
result = execute_tool(tool_call)
messages.append({"role": "tool", "content": result})
messages.append(response)
return response.content
这段代码在演示中没问题,但在生产环境是灾难。没有步数上限、没有预算控制、没有循环检测------Agent 可能在同一个错误上反复重试 50 次,烧光你的 API 额度。
正确的做法是用有界状态机替代无限循环:
python
from enum import Enum
from dataclasses import dataclass
class AgentState(Enum):
PLANNING = "planning"
EXECUTING = "executing"
REVIEWING = "reviewing"
DONE = "done"
FAILED = "failed"
@dataclass
class AgentConfig:
max_steps: int = 30
max_tokens_per_step: int = 4000
max_total_tokens: int = 80000
max_retries_per_error: int = 2
cost_budget_usd: float = 2.0
class BoundedAgent:
def __init__(self, config: AgentConfig):
self.config = config
self.state = AgentState.PLANNING
self.step_count = 0
self.total_tokens = 0
self.total_cost = 0.0
self.error_counts = {} # 每个错误的连续重试次数
def run(self, task: str) -> str:
while self.state not in (AgentState.DONE, AgentState.FAILED):
self._check_limits()
self._execute_step(task)
if self.state == AgentState.FAILED:
return self._generate_failure_report()
return self._get_result()
def _check_limits(self):
"""每次执行前检查所有预算边界"""
if self.step_count >= self.config.max_steps:
self.state = AgentState.FAILED
raise AgentLimitExceeded(f"超过最大步数 {self.config.max_steps}")
if self.total_tokens >= self.config.max_total_tokens:
self.state = AgentState.FAILED
raise AgentLimitExceeded(f"超过 token 预算 {self.config.max_total_tokens}")
if self.total_cost >= self.config.cost_budget_usd:
self.state = AgentState.FAILED
raise AgentLimitExceeded(f"超过成本预算 ${self.config.cost_budget_usd}")
def _execute_step(self, task: str):
self.step_count += 1
try:
response = self.llm.chat(self.messages)
self.total_tokens += response.usage.total_tokens
self.total_cost += self._calculate_cost(response.usage)
if self._detect_loop(response):
# 检测到循环行为,注入提示让 Agent 换方向
self.messages.append({
"role": "system",
"content": "你似乎在重复相同的操作。请尝试不同的方法。"
})
return
self._process_response(response)
except Exception as e:
error_key = type(e).__name__
self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1
if self.error_counts[error_key] > self.config.max_retries_per_error:
self.state = AgentState.FAILED
else:
self._inject_error_context(e)
关键改进点:
- 多维度预算控制:不只是步数,还有 token 总量、成本上限
- 循环检测:记录最近 N 步的工具调用签名,发现重复模式时主动打断
- 错误分类重试:不同错误有独立的重试计数,避免某类错误耗尽全局重试预算
二、状态管理:Checkpoint 是生产 Agent 的生命线
演示型 Agent 通常是无状态的------如果中途崩溃,从头来过。但生产环境中,一个复杂任务可能执行 20 分钟、消耗几美元的 API 调用。中途崩溃后重来是不可接受的。
核心思路:把 Agent 的执行过程建模为可序列化的检查点。
typescript
interface AgentCheckpoint {
step: number;
state: AgentState;
messages: Message[];
working_memory: Record<string, unknown>;
tool_results: ToolResult[];
timestamp: number;
token_usage: number;
cost_usd: number;
}
class CheckpointManager {
private storage: CheckpointStorage;
private checkpointInterval: number = 5; // 每5步存一次
constructor(storage: CheckpointStorage) {
this.storage = storage;
}
async save(checkpoint: AgentCheckpoint): Promise<void> {
await this.storage.write(
checkpoint.step.toString(),
JSON.stringify(checkpoint)
);
// 同时保留最近3个检查点,防止写入损坏
await this.storage.prune(3);
}
async load(step?: number): Promise<AgentCheckpoint | null> {
if (step) {
const data = await this.storage.read(step.toString());
return data ? JSON.parse(data) : null;
}
// 加载最新检查点
return await this.storage.readLatest();
}
}
// 恢复执行
async function resumeAgent(taskId: string): Promise<string> {
const checkpoint = await checkpointManager.load();
if (!checkpoint) {
return runAgent(taskId); // 没有检查点,从头开始
}
console.log(`从第 ${checkpoint.step} 步恢复,已用 ${checkpoint.token_usage} tokens`);
const agent = new BoundedAgent(config);
agent.state = checkpoint.state;
agent.messages = checkpoint.messages;
agent.step_count = checkpoint.step;
agent.total_tokens = checkpoint.token_usage;
agent.total_cost = checkpoint.cost_usd;
return agent.run(checkpoint.messages[0].content);
}
这套机制的价值在于:
- 崩溃恢复:进程被 kill、网络中断、API 限流,都能从最近检查点续跑
- 调试回放:出问题时可以加载任意检查点,复现当时的 Agent 状态
- 成本审计:每个检查点都记录了 token 消耗和成本,方便事后分析
三、工具治理:给 Agent 加上"刹车系统"
自主编程 Agent 最危险的环节是工具调用。一个不受控的 Agent 可能删除重要文件、执行危险命令、或者对同一个 API 端点发起数百次请求。
很多框架的默认做法是"白名单 + 确认弹窗",但这远远不够。生产级 Agent 需要一个工具治理层:
python
from dataclasses import dataclass
from typing import Callable, Any
from enum import Enum
class RiskLevel(Enum):
SAFE = 0 # 只读操作,如读取文件、搜索代码
MODERATE = 1 # 写操作,如创建文件、修改配置
DANGEROUS = 2 # 不可逆操作,如删除文件、执行 shell 命令、发送网络请求
@dataclass
class ToolPolicy:
name: str
risk_level: RiskLevel
rate_limit_per_minute: int
max_consecutive_calls: int
requires_confirmation: bool
rollback_handler: Callable | None = None # 危险操作的回滚函数
class ToolGovernor:
def __init__(self):
self.policies: dict[str, ToolPolicy] = {}
self.call_history: dict[str, list[float]] = {} # tool_name -> [timestamps]
self.consecutive_counts: dict[str, int] = {}
def register(self, policy: ToolPolicy):
self.policies[policy.name] = policy
self.call_history[policy.name] = []
self.consecutive_counts[policy.name] = 0
def check(self, tool_name: str) -> tuple[bool, str]:
"""执行前的多重检查"""
policy = self.policies.get(tool_name)
if not policy:
return False, f"未注册的工具: {tool_name}"
now = time.time()
# 1. 速率限制
recent = [t for t in self.call_history[tool_name] if now - t < 60]
if len(recent) >= policy.rate_limit_per_minute:
return False, f"超过速率限制: {policy.rate_limit_per_minute}/min"
# 2. 连续调用限制(防止 Agent 陷入循环)
if self.consecutive_counts[tool_name] >= policy.max_consecutive_calls:
return False, f"连续调用超过上限: {policy.max_consecutive_calls}"
# 3. 危险操作需要人工确认
if policy.risk_level == RiskLevel.DANGEROUS and policy.requires_confirmation:
approved = self._request_human_approval(tool_name)
if not approved:
return False, "人工审核未通过"
# 记录调用
self.call_history[tool_name].append(now)
self.consecutive_counts[tool_name] += 1
return True, "approved"
def reset_consecutive(self, tool_name: str):
"""当 Agent 切换到不同工具时,重置连续计数"""
for name in self.consecutive_counts:
if name != tool_name:
self.consecutive_counts[name] = 0
这套治理层做了三件事:
- 风险分级:只读操作自动放行,写操作限流,危险操作需人工确认
- 循环检测:同一工具连续调用超过阈值时自动熔断
- 可回滚:危险操作注册了回滚函数,出错时可以撤销
四、错误恢复:让 Agent 学会"止损"而不是"硬刚"
新手 Agent 遇到错误时的典型行为:看到报错 → 修改代码 → 再跑 → 又报错 → 再改 → 循环 20 次 → token 耗尽。
成熟的 Agent 应该有错误分级处理策略:
python
class ErrorRecoveryStrategy:
"""根据错误类型选择不同的恢复策略"""
def handle_error(self, error: Exception, context: AgentContext) -> RecoveryAction:
error_type = type(error).__name__
# 策略1: 编译/类型错误 → 让 Agent 自己修
if isinstance(error, (SyntaxError, TypeError)):
if context.retry_count < 2:
return RetryWithHint(
hint=f"编译错误: {error}. 请检查类型匹配和语法。"
)
return EscalateToHuman("连续2次编译错误,可能需要人工介入")
# 策略2: 依赖缺失 → 自动安装而不是反复重试
if self._is_dependency_error(error):
package = self._extract_package_name(error)
return RunSubtask(
f"安装缺失依赖: pip install {package}",
on_success="retry_original_task"
)
# 策略3: 权限错误 → 不要让 Agent 尝试 sudo
if isinstance(error, PermissionError):
return Abort(
reason="权限不足,不应尝试提权操作",
suggestion="请检查文件权限或运行环境配置"
)
# 策略4: 网络超时 → 指数退避重试
if isinstance(error, (TimeoutError, ConnectionError)):
delay = min(2 ** context.retry_count, 30)
return RetryAfterDelay(delay_seconds=delay)
# 策略5: 未知错误 → 保存状态,转人工
return EscalateToHuman(
f"未知错误类型 {error_type}: {str(error)[:200]}"
)
这个策略的核心理念是:不是所有错误都应该重试。权限错误应该直接终止,依赖缺失应该先解决依赖再重试,只有可恢复的运行时错误才值得指数退避重试。
五、实战检查清单
把上面的架构理念落地,我总结了一份生产级 Agent 的检查清单:
| 维度 | 检查项 | 不达标的风险 |
|---|---|---|
| 执行循环 | 有最大步数限制 | Agent 陷入死循环,烧光预算 |
| 执行循环 | 有 token / 成本预算 | 单次任务花费失控 |
| 执行循环 | 有循环行为检测 | 重复执行相同无效操作 |
| 状态管理 | 定期保存检查点 | 崩溃后无法恢复,浪费已完成的工作 |
| 状态管理 | 检查点可序列化完整状态 | 恢复后状态不一致 |
| 工具治理 | 工具有风险分级 | 危险操作未经验证直接执行 |
| 工具治理 | 有速率限制和熔断 | 对外部 API 发起异常请求 |
| 工具治理 | 危险操作可回滚 | 不可逆破坏后无法恢复 |
| 错误恢复 | 错误有分级处理策略 | 对不可恢复的错误反复重试 |
| 错误恢复 | 有人工升级通道 | 遇到未知问题无人兜底 |
总结:Agent 不是更聪明的 Chatbot,是有约束的系统
回到开头的问题:为什么自主编程 Agent 的热度在回落?不是因为方向错了,而是因为很多人把 Agent 理解成了"一个更聪明的 Chatbot 加上几个工具调用"。这种理解在演示场景下足够,但在生产环境中远远不够。
生产级 Agent 的核心不是模型能力,而是系统设计的约束力。执行循环要有边界,状态要可持久化,工具要受治理,错误要能分级恢复。这些不是"锦上添花"的工程实践,而是"有和没有"之间差着整个可用性鸿沟的架构基石。
当我们讨论 Agent 的未来时,与其期待下一个更强大的模型,不如先把架构基础打好。毕竟,再好的引擎,装在一辆没有刹车系统的车上,也只能在演示赛道上跑两圈。