AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

查询改写与多查询检索

我的学习 →

正在读取学习记录…

查询改写是把客户的说法整理成更适合资料库查找的词语,同时保留原意。多查询检索用几种表达分别查找,再合并候选。它适合口语、省略和资料术语不同的情况;把“退款”误改成“配送”会让后面的检索走错方向。

用退款资料观察改写

客户说“退钱几天到”。政策写“退款到账”,支付说明写“退回原支付渠道”。程序保留原问句,再按明确的同义短语规则生成两条查询,并把不同路径命中的同一文档合并。

固定资料P1讲退款到账,P2讲退回原支付渠道,P3讲退款申请,D1讲配送。每条资料用空格标出词项,查找时任一词项命中就进入候选。改写不会直接生成退款答案,只改变查找资料用的表达。

保存完整程序并运行

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l5-rewrite" -Force
cd "F:\学习\l5-rewrite"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

新建学习目录,例如 F:\学习\l5-rewrite。将下面代码完整保存为 rewrite_demo.py;代码包含固定资料和运行入口,保存后就能执行。

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l5-rewrite" -Force
cd "F:\学习\l5-rewrite"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

Python
"""保留原始意图,以固定短语规则生成查询并合并文档。"""
DOCUMENTS = {
    "P1": "退款 到账 三至五日",
    "P2": "退回 原支付渠道 三至五日",
    "P3": "退款 申请 订单编号",
    "D1": "配送 两天",
}

def rewrite(question):
    queries = [question.strip()]
    # 短语对应写在规则中;范围仍为退款到账,保留原问句。
    if "退钱" in question and "几天" in question:
        queries.extend(["退款 到账", "退回 原支付渠道"])
    return list(dict.fromkeys(query for query in queries if query))

def retrieve(queries, documents):
    hits = {}
    for query in queries:
        terms = set(query.split())
        for doc_id, text in documents.items():
            if terms & set(text.split()):
                # 按编号去重,同时保留命中路径供排查。
                hits.setdefault(doc_id, []).append(query)
    return sorted(hits.items())

def main():
    question = "退钱几天到"
    queries = rewrite(question)
    print("原问题:" + question)
    for number, query in enumerate(queries, 1):
        print(f"查询{number}:{query}")
    for doc_id, paths in retrieve(queries, DOCUMENTS):
        print(f"候选 {doc_id};命中路径:" + " / ".join(paths))

if __name__ == "__main__":
    main()

在保存文件的文件夹地址栏输入 powershell 并回车,再执行:

macOS:先在 Python 篇的学习文件夹中打开终端,执行 source .venv/bin/activate。再输入 cd ,把当前例子的文件夹拖入终端并回车,运行下面的命令。

Bash
cd "F:\学习\l5-rewrite"
ls
python -X utf8 rewrite_demo.py

使用其他目录时,把第一行换成自己的路径。dir 应能列出 rewrite_demo.py。终端输出如下:

TXT
原问题:退钱几天到
查询1:退钱几天到
查询2:退款 到账
查询3:退回 原支付渠道
候选 P1;命中路径:退款 到账
候选 P2;命中路径:退回 原支付渠道
候选 P3;命中路径:退款 到账

原问句没有空格切词,无法直接命中按空格分开的业务词项;“退款 到账”找到P1和P3,“退回 原支付渠道”补回P2。候选列表每个编号只出现一次,后面记录是哪条查询找到它。P3只匹配退款而非到账,需要在重排时进一步区分。

多条查询怎样保持一个问题

rewrite首先保存原问题,再检查明确短语是否满足条件。dict.fromkeys保持第一次出现的顺序并去重。原问题留在第一条路径,便于对照改写是否改变了商品、时间、订单号或用户限制;额外路径补齐资料里常用的业务表达。

retrieve按每条查询分别做词项匹配,用文档编号合并命中。同一篇资料被三种说法找到,仍是一份资料;保留命中路径可以查清是哪条改写扩大了候选范围。只合并结果并不保证排序合理,可进一步用RRF名次融合或统一重排。

