首页 › AI Agent › Agent100道面试题 › 生产部署production-and-deployment › 代码 Review 题:找出这段 Agent 代码中的设计问题并修复

代码 Review 题:找出这段 Agent 代码中的设计问题并修复

难度:中级 分类:Production & Deployment​

简短回答​

Agent 代码的 Review 与普通业务代码有本质区别,因为 Agent 运行在一个自主循环(Autonomous Loop)中,每一轮迭代都涉及 LLM 调用和外部工具执行,错误代价远高于普通函数调用。一次合格的 Agent Code Review 至少需要检查六个维度:**循环保护(Loop Guard)**防止无限消耗 Token;**工具调用超时(Timeout)**避免单次调用阻塞整个 Agent;**结构化错误处理(Structured Error Handling)**而非简单吞掉异常;Prompt 版本管理确保可复现与可回滚;**可观测性(Observability)覆盖 logging、tracing、metrics 三大支柱;以及敏感操作确认(Human-in-the-Loop)**防止 Agent 自主执行破坏性操作。掌握这六个维度,就能系统性地发现初级 Agent 代码中的典型问题。​

详细解析​

问题代码:一个 "看起来能跑" 的 SimpleAgent​

以下代码能跑通 demo,但隐藏了六个生产级设计问题。请先通读,尝试自己找出问题。​

​

代码块​

Python

import json​

import openai​

​

class SimpleAgent:​

def __init__(self, model="gpt-4o"):​

self.model = model​

self.client = openai.OpenAI()​

self.messages = [​

{"role": "system", "content": "你是一个全能助手,可以调用工具完成任务。"​

"当用户让你做某件事时,直接调用对应工具执行。"​

"如果不确定,就多试几次。"}​

]​

self.tools = {​

"search_web": self.search_web,​

"read_file": self.read_file,​

"write_file": self.write_file,​

"delete_file": self.delete_file,​

"execute_sql": self.execute_sql,​

}​

​

def run(self, user_input: str) -> str:​

self.messages.append({"role": "user", "content": user_input})​

while True:​

response = self.client.chat.completions.create(​

model=self.model,​

messages=self.messages,​

tools=self._get_tool_schemas(),​

)​

msg = response.choices[0].message​

self.messages.append(msg)​

if msg.tool_calls:​

for tool_call in msg.tool_calls:​

name = tool_call.function.name​

args = json.loads(tool_call.function.arguments)​

try:​

​

问题逐项解析​

问题 1:无循环保护(No Loop Guard)​

while True 没有退出条件。LLM 若反复生成 tool_calls 而不返回最终回答(模型幻觉、prompt 缺陷),Agent 将无限循环消耗 Token,一次失控可能烧掉数百美元。​

​

代码块​

Python

# ✅ 修复:max_steps 保护​

def run(self, user_input: str, max_steps: int = 25) -> str:​

for step in range(max_steps):​

response = self.client.chat.completions.create(...)​

# ...正常逻辑...​

raise AgentLoopLimitError(f"Agent 在 {max_steps} 步后未完成")​

​

问题 2:工具调用无超时(No Timeout)​

requests.get 默认无超时,外部 API 无响应时阻塞整个进程,连带影响其他用户请求。​

​

代码块​

Plain Text

# ✅ 修复:httpx + 显式超时​

import httpx​

resp = httpx.get("https://api.search.com/v1/search",​

params={"q": query}, timeout=10.0)​

​

问题 3:错误处理太粗暴(Catch-All Swallowing)​

except Exception + print 存在三个缺陷:吞掉所有异常(含 MemoryError);print 无法被监控系统捕获;LLM 收到模糊的 Error 字符串,无法判断重试还是放弃。​

​

代码块​

Python

# ✅ 修复:分层捕获 + 结构化返回​

try:​

result = self.tools[name](**args)​

except KeyError:​

result = json.dumps({"error": "tool_not_found", "name": name})​

logger.error("未知工具: %s", name)​

except TimeoutError as e:​

​

问题 4:Prompt 硬编码(Hardcoded Prompt)​

System prompt 直接写在 init,无法 A/B 测试、无法版本管理、无法热更新。且 "如果不确定,就多试几次" 直接鼓励盲目重试,加剧循环失控。​

​

代码块​

Python

# ✅ 修复:Prompt 外部化 + 版本管理​

class PromptRegistry:​

def __init__(self, prompt_dir: str = "prompts/"):​

self.prompt_dir = Path(prompt_dir)​

​

def load(self, name: str, version: str = "latest") -> str:​

path = self.prompt_dir / f"{name}.{version}.yaml"​

with open(path) as f:​

​

问题 5:无可观测性(No Observability)​

零 logging、零 tracing、零 metrics。生产出问题时无法回溯 Agent 走了几步、调用了哪些工具、每步耗时和 Token 消耗。LLM 的不确定性使问题难以复现,可观测性是唯一的事后诊断手段。​

​

代码块​

Plain Text

# ✅ 修复:trace_id + 结构化日志​

trace_id = str(uuid.uuid4())[:8]​

logger.info("[%s] Step %d | latency=%.2fs | tokens=%d+%d",​

trace_id, step, llm_latency,​

usage.prompt_tokens, usage.completion_tokens)​

​

问题 6:敏感操作无确认(No Human-in-the-Loop)​

delete_file、execute_sql 直接执行,LLM 产生幻觉或遭遇 prompt injection 时,破坏性操作不可逆。​

​

代码块​

Python

# ✅ 修复:敏感工具标记 + 确认拦截​

SENSITIVE_TOOLS = {"delete_file", "execute_sql", "write_file"}​

​

def _execute_tool(self, name: str, args: dict) -> str:​

if name in SENSITIVE_TOOLS:​

if not self._request_human_approval(name, args):​

