资讯动态

AgentScope 核心概念与 ReAct 实战解析

发布时间:2026/10/27 20:05:01 来源:尧图企业网站定制
第一次打开 AgentScope 文档的时候我心里是有点抵触的。文档开篇就把 Agent、Message、Model 与 ReAct 四个概念摆在最前面看起来像四个抽象名词的排列组合。很多做 AI 应用的朋友和我一样之前写 agent 都是拿大模型 SDK 直接 while 循环——对话、拼历史、判断要不要调工具全靠手写。一旦项目复杂起来模型换来换去、工具越加越多代码立刻散成一地。AgentScope 这类框架的价值就在于把这四个东西固定成清晰的概念和统一的接口让你把精力放到业务上而不是每次都在消息到底怎么传的泥潭里挣扎。这篇文章适合两类人刚接触 AgentScope、看文档被概念砸晕的新手以及已经开始写 agent 但总觉得框架有点绕的开发者。我会围绕 Agent、Message、Model 与 ReAct 这四个核心概念逐个拆解讲清楚每个概念在真实场景里到底扮演什么角色再给一个能跑通的最小例子最后把我在配置模型时踩过的几个高频坑一并列出来。看完之后你再回去看 AgentScope 文档会发现它其实没有想象中那么抽象。1. 为什么是这四个概念AgentScope 的最小世界观1.1 一个朴素的问题写 agent 为什么需要框架我早年写过一个客服 bot那时候的想法非常简单用户发一句话我就把历史拼成一个 messages 数组发给模型拿回文本就返回给用户。后来要支持天气查询我在系统提示词里写如果要查询天气请输出以 WEATHER: 开头的文本然后用正则解析。这个方案确实能跑但每个写过类似代码的人都会遇到同样的三个问题第一模型切换成本极高。从 OpenAI 换到通义、再换到本地部署模型消息格式、工具定义、API 地址全都不一样业务代码和供应商 SDK 紧紧耦合在一起。第二日志里全是乱七八糟的字符串。你看到一段以 WEATHER: 开头的输出根本无法确认是用户说的、模型说的、还是工具返回的更别提复盘一条完整的决策链路。第三一旦想让两个 agent 协作代码结构直接崩。两套 while 循环、两套历史记录、两套工具解析逻辑肉眼可见地失控。所以框架要做的抽象其实很朴素把谁在干活和信息怎么传分开把调用哪个大脑和业务逻辑分开再把如何决定下一步行动变成一种可复用、可观测的模式。AgentScope 把这四件事分别命名为 Agent、Message、Model 与 ReAct。1.2 一个生活化类比这四件事就像一家公司的运转方式我在给团队讲的时候喜欢用一个类比把多 agent 系统想成一家公司。Agent 是员工。每个人有自己的岗位说明书系统提示词、工作目标、工作记忆上下文状态。Message 是工单。工单上除了正文还带着发起人、接收人、归档编号。没有工单制度的时候大家只能在办公室里大声喊话所有人都能听到但没人知道该由谁处理有了工单信息才能被路由、审计、归档。Model 是员工背后的知识顾问。员工遇到不确定的问题就去问顾问拿到建议再回来干活。顾问本身不直接干活但它决定了员工的智力上限。ReAct 是标准作业流程先阅读工单Reason确定要不要调用工具Action看执行结果Observation再判断下一步。没有这套流程员工可能凭感觉乱答有了这套流程每个动作都有据可循。这套类比帮我解决了很多概念有什么用的疑问。你写代码的时候实际就是在定义员工、流转工单、接顾问、跑流程。1.3 版本差异1.x 和 2.x 的概念位置不一样看 AgentScope 资料时有一个特别容易踩的坑版本。网上搜出来的react agent 框架图agent 架构很多混着 1.x 和 2.x 的写法新手很容易被带偏。我整理了一个粗略对照维度1.x 常见姿势2.x 推荐姿势初始化agentscope.init(model_configs[...])config.toml ModelManagerAgent 定义继承 AgentBase / ReActAgent 等类agent装饰的生成器函数消息载体Msg 类Message 类模型管理init 参数里塞 model_configs统一从 ModelManager 获取配置方式Python dict / yamltoml 统一配置2.x 把概念收敛得更轻。它的核心思路是agent 就是一个被装饰的生成器函数函数通过yield输出消息通过入参接收消息。模型接入统一走 ModelManager配置文件统一走 toml。理解了这个大方向再去看具体 API 就不会懵。2. Agent 不是大模型的别名装饰器如何重新定义参与者的边界2.1 最常见的一个误区Agent 等于大模型很多同学一开口就说我用 GPT 做了一个 agent。但严格来说模型只是 agent 的大脑组件agent 是模型、提示词、记忆、工具权限、终止逻辑这几样东西的封装。模型不会自己开口说话它只会根据输入生成文本是 agent 把系统提示词、历史消息、工具定义拼装起来发给模型再决定如何处理输出。这个区别不是文字游戏。如果你把 agent 直接等同于模型你就会把全部逻辑都塞进提示词里然后期待模型自己解决问题。但实际项目中决定一个 agent 强不强的是它周围的结构它能看到哪些工具、它的记忆如何管理、它在什么条件下停止循环。这些都是模型之外的东西。AgentScope 用agent装饰器做的就是把模型调用这个动作藏起来让你专注于定义参与者的行为和状态。2.2 agent 装饰器生成器函数即 Agent在 AgentScope 2.x 中定义一个 agent 最直接的姿势是用agent装饰一个生成器函数。下面是一个最简单的示例from agentscope.agent import agent from agentscope.message import Message agent def sink_agent(msg: str | Message | None): if msg is None: yield 请给我一句话我帮你转达。 else: content msg.content if isinstance(msg, Message) else msg yield f我收到了{content} a sink_agent(namereceiver) reply a(你好AgentScope) print(reply)这里有两个关键设计第一次接触时可能不习惯但用顺手之后会觉得非常自然。第一个关键点是函数用yield而不是return。因为一个 agent 在一次交互中可以产生不止一条消息它可能先输出一句让我查一下再输出工具调用结果最后给出最终答案。生成器天然适合表达我暂时说完一段把控制权交回去等下一次输入再继续。如果你用过 Python 的协程理解起来会很快。第二个关键点是实例化时需要给它一个name。这个名字不是装饰性的它就是 Message 路由的依据。在多 agent 系统里用户消息、工具结果、其他 agent 的输出最终都靠 name 来判断这条消息是发给谁的。命名规范很重要我建议用域名式的命名比如user_proxy、assistant_coder、tool_executor避免重名导致消息串线。2.3 需要状态时从 agent 升级到 Roleagent适合轻量场景但一旦 agent 要维护记忆、账号信息、上下文窗口管理函数闭包就不够用了。这时候可以用 AgentScope 的角色对象Role。你可以把 Role 理解成一个带状态的 agent 壳子它内部持有模型、系统提示词、工具列表、会话状态对外暴露统一的调用接口。简单需求用agent装饰器复杂需求用 Role 类。我的判断标准是如果这个 agent 内部状态超过两个变量或者需要共享工具列表就直接上 Role。否则后续要加记忆功能时你会发现把闭包函数改成类对象的成本比一开始用 Role 高得多。2.4 多 Agent 是怎么串起来的理解 Agent 的最好方式是看一个两个 agent 互相传递消息的经典示例from agentscope.agent import agent agent def user_agent(msg: str | None): if msg is None: yield 你好我是用户侧 agent else: yield fuser 收到{msg} agent def assistant_agent(msg: str | None): if msg is None: yield 你好我是助手 agent else: yield fassistant 收到{msg} u user_agent(nameuser) a assistant_agent(nameassistant) msg None for _ in range(3): msg u(msg) msg a(msg)跑一遍你就会感受到消息像乒乓球一样在 u 和 a 之间来回跳动每次返回的都是一个 Message 对象而不是裸字符串。这个循环虽然简单但它揭示了 agent 系统的本质——所谓智能体系统就是一堆有名字的参与者通过消息互相驱动。你后续做的所有复杂编排都是在这个基础之上叠加路由、并行、工具和终止条件。3. Message 的完整形状从文本搬运工到多 Agent 路由的枢纽3.1 Message 到底携带了什么很多刚上手的人会问为什么不能直接用字符串用一个 str 传递不就行了吗答案是字符串只能表达内容表达不了谁说的、发给谁、这是普通回答还是工具调用结果。尤其在多 agent 协作中路由和审计必须依赖消息的元信息。AgentScope 的 Message 对象大致包含这些内容维度作用content消息正文可以是文本或结构化内容发送者标识这条消息来自哪个 agent接收者可选用于消息路由工具调用信息模型请求调用工具时的参数metadata附加信息比如会话 ID、时间戳、上下文标记所以 Message 的本质是一张带抬头和归档号的工单而字符串只是一张没有抬头的小纸条。工单制度看似笨重但在多方协作时路由、去重、追溯全部依赖它。3.2 消息是决策链路的骨架我在实际调试中最深刻的体会是ReAct 循环里的 observation本质上也是一条 Message。当模型决定调用工具时它输出的不是一个普通句子而是带工具调用信息的特殊消息框架帮你执行工具之后工具执行结果也应该构造回一条 Message再送回给模型。这样一来从用户提问到最终回答整条链路就是一条消息链。所以调试时不要只看 content要把整条 Message 对象完整打印出来特别是工具调用信息和 metadata。我见过太多开发者排查问题时只盯着文本内容结果发现模型其实一直没看到工具返回的结果或者看到的是上一步的旧结果。只要在日志里把每条 Message 发送者、工具调用参数、时间戳都打出来问题定位立刻清楚。3.3 Message 和对话历史不是一回事不少框架把对话历史实现成list[Message]AgentScope 更进一步消息之间可以形成有向的流转结构因为它们可能来自不同 agent、存在分支和合并。早期你可以不深究这一点把消息理解成按顺序流动的消息队列就行。但如果你要做的场景涉及并行 agent、条件分支、多路汇总建议提前看文档中关于消息图的部分。这里有一个实操技巧让每条 Message 都带上会话 ID 和步骤序号。多 agent 场景下日志会混在一起没有这两个字段你几乎不可能还原一次完整交互。4. Model 接入层不是换个 API 就完事统一接口与配置文件五坑4.1 Model 抽象到底解决了什么Model 这个概念的引入就是为了让业务代码不关心你用的是哪家模型。在 AgentScope 里获取一个模型实例的典型方式是这样from agentscope.manager import ModelManager model ModelManager.get()[gpt-4o-mini] resp model(你好请介绍一下你自己) print(resp)这段代码跑通之后如果你想从 GPT 换成通义千问或者换成熟本地部署模型业务代码一行都不用改只需要改配置文件。这种统一插座的设计在你只需要一个模型的时候看不出好处但当你同时要跑三四个模型做对比实验、或者在模型服务繁忙时需要降级切换时价值就非常明显。4.2 config.toml 是模型配置的第一现场AgentScope 2.x 把模型配置统一放在 config.toml 里。一个典型的配置文件长这样[models.gpt-4o-mini] model_type openai model_name gpt-4o-mini api_key sk-这里是你的密钥 [models.qwen-plus] model_type dashscope model_name qwen-plus api_key sk-这里是你的密钥注意[models.xxx]这个层级xxx就是你在代码里取模型时用的 key。热词里有人反馈chatgpt 无法加载 config.toml绝大多数情况都是这个原因文件路径不对或者model_type写错导致框架无法解析。我建议在项目根目录放 config.toml并且把密钥放到环境变量里注入避免把真实 key 提交进代码仓库。4.3 自定义模型Model 是一个可扩展的协议如果你的公司内部有统一的模型网关不想直接调 OpenAI 或 DashScope 的裸接口可以实现自己的 Model。基本思路是继承 Model 协议把请求转发到内部网关from agentscope.model import Model class GatewayModel(Model): def __init__(self, gateway_url: str, model_name: str): self.gateway_url gateway_url self.model_name model_name def __call__(self, *args, **kwargs) - str: # 把 args/kwargs 转成你的网关入参 # 然后把响应转成纯文本返回 ...自定义 Model 时有两点最重要超时和重试策略。因为 agent 在 ReAct 循环里会反复调用模型模型接口一旦不稳定整个链路都会跟着抖动。我习惯在自定义模型里统一加 30 秒超时和 3 次指数退避重试这样上层业务逻辑不需要到处处理异常。4.4 配置文件里的五个高频报错以及我的排查套路在学习 AgentScope 的过程中配置报错占了我遇到问题的一半以上。下面这五个错误几乎每个上手的人都会碰到报错关键词常见原因处理方式nosuchkey / invalid api keyAPI key 没配、配错或环境变量没注入检查 config.toml 和启动环境重新生成 keymodel not found模型名拼写错误或该名称在当前账号下不可用去模型服务商控制台复制准确的模型名selected model is at capacity模型服务端过载触发限流指数退避重试并准备降级模型maximum context length is 1048576 tokens上下文中消息太多太长超限截断、摘要历史、切换更长上下文模型unsupported_country_region_territory服务商限制部分地区/账号访问该模型确认服务商支持范围换用可用服务方案nosuchkey这类问题最气人因为它不一定是 key 真错了。我遇到过把 key 正确写在 config.toml 里但程序仍然报 invalid api key 的情况最后发现是启动脚本里的环境变量覆盖了配置文件字段。排查时先去启动环境把所有变量 print 出来比逐个怀疑 key 快得多。selected model is at capacity是另一个高频问题。这个错误不一定是你代码的问题纯粹是模型服务端忙不过来。我现在的做法是在 ModelManager 里注册一个主模型和一个备用模型遇到容量错误就自动切换备用模型。重试策略用先等 2 秒再等 4 秒再等 8 秒最多 3 次的指数退避简单且有效。5. ReAct 循环让模型边想边做的机制以及必须加的护栏5.1 ReAct 不是 React 前端而是 Reasoning Acting如果你在网上搜 ReAct很容易被 React 前端框架的内容淹没。react 生命周期函数react 图表这些热搜词和 agent 开发根本不是一件事。这里说的 ReAct 指的是Reasoning Acting一种让模型在回答问题时交替进行思考与行动的模式。标准流程是四步循环Thought模型分析当前任务决定下一步怎么做。Action模型选择并调用一个工具。Observation拿到工具执行结果相当于模型的眼睛看到了环境反馈。重复 1-3直到模型认为可以给出最终答案。为什么这种模式比一次性让模型输出答案更可靠因为模型有机会在实际执行中修正自己的判断。比如你问明天北京会下雨吗模型如果直接回答很可能凭训练数据瞎猜但如果它能先调用天气 API 拿到数据再根据数据回答准确率会高得多。用最少机制获得可行动性这就是 ReAct 的吸引力。5.2 在 AgentScope 里怎么把 ReAct 组织起来AgentScope 中 ReAct 的实现思路本质上还是消息流转。你可以把工具调用看作一个特殊的 agent主 agent 输出一条带工具调用请求的消息工具执行模块收到后运行对应函数然后把结果作为一条新消息送回给主 agent。用文字描述就是1. 构造用户任务 Message 2. 把任务交给带工具的 agent 3. agent 的回复中出现工具调用请求 4. 执行工具函数 5. 工具结果封装成 Message继续交给 agent 6. 如果 agent 决定不再调用工具得到最终答案这个循环里的每一步都是 Message 的流转而不是散落各处的字符串拼接。这也是为什么我前面强调要理解 Message——ReAct 的所有中间状态都由 Message 承载。5.3 实战中的安全护栏工具调用是一把双刃剑。你给 agent 的工具越多它的能力越强但执行意外操作的风险也越高。热词里有人关注agent 安全这确实是实用项目绕不开的话题。我的几条经验第一白名单工具。只暴露当前任务必要的最小工具集合不要一股脑把所有工具都注册进去。比如先只让它查时间不碰文件删除、数据库写入这类高危操作。第二限制最大循环次数。ReAct 循环理论上可以一直转下去如果模型卡在调工具、看结果、再调工具的死循环里你的 token 消耗会非常恐怖。我一般设置 3 到 5 步上限超了就强制结束或者让模型直接给出当前最优答案。第三对工具名做校验。模型偶尔会幻觉出一些不存在的工具名框架如果直接执行就会报错稳妥的做法是在执行前检查工具名是否在白名单里不在就返回一个提示消息告诉模型没有这个工具请检查你的选择。第四写操作需要确认。涉及外部副作用发送邮件、改数据库、发起支付的工具设计时加一道人工确认或者至少在日志里完整记录每次调用的参数。5.4 token 为什么会涨这么快热词里ai agent token 是什么意思这个搜索我猜背后就是这个问题为什么用 ReAct 模式之后 token 消耗特别快原因其实不难理解。模型是无状态的每次请求都要把前面所有的思考过程、工具调用记录、工具结果重新传给模型。一次 5 步的 ReAct 循环最后一步模型看到的上下文包含前面 4 轮的全部内容。如果你把用户历史、系统提示词、工具 schema 全塞进去token 自然成倍增长。我的优化手段有三个。一是精简系统提示词把不重要的说明移除二是把历史中较早的对话做摘要而不是全部保留三是给工具返回结果设置长度上限一些日志式输出只截取关键部分。这些优化做完之后token 消耗通常能下降 30%-50%而且模型输出质量反而更稳定。6. 最小可跑 Demo把 Agent、Message、Model 和 ReAct 接成一条流水线6.1 先准备一个项目结构和配置概念讲多了还是需要一个能落地的例子。我们先建一个目录agent_demo/ ├── config.toml └── main.pyconfig.toml 里注册一个模型[models.gpt-4o-mini] model_type openai model_name gpt-4o-mini api_key 在此填入你的密钥如果你用的是国内可访问的模型服务把model_type换成对应服务商支持的类型即可代码不需要改。6.2 用最朴素的循环演示 ReAct下面的示例故意不用太高层的内置封装而是把 ReAct 的循环逻辑直接写在主流程里。这样你能看清每一步消息是怎么流转的等理解了再回到框架的高层 API就没有黑盒感了。import time from agentscope.manager import ModelManager # 从配置中获取模型 model ModelManager.get()[gpt-4o-mini] # 工具函数这里用最简单的取当前时间 def get_current_time() - str: return time.strftime(%Y-%m-%d %H:%M:%S) # 工具注册表 tools { get_current_time: get_current_time, } def build_prompt(user_text: str) - str: return ( 你是一个会调用工具的智能体。 当需要获取当前时间时请调用工具 get_current_time。 如果已经拿到工具结果请基于结果直接回答用户。 f\n用户问题{user_text} ) def run_react(user_text: str, max_steps: int 3) - None: prompt build_prompt(user_text) observation None for step in range(max_steps): if observation is None: current_prompt prompt else: current_prompt prompt f\n工具返回结果{observation} reply model(current_prompt) # 概念演示这里用简单的字符串标记判断是否需要调用工具 if tool_call: get_current_time in reply: obs tools[get_current_time]() print(f[step {step}] 模型决定调用工具返回{obs}) observation obs continue print(f[step {step}] 最终答案{reply}) return print(达到最大步数强制结束) if __name__ __main__: run_react(现在几点了)实际运行后你会看到类似这样的输出[step 0] 模型决定调用工具返回2026-05-12 14:03:22 [step 1] 最终答案当前时间是 2026-05-12 14:03:22这就是一次完整的 Observation-Action-Observation 循环。现实项目里你不会用字符串匹配来判断工具调用而是用框架提供的 tool_call 解析、更严格的 schema 校验以及带工具列表的系统提示词。但底层的消息流转逻辑和我上面写的一模一样。6.3 把 Demo 改成更贴合 AgentScope 风格上面那段代码完全没有用到agent讲清楚概念可以但还不是 AgentScope 的常用姿势。熟悉了消息流转后可以把它升级成 agent 风格from agentscope.agent import agent from agentscope.manager import ModelManager agent def assistant(msg: str | None): model ModelManager.get()[gpt-4o-mini] if msg is None: yield 请告诉我你想做什么。 else: yield model(str(msg)) a assistant(nameassistant) response a(现在几点了) print(response)这段代码里assistant是一个定义了行为的 Agentmsg参数是流进来的 Message简化示例中直接处理文本形式模型调用被封装在函数内部。真实项目中工具调用、历史记忆、终止条件都会由 Role 和框架层接管而你只需要把注意力放在这个 agent 该做什么、能碰哪些工具上。6.4 跑通之后的扩展方向最小 Demo 跑通之后往下的扩展方向基本是三件事第一把工具换成真实能力。比如接入天气 API、查询数据库、调用内部服务。每加一个工具记得同时更新工具描述和参数格式模型对工具的理解完全来自这些描述。第二把单 agent 变成多 agent。拆成用户代理 任务理解 agent 工具执行 agent三层消息在它们之间流转。这个阶段你会真正体会到 Message 路由的价值。第三接上可观测性。注册工具调用日志、打印完整 Message、记录每次模型请求的 token 消耗。项目一旦超过一个 agent没有观测基本等于盲人摸象。最后说一点实际操作中的体会如果你正在学习 AgentScope我的建议是不要一上来就啃全部 API。先理解这四个概念的关系再跑通上面的最小 Demo然后才去研究配置文件里的细枝末节。遇到报错先分门别类model not found大概率是名字拼错nosuchkey大概率是环境变量和配置文件打架maximum context length大概率是历史消息没做压缩。概念层面清晰了这些坑都只是体力活。我实际用下来的感受是AgentScope 真正难的不是某个 API 怎么调而是你愿不愿意按照它的抽象去组织代码——一旦你接受了 Agent、Message、Model 与 ReAct 这套世界观剩下的开发体验其实相当顺。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