☰
AI Agent核心架构与工程实践:从ReAct循环到工具调用全解析
2026/10/21 18:14:52 网站建设 项目流程

1. 从零理解AI Agent:它不是一个聊天框,而是一套决策系统

直接把大模型API接上就能叫Agent吗?我一开始也是这么干的,结果发现完全不是那么回事。我有个朋友做过一个"自动订餐助手",核心就一个Prompt加上几个函数调用,表面上看确实能处理"帮我订个外卖"这种指令,但一旦用户说"昨天说的那家川菜馆别点了,换成附近评分最高的",整个系统就崩了——它根本记不住上下文,也不知道"换一家"意味着要把之前选择的店铺从计划里摘除,更不会去重新规划整个下单流程。

这个例子说明了一件事:Agent和单轮问答的本质区别,不在于"能不能调用工具",而在于它是否具备一套完整的"感知-决策-行动-反思"闭环。你要做的不是给模型套一层壳,而是给它构建一个可以持续运作的微型操作系统。这篇东西我不打算讲太虚的概念,直接拆开讲架构、算法和源码,讲清楚一个能真正稳定运行的Agent到底该怎么搭。

适合谁看?你已经跑通过大模型的基本API调用,但觉得单次对话能力上限太低,想让系统能自己规划步骤、调用工具、处理异常。不需要你有深度强化学习的背景,但至少得写过Python,并理解异步编程的基本概念。我会从架构层面讲到代码层面,最后给几段可以直接落到项目里的参考实现。

2. 架构拆解:Agent的核心循环为什么必须是"规划-执行-反思"

2.1 为什么要设计循环,而不是一次推理到底

很多团队第一次做Agent,都倾向于用一个巨大的Prompt,把任务描述、工具列表、约束条件全塞进去,然后让模型一口气输出最终结果。这种做法在Demo阶段没问题,但一旦任务时长超过两分钟、涉及多个工具调用就会失效。原因在于:大模型的单次推理窗口是有限的,它对"中途状态"的记忆和推算能力非常不稳定。

我做过一个数据抓取类的Agent,任务是"从某信息平台提取近一个月的会议通知,整理成表格并发送邮件"。如果直接在大模型的一次调用里完成,它大概率会在第三步就忘记前两步的结构化约定。后来我把它改成了循环结构:模型每一轮只做三件事——观察当前状态、决定下一步动作、输出一个结构化指令。这个循环没有让模型变聪明,但大大降低了单次推理的负担。

这个设计思路本质上就是ReAct(Reasoning + Acting)模式的工程化实现。你不需要去读那篇论文的数学推导,只需要理解它的核心逻辑:思维过程产生行动指令,行动结果反过来喂给下一轮思维。这跟人做事的方式是一样的——先看一眼现状,想一下该干嘛,动一下手,再看看结果对不对,不对就调整。

2.2 Agent系统的七个标准组件

一个完整可用的Agent系统,我习惯拆成七个组件。你不用全部实现,但脑子里要有这张地图,否则后面加功能会越加越乱:

组件职责关键点
任务解析器把用户模糊的指令拆成可执行目标与LLM配合,产出结构化JSON指令
规划器生成多步行动计划决定用ReAct还是Plan-and-Execute
工具注册中心维护所有工具的描述与参数Schema用JSON Schema描述,便于模型理解
执行器调用真实的外部函数/API负责参数校验、超时控制、异常捕获
记忆模块存储短期上下文与长期事实短期用窗口截断,长期用向量库
反思评估器判断上一轮动作是否有效检查是否出现重复、死循环、幻觉
终止控制器判断任务是否完成设定最大轮次,避免无限循环

这里最容易忽略的是"反思评估器"和"终止控制器"。很多初版Agent跑飞,不是因为模型太笨,而是因为没有任何机制告诉它"你已经重复尝试同一个动作三次了,该换个思路"。后面我会专门讲怎么用源码实现这个机制。

2.3 两种主流工作模式的选型:ReAct与Plan-and-Execute

实践中我搭过两种主流Agent模式,各有适用场景。