模型改写可以处理更多口语,但也会补出客户没有说的事实。订单号和金额宜作为必须保留字段;“不要退回原卡”这样的否定条件必须留住;问题缺少订单状态时,先追问比猜测已发货更可靠。改写后的查询应与原问题一起记录,权限过滤仍基于当前身份。

多轮对话中的“那要多久”需要结合前文确认“那”指退款还是配送。对复杂问题可拆成“申请条件”和“到账时间”分别查找,再组合依据。HyDE通常译为“假设文档嵌入”:先生成一段假设答案,把它转换成向量辅助检索,再用实际找到的原文组织答案。

查询数增加会扩大召回,也增加延迟、费用与无关候选。固定最多生成几条、保留候选上限,按标注问题集检查多查询是否补回正确依据。当前规则是确定性短语替换,结果可逐项复查;学习型改写需要另做意图保持与质量检查。

原问句:退钱几天到;路径1:保留原问句;路径2:退款 到账 → P1、P3;路径3:退回 原支付渠道 → P2;合并编号:P1、P2、P3
保留原句、补业务表达、按文档编号合并结果。

改一处输入,核对机制变化

将 main 的问题改为 "配送几天到",运行 python -X utf8 rewrite_demo.py。只保留一条原问句,没有退款改写和候选;当前按空格切词的检索也不会自动把口语切成“配送”。恢复退钱几天到再重跑,三条查询和P1、P2、P3恢复。

运行配套检查

把下面测试保存为 test_rewrite_demo.py,与主程序放在同一目录。5项测试检查上面的实际处理结果,也覆盖意图保留、路径去重和无命中。

查看测试代码:test_rewrite_demo.py
Python
"""核对查询改写与多查询检索的正常结果与具体边界。"""
import unittest
import rewrite_demo as demo


class DemoTests(unittest.TestCase):
    def test_original_query_is_preserved(self):
        # 原问句保留,再补两种业务表达。
        self.assertEqual(demo.rewrite("退钱几天到"), ["退钱几天到", "退款 到账", "退回 原支付渠道"])

    def test_irrelevant_question_does_not_become_refund(self):
        # 配送问题不会被改成退款问题。
        self.assertEqual(demo.rewrite("配送几天到"), ["配送几天到"])

    def test_multi_query_deduplicates_document_ids(self):
        # 候选按编号去重,但保留两条命中路径。
        hits = demo.retrieve(["退款", "退款 到账"], demo.DOCUMENTS)
        self.assertEqual([key for key, _ in hits], ["P1", "P3"])
        self.assertEqual(len(dict(hits)["P1"]), 2)

    def test_rewrite_recovers_payment_channel_wording(self):
        # 改写确实补回原支付渠道资料。
        self.assertIn("P2", dict(demo.retrieve(demo.rewrite("退钱几天到"), demo.DOCUMENTS)))

    def test_empty_and_unmatched_query(self):
        # 空问题与无匹配问题不会编造候选。
        self.assertEqual(demo.rewrite("  "), [])
        self.assertEqual(demo.retrieve(["海外"], demo.DOCUMENTS), [])


if __name__ == "__main__":
    unittest.main()
Bash
python -X utf8 -m unittest -v test_rewrite_demo.py

测试名称后应显示 ok,末尾是 Ran 5 tests 和 OK。

运行时怎样定位问题

看到的情况检查与操作
找不到 rewrite_demo.py运行 dir,切换到保存主程序的目录
改写后出现用户没说的订单状态保留原问题并检查事实字段,缺失状态先追问
同一资料出现多次按稳定doc_id合并,并保留命中路径
多查询找回更多无关资料限制路径数量,过滤后统一重排

多查询补回候选后,重排继续区分哪份真正回答当前问题。

参考阅读:LlamaIndex 源码与检索流程。阅读时可以沿文档、节点、索引和检索入口对照这里的数据流。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“查询改写与多查询检索”,哪种做法更稳妥?

对应知识点:L5-rewrite

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L5-rewrite

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L5-rewrite

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L5-rewrite

下一篇:重排、过滤与上下文压缩。

图片查看器
100%

文章配图 · 按 Esc 关闭