【项目实训7】分块、嵌入与检索优化工程

—— written by Unalome (2025.06.07)

本次优化改动幅度较大,主要针对预处理分块和检索,优化后的整体检索和性能都有了非常优秀的提升。其中主要优化函数主要由队友们编写,本人负责各模块的连接与组装


一、分块方法优化

由原本的按固定长度暴力分块改为智能分块(固定长度与语义感知相结合),具体流程如下:

1. 文本预处理阶段

  • 使用正则表达式去除文本中的多余空格、制表符等空白字符
  • 将连续的换行符规范化为单个换行,统一文本格式
  • 对特殊字符进行转义处理,确保后续处理的一致性

2. 滑动窗口分块阶段

  • 按照预设的CHUNK_SIZE=100字符进行固定长度分块
  • 相邻分块之间保留OVERLAP_SIZE=10字符的重叠区域
  • 使用滑动窗口机制,每次移动步长为CHUNK_SIZE - OVERLAP_SIZE

3. 边界条件处理

  • 对于长度不足CHUNK_SIZE的文本,作为独立块保留
  • 对文本末尾部分进行特殊处理,确保不遗漏任何信息
  • 记录每个分块在原文中的起始位置,用于后续定位

4. 分块质量验证

  • 检查每个分块的实际字节数,确保不超过MAX_CHUNK_BYTES=300
  • 对分块结果进行统计分析,生成日志报告辅助调优
class SemanticTextSplitter:
    def split_text(self, text: str) -> List[str]:
        # 文本预处理,确保格式统一
        text = re.sub(r'\s+', ' ', text).strip()
        if not text:
            return []
        
        # 短文本直接返回
        if len(text) <= CHUNK_SIZE:
            return [text]
        
        # 滑动窗口分块,实现重叠设计
        chunks = []
        start = 0
        while start < len(text):
            end = start + CHUNK_SIZE
            
            # 处理文本末尾边界情况
            if end >= len(text):
                chunks.append(text[start:])
                break
                
            # 记录分块并移动窗口
            chunks.append(text[start:end])
            start = end - OVERLAP_SIZE
            
        return chunks 

二、嵌入方法优化

采用OpenAI文本嵌入API将文本转换为向量表示,具体流程如下:

1. 模型配置

  • 使用text-embedding-v2模型替代原本的bge-m3嵌入模型,使后续检索精度获得明显提升
  • 针对不同模型版本设置特定参数(v3需要指定1024维)

2. 批量处理优化

  • 将分块文本按批次组织,每批不超过API最大限制
  • 实现异步批量调用,充分利用网络带宽资源
  • 对大文本进行智能分组,避免超出API请求长度限制

3. 向量转换流程

  • 将文本块序列化为符合API要求的JSON格式
  • 发送HTTP请求到OpenAI嵌入服务
  • 解析返回结果,提取向量数据并进行格式转换

4. 鲁棒性保障机制

  • 设置请求超时控制防止长时间阻塞
  • 当API调用失败时返回预设维度的零向量
  • 记录详细的错误日志,包含请求内容和错误码
class OpenAIEmbedder(EmbeddingFunction):
    def __call__(self, texts: List[str]) -> List[List[float]]:
        try:
            # 根据模型版本动态配置参数
            payload = {
                "model": self.model_name,
                "input": texts,
                "encoding_format": "float"
            }
            if "v3" in self.model_name:
                payload["dimensions"] = 1024
                
            # 调用API获取嵌入向量
            response = client.embeddings.create(**payload)
            return [data.embedding for data in response.data]
            
        except Exception as e:
            # 异常处理:记录错误并返回默认向量
            logger.error(f"嵌入失败: {e}, 文本: {texts[:100]}...")
            dim = 1024 if "v3" in self.model_name else 1536
            return [[0.0] * dim] * len(texts)

三、检索方法优化

采用多条件过滤与混合评分的检索策略,具体流程如下:

1. 查询解析阶段

  • 自然语言理解:识别查询中的时间、地点等结构化信息
  • 关键词提取:使用TF-IDF算法提取查询中的关键术语
  • 条件构建:将解析结果转换为数据库可理解的过滤条件

2. 初始检索阶段

  • 向量相似度检索:基于嵌入向量进行语义搜索
  • 条件过滤:应用时间、校区等结构化条件筛选结果
  • 候选集扩展:先获取多于所需数量的结果(默认20倍)

3. 混合评分阶段

  • 相似度计算:基于向量距离计算语义相似度得分
  • 时间匹配度:评估文档时间与查询时间的匹配程度
  • 历史相关性:分析历史对话记录,提升相关主题权重
  • 最终得分:综合多种因素计算最终排序得分

4. 结果精筛阶段

  • 按最终得分降序排序
  • 去除冗余重复结果
  • 返回TopN最相关的结果

5. 缓存与优化

  • 查询缓存:对相同查询直接返回缓存结果
  • 结果预热:对高频查询结果进行预加载
  • 性能监控:记录查询耗时和结果质量指标
class TextSearchEngine:
    def search(self, query: str, top_n: int = 5) -> Dict:
        # 解析查询条件
        time_cond = self._parse_time_condition(query)
        campus_cond = self._parse_campus_condition(query)
        where_cond = self._build_where_condition(time_cond, campus_cond)
        
        # 执行数据库查询(扩大候选集)
        results = self.collection.query(
            query_texts=[query],
            n_results=top_n * 20,
            where=where_cond
        )
        
        # 混合评分
        scored_results = []
        for doc, meta, dist in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0]
        ):
            # 基础相似度得分
            similarity = 1 - dist
            
            # 时间匹配度计算
            time_score = self._calculate_time_score(meta, time_cond)
            
            # 动态加权策略
            if time_score >= 0.95:  # 时间高度匹配
                final_score = similarity * 0.3 + time_score * 0.7
            elif "year" in time_cond and "month" in time_cond:
                final_score = similarity * 0.4 + time_score * 0.6
            else:  # 时间因素较弱
                final_score = similarity * 0.7 + time_score * 0.3
                
            # 历史记录增强(基于对话历史提升相关结果的优先级)
            history_boost = self._get_history_boost(query, doc)
            final_score *= history_boost
            
            scored_results.append((doc, meta, final_score))
        
        # 排序并返回TopN
        scored_results.sort(key=lambda x: x[2], reverse=True)
        return scored_results[:top_n]

四、优化效果总结

优化点 改进效果 实现方式
分块策略 信息完整性提升>60% 固定长度 + 重叠区域 + 边界处理
检索准确率 相关结果召回率提升 >30% 混合评分 + 历史增强 + 条件过滤
查询响应速度 重复查询加速 >80% 查询缓存 + 智能预处理
时间敏感查询 准确率提升 >90% 动态加权 + 时间条件解析
posted @ 2025-06-08 15:00  Unalome  阅读(39)  评论(0)    收藏  举报