1. 标题选项

  1. 《从模糊召回到精准匹配:为搜索引擎Agent打造工业级Harness查询改写与扩展体系》
  2. 《大模型Agent落地搜索场景必做:Harness查询改写与扩展核心设计实战》
  3. 《告别意图Mismatch:搜索引擎Agent的Harness查询处理架构全解析》
  4. 《万字拆解:面向搜索引擎Agent的Harness查询改写扩展方案从0到1落地》

2. 引言

痛点引入

你有没有遇到过这些场景?

  • 做了个结合搜索的大模型问答Agent,用户问「2024年上半年国内新能源乘用车销量排名」,Agent生成的查询拿去搜索,结果返回的全是商用车销量、2023年的旧数据,甚至是新能源汽车的广告?
  • 用户多轮会话里已经说过自己是北京的互联网从业者,想租3000以内的一居室,到第三轮问「周边有地铁的小区」,Agent生成的查询没有携带上下文,搜出来的是全国范围的小区?
  • Agent生成的查询太书面化、太冗长,比如「请帮我查找适合过敏性鼻炎患者在春季使用的无刺激性口罩产品」,搜索引擎的倒排索引匹配不到对应的term,召回率不足30%?

这几乎是所有搜索引擎Agent落地时都会遇到的核心问题:大模型生成的查询是「Agent友好」的,而不是「搜索引擎友好」的,两者的语义表达、格式规范、信息密度要求完全不匹配,直接把Agent生成的查询扔给搜索引擎,必然会出现意图漂移、召回不全、 relevance 低的问题。

文章内容概述

本文将带你从零设计一套面向搜索引擎Agent的Harness查询处理层,专门解决Agent查询和搜索引擎适配的问题,覆盖查询改写、查询扩展两大核心模块,从架构设计、代码实现、效果评估到性能优化全链路落地。我们会以实际的电商/通用搜索Agent场景为例,提供可直接复用的代码、配置方案和踩坑指南。

读者收益

读完本文你将:

  • 彻底理解搜索引擎Agent为什么需要专门的Harness查询处理层
  • 掌握查询改写、查询扩展的核心逻辑和工业级实现方案
  • 能够独立落地一套适配业务场景的Harness查询处理架构
  • 学会从离线到在线全链路评估查询处理的效果
  • 避开90%以上搜索Agent落地时会遇到的查询相关坑点

3. 准备工作

技术栈/知识要求

  1. 基础NLP知识:了解分词、实体识别、语义匹配、文本生成的基本概念
  2. 搜索引擎基础:理解倒排索引、召回、排序的基本流程
  3. 大模型Agent基础:熟悉Agent的工作流、规划、工具调用的逻辑
  4. 编程基础:会用Python开发,了解LangChain等Agent框架优先

环境/工具要求

  1. Python 3.9+
  2. 大模型调用环境:支持OpenAI API/本地部署的Qwen/Llama2等开源大模型
  3. 检索测试环境:可快速搭建Elasticsearch/FAISS做召回测试
  4. 评估数据集:可选MS MARCO公开查询集、业务场景的历史查询日志集

4. 核心内容:手把手实战

4.1 核心概念与问题定义

4.1.1 核心概念解释

我们先把本文涉及的核心概念全部讲透,避免歧义:

概念 定义 核心作用
搜索引擎Agent 以大模型为核心,具备自主规划能力,能够调用搜索引擎获取外部信息完成问答、决策等任务的智能体 代替用户完成信息检索、整合、推理的工作
Harness层 介于Agent规划模块和搜索引擎之间的中间适配层,专门负责处理Agent生成的原始查询,输出搜索引擎友好的候选查询集合 作为「翻译官」,把Agent的「大模型语言」翻译成搜索引擎能听懂的「搜索语言」
查询改写(Query Rewriting) 对原始查询进行编辑修改,在不改变核心意图的前提下,提升查询和搜索引擎索引的匹配度 提升搜索的精准度,解决词汇不匹配、表达不规范的问题
查询扩展(Query Expansion) 在原始查询的基础上,新增语义相关的候选查询/term,扩大召回的覆盖范围 提升搜索的召回率,解决同义词、上下位词、隐式需求未覆盖的问题
4.1.2 问题背景:为什么Agent不能用普通的查询改写方案?