ReAct模式(推理-行动交替)适合任务步骤不固定、需要随时根据中间结果调整方向的场景。比如一个客户服务Agent,用户提出的问题千奇百怪,你没法预设它下一步该调哪个库。而Plan-and-Execute模式则适合流程相对稳定、但执行步骤较多的场景,比如"批量生成日报并发送"——它先让模型一次性列出完整计划,然后逐条执行。

我之前在某公司做过一个混合方案:先用Plan-and-Execute做整体规划,让LLM产出一个三到五步的计划,然后在执行每一步时,内部再用ReAct处理该步骤内的意外情况。用代码来表达,这个两层结构是这样的:

plan = planner.plan(task) # 第一层:全局规划 for step in plan: result = react_executor.execute(step) # 第二层:局部自适应执行

这种设计的妙处在于:全局规划保证了整体方向不偏,局部ReAct保证了遇到意外时有应变能力。代价是你要多写不少代码来管理两层状态,但对于中等复杂度任务,这个代价完全值得。

3. 核心算法选型:从意图识别到工具调用的完整推理链路

3.1 让大模型"看懂"外部工具:函数调用的本质是结构化约束

很多初学者有个误解,觉得让Agent用工具就是把工具文档贴进Prompt里让模型"看着办"。实际上,现代Agent系统的工具调用,通常依赖模型内置的函数调用能力——你在API层声明一批可调用的函数,模型在推理时会输出一段结构化的参数JSON,而不是自由文本。

比如你做的是一个天气查询Agent,你需要向模型声明一个get_weather工具:

