用 Spring AI Alibaba 打造智能查询增强引擎
用 Spring AI Alibaba 打造智能查询增强引擎系列导读在上一篇文章《基于 Spring AI Alibaba 构建混合 RAG Agent》中我们描绘了一套融合“侦探的灵活”与“会计的严谨”的架构蓝图。其中查询增强Query Enhancement被置于六大核心步骤之首。很多开发者会有疑问“大模型上下文窗口那么大直接把历史对话丢进去不就行了为什么还要多此一举去改写问题”本文将深入代码一线通过真实的QueryEnhancementHook实现为你揭示没有查询增强的 RAG就像让一个记忆力超群但耳朵背的专家去工作——他记得住所有书却听不清你在问什么。一、为什么“丰富的上下文”救不了检索在 RAG检索增强生成的流程中存在一个致命的时序错位用户提问“它多少钱”依赖上文检索阶段(Retrieval)系统拿着“它多少钱”去向量数据库搜索。失败数据库里没有“它”只有“iPhone 15”。生成阶段(Generation)系统把检索到的可能是错误的文档 完整的历史对话丢给 LLM。LLM 回答LLM 看着历史对话说“哦你刚才问的是 iPhone所以它是 8999 元。”发现问题了吗虽然 LLM 在最后一步通过上下文猜对了答案但检索阶段已经失败了。LLM 是基于“错误检索到的文档”在强行解释或者完全依靠它训练时的内部知识在回答这会导致幻觉且无法利用企业私有知识库。查询增强的核心价值就是在第 2 步检索发生之前把“它多少钱”改写成“iPhone 15 Pro Max 最新价格是多少”。只有问题清晰了检索才能精准只有检索精准了RAG 才有意义。二、代码实战Spring AI Alibaba 中的QueryEnhancementHook基于 Spring AI Alibaba 强大的AgentHook机制我们可以轻松在 Agent 执行前拦截请求完成“翻译”工作。以下是我们生产环境中的核心实现。1. Hook 的定义与入口我们定义了一个QueryEnhancementHook它继承自AgentHook并标注HookPositions({HookPosition.BEFORE_AGENT})确保它在任何工具调用或检索发生之前执行。Slf4jRequiredArgsConstructorHookPositions({HookPosition.BEFORE_AGENT})publicclassQueryEnhancementHookextendsAgentHook{privatefinalChatClientchatClient;// 用于执行重写的小模型OverridepublicStringgetName(){returnquery_enhancement;}OverridepublicCompletableFutureMapString,ObjectbeforeAgent(OverAllStatestate,RunnableConfigconfig){// ... 核心逻辑见下文拆解}}2. 核心三步走策略在beforeAgent方法中我们并没有盲目地调用大模型而是执行了严密的三步走策略第一步精准提取“当前意图”用户的消息列表里可能包含多轮对话我们需要锁定最后一条用户消息。// 提取历史消息ListMessagemessages(ListMessage)state.value(messages).orElse(List.of());// 【关键】获取最后一个有效的用户查询StringoriginalQuerymessages.stream().filter(msg-msginstanceofUserMessage).map(msg-((UserMessage)msg).getText()).reduce((first,second)-second)// 取最后一条.orElse();if(originalQuery.trim().isEmpty()){returnCompletableFuture.completedFuture(Map.of());// 无内容则跳过}第二步注入“长期记忆” (User Profile)这是本架构的亮点之一。传统的查询重写只依赖短期对话历史而忽略了用户是谁。我们通过config.store()加载用户的长期画像如职位、偏好、过往关注点。用户画像更新以及持久化应该在每次与LLM交互后进行后面会有详细介绍。// 加载长期记忆用户档案 偏好StringuserContextloadUserContext(config);// loadUserContext 内部逻辑// 1. 从 Store 获取 user_profiles/{userId}_profile// 2. 获取 user_profiles/{userId}_preferences// 3. 格式化为 prompt 的一部分例如// 【用户档案】// - 职位: 高级Java架构师// - 偏好: 喜欢简洁的代码示例不喜欢长篇理论场景举例用户问“推荐个方案。”无画像重写为“推荐一个技术方案。”太泛有画像重写为“为高级Java架构师推荐一个高并发微服务架构方案要求简洁的代码示例。”精准命中知识库第三步LLM 智能重写 (The Magic)构造一个精心设计的 Prompt调用chatClient进行重写。这里我们采用了降级策略如果 LLM 挂了直接返回原查询保证系统可用性。privateStringenhanceQueryLogic(StringcurrentQuery,ListMessagehistory,StringuserContext){// 构建对话历史上下文StringconversationHistoryhistory.stream().map(m-m.getMessageType(): m.getText()).collect(Collectors.joining(\n));// 动态指令长文本压缩 vs 短文本补全StringinstructioncurrentQuery.length()500?用户输入过长请压缩至 500 字以内保留关键实体消除歧义。:结合【对话历史】和【用户背景】消除指代歧义如它补充隐含主语使查询成为独立完整的陈述句。;StringpromptTextString.format( ### 角色 你是一个专业的查询重写专家。 ### 用户背景来自长期记忆 %s ### 对话历史短期记忆 %s ### 当前用户输入 %s ### 任务指令 %s ### 输出要求 1. 仅输出重写后的查询文本不要任何解释。 2. 必须利用【用户背景】补全缺失的主语或偏好。 3. 必须保留所有具体参数日期、ID、版本。 4. 如果原句已清晰则原样输出。 ### 重写后的查询 ,userContext,conversationHistory,currentQuery,instruction);try{// 调用 LLM 执行重写StringresultchatClient.prompt().user(promptText).call().content();returnresult!null?result.trim():currentQuery;}catch(Exceptione){log.warn(LLM 查询增强失败降级使用原始查询,e);returncurrentQuery;// 故障安全}}这里会从短期记忆中加载对话历史并注入上下文长对话可能超过 LLM 的上下文窗口。常见的解决方案包括修剪消息。在调用 LLM 之前移除前 N 条或后 N 条消息删除消息。从 Graph 状态中永久删除消息总结消息。总结历史中较早的消息并用摘要替换它们【推荐】自定义策略。自定义策略例如消息过滤等这允许 Agent 在 reasoning-acting 循环中持续跟踪对话而不超过 LLM 的上下文窗口。后面会介绍我的实现。3. 状态更新无缝替换一旦获得增强后的查询我们不是把它放在一边而是直接替换state中的消息列表。这样后续的 RAG 节点、Tool Router 拿到的就是已经“翻译”好的完美问题无需任何额外适配。if(!enhancedQuery.equals(originalQuery)){log.info(查询已增强[{}] - [{}],originalQuery,enhancedQuery);// 构建新消息列表仅替换最后一条 UserMessageListMessageenhancedMessagesnewArrayList(messages);intlastUserIndexfindLastUserMessageIndex(messages);// 辅助方法倒序查找if(lastUserIndex!-1){enhancedMessages.set(lastUserIndex,newUserMessage(enhancedQuery));}// 返回修改后的 stateSpring AI 会自动应用returnCompletableFuture.completedFuture(Map.of(messages,enhancedMessages));}三、效果对比有无增强的天壤之别让我们看两个真实场景的对比感受查询增强的威力。场景 A多轮对话中的指代消解步骤用户行为无增强 (传统 RAG)有增强(混合 RAG)Round 1用户“2026年的病假流程是什么”检索“2026年病假流程” ✅ 成功检索“2026年病假流程” ✅ 成功Round 2用户“那事假呢”检索“那事假呢” ❌失败(向量库无此句) 结果胡乱匹配或返回空重写“2026年的事假申请流程是什么” ✅成功(精准命中)Round 3用户“需要审批吗”检索“需要审批吗” ❌失败(不知道指哪个流程)重写“2026年事假申请流程需要审批吗” ✅成功(上下文完整)场景 B个性化偏好注入维度无增强有增强用户画像(系统未知)职位运维工程师偏好只要 Shell 脚本不要 Python用户提问“怎么重启服务”“怎么重启服务”重写结果“怎么重启服务”“Linux 环境下重启服务的Shell 脚本命令是什么”检索结果混杂了 Java、Python、Windows 的通用教程精准命中Linux Shell 相关文档最终体验用户需要二次追问“我要 Shell 版的”一次命中用户感觉 AI“懂我”四、避坑指南与最佳实践在实现过程中我们也踩过一些坑总结以下几点供参考不要过度重写如果用户的问题已经很清晰如“什么是 Spring Boot”LLM 可能会画蛇添足。对策在 Prompt 中明确强调“如果当前输入已经非常清晰且无指代则原样输出”。性能与成本的平衡查询增强需要额外调用一次 LLM。对策使用小模型如 Qwen-Turbo 或本地部署的 7B 模型专门做重写速度快、成本低。配合语义缓存本系列下一篇重点高频问题直接缓存根本不需要触发重写和检索。容错设计LLM 可能会超时或报错。对策务必像代码中那样做try-catch降级。增强是“锦上添花”不能因为增强失败导致整个对话不可用。Fail-fast, fallback to original.五、结语查询增强Query Enhancement绝不是多余的步骤它是连接人类模糊语言与机器精准检索的唯一桥梁。通过 Spring AI Alibaba 的AgentHook我们不仅实现了指代消解更将长期用户画像融入到了检索的源头。这使得我们的 RAG 系统不再是一个冷冰冰的搜索引擎而是一个能听懂“弦外之音”、记得住“老客喜好”的智能助手。下一步预告问题清楚了检索也精准了但如果每次都要重新查一遍速度够快吗下一篇我们将深入语义缓存Semantic Cache的实现揭秘如何让高频问题实现“毫秒级”响应进一步降低 Token 成本
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2433966.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!