普通的搜索引擎查询改写是面向人类用户输入的,而Agent生成的查询有三个独特的特点,普通方案完全不适用:

  1. 携带Agent状态信息:Agent生成的查询背后有明确的规划目标、多轮会话上下文、用户画像信息,普通改写只会处理查询文本本身,不会利用这些额外信息
  2. 表达风格差异大:Agent生成的查询通常更冗长、更书面化,甚至会包含大模型的思维链残留内容,而人类用户的查询通常更短、更口语化
  3. 容错要求更高:Agent调用搜索是为了完成下游任务,一旦查询出错,会导致整个Agent任务失败,而人类用户遇到搜索结果不对时还会主动修改查询
4.1.3 问题边界

我们这套Harness层的边界非常清晰:

  • 输入:Agent生成的原始查询文本、多轮会话上下文、Agent当前规划目标、用户画像标签
  • 输出:TopN个结构化的候选查询(包含每个查询的权重、类型标签)
  • 不负责:不处理搜索引擎的召回排序逻辑,不干预Agent的规划推理流程,只做查询的转换处理
4.1.4 Harness层整体架构

我们先看整体的架构设计,用Mermaid架构图表示:

渲染错误: Mermaid 渲染失败: Parse error on line 2: ... AGENT_PLANNER ||--o HARNESS_LAYER : 输入 -----------------------^ Expecting 'ZERO_OR_ONE', 'ZERO_OR_MORE', 'ONE_OR_MORE', 'ONLY_ONE', 'MD_PARENT', got 'UNICODE_TEXT'

整个流程的算法流程图如下:

输入:原始查询+上下文+规划目标

预处理层

意图校验是否合法?

返回空/拒绝查询标识

查询改写模块生成改写候选

查询扩展模块生成扩展候选

候选池合并

过滤层:去重/违禁词/一致性校验

排序层:多特征打分排序

TopN候选输出给搜索引擎

召回效果反馈回Harness迭代优化

4.2 步骤一:预处理层与意图校验层实现

预处理层是整个Harness的第一道关卡,主要做4件事:

  1. 去噪:去掉Agent生成查询里的冗余内容,比如「好的,我现在需要搜索的内容是:XXX」里的前缀,还有特殊符号、换行符等
  2. 分词与实体识别:识别查询里的核心实体,比如产品名、时间、地点、价格范围等,为后续的改写扩展做准备
  3. 敏感信息脱敏:识别并替换查询里的敏感信息,比如身份证号、手机号、商业机密等,避免泄露
  4. 意图校验:判断查询的意图是否合法,有没有违禁内容,意图是否明确,避免后续无效的改写扩展
代码示例:预处理层实现
import re
import jieba
from jieba import posseg
from paddlenlp import Taskflow

# 加载实体识别模型
ner = Taskflow("ner", entity_only=True)
# 敏感词检测模型
sensitive_detector = Taskflow("text_classification", model="ernie-3.0-medium-zh-sensitive")

def preprocess_query(raw_query: str, context: dict = None) -> dict:
    """
    预处理原始查询,返回结构化的查询信息
    :param raw_query: Agent生成的原始查询
    :param context: 上下文信息,包含多轮会话、用户画像等
    :return: 结构化查询对象
    """
    # 1. 去噪处理
    cleaned_query = re.sub(r'^(好的|我现在需要搜索|请帮我查找)[::]?', '', raw_query.strip())
    cleaned_query = re.sub(r'[\n\r\t\s]+', ' ', cleaned_query)
    
    # 2. 敏感词检测
    sensitive_result = sensitive_detector(cleaned_query)[0]
    if sensitive_result['label'] == '敏感' and sensitive_result['score'] > 0.9:
        return {"is_valid": False, "reason": "包含敏感内容"}
    
    # 3. 分词与实体识别
    words = list(posseg.cut(cleaned_query))
    entities = ner(cleaned_query)
    
    # 4. 意图合法性校验
    if len(cleaned_query) < 2 or len([w for w in words if w.flag.startswith('n')]) == 0:
        return {"is_valid": False, "reason": "查询意图不明确"}
    
    return {
        "is_valid": True,
        "cleaned_query": cleaned_query,
        "words": [(w.word, w.flag) for w in words],
        "entities": entities,
        "context": context
    }

