
多跳问题需要组合多个文档片段才能回答,直接用原问题做向量检索容易漏掉关键子信息。查询分解先把复杂问题拆成若干子查询,分别检索后再合并,召回完整度明显提升。
向量检索比较查询向量与文档向量的相似度。问题"对比 A 和 B 的定价并给出选型建议"包含三个子意图:A 定价、B 定价、选型维度。单一向量只能逼近其中一个,其余子信息可能落在不同段落,直接检索会丢。
python
# 依赖:pip install sentence-transformers==2.7.0 openai==1.30.0 numpy==1.26.4
import os
import numpy as np
from sentence_transformers import SentenceTransformer
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
embedder = SentenceTransformer("BAAI/bge-small-zh")
docs = [
"产品A定价每月 299 元,按年付八折。",
"产品B定价每月 199 元,无年付折扣。",
"选型建议:预算紧选 B,要稳定性选 A。",
"产品A支持私有化部署,产品B仅SaaS。",
]
doc_vecs = embedder.encode(docs, normalize_embeddings=True)
def decompose(query: str):
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user",
"content": f"把下面问题拆成2~3个子查询,每行一个:{query}"}],
)
return [line.strip("- ").strip()
for line in resp.choices[0].message.content.splitlines()
if line.strip()]
def retrieve(sub_q: str, k: int = 2):
qv = embedder.encode([sub_q], normalize_embeddings=True)
scores = doc_vecs @ qv[0]
return [docs[i] for i in np.argsort(-scores)[:k]]
if __name__ == "__main__":
subs = decompose("对比产品A和B的定价并给选型建议")
for s in subs:
print("子查询:", s)
print("召回:", retrieve(s))python
子查询: 产品A的定价是多少
召回: ['产品A定价每月 299 元,按年付八折。', '产品A支持私有化部署,产品B仅SaaS。']
子查询: 产品B的定价是多少
召回: ['产品B定价每月 199 元,无年付折扣。', '选型建议:预算紧选 B,要稳定性选 A。']
子查询: 如何根据定价做选型
召回: ['选型建议:预算紧选 B,要稳定性选 A。', '产品A支持私有化部署,产品B仅SaaS。']原查询直接检索可能只召回到"选型建议"一条;分解后三个子查询分别召回到 A 定价、B 定价、选型维度,合并即得完整证据链。
查询分解对单跳事实类问题增益有限,主要价值在多跳、对比、综述类问题。线上 serving 时,分解会增加一次大模型调用(约 300~800ms 额外延迟),可通过缓存高频问题的子查询模板缓解。落地前建议在固定多跳样本上对比原检索与分解检索的 context_recall 指标,确认提升后再接入。
将查询分解接入检索链路,RAG 系统可从"单向量匹配"演进为"多子问题并行召回",多跳问答的完整性上限随之提升。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。