return json.dumps({"error": "rejected", "detail": "用户拒绝了此操作"})​

return self.tools[name](**args)​

​

修复后的完整代码​

​

代码块​

Python

import json, time, uuid, yaml, httpx, logging​

from pathlib import Path​

import openai​

​

logger = logging.getLogger(__name__)​

​

六大问题对比总结​

​

#WRvtLQaHJNrP text { white-space: pre; }#WRvtLQaHJNrP .fb0 { stroke: var(--N900-FG, 0.15); }#WRvtLQaHJNrP .fb0 {stroke: rgba(31,35,41, 0.15)}#WRvtLQaHJNrP .fx0 { stroke-miterlimit: 10; }#WRvtLQaHJNrP .fx0 {stroke-miterlimit: 10}#WRvtLQaHJNrP .fa1 { fill: rgb(187, 191, 196); }#WRvtLQaHJNrP .fa1 {fill: rgb(187, 191, 196)}#WRvtLQaHJNrP .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#WRvtLQaHJNrP .fx1 {font-style: normal}#WRvtLQaHJNrP .fx2 { text-anchor: middle; }#WRvtLQaHJNrP .fx2 {text-anchor: middle}#WRvtLQaHJNrP .fx3 { dominant-baseline: central; }#WRvtLQaHJNrP .fx3 {dominant-baseline: central}#WRvtLQaHJNrP .fx4 { stroke: none; }#WRvtLQaHJNrP .fx4 {stroke: none}#WRvtLQaHJNrP .fc0 { fill: var(--text-caption); }#WRvtLQaHJNrP .fc0 {fill: #646a73}#WRvtLQaHJNrP .fa2 { fill: var(--text-link-disabled); }#WRvtLQaHJNrP .fa2 {fill: rgb(187, 191, 196)}#WRvtLQaHJNrP .fx5 { font: 400 13px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#WRvtLQaHJNrP .fx5 {font-style: normal}#WRvtLQaHJNrP .fx6 { text-anchor: start; }#WRvtLQaHJNrP .fx6 {text-anchor: start}#WRvtLQaHJNrP .fx7 { dominant-baseline: alphabetic; }#WRvtLQaHJNrP .fx7 {dominant-baseline: alphabetic}#WRvtLQaHJNrP .fc1 { fill: rgb(0, 0, 0); }#WRvtLQaHJNrP .fc1 {fill: rgb(0, 0, 0)}#WRvtLQaHJNrP .fx8 { font: 400 13px LarkHackSafariFont, LarkEmojiFont, ui-monospace, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#WRvtLQaHJNrP .fx8 {font-style: normal}#WRvtLQaHJNrP .fb1 { fill: var(--N900-FG, 0.15); }#WRvtLQaHJNrP .fb1 {fill: rgba(31,35,41, 0.15)}#WRvtLQaHJNrP .fb2 { fill: rgb(209, 217, 224); }#WRvtLQaHJNrP .fb2 {fill: rgb(209, 217, 224)}#WRvtLQaHJNrP .fa3 { fill: var(--bg-body); }#WRvtLQaHJNrP .fa3 {fill: #fff}ABCDE1234567#123456问题无循环保护工具无超时错误处理粗暴Prompt 硬编码无可观测性敏感操作无确认原始代码 无退出条件 无 timeout + 字符串写死在 零日志、零追踪 直接执行修复方案 + 异常分层捕获 + + JSON 返回 外部加载 + 版本管理 + + Token 埋点 + Human-in-the-Loop风险等级严重严重中等中等中等严重while Truerequests.get()except Exceptionprint__init__delete_filefor step in range(max_steps)httpx.get(timeout=10.0)loggingPromptRegistrytrace_idloggingSENSITIVE_TOOLS

​

Agent Review 与普通 Review 的核心差异​

​

​

Agent 代码因此多出六个额外 Review 维度:​

•

循环边界​

•

成本上限​

•

权限 & 安全​

•

可观测性​

•

Prompt 版本管理​

•

超时 & 重试​

常见误区 / 面试追问​

误区:"Agent 代码和普通代码的 Review 标准一样" — Agent 代码的核心特征是非确定性循环 + 外部副作用。普通代码 Review 关注逻辑正确性和代码风格,但 Agent 代码必须额外审查循环边界、成本控制、权限隔离、可观测性等维度。忽略这些维度的 Review 等于没有 Review,建议维护专门的 Agent Code Review Checklist。​

误区:"加了 try-catch 就算处理了错误" — except Exception 配 print 是最危险的"伪处理"。生产级错误处理需要:分层捕获(区分可重试与致命错误)、结构化返回(让 LLM 根据错误类型决策)、持久化记录(logging 而非 print)。Agent 的错误信息会反馈给 LLM,格式化 JSON 远比 "Error: something went wrong" 有用。​

追问:"如何设计 Agent 代码审查 Checklist?" — 可按 SECURE 助记法组织:Steps Limit(最大步数)、Error Handling(分层处理 + 结构化返回)、Cost Guard(Token / 费用上限)、User Confirmation(敏感操作确认)、Replay & Trace(trace_id 可回溯)、External Timeout(外部调用超时)。每次 PR Review 逐条过一遍即可系统性覆盖。​

追问:"如何为 Agent 编写单元测试?" — 分三层:第一层工具函数单测,Mock 外部依赖验证正常 / 超时 / 异常行为;第二层 Agent 循环单测,Mock LLM 返回固定 tool_calls 序列,验证 max_steps 触发和敏感工具拦截;第三层集成 Eval,真实 LLM + 沙箱环境验证端到端完成率。关键原则:不断言 LLM 的具体输出,而是断言 Agent 的行为属性——是否限步完成、是否拦截危险操作、是否生成完整 trace。​