最近几年,做大模型应用的人越来越绕不开一个词:智能体(Agent)。我见过不少团队,LLM API接得贼溜,RAG流程也跑得通,但一问到“你的智能体是怎么设计的”,就支支吾吾说不出个所以然。问题出在哪儿?很多人一上来就扎进LangChain源码、看各种Agent框架的Demo,反而把最基础的设计方法论落下了。
这篇文章我想换个思路,不聊某一个具体框架,也不贴一堆云里雾里的源码,而是把智能体设计里最底层的两个东西讲透:一个是从传统AI继承过来的PEAS模型(用来定义智能体到底该做什么),一个是这几年大模型带火的ReAct架构(用来解决模型怎么边想边做的循环问题)。这两件事想明白了,你再去用任何框架都只是工具选择的问题,而不是摸着石头过河。适合谁看?刚接触智能体、想系统理清概念的程序员,或者已经在调智能体但总觉得哪里不对的开发者,都建议收藏。
1. 先把“智能体”拆开:PEAS模型讲了什么
1.1 一个容易被跳过的四要素框架
PEAS不是新概念,它来自传统人工智能教材里的智能体设计理论,四个字母分别代表 Performance(性能)、Environment(环境)、Actuators(执行器)、Sensors(传感器)。当年学的时候,我觉得这就是个“填表工具”,直到开始做大模型智能体才意识到,这其实是整个设计流程里最先应该动手的地方。
先说说这四个词分别是什么意思。Performance,通俗讲就是“怎么算它干得好”。不是产品经理拍脑袋说“体验不错”,而是可度量、可比较的指标。Environment是智能体存在于哪个环境里,它能感知到什么、环境又藏着什么不确定性。Actuators是智能体影响环境的手段,在软件系统里就是它能调用的函数、能写入的数据、能触发的动作。Sensors则是它读取外部信息的通道,比如用户文本、数据库返回、API响应、系统时间。
很多开发者的误区在于:他们先把模型用起来,再去想工具和策略,最后才补绩效定义。顺序一旦反了,后面所有调试都会很痛苦。举个例子,你做一个日程提醒智能体,如果没有提前想清楚“Performance”,等上线后你根本没法回答“这个智能体比普通的日历提醒好在哪”,也没法判断模型的那次回复究竟算成功还是失败。
1.2 用大众熟悉的场景体会PEAS
为了把PEAS从抽象术语变成能动手的东西,我用一个很常见的场景来拆解:一个日程助手智能体。
这个助手需要接收用户的文字指令,比如“明天上午十点开会一小时,记得提醒我”,然后创建日历事件并在合适时间提醒。对照PEAS:
- Performance:意图识别准确率、字段解析完整度(时间、标题、时长一个不能少)、提醒准时性、用户编辑后的事件冲突率。
- Environment:用户的历史日程、当前时间、节假日信息、用户偏好的提醒提前量。
- Actuators:创建日程、更新日程、删除日程、主动推送提醒消息。
- Sensors:用户输入的聊天文本、当前的系统时间、日历数据库中的事件列表。
你看,这样一列,后面无论用提示词工程还是写代码,工作边界全清楚了。哪一环节该加重投入、哪一环节可以考虑简化,PEAS直接给了你一张需求全景图。我在实际项目里,一般第一步就把这套分析写到设计文档里,后面团队讨论的时候,大家争的是指标和边界,而不是“我感觉”“我觉得”。
1.3 PEAS在纯大模型场景里的变化
传统PEAS是从机器人学那边继承过来的,传感器和执行器往往是物理世界的摄像头、电机。但大模型智能体跑在软件环境里,"传感器"成了API调用、文件读取、网页检索、用户文本输入;"执行器"成了JSON格式的动作输出、数据库变更、消息推送。理解了这种映射,你就能明白为什么现在很多Agent框架的核心其实是"工具调用",因为工具就是执行器,而工具返回的内容就是传感器的输入。
还有一点很多人忽略:大模型本身也是一个"感知源"——它从系统提示词里感知到任务边界,从对话历史里感知到上下文状态。所以在填写PEAS的时候,我建议把"模型通过输入看到的全部信息"也纳入Sensors的考虑范围,别只顾着外部API。
2. ReAct架构:让模型真正“动起来”的设计范式
2.1 从纯对话到“想一步、做一步”的转变
如果你只让大模型直接输出最终答案,那它本质上还是一个高级的文本猜测器:它没有验证手段,没有工具支持,也没有状态推进,一切全靠训练参数里沉淀的那些统计规律。换句话说,纯Prompt方式做复杂任务,就像让人蒙着眼睛走迷宫,每一步都靠感觉,撞墙了也不自知。
ReAct架构(Reasoning + Acting)解决的正是这个问题。它的核心逻辑很直接:模型在推理过程中不急着给答案,而是反复执行一种循环——先输出一段 Thought(推理:我现在遇到了什么,下一步该做什么),然后输出一个 Action(行动:调用某个工具/执行某个指令),再拿到 Observation(观察:工具返回了什么结果),这个结果又喂回给模型,进入下一轮 Thought。循环直到模型认为信息足够,输出最终回答。
这个概念听起来不复杂,但它是大模型智能体真正可用的分水岭。原因在于:当模型能把推理过程外显成文字、把决策外显成动作,你就拥有了可审计的日志、可干预的断点,以及可接驳无数工具的扩展面。这比一股脑把整个任务直接丢给模型要可靠得多。
2.2 ReAct里的三个关键节点如何协作
我一直跟同事讲,ReAct的核心不是"想",也不是"做",而是用外部观察把两者咬合起来。你只看Thought会变成白日梦,只看Action会变成乱来,只有在Thought->Action->Observation这个三角循环里,每一步都有据可依,系统才稳定。
我们用日程智能体的一个片段来演示:
- Thought:"用户说的是'明天上午十点开会一小时'。我需要从中解析出日期、时间、时长和标题。当前系统时间是晚上八点,明天上午十点距今还有14小时,需要创建事件并且设定好提醒时机。"
- Action:
create_event(title="会议", date="2025-01-15", time="10:00", duration=60, reminder=30) - Observation:
{"success": true, "event_id": "evt_0001", "conflict": false}
模型拿到这个观察结果之后,才会进入下一轮Thought:"事件创建成功,没有冲突。是否需要告知用户?"然后输出最终回答。如果没有ReAct循环,模型一步到位直接瞎编一个"好的,已经提醒你了",但到底有没有写进日历,模型自己也不知道。这是很多Demo和真实项目日常之间巨大的差距。
2.3 ReAct和CoT的对比,为什么不是互斥关系
你可能听过另一个名词:CoT(Chain-of-Thought,思维链)。它指的是让模型把思考过程写出来,再生成答案。CoT非常有用,但它停留在"嘴巴上"——它只改变了模型的推理文本形态,模型还是没法真正触碰外部世界。
ReAct可以理解为CoT的增强版:它在CoT的"想"之外增加了"做"和"看",让推理链路有机会被外部反馈纠正。有个说法我很认同:CoT是让模型在大脑内部做推演,ReAct则是给大脑接上手脚和眼睛。在做多步算术题时,CoT可能不依赖工具也能硬算;但一旦遇到"查询实时天气再决定是否通知用户带伞"这种任务,纯CoT就走不通了,因为模型的训练数据里根本没有今天的天气数据,必须靠工具获取。理解了这个,你就能判断什么时候用ReAct,什么时候其实CoT就够了。
3. 从零手写一个最小可用智能体:实操记录
3.1 前置准备与工具选型
通常到这一步,大家会纠结要不要上某个现成的Agent框架。我的建议是:第一版尽量不依赖重型框架。我更喜欢用最朴素的Python + LLM API来完成初版,框架太重反而把核心逻辑包住了,排查起问题特别麻烦。当你把某个场景的ReAct循环跑明白了,再迁移到框架里几乎没有任何成本。
环境方面其实没太多花头:Python 3.10以上,一个能处理对话补全的LLM接口(OpenAI兼容格式就行,国内外的模型服务都支持这种形式),再加上一个JSON解析库。别小看这个选型,它决定了你后面调试日志的直观程度。我早期用过一个很复杂的中间层,后来出了问题,日志里全是框架自带的抽象信息,反而看不到模型的Thought输出,排查效率低到离谱。后来换回裸API,一眼就能定位问题。
3.2 用PEAS定义日程助手的需求边界
你说你直接开写代码?我劝你先停下来,花二十分钟把PEAS表格填完。我习惯用一张Markdown表格来沉淀这个设计过程:
| PEAS要素 | 本项目的具体定义 |
|---|---|
| Performance | 事件创建成功率、字段解析完整度、冲突检测召回率、用户反馈满意率 |
| Environment | 日历数据表、系统时间、用户偏好设置、历史对话记录 |
| Actuators | create_event、query_calendar、update_event、send_notification |
| Sensors | 用户输入文本、当前时间、日历数据库返回、系统通知状态 |
这张表你以后一定会反复用:写提示词时参考它,定义每个工具的参数时参考它,写测试用例时还是参考它。很多人做到一半发现工具和场景不匹配,往往就是因为一开始没把PEAS定死。
3.3 实现一个最简ReAct循环
核心代码其实非常短,核心思路就是循环调用模型,解析动作,执行工具,再把结果拼回去。下面这段是我在模拟项目里使用的核心调度逻辑的简化版本,你可以直接照着跑通:
import json import datetime # 模拟的工具注册表 TOOLS = { "create_event": { "description": "创建日程事件,参数:title, date, time, duration, reminder", "handler": lambda args: calendar_db.create_event(args) }, "query_calendar": { "description": "查询指定日期的日程,参数:date", "handler": lambda args: calendar_db.query(args["date"]) } } def run_agent(user_input, max_iterations=5): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for step in range(max_iterations): response = call_llm(messages) # 返回模型文本 content = response.content # 尝试解析模型输出中的动作 action = try_parse_action(content) # {"name": "...", "args": {...}} if action is None: # 模型认为信息足够了,直接输出答案 return content if action["name"] in TOOLS: observation = TOOLS[action["name"]]["handler"](action["args"]) else: observation = {"error": f"未知工具 {action['name']}"} # 把推理和观察都放回上下文里 messages.append({"role": "assistant", "content": content}) messages.append({"role": "user", "content": f"工具返回结果:{json.dumps(observation, ensure_ascii=False)}"}) return "达到最大迭代次数,任务未完成"这里有一个关键点:try_parse_action看着简单,实际非常值得重视。我建议模型输出动作时用严格的JSON包裹,比如{"name": "create_event", "args": {...}}。解析时先用正则提取,再用json.loads。遇到解析失败不要慌,把原始内容原样返回给模型,让它自己修正格式,这比用正则强修可靠得多。
3.4 让模型“学会”使用工具的提示词写法
许多人以为提示词写得越细越好,其实不然。ReAct场景下,你真正要做的是把PEAS里的信息转成模型能理解的约束:
- 告诉模型它有哪些工具可用,每个工具的参数要求和返回格式是什么。
- 明确告诉模型输出格式:要么是JSON动作,要么是最终回答,不要混着来。
- 告诉模型它的每一步观察是什么含义。比如工具报错时,它应该修正参数重试而不是死循环。
- 约束它的行为边界:查询失败时不能自作主张伪造结果,应该返回错误信息给用户。
有一个比较通用的结构,我一直在用,大致是:角色定位、任务目标、工具清单、行动规则、输出格式、兜底策略。写Prompt并不需要去追求那些花哨的模板,把这几块交代清楚,模型的稳定性会有质的提升。
3.5 跑通一次调用的完整日志记录
真实运行的效果,我贴一段简化的日志(内容做了脱敏处理)。你会看到模型在整个循环里的思考轨迹很清晰:
--- Step 1 --- User: 明天上午10点安排一个项目评审会议,时长1小时,提前30分钟提醒我。 Assistant Thought: 用户要求创建日程事件,时间是明天上午10点,时长60分钟,需要提前提醒。先查询当前日历避免冲突。 Action: {"name": "query_calendar", "args": {"date": "2025-06-12"}} Observation: {"events": [{"start": "09:00", "end": "09:30", "title": "站会"}]} --- Step 2 --- Assistant Thought: 查询结果显示10点这个时间段没有冲突,可以创建事件。 Action: {"name": "create_event", "args": {"title": "项目评审会议", "date": "2025-06-12", "time": "10:00", "duration": 60, "reminder": 30}} Observation: {"success": true, "event_id": "evt_001"} Final: 已为你创建日程“项目评审会议”,时间明天上午10:00-11:00,会提前30分钟提醒你。这看起来代码简单,但实际写的时候,有大量细节容易踩坑。下面这部分,我把这几年的排查经验整理成一个速查清单,全是实战里血泪换来的。
4. 常见问题与排查技巧实录
4.1 模型输出JSON不稳定,动作解析频繁失败
这是所有ReAct项目里碰到最频繁的问题。哪怕你很明确地说了“只输出JSON”,模型还是可能多写一句解释,或者用代码块的 Markdown 格式把它包起来。
我的处理经验分三层。第一层是宽容解析:不要用json.loads直接硬卡,先用正则把花括号之间的内容提取出来,再去掉Markdown代码块标记,最后才尝试解析。第二层是让模型自纠错:如果解析失败,就把原始输出和报错信息一起拼到下一轮对话里,明确告诉模型“你的输出格式不符合要求,请重新输出JSON动作”。第三层是准备后手:连续两轮解析失败时,直接按错误流程回复用户,不要无限循环占用资源。
4.2 智能体陷入思考死循环,Action来回反复
表现是模型不断调用同一个查询工具,或者反复输出Thought却始终不收敛。根本原因通常有两个:一是Observation信息太多,模型被无用信息淹没,抓不住关键信号;二是工具返回的失败信息没有结构化,模型不知道下一步该怎么办。
对应的解法也简单:把Observation做归一化,统一返回{"success": bool, "data": ..., "error": ...}这样的结构,让模型一眼能看出成功还是失败;另外设置最大迭代次数,比如5轮,超过就直接跳出并提示用户稍后再试。我还会在Prompt里加一句“如果你已经拿到了相同的观察结果,不要重复同样的动作,请尝试更换参数或直接告知用户”。
4.3 上下文窗口被工具返回结果撑爆
这是“长任务智能体”最容易犯的错。工具返回的数据动不动几百上千行,全部塞进对话历史,几轮之后Token就爆了。实际项目中,我在工具层做了一层轻量摘要:只给模型返回关键字段和截断后的前N行数据,并附带上"已截断"的标识。比如查询日程,只返回事件标题、起止时间、状态,而不返回背后的原始日志对象。不要把所有上下文都一股脑交给模型,模型需要的是决策因子,不是原始数据全量。
4.4 智能体在关键节点“说谎”
这是个容易让人发毛的问题。比如查询工具明确返回没有此人,模型却回复“已找到并处理”。为什么?因为在训练数据里,“假装成功”在很多时候比“承认失败”更符合对话流畅度的要求。我的处理方式:要求模型在无法完成工具调用或工具报错时,必须原样向用户反馈错误信息,并在系统提示词里反复强调“绝不虚构工具执行结果”。同时在工具返回的Observation里,强制包含一个success字段,让模型没有模糊空间。别指望一次Prompt就能根治,需要反复试跑几个失败场景来校准。
5. 已经不是Demo级别:走向可靠智能体的几个进阶方向
5.1 把记忆从“对话上下文”升级成“结构化记忆”
纯靠上下文传递信息,智能体的记忆力等于它的窗口大小,可用的历史信息稍纵即逝。我现在的做法是加一个轻量级的记忆数据库:把用户偏好、历史决策、之前任务的总结都存起来。每次任务开始前,把相关记忆检索出来塞进系统提示词。方法不复杂,但对体验的提升非常直接。用户发现这个智能体“记得上次说过的话”,信任感完全不一样。
5.2 从单智能体到多智能体协作
如果你只有一个模型循环,那一切好办。但真实复杂场景里,把“意图理解”“任务规划”“工具执行”“结果审核”分给不同的角色去做,往往比一个掌握所有技能的智能体更稳定。原因在于:分角色以后,每个角色的Prompt更短、工具更少、决策面更窄,模型越容易表现得稳定。代价是上下文管理变复杂,很多时候需要在不同角色之间传递状态。我的建议是,团队初期先把单智能体的ReAct循环玩熟,再拆角色,否则排查问题时会陷入角色之间的“甩锅”困境。
5.3 建立一套围绕测试集的效果评估机制
这是很多独立开发者和团队都会忽略的点。优化智能体不能靠感觉,你要准备一份场景测试集:每个用例包含用户输入、期望调用的工具序列、允许的结果误差范围。每次改动Prompt或工具定义,都把这套测试集跑一遍,对比通过率。哪怕只有二十个用例,也比拍脑袋调提示词有效得多。别等到上线后用户发现问题才去补救,那是最昂贵的调试方式。
我个人做智能体项目最大的体会是,AI能力只是整个系统里的一环,反而是在“定义清晰、边界明确、容错可靠”这些工程问题上花的功夫,直接决定了一个智能体是玩具还是工具。ReAct给了模型思考与行动的能力,PEAS给了我们定义与衡量的框架,但真正让一个智能体能在真实业务里跑起来的,是开发者对每一个细节的控制力。如果你也想动手做一个自己的智能体,不要一上来就追求复杂炫酷的效果,先拿一个最简单的日程助手,把PEAS填完,把ReAct循环写通,把上面那些坑都踩一遍,你对智能体的理解会脱胎换骨。