{ "name": "get_weather", "description": "查询指定城市未来n天的天气预报", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,如北京、上海"}, "days": {"type": "integer", "description": "查询天数,1-7"} }, "required": ["city"] } }

这个JSON Schema不是一个摆设,它直接决定了模型能不能正确生成工具调用参数。我见过太多初版Agent的报错,90%以上都出在这里——description写得含糊不清,参数类型定义不对,或者required字段标错。记住一个原则:工具描述要写得像给一个从没见过这个API的新同事看的那样清楚。

3.2 上下文管理算法:你不需要记住所有东西,你只需要记住该记的

Agent运行过程中,最贵的资源不是API调用次数,而是上下文窗口。一个5轮以内的简单任务可能只需要几千个token,但一个涉及多工具、长流程的Agent任务,上下文很容易被撑爆。

我用的上下文管理策略分三级:

第一级,对话历史窗口截断——只保留最近N轮的对话消息,更早的内容要么丢弃、要么压缩成摘要; 第二级,工具结果裁剪——工具返回的数据如果超过一定长度,就只保留前若干字符和末尾若干字符,中间用"……[已省略中间内容]……"代替; 第三级,长期事实抽取——从历史对话中抽取固定的事实(比如用户的偏好、已完成的任务ID),存入一个持久化的记忆对象里,需要时再注入到系统Prompt中。

这里有个很容易踩的坑:很多人喜欢把工具返回的完整JSON直接塞回上下文里。一旦某个接口返回了几百条数据库记录,下一轮模型就开始"胡言乱语"——因为信息密度太高,注意力被稀释了。后来我的做法是:在把工具结果放回上下文之前,先用一段独立的代码逻辑做后处理,只抽取与当前目标相关的字段,把它整理成一条较短的自然语言语句。这一步很朴素,但实测对准确率的提升非常显著。

3.3 行为决策中的约束算法:如何防止Agent"自由发挥"

没有约束的Agent,就像没有轨道的高铁,跑得快但必翻。我在系统Prompt里固定了三类硬约束,用词非常直接:

  • 权限边界:列出绝对不允许调用的操作(比如删除数据、发送邮件前必须经用户确认);
  • 动作白名单:明确说明可以使用的工具清单,不在清单内的工具视为不存在;
  • 回复格式约束:要求模型在需要用户确认时,必须输出特定格式的确认请求,而不是自由文本。

这些约束并不是模型天然具备的。某个Agent因为我没写"禁止删除"约束,在一次测试中真的调用了删除接口,把测试数据库清了一部分。从那以后,我建立了一个习惯:每个Agent上线前,必须跑一遍"恶意操作测试"——直接给Agent下发一个带有破坏性意图的指令,看看它的行为边界是否可靠。这一步能帮你避开后面大量的线上事故。

4. 源码实现拆解:一个可运行的Agent骨架

4.1 代码骨架:用Python实现一个最小ReAct循环

这一节我会给出一个可以直接运行的最小实现。它不依赖任何重量级框架,只用OpenAI兼容的API接口和标准库里的asyncio。因为你可能在本地或云端跑,我用一个抽象的LLM接口来演示。

import asyncio import json from typing import Any, Callable, Dict, List, Optional class Tool: """工具类:封装一个可被Agent调用的函数""" def __init__(self, name: str, description: str, parameters: dict, func: Callable): self.name = name self.description = description self.parameters = parameters self.func = func def to_schema(self) -> dict: return { "type": "function", "function": { "name": self.name, "description": self.description, "parameters": self.parameters } } async def invoke(self, **kwargs) -> Any: # 实际项目里可以在这里加入超时、重试、日志等逻辑 return await asyncio.to_thread(self.func, **kwargs) class Agent: def __init__(self, llm, tools: List[Tool], system_prompt: str, max_steps: int = 10): self.llm = llm self.tools = {t.name: t for t in tools} self.system_prompt = system_prompt self.max_steps = max_steps self.messages = [{"role": "system", "content": system_prompt}] def _build_tool_schemas(self) -> List[dict]: return [t.to_schema() for t in self.tools.values()] async def run(self, user_input: str) -> str: self.messages.append({"role": "user", "content": user_input}) step_count = 0 while step_count < self.max_steps: step_count += 1 response = await self.llm.chat_completion( messages=self.messages, tools=self._build_tool_schemas() ) msg = response.choices[0].message # 情况1:模型决定调用工具 if msg.tool_calls: self.messages.append(msg.model_dump()) for tool_call in msg.tool_calls: result = await self._execute_tool_call(tool_call) self.messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) continue # 情况2:模型直接输出最终回答 if msg.content: return msg.content # 超过最大步数,强制终止 return "任务达到最大执行步数,已自动终止。请简化任务或调整工具配置。" async def _execute_tool_call(self, tool_call) -> str: try: args = json.loads(tool_call.function.arguments) tool_name = tool_call.function.name if tool_name not in self.tools: return {"error": f"未知工具: {tool_name}"} tool = self.tools[tool_name] result = await tool.invoke(**args) return {"result": result} except Exception as e: return {"error": str(e)}

这段代码虽然短,但它已经具备了基本Agent循环的全部要素:消息历史、工具Schema注入、工具调用分发、异常捕获、最大步数终止。你可以在这个基础上扩展记忆、评估器等模块。

4.2 规划器的实现:让Agent产出可验证的结构化计划

纯ReAct循环的缺点是它没有"全局观"——每轮只决定下一步动作,容易陷入局部最优。因此我建议在Agent里加一个独立的规划步骤。这个规划器单独调用一次LLM,输出一个JSON计划数组,每个计划项包含步骤描述、依赖工具、预期产出。

PLANNER_PROMPT = """ 你是任务规划器。请将用户目标拆解为2-5个按顺序执行的步骤。 输出JSON数组,每个元素包含: - "step": 步骤描述,一句话,具体明确 - "tool": 预计使用的工具名,若无需工具则填 "none" - "expected_output": 该步骤成功完成的标志 只输出JSON,不要输出其他内容。 """ async def generate_plan(llm, user_task: str) -> List[dict]: messages = [ {"role": "system", "content": PLANNER_PROMPT}, {"role": "user", "content": user_task} ] response = await llm.chat_completion(messages=messages) content = response.choices[0].message.content.strip() # 防止模型输出附带markdown代码块标记 if content.startswith("```"): content = content.strip("`") if content.startswith("json"): content = content[4:] return json.loads(content)

我踩过的一个坑是:模型偶尔会在JSON前后加markdown代码块标记,导致json.loads直接抛异常。上面这段代码里的清理逻辑很重要,别去掉。更稳妥的做法是要求模型用严格模式输出,或者用支持结构化输出的新API特性。

4.3 反思评估器的实现:三行代码就能阻止大多数死循环

死循环是Agent最常见的事故,尤其是当模型选择了错误的工具、拿到错误的结果之后,往往会傻乎乎地重试同一个错误操作。我写了一个非常轻量的检查器:记录最近三次相同的动作,如果动作完全相同且间隔不超过三轮,就强制给模型注入一条"警告"消息。

last_actions = [] def detect_repetition(new_action: str) -> bool: last_actions.append(new_action) if len(last_actions) > 4: last_actions.pop(0) # 检查最近的3个动作是否完全一致 return len(last_actions) >= 3 and len(set(last_actions[-3:])) == 1 # 在主循环中使用: if detect_repetition(action_description): agent.messages.append({ "role": "system", "content": "警告:检测到重复动作。前一步操作未产生有效进展,请不要重试相同方案,尝试改变策略或请求用户澄清。" })

这个方法不聪明,但它极其实用。它不需要训练、不需要模型微调,就是纯规则,但能拦住大量推理模型的"原地踏步"行为。

4.4 记忆模块的代码级设计:短期缓存与长期向量存储的配合

记忆模块是Agent项目中最大的分水岭。我见过太多项目在初期不设计记忆,等到对话轮次多了,开始暴力拼接历史消息,最终token爆炸,效果反而变差。

我的轻量级记忆方案分两层。短期记忆就是主循环里的self.messages列表,但它需要定期压缩。我写了一个历史压缩函数:当消息条数超过20条时,将前10条消息合并成一段摘要,替换为一个"summary"消息。

长期记忆我通常接入一个本地向量库。每次Agent从用户输入或工具结果中提取出"值得记住的事实"(比如用户的ID、用户的偏好、任务的最终结论),就把它写入向量库。在下一次会话开始前,先做一次相似度检索,把最相关的几条记忆注入到系统Prompt中。

class MemoryStore: def __init__(self, embed_func, top_k: int = 3): self.embed_func = embed_func self.top_k = top_k self.items = [] # 每个item是{"text": str, "embedding": [...], "metadata": {...}} def add(self, text: str, metadata: dict = None): emb = self.embed_func(text) self.items.append({"text": text, "embedding": emb, "metadata": metadata or {}}) def search(self, query: str) -> List[str]: query_emb = self.embed_func(query) scored = [] for item in self.items: score = self._cosine_similarity(query_emb, item["embedding"]) scored.append((score, item["text"])) scored.sort(key=lambda x: -x[0]) return [text for _, text in scored[:self.top_k]]

注意这里的向量化函数在demo阶段可以直接用大模型的embedding接口。但生产环境里,如果记忆条目很多,一定要换专业的向量数据库,不然线性扫描会慢到你怀疑人生。

5. 工具调用模块的深度优化:如何让Agent稳定调用真实API

5.1 参数校验的"Trust but Verify"原则

工具调用中最隐蔽的问题,是模型生成的参数符合JSON Schema语法,但不符合业务逻辑。比如日期格式传了"明天",城市名传了"帝都",分页参数传了-1。这些错误在Schema校验阶段是查不出来的,因为类型都对。

我的做法是在每个工具的Python函数内部,增加一层业务级参数校验。这个校验不一定复杂,但至少要做到三件事:检查枚举值是否合法、检查数值是否在合理区间、检查时间日期是否满足任务的相对语义。如果校验失败,直接返回一个明确的错误信息给模型,让它自己修正参数再调用一次。

async def get_weather(city: str, days: int): # 业务级校验 valid_cities = {"北京", "上海", "广州", "深圳"} if city not in valid_cities: return {"error": f"暂不支持城市: {city},可选: {valid_cities}"} if days < 1 or days > 7: return {"error": "days参数必须在1-7之间"} # 正常调用真实天气API ...

这样设计的价值在于:模型收到错误信息后,会自动调整参数并重新尝试。实测中,模型在这种反馈驱动下修正参数的成功率非常高,远高于你写一堆Prompt去"要求"它别传错。

5.2 多工具选择冲突时的消歧策略

Agent工具多了以后,会出现另一个问题:模型面对两个描述相似的工具,不知道该选哪个。比如一个"get_user_info"和一个"get_user_orders",模型可能因为描述不够精确而选错。

解决办法是在工具描述里加一个"适用场景"字段。我通常这样写工具描述:"当用户询问个人信息(如姓名、手机号、会员等级)时使用本工具;当用户询问订单历史或购物记录时,请使用get_user_orders"。实测中,加了这个字段之后,工具选择准确率能提升十个百分点以上。

5.3 超时与重试:Agent的"心跳机制"

一个线上Agent必然要面对第三方API的不稳定。我的超时策略是三段式:单次工具调用超时5秒,超时后重试1次(重试间隔0.5秒),如果还是失败,返回给模型的错误信息会带上"该接口暂时不可用,建议间隔1分钟后再试"。这个反馈本身也是Agent规划的一部分——它可能会决定先去做别的任务,或者直接向用户说明当前无法完成。

不要小看重试逻辑的设计,我见过太多Agent在某个工具瞬时失败后直接放弃整个任务。在大模型时代,"一次失败就放弃"是几乎不可接受的——因为重试和绕路是模型最擅长的事情,你不给它机会,它就没机会展示这个能力。

6. 系统提示词的工程化写法:从"聊天风格"升级为"操作手册"

6.1 系统提示词的四大内容板块

很多团队写的系统提示词,完全就是一段"角色扮演引导语":"你是一个智能助手,请帮助用户完成各种任务。"这种写法对Agent来说远远不够。

我常用的系统提示词结构分四大板块:

  • 身份与目标:一两句说明Agent是谁、服务目标是什么;
  • 工具使用规则:明确何时使用工具、何时不使用工具、工具调用的先后顺序;
  • 工作流约束:包括最大轮次、遇到错误时的处理方式、用户确认的触发条件;
  • 输出格式规范:要求模型在最终回答里附带关键推理摘要,方便调试。

这里给一段我在实战项目里用过的模板片段(它不是我编的理想化内容,而是真从某项目里抽出来的):

你是一名企业知识库查询助手。你的任务是基于企业内部文档回答用户问题。 工具使用规则: - 当问题涉及某项具体制度、流程或数值时,必须先调用 search_docs 检索。 - 当检索结果不足时,允许调用 search_web 二次确认。 - 若用户提问不在知识库范围内,不要尝试编造答案,直接回复"未在知识库中找到相关信息"。 工作流约束: - 最大工具调用次数为5次,超过次数必须停止并总结已获取的信息。 - 如果检索到相互矛盾的信息,须列出双方观点并注明信息来源。

你会发现这里的用词全是祈使句、可验证的规则,而不是"你可以试着……"这类模糊引导。模型对明确数字和强约束的遵从度,远高于对模糊建议的遵从度。

6.2 一个被大家忽视的细节:错误信息的格式要"机器可读"

很多Agent反馈给模型的内容是自由文本,比如"工具调用失败了,原因是……"。这没问题,但如果你想让模型更准确地规划下一步,最好把错误信息结构化。我的习惯是这样的格式:

{ "action": "get_weather", "status": "failed", "error_code": "INVALID_CITY", "message": "城市参数'帝都'不在支持列表内。支持的列表: 北京、上海、广州、深圳", "suggestion": "请将城市名转换为官方标准名称后重试" }

把错误信息结构化之后,模型的处理能力会明显提升,因为它不用自己从大段文本里"阅读理解"到底是哪里出了错。这个技巧几乎零成本,但效果立竿见影。

7. 完整案例演示:一个"会议安排Agent"的从零搭建

7.1 需求拆解与工具准备

为了把前面的理论落到实际,我挑一个常见场景:会议安排Agent。用户给它一句"帮我约张三和李四明天下午开会讨论项目进度",它需要完成以下事情:

  • 查询两人明天下午的空闲时间;
  • 找到一个两人都空闲的时间段;
  • 创建一个会议邀请并发送。

这个任务涉及三个工具:

  • query_schedule(user_name, date):查询某人的日程;
  • find_free_slot(schedule_a, schedule_b, duration_minutes):计算空闲交集;
  • create_meeting(title, invitees, start_time, end_time):创建会议。

这三个工具都不需要自己写函数体,只需要定义好调用接口。Agent的核心逻辑完全复用我在第4节里写好的骨架。

7.2 关键Prompt与预期输出

当用户输入"帮我约张三和李四明天下午开会讨论项目进度"时,Agent的推理过程会很自然地展开成下面这样的步骤:

第一轮,Agent判断需要先查张三明天的日程,于是调用query_schedule(user_name="张三", date="明天"),拿到张三下午2点到4点空闲的信息; 第二轮,Agent查询李四的日程,拿到李四下午3点到5点空闲的信息; 第三轮,Agent调用find_free_slot,计算出共同空闲区间是下午3点到4点; 第四轮,Agent调用create_meeting,创建会议并返回会议链接。

这个过程中,你不需要写一行"规划逻辑"——规划完全由模型自动做出。你的工作只是把工具注册好、把系统提示词写清楚、做好异常反馈。

7.3 中间态的调试技巧:日志的重要性

一旦Agent跑起来,你的调试手段就不再是"看输出对不对",而是"看每一轮的信息流是否合理"。我强烈建议你在Agent循环的每个关键节点打结构化日志:

async def run(self, user_input: str): ... self.logger.info({ "step": step_count, "message_count": len(self.messages), "tool_call": tool_call.function.name if tool_call else None, "tool_args": tool_call.function.arguments if tool_call else None, })

这些日志在事后分析Agent为什么跑偏时,价值怎么强调都不过分。我至今仍然会碰到花了三十分钟都复现不了的Agent事故,最后靠的就是日志一步步回放,才发现是某个中间工具返回的时间格式与预期不符。

8. 调试与实战排雷:Agent项目最常见的坑与解法

8.1 坑一:模型"胡编工具名"

即使用了工具Schema,模型偶尔仍会输出一个你根本没注册过的工具名。原因通常是你给模型的工具描述不够清晰,或者工具名之间太相似。解法有两个:一是在系统提示词里显式列出"可用工具清单",二是在_execute_tool_call里对未知工具返回友好错误信息,让模型自己纠错。

8.2 坑二:上下文被工具结果撑爆

这是我被问得最多的问题。工具返回了一个超长列表,然后下一轮推理质量断崖式下跌。我在第3.2节提到的"工具结果裁剪"策略,是性价比最高的方案。再补充一点:裁剪之后,最好在结果末尾附一句"以上为截断后的摘要,完整结果共X条,如有需要可查询第X条细节"。这句提示能给模型一个继续深挖的入口,避免它以为数据只有这些。

8.3 坑三:模型陷入自我怀疑循环

我在实际测试里见过一种很奇特的现象:模型已经开始输出最终回答了,但中间突然觉得"我是不是漏了什么",于是转回去又调了一次工具,然后在后续几轮里不断重复"检查-确认-再检查"的循环。这种循环用动作重复检测很难拦,因为每轮动作不完全一致,只是范式相似。

我的解法有两个:一是直接在系统提示词里写"当你已经完成核心任务并得到必要信息时,直接输出最终答案,不要进行不必要的额外检查";二是在反思评估器里加一个"工具调用次数上限"的硬指标,一旦超过预设阈值,自动进入收尾模式。

8.4 坑四:Agent的复现性问题

Agent系统的非确定性(模型采样造成)在生产环境里是个大问题。同一句话,你跑两次,结果可能不同,其中一次可能调用了错误的工具。

缓解手段是降低采样温度,典型值0到0.2之间。我用的策略是:工具调用决策阶段温度设为0,最终回答生成阶段温度可以调高到0.7左右。严格来说,你没法做到100%复现,但把决策阶段的随机性压到最低,能显著减少"抽风"的概率。

8.5 坑五:工具返回结果里混有无关信息

这个问题在RAG类Agent里特别常见。你检索回来的文档片段,可能包含大量与当前子问题无关的内容。模型看到这些无关内容后,容易被带偏、甚至自作主张去回答一个不相干的问题。

我的做法是:在把检索结果注入上下文之前,先用一个独立的"相关性过滤"步骤——调一次轻量级模型判断该结果与当前任务的相关性分数,低于阈值的直接丢弃。虽然多费了一次API调用,但整体效果提升非常明显。

9. 进阶扩展:多Agent协作与工具的自主编写

9.1 多Agent架构的三种协作模式

当你把一个Agent的能力用到极致后,自然会遇到瓶颈:单个Agent的上下文有限,无法统筹任务的所有环节。这时候就该考虑多Agent架构。我在实践中常用的有三种模式:

  • 主管-下属模式:一个主管Agent负责拆解任务,将子任务分发给多个专职Agent执行,收集结果并汇总;
  • 流水线模式:任务按照固定顺序在不同Agent之间流转,比如"数据分析Agent"先产出报告,"图表生成Agent"再把报告画成图;
  • 辩论模式:多个Agent从不同角度分析同一个问题,最后交给人或另一个Agent做裁决。

这三种模式各有适用范围,但我不建议一开始就为了"架构好看"而上多Agent。多Agent带来的协调问题、上下文隔离问题、状态共享问题,复杂度是单Agent的好几倍。

9.2 让Agent自己写工具:一个很有前景的方向

最近我在做的一个实验是:给Agent一个Python执行环境,让它自己写代码解决当前问题。这其实是一个更底层的能力——Agent不再局限于调用预先定义好的工具,而是可以按需生成一个新的"临时工具"。

这个方向在技术上有两个关键前提:一是代码沙箱,必须保证Agent生成的代码不能访问敏感网络或文件系统;二是错误反馈闭环,Agent生成代码、执行、看到报错、修改代码、再执行。我在这条路上踩过不少坑,其中最典型的是:Agent生成的代码往往"能跑但跑得慢",因为它不会考虑性能。如果你对这个方向感兴趣,我的建议是先从一个受限的、单一的领域开始(比如只处理CSV数据),别一上来就给它全能的Python环境。

9.3 评估Agent系统的三个维度

最后聊一下Agent系统的评估。传统模型评测看准确率,但Agent系统的评估要复杂得多。我会从三个维度来评估:

  • 任务完成率:给定一批标准任务,Agent能够完全无人工干预完成的百分比;
  • 行为规范性:检查Agent在执行过程中是否出现了越权操作、绕过了约束、进入了死循环等行为;
  • 成本效率:单任务平均消耗的token数、API调用次数和耗时。

这三个维度缺一不可。有的Agent任务完成率很高,但行为不规范(偷偷调用了不该调用的工具),这种Agent不能上线;有的Agent行为很规范,但每次任务要调用几十次API,成本高到离谱,这种Agent也不适合生产场景。

我个人的体会是,做Agent项目与做传统软件项目最大的不同在于:你需要同时处理确定性逻辑和不确定性逻辑。传统软件的调试是线性的,而Agent的调试是概率性的——你必须接受"它偶尔就是会犯错"这个现实,然后用评估、监控、护栏、重试等手段去对冲这种不确定性。这不代表你无法构建可靠的系统,而是意味着你的思维模式要从"写完代码就完事"切换到"系统要持续被观察和修正"。

再分享最后一个小技巧:如果你刚开始接触Agent开发,不要一上来就追求大而全的框架。我见过太多人花了两周时间研究各种Agent框架的抽象概念,结果真正写业务逻辑的时间被压缩得所剩无几。先从手写一个二十行的循环开始,让它调一个工具、处理一个错误、完成一个最简单的任务。跑通之后,再逐步往上加记忆、规划器、评估器。这个从简到繁的过程,能让你真正理解每一层抽象存在的意义,也让你将来无论用哪个框架,都不会被框架的"黑盒行为"困住。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询