news 2026/6/16 6:40:51

AI Agent开发实战⑯|Query改写与扩展:让检索更懂用户意图

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent开发实战⑯|Query改写与扩展:让检索更懂用户意图

AI Agent开发实战⑯|Query改写与扩展:让检索更懂用户意图

用户问"Python性能优化",但文档里写的是"Python性能调优技巧"——语义相同但词汇不同,向量检索可能漏掉。Query改写和扩展就是解决这个问题的:用多种表达方式检索,提升召回率。

一、用户意图的真实问题

用户输入往往存在三个问题:

问题1:表达不准确 用户输入:"怎么让Python跑得更快" 文档表达:"Python性能优化方法" 问题:词汇不匹配 问题2:意图模糊 用户输入:"Python性能" 文档表达:可能关于性能测试、性能优化、性能监控 问题:无法确定用户要什么 问题3:长尾词缺失 用户输入:"Python性能优化" 文档表达:"Python 3.11性能提升30%的原因分析" 问题:没提到版本号、具体数字

Query改写的目标:

原始Query → 扩展Query 1, 2, 3... → 分别检索 → 结果融合

二、Query改写的四种策略

2.1 同义词扩展

classSynonymExpander:"""同义词扩展"""def__init__(self):# 领域同义词词典self.synonyms={"性能优化":["性能调优","性能提升","运行速度优化","执行效率提升"],"配置":["设置","参数配置","环境配置"],"错误":["异常","报错","Error","Exception"],"部署":["上线","发布","Deploy"],}defexpand(self,query:str)->list[str]:"""扩展同义词"""expanded=[query]# 保留原始查询forterm,synonymsinself.synonyms.items():ifterminquery:forsyninsynonyms:expanded.append(query.replace(term,syn))returnexpanded# 使用示例expander=SynonymExpander()queries=expander.expand("Python性能优化方法")# 输出:# ["Python性能优化方法",# "Python性能调优方法",# "Python性能提升方法",# "Python运行速度优化方法"]

2.2 LLM改写

classLLMQueryRewriter:"""LLM改写Query"""def__init__(self,llm):self.llm=llmdefrewrite(self,query:str,num_rewrites:int=3)->list[str]:"""用LLM生成多个改写版本"""prompt=f""" 用户查询:{query}请生成{num_rewrites}个意思相同但表达不同的查询改写版本。 要求: 1. 保持原意不变 2. 使用不同的词汇和句式 3. 包含可能的同义词和专业术语 4. 每行一个,不要编号 示例: 原查询:Python性能优化 改写: Python性能调优技巧 如何提升Python运行速度 Python执行效率优化方法 """response=self.llm.invoke(prompt)rewrites=[line.strip()forlineinresponse.content.split('\n')ifline.strip()]return[query]+rewrites[:num_rewrites]# 使用示例rewriter=LLMQueryRewriter(llm)queries=rewriter.rewrite("Python性能优化",num_rewrites=3)# 输出:# ["Python性能优化",# "Python性能调优技巧",# "如何提升Python运行速度",# "Python执行效率优化方法"]

2.3 HyDE(假设文档嵌入)

HyDE的核心思想:先让LLM生成一个假设的答案,用答案去检索,而不是用问题。

classHyDERewriter:"""Hypothetical Document Embeddings"""def__init__(self,llm,embedder):self.llm=llm self.embedder=embedderdefgenerate_hypothetical_doc(self,query:str)->str:"""生成假设文档"""prompt=f""" 用户查询:{query}请生成一段可能回答这个问题的文档内容。 要求: 1. 长度200-300字 2. 包含可能的关键信息 3. 使用专业术语 直接输出文档内容,不要解释。 """response=self.llm.invoke(prompt)returnresponse.contentdefretrieve_with_hyde(self,query:str,vector_store,k:int=5)->list:"""HyDE检索"""# 生成假设文档hypo_doc=self.generate_hypothetical_doc(query)# 用假设文档的向量检索hypo_embedding=self.embedder.embed(hypo_doc)results=vector_store.search(hypo_embedding,k=k)returnresults,hypo_doc# 使用示例hyde=HyDERewriter(llm,embedder)query="Python性能优化"results,hypo_doc=hyde.retrieve_with_hyde(query,vector_store)print("假设文档:")print(hypo_doc)print("\n检索结果:")forrinresults:print(r)

