主要技术:Spring Boot、AgentScope、PostgreSQL、pgvector、Redis、Elasticsearch、MCP
项目目录
项目简介
Ragent 是一套基于 Java、Spring Boot 和 React 的开源知识问答与业务助手。它将企业文档检索、智能体和业务工具调用组合在一起:既可以回答制度、产品手册中的问题,也可以查询订单、处理售后申请。
仓库自带企业知识库和「比特严选」电商示例。例如,用户询问“上周买的耳机能退吗,能退就帮我申请”,智能体可以查询订单、检索退货政策,补问缺少的信息,并在用户确认后调用工具提交申请。项目同时提供管理控制台,用于维护知识库、意图分类、技能和运行记录。

项目亮点
- 多路检索与融合排序: 根据问题识别检索范围,并行调用配置启用的向量、关键词等通道,再经过去重、
RRF融合和重排序选取资料。召回数量、精排数量和最终上下文分别控制,兼顾资料覆盖与模型输入成本。 - 从知识问答到业务办理: 基于
AgentScope ReAct,让智能体结合检索结果和MCP工具返回值决定下一步。需要确认的操作会暂停执行,向用户展示参数,确认后继续调用业务工具。 - 按需加载技能与管理上下文: Skills 提供具体任务的办理步骤,并结合已加载的技能控制工具开放范围;长对话通过工具结果裁剪、会话摘要和长期记忆控制上下文规模。
- 异步任务与故障处理: 文档解析、分块和索引通过异步任务处理;检索支持单通道超时或失败降级,Workflow 的模型调用支持首包探测与候选切换。
技术架构
项目采用前后端分离的模块化单体架构,提供两种执行方式:
Workflow 知识问答: 问题重写 → 意图识别 → 多路检索 → 结果整理 → 生成回答与引用,适合步骤明确的知识查询。
Agent 业务助手: 用户提出任务 → 智能体选择知识检索或业务工具 → 根据结果继续处理 → 必要时等待用户确认,适合需要多步查询和操作的任务。
阅读源码时,可以先从这四个模块建立整体认识:
| 模块 | 主要职责 |
|---|---|
rag/ |
知识库管理、文档入库、意图路由、检索与 Workflow 问答 |
agent/ |
ReAct 执行、Skills、上下文管理和人工确认 |
infra-ai/ |
模型客户端适配,以及模型路由、首包探测和降级 |
mcp-server/ |
独立的业务工具示例服务,供 Agent 调用 |
前端位于 frontend/,使用 React、TypeScript 和 Vite。数据层以 PostgreSQL、pgvector 保存业务数据并提供向量检索,Redis 用于缓存与并发协调,RocketMQ 承担文档任务投递;Elasticsearch 等检索通道按配置接入。启动配置、认证审计和通用基础能力分别放在 bootstrap/、system/ 与 framework/。
如何写进简历
比特严选 · 电商咨询与售后助手 | 电商零售场景示例
项目描述:面向自营电商的商品咨询与售后助手,结合商品资料和售后政策解答使用、退换问题,支持连续咨询、订单查询和用户确认后的售后申请,衔接问题解答与申请提交。
技术栈:Java、Spring Boot、AgentScope、PostgreSQL、pgvector、Redis、Elasticsearch、MCP
主要工作:
- ReAct 编排与人工确认恢复:基于
AgentScope ReAct串联知识检索、订单查询与MCP售后工具,根据工具结果继续补问或执行;将待确认参数持久化,用户确认后从服务端状态恢复原调用,并在业务执行端校验订单归属与申请条件。 - Skills 渐进加载与工具约束:通过
load_skill按需加载售后办理手册,在模型工具清单与MCP执行入口检查技能解锁状态;手册随上下文压缩移除后重新遮蔽关联工具,控制提示长度并约束办理步骤。 - 混合召回与融合精排:围绕商品型号、使用问题和售后政策,并行组织
pgvector与Elasticsearch检索,经片段去重、加权RRF与Rerank融合结果;分别控制召回量、精排候选和最终上下文,兼顾型号精确匹配与自然语言问法。 - 分层记忆与长会话压缩:采用工具结果裁剪、历史摘要与用户长期记忆分层管理上下文,保护近期完整工具交互,压缩早期冗长结果;将稳定偏好与即时订单状态分开处理,支持连续咨询并控制模型输入规模。
维保帮 · 工业设备售后知识助手 | 制造业场景示例
项目描述:面向工业设备售后工程师的知识问答系统,按设备型号、固件版本和告警代码检索适用手册与故障资料,返回排查说明及原文依据。
技术栈:Java、Spring Boot、PostgreSQL、pgvector、Redis、RocketMQ、Elasticsearch
主要工作:
- 文档异步入库与事务消息:基于
RocketMQ事务消息衔接手册任务投递与文档状态更新,通过状态回查决定消息提交;异步执行解析、分块、向量化与索引写入,记录阶段耗时及失败原因,解耦大文档处理与请求响应。 - 追问重写与设备范围约束:结合术语映射和历史对话补全故障追问,以意图分类选择检索方向;将型号、固件对应的文档白名单同时作用于定向与补充召回,避免低置信度回退混入不适用手册。
- 并行检索与融合排序:使用
CompletableFuture和分层线程池并发调度向量、关键词等通道,单路超时或异常时保留健康结果;经加权RRF与Rerank融合告警码和症状描述的命中,分层控制候选规模与生成上下文。 - 流式探测与模型熔断:在
Workflow首个有效内容输出前探测模型响应,失败时取消调用并切换候选;结合三态熔断与半开单探针隔离异常模型,使设备排查问答在模型波动时仍可选择健康服务。
面试题与回答
6 题如何在 Agent 系统中设计 Human-in-the-loop 人工介入能力?
展开收起
Ragent把人工确认接在AgentScope的工具执行环节:模型提出调用,运行时判断是否需要确认;需要时保存待执行状态,用户决定后再继续同一会话。 知识检索与业务操作由ReAct循环选择,写操作不靠提示词里的“请先询问用户”来约束。怎样暂停和继续
工具目录结合管理端配置与
MCP的readOnlyHint决定确认要求:显式配置确认,或工具没有明确声明只读,都会要求确认。框架将调用标记为ASKING,页面展示工具及参数,服务端保存待确认消息;Agent 状态通过PostgreSQL保存。用户提交
conversationId、messageId和同意或拒绝。后端核对用户、会话及待确认状态,再从 Agent 状态取回原始工具调用,构造ConfirmResult续跑;前端不能在确认请求里替换工具参数。已经处理或失效的卡片会被拒绝。这里支持同意、拒绝,修改方案需要重新形成调用,不等于任意编辑参数后直接执行。确认与续跑实现。确认之后仍要检查什么
以仓库中的比特严选
apply_after_sale为例,用户确认订单、商品、售后类型和原因;执行时工具仍按当前用户查询订单,校验签收状态和受理时间窗,检查同一订单同一商品是否已有未结售后单,并借助唯一约束拦截并发重复申请。成功回执只表示售后单已提交、待人工审核,不能说已经退款或完成换货。售后申请工具。人工确认也不自动解决外部写操作的幂等:远程调用超时后,仍需根据业务回执核对结果,不能盲目重新生成一次售后申请。这个边界决定了确认负责表达意愿,业务服务负责最终校验与执行。
Agent 调用 Skill 工具的完整流程是什么?
展开收起
Ragent的执行链是:先看技能目录,调用load_skill取得手册,再让相关业务工具进入可用集合,执行时复查。 手册告诉模型怎样处理任务,工具才负责真正查询或修改业务数据。手册怎样进入上下文
load_skill的工具说明列出已启用技能的标识、名称和用途,初始输入不包含所有手册正文。模型选定技能后传入skill_code,服务端从注册表读取正文,作为工具结果返回,并写入ragent_loaded_skill元数据;不存在或停用的技能返回错误,不伪装成加载成功。技能加载实现。工具怎样随手册开放
AgentSkillMaskingMiddleware根据当前上下文里的加载标记计算可见工具:所有启用技能关联的工具,减去已加载技能解锁的工具,余下的在本轮推理中隐藏。一个工具关联多份手册时,任一份已加载即可开放;未被技能关联的通用工具保持可见。这不只修改模型看到的
Schema。中间件把隐藏集合放入运行上下文,McpToolProxy执行前再检查一次;模型直接猜中工具名,也会收到“先加载手册”的错误。手册被会话压缩移走后,其加载标记消失,相关工具重新隐藏,摘要里一句“已经加载过”不能替代原手册。可见性计算、执行前复查。这套机制控制按需披露,仍需配合写操作确认与业务权限。当前实现中,遮蔽计算异常会保留完整工具清单,因此不能把它当作任何故障下都能阻断越权的授权系统。
RRF 如何融合向量检索与 ES 结果?
展开收起
核心回答
Ragent的Java Workflow在知识检索后,按“去重、加权RRF融合、截断候选、可选模型重排”处理多路结果。去重保证同一片段只保留一份;RRF根据它在各通道中的原始名次累计贡献,避免直接比较向量相似度与BM25分数;重排模型再阅读问题与候选内容,判断哪些片段更能回答问题。以向量加
ES关键词检索为例,需要先配置启用关键词后端和通道。该版本默认rag.keyword.type=none,关键词通道也默认关闭,不能把这条双路融合写成默认运行状态。去重以后,怎样保留多路贡献
MultiChannelRetrievalEngine保存各通道的SearchChannelResult,并将后置处理器按顺序执行。DeduplicationPostProcessor的顺序为1,用RetrievedChunkKey放入LinkedHashMap,同键片段保留首次出现的对象。键优先取片段id,缺失时使用文本的SHA-256摘要;两路索引应使用一致的片段标识,才能正确对齐。去重处理器、片段键。去重处理的是输出对象,原通道列表仍然保留。例如片段 A 同时命中向量和关键词通道,去重后只有一个 A,但融合时仍能分别找到它在两路中的名次,不会因为只保留一个对象而丢掉另一路贡献。
加权 RRF 怎样实现
FusionPostProcessor的顺序为5。它遍历每条原通道列表,按片段键累计:score(chunk) = Σ channelWeight / (rrfK + rank)源码循环下标从
0开始,所以计算用weight / (k + rank + 1)。得到所有贡献后,把融合分写回去重后的片段,再按融合分降序排列。这里用的是各通道原始召回名次,不是去重后的遍历顺序,也不是原始相似度分数。融合计算。rrfK控制名次之间的贡献差异,通道权重控制各路贡献大小,两者作用不同。当前配置的rrf-k为20,向量和关键词权重均为1.0;它们是该版本配置值,是否适合具体知识库仍应评估。只有一条通道时,处理器跳过跨路融合,保留原顺序并执行候选截断。检索与融合配置。候选截断与模型重排怎样分工
融合后按
candidateLimit截断候选池,控制送入重排阶段的数量。RerankPostProcessor的顺序为10,开启时把当前子问题、候选片段和contextTopK传给重排服务;后者决定内容相关性的排序及返回条数。若使用NoopRerankClient,则仅截取前若干条,没有模型重新评分。候选截断、可选重排、无模型重排实现。重排调用抛出异常时,多通道引擎会捕获异常并跳过该处理器,后续流程继续使用当时已有的候选。这里没有重排自动重试,返回候选也不代表本次完成了模型精排。处理器异常分支。
这条链路区分三段预算:
recallBudget是各通道召回的基数,candidateLimit是融合后的候选上限,contextTopK是后续上下文的条数预算。检索结果格式化时也会使用contextTopK,因此不能把三者合成一个含义模糊的Top-K。预算定义、上下文装配。怎样验证这套实现有价值
固定文档、问题集和检索范围,分别比较向量单路、关键词单路、融合,以及融合后加模型重排。先检查正确证据是否进入各路,再看融合排名、候选截断和精排各淘汰了什么,最后比较答案、引用和延迟。
加权
RRF实现简单,不需要校准不同通道的原始分数,但会舍弃分数差距,也可能让噪声通道贡献过高。模型重排能补充内容判断,却无法找回已被候选截断淘汰的片段;扩大候选池又会增加重排成本,应在同一任务集上取舍。参考资料
- 国内:JavaGuide:RAG 检索优化:参考召回、融合、精排与上下文预算分开控制的解释。
- 国内:Datawhale:Hello-Agents 第八章——记忆与检索:参考扩展检索按稳定标识合并候选及扩大候选池的组织;其多查询向量分数合并不等同本文的异构通道
RRF。 - 国内:zero2Agent:RAG 与检索系统:参考多路召回、合并与精排的职责,以及按召回和答案分别评估的安排。
- 国内:BAAI:BGE-M3 模型说明:参考词项与语义信号的互补,以及混合召回后再重排的两阶段取舍。
- 国外:Microsoft:Hybrid search scoring with RRF:依据倒数名次累加及融合后再语义重排的机制,说明融合与模型重排的区别。
- 国外:Elastic:RRF retriever:依据名次贡献加权、排名常数和候选窗口的定义,核对算法与参数职责;本项目在
Java处理器中融合,不依赖ES内置RRF接口。 - 国外:Sentence Transformers:Retrieve & Re-Rank:参考高效候选召回与内容交互精排的分工,以及候选规模与模型成本的关系。
Agent 如何设计分层记忆,实现短期与长期信息的保存和使用?
展开收起
Ragent分别处理当前会话中的工具结果、较早的对话内容、跨会话的用户记忆:旧工具结果优先裁剪,长会话压成摘要,值得长期保留的信息单独写入。三者有不同的触发条件和读取范围。执行中保留什么,压缩什么
工具结果裁剪按上下文字符水位触发,只处理配置允许裁剪的旧结果;本轮、尚未闭合及最近保留的工具循环受到保护。被裁剪的正文换成省略标记,但调用
id、工具名、元数据与成功或失败状态仍保留,避免把一次失败改写成成功,或破坏调用与结果的对应关系。会话摘要只在新用户消息到来且超过压缩水位时尝试,沿用户轮次选择切点,合并旧摘要与早期消息,保留近期完整片段。摘要生成失败或找不到安全切点时不直接删除原文,退回工具结果裁剪。这些水位按字符估算,不是精确的
Token计数。裁剪实现、摘要触发与失败处理。用户记忆怎样跨会话生效
长期记忆按用户独立保存;一次运行首次读取有效条目,形成调用级快照,并注入模型输入。后台学习按消息游标读取新增用户发言,对照已有记忆判断新增、修改或删除;记忆变更与游标推进在同一事务提交。用户明确要求记住、修改或忘掉时,另走主动整理入口。记忆注入、增量学习。
例如商品咨询中的本次预算和已选型号用于当前任务,稳定偏好才适合跨会话复用;订单状态、库存和优惠有效性仍要查询业务工具。摘要有损,记忆写入也可能失败,保存了偏好不等于取得了最新业务事实。
意图识别使用什么模型,意图识别树如何组织?
展开收起
核心回答
Ragent这条链使用 LLM 对候选叶子节点打分,意图树负责组织业务分类和执行所需的配置。树从Redis加载,缓存缺失时查询数据库并重建;识别时展开树,把当前模式允许的叶子节点及其路径、描述、示例一次性放进提示词,而不是从根节点开始逐层调用分类模型。这里选择可配置的
Workflow模式说明:仓库部署配置的ragent.engine.type默认为agent,两条入口不能混为同一流程。Workflow的意图候选包含知识检索类KB和系统交互类SYSTEM;另一条 Agent 入口的知识工具只识别KB,不会把系统应答混进知识库选择。实际调用什么模型
意图分类器通过统一的
LLMService.chat()同步调用模型,构造系统提示词和用户问题,并设置较低的temperature、top_p,关闭思考模式。它没有在分类器中写死一个专用分类模型,而是由RoutingLLMService、ModelSelector根据默认standard档位选择模型,再调用对应供应商的适配客户端。固定版本的配置中,
standard的首个候选是qwen3.8,对应百炼的模型名qwen3.8-27b;后续登记了qwen-plus、deepseek-flash和本地qwen3等回退候选。实际使用的模型由启用状态和路由结果决定。模型配置与路由服务共同决定这条调用链。供应商客户端适配请求与返回,例如百炼客户端复用基于
OkHttp的聊天请求实现,并处理供应商的思考开关。意图树怎样保存与加载
数据库节点用
intentCode、parentCode表达父子关系,加载时只取未删除且已启用的节点,先建立节点映射,再连接父子节点,并生成从根到叶的fullPath。节点有名称、描述、示例问题和层级标识;叶子还可以关联知识库与逻辑集合、检索条数及提示配置。树的层级可以按业务组织,不要求所有分类都固定为同样深度。例如“业务系统 > OA 系统 > 数据安全”这样的路径,既能帮助维护者区分同名主题,也能连同描述和示例一起进入分类提示词。
kind=KB表示知识检索,kind=SYSTEM表示问候、助手身份或反馈等系统交互,两者后续处理不同。每次分类会读取
Redis中的树;缓存不存在或读取失败时,再从数据库加载并回写缓存。管理端增删改节点后会清理缓存,使后续读取重新加载。这是数据库配置、缓存与请求内候选结构的分工,不能把“树在缓存里”理解为树只存在内存中,也不能把读缓存说成每次都查数据库。实现见树加载与组装和数据库节点重建。怎样从叶子评分走到检索或应答
流程先结合会话历史改写问题并拆分子问题,再对每个子问题分别做意图识别。分类器提取符合当前模式的叶子候选,将
id、完整路径、描述、示例和类型放进同一份提示词,请模型返回带id、score的JSON数组。提示词里虽然有判断步骤,但不是服务端沿树每层发起一次模型调用。收到结果后,代码只接受本次候选集合中的 ID,忽略不存在或当前模式不可用的节点,按分数排序;
IntentResolver再做最低分过滤与候选数量限制。模型调用失败或返回无法解析时,分类器返回空意图。这些分数是模型给出的相关性评分,不是经过校准的正确概率。 候选白名单校验也只限制可选节点,不能证明选中的分类一定正确。具体见候选构造与结果解析。分类之后,固定流水线先检查是否需要澄清:例如多个系统下都有“数据安全”主题,结合候选路径和歧义判断,可以先让用户选择系统。只有所有子问题都各自仅命中一个
SYSTEM意图时,才进入系统应答分支;其余请求进入知识检索流程。固定流水线负责这个顺序。知识意图用于选择检索范围,低分或无有效知识意图时,当前实现可以退回有效知识库的全局检索;分数足够时采用命中范围,并保留补充范围。因此,空意图不等于必然拒答,命中意图也不等于权限校验已经完成。验证这条链应分别看候选分类、检索范围和最终回答,用明确实体、同名主题、未知请求及模型返回非法 ID 等样本定位错误,而不是只统计模型有没有返回一个分类。
多数据源并发召回为什么使用专用线程池,如何隔离共享线程池风险?
展开收起
核心回答
Ragent的固定问答流程会先改写、拆分问题和识别意图,再并发召回相关知识片段,融合后交给模型生成回答。本例按ragent.engine.type=workflow说明固定问答流程。固定版本默认配置为agent,由另一个AgentScopeAgent 入口通过知识工具复用检索模块,两条主线应分别说明。这条路线把子问题编排、通道召回和内部取数放进不同的专用执行器,减少对全局共享池的依赖;但不同检索源并没有各自独占一个池。它还提供通道超时与空结果降级,能放弃慢通道结果,不能据此认为底层任务已经停止或请求总耗时一定不超过该值。
三层线程池分别承担什么?
检索模块的输入是子问题、意图作用域与召回预算,输出是供融合、重排和上下文组装使用的知识片段。
ragContextExecutor按子问题并发构建检索上下文。ragRetrievalExecutor并发执行当前启用的向量、关键词、图谱或网页通道。通道是否参与,仍受后端配置和开关控制。innerRetrievalExecutor承担通道内部的并行取数,例如向量和关键词的补充路,以及需要逐库执行的向量检索。
线程池配置中,前两者的线程数均为可用 CPU 数的四倍,使用不存储任务的
SynchronousQueue;内部取数池的核心数为两倍、最大数为四倍,队列容量为 100。三者都使用CallerRunsPolicy。这些是当前实现的配置,是否适合部署环境仍需结合任务扇出、连接池和负载验证。这种划分分开了编排与子任务使用的执行资源,但向量、关键词等通道仍共享通道池,主路与补充路也共享内部池。若某个外部源经常超时,进一步按依赖设置独立池或并发额度,属于可评估的改进,不是已有的每源隔离能力。
超时和降级具体怎样生效?
多通道引擎先提交各通道任务,分别挂上
orTimeout,再等待结果;超时或异步失败会转换为空通道结果,让其余结果继续融合。各任务先并发提交,后面逐个join()不等于按顺序才开始查询。超时处理没有取消底层任务。向量、关键词补充路的异常也分别转换为空列表,正常返回的主路候选仍可保留。
还有一个饱和边界:代码先调用
supplyAsync(..., executor),返回后才配置超时。若触发调用者执行,supplyAsync可能在提交线程中执行慢查询,直到它返回后才挂上计时;后续通道的提交也会被延后。因此,配置中的通道超时不是整次请求的硬截止,调用者执行还会突破“任务只占专用池工作线程”的设想。怎样验证隔离是否有效?
验证需要同时看结果与资源占用,可以用可控的检索替身注入慢响应、异常和正常结果,再逐步增加并发:
- 单源变慢: 检查健康通道的候选是否仍进入融合、超时是否被记录;继续观察返回后的活动线程和连接,确认后台任务何时真正结束。
- 线程池饱和: 检查任务是否在调用者线程运行、后续通道提交是否延迟,以及队列等待和端到端 P95/P99 是否上涨。
SynchronousQueue没有积压长度,不能只靠队列为零判断系统有余量。 - 补充路失败: 检查主路候选是否保留;全部通道无可用证据时,固定流程应结束并提示未检索到相关文档,而非继续生成知识答案。
- 持续负载与恢复: 关联提交/完成速率、活跃线程、排队时间、拒绝或调用者执行次数、超时率和连接等待。恢复依赖后,确认过期任务没有持续挤占新请求。
这些检查分别回答“返回是否按预期降级”和“资源是否真正受到约束”;仅看到接口及时返回,不能证明慢源已经被隔离。