AI Agent 技术全景解析 —— 从提示词工程到高级智能体构建
作者:vivo 互联研发效能团队- Wu Qinghua
本文系统梳理了 AI Agent 技术演进全景,核心目标是构建能自主规划、调用工具、交付结果的"数字员工"。从提示词工程(如何精准传达意图),到 RAG(如何接入外部知识),再到 Harness 工程(如何让模型持续可靠运行),再到 ReAct 循环(如何赋予自主决策与执行能力),最后到多智能体协作。
1分钟看图掌握核心要点👇

引言:从"问答工具"到"数字员工"
我们熟悉的 AI 交互
大家都用过 ChatGPT 或类似的大语言模型。交互方式很简单:你问一个问题,它给一个回答。这种"对话式"交互已经很强大了,但仔细想想,它有三个明显的局限:
- 一次性任务:每次对话都是独立的,它不会主动延续上次的话题
- 缺乏长期记忆:换个窗口,它就把你忘了
- 无法自主执行:它只能"说",不能"做"——不能帮你查数据库、发邮件、跑测试
打个比方:这就像一个博学但没有手脚、也没有记忆的顾问。你每次都得把背景重新交代一遍,他给你建议,但你得自己动手执行。
核心问题
我们真正想要的是什么?是一个能理解目标、自主规划、并执行复杂任务的"智能代理"——就像一个新员工,你给他一个任务,他会自己拆解步骤、调用工具、遇到问题会调整方案,最终交付结果。
这就是 AI Agent 要解决的核心问题。
一段简短的历史演进
在正式开始之前,让我们快速回顾一下这条技术路线是如何演进过来的——理解历史,能帮我们看清未来:

一条主线贯穿始终:从"人写好 prompt 让 LLM 执行",到"LLM 自己规划并执行"——自主性一步一步提升,每一步都伴随着新的工程挑战。
今天的路线图
本次分享,我将带大家走完这样一条路:

每一步都不是凭空出现的,而是前一步跑不动了,才催生了这一步。理解了这条因果链,你就理解了 Agent 的全貌。
一、基石:与 LLM 高效协作的艺术
在构建 Agent 之前,我们必须先掌握与 LLM 高效沟通的方法。这一部分讲三个层次的"协作艺术"。
1.1 提示词工程(Prompt Engineering)
解决的问题:如何精确地向 LLM 传达意图,获得高质量的回答?
这是人机协作的起点。LLM 的输出质量高度依赖输入——同样的问题,不同的"问法"可能得到天差地别的结果。这不是 bug,而是 LLM 概率推理本质的必然结果:prompt 决定了模型在概率空间中往哪个方向走。
底层原理:为什么 prompt 如此重要
要理解提示词工程为什么有效,需要回到 LLM 的工作原理。LLM 本质上是一个自回归语言模型——它做的事情是"给定前面的 token,预测下一个 token 的概率分布":
P(token_n | token_1, token_2, ..., token_{n-1})
这意味着:
- 每一个 token 都会改变后续所有 token 的概率分布。prompt 中的每一个词,都在把模型"推向"某个方向。这就是为什么角色设定("你是 Java 工程师")有效——它把后续 token 引向技术领域的概率空间。
- LLM 没有真正的"理解"。它不是先理解你的意图再生成回答,而是基于训练数据中的 pattern 统计,逐 token 生成最"像"训练数据中类似上下文的文本。Few-shot 示例有效,正是因为它在 prompt 中构造了"这种输入应该对应这种输出"的 pattern。
- 上下文窗口是注意力机制的作用范围。Transformer 的自注意力机制让每个 token 都能"看到"窗口内的所有其他 token,但窗口之外的内容完全不可见。所以"放在上下文里"和"不在上下文里",对 LLM 来说是"存在"和"不存在"的区别。
理解了这些,你就会明白为什么 prompt 不是"花言巧语",而是控制 LLM 输出分布的工程手段。
基础技巧
- 角色扮演:给 LLM 一个明确的身份。"你是一个资深 Java 后端工程师"比"请回答以下问题"有效得多——它约束了输出范围,影响模型的注意力分布。
- 清晰指令:把任务要求说清楚、说具体。"写一个分页查询接口,用 MyBatis-Plus,返回 Result 包装"比"写个接口"好得多。
- Few-shot 示例:给几个输入-输出的例子。LLM 是从例子中 pattern matching 的,给它看三个情感分析的例子,比单纯说"请分析情感"有效得多。
// AgentScope 中的角色设定 + 结构化输出
ReActAgent agent = ReActAgent.builder()
.name("weather_assistant")
.sysPrompt("你是一个天气助手,可以查询不同的城市的天气情况,可以简洁的按照格式给用户回答。")
.model("dashscope:qwen-plus")
.build();
// 结构化输出——让 LLM 按 JSON Schema 返回,而非自由文本
public record WeatherResponse(String location, String temperature, String condition){}
Msg result = agent.call(
List.of(new UserMessage("旧金山天气如何?")),
WeatherResponse.class
).block();
WeatherResponse weather = result.getStructuredData(WeatherResponse.class);
// weather.location() → "San Francisco"
框架会根据模型能力自动选择路径:支持原生结构化输出的模型走 response_format 参数,不支持的走"合成工具 + 指令"的降级路径——但对调用方完全透明。
高级技巧
思维链(Chain-of-Thought, CoT):让 LLM "想一想再回答",把推理过程写出来。本质是把"一步到位的直觉判断"拆成"多步的显式推理",每一步都在缩小概率空间,降低单步出错率。这个思想后来直接演变成了 Agent 的 ReAct 循环。
CoT 之所以有效,有一个信息论视角的解释:直接生成最终答案时,模型需要在一个 step 内完成"从输入到答案"的全部映射,这个映射的熵很高(不确定性大),出错概率高。而 CoT 把这个映射拆成多个 step,每个 step 的熵更低(从"当前推理状态"到"下一步推理状态"的映射更确定),每步出错率更低,整体准确率就上去了。
自洽性(Self-Consistency):让 LLM 对同一问题多次推理(设 temperature > 0),取多数票结果。用"多个独立推理的共识"代替"单次推理的直觉",降低随机性。代价是成本翻 N 倍。
横向对比:不同提示策略的取舍

