Agent怎么学?先做一个会暴露问题的真实项目
聊《Agent怎么学?先做一个会暴露问题的真实项目》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。
摘要
去年帮一个电商团队做Agent项目,Demo阶段跑得很顺。模型能查库存、能改价格、能推订单,业务方拍板说"就这么上"。
结果上线第一周,权限问题炸了三次。
第一次是Agent擅自调了退款接口,客服群里收到三条不合规的退款记录;第二次是模型"幻觉"出的接口参数,把订单状态改成了乱码;第三次最离谱,Agent在日志里留了用户的手机号明文,被安全团队直接封了。
事后复盘,团队里懂Agent原理的人不多。工具调用谁都会,调个OpenAPI的事。但权限边界在哪、日志怎么留、失败怎么恢复,这些才是生产环境的硬门槛。
这篇文章不想讲理论,想从那次联调失败里,把Agent的底层机制拆清楚,顺便说说怎么避坑。
---
目录
- Agent的本质:不是聊天机器人,是执行系统
- 规划能力:模型会"想",但不一定"想对"
- 工具调用:调API谁都会,关键是权限边界
- 记忆系统:上下文不是无限的,也不是免费的
- 失败恢复:Demo能跑不代表能兜底
- 权限和日志:上线后的真正硬仗
- 总结:Agent不是模型调得好就行
Agent的本质:不是聊天机器人,是执行系统
很多人对Agent的理解还停留在"能对话的机器人"。这没错,但不完整。
Agent的核心是自主执行。它能感知环境、做出决策、调用工具、拿到结果、继续决策。这是一个闭环,不是单次问答。
我见过的最直观的区分方式:
聊天机器人:用户问 → 模型答 → 结束 Agent:用户问 → 模型分析 → 调用工具 → 拿到结果 → 再次分析 → 再次调用 → 输出最终答案这个区别看起来简单,但直接影响架构设计。聊天机器人只需要一个模型接口,Agent需要规划器 + 工具集 + 记忆系统 + 执行引擎四个模块协同工作。
我们当时的项目,规划器用的是ReAct模式,工具集接了库存、订单、客服三个系统的API,记忆系统用了简单的Redis缓存,执行引擎是LangGraph写的状态机。
Demo阶段,这四个模块配合得还不错。但一上生产,问题就出来了。
---
规划能力:模型会"想",但不一定"想对"
规划是Agent最容易被高估的能力。
很多人以为把任务丢给大模型,模型就会自动拆解、执行、验证。实际上,模型只是在做概率预测,它没有真正的推理能力。
ReAct模式(Reasoning + Acting)是目前最常用的规划框架,思路很简单:让模型在每次调用工具前,先输出自己的思考过程。
# ReAct模式的典型循环 while not finished: # 1. 模型根据当前状态生成思考 thought = llm.generate(state, memory) # 2. 解析思考,判断是否需要调用工具 if "工具调用" in thought: tool_name, params = parse_tool_call(thought) result = call_tool(tool_name, params) state.append(f"工具{tool_name}返回: {result}") else: # 3. 模型直接输出最终答案 answer = llm.generate_final(thought) finished = True这个循环看起来简单,但有几个坑:
第一个坑是工具参数校验。 模型生成的参数经常不符合接口要求。我们当时有一个订单查询接口,要求order_id是16位数字,模型经常生成类似"订单12345"这种带前缀的字符串。结果就是接口报错,Agent卡在循环里出不来。
解决办法很简单:在调用工具前加一层参数校验。
def validate_order_params(params): if not params.get("order_id", "").isdigit() or len(params["order_id"]) != 16: raise ValueError(f"订单ID格式错误: {params.get('order_id')}") return True # 调用前校验 if tool_name == "query_order": validate_order_params(params) result = call_tool(tool_name, params)第二个坑是规划深度。 模型不是无限推理的,它有自己的"思考预算"。任务越复杂,模型越容易在中间步骤"迷路",输出无意义的循环。
我们的解决方案是限制最大步骤数,超时直接返回错误。
MAX_STEPS = 10 step_count = 0 while step_count < MAX_STEPS: step_count += 1 # ... 执行逻辑 ... else: return "任务执行步骤过多,可能陷入循环"第三个坑是规划不可观测。 这是最致命的。模型在"思考"什么,调用什么工具,为什么调用,这些在Demo阶段没人关心。但生产环境出了问题,你连排查路径都没有。
我们后来给规划器加了一套日志:
import logging logger = logging.getLogger("agent_planner") def log_planning_step(step, thought, tool_call=None, tool_result=None): logger.info({ "step": step, "thought": thought, "tool_call": tool_call, "tool_result": tool_result, "timestamp": datetime.now().isoformat() })这套日志后来成了排查问题的救命稻草。
---
工具调用:调API谁都会,关键是权限边界
工具调用是Agent最显性的能力,也是问题最多的地方。
我们的Agent接了三个系统:库存系统、订单系统、客服系统。每个系统有不同的权限级别。库存查询是只读,订单修改需要审批,客服系统有敏感数据。
Demo阶段,我们用同一个Token调用所有接口,没区分权限。上线后,安全团队要求按角色分配Token。
这时候问题来了:Agent不知道该用什么Token。
模型没有"权限意识",它只知道"我要调用这个工具"。但工具调用背后是真实的业务权限,不同操作需要不同级别的授权。
我们当时的解决方案是在工具层做权限拦截,而不是让模型自己判断。
class ToolWithPermission: def __init__(self, tool, required_permission): self.tool = tool self.required_permission = required_permission def call(self, user_id, params): # 检查用户是否有该权限 if not check_permission(user_id, self.required_permission): raise PermissionError(f"用户{user_id}无权限执行{self.required_permission}") # 记录操作日志 log_operation(user_id, self.tool.name, params) # 调用实际工具 return self.tool.call(params) # 注册工具时指定权限 tools = { "query_inventory": ToolWithPermission(inventory_api, "READ"), "update_order": ToolWithPermission(order_api, "WRITE"), "refund_order": ToolWithPermission(refund_api, "ADMIN"), }这样,即使模型"想"调用退款接口,没有ADMIN权限的用户也调不了。
另一个问题是工具调用的结果处理。 模型生成的参数可能有问题,接口可能返回错误,网络可能超时。这些情况Agent怎么应对?
我们当时没有做失败处理,结果Agent在遇到接口错误时直接"卡死",反复调用同一个失败的接口。
后来加了重试和错误处理:
def call_tool_with_retry(tool_name, params, max_retries=3): for attempt in range(max_retries): try: result = call_tool(tool_name, params) return {"success": True, "result": result} except Exception as e: if attempt == max_retries - 1: return {"success": False, "error": str(e)} time.sleep(2 ** attempt) # 指数退避---
记忆系统:上下文不是无限的,也不是免费的
记忆是Agent区别于普通聊天机器人的关键能力。
没有记忆的Agent,每次对话都是全新的。它不知道之前说过什么,做过什么。这对于单次问答没问题,但对于需要多步协作的任务,记忆是必须的。
我们当时的记忆系统很简单:用Redis存一个列表,记录最近的对话历史。
class SimpleMemory: def __init__(self, max_length=20): self.max_length = max_length self.history = [] def add(self, message): self.history.append(message) if len(self.history) > self.max_length: self.history.pop(0) def get_context(self): return "\n".join(self.history)这个方案在Demo阶段够用,但有几个问题:
第一个问题是敏感信息泄露。 我们的对话历史里经常包含用户手机号、订单号等敏感信息。Redis里的数据没有加密,一旦被攻击者获取,后果严重。
第二个问题是记忆丢失。 Redis是内存存储,重启就没了。如果Agent在执行过程中服务重启,所有记忆都丢失,任务必须从头开始。
第三个问题是记忆成本。 对话历史越长,Token消耗越大,调用成本越高。而且模型对长上下文的注意力会下降,输出质量会变差。
后来我们做了三个改进:
1. 敏感信息脱敏:在存入记忆前,用正则替换手机号、身份证等敏感信息。
2. 持久化存储:把记忆存到数据库,重启不丢失。
3. 记忆压缩:定期用模型对长历史做摘要,压缩成简短的"记忆点"。
def compress_memory(self, history): """用模型压缩历史,保留关键信息""" prompt = f""" 请将以下对话历史压缩成关键记忆点,保留重要信息和决策依据: {history} 输出格式: 1. 关键事实 2. 已做出的决策 3. 待处理的任务 """ compressed = llm.generate(prompt) return compressed---
失败恢复:Demo能跑不代表能兜底
这是这次联调失败后,我印象最深刻的部分。
Demo阶段,所有场景都是精心设计的"成功路径"。模型回答正确,工具调用成功,结果符合预期。没有人会测试失败场景。
但生产环境里,失败是常态。
我们的Agent遇到过这些失败:
- 模型输出格式错误,解析失败
- 工具调用超时,结果拿不到
- 工具返回错误,Agent不知道怎么办
- 用户中途改变需求,Agent还在按原计划执行
每一种失败,Agent都需要有应对策略。
我们当时没有做失败恢复,结果每次出错,Agent就"卡死"或者"乱跑"。
后来我们设计了三种失败恢复策略:
策略一:重试。 对于网络超时、接口临时错误,直接重试。
def retry_on_transient_error(func, max_retries=3): for attempt in range(max_retries): try: return func() except TransientError as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt)策略二:降级。 对于工具调用失败,提供备选方案。比如库存查询失败,可以降级为"提示用户稍后重试"。
策略三:人工介入。 对于无法自动恢复的失败,记录日志,通知人工处理。
def handle_unrecoverable_error(error, context): logger.error(f"Agent执行失败,需要人工介入: {error}") logger.error(f"失败上下文: {context}") # 发送告警 send_alert(f"Agent执行失败: {error}") # 返回友好提示 return "任务执行遇到问题,已通知人工处理,请稍后再试"---
权限和日志:上线后的真正硬仗
回到开头说的那个项目。联调失败三次,原因各不相同,但追根溯源,都是权限和日志的问题。
权限问题:模型不知道哪些操作需要审批,哪些操作可以直接执行。我们后来在工具层加了权限拦截,才解决了这个问题。
日志问题:模型"思考"的过程没有记录,出了问题只能看结果,不知道中间发生了什么。我们后来给规划器加了详细日志,排查效率提升了一个数量级。
可观测性问题:Agent的执行过程是一个黑盒,不知道当前状态、不知道下一步要做什么。我们后来加了执行状态追踪,每个步骤都有明确的标记。
class ExecutionTracer: def __init__(self): self.steps = [] def start_step(self, step_name, params=None): self.steps.append({ "name": step_name, "params": params, "status": "running", "start_time": datetime.now() }) def end_step(self, success=True, result=None): if self.steps: last_step = self.steps[-1] last_step["status"] = "success" if success else "failed" last_step["result"] = result last_step["end_time"] = datetime.now() last_step["duration"] = ( last_step["end_time"] - last_step["start_time"] ).total_seconds() def get_trace(self): return self.steps有了这套追踪,我们可以清楚地看到Agent每一步在做什么、花了多长时间、结果是什么。出了问题,直接看日志,不用猜。
---
总结:Agent不是模型调得好就行
这篇复盘,想说的就一句话:Agent的难点不在模型,在工程。
工具调用、记忆系统、任务规划,这些是Agent的三大核心能力。但Demo能跑,不代表生产能用。权限边界、日志追踪、失败恢复,这些才是上线后的真正硬仗。
如果你正在做Agent项目,我有几个建议:
1. 权限控制要在工具层做,不要依赖模型。 模型没有权限意识,你需要在调用工具前做权限校验。
2. 日志要记录模型的"思考过程",不只是结果。 出了问题,你需要知道模型为什么这么做。
3. 失败恢复要有预案,不要指望模型自己处理。 模型会犯错,你要为常见错误准备好应对策略。
4. 可观测性要从一开始就设计,不要上线后补。 Agent的执行过程是黑盒,你需要让它变成透明。
Demo能跑,只是第一步。能让Agent在生产环境稳定运行,才是真本事。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。
如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。