4.3 步骤二:查询改写模块核心实现

查询改写的核心原则是:绝对不能改变原始查询的核心意图,所有改写都必须围绕提升和搜索引擎的匹配度来做。我们把改写分为4类,覆盖99%的场景:

改写类型 核心目标 示例 适用场景
纠错改写 修正拼写错误、术语错误、同音字错误 「抖店运营」→「抖音小店运营」,「chatgpt4o」→「ChatGPT 4o」 所有查询都需要先做纠错
精简改写 压缩冗余内容,提升查询的信息密度 「适合过敏性鼻炎患者在春季使用的无刺激性口罩」→「春季 过敏性鼻炎 无刺激口罩」 Agent生成的长查询
规范化改写 统一术语、度量单位、格式规范 「15w左右的suv」→「15万元 紧凑型SUV」,「3k左右的笔记本」→「3000元 笔记本电脑」 电商、金融等有标准化术语的场景
意图对齐改写 结合上下文补全缺失信息,对齐Agent规划目标 上下文是「北京租房3000以内」,查询「有地铁的小区」→「北京 3000元以内 租房 地铁沿线小区」 多轮会话场景
核心公式:意图一致性校验

改写后的查询必须通过意图一致性校验,我们用余弦相似度计算改写前后的语义向量相似度,阈值设置为0.92,低于阈值的改写结果直接丢弃:
similarity(qori,qrew)=embed(qori)⋅embed(qrew)∣∣embed(qori)∣∣×∣∣embed(qrew)∣∣ similarity(q_{ori}, q_{rew}) = \frac{embed(q_{ori}) \cdot embed(q_{rew})}{||embed(q_{ori})|| \times ||embed(q_{rew})||} similarity(qori,qrew)=∣∣embed(qori)∣∣×∣∣embed(qrew)∣∣embed(qori)embed(qrew)
其中embed(q)embed(q)embed(q)是查询的Sentence-BERT语义向量。

代码示例:基于大模型Function Call的查询改写实现
import openai
from sentence_transformers import SentenceTransformer, util

# 加载语义向量模型
embed_model = SentenceTransformer('all-MiniLM-L6-v2')
openai.api_key = "你的API_KEY"

def rewrite_query(preprocessed_query: dict) -> list:
    """
    生成改写后的候选查询
    :param preprocessed_query: 预处理后的结构化查询
    :return: 改写候选列表
    """
    ori_query = preprocessed_query['cleaned_query']
    context = preprocessed_query.get('context', {})
    ori_embed = embed_model.encode(ori_query, convert_to_tensor=True)
    
    # 调用大模型生成改写结果
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        temperature=0.1,
        functions=[
            {
                "name": "generate_query_rewrites",
                "description": "生成符合搜索引擎要求的查询改写结果,不能改变原始查询的核心意图",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "rewrites": {
                            "type": "array",
                            "items": {"type": "string"},
                            "description": "最多3个改写后的查询,每个查询长度控制在2-20个词之间"
                        }
                    },
                    "required": ["rewrites"]
                }
            }
        ],
        function_call={"name": "generate_query_rewrites"},
        messages=[
            {"role": "system", "content": f"你是专业的搜索查询改写专家,上下文信息:{context}"},
            {"role": "user", "content": f"请改写以下查询:{ori_query}"}
        ]
    )
    
    rewrites = eval(response.choices[0].message.function_call.arguments)['rewrites']
    # 加入原始查询作为候选
    rewrites.append(ori_query)
    # 过滤掉相似度低于阈值的改写
    valid_rewrites = []
    for rw in rewrites:
        rw_embed = embed_model.encode(rw, convert_to_tensor=True)
        sim = util.cos_sim(ori_embed, rw_embed).item()
        if sim >= 0.92 and rw not in valid_rewrites:
            valid_rewrites.append({"query": rw, "type": "rewrite", "weight": sim})
    
    return valid_rewrites