一个趋势:目前新一代的模型基本都开始把 CoT 内化到模型训练中——模型自己学会"内部思考",不再需要用户手动加 "think step by step"。但理解 CoT 的原理仍然重要,因为它是 ReAct 循环的思想源头。
类比:提示词工程就像给一个聪明但不了解你业务的新员工写"沟通指南"。指南写得越清楚,他交付的成果越符合预期。
1.2 上下文工程(Context Engineering)
解决的问题:LLM 的上下文窗口有限,如何处理需要外部知识或超长内容的任务?
提示词工程管的是"一次推理怎么说清楚",但很快我们会遇到一个问题:有些知识不在 prompt 里,LLM 根本不知道。比如公司内部的报销制度、最新的产品文档、某个客户的历史订单——这些不在 LLM 的训练数据中。
为什么不能直接微调(Fine-tuning)?
一个自然的问题是:既然 LLM 不知道私有知识,为什么不在私有数据上微调它?RAG 相比微调有什么优势?

核心区别:微调改变的是模型的"能力"(怎么回答),RAG 改变的是模型的"输入"(基于什么回答)。事实性知识适合 RAG(可更新、可溯源),行为模式/领域推理能力适合微调。两者不互斥,实际系统常常组合使用。
RAG 的工作流程
RAG(Retrieval-Augmented Generation,检索增强生成) 的核心流程:

RAG 的精妙之处在于:不把所有知识塞进 LLM,而是在需要时按需检索。就像人不需要把百科全书背下来,遇到问题时去查就行。
底层原理:向量检索是怎么工作的
具体怎么"查"?通常用向量检索:
- 文档预处理:把文档切成片段(通常 200-500 token 一段),每个片段用 embedding 模型转成向量(一个几百到几千维的浮点数数组)
- 查询时:用户问题也用同一个 embedding 模型转成向量
- 相似度计算:计算问题向量与每个文档向量的余弦相似度,取最相关的 Top-K
为什么 embedding 能捕捉语义相似度? embedding 模型在训练时,学会了把"语义相近"的文本映射到"空间相近"的向量。比如"如何报销差旅费"和"差旅费报销流程"这两个短语,虽然用词不同,但 embedding 向量的余弦相似度会很高。这比关键词匹配强得多——关键词匹配找的是"字面相同",向量检索找的是"意思相近"。
// AgentScope 中接入知识库的代码示例
KnowledgeBase kb = new BailianKnowledge(baseConfig);
ReActAgent agent = ReActAgent.builder()
.name("assistant")
.model("dashscope:qwen-max")
.knowledgeBase(kb) // 注册知识库
.build();
// Agent 在需要领域知识时会自动检索知识库
Msg result = agent.call(
List.of(new UserMessage("我们公司的差旅费报销标准是什么?")),
RuntimeContext.empty()
).block();
// Agent 内部:检索"差旅费报销"相关文档片段 → 拼入 prompt → LLM 基于文档回答
RAG 的进阶:不只是"检索+拼接"
朴素 RAG(检索 Top-K 拼入 prompt)有很多变体来提升效果:
- 混合检索:向量检索(语义)+ 关键词检索(BM25,精确匹配)+ 重排序,取长补短
- 查询改写:LLM 先把用户问题改写得更适合检索(如"那个报销的事" → "差旅费报销标准 流程")
- 分块策略:按语义切分而非固定长度,或用父子文档(检索子块、返回父块获取更多上下文)
- 多跳检索:第一轮检索结果中发现新线索,再检索一轮(适合"A 公司的 CEO 之前在哪家公司"这种需要推理的问题)
RAG 解决了两个关键问题:知识过时(知识库可以随时更新)和幻觉(LLM 基于检索到的真实文档回答,而非凭空编造)。它也是后面 Agent 连接外部知识库的基础。
类比:如果提示词工程是给新员工写"沟通指南",那 RAG 就是给他配一个"公司内部文档检索系统"——他不需要背下所有文档,需要时搜一下就行。
1.3 Harness 工程(Agent的运行时基础设施)
解决的问题:单一的 Prompt 无法完成复杂任务,如何将多个 LLM 调用串联起来,形成工作流?
从"链"到"Harness"的演进
到目前为止,我们讲的都是"一次 LLM 调用"。但真实任务往往需要多步:
- 先理解用户意图 → 再检索相关知识 → 再生成回答 → 最后格式化输出
- 先写代码 → 再跑测试 → 测试失败则修改 → 再跑测试
2022 年,LangChain 提出了"链(Chains)"的概念——把多个 LLM 调用(以及非 LLM 的处理步骤)用代码串成一条流水线。这是最早的流程编排尝试:
# LangChain 的早期"链"范式(伪代码)
chain = LLMChain(
llm=OpenAI(),
prompt=PromptTemplate("翻译以下文本为英文:{text}")
)
result = chain.run(text="你好世界")
但"链"有一个根本局限:它是开发者硬编码的固定流程。链走哪条路、在哪分叉、在哪循环,都是写死的。一旦任务需要"根据情况动态决定下一步做什么",链就力不从心了。
这就催生了从"链"到"Agent"的转变——而 Agent 需要长期运行、状态管理、安全管控等工程基础设施,这就是 Harness 工程。
Harness 工程 关心的是运行时基础设施:让一个自主 Agent 能长期、可靠地跑起来,需要哪些支撑层。它解决的不是"走哪条路",而是"在路上怎么不翻车"。
在 AgentScope 2.0 中,这套编排能力被系统化为 Harness 工程。它不只是把调用串起来,还解决了:
- 状态管理:每一步的中间结果如何保存和传递
- 上下文压缩:多步对话后上下文膨胀怎么办(自动摘要 + 大结果落盘)
- 错误处理:某一步失败了如何重试或降级
- 可观测性:整个流程的执行过程如何追踪和调试
底层原理:Middleware 洋葱模型
Harness 的核心扩展机制是 Middleware(中间件)——它借鉴了 Web 框架(如 Express、Koa)的"洋葱模型",在 ReAct 循环的关键时机插入自定义逻辑:

