--- title: "01-核心架构设计" created: 2026-05-13 aliases: - 核心架构设计 tags: - 项目 --- # 核心架构设计 用户在输入框里敲了一句话,点了发送。看起来很简单,但在 Super Agent 内部,这条消息要经过一条远比你想象中复杂的链路,才能变成一个靠谱的回答。 先看整体流程,后面再逐块拆解每个环节的设计细节: ![[Fi0Q_mSxh3zRCAh_5orNt-Rbx0Dr-110dacf9.png]] 核心思路是:**不是让 Agent 自己决定所有事情,而是先用确定性的编排逻辑做好决策,再把执行交给最合适的引擎。** 知识问答走稳定的证据驱动生成,开放式问题才走 Agent 自由探索。 > "确定性编排 + 场景化执行"是 Super Agent 区别于市面上其他 Agent 项目的根本设计哲学。大多数项目把所有问题都扔给一个 Agent 去处理,结果是不可控、不可解释。Super Agent 的做法是:**能确定的事情先确定好,只把真正需要自主探索的部分交给 Agent**。 ### **完整请求链路概述** 一条用户消息从进入系统到返回回答,要经过以下核心阶段: - **会话记忆加载**:从 MySQL 中加载历史对话记忆,按配置的策略(无记忆 / 滑动窗口 / 摘要压缩)组装上下文 - **前置编排决策**:LLM 分析用户意图,执行问题改写、路由判定、歧义检测、子问题拆分、知识域收缩五步决策 - **执行器分发**:根据编排结果选择最合适的执行器——歧义追问、RAG 知识问答或 ReAct Agent - **回答生成**:执行器执行检索或推理,生成最终回答 - **SSE 流式输出**:正文分片推送、引用来源补发、推荐追问问题生成 - **会话记忆更新**:将本轮对话持久化,触发摘要压缩(如需要) 整个链路中,每一步的输入输出、耗时、决策结果都有完整的 Trace 记录,出了问题可以精确定位到具体环节。 下面这张截图是对话观测中的会话链路面板,可以看到本次会话中每一轮问答的完整信息——执行模式(知识问答还是 Agent)、各阶段耗时、Token 消耗和费用,一目了然: ![[FsL1L_6oIy8WwKP877IvcN6At4p_-93a72246.png]] ## **Agent 这块是怎么设计的** 面试中聊 Agent,面试官不想听你说"我用了 ReactAgent",他想听的是你对这些问题的思考: - **用户提了一个问题,系统是怎么决定用哪种方式来回答的?** 不是让模型自己选,而是先经过路由判定、问题改写、歧义检测这一整轮编排,最后才决定走哪个执行器。判断顺序是:歧义澄清 > 知识问答 > 开放式 Agent - **为什么不是所有问题都走 Agent?** 知识问答追求"稳"和"可解释",让 Agent 自己探索反而容易不可控。只有真正需要联网搜索、多步推理的开放式问题,才适合走 Agent - **Agent 死循环了怎么办?** 用 ModelCallLimitHook 限制单次运行最多调用模型 8 次,用 ToolCallLimitHook 限制 Tavily 搜索最多调 6 次。单个会话线程累计也有上限,分别是 40 次和 30 次 - **联网搜索调用失败了怎么处理?** ToolRetryInterceptor 做指数退避重试,最多重试 2 次,初始延迟 200ms,最大延迟 1200ms,带随机抖动。如果最终还是失败,ToolErrorInterceptor 做兜底,不让异常直接抛给用户 - **对话状态怎么持久化?** 用 Spring AI Alibaba 的 MysqlSaver 把 ReactAgent 的 Checkpoint 存到 MySQL,应用重启后能继续之前的对话 - **并行工具执行怎么做?** ReactAgent 配置了 parallelToolExecution,最多 4 个工具并行执行 这些设计在代码里都能找到对应实现,不是概念上的东西。 ![[FkPkR-g8m2dD7DQxD6TC47896MLF-d909a90e.png]] ![]() ## **三层执行器:不是所有问题都该交给 Agent** 知识问答追求"稳"和"可解释"——用户问"退款规则是什么",你需要的是从文档中精准检索证据,然后让模型基于证据生成回答。这种场景让 Agent 自己探索反而容易不可控,可能跑去联网搜索一堆无关内容。 而"今天北京天气怎么样"这种问题,知识库里根本没有答案,必须让 Agent 调用搜索工具去获取实时信息。 **所以 Super Agent 设计了三层执行器,按场景精准分流:** | 执行器 | 触发条件 | 处理方式 | | --- | --- | --- | | 歧义追问执行器 | 用户问题信息量不足,无法确定意图 | 生成澄清问题,引导用户补充信息 | | RAG 知识问答执行器 | 问题可以在知识库中找到答案 | 证据驱动生成,引用来源可追溯 | | ReAct Agent 执行器 | 需要联网搜索、多步推理的开放式问题 | 自主决策 + 工具调用 + 多轮推理循环 | 判断顺序是:**歧义澄清 > 知识问答 > 开放式 Agent**。优先用最稳定的方式回答,只有确实需要自由探索时才启动 Agent。 > 歧义优先是因为在信息不足时,任何回答都可能是错的,不如先问清楚。知识问答优先于 Agent 是因为知识库的回答可控、可解释、可追溯来源,而 Agent 的自由探索虽然能力更强,但不可预测性也更高。**在企业级系统中,可控比聪明更重要。** ### **Agent 安全机制详解** 在生产环境中,Agent 的自主决策能力是一把双刃剑。如果不加控制,Agent 可能会陷入无限推理循环、过度消耗搜索资源,甚至因为工具调用异常导致整个会话崩溃。Super Agent 在这方面做了多层防护: | 安全机制 | 配置参数 | 作用 | | --- | --- | --- | | 单次模型调用上限 | ModelCallLimitHook = 8 次 | 防止 Agent 单次请求陷入死循环 | | 单次工具调用上限 | ToolCallLimitHook = 6 次 | 防止搜索工具被过度调用 | | 会话模型调用累计上限 | 40 次 / 线程 | 防止长对话累积消耗过多资源 | | 会话工具调用累计上限 | 30 次 / 线程 | 控制整体工具使用成本 | | 工具重试机制 | 最多 2 次,指数退避 200-1200ms | 容忍短暂网络抖动 | | 异常兜底 | ToolErrorInterceptor | 工具彻底失败时优雅降级,不影响主流程 | 在观测面板的轮次详情中,可以直观看到每一轮对话的模型使用情况:输入 Token、输出 Token、总 Token、费用估算,以及当前模型调用次数和工具调用次数相对于上限的使用进度。这些数据让资源消耗完全透明可控: ![[image-fd0273fb.png]] ## **前置编排器到底做了什么** 前面说了系统不会把所有问题直接扔给模型,而是先做一轮完整的编排。它在每次对话中做的事情远比你想象的多: ### **路由判定** 用户发进来的消息,首先要判断它属于哪种类型。是一个可以在知识库里找到答案的问题?还是需要联网搜索的开放式问题?还是信息不全需要先追问?这个判定不是靠关键词匹配,而是通过模型分析上下文后给出路由方向。 > 路由判定的本质是一次带结构化输出的 LLM 调用。系统会把用户的当前问题、最近几轮对话历史、以及知识库的元信息一起传给模型,让模型判断这个问题最适合走哪个执行器。这种方式比基于规则的路由更灵活,能处理各种模糊边界的情况。 ### **问题改写** 用户的问题往往不适合直接检索。比如"那它怎么配置?"——"它"指的是什么?得结合前几轮对话才知道。问题改写就是把这些省略的信息补回来,让检索能找到东西。 下面是观测面板中问题改写的实际效果。可以看到系统把用户的原始问题、结合对话历史改写后的完整问题、以及参考的历史上下文都记录了下来,改写前后的对比一目了然: ![[Fnt4UeGkIHnhs4a1p3KansJbIyrl-3cf77ce9.png]]![]() **问题改写的典型场景:** | 原始问题 | 改写后 | 改写原因 | | --- | --- | --- | | 那它怎么配置? | MinIO 对象存储怎么配置? | 指代消解,补全主语 | | 还有其他方案吗? | 除了向量检索,还有其他检索方案吗? | 补全省略的上下文 | | 怎么用? | Spring AI 的 ChatClient 怎么使用? | 结合前文话题补全完整问题 | ### **子问题拆分** "退款规则是什么?审批流程怎么走?"这种复合问题,如果直接拿去检索,两个意图互相干扰,效果很差。系统会拆成独立子问题,每个单独走检索链路,最后合并结果。单轮最多拆 4 个子问题,避免过度切碎。 ### **意图解析与知识域收缩** 拆完子问题后,还要分析每个子问题属于哪个知识域,把检索范围从"全库"缩小到"相关领域"。这一步直接影响检索的精准度和速度。 > 假设知识库里有"产品手册""开发文档""HR 制度"三个领域的文档。用户问"年假怎么申请",如果在全库范围内检索,可能会因为产品手册里也提到了"申请"这个词而引入噪声。知识域收缩会先判断这是 HR 相关问题,只在"HR 制度"领域内检索,大幅提升精准度。 ### **歧义检测** 如果用户的问题信息量不够,比如只说"查一下那个",系统不会硬着头皮去检索,而是先生成澄清问题让用户补充信息。这比返回一个不相关的答案体验好得多。 所有这些步骤完成后,编排器会产出一个 `执行计划`,里面包含执行多种执行编排模式、改写后的问题、拆分后的子问题列表、知识域范围等,交给对应的执行器去执行。 ![[Fhlgtu1zQWc3WjPpTjHWxVcozK4S-e0922214.png]] ### **编排器的产出物:ConversationExecutionPlan** 前置编排的最终产物是一个结构化的执行计划,包含以下关键信息: | 字段 | 说明 | 示例 | | --- | --- | --- | | executionMode | 执行模式 | CLARIFY / RAG\_CHAT / REACT\_AGENT | | rewrittenQuestion | 改写后的完整问题 | "MinIO 对象存储怎么配置连接参数?" | | subQuestions | 拆分后的子问题列表 | ["退款规则是什么", "审批流程怎么走"] | | knowledgeDomains | 知识域范围 | ["产品手册", "运维文档"] | | clarifyQuestion | 澄清问题(仅 CLARIFY 模式) | "请问你想了解的是哪个产品的配置?" | 这个执行计划是编排层和执行层之间的标准契约,执行器只需要根据计划执行,不需要关心编排过程的细节。这种解耦设计使得编排逻辑和执行逻辑可以独立演进。 ## **检索链路到底有多细** 很多人以为检索过程就是"查个向量库 + 让模型回答",但实际上中间的环节比想象中多得多。下面列的这些问题,在项目中都有对应的设计: ### **问题进来之后怎么处理?** - 用户问"那它怎么配置?"这种省略了主语的追问,直接拿去检索什么都找不到。所以要先做**问题改写**,结合最近几轮历史把指代补全 - 一个问题里问了两件事,比如"退款规则是什么?审批流程怎么走?",不能一股脑去检索。要**拆成独立子问题**,每个子问题单独走检索链路 - 单轮最多拆 4 个子问题,避免过度切碎 ### **检索是怎么做的?** - 不是只用向量检索。用户问一个订单号、一个配置项名,向量检索很可能找不到。所以用了**双通道并行**:向量检索走 + 关键词检索 - 两路结果分数量纲完全不同,不能直接比大小。用 **RRF(Reciprocal Rank Fusion)** 按排名倒数法融合 - 向量通道设了最低相似度,关键词通道用相对阈值,低于阈值的弱命中直接过滤掉 - 融合后可以接**外部 Rerank 精排**(支持 SiliconFlow 兼容协议),在较干净的候选集上继续优化排序 - 检索粒度用 Child 小块保证命中率,回答阶段通过 **Parent-Child 聚合**提升到 Parent 大块,保证上下文完整性 ### **证据够不够怎么判断?** - 如果最终没有任何有效证据,**直接短路返回**,告诉用户"当前文档中没有检索到足够证据"。不让模型凭空编造,这是防止幻觉最直接的手段 - 如果证据太多,有预算控制:单个子问题字符、全部证据总预算,单个父块最大字符。防止把模型上下文窗口吃满 ### **最终怎么组装回答?** - 按子问题边界分别组织证据,注入到 Prompt 里,模型按编号逐一回答 - 要求模型在引用证据时标注来源编号 `[1][2]` - 答案通过 SSE 实时流式推送,结束时补发引用来源和推荐追问问题 ![[Fs6IzbN7UwyMlHbl6EeLrs_PMnTL-6a1c2cfc.png]] ![]() ### **双通道并行检索** 用户问一个订单号、一个配置项名,向量检索很可能找不到——语义相似度对精确匹配天然弱势。所以 Super Agent 用了双通道并行:**向量检索 + 关键词检索**,两路同时出发,互不阻塞。 两路结果的分数量纲完全不同,不能直接比大小。系统用 **RRF(Reciprocal Rank Fusion)** 按排名倒数法融合,向量通道设了最低相似度阈值,关键词通道用相对阈值,低于阈值的弱命中直接过滤。融合后还可以接**外部 Rerank 精排**,在较干净的候选集上继续优化排序。 > RRF 的核心公式是:`score = Σ 1/(k + rank_i)`,其中 k 是常数(通常为 60),rank\_i 是文档在第 i 路检索结果中的排名。这个公式的巧妙之处在于:它不依赖原始分数的绝对值,只依赖排名,因此可以直接融合不同量纲的检索结果。一个文档如果在两路检索中排名都靠前,融合后的分数就会很高。 ### **父子块聚合** 这是整个检索链路中最精巧的设计之一。检索粒度用 Child 小块保证命中率——小块语义集中,更容易被向量检索命中。但回答阶段如果只用小块,上下文往往不完整。所以系统在命中 Child 块后,自动聚合提升到 Parent 大块,保证回答时有足够的上下文信息。 **检索用小块保精度,回答用大块保完整性。** 这个设计在业界也属于比较前沿的实践。 ### **证据预算控制参数** | 参数 | 默认值 | 说明 | | --- | --- | --- | | 单子问题证据上限 | 2,200 字符 | 防止单个子问题的证据过多 | | 全部证据总预算 | 5,200 字符 | 控制注入 Prompt 的总证据量 | | 单父块最大字符 | 2,200 字符 | 防止单个父块占用过多预算 | | 向量检索最低相似度 | 0.45 | 过滤低质量向量匹配 | | 关键词检索相对阈值 | 0.35 | 过滤弱命中的关键词结果 | ## **文档从上传到可检索经历了什么** 很多项目的文档处理就是"切成固定长度 → 向量化 → 完事"。但实际上不同文档差异很大,一刀切的效果很难好。Super Agent 的文档处理是一条完整的异步流水线,每一步都有独立的状态追踪和任务日志: ![[Fn9VJM7B9KZZ2x4a1TqKKrZBrqxe-1a958111.png]]![]() ### **第一步:上传和存储** 文件上传到 MinIO 对象存储后,通过 Kafka 异步触发解析任务。Apache Tika 负责处理 PDF、Word、PPT 等多种格式,把五花八门的文档统一转成干净的文本。PDF 里的表格、扫描件、双栏排版——每一个都是坑,Tika 帮你踩过了。 并且可以配置 **知识域编码**、**知识域名称**、**业务分类**、**文档标签** 等元信息,后续检索和分析都能用得上。 ![[image-9d811ecc.png]] ### **第二步:异步解析和策略推荐** 解析完成后,系统不是让用户盲选切块算法,而是根据文档类型和内容特征**自动推荐最优的切块策略组合**。用户可以查看推荐结果,也可以手动调整——比如文档质量不太好,可以额外开启 LLM 智能切块。 ![[Fiu_x5T7ZoUfnnoLxCcveJ_7pKFv-dd31da61.png]] ### **第三步:组合式切块引擎** 四种切块策略不是四选一,而是各司其职的组合流水线: | 策略 | 角色 | 什么时候用 | | --- | --- | --- | | 基于文档结构切块 | 主干 | 按标题/章节/段落切成语义完整的块 | | 递归分块 | 兜底 | 结构块太大时继续往下裁剪,控制块大小 | | 语义分块 | 优化 | 在结构切块基础上做边界精修 | | LLM 智能切块 | 增强 | 处理低质量文档和复杂文档,默认关闭 | 一句话:**结构负责保留文档天然边界,递归负责控制块大小,语义负责优化块边界,大模型负责处理疑难场景。** > 一份包含标题层级的产品手册,系统会推荐:结构切块(主干)+ 递归分块(兜底)。如果这份文档的段落非常长且话题跳跃频繁,系统还会自动开启语义分块来优化边界。而对于一份扫描后 OCR 识别的低质量文档,可以手动开启 LLM 智能切块来增强效果。 ![[Ful5rW11KmCFWv0Dwce6Tx9dHSWA-b6c4be14.png]]![]() ### **第四步:向量化与双引擎索引** 确认策略后,系统通过 Kafka 异步执行向量化和索引构建。向量写入向量数据库,关键词写入倒排索引数据库,构建双通道检索的基础。每一步都有独立的任务日志,出了问题能精确定位到哪一步失败。 ![[FgOQcRirFAHOnRiToTuhjEBdleLL-5b081145.png]]![]() ## **会话记忆:Token 成本和上下文完整性的博弈** 20 轮对话全塞给模型?Token 成本扛不住。只带最近几轮?可能丢掉关键上下文。这是生产环境下绕不开的问题。 ![[FqG_eDQ5RoZvAAbKhlUkmnBc6MfU-05f1eca2.png]]![]() ### **项目中设计了三种策略:** - **无记忆**:每轮独立,不携带历史。适合一次性查询 - **滑动窗口**:保留最近 N 轮完整对话。适合短期连续追问 - **摘要压缩**:长期摘要 + 最近原文窗口。这是生产环境最推荐的方案 ![[FulDFy2EFTJS_ynHuDXDmM7ilIAp-37c4f68c.png]]![]() ### **摘要压缩的具体设计:** - 最近 4 轮原文始终保留,不做压缩 - 更早的历史增量摘要,单次最多推进 6 轮,避免一次处理超长历史 - 最近原文窗口最大 2200 字符,长期摘要最大 1400 字符 - 所有记忆数据持久化到 MySQL,应用重启不丢失 > 如果每次都对全部历史做摘要,随着对话越来越长,摘要的输入会越来越大,成本和延迟都会线性增长。增量摘要的做法是:每次只处理新增的几轮对话,把它们融合到已有的摘要中。这样不管对话多长,每次摘要的成本都是固定的。 ### **三种策略的适用场景对比** | 策略 | Token 成本 | 上下文完整性 | 适用场景 | | --- | --- | --- | --- | | 无记忆 | 最低 | 无 | 一次性查询、FAQ 问答 | | 滑动窗口 | 中等 | 最近 N 轮完整 | 短期连续追问、调试场景 | | 摘要压缩 | 可控 | 长期摘要 + 近期原文 | 生产环境长对话 | ## **MCP 工具协议与 Skills 能力扩展** Agent 的价值不只是能聊天,更在于能**调用外部工具完成实际任务**。Super Agent 在工具集成这块做了两层设计: ### **MCP 工具协议** MCP(Model Context Protocol)是 Anthropic 提出的开放协议标准,定义了模型与外部工具之间的通信规范。Super Agent 基于 MCP 协议实现了完整的工具调用链路: - **动态工具发现**:Agent 启动时自动扫描并注册可用的 MCP 工具,不需要硬编码工具列表 - **标准化通信**:工具的输入输出遵循 MCP 协议规范,支持 Stdio 和 SSE 两种传输模式,兼容主流 MCP Server 生态 - **参数自动提取**:模型根据用户意图自动识别需要调用的工具并提取参数,无需用户手动指定 - **多工具编排**:单次对话中可以串联调用多个 MCP 工具,前一个工具的输出可以作为后一个工具的输入 - **安全沙箱**:工具调用在受控环境中执行,有超时限制和异常隔离,防止恶意工具影响主流程 相比传统的 Function Call 硬编码方式,MCP 协议的优势在于**标准化和可扩展性**——新增一个工具不需要改任何业务代码,只需要部署一个符合 MCP 协议的 Server 即可。 ### **Skills 能力扩展** Skills 是比 MCP 更上层的能力抽象,解决的是"Agent 怎么获得特定领域的专业能力"这个问题: - **声明式定义**:每个 Skill 通过 `SKILL.md` 配置文件描述能力边界、触发条件、执行逻辑,结构清晰易维护 - **目录化管理**:Skills 按领域组织成目录结构,支持嵌套分类,方便大规模能力管理 - **自动加载机制**:系统启动时自动扫描 Skills 目录,新增 Skill 只需要放入对应目录,零配置生效 - **引用脚本与参考资料**:Skill 可以关联外部脚本(Python、Shell 等)和参考文档,执行时自动加载上下文 - **能力组合**:多个 Skills 可以组合使用,Agent 根据任务需求自动选择最合适的 Skill 组合 Skills 体系让 Agent 的能力边界不再是固定的,而是可以**持续扩展**的。今天加一个"数据分析"Skill,明天加一个"代码审查"Skill,Agent 的能力就跟着增长,核心代码一行不用改。 > info MCP 与 Skills 的关系 > MCP 解决的是"工具怎么调"的问题——标准化工具的通信协议。Skills 解决的是"能力怎么扩"的问题——如何让 Agent 获得特定领域的专业知识和决策能力。两者互补:MCP 提供工具执行能力,Skills 提供领域知识和决策框架。一个 Skill 可以调用多个 MCP 工具来完成复杂任务。 ## **全链路可观测:出了问题不用猜** 前面讲了这么多环节,如果某一步出了问题怎么排查?Super Agent 基于 AOP 做了全链路追踪,每一轮对话的完整执行过程都能在观测面板中看到。 下面这张截图是轮次详情中的执行阶段时间线。可以看到一次对话请求经过了哪些阶段(会话记忆加载 → 问题改写 → 路由判定 → 结构图查询 → 推荐问题生成 → 收尾归档),每个阶段花了多长时间,关键决策结果是什么——整个链路一目了然: ![[image-dc988764.png]] --- **企业级项目导航**:⬅️ [[00-SuperAgent总览|00-SuperAgent总览]] | 01-核心架构设计 | ➡️ [[02-工程化技术概括|02-工程化技术概括]]