为搜索引擎 Agent 设计 Harness 查询改写与扩展
为搜索引擎 Agent 设计 Harness 查询改写与扩展
1. 标题 (Title)
从零到一:构建智能搜索引擎 Agent 的查询改写与扩展系统Harness 查询改写:让你的搜索引擎 Agent 更懂用户意图深度解析:搜索引擎 Agent 中的查询扩展技术与实践提升搜索体验:设计高效的查询改写与扩展 Harness 框架
2. 引言 (Introduction)
痛点引入 (Hook)
你是否有过这样的经历:当你使用搜索引擎时,明明心里想找的是“如何在家做意大利面”,但输入的却是“意面做法”,结果搜索引擎返回了一堆不相关的意大利面品牌广告?或者你搜索“AI 框架”,但其实你想了解的是“2023 年最流行的深度学习框架对比”?
在构建智能搜索引擎 Agent(代理)时,我们面临的最大挑战之一就是:用户的查询往往是模糊、不完整或带有歧义的。直接将这些原始查询丢给搜索引擎,往往无法获得最佳结果。
文章内容概述 (What)
本文将带你深入探讨如何为搜索引擎 Agent 设计一套强大的 Harness(框架),专门用于查询改写(Query Rewriting)与查询扩展(Query Expansion)。我们将从理论基础出发,一步步构建一个实用的系统,包括同义词扩展、意图识别、错误纠正、上下文理解等核心功能。
读者收益 (Why)
读完本文,你将:
- 深刻理解查询改写与扩展在搜索引擎 Agent 中的核心作用
- 掌握设计 Harness 框架的方法论和最佳实践
- 学会实现常见的查询改写与扩展算法
- 具备将这些技术整合到实际项目中的能力
3. 准备工作 (Prerequisites)
在开始我们的旅程之前,请确保你已经具备以下条件:
技术栈/知识:
- 熟悉 Python 编程语言(我们将主要使用 Python 进行代码演示)
- 了解基本的自然语言处理(NLP)概念,如分词、词性标注
- 对搜索引擎的基本工作原理有一定了解(倒排索引、相关性排序等)
- 具备基本的算法和数据结构知识
环境/工具:
- 已安装 Python 3.8+
- 推荐使用虚拟环境(virtualenv 或 conda)
- 我们将使用的主要库:NLTK, spaCy, Transformers (Hugging Face), Elasticsearch (可选,用于演示搜索效果)
4. 核心内容:手把手实战 (Step-by-Step Tutorial)
步骤一:理解核心概念与架构设计
在我们开始写代码之前,让我们先建立一个清晰的概念框架。
核心概念
查询改写(Query Rewriting):指将用户的原始查询转换为另一种形式,以更准确地表达用户的意图,同时保持语义的一致性。例如,将“最好的手机 2023”改写为“2023 年顶级智能手机推荐”。
查询扩展(Query Extension):指在原始查询的基础上,添加相关的术语或短语,以提高召回率。例如,将“AI 框架”扩展为“AI 框架 深度学习 TensorFlow PyTorch”。
搜索引擎 Agent:一个智能代理系统,它能够接收用户的自然语言查询,理解用户意图,执行搜索操作,并将结果以友好的方式返回给用户。
问题背景
传统的搜索引擎通常依赖于关键词匹配。当用户的查询词与文档中的词不完全匹配时,搜索效果就会大打折扣。查询改写与扩展就是为了解决这一问题而诞生的技术。
系统架构设计
让我们设计一个 Harness 框架的整体架构:
这个架构图展示了从用户输入到最终结果返回的完整流程。我们的 Harness 框架将主要负责从“查询预处理”到“最终查询集合”这一部分。
步骤二:搭建基础环境与查询预处理
让我们开始动手实现。首先,我们需要搭建基础环境并实现查询预处理模块。
环境安装
# 创建虚拟环境
python -m venv query-harness-env
source query-harness-env/bin/activate # Windows 上使用 query-harness-env\Scripts\activate
# 安装必要的库
pip install nltk spacy transformers torch
python -m spacy download en_core_web_sm # 下载 spaCy 的英文模型
查询预处理模块
查询预处理是整个流程的第一步,它包括以下几个关键步骤:
- 文本清洗
- 分词
- 去除停用词
- 词形还原
让我们编写代码:
import nltk
import spacy
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
import string
# 下载必要的 NLTK 数据
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
class QueryPreprocessor:
def __init__(self, language='english'):
self.language = language
self.stop_words = set(stopwords.words(language))
self.lemmatizer = WordNetLemmatizer()
self.nlp = spacy.load('en_core_web_sm') # 使用 spaCy 做更高级的处理
def clean_text(self, text):
"""
清洗文本:去除特殊字符、多余空格等
"""
# 转换为小写
text = text.lower()
# 去除标点符号
text = text.translate(str.maketrans('', '', string.punctuation))
# 去除多余空格
text = ' '.join(text.split())
return text
def tokenize(self, text):
"""
分词
"""
return word_tokenize(text)
def remove_stopwords(self, tokens):
"""
去除停用词
"""
return [token for token in tokens if token not in self.stop_words]
def lemmatize(self, tokens):
"""
词形还原
"""
return [self.lemmatizer.lemmatize(token) for token in tokens]
def preprocess(self, query):
"""
完整的预处理流程
"""
# 1. 清洗文本
cleaned_query = self.clean_text(query)
# 2. 分词
tokens = self.tokenize(cleaned_query)
# 3. 去除停用词
filtered_tokens = self.remove_stopwords(tokens)
# 4. 词形还原
lemmatized_tokens = self.lemmatize(filtered_tokens)
return {
'original_query': query,
'cleaned_query': cleaned_query,
'tokens': tokens,
'filtered_tokens': filtered_tokens,
'lemmatized_tokens': lemmatized_tokens
}
# 测试预处理模块
preprocessor = QueryPreprocessor()
test_query = "What's the best way to learn Python programming for beginners?"
preprocessed_result = preprocessor.preprocess(test_query)
print("预处理结果:")
print(preprocessed_result)
这个预处理模块为我们后续的查询改写与扩展提供了干净、标准化的数据。
步骤三:实现查询扩展模块 - 同义词与相关词
查询扩展的一个常见方法是添加同义词和相关词。让我们实现这个功能。
核心概念:基于 WordNet 的同义词扩展
WordNet 是一个英语的词汇数据库,它将单词分组为同义词集(synsets),并记录了它们之间的语义关系。
from nltk.corpus import wordnet
class SynonymExpander:
def __init__(self):
pass
def get_synonyms(self, word):
"""
获取单词的同义词
"""
synonyms = set()
for syn in wordnet.synsets(word):
for lemma in syn.lemmas():
synonyms.add(lemma.name())
# 移除原单词
if word in synonyms:
synonyms.remove(word)
return list(synonyms)
def expand_query(self, preprocessed_data, max_synonyms_per_word=2):
"""
扩展查询:为每个词添加同义词
"""
original_tokens = preprocessed_data['lemmatized_tokens']
expanded_tokens = original_tokens.copy()
for token in original_tokens:
synonyms = self.get_synonyms(token)
# 只添加前 N 个同义词,避免过度扩展
expanded_tokens.extend(synonyms[:max_synonyms_per_word])
# 去重并重新组合成查询字符串
expanded_query = ' '.join(list(set(expanded_tokens)))
return {
'original_preprocessed': preprocessed_data,
'expanded_tokens': expanded_tokens,
'expanded_query': expanded_query
}
# 测试同义词扩展模块
synonym_expander = SynonymExpander()
expansion_result = synonym_expander.expand_query(preprocessed_result)
print("\n同义词扩展结果:")
print(expansion_result)
基于预训练语言模型的相关词扩展
除了 WordNet,我们还可以使用预训练语言模型(如 BERT)来找到语义上更相关的词。
from transformers import pipeline, AutoTokenizer, AutoModel
import torch
class ContextualRelatedWordExpander:
def __init__(self, model_name='bert-base-uncased'):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModel.from_pretrained(model_name)
self.fill_mask = pipeline('fill-mask', model=model_name)
self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
self.model.to(self.device)
def get_related_words(self, query, target_word, top_k=5):
"""
使用 Fill-Mask 任务获取上下文相关的词
"""
# 简单的策略:将目标词替换为 [MASK],让模型预测
masked_query = query.replace(target_word, self.tokenizer.mask_token, 1)
if masked_query == query: # 如果没找到目标词(可能已经被预处理了)
return []
predictions = self.fill_mask(masked_query, top_k=top_k)
related_words = [pred['token_str'] for pred in predictions]
return related_words
def expand_query_contextual(self, preprocessed_data, original_query):
"""
基于上下文的查询扩展
"""
original_tokens = preprocessed_data['lemmatized_tokens']
expanded_tokens = original_tokens.copy()
for token in original_tokens:
related_words = self.get_related_words(original_query, token)
expanded_tokens.extend(related_words)
# 去重并重新组合
expanded_query = ' '.join(list(set(expanded_tokens)))
return {
'original_preprocessed': preprocessed_data,
'contextual_expanded_tokens': expanded_tokens,
'contextual_expanded_query': expanded_query
}
# 测试基于上下文的扩展(注意:这可能需要一些时间下载模型)
print("\n正在加载预训练模型,这可能需要一些时间...")
contextual_expander = ContextualRelatedWordExpander()
contextual_result = contextual_expander.expand_query_contextual(preprocessed_result, test_query)
print("\n基于上下文的扩展结果:")
print(contextual_result)
步骤四:实现查询改写模块 - 意图识别与纠错
查询改写不仅仅是添加词,还包括理解用户意图并修正查询中的错误。
意图识别
意图识别是理解用户到底想做什么的关键步骤。
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
class IntentClassifier:
def __init__(self, model_name='navteca/intent-classification'):
# 注意:这里我们使用一个通用的意图分类模型,实际项目中可能需要微调
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
self.model.to(self.device)
# 定义一些常见的意图标签(实际应该根据模型的标签来)
self.intent_labels = [
"information_search", # 信息查询
"transactional_query", # 事务查询
"navigational_query", # 导航查询
"question_answering" # 问答
]
def classify_intent(self, query):
"""
对查询进行意图分类
"""
inputs = self.tokenizer(query, return_tensors="pt", truncation=True, padding=True).to(self.device)
with torch.no_grad():
outputs = self.model(**inputs)
probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
top_prob, top_class = torch.topk(probabilities, k=1)
# 注意:这里我们简化了,实际应该映射到模型正确的标签
# 为了演示,我们使用一个简单的启发式规则
intent = self._heuristic_intent_classification(query)
return {
'query': query,
'intent': intent,
'confidence': top_prob.item()
}
def _heuristic_intent_classification(self, query):
"""
启发式的意图分类,用于演示
"""
query_lower = query.lower()
# 导航查询:包含特定的品牌或网站名
navigational_keywords = ["facebook", "youtube", "twitter", "github", "amazon"]
if any(keyword in query_lower for keyword in navigational_keywords):
return "navigational_query"
# 事务查询:包含购买、预订等词汇
transactional_keywords = ["buy", "purchase", "order", "book", "reserve"]
if any(keyword in query_lower for keyword in transactional_keywords):
return "transactional_query"
# 问答查询:以疑问词开头
question_starters = ["what", "how", "why", "when", "where", "who", "which"]
if any(query_lower.startswith(starter) for starter in question_starters):
return "question_answering"
# 默认是信息查询
return "information_search"
# 测试意图分类
intent_classifier = IntentClassifier()
intent_result = intent_classifier.classify_intent(test_query)
print("\n意图识别结果:")
print(intent_result)
查询纠错
用户的查询中常常包含拼写错误,我们需要能够自动纠正这些错误。
import re
from collections import Counter
class QueryCorrector:
def __init__(self, vocabulary_path=None):
# 这里我们使用一个简单的词汇表,实际项目中可以使用更大的语料库
self.vocab = self._build_vocabulary()
def _build_vocabulary(self):
"""
构建一个简单的词汇表(实际中应该使用更大的语料)
"""
# 这里只是为了演示,实际项目可以加载一个大的文本文件
common_words = """
python programming learn code beginner best way what how why when where
machine learning artificial intelligence data science algorithm
"""
words = re.findall(r'\w+', common_words.lower())
return Counter(words)
def _edit_distance_one(self, word):
"""
计算编辑距离为1的所有可能字符串
"""
letters = 'abcdefghijklmnopqrstuvwxyz'
splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
deletes = [L + R[1:] for L, R in splits if R]
transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
inserts = [L + c + R for L, R in splits for c in letters]
return set(deletes + transposes + replaces + inserts)
def _edit_distance_two(self, word):
"""
计算编辑距离为2的所有可能字符串
"""
return set(e2 for e1 in self._edit_distance_one(word)
for e2 in self._edit_distance_one(e1))
def _known(self, words):
"""
从候选词中筛选出在词汇表中的词
"""
return set(w for w in words if w in self.vocab)
def correct_word(self, word):
"""
纠正单个单词
"""
candidates = self._known([word]) or self._known(self._edit_distance_one(word)) \
or self._known(self._edit_distance_two(word)) or [word]
return max(candidates, key=self.vocab.get)
def correct_query(self, query):
"""
纠正整个查询
"""
words = query.lower().split()
corrected_words = [self.correct_word(word) for word in words]
corrected_query = ' '.join(corrected_words)
return {
'original_query': query,
'corrected_query': corrected_query,
'changes_made': corrected_query != query.lower()
}
# 测试查询纠错
query_corrector = QueryCorrector()
# 假设用户输入了一个拼写错误的查询
misspelled_query = "What's the best way to lern Pythn programing?"
correction_result = query_corrector.correct_query(misspelled_query)
print("\n查询纠错结果:")
print(correction_result)
步骤五:整合 Harness 框架 - 候选生成与排序
现在,让我们将前面的模块整合起来,形成一个完整的 Harness 框架。
候选查询生成
class QueryCandidateGenerator:
def __init__(self):
self.preprocessor = QueryPreprocessor()
self.synonym_expander = SynonymExpander()
self.intent_classifier = IntentClassifier()
self.query_corrector = QueryCorrector()
# 注意:为了避免每次都加载大模型,这里我们选择性地初始化
self._contextual_expander = None
@property
def contextual_expander(self):
if self._contextual_expander is None:
print("延迟加载上下文扩展模型...")
self._contextual_expander = ContextualRelatedWordExpander()
return self._contextual_expander
def generate_candidates(self, original_query, use_contextual=False):
"""
生成候选查询集合
"""
candidates = []
# 1. 首先进行纠错
correction_result = self.query_corrector.correct_query(original_query)
base_query = correction_result['corrected_query']
candidates.append({
'query': base_query,
'type': 'corrected',
'score': 1.0
})
# 2. 预处理
preprocessed = self.preprocessor.preprocess(base_query)
# 3. 识别意图
intent_result = self.intent_classifier.classify_intent(base_query)
intent = intent_result['intent']
# 4. 根据意图生成不同的候选
# 4.1 同义词扩展
synonym_result = self.synonym_expander.expand_query(preprocessed)
candidates.append({
'query': synonym_result['expanded_query'],
'type': 'synonym_expanded',
'intent': intent,
'score': 0.9
})
# 4.2 上下文相关扩展(可选,因为比较慢)
if use_contextual:
contextual_result = self.contextual_expander.expand_query_contextual(preprocessed, base_query)
candidates.append({
'query': contextual_result['contextual_expanded_query'],
'type': 'contextual_expanded',
'intent': intent,
'score': 0.85
})
# 4.3 对于问答类型,可以添加一些常见的改写
if intent == 'question_answering':
# 例如:将 "What is..." 改写为 "Explain..."
if base_query.lower().startswith('what is'):
rephrased = 'explain ' + base_query[8:]
candidates.append({
'query': rephrased,
'type': 'rephrased_qa',
'intent': intent,
'score': 0.8
})
return {
'original_query': original_query,
'intent': intent,
'candidates': candidates
}
# 测试候选生成
candidate_generator = QueryCandidateGenerator()
candidates_result = candidate_generator.generate_candidates(test_query, use_contextual=False)
print("\n候选查询生成结果:")
for candidate in candidates_result['candidates']:
print(candidate)
查询排序与选择
生成了多个候选查询后,我们需要对它们进行排序,选择最有可能获得好结果的查询。
class QueryRanker:
def __init__(self):
# 这里可以加载一个训练好的排序模型
# 为了演示,我们使用基于规则的排序
pass
def rank_candidates(self, candidates_result):
"""
对候选查询进行排序
"""
candidates = candidates_result['candidates']
# 基于规则的排序:
# 1. 首先根据已有的 score 排序
# 2. 然后根据查询长度进行微调(避免过长或过短的查询)
def scoring_function(candidate):
base_score = candidate['score']
query_length = len(candidate['query'].split())
# 偏好长度在 3-8 个词之间的查询
length_penalty = 0
if query_length < 3:
length_penalty = -0.1 * (3 - query_length)
elif query_length > 8:
length_penalty = -0.05 * (query_length - 8)
return base_score + length_penalty
# 排序
ranked_candidates = sorted(
candidates,
key=scoring_function,
reverse=True
)
# 更新 score
for i, candidate in enumerate(ranked_candidates):
candidate['final_score'] = scoring_function(candidate)
candidate['rank'] = i + 1
return {
'original_query': candidates_result['original_query'],
'intent': candidates_result['intent'],
'ranked_candidates': ranked_candidates
}
def select_top_k(self, ranked_result, k=3):
"""
选择 Top K 个查询
"""
return ranked_result['ranked_candidates'][:k]
# 测试查询排序
query_ranker = QueryRanker()
ranked_result = query_ranker.rank_candidates(candidates_result)
top_k_result = query_ranker.select_top_k(ranked_result, k=2)
print("\n排序后的候选查询:")
for candidate in ranked_result['ranked_candidates']:
print(candidate)
print("\nTop K 查询:")
for candidate in top_k_result:
print(candidate)
步骤六:完整的 Harness 框架演示
现在,让我们将所有组件整合在一起,形成一个完整的工作流。
class SearchAgentHarness:
def __init__(self, use_contextual_expansion=False):
self.candidate_generator = QueryCandidateGenerator()
self.query_ranker = QueryRanker()
self.use_contextual = use_contextual_expansion
def process_query(self, user_query, top_k=3):
"""
完整的查询处理流程
"""
print(f"正在处理查询: {user_query}")
# 1. 生成候选查询
print("步骤 1: 生成候选查询...")
candidates_result = self.candidate_generator.generate_candidates(
user_query,
use_contextual=self.use_contextual
)
# 2. 排序候选查询
print("步骤 2: 排序候选查询...")
ranked_result = self.query_ranker.rank_candidates(candidates_result)
# 3. 选择 Top K
print(f"步骤 3: 选择 Top {top_k} 查询...")
final_queries = self.query_ranker.select_top_k(ranked_result, k=top_k)
# 4. 这里可以添加调用搜索引擎的代码
# 为了演示,我们只返回处理后的查询
print("处理完成!")
return {
'original_query': user_query,
'detected_intent': ranked_result['intent'],
'final_queries': final_queries,
'all_candidates': ranked_result['ranked_candidates']
}
# 演示完整的 Harness 框架
print("=" * 50)
print("搜索引擎 Agent Harness 框架演示")
print("=" * 50)
# 初始化 Harness
harness = SearchAgentHarness(use_contextual_expansion=False)
# 测试几个不同的查询
test_queries = [
"What's the best way to learn Python programming for beginners?",
"how to buy a laptop online",
"lern machin learnig",
"Facebook login page"
]
for query in test_queries:
print(f"\n{'=' * 50}")
result = harness.process_query(query, top_k=2)
print(f"\n最终查询结果 (意图: {result['detected_intent']}):")
for i, final_query in enumerate(result['final_queries'], 1):
print(f"{i}. {final_query['query']} (类型: {final_query['type']}, 分数: {final_query['final_score']:.2f})")
5. 进阶探讨 (Advanced Topics)
如何创建混合改写策略?
在实际项目中,我们通常不会只依赖一种改写策略,而是会混合使用多种方法。
class HybridQueryRewriter:
def __init__(self):
# 可以组合多种改写器
self.rewriters = []
def add_rewriter(self, rewriter):
self.rewriters.append(rewriter)
def rewrite(self, query):
"""
依次应用所有改写器
"""
result = query
for rewriter in self.rewriters:
result = rewriter.rewrite(result)
return result
性能优化:当数据量很大时该怎么办?
当处理大量查询或大规模扩展时,我们需要考虑性能优化:
- 缓存策略:缓存常见查询的改写结果
- 异步处理:对于耗时的上下文扩展,可以使用异步处理
- 模型量化:对预训练模型进行量化,减少内存使用和计算时间
- 批量处理:如果有多个查询需要处理,可以批量处理
from functools import lru_cache
class CachedQueryPreprocessor(QueryPreprocessor):
@lru_cache(maxsize=1000)
def preprocess(self, query):
# 使用缓存装饰器
return super().preprocess(query)
如何封装一个通用的、可复用的图表组件?
(虽然这与查询改写不太相关,但我们可以类比:如何封装一个通用的查询改写组件?)
from abc import ABC, abstractmethod
class BaseQueryRewriter(ABC):
@abstractmethod
def rewrite(self, query):
pass
def __call__(self, query):
return self.rewrite(query)
# 然后所有具体的改写器都继承这个基类
6. 总结 (Conclusion)
回顾要点
在本文中,我们深入探讨了如何为搜索引擎 Agent 设计一个 Harness 查询改写与扩展框架:
- 架构设计:我们设计了一个从预处理到候选生成再到排序的完整流程
- 查询预处理:实现了文本清洗、分词、去停用词和词形还原
- 查询扩展:实现了基于 WordNet 的同义词扩展和基于预训练模型的上下文扩展
- 查询改写:实现了意图识别和查询纠错功能
- 整合框架:将所有组件整合为一个完整的 Harness 框架
成果展示
通过本文的学习,我们成功构建了一个功能完善的查询改写与扩展系统,它能够:
- 理解用户的查询意图
- 纠正查询中的拼写错误
- 扩展查询以提高召回率
- 生成多个候选查询并进行排序
鼓励与展望
本文介绍的只是查询改写与扩展领域的冰山一角。在实际项目中,你还可以:
- 使用更大规模的预训练模型(如 GPT 系列)进行更智能的改写
- 结合用户历史搜索记录,实现个性化的查询改写
- 利用强化学习,根据用户点击反馈持续优化改写策略
7. 行动号召 (Call to Action)
互动邀请: 如果你在实践中遇到任何问题,或者有更好的查询改写与扩展技巧,欢迎在评论区留言讨论!
实践建议: 尝试将本文中的代码整合到你自己的项目中,并根据实际需求进行调整和优化。你可以从一个简单的搜索功能开始,逐步增加更复杂的改写策略。
记住,构建一个优秀的搜索引擎 Agent 是一个迭代的过程,需要不断地实验和优化。祝你在这条路上取得成功!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)