每个 middleware 可以在"前"和"后"两个时机介入。比如你可以写一个审计 middleware,在 onActing 的"前"记录"即将调用什么工具",在"后"记录"工具返回了什么"——而不影响其他逻辑。
// 自定义 Middleware 示例:记录所有工具调用到审计日志
publicclassAuditMiddlewareextendsMiddlewareBase {
@Override
public Mono<Void> onActing(ActingContext ctx, MiddlewareNext next){
String toolName = ctx.getToolCall().getName();
String userInput = ctx.getToolCall().getInput().toString();
auditLog.info("工具调用开始: tool={}, input={}", toolName, userInput);
return next.proceed(ctx)
.doOnSuccess(v -> {
String result = ctx.getToolResult().getTextContent();
auditLog.info("工具调用完成: tool={}, resultLen={}",
toolName, result.length());
});
}
}
ReActAgent agent = ReActAgent.builder()
.name("audited_agent")
.model("dashscope:qwen-max")
.middlewares(List.of(new AuditMiddleware()))
.build();
关键设计:Harness 的内置能力(压缩、记忆、子 Agent、Plan Mode)全部以 middleware 形式实现,钩在 ReAct 循环的关键时机上。核心推理算法本身不变,Harness 只往里加东西。这意味着你可以按需开启——不需要的全关掉,Agent 依然能跑。
横向对比:主流编排框架

选型建议:简单的 LLM 应用用 LangChain 快速搭原型;以 RAG 为核心的应用用 LlamaIndex;需要构建生产级自主 Agent 用 AgentScope。三者不是互斥的——AgentScope 也可以接入 LlamaIndex 的检索能力。
Harness 工程将 LLM 从一个"单点工具"变成"流水线"中的一个环节,是通向 Agent 自动化的关键一步。
类比:如果提示词工程是"沟通指南",RAG 是"文档检索系统",那 Harness 工程就是"标准作业流程(SOP)"——把多个步骤编排成一条可重复、可监控的流水线。
二、核心:构建自主工作的 AI Agent
前面三块基石,本质上都还是"人驱动、LLM 执行"的模式。现在,我们进入真正的 Agent 领域——赋予 AI 自主性。
2.1 Agent 的"大脑"——规划与决策(Planning & Decision-Making)
解决的问题:面对复杂目标,Agent 如何自主决定"先做什么,后做什么"?
规划范式的演进
Agent 的规划能力经历了几代演进,每一代都在解决上一代的局限:
第一代:Plan-then-Execute(先规划后执行)
思路:先用 LLM 生成一个完整的计划,然后逐步执行。

局限:计划是静态的。执行中发现某步走不通,整个计划就废了——而真实世界中"执行中发现意外"是常态。
第二代:ReAct(推理+行动交错)——当前主流
ReAct(Reason + Act) 的核心思想是推理和行动交错执行:

ReAct 的精妙之处:不是"先想好所有步骤再执行"(纯规划),也不是"盲目执行不思考"(纯行动),而是"想一步、做一步、看结果、再想下一步"。这更接近人类解决陌生问题的方式。
ReAct 循环内部实际给 LLM 的 prompt 大致是这样的(简化版):
你是一个能使用工具的助手。请按以下格式回答:
Thought: 我需要先理解用户想要什么...
Action: search_web
Action Input: {"query": "Spring Boot 3.2 新特性"}
(框架执行工具,返回结果)
Observation: Spring Boot 3.2 主要更新包括...
Thought: 现在我知道了主要特性,可以总结了
Action: [无需工具,直接回答]
Final Answer: Spring Boot 3.2 的主要新特性包括...
每一轮迭代中,LLM 都能看到:之前所有的 Thought、Action、Observation。这意味着它可以根据执行结果动态调整后续计划——这是 Plan-then-Execute 做不到的。
第三代:Tree of Thoughts / MCTS(树状搜索)——前沿探索
ReAct 是"单链推理"——一条路走到底。但有些复杂问题需要"探索多条路径,回溯不靠谱的分支"。Tree of Thoughts 把推理组织成一棵树,支持分支探索和回溯,类似下棋 AI 的蒙特卡洛树搜索(MCTS)。
代价是成本高很多(每个节点都是一次 LLM 调用),目前主要在研究阶段,生产中很少用。
横向对比:三种规划范式

对于特别复杂的任务,ReAct 之上还可以叠加 Plan Mode:Agent 先在只读模式下分析任务、生成一个计划文件,用户审核计划后,Agent 再按计划执行。这实现了意图与动作的解耦——先想清楚要做什么,再动手做。
2.2 Agent 的核心循环与架构
Agent 与普通 LLM 应用的本质区别是什么?是"自主性"。
普通 LLM 应用是"你给一个输入,它给一个输出"。而 Agent 是"你给一个目标,它自己决定怎么做,做完告诉你结果"。
传统程序 vs Agent:控制权的转移
理解 Agent,最好从它与传统程序的根本区别入手:
传统程序:控制流是开发者写死的。if-else 怎么分叉、for 循环几次、调用哪个函数——都是预先确定的。程序是"开发者控制的执行体"。
Agent:控制流由 LLM 在运行时动态决定。下一步做什么、调用什么工具、什么时候停止——都由 LLM 根据当前上下文判断。程序变成了"LLM 驱动的执行体"。
这是一次根本性的转变:控制权从开发者转移到了 LLM。开发者不再编写"执行逻辑",而是编写"执行框架"——框架定义了 LLM 可以在哪些点做什么决策,但具体决策内容由 LLM 自己根据上下文判断。
实现这种自主性的,是 Agent 的核心控制循环:

这个循环的每个环节:
- 目标理解:把用户的自然语言请求,转化为可执行的任务目标
- 规划:把大目标拆成可执行的子步骤
- 执行:调用工具完成每个子步骤
- 观察:获取工具执行的结果
- 反思/修正:根据结果判断是否完成,没完成则调整计划
AgentScope 中的实际控制流

