Ragent
nageoffer/ragent项目目录
项目简介
Ragent 是基于 Java 的知识库与 Agent 平台,支持文档入库、知识检索和流式回答,提供固定问答 Workflow 与 Agent 工具调用两条执行路线。
如何写进简历
捷答 · 企业 IT 服务台知识助手 | 场景化示例
项目描述:面向企业 IT 服务台,整合 VPN、账号和办公应用的操作文档及故障知识,根据系统名称、错误码与问题描述返回带出处的排查步骤。
技术栈:Java、Spring Boot、PostgreSQL、pgvector、Redis
主要工作:
- 问题拆分与意图路由:利用问题改写与叶子意图评分,将
VPN、账号和办公应用问题解析为定向及补充知识库范围;结合低置信度回退,处理跨系统提问与同名故障的检索路由。 - 并发召回与超时降级:基于
CompletableFuture和分层执行器并发调度启用通道及主、补充检索,将超时或异常转为空结果,保留健康通道候选继续融合,避免单路失败直接中止知识问答。 - 去重融合与重排预算:按片段键去重,基于原通道名次计算加权
RRF分并截断候选池,再按需进行模型重排;分别控制召回深度、重排规模和答复上下文,处理跨通道重复与分数不可直接比较的问题。
面试题与回答
意图识别使用什么模型,意图识别树如何组织?
展开收起
核心回答
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没有积压长度,不能只靠队列为零判断系统有余量。 - 补充路失败: 检查主路候选是否保留;全部通道无可用证据时,固定流程应结束并提示未检索到相关文档,而非继续生成知识答案。
- 持续负载与恢复: 关联提交/完成速率、活跃线程、排队时间、拒绝或调用者执行次数、超时率和连接等待。恢复依赖后,确认过期任务没有持续挤占新请求。
这些检查分别回答“返回是否按预期降级”和“资源是否真正受到约束”;仅看到接口及时返回,不能证明慢源已经被隔离。
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:参考高效候选召回与内容交互精排的分工,以及候选规模与模型成本的关系。