查询转换(Query Transformation)
查询转换是将用户查询从文本形式转化为检索引擎能处理的格式(如 embedding 向量),同时做必要的清洗和保护。这是让"人类语言"适配"机器引擎"的关键桥梁。
一、是什么
查询转换是将用户查询从文本形式转化为检索引擎能处理的格式(如 embedding 向量),同时做必要的清洗和保护。
核心输入 :改写后的检索查询
核心输出:归一化的 embedding 向量 + 保护后的查询文本
二、为什么重要
检索引擎不认识"人类语言",它只认识向量。
转换质量直接影响检索质量:
- 图片标记
![figure]会干扰 embedding 模型 - 过长的 query 会导致 Rerank 模型截断
- 不同组件需要不同的 Token 计数精度
与传统方案的对比
| 优化点 | 传统方案 | 传统方案的问题 | QAnything 方案 | 优势 |
|---|---|---|---|---|
| 向量化 | 平均池化(mean pooling) | 所有 token 平均,重要信息被稀释 | CLS token 提取 [:,0] |
全局语义更聚焦 |
| 归一化 | 不归一化 | 余弦相似度计算需要除法,长向量主导排序 | L2 归一化 | 余弦相似度 = 点积,加速计算 |
| 图片处理 | 原样发送 |  干扰语义 |
发送前过滤图片标记 | 检索向量更纯净 |
| Token 计数 | 统一用一个 tokenizer | tiktoken 计 100 token,实际可能 120 | 三级 tokenizer + 安全余量 | 各组件精度匹配 |
| 推理框架 | PyTorch 直接推理 | 推理慢,部署体积大,无算子融合 | ONNX Runtime + 全图优化 | 推理速度提升 2-3x |
| 分数输出 | 原始 logits(无界) | 阈值难设,不可解释 | Sigmoid + 线性拉伸 | 0~1 可解释,区分度高 |
三、怎么做
3.1 Embedding 向量化 + L2 归一化
问题:文本需要转为向量才能在 Milvus 中检索。
方案:提取 CLS token 作为全局语义表示,然后 L2 归一化。
python
# Embedding ONNX 模型推理
embedding = outputs_onnx[0][:, 0] # 提取 CLS token(全局语义)
# L2 归一化:使所有向量落到单位球面
norm_arr = np.linalg.norm(embedding, axis=1, keepdims=True)
embeddings_normalized = embedding / norm_arr
归一化的价值:
- 归一化后余弦相似度 = 点积,加速后续相似度计算
- 所有向量长度一致,避免"长向量主导排序"的问题
为什么用 CLS token:
- BERT 类模型的
[:, 0]token 经过自注意力聚合,代表整句话的全局语义 - 比平均池化更能捕捉句子的核心含义
3.2 图片引用过滤
问题 :查询中的  会干扰 Embedding 模型的语义理解。
方案:发送前过滤掉图片和公式标记。
python
# Embedding 客户端:发送前过滤
def _process_query(query):
return '\n'.join([line for line in query.split('\n')
if not line.strip().startswith('![figure]')
and not line.strip().startswith('![equation]')])
为什么不在索引阶段过滤:
- 索引阶段的图片引用是文档内容的一部分
- 但查询阶段用户不会主动写图片标记,这些通常是系统拼接进来的
3.3 三级 Tokenizer 精度体系
问题:不同组件对 token 计数的精度要求不同,用错 tokenizer 会导致切分不准或预算超支。
| Tokenizer | 使用场景 | 为什么用它 |
|---|---|---|
tiktoken (GPT) |
LLM Token 预算计算 | 直接影响 API 成本,需要精确 |
embedding_tokenizer |
文件切分判断 | 必须与 Embedding 模型的 max_length 对齐 |
rerank_tokenizer |
Rerank 资格判断 | 必须与 Rerank 模型的 max_length 对齐 |
python
# LLM Token 估算:额外加安全余量
total_tokens *= 1.1 # tiktoken 精度较高,加 10%
total_tokens *= 1.2 # 如果用 cl100k_base 兜底(中文模型偏差大),加 20%
安全余量的原因:
- tiktoken 是 GPT 专用 tokenizer,与国产 LLM(如 Qwen)有偏差
- cl100k_base 是通用 tokenizer,中文场景偏差更大
- 加余量是为了防止实际 token 超过预算
3.4 Rerank 查询长度保护
问题:Rerank 模型 max_length = 512,如果 query 太长就没有空间放 doc。
方案:query > 300 token 时跳过 Rerank,给 doc 留出 ~200 token 空间。
python
# 三个条件全部满足才执行 Rerank
if rerank and len(source_documents) > 1 and num_tokens_rerank(query) <= 300:
source_documents = await self.rerank.arerank_documents(...)
为什么是 300 而不是 512:
- Rerank 输入 = query + doc,512 是总长度
- 给 doc 留 200 token 空间,防止截断
3.5 ONNX Runtime 推理加速
问题:PyTorch 推理速度慢,部署体积大。
方案:使用 ONNX Runtime 进行模型推理。
python
sess_options = SessionOptions()
sess_options.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL # 全图优化
# 自动线程调度 + GPU/CPU 自适应
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
优化项:
- 算子融合:将多个小算子合并为一个大算子
- 常量折叠:编译期计算常量表达式
- 自动线程调度:根据 CPU 核心数分配线程
- 设备自适应:自动选择 GPU 或 CPU
3.6 Rerank Sigmoid 分数校准
问题:交叉编码器输出 logits(无界值),需要映射为 0~1 之间的可解释分数。
python
def sigmoid(x):
scores = 1 / (1 + np.exp(-x)) # 标准 sigmoid → (0, 1)
scores = np.clip(1.5 * (scores - 0.5) + 0.5, 0, 1) # 线性拉伸 → 增强区分度
return scores
线性拉伸的作用:
标准 sigmoid 输出:
0.48 0.50 0.52 0.55 → 区分度低,难以设定阈值
拉伸后:
0.47 0.50 0.53 0.575 → 高分更高,低分更低,区分度增强
四、解决的问题汇总
| 手段 | 解决的问题 |
|---|---|
| Embedding 归一化 | 余弦相似度计算效率 |
| 图片引用过滤 | 无效 token 干扰语义 |
| 三级 Tokenizer | 不同组件精度需求不同 |
| Rerank 长度保护 | Rerank 模型输入截断 |
| ONNX 加速 | 推理速度慢 |
| Sigmoid 校准 | 分数无界不可解释 |