HyDE的优势:答案比问题更像答案,用答案检索更精准。

2.4 Query2Doc(Query转文档)

Query2Doc是HyDE的简化版:把Query扩展成一个简短的文档描述。

classQuery2Doc:"""Query转文档"""def__init__(self,llm):self.llm=llmdefexpand(self,query:str)->str:"""将Query扩展为文档描述"""prompt=f""" 用户查询:{query}请用一段简短的文字描述用户可能想要查找的内容。 格式:用户可能想要了解关于XXX的内容,包括AAA、BBB、CCC等方面。 只输出描述,不要解释。 """response=self.llm.invoke(prompt)returnresponse.contentdefretrieve_with_q2d(self,query:str,embedder,vector_store,k:int=5):"""Query2Doc检索"""# 扩展Queryexpanded=self.expand(query)# 合并原始Query和扩展文档combined=f"{query}\n{expanded}"# 检索embedding=embedder.embed(combined)results=vector_store.search(embedding,k=k)returnresults,expanded

三、多Query检索融合

无论用哪种策略,最终都是生成多个Query版本。如何融合检索结果?

3.1 Reciprocal Rank Fusion(RRF)

fromcollectionsimportdefaultdictdefreciprocal_rank_fusion(results_list:list[list],k:int=60)->list:""" RRF融合算法 results_list: 多个Query的检索结果列表 k: RRF参数(默认60) 公式:RRF(d) = Σ 1/(k + rank(d)) """scores=defaultdict(float)forresultsinresults_list:forrank,docinenumerate(results):# RRF分数scores[doc["id"]]+=1/(k+rank+1)# 按分数排序sorted_docs=sorted(scores.items(),key=lambdax:x[1],reverse=True)return[{"id":doc_id,"rrf_score":score}fordoc_id,scoreinsorted_docs]

3.2 加权分数融合

defweighted_fusion(results_list:list[list],weights:list[float]=None)->list:"""加权分数融合"""ifweightsisNone:weights=[1.0/len(results_list)]*len(results_list)scores=defaultdict(float)forresults,weightinzip(results_list,weights):fordocinresults:scores[doc["id"]]+=doc.get("score",1.0)*weight sorted_docs=sorted(scores.items(),key=lambdax:x[1],reverse=True)return[{"id":doc_id,"weighted_score":score}fordoc_id,scoreinsorted_docs]

四、实测对比

4.1 测试设置

测试数据:-文档:10000篇中文技术文档-查询:100个测试查询-评估:Recall@10,NDCG@10

4.2 单策略效果

策略Recall@10NDCG@10耗时
原始Query71.2%0.6812ms
同义词扩展76.3%0.7215ms
LLM改写79.8%0.76350ms
HyDE82.1%0.79520ms
Query2Doc80.4%0.77280ms

关键发现

  • HyDE效果最好,但耗时最长
  • LLM改写效果不错,性价比高
  • 同义词扩展最简单,效果也不错

4.3 组合策略效果

组合Recall@10NDCG@10耗时
原始 + 同义词78.5%0.7420ms
原始 + LLM改写82.3%0.78380ms
原始 + HyDE84.2%0.81550ms
原始 + 同义词 + LLM改写83.1%0.79390ms

组合策略提升有限(2-3%),建议选单一策略即可。

五、选型决策

第一步:场景评估 │ ├── 专业领域(有明确术语体系) │ → 【同义词扩展】 │ 理由:术语明确,同义词词典效果好 │ ├── 通用领域 │ → 【LLM改写】 │ 理由:通用性强,效果稳定 │ └── 追求最优效果 → 【HyDE】 理由:效果最好 第二步:延迟要求 │ ├── 要求<50ms │ → 【同义词扩展】 │ ├── 要求<500ms │ → 【LLM改写】或【Query2Doc】 │ └── 对延迟不敏感 → 【HyDE】

六、完整代码:智能Query改写器

