想说点啥
这篇文章构思有半个多月了,本来想弄个文生图的案例分享一下。
结果,最近在补 MCP 和 RAG的高级用法,还有 LangGraph。耽搁了一阵。
另一方面,也没想好这里的主干内容怎么写:继续分享怎么创建工作流么?好像已经写过不少案例了。
直到参考网上资料手搓了个Agent代码出来——感觉这里面很有意思,值得说道说道。
所以,本文会从一个游戏案例出发 ,带你了解:
1)LLM + Tools的调用细节和代码级实现
2)多模态大模型在文生图案例中的效果和提示词
3)最简单的多Agent 协同方式
4)[重点] 什么是ReAct 以及 ReAct这个范式的优缺点
游戏玩法和效果
游戏玩法
玩法很简单:用户输入一个卡通人物特点的简单描述,由工作流生成对应人物画像。
游戏组件
整个工作流展示:

主要组件就是 图中两个Agent——第一个Agent负责对卡通人物进行识别并输出人物描述,第二个Agent 则依据前面的人物描述 ,生成相关人物图像。
效果展示
我这里输入“浪浪山的小妖怪其中的黄鼠狼,一个碎碎念的有志青年”,等待1分钟后,输出了下面这张图。
怎么样?是不是除了脸部细节,其他还挺还原动画人物的。
关键步骤细节
人物形象描述Agent
-
提示词部分如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14# 角色 你是一个卡通人物形象描述专家,根据用户提供的基本信息:"{{ $('When chat message received').item.json.chatInput }}" 输出相关人物形象描述。 如果遇到你不知道的人物信息,可以调用工具进行检索。 ## 案例 { name: “多啦爱梦” description: “一只圆润的蓝色猫型机器人。大头,白色圆脸配巨大白眼睛与红球鼻。蓝色桶状身体,中央有白色半圆口袋。红色项圈挂黄铃铛。白色短肢,末端为红色圆球手与白色圆柱脚。整体线条圆滑,色彩鲜明(蓝、白、红、黄)。” } ## 注意事项 1. 不要尝试解释和讨论人物相关信息。 2. 输出内容以指定的json格式输出。 3. 人物形象描述简明、清晰,限制在100字以内。
提示词框架上仍然是之前多次使用的框架—— “角色 - 任务 - 少量案例 - 负面提示”。
其关键部分为:提示词中的角色部分使用了用户输入变量 {question},并特别强调可以调用工具进行信息检索——为的是补全LLM “出厂”后缺失的数据。
小问题:这里的提示词对LLM 来说是系统提示词(System Prompt),还是用户提示词(User Promopt) ?
-
然后,LLM发现自己无法准确描述浪浪山中的动画人物(显然这个人物数据比较新)。
于是,按照提示词中的说明,选择调用外部工具(web search)检索人物具体信息——

-
先后使用 “浪浪山的小妖怪 黄鼠狼 角色 形象”和 “浪浪山小妖怪 黄鼠狼精 形象描述 外貌特征” 两条不同的检索词去查找——
-
多次检索后,LLM 判断拿到了足够的数据。

从LLM的角度看:调用工具(Tools) 的过程,类似于前端调用后端的API接口——不关心具体处理过程,最后能拿到需要的数据就可以。
- 最后按照 指定的格式{ “name”: “黄鼠狼精”, “description”: “一只拟人化的黄鼠狼妖怪… …” } 输出本次任务执行结果。

