AI Agent 技术全景解析 —— 从提示词工程到高级智能体构建

0 阅读1分钟

作者: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/,跟代码版本走
  • 个人偏好放 /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 研究员的未来,更是我们每个工程师的未来。