在 AgentScope 中,这个循环的具体实现是 ReActAgent 的主循环:

几个值得注意的运行机制:
- 迭代上限:循环不是无限的。maxIters(默认 10)限制了最大迭代次数,防止 Agent 陷入死循环
- 并发工具调用:如果 LLM 一次返回多个工具调用,Agent 可以并发执行(标记了 concurrency
-Safe 的工具) - 权限闸门:每次工具执行前都走权限检查(允许/询问/拒绝),敏感操作会暂停等待人工确认
- 中断恢复:支持 interrupt(userId, sessionId) 精确中断某个 session,中断后状态自动保存,下次调用从中断点恢复
这个循环是理解后续所有组件的框架——工具是"执行"环节的手脚,记忆是贯穿全循环的状态,知识库是"规划"和"执行"的参考。后面讲的每个组件,都可以对应到循环中的某个位置。
2.3 Agent 的"手"和"脚"——工具(Tools)与技能(Skills)
解决的问题:LLM 本身无法与外部世界交互(调用 API、读写文件、执行代码),如何赋予它行动能力?
工具(Tools)
工具就是 Agent 可以调用的外部函数。对 LLM 来说,一个工具需要暴露三件事:名称、描述、参数格式。LLM 根据这些信息决定"什么时候用哪个工具"。
底层原理:Function Calling 是怎么工作的
工具调用的完整流程涉及 LLM 和框架的协作:
1. 框架把工具的 JSON Schema 注入 LLM 请求
POST /v1/chat/completions
{
"messages": [...],
"tools": [{
"type": "function",
"function": {
"name": "get_current_time",
"description": "返回指定时区的当前时间",
"parameters": {"type": "object", "properties": {"timezone": {"type": "string"}}}
}
}]
}
2. LLM 决定是否调用工具。如果调用,返回的不是普通文本,而是一个结构化的 tool_call:
{
"tool_calls": [{
"id": "call_abc123",
"function": {"name": "get_current_time", "arguments": "{\"timezone\": \"Asia/Shanghai\"}"}
}]
}
3. 框架解析 tool_call,执行对应的 Java 方法,拿到结果
4. 框架把工具结果作为新消息加入上下文,再次调用 LLM
→ LLM 基于工具结果继续推理
关键点:LLM 本身不"执行"工具,它只是输出一个"我想调用这个工具"的结构化指令。真正执行工具的是框架代码。LLM 做的是"决策",框架做的是"执行"。
// AgentScope 用注解定义工具,框架自动推导 JSON Schema
@Tool(name = "get_current_time",
description = "返回指定时区的当前时间", // 这个描述决定了 LLM 用不用它
readOnly = true)
public String getCurrentTime(
@ToolParam(name = "timezone", description = "IANA 时区,如 Asia/Shanghai")
String timezone) {
return LocalDateTime.now(ZoneId.of(timezone))
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
这里有一个关键点:工具的描述必须详细具体。"数据查询工具"远不如"查询指定日期范围内的订单数据,支持按客户ID筛选"有效——因为 LLM 是靠描述来决策的。
当工具数量多了,会带来两个问题:上下文膨胀(每个工具的 schema 都占 token)和决策干扰(工具太多,选错率上升)。解决方案是工具组(Tool Group):把工具分组,运行时只激活当前任务相关的组——这就是"按需披露"。
// 工具组:按需激活
ToolGroup database = new ToolGroup(
"database", "数据库操作工具",
ToolGroupScope.SESSION, /* active = */false);
database.addTool("db_query");
database.addTool("db_migrate");
ReActAgent agent = ReActAgent.builder()
.name("router")
.toolkit(toolkit)
.enableMetaTool(true) // 启用 reset_tools 元工具,让 Agent 自己切换工具组
.build();
// Agent 遇到数据库任务时,自己调用 reset_tools 激活 database 组
MCP(Model Context Protocol)
解决的问题:工具提供方和 Agent 框架的解耦。
如果没有 MCP,你用 AgentScope 写的工具,换成 LangChain 就得重写。每换一个框架,工具生态就重建一遍。
MCP 是一个标准化协议:工具提供方按 MCP 协议实现一个 server,任何支持 MCP 的 Agent 框架都能接入。它让工具变成可复用、可独立部署的"微服务"。
MCP 的协议设计
MCP 定义了三种连接方式,对应不同的部署场景:

MCP server 启动后会暴露一个工具列表,客户端(Agent 框架)连接后自动发现可用工具。这跟微服务的"服务注册与发现"是同一个思想。
// 接入一个远程 MCP server 只需几行代码
McpClientWrapper weather = McpClientBuilder.streamableHttp()
.name("weather").url("https://api.weather.com/mcp")
.header("Authorization", "Bearer xxx")
.build();
Toolkit toolkit = new Toolkit();
toolkit.registerMcpClient(weather).block();
// 框架自动处理协议协商、工具发现、结果转换
// MCP 工具在 toolkit 中以 mcp__weather__get_forecast 命名,避免冲突
类比:MCP 之于 Agent 工具,就像 HTTP 之于 Web 服务——一个标准协议,让生产者和消费者解耦。就像你不需要关心一个 REST API 是用 Java 还是 Python 写的,MCP 让你不需要关心一个工具是跑在哪个 Agent 框架里。
技能(Skills)
工具是"能做什么"(能力),但 Agent 每次处理同类任务都从零摸索——它不记得"上次怎么做最有效"。
技能(Skill) 解决的就是这个问题:它是 Agent 的 SOP,一份可复用的工作流程文档。本质是 Markdown 指令,告诉 Agent "遇到这类任务该怎么做"。
code-reviewer/ ← 一个技能就是一个目录
├── SKILL.md ← 指令文档:"先搜代码、再读文件、按检查清单评审"
├── references/ ← 参考资料(如代码风格规范)
└── scripts/ ← 可执行脚本(如自动化检查脚本)
技能 vs 工具的本质区别:工具是"一个可调用的函数",技能是"一套工作流程的指令"。技能不能直接调用——Agent 先读取技能指令,再用已有工具按指令执行。技能是"知识",不是"动作"。
更进阶的是,Agent 可以自己起草技能——成功经验自动沉淀为技能草稿,经审核后变成正式技能。这就是 Agent 的"自进化"能力:know-how 在每次运行之间累积。
2.4 Agent 的"记忆"——短期与长期(Memory)
解决的问题:如何让 Agent 记住对话历史、任务进度和过去经验,避免重复犯错?
LLM 本身是无状态的——两次调用之间,模型不记住任何东西。要让 Agent "有记忆",需要工程手段。
记忆问题的本质
"记忆"听起来是一个概念,但实际包含三个不同层次的问题:
- 单次调用内的记忆:一次 call() 内多轮 ReAct 循环中,前一步的结果如何传到下一步?→ 靠上下文(AgentState.context)
- 跨调用的记忆:用户上一次对话说的内容,这次对话还记得吗?→ 靠 AgentState 持久化
- 跨会话的记忆:上个星期对话中提取的经验,这次新对话能用上吗?→ 靠长期记忆系统
这三层问题的解决方案完全不同,不能混为一谈。
记忆分两层:
短期记忆——会话记忆(Session Memory)
管理单次任务/对话中的上下文。核心载体是 AgentState,它不只存对话历史,还存权限规则、任务清单、工具激活状态等所有影响推理的运行时状态:
AgentState
├── context ← 对话历史(用户输入、assistant 回复、工具调用、工具结果)
├── summary ← 压缩后的摘要(上下文超阈值时生成)
├── permissionContext ← 工具权限规则(哪些工具允许/询问/拒绝)
├── planModeContext ← Plan Mode 状态(当前是否在规划阶段)
├── tasksContext ← 任务清单(todo_write 维护的子任务列表)
└── toolContext ← 工具组激活状态(当前激活了哪些工具组)
为什么权限规则和任务清单也放在 AgentState 里? 因为它们都影响 LLM 的推理决策。权限规则决定 LLM 知道"哪些操作可以做",任务清单决定 LLM 知道"当前进度到哪了"——这些和对话历史一样,都是推理时需要看到的上下文。
在 AgentScope 中,Agent 实例本身是无状态的——所有状态按 (userId, sessionId) 外部化存储:
// 配置 Redis 状态存储——生产环境推荐
ReActAgent agent = ReActAgent.builder()
.name("assistant")
.model("dashscope:qwen-plus")
.stateStore(new RedisAgentStateStore(jedis)) // 分布式状态存储
.build();
// 第一次调用——创建会话,Agent 查了航班信息
agent.call(List.of(new UserMessage("帮我查北京到上海的航班")),
RuntimeContext.builder().userId("alice").sessionId("s1").build()).block();
// 第二次调用——自动恢复上次的 AgentState,Agent 知道"最便宜的"指什么
agent.call(List.of(new UserMessage("最便宜的是哪个")),
RuntimeContext.builder().userId("alice").sessionId("s1").build()).block();
一个 Agent 实例可以同时服务成千上万个用户,互不干扰。状态存储用 Redis 等分布式后端时,还能实现跨机器的故障转移和滚动发布零中断——不同物理机上的 Agent 实例都能拿到完全一致的状态。
长期记忆(Long-term Memory)
会话记忆是"短期记忆"——会话结束就归档了。但有些信息是跨会话的:用户偏好、项目约定、历史经验。这些不应该靠用户每次重说一遍。
AgentScope 的长期记忆分两层,解决"既要记住长期事实,又不能让上下文无限增长"的矛盾:

这里有三处独立的 LLM 调用,每一处都有自己的 prompt 和触发时机:

// 记忆系统配置——可以用小模型跑记忆操作,省成本
HarnessAgent agent = HarnessAgent.builder()
.model("openai:o3") // 主推理模型(强)
.memory(MemoryConfig.builder()
.model("openai:gpt-4.1-mini") // 记忆操作用小模型(便宜)
.flushTrigger(MemoryConfig.FlushTrigger.throttled(
Duration.ofMinutes(10))) // 最多每 10 分钟 flush 一次
.build())
.compaction(CompactionConfig.builder()
.triggerMessages(30) // 30 条消息触发压缩
.keepMessages(10) // 保留最近 10 条
.model("openai:gpt-4.1-mini") // 压缩也用小模型
.build())
.build();
Agent 还获得 memory_search 工具,可以主动检索历史记忆。这就像人有"日记本"和"经验总结"——日记记原始事实,经验总结是提炼后的智慧。
横向对比:记忆方案的选择

类比:Session 是"工作记忆"(你正在想的事),Memory 是"长期记忆"(你积累的经验)。
2.5 Agent 的"知识库"(Knowledge Base)
解决的问题:Agent 如何获取执行任务所需的专业领域知识?
这里我们回到第一部分讲的 RAG。知识库是 Agent 获取静态领域知识的主要来源——公司制度文档、产品技术文档、行业规范等。
知识库 vs 记忆的区别(这是容易混淆的点):

知识库 vs 技能的区别:

2.6 Agent 的"组织"——多智能体协作(Multi-Agent Systems)
解决的问题:单个 Agent 的能力有限——上下文污染、工具过多、无法并行。如何通过组织多个 Agent 协同工作来解决更宏大的
单 Agent 的瓶颈
随着任务复杂度增加,单 Agent 会遇到瓶颈。就像一个人要同时做设计、开发、测试、运维——不是做不到,而是效率低、容易出错。具体来说有三个瓶颈:
- 上下文污染:所有子任务的中间结果都堆在一个上下文里,互相干扰,LLM 注意力被稀释
- 工具过载:一个 Agent 要处理所有类型任务,工具列表越来越长,选错率上升
- 串行瓶颈:单 Agent 是串行执行的,无法同时处理多个独立子任务
子 Agent 的核心思想
子 Agent(Sub-Agent) 的核心思想是分工:每个子 Agent 有独立的上下文、工具集、人格和推理循环,可以并行执行,互不污染。
两种委派模式:
- 同步委派:主 Agent 调用子 Agent,等结果返回再继续——"我需要你的结果才能往下走"
- 后台委派:交给子 Agent 后不等待,完成后通过 system-reminder 自动推送结果——"你先做着,我干别的"
// 声明式子 Agent:在 workspace/subagents/ 目录下放一个 Markdown 文件
// subagents/code-reviewer.md 内容:
// ---
// name: code-reviewer
// sysPrompt: 你是一个代码评审专家,专注于发现潜在bug和代码规范问题。
// tools: [read_file, search_code, run_tests]
// ---
// 评审步骤:
// 1. 读取目标代码
// 2. 检查常见问题模式
// 3. 给出结构化评审报告
// 主 Agent 通过 agent_spawn 创建子 Agent,agent_send 发送任务
// 子 Agent 独立运行,有自己的上下文和工具集
多智能体协作模式
以"软件开发团队"为例:

不同的协作模式适合不同场景:

Tool vs Skill vs Subagent 的层次关系:

三者不是互斥的:子 Agent 内部会用到工具,也可能加载 Skill。它们是不同层次的抽象——Tool 是"手脚",Skill 是"方法手册",Subagent 是"另一个大脑"。
这就是从"一个全能 Agent"到"一个 Agent 团队"的演进——就像从"个人英雄"到"高效团队"。
三、实战指南:从概念到落地
前面两部分讲的是"Agent 是什么、怎么工作"。但真正落地时,你会发现"知道概念"和"做出好用的系统"之间还有很大的距离。这一部分,我们来看三个实战维度:在什么场景用、常见坑在哪里、最佳实践是什么。
3.1 典型应用场景:Agent 适合解决什么问题
不是所有问题都适合用 Agent。判断的核心标准是:任务是否需要"多步推理 + 动态决策 + 外部工具"。如果只需要一次 LLM 调用就能完成,用 Agent 是杀鸡用牛刀。
场景一:知识问答助手(RAG + 轻量 Agent)
业务背景:公司内部有大量文档(制度、产品文档、技术 wiki),员工查找效率低。
为什么用 Agent:单纯的 RAG 只能"检索+回答",但员工的真实需求往往需要多步——先查制度,再查配套流程,再结合个人情况给建议。
架构:知识库(RAG) + 轻量 Agent(ReAct 循环 + 2-3 个工具:检索、读文件、回答)
// 知识问答助手——最轻量的 Agent 应用
ReActAgent agent = ReActAgent.builder()
.name("kb_assistant")
.sysPrompt("你是公司内部知识助手。回答时请引用文档来源。"
+ "如果检索结果不足以回答,请明确告知而非编造。")
.model("dashscope:qwen-plus")
.knowledgeBase(kb) // 知识库
.maxIters(5) // 知识问答不需要太多轮迭代
.build();
落地建议:这是 Agent 的"入门级"应用,ROI 容易量化(节省的查找时间)。先从这个场景切入,验证效果后再扩展。
场景二:代码工程助手(工具密集型 Agent)
业务背景:开发团队需要一个能读代码、跑测试、提交 PR 的 AI 助手。
为什么用 Agent:代码任务天然需要多步——理解需求→搜索相关代码→读取文件→修改代码→运行测试→根据测试结果修复→提交。每一步的结果都影响下一步怎么做,这是 ReAct 循环的典型场景。
架构:HarnessAgent + 工作区(代码仓库) + 工具集(read_file, write_file, execute_shell, search_code) + Plan Mode
HarnessAgent agent = HarnessAgent.builder()
.name("code_engineer")
.sysPrompt("你是一个代码工程师。修改代码前先理解上下文,"
+ "修改后必须运行测试验证。")
.model("dashscope:qwen-max")
.workspace(workspace) // 代码仓库作为工作区
.enablePlanMode() // 复杂修改先出计划
.compaction(CompactionConfig.builder()
.triggerMessages(20) // 代码任务上下文增长快,提早压缩
.keepMessages(8)
.build())
.build();
落地建议:代码场景的 Agent 效果显著,但安全风险也高——必须配权限系统和沙箱。建议先在只读模式(只分析不修改)下验证,再逐步放开写权限。
场景三:数据处理与分析 Agent(多工具协作)
业务背景:业务团队需要从多个数据源取数、清洗、分析、生成报表。
为什么用 Agent:数据分析的过程高度动态——先看数据结构,决定分析方向,写 SQL 取数,发现异常再深挖,最后出图。这种"边探索边决策"的过程,硬编码的 ETL 管道做不到。
架构:Agent + 多工具组(数据库查询、Python 执行、图表生成) + 知识库(业务指标定义)
// 数据分析场景——工具组按需激活
ToolGroup queryGroup = new ToolGroup("db", "数据库查询工具", ...);
queryGroup.addTool("sql_query");
queryGroup.addTool("schema_inspect");
ToolGroup analysisGroup = new ToolGroup("analysis", "分析工具", ...);
analysisGroup.addTool("python_execute");
analysisGroup.addTool("plot_chart");
ReActAgent agent = ReActAgent.builder()
.name("data_analyst")
.toolkit(toolkit)
.enableMetaTool(true) // 让 Agent 自己切换工具组
.knowledgeBase(metricsKb) // 业务指标定义
.maxIters(15) // 数据探索需要更多轮次
.build();
落地建议:SQL 执行工具必须配只读数据库账号,Python 执行必须在沙箱中。数据分析 Agent 的价值在于"替代数据分析师的重复劳动",不是替代数据分析师本人。
场景四:多 Agent 协作——自动化研发流水线
业务背景:将需求分析、代码开发、代码评审、测试验证串联成自动化流水线。
为什么用多 Agent:每个环节需要不同的专业能力和工具集,放在一个 Agent 里会上下文污染、工具过载。
架构:Supervisor Agent + 4 个专业子 Agent(PM / Developer / Reviewer / Tester)

落地建议:多 Agent 系统复杂度高,建议在单 Agent 验证效果后再引入。初期可以用"人在回路"(HITL)替代部分子 Agent,逐步自动化。
不适合 Agent 的场景

3.2 常见痛点与避坑指南
Agent 落地过程中,以下痛点几乎每个人都会遇到。这里把"踩过的坑"提前列出来。
痛点一:Agent "不按预期行动"——工具调用决策不准
现象:Agent 该用工具时不用,不该用时乱用;或者选错了工具。
根因:LLM 是根据工具的 description 来决策的。描述模糊、描述之间有重叠,都会导致选错。
避坑指南:
❌ 坏的描述:description = "查询数据"
✅ 好的描述:description = "查询 MySQL 订单表中指定日期范围内的订单记录,
支持按客户ID和订单状态筛选。返回字段:订单号、金额、状态、创建时间。"
- 工具描述要写到同事能看懂的程度——不只是"做什么",还要包括"输入是什么格式、输出是什么格式、什么时候该用"
- 工具之间不要有职责重叠——如果有两个工具都能"查数据",LLM 会困惑。合并它们,或者用更清晰的命名区分
- 给工具加 readOnly 标记——只读工具可以放心并发,框架会自动优化
痛点二:上下文爆炸——Token 成本失控
现象:长对话中 token 消耗线性增长,成本越来越高,效果反而越来越差("迷失在中间")。
根因:每轮 ReAct 循环都会往上下文加消息(推理过程 + 工具调用 + 工具结果),如果不管理,几十轮后上下文就爆了。
避坑指南:
// 生产环境的标准配置
HarnessAgent agent = HarnessAgent.builder()
.compaction(CompactionConfig.builder()
.triggerMessages(20) // 20 条就触发,不要等太多
.keepMessages(8) // 只保留最近 8 条 + 摘要
.truncateArgs(CompactionConfig.TruncateArgsConfig.builder()
.maxArgLength(2000) // 工具参数超 2000 字符就截断
.build())
.build())
.toolResultEviction(ToolResultEvictionConfig.defaults()) // 大结果落盘
.memory(MemoryConfig.builder()
.flushTrigger(MemoryConfig.FlushTrigger.throttled(
Duration.ofMinutes(10))) // flush 节流,不是每次都跑
.model("dashscope:qwen-turbo") // 记忆操作用便宜模型
.build())
.build();
- 压缩阈值宁低勿高:20 条消息触发压缩,比 50 条再压缩效果好——上下文越长,LLM 注意力越分散
- 大工具结果必须落盘:read_file 读了个 100KB 的文件,全文进上下文就是灾难。配 toolResultEviction 让超 80K 的结果自动落盘
- 记忆操作用小模型:flush 和 consolidation 不需要主模型那么强,用 qwen-turbo 等轻量模型能省 80% 成本
痛点三:Agent 陷入死循环——反复调用同一个工具
现象:Agent 反复调用同一个工具,或者在不同步骤间来回跳,无法收敛到最终答案。
根因:通常是 prompt 不够清晰,或者工具返回的结果让 LLM 误判了任务状态。
避坑指南:
- 设 maxIters 兜底:生产环境必须设,默认 10,复杂任务最多 20。宁可让 Agent "没做完就返回",也不能让它无限跑
- 工具结果要包含状态信息:不要只返回数据,还要告诉 LLM "这个操作成功了吗""还有多少没做"
// ❌ 坏的工具返回:只有数据,没有状态
return"Order #12345, amount: 100, status: pending";
// ✅ 好的工具返回:包含状态,帮助 LLM 判断下一步
return"查询成功。找到 1 条订单:Order #12345, amount: 100, status: pending。\n"
+ "该订单处于待审批状态,你可以使用 approve_order 工具进行审批。";
- 用 TodoTools 让 Agent 跟踪进度:复杂任务开启 enableTaskList(),Agent 维护一个任务清单,每完成一项划掉,避免"忘了做到哪了"
痛点四:安全问题——Agent 执行了危险操作
现象:Agent 误删文件、执行了危险命令、把敏感数据发到了外部 API。
根因:Agent 有了行动力,也意味着有了破坏力。没有权限管控的 Agent 就像没有刹车的汽车。
避坑指南:
// 生产环境的安全配置——三道防线
ReActAgent agent = ReActAgent.builder()
.permissionContext(PermissionContextState.STRICT) // 严格权限模式
.toolkit(toolkit) // 工具白名单
.build();
// 第一道防线:工具粒度权限
// 写操作工具必须标记 readOnly = false,框架会走权限检查
@Tool(name = "delete_file", description = "删除指定文件",
readOnly = false, dangerousDirectories = {"/etc", "/system"})
public String deleteFile(@ToolParam(name = "path") String path){ ... }
// 第二道防线:HITL 人工确认
// 敏感操作触发 RequireUserConfirmEvent,暂停等待人工确认
// 确认后规则可持久化——同类操作以后自动放行
// 第三道防线:沙箱隔离
// 不可信代码在 Docker 沙箱中执行,宿主不受影响
HarnessAgent agent = HarnessAgent.builder()
.filesystem(new DockerFilesystemSpec()...) // 沙箱文件系统
.build();
- 最小权限原则:Agent 初始权限只给只读工具,写操作需要审批。宁可多确认几次,也不要一上来就给全权
- 危险路径黑名单:delete_file、execute_shell 等工具要配 dangerousDirectories / dangerousCommands
- 沙箱是最后一道墙:即使权限系统失效,沙箱也能保证影响范围可控
痛点五:评估困难——不知道 Agent 效果好不好
现象:传统软件测试是"输入→断言输出",但 Agent 的多步推理 + 工具调用让输出变得非确定性——同样的输入可能走不同的路径。
避坑指南:
分层评估:
- 工具层:每个工具单独测(传统单元测试)
- 推理层:给定上下文,断言 LLM 是否选对了工具(批量用例)
- 端到端:给定目标,断言最终结果是否正确(允许路径不同,只看结果)
黄金集回归:维护一批"标准问题+标准答案",每次模型升级或 prompt 修改后跑一遍
可观测是前提:必须能追踪 Agent 的每一步推理和工具调用。AgentScope 的 Studio 可视化工具就是干这个的
// 开启可观测——生产环境必备
ReActAgent agent = ReActAgent.builder()
.name("observable_agent")
.model("dashscope:qwen-max")
.toolkit(toolkit)
// 每一步推理、工具调用、工具结果都以事件流出
.build();
// 用 streamEvents 而非 call,实时追踪执行过程
agent.streamEvents(userMsg)
.doOnNext(event -> {
switch (event.getType()) {
case TEXT_BLOCK_DELTA -> System.out.print(((TextBlockDeltaEvent) event).getDelta());
case TOOL_CALL_START -> log.info("工具调用: {}", ((ToolCallStartEvent) event).getToolCallName());
case TOOL_CALL_END -> log.info("工具完成: {}", ((ToolCallEndEvent) event).getToolCallName());
}
})
.blockLast();
3.3 行业最佳实践
实践一:渐进式引入——从"HITL"到"全自动"
不要一上来就做全自动 Agent。推荐的分阶段路径:

实践二:Model Routing——不同任务用不同模型
不是所有步骤都需要最强的模型。一个常见成本优化策略:

// 主模型用强模型,辅助操作用小模型
HarnessAgent agent = HarnessAgent.builder()
.model("dashscope:qwen-max") // 主推理
.memory(MemoryConfig.builder()
.model("dashscope:qwen-turbo") // 记忆操作
.build())
.compaction(CompactionConfig.builder()
.model("dashscope:qwen-turbo") // 压缩摘要
.build())
.build();
// 这样整体成本能降 40-60%,效果几乎无损
实践三:Skill 沉淀——让 Agent 越用越强
团队在使用 Agent 的过程中,会积累大量有效的工作模式。把这些模式沉淀成 Skill,让 Agent 越用越强:

关键原则:
- 通用能力放 Git 技能仓库,团队共享
- 项目特有规范放 workspace/skills/,跟代码版本走
- 个人偏好放 <userId>/skills/,用户隔离
实践四:生产部署 Checklist
上线前逐项检查:
- 状态存储:用 Redis/MySQL,不要用内存或本地 JSON(多副本会丢状态)
- maxIters 上限:必须设,防止死循环烧 token
- 权限模式:生产环境用 STRICT 模式,不要用 DEFAULT
- 沙箱隔离:涉及代码执行的工具必须在沙箱中
- 上下文压缩:必须配 compaction,否则长对话必爆
- 大结果卸载:必须配 toolResultEviction,否则大文件读取必爆
- 可观测性:开启事件流日志,接监控告警
- 降级方案:模型超时/限流时的 fallback 模型
- 成本监控:设置 token 用量告警阈值
- 隐私脱敏:工具返回中的敏感信息(手机号、身份证号)要脱敏后再进上下文
// 生产级 Agent 配置模板
HarnessAgent agent = HarnessAgent.builder()
.name("production_agent")
.model("dashscope:qwen-max")
.maxRetries(3) // 模型重试
.fallbackModel("dashscope:qwen-plus") // 降级模型
.workspace(workspace)
.stateStore(new RedisAgentStateStore(jedis)) // 分布式状态
.compaction(CompactionConfig.builder()
.triggerMessages(20).keepMessages(8).build())
.toolResultEviction(ToolResultEvictionConfig.defaults())
.memory(MemoryConfig.builder()
.flushTrigger(MemoryConfig.FlushTrigger.throttled(Duration.ofMinutes(10)))
.model("dashscope:qwen-turbo")
.build())
.permissionContext(PermissionContextState.STRICT)
.enablePlanMode()
.build();
四、总结与展望
全景架构图
让我们把今天讲的所有概念整合到一张图里:

一句话总结:
- 推理层让 Agent 会思考(提示词 + 上下文 + ReAct)
- 行动层让 Agent 能做事(工具 + MCP + 技能 + 权限管控)
- 知识层让 Agent 有知识(知识库 + 记忆 + Session)
- 协作层让 Agent 能分工(子 Agent + 多 Agent 协作)
- Harness 底座让 Agent 稳定长跑(编排 + 状态 + 压缩 + 容错)
四层叠加在底座上 = 完整的自主智能体。
当前挑战
Agent 技术发展很快,但也面临实实在在的挑战:
- 稳定性:LLM 的概率推理本质意味着 Agent 不可能 100% 可靠,同样的输入可能得到不同结果
- 成本:多轮推理 + 工具调用 + 记忆提炼,一个复杂任务的 token 消耗是单次对话的几十倍
- 安全性:Agent 有了行动力,也意味着有了破坏力——误删文件、执行危险命令的风险真实存在
- 评估困难:传统软件测试是"输入→断言输出",Agent 的多步推理 + 工具调用让测试变成一个非确定性问题
这些挑战也催生了很多工程实践:权限系统做三态决策(允许/询问/拒绝)、HITL 让敏感操作可暂停等待人工确认、沙箱隔离让不可信代码安全执行。
未来方向
展望未来,几个值得关注的方向:
- 更强的自主规划能力:从 ReAct 的"走一步看一步"到更全局的规划——树状搜索、蒙特卡洛树搜索(MCTS)等
- Agent 自进化:Agent 自动沉淀成功经验为技能、自动清理过时技能——know-how 越用越多
- 多 Agent 生态:不同团队、不同公司的 Agent 通过标准协议(如 A2A 协议)互相协作,形成"Agent 的互联网"
- 与物理世界结合:Agent + 机器人,从数字世界走向物理世界——具身智能(Embodied AI)
一个启发
最后,我想留给大家一个思考:
Agent 技术的本质,不是让 AI 变得更"聪明",而是给 AI 搭一套"工程基础设施"——让它从"灵光一闪的问答"变成"可靠运转的系统"。这跟软件工程的发展史如出一辙:从单个脚本,到工程化,到平台化。
Agent 就是软件工程3.0--软件工程3.0的开发新范式。
我们正在经历的,是 AI 从"模型"到"系统"的工程化转折点。而这个转折点中的每一个工程问题——可靠性、可观测性、安全性、可维护性——都是我们工程师最擅长的领域。
Agent 的未来,不只是 AI 研究员的未来,更是我们每个工程师的未来。

浙公网安备 33010602011771号