不难看出,Agent 这里的实现过程为:
LLM 识别用户问题 –> 发起工具调用,尝试获取人物准确信息 –> 尝试使用不同的查询语句,进一步查询 –> 最后确定得到所需数据 –> 按照要求输出 json 格式数据。
这其中所有子动作,如:检索词调整,去哪里检索,判断检索到的内容是否有用,以及如何汇总为 100字以内的人物描述——所有这些以往需要人工操作、执行的动作,在大模型内部自主决策并执行了。
还有一点:虽然用户没有参与具体的执行步骤。但是,可以观察到它检索了什么主题,得到了什么数据等内部细节 。
把以上实现过程抽象一下,就是后面会重点讨论的 “ ReAct”模式: Reason + Action。
该模式最大的好处就是:将传统 AI 那种黑盒执行的过程 暴露出来!并大幅降低 AI “一本正经胡说八道”的情况。
文生图Agent
在这个Agent中,目标是实现使用上一步骤的json数据:
{ “name”: “黄鼠狼精”, “description”: “一只拟人化的黄鼠狼妖怪… …” }
生成对应人物图像。
和生图大模型的交互,还是使用提示词。那提示词是怎么写的呢?
|
|
其中,上一步输出的人物信息可以直接在提示词中以变量名 “name” 和 “description"引用。
同时,提示词中也明确指定了出图的尺寸( 960 * 1280 )和位置信息( 上部 3/4的区域 ) 。当然还可以指定画面风格 ,这样就可以进一步保证产出的图片风格是一致的。
为了保持每次生成的图片一致性,以上这些细节(背景,尺寸,位置,颜色,字体,风格等)都是必要的。
这也是调用多模态模型和自然语言模型时的主要差别。
这里用到的生图工具,是阿里云的百炼生图模型。
在MCP工具中,可以看到LLM在调用工具时,根据提示词 补全了相关工具参数——生成图片的张数、大小、生图提示词:
单Agent vs 多Agent 系统
本案例,就是一个最简单的多Agent 系统。稍微提一句:什么是多 Agent ?
我理解下来,就是有的任务变复杂了,一个Agent 完成的效果不好,拆分任务到两个(甚至更多)Agent 中,效果不错。于是就采用这种分工的方式共同完成某一项任务 。
类比于餐厅:
顾客不多时, 一个店员负责洗、切、炒、送、收钱。——可实现基本功能,成本低,但是出餐慢、做不了太复杂的菜。
顾客很多时,点单收钱的一个店员,送菜擦桌子的一个店员,后面炒菜配菜 可能是两个店员。
—— 扩展性好,每个人专注于自己的任务,上菜效率高,但是成本和复杂度也高,如果某个环节断了,其他人也无能为力。
对比单Agent 和 多Agent间的差异:
| 维度 | 单Agent系统 | 多Agent系统 |
|---|---|---|
| 核心定义 | 一个独立的、功能完备的AI模型或程序,负责处理从输入到输出的完整任务。 | 由多个相互协作、通信的AI Agent组成的系统,每个Agent有特定角色或专长,共同完成复杂任务。 |
| 优势 | 1. 简单性: 架构简单,易于开发、部署和调试。 2. 一致性: 决策和行为风格统一,输出连贯。 3. 可控性: 责任边界清晰,易于监控和管理。 4. 资源效率: 通常计算和通信开销较低。 |
1. 专业化与模块化: “分而治之”,每个Agent可针对特定子任务进行优化,能力更强。 2. 复杂问题解决: 能处理需要多步骤推理、多领域知识或并行任务的复杂工作流。 3. 鲁棒性与容错性: 单个Agent故障不一定导致系统崩溃,任务可能由其他Agent接管或重试。 4. 可扩展性: 可通过增加新的专业Agent来轻松扩展系统能力。 |
| 劣势 | 1. 能力瓶颈: 受限于单一模型的能力上限,难以精通所有领域。 2. 单点故障: 一旦该Agent出错,整个系统即失效。 3. 灵活性差: 工具一多的对大模型的理解、规划要求随之变高。 4. 长对话下表现变差: 过长的对话内容,大模型容易失焦,tokens 消耗大。 |
1. 系统复杂性: 架构、通信协议和协作逻辑的设计与调试极其复杂。 2. 协调开销: Agent间的通信、协商、任务分配会引入显著的延迟和计算成本。 3. 一致性与连贯性挑战: 需精心设计以确保最终输出的整体一致性和风格统一。 4. 开发与运维成本高: 需要更多开发资源,且监控、维护难度大。 |
| 核心挑战 | 1. 能力泛化: 如何让一个模型具备广泛且深入的能力。 2. 任务分解: 在模型内部有效进行复杂的任务规划和步骤分解。 |
1. 高效协作机制: 如何设计通信协议(如共享黑板、消息传递)、决策框架(如投票、领导选举)以实现高效协作。 2. 知识共享与冲突消解: 如何让Agent共享上下文,并解决它们之间可能产生的意见或行动冲突。 3. 系统级优化: 如何优化整体工作流,减少通信轮次,避免“讨论循环”。 4. 评估难度: 难以评估是哪个Agent或协作环节导致了最终的成功或失败。 |
| 典型应用场景 | • 简单的问答与对话 • 文本摘要/翻译 • 基础内容生成 • 单一工具调用(如查天气) |
• 复杂的项目规划与执行 • 多步骤研究与分析报告 • 软件开发(设计、编码、测试分工) • 模拟社会或经济系统 |
那使用哪种Agent 系统比较好呢 ?
第一点,单Agent可以满足业务需求的,就使用单Agent。
第二点,单Agent 效果不好时,再从以下四个维度评估应用场景:问题复杂度、工具种类 、容错性要求、问题泛性等四个方面。
| 角度 | 案例产品 | 案例说明 |
|---|---|---|
| 1. 问题复杂度不高 | ChatGPT / Claude 网页聊天界面 | 用户与一个单一的、强大的语言模型进行开放式对话。虽然模型本身复杂,但任务形态是线性的:接收提示词 -> 思考 -> 生成回复。它不需要在内部模拟多个角色进行辩论或分工。 |
| 2. 工具种类不多 | Notion AI / 文档助手 | 这类产品深度集成在特定环境(如文档编辑器)中。它的核心工具集非常聚焦:文本生成、改写、总结、翻译。它不需要同时调用代码解释器、搜索引擎、绘图API等多种外部工具来完成一个任务。 |
| 3. 容错性要求不高 | AI 写作助手 (如 文案仿写) | 用于营销文案、博客草稿、广告语生成。输出结果通常需要人工审核和润色,AI提供的是创意初稿或灵感,而非最终交付物。即使偶尔生成不理想的内容,用户修正的成本较低,容错空间较大。 |
| 4. 问题泛性可约束 | 智能客服聊天机器人 | 虽然基于大模型,但其任务范围被严格约束在公司产品、服务、政策的问答上。通过系统提示词、知识库检索和对话流程设计,将AI的泛化能力引导至一个明确的业务范围内,确保回答的相关性和准确性。 |
单Agent 系统效果不好的场景:项目代码AI工具(例如市面上比较知名的 Cursor, Claude Code, Trae)。
这种就不是一个Agent 可以独立完成的,它必然是多Agent 的结构。因为它不满足上述四个维度中任何一个——通用场多,逻辑和实现上复杂,调用的工具种类多,而且容错性要控制在低的水平。
而多Agent 最大的挑战就是跨Agent 间的通信和协作。架构上和工程上都会复杂的多。
这里以OpenManus 结构为例,有两个Agent:一个负责指定规划(不执行),一个负责具体执行和执行过程中的检查、矫正(结构上同样参考了 ReAct 模式)。两者间有一个 todo-list(代办事项)作为沟通协作的桥梁。
代码级复现“人物形象描述Agent”
最后花点功夫,使用简单的Python 代码实现 游戏中第一个Agent——人物形象描述Agent。
用到的工具一览
| 工具名 | 工具类型 | 描述 |
|---|---|---|
| Python | 面向对象的编程语言工具 | >=3.10 版本,生成Agent代码 |
| uv | 增强版的 python 包管理工具 | 处理&记录python项目依赖包,同时生成项目相关描述文件 |
| openai-sdk | OpenAI提供的SDK包 | 用于生成LLM 实例 |
| tavily-sdk | Tavily 提供的SDK包 | 用于实现 web检索工具 |
| DeepSeek API_key | API Token | 调用 DeepSeek 大模型的用户凭据 |
| tavily API_key | API Token | 调用 Tavily 检索服务时的用户凭据 |
项目代码
因为有SDK 工具的帮助,实现起来真的不复杂。
感兴趣的话 可以后台发送【1215】获取原代码。
项目目录下的就4个文件:tools.py, llm.py, prompt.py, agent.py。
他们相互间的关系:
| 文件名 | 资源描述 | 调用关系 |
|---|---|---|
| tools.py | web search 工具描述 web search 函数 |
Json格式化的tools 描述 –> 填充进prompt.py提示词{tools}中 Tavily-SDK –> 工具函数tavily_search()–> agent.py 中使用 |
| llm.py | llm 客户端(调用 DeepSeek API 接口) | OpenAI-SDK –> llm.client –> agent.py 中 send_messages() 函数 |
| prompt.py | ReAct 模式提示词(含有带替换的数据占位) | prompt –> 构成agent.py send_messages(message) 函数中message 的一部分 |
| agent.py | send_messages( ) 函数 ReAct 模式的Agent实现 |
send_messages( ) <– llm.client, ReAct 提示词 Agent执行任务 <– send_messages(message) 函数 <– 预先定义好的系统提示词和填充{tools}和{user_input}后的用户提示词 <– 用户提问内容, Json格式化的tools 描述 |
运行一下,看看效果

