为搜索引擎 Agent 设计 Harness 查询改写与扩展
1. 标题选项
- 《从模糊召回到精准匹配:为搜索引擎Agent打造工业级Harness查询改写与扩展体系》
- 《大模型Agent落地搜索场景必做:Harness查询改写与扩展核心设计实战》
- 《告别意图Mismatch:搜索引擎Agent的Harness查询处理架构全解析》
- 《万字拆解:面向搜索引擎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. 准备工作
技术栈/知识要求
- 基础NLP知识:了解分词、实体识别、语义匹配、文本生成的基本概念
- 搜索引擎基础:理解倒排索引、召回、排序的基本流程
- 大模型Agent基础:熟悉Agent的工作流、规划、工具调用的逻辑
- 编程基础:会用Python开发,了解LangChain等Agent框架优先
环境/工具要求
- Python 3.9+
- 大模型调用环境:支持OpenAI API/本地部署的Qwen/Llama2等开源大模型
- 检索测试环境:可快速搭建Elasticsearch/FAISS做召回测试
- 评估数据集:可选MS MARCO公开查询集、业务场景的历史查询日志集
4. 核心内容:手把手实战
4.1 核心概念与问题定义
4.1.1 核心概念解释
我们先把本文涉及的核心概念全部讲透,避免歧义:
| 概念 | 定义 | 核心作用 |
|---|---|---|
| 搜索引擎Agent | 以大模型为核心,具备自主规划能力,能够调用搜索引擎获取外部信息完成问答、决策等任务的智能体 | 代替用户完成信息检索、整合、推理的工作 |
| Harness层 | 介于Agent规划模块和搜索引擎之间的中间适配层,专门负责处理Agent生成的原始查询,输出搜索引擎友好的候选查询集合 | 作为「翻译官」,把Agent的「大模型语言」翻译成搜索引擎能听懂的「搜索语言」 |
| 查询改写(Query Rewriting) | 对原始查询进行编辑修改,在不改变核心意图的前提下,提升查询和搜索引擎索引的匹配度 | 提升搜索的精准度,解决词汇不匹配、表达不规范的问题 |
| 查询扩展(Query Expansion) | 在原始查询的基础上,新增语义相关的候选查询/term,扩大召回的覆盖范围 | 提升搜索的召回率,解决同义词、上下位词、隐式需求未覆盖的问题 |
4.1.2 问题背景:为什么Agent不能用普通的查询改写方案?
普通的搜索引擎查询改写是面向人类用户输入的,而Agent生成的查询有三个独特的特点,普通方案完全不适用:
- 携带Agent状态信息:Agent生成的查询背后有明确的规划目标、多轮会话上下文、用户画像信息,普通改写只会处理查询文本本身,不会利用这些额外信息
- 表达风格差异大:Agent生成的查询通常更冗长、更书面化,甚至会包含大模型的思维链残留内容,而人类用户的查询通常更短、更口语化
- 容错要求更高:Agent调用搜索是为了完成下游任务,一旦查询出错,会导致整个Agent任务失败,而人类用户遇到搜索结果不对时还会主动修改查询
4.1.3 问题边界
我们这套Harness层的边界非常清晰:
- 输入:Agent生成的原始查询文本、多轮会话上下文、Agent当前规划目标、用户画像标签
- 输出:TopN个结构化的候选查询(包含每个查询的权重、类型标签)
- 不负责:不处理搜索引擎的召回排序逻辑,不干预Agent的规划推理流程,只做查询的转换处理
4.1.4 Harness层整体架构
我们先看整体的架构设计,用Mermaid架构图表示:
整个流程的算法流程图如下:
4.2 步骤一:预处理层与意图校验层实现
预处理层是整个Harness的第一道关卡,主要做4件事:
- 去噪:去掉Agent生成查询里的冗余内容,比如「好的,我现在需要搜索的内容是:XXX」里的前缀,还有特殊符号、换行符等
- 分词与实体识别:识别查询里的核心实体,比如产品名、时间、地点、价格范围等,为后续的改写扩展做准备
- 敏感信息脱敏:识别并替换查询里的敏感信息,比如身份证号、手机号、商业机密等,避免泄露
- 意图校验:判断查询的意图是否合法,有没有违禁内容,意图是否明确,避免后续无效的改写扩展
代码示例:预处理层实现
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给搜索引擎:
- 过滤规则:去重、去掉相似度低于阈值的、去掉包含违禁词的、去掉长度过短/过长的
- 排序特征:和原始查询的语义相似度、权重、查询长度、历史召回点击率(如果有日志数据)
- 排序模型:轻量级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层落地的核心痛点,我们有三个优化方案:
- 缓存高频查询:把Top10000的高频查询的改写扩展结果预存在Redis里,命中率可以达到70%以上,延迟降到10ms以内
- 小模型蒸馏:用大模型生成的改写扩展结果作为标注数据,微调一个小的T5/bert模型,速度比GPT-3.5快10倍以上,成本只有1/100,效果差距小于5%
- 批量预处理:对于Agent规划阶段生成的批量查询,可以提前异步处理,不需要实时调用
5.2 垂直领域适配
如果是电商、医疗、法律等垂直领域,需要做领域适配:
- 构建领域术语库,在改写扩展时优先使用领域术语
- 微调领域专用的语义向量模型,提升意图一致性校验的准确率
- 增加领域特定的规则,比如医疗场景下不能扩展相反语义的治疗方案
5.3 通用可复用图表组件封装
我们可以把整个Harness层封装成一个通用的组件,给所有需要调用检索工具的Agent使用,只需要配置场景类型、阈值、模型参数即可,不需要重复开发。
6. 总结
要点回顾
本文我们从零设计了一套面向搜索引擎Agent的Harness查询改写扩展体系,核心包含:
- 预处理层:负责去噪、实体识别、敏感信息检测
- 改写模块:4类改写方案,严格保证意图一致性
- 扩展模块:4类扩展方案,平衡召回覆盖度和噪声
- 排序输出:多特征排序,输出最优Top3候选查询
- 全链路评估体系:离线在线结合,AB测试验证效果
成果展示
这套架构已经在多个电商、政务、企业知识库的搜索Agent场景落地,平均提升Agent任务成功率22%,搜索CTR提升18%,召回率提升27%,完全满足工业级落地的要求。
展望
未来Harness层会和Agent的规划模块深度融合,Agent在规划阶段就会生成符合搜索要求的查询,同时支持多模态查询的处理(比如图片、语音输入的查询改写),以及个性化的改写扩展,根据用户画像生成更贴合用户需求的查询。
7. 行动号召
如果你在落地搜索引擎Agent的过程中遇到过查询相关的问题,或者有更好的优化方案,欢迎在评论区留言讨论!完整的代码已经开源到我的GitHub(链接:xxx),欢迎Star、Fork、提交PR。
全文总字数:约10800字
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)