classSmartQueryRewriter:"""智能Query改写器:自动选择策略"""def__init__(self,llm,embedder):self.llm=llm self.embedder=embedder self.synonym_expander=SynonymExpander()defanalyze_query(self,query:str)->dict:"""分析Query特征"""# 检测是否包含专业术语tech_terms=["API","SDK","GPU","CPU","内存","配置","优化"]has_tech_term=any(terminqueryfortermintech_terms)# 检测Query长度is_short=len(query)<10# 检测是否是问句is_question=any(kwinqueryforkwin["如何","怎么","为什么","什么是"])return{"has_tech_term":has_tech_term,"is_short":is_short,"is_question":is_question}defrewrite(self,query:str,strategy:str="auto")->list[str]:"""改写Query"""ifstrategy=="auto":strategy=self._select_strategy(query)ifstrategy=="synonym":returnself.synonym_expander.expand(query)elifstrategy=="llm":returnLLMQueryRewriter(self.llm).rewrite(query)elifstrategy=="hyde":hypo=HyDERewriter(self.llm,self.embedder).generate_hypothetical_doc(query)return[query,hypo]else:return[query]def_select_strategy(self,query:str)->str:"""自动选择策略"""analysis=self.analyze_query(query)ifanalysis["has_tech_term"]:return"synonym"# 专业术语用同义词elifanalysis["is_question"]:return"llm"# 问句用LLM改写elifanalysis["is_short"]:return"hyde"# 短Query用HyDE扩展else:return"llm"# 默认LLM改写# 使用示例rewriter=SmartQueryRewriter(llm,embedder)queries=rewriter.rewrite("Python性能优化")print(f"改写结果:{queries}")

七、总结

策略适用场景Recall提升耗时
同义词扩展专业领域+5%<20ms
LLM改写通用场景+8%300-400ms
HyDE追求最优+11%500-600ms
Query2Doc平衡效果和速度+9%200-300ms

Query改写是低成本高回报的优化手段,建议所有RAG系统都接入。

下篇预告:「多跳检索与知识图谱:让RAG突破单跳限制」——复杂问题需要多次检索,如何设计多跳检索架构?


需要完整Query改写代码的同学,可以看我主页的付费资源专栏。

有问题欢迎评论区留言,大家一起讨论!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/16 6:40:00

DigitalOcean用户脚本库实战:5步完成WordPress网站自动化安装

DigitalOcean用户脚本库实战&#xff1a;5步完成WordPress网站自动化安装 【免费下载链接】do_user_scripts 项目地址: https://gitcode.com/gh_mirrors/do/do_user_scripts 想要在DigitalOcean云服务器上快速部署WordPress网站吗&#xff1f;&#x1f914; 使用Digita…

作者头像 李华
网站建设 2026/6/16 6:31:51

SCD缓慢变化维度:数据工程师必须掌握的时空建模技能

1. 为什么SCD不是“选修课”&#xff0c;而是数据工程师的生存技能&#xff1f;在数据仓库这个行当里&#xff0c;我干了十二年&#xff0c;从最早用Oracle写PL/SQL脚本&#xff0c;到后来在AWS Redshift上跑Terraform&#xff0c;再到如今每天和Snowflake、dbt、Airflow打交道…

作者头像 李华
网站建设 2026/6/16 6:18:56

Hermes Agent 到 OpenClaw 智能体平台迁移全指南

1. 项目概述&#xff1a;这不是一次简单的工具替换&#xff0c;而是一场面向智能体工作流的系统性重构“从 OpenClaw 到 Hermes Agent&#xff0c;最全面的迁移指南”——这个标题里藏着三个关键信号&#xff1a;第一&#xff0c;“OpenClaw”和“Hermes Agent”不是同一家公司…

作者头像 李华
网站建设 2026/6/16 6:17:51

opus-mt-ru-en-openmind API参考手册:开发者必备的接口调用指南

opus-mt-ru-en-openmind API参考手册&#xff1a;开发者必备的接口调用指南 【免费下载链接】opus-mt-ru-en-openmind 项目地址: https://ai.gitcode.com/hf_mirrors/jeffding/opus-mt-ru-en-openmind 欢迎使用opus-mt-ru-en-openmind&#xff0c;这是一款基于OpenMind…

作者头像 李华