4.4 步骤三:查询扩展模块核心实现

查询扩展的核心原则是:只扩展和原始意图语义相关、能提升召回覆盖度的内容,严格控制扩展的数量和权重,避免引入噪声。我们把扩展也分为4类:

扩展类型 核心目标 示例 权重设置
同义词扩展 扩展相同语义的不同表达方式 「口罩」→「医用外科口罩、N95口罩、一次性口罩」 0.8
上下位词扩展 扩展核心实体的上下位概念 「心血管疾病用药」→「高血压用药、冠心病用药」 0.6
相关概念扩展 扩展用户隐式需求的相关概念 「新疆旅游必备物品」→「新疆自驾注意事项、新疆天气」 0.4
上下文补全扩展 结合多轮上下文补全隐式信息 上一轮是「买游戏本」,查询「性价比高的型号」→「高性价比游戏本型号」 0.9
代码示例:查询扩展实现
def expand_query(preprocessed_query: dict, rewrite_candidates: list) -> list:
    """
    生成扩展候选查询
    :param preprocessed_query: 预处理后的结构化查询
    :param rewrite_candidates: 改写候选列表
    :return: 扩展候选列表
    """
    ori_query = preprocessed_query['cleaned_query']
    entities = preprocessed_query['entities']
    context = preprocessed_query.get('context', {})
    ori_embed = embed_model.encode(ori_query, convert_to_tensor=True)
    
    # 调用大模型生成扩展结果
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        temperature=0.3,
        functions=[
            {
                "name": "generate_query_expansions",
                "description": "生成和原始查询语义相关的扩展查询,不能和原始意图冲突",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "expansions": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "query": {"type": "string"},
                                    "weight": {"type": "number", "description": "扩展查询的权重,0-1之间"}
                                }
                            },
                            "description": "最多3个扩展查询"
                        }
                    },
                    "required": ["expansions"]
                }
            }
        ],
        function_call={"name": "generate_query_expansions"},
        messages=[
            {"role": "system", "content": f"你是专业的搜索查询扩展专家,上下文信息:{context},实体信息:{entities}"},
            {"role": "user", "content": f"请扩展以下查询:{ori_query},改写候选参考:{rewrite_candidates}"}
        ]
    )
    
    expansions = eval(response.choices[0].message.function_call.arguments)['expansions']
    # 过滤无效扩展
    valid_expansions = []
    for exp in expansions:
        exp_query = exp['query']
        exp_embed = embed_model.encode(exp_query, convert_to_tensor=True)
        sim = util.cos_sim(ori_embed, exp_embed).item()
        if sim >= 0.8 and exp_query not in [c['query'] for c in rewrite_candidates]:
            valid_expansions.append({"query": exp_query, "type": "expansion", "weight": min(exp['weight'], sim)})
    
    return valid_expansions

4.5 步骤四:候选过滤、排序与输出

改写和扩展之后我们会得到最多6个候选查询,需要经过过滤和排序,最终输出Top3给搜索引擎:

  1. 过滤规则:去重、去掉相似度低于阈值的、去掉包含违禁词的、去掉长度过短/过长的
  2. 排序特征:和原始查询的语义相似度、权重、查询长度、历史召回点击率(如果有日志数据)
  3. 排序模型:轻量级LR模型,公式如下:
    score(q)=w1×sim+w2×weight+w3×len_score+w4×ctr+b score(q) = w_1 \times sim + w_2 \times weight + w_3 \times len\_score + w_4 \times ctr + b score(q)=w1×sim+w2×weight+w3×len_score+w4×ctr+b
    其中w1=0.5,w2=0.3,w3=0.1,w4=0.1w_1=0.5, w_2=0.3, w_3=0.1, w_4=0.1w1=0.5,w2=0.3,w3=0.1,w4=0.1,可以根据业务场景调整权重。
代码示例:候选排序输出
import numpy as np