|
|
Agent 在做什么
通过输出的前缀可以暴露LLM 的执行步骤:这里大模型思考(Thought)了两次,工具执行(Action)一次。
注意:工具执行环节不是LLM 完成的,而是由代码中的工具函数tavily_search() 执行的。
它们间的关系是: LLM 生成函数查询语句 {“query”: “浪浪山的小妖怪 黄鼠狼 角色 形象"},然后交给工具函数tavily_search(“浪浪山的小妖怪 黄鼠狼 角色 形象”)执行,执行后的结果 再放入messages中,提供给下次循环中LLM使用。
-
在ReAct提示词模板中,通过文字描述定义了大模型的状态标签和状态流转过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39REACT_PROMPT = """ You run in a loop of Thought, Action, Action Input, PAUSE, Observation. At the end of the loop you output an Answer Use Thought to describe your thoughts about the question you have been asked. Use Action to run one of the actions available to you use Action Input to indicate the input to the Action- then return PAUSE. Observation will be the result of running those actions. Your available actions are: {tools} Rules: 1- If the input is a greeting or a goodbye, respond directly in a friendly manner without using the Thought-Action loop. 2- Otherwise, follow the Thought-Action Input loop to find the best answer. 3- If you already have the answer to a part or the entire question, use your knowledge without relying on external actions. 4- If you need to execute more than one Action, do it on separate calls. 5- At the end, provide a final answer. Some examples: ### 1 Question: 今天北京天气怎么样? Thought: 我需要调用 get_weather 工具获取天气 Action: get_weather Action Input: {"city": "BeiJing"} PAUSE You will be called again with this: Observation: 北京的气温是0度. You then output: Final Answer: 北京的气温是0度. Begin! New input: {input}"""问题解决的过程被细化为不同状态:

“Thought”: “问题规划” –> “Action”: “外部工具名” –> “Action Input”: “外部工具参数” –> “PAUSE”(等待工具执行结果) –> “Observation”: “工具执行结果” –> “Thought”: “判断执行结果是否有助于回答用户提问” –if可以解决–> “Final Answer”: “LLM 参考工具执行结果 回答用户提问”
-
逻辑上 Agent 的执行过程就是个 循环语句——直到LLM 判断得出 “Final Answer:” 后任务才会停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32while True: response = send_messages(messages) response_text = response.choices[0].message.content print("大模型的回复:") print(response_text) final_answer_match = re.search(r'Final Answer:\s*(.*)', response_text) if final_answer_match: final_answer = final_answer_match.group(1) print("最终答案:", final_answer) # LLM 答复中出现 "Final Answer:" 才会跳出循环 break messages.append(response.choices[0].message) action_match = re.search(r'Action:\s*(\w+)', response_text) action_input_match = re.search(r'Action Input:\s*({.*?}|".*?")', response_text, re.DOTALL) # LLM 思考后同时输出"Action" 和 "Action Input" 才会执行对应的工具(任务) if action_match and action_input_match: tool_name = action_match.group(1) params = json.loads(action_input_match.group(1)) observation = "" if tool_name == "tavily_search": observation = tavily_search(params['query']) print("工具执行:Observation:", observation) else: observation = f"未知的工具: {tool_name}" print("工具执行:Observation:", observation) messages.append({"role": "user", "content": f"Observation: {observation}"}) -
大模型本身的思考、工具描述和工具函数执行的结果都会追加到变量 messages 中。
1 2messages.append(response.choices[0].message) messages.append({"role": "user", "content": f"Observation: {observation}"})
再考虑到Agent本身就是循环执行的,可以想见如果一个问题要多次调用一个或多个工具,messages中的内容也是越来越长的。
信息越来越长的结果有几个:
- Tokens 消耗加速
- 长的上下文可能会导致模型失焦
- 过长的上下文(如 16k) 可能达到LLM 上下文的上限,那超出部分的内容模型根本看不到
ReAct 范式是什么
首先 他不是单独一个元素:不是工具,不是agent ,也不是提示词。
它是一套指导AI解决问题的框架:通过暴露解决问题的状态和灵活调用外部工具的特点,从逻辑和数据两个层面 提高AI回答问题的能力。在回答质量和回答准确度上都有提升。
可以说 ,这就是当前单Agent系统下的标准框架。
【彩蛋】

