795 lines
21 KiB
Markdown
795 lines
21 KiB
Markdown
# 检索优化
|
||
|
||
---
|
||
|
||
## 优化技术概览
|
||
|
||
| 技术 | 影响 | 复杂度 | 使用时机 |
|
||
|-----------|--------|------------|-------------|
|
||
| **混合搜索** | 高 | 中 | 生产环境始终启用 |
|
||
| **重排序** | 高 | 低 | Top-k 精排 |
|
||
| **查询扩展** | 中 | 中 | 模糊查询 |
|
||
| **HyDE** | 中-高 | 中 | 概念密集型检索 |
|
||
| **元数据过滤** | 高 | 低 | 多租户、分类场景 |
|
||
| **查询分解** | 中 | 高 | 复杂问题 |
|
||
| **上下文压缩** | 中 | 中 | 长检索片段 |
|
||
|
||
---
|
||
|
||
## 混合搜索(向量 + 关键词)
|
||
|
||
### 倒数排序融合(RRF)
|
||
|
||
```python
|
||
from dataclasses import dataclass
|
||
from typing import Callable
|
||
|
||
@dataclass
|
||
class SearchResult:
|
||
id: str
|
||
text: str
|
||
score: float
|
||
source: str # "vector" or "keyword"
|
||
|
||
def reciprocal_rank_fusion(
|
||
vector_results: list[SearchResult],
|
||
keyword_results: list[SearchResult],
|
||
k: int = 60,
|
||
vector_weight: float = 0.5
|
||
) -> list[SearchResult]:
|
||
"""
|
||
使用 RRF 融合向量和关键词结果。
|
||
k 是用于降低高排名影响的常数(通常为 60)。
|
||
"""
|
||
scores: dict[str, float] = {}
|
||
docs: dict[str, SearchResult] = {}
|
||
|
||
# 对向量结果打分
|
||
for rank, result in enumerate(vector_results, 1):
|
||
rrf_score = vector_weight * (1 / (k + rank))
|
||
scores[result.id] = scores.get(result.id, 0) + rrf_score
|
||
docs[result.id] = result
|
||
|
||
# 对关键词结果打分
|
||
keyword_weight = 1 - vector_weight
|
||
for rank, result in enumerate(keyword_results, 1):
|
||
rrf_score = keyword_weight * (1 / (k + rank))
|
||
scores[result.id] = scores.get(result.id, 0) + rrf_score
|
||
if result.id not in docs:
|
||
docs[result.id] = result
|
||
|
||
# 按融合得分排序
|
||
sorted_ids = sorted(scores.keys(), key=lambda x: scores[x], reverse=True)
|
||
|
||
return [
|
||
SearchResult(
|
||
id=doc_id,
|
||
text=docs[doc_id].text,
|
||
score=scores[doc_id],
|
||
source="hybrid"
|
||
)
|
||
for doc_id in sorted_ids
|
||
]
|
||
|
||
# 使用示例
|
||
hybrid_results = reciprocal_rank_fusion(
|
||
vector_results=vector_search(query_embedding, top_k=20),
|
||
keyword_results=bm25_search(query_text, top_k=20),
|
||
vector_weight=0.6 # 偏向语义相似度
|
||
)
|
||
```
|
||
|
||
### BM25 + 向量(Weaviate 实现)
|
||
|
||
```python
|
||
from weaviate.classes.query import HybridFusion
|
||
|
||
collection = client.collections.get("Documents")
|
||
|
||
# 可配置融合方式的混合搜索
|
||
results = collection.query.hybrid(
|
||
query="how to configure authentication",
|
||
alpha=0.5, # 0 = 纯 BM25,1 = 纯向量
|
||
fusion_type=HybridFusion.RELATIVE_SCORE, # 或 RANKED
|
||
limit=10,
|
||
return_metadata=["score", "explain_score"]
|
||
)
|
||
|
||
# 遍历结果
|
||
for obj in results.objects:
|
||
print(f"Score: {obj.metadata.score}")
|
||
print(f"Explanation: {obj.metadata.explain_score}")
|
||
print(f"Text: {obj.properties['content'][:200]}")
|
||
```
|
||
|
||
### Pinecone 稀疏-稠密混合
|
||
|
||
```python
|
||
from pinecone_text.sparse import BM25Encoder
|
||
|
||
# 在语料上训练 BM25 编码器
|
||
bm25 = BM25Encoder()
|
||
bm25.fit(corpus_documents)
|
||
|
||
# 为混合搜索编码查询
|
||
sparse_vector = bm25.encode_queries(query_text)
|
||
dense_vector = get_embedding(query_text)
|
||
|
||
# 使用两个向量进行搜索
|
||
results = index.query(
|
||
vector=dense_vector,
|
||
sparse_vector=sparse_vector,
|
||
top_k=10,
|
||
include_metadata=True
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 重排序
|
||
|
||
### Cohere Rerank
|
||
|
||
```python
|
||
import cohere
|
||
|
||
co = cohere.Client(api_key="your-api-key")
|
||
|
||
def rerank_results(
|
||
query: str,
|
||
documents: list[str],
|
||
top_n: int = 5,
|
||
model: str = "rerank-english-v3.0"
|
||
) -> list[dict]:
|
||
"""使用 Cohere 对文档进行重排序。"""
|
||
response = co.rerank(
|
||
query=query,
|
||
documents=documents,
|
||
top_n=top_n,
|
||
model=model,
|
||
return_documents=True
|
||
)
|
||
|
||
return [
|
||
{
|
||
"text": result.document.text,
|
||
"relevance_score": result.relevance_score,
|
||
"original_index": result.index
|
||
}
|
||
for result in response.results
|
||
]
|
||
|
||
# 流程:先多检索,再少重排
|
||
initial_results = vector_search(query_embedding, top_k=50)
|
||
documents = [r.text for r in initial_results]
|
||
|
||
reranked = rerank_results(
|
||
query="how to configure OAuth2 authentication",
|
||
documents=documents,
|
||
top_n=5
|
||
)
|
||
|
||
# 使用重排后的前 5 条文档作为 LLM 上下文
|
||
context = "\n\n".join([r["text"] for r in reranked])
|
||
```
|
||
|
||
### Cross-Encoder 重排序(开源方案)
|
||
|
||
```python
|
||
from sentence_transformers import CrossEncoder
|
||
|
||
class Reranker:
|
||
"""使用 cross-encoder 模型进行重排序。"""
|
||
|
||
def __init__(self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2"):
|
||
self.model = CrossEncoder(model_name)
|
||
|
||
def rerank(
|
||
self,
|
||
query: str,
|
||
documents: list[str],
|
||
top_k: int = 5
|
||
) -> list[tuple[str, float]]:
|
||
"""根据与查询的相关性对文档进行重排序。"""
|
||
# 创建查询-文档对
|
||
pairs = [[query, doc] for doc in documents]
|
||
|
||
# 获取相关性得分
|
||
scores = self.model.predict(pairs)
|
||
|
||
# 按得分排序
|
||
doc_scores = list(zip(documents, scores))
|
||
doc_scores.sort(key=lambda x: x[1], reverse=True)
|
||
|
||
return doc_scores[:top_k]
|
||
|
||
# 使用示例
|
||
reranker = Reranker()
|
||
top_docs = reranker.rerank(
|
||
query="OAuth2 setup guide",
|
||
documents=retrieved_documents,
|
||
top_k=5
|
||
)
|
||
```
|
||
|
||
### ColBERT 风格延迟交互
|
||
|
||
```python
|
||
from colbert import Searcher
|
||
from colbert.infra import Run, RunConfig
|
||
|
||
# 设置 ColBERT 索引(一次性)
|
||
with Run().context(RunConfig(nranks=1)):
|
||
searcher = Searcher(index="path/to/colbert_index")
|
||
|
||
# 使用延迟交互评分进行搜索
|
||
results = searcher.search(
|
||
query="how to configure authentication",
|
||
k=10
|
||
)
|
||
|
||
# 结果包含 token 级别的匹配得分
|
||
for passage_id, rank, score in zip(*results):
|
||
print(f"Rank {rank}: Doc {passage_id}, Score: {score}")
|
||
```
|
||
|
||
---
|
||
|
||
## 查询扩展
|
||
|
||
### 基于 LLM 的查询扩展
|
||
|
||
```python
|
||
from openai import OpenAI
|
||
|
||
client = OpenAI()
|
||
|
||
def expand_query(query: str, num_expansions: int = 3) -> list[str]:
|
||
"""使用 LLM 生成查询变体。"""
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": f"""生成 {num_expansions} 个替代搜索查询,
|
||
这些查询应有助于找到用户问题的相关文档。
|
||
包括:
|
||
- 同义词变体
|
||
- 更具体的版本
|
||
- 更通用的版本
|
||
以 JSON 字符串数组格式返回。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": query
|
||
}
|
||
],
|
||
response_format={"type": "json_object"}
|
||
)
|
||
|
||
import json
|
||
result = json.loads(response.choices[0].message.content)
|
||
return [query] + result.get("queries", [])
|
||
|
||
# 使用示例
|
||
original_query = "how to fix memory leak"
|
||
expanded_queries = expand_query(original_query)
|
||
# ["how to fix memory leak", "debug memory issues", "memory leak detection",
|
||
# "troubleshoot high memory usage"]
|
||
|
||
# 使用所有查询进行搜索并合并结果
|
||
all_results = []
|
||
for q in expanded_queries:
|
||
results = vector_search(get_embedding(q), top_k=10)
|
||
all_results.extend(results)
|
||
|
||
# 去重并按出现频率排序
|
||
deduped = deduplicate_by_id(all_results)
|
||
```
|
||
|
||
### 查询改写
|
||
|
||
```python
|
||
def rewrite_query_for_retrieval(
|
||
conversational_query: str,
|
||
chat_history: list[dict]
|
||
) -> str:
|
||
"""将会话查询改写为独立的搜索查询。"""
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": """将用户的问题改写为独立的搜索查询。
|
||
包含聊天历史中的相关上下文。
|
||
只输出改写后的查询,不附带其他内容。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": f"""聊天历史:
|
||
{format_chat_history(chat_history)}
|
||
|
||
用户的问题:{conversational_query}
|
||
|
||
改写后的搜索查询:"""
|
||
}
|
||
],
|
||
max_tokens=100
|
||
)
|
||
|
||
return response.choices[0].message.content.strip()
|
||
|
||
# 示例
|
||
history = [
|
||
{"role": "user", "content": "Tell me about Python web frameworks"},
|
||
{"role": "assistant", "content": "Popular Python web frameworks include Django, Flask, and FastAPI..."}
|
||
]
|
||
query = "Which one is best for APIs?"
|
||
|
||
rewritten = rewrite_query_for_retrieval(query, history)
|
||
# 输出:"Best Python web framework for building REST APIs: Django vs Flask vs FastAPI"
|
||
```
|
||
|
||
---
|
||
|
||
## HyDE(假设性文档嵌入)
|
||
|
||
```python
|
||
def hyde_search(
|
||
query: str,
|
||
vector_store,
|
||
embedding_model,
|
||
top_k: int = 10
|
||
) -> list[SearchResult]:
|
||
"""
|
||
生成假设性答案,对其进行嵌入,然后进行搜索。
|
||
使查询嵌入空间与文档嵌入空间对齐。
|
||
"""
|
||
# 生成假设性文档
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": """撰写一段能够回答用户问题的短文。
|
||
以专业文档作者的身份来写。
|
||
内容需具体且偏技术性,约 100-200 词。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": query
|
||
}
|
||
],
|
||
max_tokens=300
|
||
)
|
||
|
||
hypothetical_doc = response.choices[0].message.content
|
||
|
||
# 嵌入假设性文档
|
||
hyde_embedding = embedding_model.encode(hypothetical_doc)
|
||
|
||
# 使用假设性文档的嵌入进行搜索
|
||
results = vector_store.search(
|
||
vector=hyde_embedding,
|
||
top_k=top_k
|
||
)
|
||
|
||
return results
|
||
|
||
# 使用示例
|
||
results = hyde_search(
|
||
query="How do I handle rate limiting in my API?",
|
||
vector_store=qdrant_client,
|
||
embedding_model=sentence_transformer
|
||
)
|
||
```
|
||
|
||
### Multi-HyDE(多视角生成)
|
||
|
||
```python
|
||
def multi_hyde_search(
|
||
query: str,
|
||
vector_store,
|
||
embedding_model,
|
||
num_hypotheticals: int = 3,
|
||
top_k: int = 10
|
||
) -> list[SearchResult]:
|
||
"""生成多个假设性文档,实现多样化检索。"""
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": f"""生成 {num_hypotheticals} 篇不同的段落,
|
||
从不同角度回答该问题:
|
||
1. 技术深度剖析
|
||
2. 初学者友好解释
|
||
3. 最佳实践总结
|
||
|
||
以 JSON 格式返回,包含 "passages" 数组。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": query
|
||
}
|
||
],
|
||
response_format={"type": "json_object"}
|
||
)
|
||
|
||
import json
|
||
passages = json.loads(response.choices[0].message.content)["passages"]
|
||
|
||
# 嵌入所有假设性文档
|
||
all_results = []
|
||
for passage in passages:
|
||
embedding = embedding_model.encode(passage)
|
||
results = vector_store.search(vector=embedding, top_k=top_k)
|
||
all_results.extend(results)
|
||
|
||
# 去重并合并得分
|
||
return deduplicate_and_merge(all_results)
|
||
```
|
||
|
||
---
|
||
|
||
## 元数据过滤
|
||
|
||
### 多租户过滤
|
||
|
||
```python
|
||
class MultiTenantRetriever:
|
||
"""带强制租户隔离的检索器。"""
|
||
|
||
def __init__(self, vector_store):
|
||
self.vector_store = vector_store
|
||
|
||
def search(
|
||
self,
|
||
query_embedding: list[float],
|
||
tenant_id: str,
|
||
top_k: int = 10,
|
||
additional_filters: dict | None = None
|
||
) -> list[SearchResult]:
|
||
"""使用强制租户过滤器进行搜索。"""
|
||
# 构建过滤器 —— 租户始终为必填项
|
||
filters = {"tenant_id": {"$eq": tenant_id}}
|
||
|
||
if additional_filters:
|
||
filters = {"$and": [filters, additional_filters]}
|
||
|
||
return self.vector_store.search(
|
||
vector=query_embedding,
|
||
filter=filters,
|
||
top_k=top_k
|
||
)
|
||
|
||
# 使用示例
|
||
retriever = MultiTenantRetriever(pinecone_index)
|
||
results = retriever.search(
|
||
query_embedding=embedding,
|
||
tenant_id="acme-corp",
|
||
additional_filters={
|
||
"doc_type": {"$in": ["manual", "faq"]},
|
||
"published": {"$eq": True}
|
||
}
|
||
)
|
||
```
|
||
|
||
### 时间范围过滤
|
||
|
||
```python
|
||
from datetime import datetime, timedelta
|
||
|
||
def search_recent_documents(
|
||
query_embedding: list[float],
|
||
vector_store,
|
||
days_back: int = 30,
|
||
top_k: int = 10
|
||
) -> list[SearchResult]:
|
||
"""搜索指定时间窗口内更新过的文档。"""
|
||
cutoff_date = datetime.utcnow() - timedelta(days=days_back)
|
||
|
||
return vector_store.search(
|
||
vector=query_embedding,
|
||
filter={
|
||
"updated_at": {"$gte": cutoff_date.isoformat()}
|
||
},
|
||
top_k=top_k
|
||
)
|
||
|
||
def search_with_recency_boost(
|
||
query_embedding: list[float],
|
||
vector_store,
|
||
recency_weight: float = 0.2,
|
||
top_k: int = 10
|
||
) -> list[SearchResult]:
|
||
"""在排序中提升近期文档的权重。"""
|
||
# 获取更多结果以便进行后置过滤
|
||
results = vector_store.search(
|
||
vector=query_embedding,
|
||
top_k=top_k * 3
|
||
)
|
||
|
||
now = datetime.utcnow()
|
||
|
||
def compute_boosted_score(result):
|
||
doc_date = datetime.fromisoformat(result.metadata["updated_at"])
|
||
days_old = (now - doc_date).days
|
||
recency_score = max(0, 1 - (days_old / 365)) # 一年内衰减
|
||
return result.score * (1 - recency_weight) + recency_score * recency_weight
|
||
|
||
# 使用时效性加分进行重排序
|
||
for result in results:
|
||
result.boosted_score = compute_boosted_score(result)
|
||
|
||
results.sort(key=lambda x: x.boosted_score, reverse=True)
|
||
return results[:top_k]
|
||
```
|
||
|
||
---
|
||
|
||
## 查询分解
|
||
|
||
```python
|
||
def decompose_complex_query(query: str) -> list[str]:
|
||
"""将复杂查询分解为多个子问题。"""
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": """将这个复杂问题拆分为更简单的子问题,
|
||
每个子问题应能独立回答。每个子问题应具备可搜索性。
|
||
以 JSON 格式返回,包含 "questions" 数组。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": query
|
||
}
|
||
],
|
||
response_format={"type": "json_object"}
|
||
)
|
||
|
||
import json
|
||
result = json.loads(response.choices[0].message.content)
|
||
return result.get("questions", [query])
|
||
|
||
def search_with_decomposition(
|
||
complex_query: str,
|
||
vector_store,
|
||
embedding_model,
|
||
top_k_per_subquery: int = 5
|
||
) -> dict:
|
||
"""对每个子问题进行搜索并汇总结果。"""
|
||
sub_questions = decompose_complex_query(complex_query)
|
||
|
||
aggregated_results = {
|
||
"sub_questions": [],
|
||
"all_documents": []
|
||
}
|
||
|
||
seen_doc_ids = set()
|
||
|
||
for sub_q in sub_questions:
|
||
embedding = embedding_model.encode(sub_q)
|
||
results = vector_store.search(vector=embedding, top_k=top_k_per_subquery)
|
||
|
||
sub_q_results = []
|
||
for r in results:
|
||
if r.id not in seen_doc_ids:
|
||
seen_doc_ids.add(r.id)
|
||
sub_q_results.append(r)
|
||
aggregated_results["all_documents"].append(r)
|
||
|
||
aggregated_results["sub_questions"].append({
|
||
"question": sub_q,
|
||
"results": sub_q_results
|
||
})
|
||
|
||
return aggregated_results
|
||
|
||
# 使用示例
|
||
complex_q = "Compare the security features of OAuth2 and API keys, and explain when to use each"
|
||
results = search_with_decomposition(complex_q, vector_store, embedding_model)
|
||
```
|
||
|
||
---
|
||
|
||
## 上下文压缩
|
||
|
||
```python
|
||
def compress_retrieved_context(
|
||
query: str,
|
||
documents: list[str],
|
||
max_tokens: int = 2000
|
||
) -> str:
|
||
"""仅从文档中提取与查询相关的部分。"""
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{
|
||
"role": "system",
|
||
"content": f"""仅提取这些文档中与回答用户问题相关的部分。
|
||
删除无关信息。
|
||
将提取内容控制在 {max_tokens} 个 token 以内。
|
||
保留来源归属信息。"""
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": f"""问题:{query}
|
||
|
||
文档:
|
||
{chr(10).join([f'[Doc {i+1}]: {doc}' for i, doc in enumerate(documents)])}
|
||
|
||
提取的相关内容:"""
|
||
}
|
||
],
|
||
max_tokens=max_tokens
|
||
)
|
||
|
||
return response.choices[0].message.content
|
||
```
|
||
|
||
### 基于 Cross-Encoder 的抽取式压缩
|
||
|
||
```python
|
||
from sentence_transformers import CrossEncoder
|
||
|
||
def extractive_compress(
|
||
query: str,
|
||
document: str,
|
||
cross_encoder: CrossEncoder,
|
||
top_k_sentences: int = 5
|
||
) -> str:
|
||
"""从文档中提取最相关的句子。"""
|
||
import re
|
||
sentences = re.split(r'(?<=[.!?])\s+', document)
|
||
|
||
if len(sentences) <= top_k_sentences:
|
||
return document
|
||
|
||
# 对每个句子打分
|
||
pairs = [[query, sent] for sent in sentences]
|
||
scores = cross_encoder.predict(pairs)
|
||
|
||
# 按原始顺序获取 top 句子
|
||
scored_sentences = list(zip(range(len(sentences)), sentences, scores))
|
||
top_sentences = sorted(scored_sentences, key=lambda x: x[2], reverse=True)[:top_k_sentences]
|
||
top_sentences = sorted(top_sentences, key=lambda x: x[0]) # 恢复原始顺序
|
||
|
||
return " ".join([s[1] for s in top_sentences])
|
||
```
|
||
|
||
---
|
||
|
||
## 完整优化流程
|
||
|
||
```python
|
||
class OptimizedRetriever:
|
||
"""包含所有优化策略的生产级检索流程。"""
|
||
|
||
def __init__(
|
||
self,
|
||
vector_store,
|
||
embedding_model,
|
||
reranker,
|
||
bm25_index
|
||
):
|
||
self.vector_store = vector_store
|
||
self.embedding_model = embedding_model
|
||
self.reranker = reranker
|
||
self.bm25_index = bm25_index
|
||
|
||
async def retrieve(
|
||
self,
|
||
query: str,
|
||
tenant_id: str,
|
||
top_k: int = 5,
|
||
use_hyde: bool = False,
|
||
use_query_expansion: bool = True
|
||
) -> list[dict]:
|
||
"""完整的优化检索流程。"""
|
||
# 第 1 步:查询预处理
|
||
processed_query = self._preprocess_query(query)
|
||
|
||
# 第 2 步:可选的 HyDE
|
||
if use_hyde:
|
||
query_embedding = await self._hyde_embed(processed_query)
|
||
else:
|
||
query_embedding = self.embedding_model.encode(processed_query)
|
||
|
||
# 第 3 步:混合搜索(向量 + BM25)
|
||
vector_results = self.vector_store.search(
|
||
vector=query_embedding,
|
||
filter={"tenant_id": tenant_id},
|
||
top_k=50
|
||
)
|
||
bm25_results = self.bm25_index.search(processed_query, top_k=50)
|
||
|
||
# 第 4 步:使用 RRF 合并
|
||
merged = reciprocal_rank_fusion(
|
||
vector_results,
|
||
bm25_results,
|
||
vector_weight=0.6
|
||
)[:30]
|
||
|
||
# 第 5 步:可选的查询扩展
|
||
if use_query_expansion:
|
||
expanded_queries = await self._expand_query(processed_query)
|
||
for exp_query in expanded_queries[1:]: # 跳过原始查询
|
||
exp_embedding = self.embedding_model.encode(exp_query)
|
||
exp_results = self.vector_store.search(
|
||
vector=exp_embedding,
|
||
filter={"tenant_id": tenant_id},
|
||
top_k=10
|
||
)
|
||
merged.extend(exp_results)
|
||
merged = deduplicate_by_id(merged)[:30]
|
||
|
||
# 第 6 步:重排序
|
||
documents = [r.text for r in merged]
|
||
reranked = self.reranker.rerank(
|
||
query=processed_query,
|
||
documents=documents,
|
||
top_k=top_k
|
||
)
|
||
|
||
return [
|
||
{
|
||
"text": doc,
|
||
"score": score,
|
||
"metadata": merged[i].metadata
|
||
}
|
||
for i, (doc, score) in enumerate(reranked)
|
||
]
|
||
|
||
def _preprocess_query(self, query: str) -> str:
|
||
"""清洗和标准化查询。"""
|
||
import re
|
||
query = re.sub(r'\s+', ' ', query).strip()
|
||
return query
|
||
|
||
async def _hyde_embed(self, query: str) -> list[float]:
|
||
"""生成假设性文档并进行嵌入。"""
|
||
# 实现参考 HyDE 章节
|
||
pass
|
||
|
||
async def _expand_query(self, query: str) -> list[str]:
|
||
"""用变体扩展查询。"""
|
||
# 实现参考查询扩展章节
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
## 性能基准
|
||
|
||
| 技术 | 延迟影响 | 质量影响 | 成本影响 |
|
||
|-----------|----------------|----------------|-------------|
|
||
| 纯向量 | 基准 | 基准 | 基准 |
|
||
| + BM25 混合 | +10-20ms | +5-15% 精确率 | 极小 |
|
||
| + 重排序 | +50-100ms | +10-20% 精确率 | +$0.001/查询 |
|
||
| + 查询扩展 | +100-200ms | +5-10% 召回率 | +$0.002/查询 |
|
||
| + HyDE | +200-500ms | +10-25% 精确率 | +$0.003/查询 |
|
||
|
||
---
|
||
|
||
## 快速参考
|
||
|
||
| 目标 | 技术 | 实现方式 |
|
||
|------|-----------|----------------|
|
||
| 提升精确率 | 重排序 | Cross-encoder 或 Cohere |
|
||
| 提升召回率 | 查询扩展 | LLM 生成的变体 |
|
||
| 处理同义词 | 混合搜索 | BM25 + 向量 + RRF |
|
||
| 概念搜索 | HyDE | 假设性文档嵌入 |
|
||
| 多租户 | 元数据过滤 | 强制 tenant_id |
|
||
| 最新内容 | 时间范围过滤 | 日期范围查询 |
|
||
| 复杂问题 | 分解 | 子问题检索 |
|
||
|
||
## 相关技能
|
||
|
||
- **RAG 架构师** — 系统设计与架构
|
||
- **NLP 工程师** — 查询理解
|
||
- **Python 高手** — 异步实现
|
||
- **ML 管道** — 重排序模型的服务部署
|