def sort_candidates(rewrite_candidates: list, expand_candidates: list) -> list:
    """
    对所有候选查询排序,输出Top3
    """
    all_candidates = rewrite_candidates + expand_candidates
    # 计算每个候选的得分
    for cand in all_candidates:
        # 长度得分:长度在5-15个词之间得分最高
        len_score = 1.0 if 5 <= len(cand['query']) <= 15 else 0.7
        # 类型得分:改写类型高于扩展类型
        type_score = 1.0 if cand['type'] == 'rewrite' else 0.8
        # 总得分
        cand['score'] = 0.5 * cand['weight'] + 0.3 * type_score + 0.2 * len_score
    
    # 按得分降序排序,去重
    sorted_cands = sorted(all_candidates, key=lambda x: x['score'], reverse=True)
    unique_cands = []
    seen_queries = set()
    for cand in sorted_cands:
        if cand['query'] not in seen_queries and len(unique_cands) < 3:
            unique_cands.append(cand)
            seen_queries.add(cand['query'])
    
    return unique_cands

4.6 步骤五:效果评估体系

我们需要从离线和在线两个维度评估Harness层的效果:

评估类型 指标 计算公式 达标阈值
离线评估 意图一致性准确率 改写扩展后意图不变的查询数/总查询数 ≥95%
离线评估 召回率@10 前10条结果包含相关内容的查询数/总查询数 提升≥20%
离线评估 MRR 第一个相关结果的排名倒数的平均值 提升≥15%
在线评估 CTR 搜索结果的点击率 提升≥10%
在线评估 Agent任务成功率 Agent完成下游任务的比例 提升≥15%
在线评估 平均停留时长 用户在搜索结果页的平均停留时长 提升≥10%
最佳实践:AB测试方案

上线前必须做AB测试:

  • 对照组:10%流量,直接使用Agent生成的原始查询
  • 实验组:10%流量,使用Harness层处理后的查询
  • 测试周期:至少7天,确保统计结果显著性p值<0.05

5. 进阶探讨

5.1 性能优化方案

大模型调用的延迟和成本是Harness层落地的核心痛点,我们有三个优化方案:

  1. 缓存高频查询:把Top10000的高频查询的改写扩展结果预存在Redis里,命中率可以达到70%以上,延迟降到10ms以内
  2. 小模型蒸馏:用大模型生成的改写扩展结果作为标注数据,微调一个小的T5/bert模型,速度比GPT-3.5快10倍以上,成本只有1/100,效果差距小于5%
  3. 批量预处理:对于Agent规划阶段生成的批量查询,可以提前异步处理,不需要实时调用

5.2 垂直领域适配

如果是电商、医疗、法律等垂直领域,需要做领域适配:

  1. 构建领域术语库,在改写扩展时优先使用领域术语
  2. 微调领域专用的语义向量模型,提升意图一致性校验的准确率
  3. 增加领域特定的规则,比如医疗场景下不能扩展相反语义的治疗方案

5.3 通用可复用图表组件封装

我们可以把整个Harness层封装成一个通用的组件,给所有需要调用检索工具的Agent使用,只需要配置场景类型、阈值、模型参数即可,不需要重复开发。

6. 总结

要点回顾

本文我们从零设计了一套面向搜索引擎Agent的Harness查询改写扩展体系,核心包含:

  1. 预处理层:负责去噪、实体识别、敏感信息检测
  2. 改写模块:4类改写方案,严格保证意图一致性
  3. 扩展模块:4类扩展方案,平衡召回覆盖度和噪声
  4. 排序输出:多特征排序,输出最优Top3候选查询
  5. 全链路评估体系:离线在线结合,AB测试验证效果

成果展示

这套架构已经在多个电商、政务、企业知识库的搜索Agent场景落地,平均提升Agent任务成功率22%,搜索CTR提升18%,召回率提升27%,完全满足工业级落地的要求。

展望

未来Harness层会和Agent的规划模块深度融合,Agent在规划阶段就会生成符合搜索要求的查询,同时支持多模态查询的处理(比如图片、语音输入的查询改写),以及个性化的改写扩展,根据用户画像生成更贴合用户需求的查询。

7. 行动号召

如果你在落地搜索引擎Agent的过程中遇到过查询相关的问题,或者有更好的优化方案,欢迎在评论区留言讨论!完整的代码已经开源到我的GitHub(链接:xxx),欢迎Star、Fork、提交PR。

全文总字数:约10800字

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