17种经过验证的RAG优化技术,从基础到高级全覆盖

🎯 RAG 高级优化策略大全

17种经过验证的RAG优化技术,从基础到高级全覆盖


📋 目录


🎯 策略分类

按优化阶段分类

文档处理(6种)          检索增强(7种)           生成优化(4种)
    ↓                       ↓                        ↓
┌──────────────┐      ┌──────────────┐       ┌──────────────┐
│ 1.文本切分    │      │ 7.二次排序    │       │ 9.上下文压缩  │
│ 2.语义拆分    │      │ 8.连续片段    │       │ 16.Fusion融合 │
│ 3.上下文增强  │      │ 10.用户反馈   │       │ 17.CRAG纠错   │
│ 4.添加描述    │  →  │ 11.场景细分   │   →   │ 13.知识图谱   │
│ 5.文档增强    │      │ 12.自我检索   │       └──────────────┘
│ 14.层次索引   │      │ 15.HyDE       │
└──────────────┘      └──────────────┘

特殊技术:6.查询转换重写(跨阶段)

按难度和效果分类

策略 难度 效果 优先级 实施时间
1. 文本切分优化 ⭐⭐⭐⭐ 🔥🔥🔥 1天
2. 语义拆分 ⭐⭐ ⭐⭐⭐⭐ 🔥🔥 2天
3. 上下文增强 ⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 1天
4. 添加描述标题 ⭐⭐ ⭐⭐⭐⭐ 🔥🔥 2天
5. 文档增强 ⭐⭐ ⭐⭐⭐ 🔥 3天
6. 查询转换 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 2天
7. 二次排序 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 3天
8. 连续片段 ⭐⭐ ⭐⭐⭐⭐ 🔥🔥 2天
9. 上下文压缩 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 3天
10. 用户反馈 ⭐⭐⭐ ⭐⭐⭐⭐ 🔥🔥 5天
11. 场景细分 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 4天
12. 自我检索 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 🔥🔥 5天
13. 知识图谱 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥 2周+
14. 层次索引 ⭐⭐⭐ ⭐⭐⭐⭐ 🔥🔥 3天
15. HyDE ⭐⭐⭐ ⭐⭐⭐⭐ 🔥🔥 2天
16. Fusion融合 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥🔥 4天
17. CRAG纠错 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 🔥🔥 5天

一、文档处理优化

策略 1:文本切分优化 🔥🔥🔥

原理:合理的文档切分是RAG的基础,影响检索准确率和生成质量。

关键参数

  • chunk_size:每个片段的字符数
  • chunk_overlap:片段之间的重叠
  • separators:分隔符优先级

实现代码

/**
 * 智能文本切分
 * 根据文档类型自适应调整参数
 */
class SmartTextSplitter {
  constructor(documentType = 'general') {
    // 根据文档类型预设参数
    this.configs = {
      'general': {
        chunkSize: 500,
        chunkOverlap: 100,
        separators: ['\n\n', '\n', '。', '!', '?', ';', ',', ' '],
      },
      'technical': {
        chunkSize: 800,
        chunkOverlap: 150,
        separators: ['\n\n', '\n', '。', ';', '.', ','],
      },
      'legal': {
        chunkSize: 1000,
        chunkOverlap: 200,
        separators: ['\n\n', '。', '\n', ';'],
      },
      'qa': {
        chunkSize: 300,
        chunkOverlap: 50,
        separators: ['\n\n', '\n', '?', '。'],
      },
    };
    
    this.config = this.configs[documentType] || this.configs['general'];
  }

  /**
   * 递归切分文本
   */
  split(text) {
    const chunks = [];
    const { chunkSize, chunkOverlap, separators } = this.config;

    // 如果文本小于 chunk_size,直接返回
    if (text.length <= chunkSize) {
      return [text];
    }

    // 尝试每个分隔符
    for (const separator of separators) {
      if (text.includes(separator)) {
        const parts = text.split(separator);
        let currentChunk = '';

        for (const part of parts) {
          const testChunk = currentChunk 
            ? currentChunk + separator + part 
            : part;

          if (testChunk.length <= chunkSize) {
            currentChunk = testChunk;
          } else {
            if (currentChunk) {
              chunks.push(currentChunk);
              // 保留重叠部分
              const overlapStart = Math.max(0, currentChunk.length - chunkOverlap);
              currentChunk = currentChunk.substring(overlapStart) + separator + part;
            } else {
              // 单个部分就超过 chunk_size,递归切分
              chunks.push(...this.split(part));
              currentChunk = '';
            }
          }
        }

        if (currentChunk) {
          chunks.push(currentChunk);
        }

        return chunks;
      }
    }

    // 如果没有找到分隔符,按字符切分
    return this.splitByCharacter(text, chunkSize, chunkOverlap);
  }

  /**
   * 按字符切分
   */
  splitByCharacter(text, chunkSize, overlap) {
    const chunks = [];
    let start = 0;

    while (start < text.length) {
      const end = start + chunkSize;
      chunks.push(text.substring(start, end));
      start = end - overlap;
    }

    return chunks;
  }
}

// 使用示例
const splitter = new SmartTextSplitter('technical');
const chunks = splitter.split(document);
console.log(`文档切分为 ${chunks.length} 个片段`);

效果对比

方案 chunk_size overlap 检索准确率 上下文完整性
固定切分 1000 0 60% ⭐⭐
基础优化 500 50 70% ⭐⭐⭐
智能切分 自适应 自适应 85% ⭐⭐⭐⭐⭐

策略 2:语义拆分 🔥🔥

原理:基于语义边界切分,而非固定长度,保持内容完整性。

实现方式

  1. 使用 NLP 模型识别段落边界
  2. 基于主题变化切分
  3. 使用向量相似度判断

实现代码

/**
 * 语义拆分
 * 基于句子 Embedding 相似度
 */
class SemanticSplitter {
  constructor(embeddingModel, similarityThreshold = 0.5) {
    this.embeddingModel = embeddingModel;
    this.threshold = similarityThreshold;
  }

  /**
   * 将文本按句子分割
   */
  splitSentences(text) {
    // 中文句子分割
    const sentences = text.split(/([。!?\n]+)/).filter(s => s.trim());
    
    // 重新组合句子和标点
    const result = [];
    for (let i = 0; i < sentences.length; i += 2) {
      const sentence = sentences[i] + (sentences[i + 1] || '');
      result.push(sentence.trim());
    }
    
    return result.filter(s => s.length > 0);
  }

  /**
   * 计算句子 Embedding
   */
  async getEmbedding(text) {
    const response = await fetch('http://localhost:11434/api/embeddings', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: this.embeddingModel || 'bge-m3',
        prompt: text,
      }),
    });
    
    const data = await response.json();
    return data.embedding;
  }

  /**
   * 计算余弦相似度
   */
  cosineSimilarity(vec1, vec2) {
    const dotProduct = vec1.reduce((sum, val, i) => sum + val * vec2[i], 0);
    const mag1 = Math.sqrt(vec1.reduce((sum, val) => sum + val * val, 0));
    const mag2 = Math.sqrt(vec2.reduce((sum, val) => sum + val * val, 0));
    return dotProduct / (mag1 * mag2);
  }

  /**
   * 语义切分
   */
  async split(text) {
    // 1. 分句
    const sentences = this.splitSentences(text);
    console.log(`文本分为 ${sentences.length} 个句子`);

    // 2. 获取每个句子的 Embedding
    console.log('计算句子向量...');
    const embeddings = await Promise.all(
      sentences.map(s => this.getEmbedding(s))
    );

    // 3. 计算相邻句子的相似度
    const similarities = [];
    for (let i = 0; i < embeddings.length - 1; i++) {
      const sim = this.cosineSimilarity(embeddings[i], embeddings[i + 1]);
      similarities.push(sim);
    }

    // 4. 在相似度低的地方切分(主题变化)
    const chunks = [];
    let currentChunk = sentences[0];

    for (let i = 0; i < similarities.length; i++) {
      if (similarities[i] < this.threshold) {
        // 主题变化,创建新片段
        chunks.push(currentChunk);
        currentChunk = sentences[i + 1];
      } else {
        // 主题连续,合并
        currentChunk += ' ' + sentences[i + 1];
      }
    }

    if (currentChunk) {
      chunks.push(currentChunk);
    }

    console.log(`语义切分为 ${chunks.length} 个片段`);
    return chunks;
  }
}

// 使用示例
const semanticSplitter = new SemanticSplitter('bge-m3', 0.5);
const chunks = await semanticSplitter.split(document);

效果

  • ✅ 保持语义完整性
  • ✅ 减少跨主题切分
  • ✅ 提升检索准确率 15-20%

策略 3:上下文增强检索 🔥🔥🔥

原理:在每个 chunk 前后添加上下文信息,提供更完整的背景。

三种方式

方式 1:句子窗口扩展

/**
 * 上下文增强
 * 在检索到的 chunk 前后添加句子
 */
class ContextEnhancer {
  constructor(windowSize = 2) {
    this.windowSize = windowSize;  // 前后各添加2个句子
  }

  /**
   * 存储时:记录 chunk 的位置信息
   */
  storeWithContext(chunks) {
    return chunks.map((chunk, index) => ({
      id: index,
      content: chunk,
      previousChunks: chunks.slice(Math.max(0, index - this.windowSize), index),
      nextChunks: chunks.slice(index + 1, index + 1 + this.windowSize),
    }));
  }

  /**
   * 检索时:扩展上下文
   */
  enhanceRetrievedChunk(chunk) {
    const context = [
      ...chunk.previousChunks,
      chunk.content,
      ...chunk.nextChunks,
    ].join('\n\n');

    return {
      content: chunk.content,           // 原始 chunk
      enhancedContent: context,         // 增强后的内容
      contextWindow: this.windowSize,
    };
  }
}

// 使用示例
const enhancer = new ContextEnhancer(2);

// 存储时
const chunksWithContext = enhancer.storeWithContext(chunks);
await vectorDB.store(chunksWithContext);

// 检索时
const retrieved = await vectorDB.search(query);
const enhanced = retrieved.map(chunk => enhancer.enhanceRetrievedChunk(chunk));

方式 2:父文档检索

/**
 * 父文档检索
 * 存储小 chunk 用于检索,返回大 chunk 用于生成
 */
class ParentDocumentRetriever {
  /**
   * 创建父子 chunk 关系
   */
  createParentChildChunks(document) {
    // 1. 创建父 chunk(大)
    const parentChunks = this.splitLarge(document, 2000, 200);
    
    // 2. 为每个父 chunk 创建子 chunk(小)
    const childChunks = [];
    
    parentChunks.forEach((parent, parentId) => {
      const children = this.splitSmall(parent.content, 400, 50);
      
      children.forEach((child, childId) => {
        childChunks.push({
          id: `${parentId}-${childId}`,
          content: child,
          parentId: parentId,
          parentContent: parent.content,  // 关联父文档
        });
      });
    });
    
    return childChunks;
  }

  /**
   * 检索:用小 chunk 检索,返回大 chunk
   */
  async retrieve(query, topK = 5) {
    // 1. 用小 chunk 检索(更精确)
    const childResults = await vectorDB.search(query, topK);
    
    // 2. 返回对应的父 chunk(更完整)
    const parentResults = childResults.map(child => ({
      content: child.parentContent,  // 返回父文档
      childContent: child.content,    // 原始匹配的子文档
      score: child.score,
    }));
    
    return parentResults;
  }
}

效果对比

方案 检索精度 上下文完整性 推荐
无上下文 ⭐⭐⭐ ⭐⭐ -
句子窗口 ⭐⭐⭐⭐ ⭐⭐⭐⭐ ✅ 简单场景
父文档检索 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ✅ 复杂文档

策略 4:为 Chunk 添加描述标题 🔥🔥

原理:为每个 chunk 生成概括性的标题/描述,提升检索准确性。

实现代码

/**
 * Chunk 描述生成器
 */
class ChunkDescriptor {
  constructor(llmModel = 'qwen2.5:7b') {
    this.llmModel = llmModel;
  }

  /**
   * 为 chunk 生成描述
   */
  async generateDescription(chunk) {
    const prompt = `
请为以下文本片段生成一个简洁的标题和描述(50字以内)。

文本内容:
${chunk.substring(0, 500)}

输出格式:
标题:[一句话概括]
描述:[简要说明内容要点]

你的输出:`;

    const response = await fetch('http://localhost:11434/api/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: this.llmModel,
        prompt: prompt,
        stream: false,
      }),
    });

    const data = await response.json();
    return this.parseDescription(data.response);
  }

  /**
   * 解析模型输出
   */
  parseDescription(output) {
    const titleMatch = output.match(/标题[::]\s*(.+)/);
    const descMatch = output.match(/描述[::]\s*(.+)/);
    
    return {
      title: titleMatch ? titleMatch[1].trim() : '',
      description: descMatch ? descMatch[1].trim() : output,
    };
  }

  /**
   * 批量处理
   */
  async addDescriptionsToChunks(chunks) {
    console.log(`为 ${chunks.length} 个 chunk 生成描述...`);
    
    const enrichedChunks = [];
    
    for (let i = 0; i < chunks.length; i++) {
      console.log(`进度: ${i + 1}/${chunks.length}`);
      
      const description = await this.generateDescription(chunks[i]);
      
      enrichedChunks.push({
        content: chunks[i],
        title: description.title,
        description: description.description,
        // 用于向量化的增强文本
        enhancedText: `${description.title}\n${description.description}\n\n${chunks[i]}`,
      });
      
      // 避免请求过快
      await new Promise(resolve => setTimeout(resolve, 200));
    }
    
    return enrichedChunks;
  }
}

// 使用示例
const descriptor = new ChunkDescriptor();
const enrichedChunks = await descriptor.addDescriptionsToChunks(chunks);

// 存储时使用 enhancedText
await vectorDB.store(enrichedChunks.map(chunk => ({
  content: chunk.content,
  vector: await getEmbedding(chunk.enhancedText),  // 使用增强文本
  metadata: {
    title: chunk.title,
    description: chunk.description,
  },
})));

效果

  • ✅ 提升检索准确率 20-30%
  • ✅ 改善长文档检索效果
  • ✅ 用户可以看到 chunk 标题,体验更好

策略 5:文档增强 🔥

原理:为文档添加额外的元数据和结构化信息。

增强方式

/**
 * 文档增强器
 */
class DocumentEnhancer {
  /**
   * 提取文档元数据
   */
  extractMetadata(document, filename) {
    return {
      filename: filename,
      fileType: this.detectFileType(filename),
      length: document.length,
      language: this.detectLanguage(document),
      keywords: this.extractKeywords(document),
      topics: this.extractTopics(document),
      summary: this.generateSummary(document),
      createdAt: new Date().toISOString(),
    };
  }

  /**
   * 提取关键词
   */
  extractKeywords(text) {
    // 简单的关键词提取(实际可用 TF-IDF 或 YAKE)
    const words = text.match(/[\u4e00-\u9fa5]+/g) || [];
    const frequency = {};
    
    words.forEach(word => {
      if (word.length >= 2) {  // 只统计2字以上的词
        frequency[word] = (frequency[word] || 0) + 1;
      }
    });
    
    // 返回频率最高的前10个词
    return Object.entries(frequency)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([word]) => word);
  }

  /**
   * 生成文档摘要
   */
  async generateSummary(document) {
    const prompt = `请为以下文档生成一个100字以内的摘要:\n\n${document.substring(0, 2000)}`;
    
    // 调用 LLM 生成摘要
    const response = await callLLM(prompt);
    return response;
  }

  /**
   * 增强 chunk
   */
  enhanceChunks(chunks, metadata) {
    return chunks.map((chunk, index) => ({
      content: chunk,
      metadata: {
        ...metadata,
        chunkIndex: index,
        totalChunks: chunks.length,
        // 添加文档摘要到每个 chunk
        documentSummary: metadata.summary,
      },
      // 增强检索文本
      searchText: `
文档:${metadata.filename}
主题:${metadata.topics.join(', ')}
摘要:${metadata.summary}

内容:${chunk}
      `.trim(),
    }));
  }
}

策略 14:层次索引 🔥🔥

原理:建立多层次的索引结构,先检索大分类,再检索具体内容。

实现代码

/**
 * 层次索引
 * 文档 → 章节 → 段落 → 句子
 */
class HierarchicalIndex {
  constructor() {
    this.levels = ['document', 'chapter', 'section', 'chunk'];
  }

  /**
   * 构建层次结构
   */
  buildHierarchy(document) {
    // 层次1:文档级别
    const documentSummary = await this.generateSummary(document);
    
    // 层次2:章节级别
    const chapters = this.splitIntoChapters(document);
    const chapterSummaries = await Promise.all(
      chapters.map(ch => this.generateSummary(ch))
    );
    
    // 层次3:段落级别
    const sections = chapters.flatMap(ch => this.splitIntoSections(ch));
    
    // 层次4:chunk 级别
    const chunks = sections.flatMap(sec => this.splitIntoChunks(sec));
    
    return {
      document: {
        summary: documentSummary,
        chapters: chapterSummaries.map((summary, i) => ({
          summary: summary,
          content: chapters[i],
        })),
      },
      chunks: chunks.map((chunk, i) => ({
        content: chunk,
        chapterIndex: Math.floor(i / (chunks.length / chapters.length)),
        hierarchy: this.getHierarchyPath(i, chapters.length, chunks.length),
      })),
    };
  }

  /**
   * 层次检索
   */
  async hierarchicalSearch(query, topK = 5) {
    // 步骤1:在文档摘要中检索
    const relevantDocs = await this.searchLevel('document', query, 3);
    
    // 步骤2:在相关文档的章节中检索
    const relevantChapters = await this.searchLevel('chapter', query, 5, {
      filter: { documentId: { $in: relevantDocs.map(d => d.id) } },
    });
    
    // 步骤3:在相关章节的 chunk 中检索
    const relevantChunks = await this.searchLevel('chunk', query, topK, {
      filter: { chapterId: { $in: relevantChapters.map(ch => ch.id) } },
    });
    
    return relevantChunks;
  }
}

优势

  • ✅ 提高检索效率(粗筛选 → 细检索)
  • ✅ 减少无关文档干扰
  • ✅ 适合大规模文档库

二、检索优化

策略 6:查询转换重写 🔥🔥🔥

原理:将用户的原始查询转换为更适合检索的形式。

5种转换方式

方式 1:查询扩展

/**
 * 查询扩展
 * 添加同义词、相关词
 */
async function expandQuery(query) {
  const prompt = `
请为以下查询生成3个语义相似的变体,用于改善搜索结果。

原始查询:${query}

要求:
1. 使用同义词
2. 不同的表达方式
3. 保持原意

输出格式(每行一个变体):
1. 
2. 
3. 

你的输出:`;

  const response = await callLLM(prompt);
  const variants = response.split('\n')
    .filter(line => line.match(/^\d+\./))
    .map(line => line.replace(/^\d+\.\s*/, ''));
  
  return [query, ...variants];
}

// 使用示例
const query = "如何提升模型性能?";
const expandedQueries = await expandQuery(query);
// ['如何提升模型性能?', '怎样优化模型效果?', '模型性能改进方法', ...]

// 对每个查询都进行检索,合并结果
const allResults = await Promise.all(
  expandedQueries.map(q => vectorDB.search(q, 5))
);
const mergedResults = this.mergeAndDeduplicate(allResults);

方式 2:查询分解

/**
 * 查询分解
 * 将复杂查询拆分为多个子查询
 */
async function decomposeQuery(complexQuery) {
  const prompt = `
将以下复杂问题拆分为2-4个简单的子问题:

复杂问题:${complexQuery}

输出格式:
1. [子问题1]
2. [子问题2]
...

你的输出:`;

  const response = await callLLM(prompt);
  const subQueries = response.split('\n')
    .filter(line => line.match(/^\d+\./))
    .map(line => line.replace(/^\d+\.\s*/, ''));
  
  return subQueries;
}

// 使用示例
const query = "比较 Ollama 和 vLLM 的优缺点,并推荐适合中小企业的方案";
const subQueries = await decomposeQuery(query);
// [
//   "Ollama 的优点和缺点是什么?",
//   "vLLM 的优点和缺点是什么?",
//   "中小企业应该如何选择部署方案?"
// ]

// 分别检索每个子查询
const subResults = await Promise.all(
  subQueries.map(sq => vectorDB.search(sq, 3))
);

// 综合结果生成回答
const context = subResults.flat().map(r => r.content).join('\n\n');

方式 3:Step-back 提问

/**
 * Step-back 提问
 * 将具体问题转换为更抽象的问题
 */
async function stepBackQuery(specificQuery) {
  const prompt = `
将以下具体问题转换为一个更抽象、更通用的问题:

具体问题:${specificQuery}

抽象问题:`;

  const abstractQuery = await callLLM(prompt);
  
  return {
    specific: specificQuery,
    abstract: abstractQuery,
  };
}

// 使用示例
const query = "Qwen2.5:7b 的显存需求是多少?";
const queries = await stepBackQuery(query);
// {
//   specific: "Qwen2.5:7b 的显存需求是多少?",
//   abstract: "大语言模型的硬件资源需求如何评估?"
// }

// 同时检索具体和抽象的查询
const specificResults = await vectorDB.search(queries.specific, 3);
const abstractResults = await vectorDB.search(queries.abstract, 2);

方式 4:意图识别重写

/**
 * 意图识别和查询重写
 */
class QueryRewriter {
  async rewrite(query) {
    // 1. 识别意图
    const intent = await this.detectIntent(query);
    
    // 2. 根据意图重写查询
    const rewriteStrategy = {
      'factual': (q) => `准确的事实:${q}`,
      'howto': (q) => `详细步骤和方法:${q}`,
      'comparison': (q) => `对比分析:${q}`,
      'troubleshooting': (q) => `问题解决方案:${q}`,
    };
    
    const rewritten = rewriteStrategy[intent]?.(query) || query;
    
    return {
      original: query,
      rewritten: rewritten,
      intent: intent,
    };
  }

  async detectIntent(query) {
    const patterns = {
      'howto': /如何|怎么|怎样|步骤|方法/,
      'comparison': /比较|对比|区别|哪个更好/,
      'troubleshooting': /错误|失败|问题|不工作|报错/,
      'factual': /什么是|定义|解释/,
    };
    
    for (const [intent, pattern] of Object.entries(patterns)) {
      if (pattern.test(query)) {
        return intent;
      }
    }
    
    return 'general';
  }
}

策略 7:二次排序 (Reranking) 🔥🔥🔥

原理:向量检索后,使用更强大的模型重新排序,提升准确性。

实现代码

/**
 * 重排序器
 * 使用 Cross-Encoder 或 LLM 重新评分
 */
class Reranker {
  constructor(method = 'llm') {
    this.method = method;  // 'llm' | 'cross-encoder' | 'bm25'
  }

  /**
   * 方法1:使用 LLM 评分
   */
  async rerankWithLLM(query, documents, topK = 5) {
    console.log(`重排序 ${documents.length} 个文档...`);
    
    const scores = [];
    
    for (const doc of documents) {
      const prompt = `
任务:评估文档与查询的相关性

查询:${query}

文档:${doc.content}

请给出相关性评分(0-10分,只输出数字):`;

      const response = await callLLM(prompt, { maxTokens: 10 });
      const score = parseFloat(response.trim()) || 0;
      
      scores.push({
        ...doc,
        rerankScore: score,
      });
    }
    
    // 按评分排序
    return scores
      .sort((a, b) => b.rerankScore - a.rerankScore)
      .slice(0, topK);
  }

  /**
   * 方法2:使用 BM25 算法
   */
  rerankWithBM25(query, documents, topK = 5) {
    const queryTerms = this.tokenize(query);
    
    const scores = documents.map(doc => {
      const docTerms = this.tokenize(doc.content);
      const bm25Score = this.calculateBM25(queryTerms, docTerms, documents);
      
      return {
        ...doc,
        bm25Score: bm25Score,
        // 结合原始向量得分
        combinedScore: doc.score * 0.6 + bm25Score * 0.4,
      };
    });
    
    return scores
      .sort((a, b) => b.combinedScore - a.combinedScore)
      .slice(0, topK);
  }

  /**
   * 方法3:混合排序
   */
  async hybridRerank(query, documents, topK = 5) {
    // 1. BM25 重排序(快速)
    const bm25Ranked = this.rerankWithBM25(query, documents, topK * 2);
    
    // 2. LLM 精排(精确)
    const llmRanked = await this.rerankWithLLM(query, bm25Ranked, topK);
    
    return llmRanked;
  }

  /**
   * BM25 算法实现
   */
  calculateBM25(queryTerms, docTerms, corpus, k1 = 1.5, b = 0.75) {
    const avgDocLength = corpus.reduce((sum, doc) => 
      sum + this.tokenize(doc.content).length, 0
    ) / corpus.length;
    
    const docLength = docTerms.length;
    const termFreq = {};
    
    // 计算词频
    docTerms.forEach(term => {
      termFreq[term] = (termFreq[term] || 0) + 1;
    });
    
    // 计算 BM25 分数
    let score = 0;
    
    queryTerms.forEach(term => {
      const tf = termFreq[term] || 0;
      const idf = this.calculateIDF(term, corpus);
      
      score += idf * (tf * (k1 + 1)) / 
        (tf + k1 * (1 - b + b * (docLength / avgDocLength)));
    });
    
    return score;
  }

  tokenize(text) {
    // 简单分词(实际应使用专业分词工具)
    return text.toLowerCase()
      .match(/[\u4e00-\u9fa5]+|[a-z0-9]+/g) || [];
  }
}

// 使用示例
const reranker = new Reranker('hybrid');

// 1. 初始向量检索(召回更多文档)
const initialResults = await vectorDB.search(query, 20);

// 2. 重排序(精选最相关的)
const reranked = await reranker.hybridRerank(query, initialResults, 5);

效果对比

方法 准确率 速度 成本
仅向量检索 70%
+ BM25 重排 80%
+ LLM 重排 90%
混合重排 92%

策略 8:连续片段查找和筛选 🔥🔥

原理:检索到的相关 chunk 如果是连续的,可以合并提供更完整的上下文。

实现代码

/**
 * 连续片段合并器
 */
class ConsecutiveChunkMerger {
  /**
   * 检测并合并连续 chunk
   */
  mergeConsecutiveChunks(retrievedChunks) {
    // 按文档ID和chunk索引排序
    const sorted = retrievedChunks.sort((a, b) => {
      if (a.metadata.documentId !== b.metadata.documentId) {
        return a.metadata.documentId - b.metadata.documentId;
      }
      return a.metadata.chunkIndex - b.metadata.chunkIndex;
    });

    const merged = [];
    let currentGroup = [sorted[0]];

    for (let i = 1; i < sorted.length; i++) {
      const current = sorted[i];
      const previous = sorted[i - 1];

      // 检查是否连续
      if (
        current.metadata.documentId === previous.metadata.documentId &&
        current.metadata.chunkIndex === previous.metadata.chunkIndex + 1
      ) {
        // 连续,加入当前组
        currentGroup.push(current);
      } else {
        // 不连续,保存当前组,开始新组
        merged.push(this.mergeGroup(currentGroup));
        currentGroup = [current];
      }
    }

    // 处理最后一组
    if (currentGroup.length > 0) {
      merged.push(this.mergeGroup(currentGroup));
    }

    return merged;
  }

  /**
   * 合并一组连续 chunk
   */
  mergeGroup(group) {
    if (group.length === 1) {
      return group[0];
    }

    return {
      content: group.map(c => c.content).join('\n\n'),
      metadata: {
        ...group[0].metadata,
        isMerged: true,
        chunkCount: group.length,
        chunkRange: [
          group[0].metadata.chunkIndex,
          group[group.length - 1].metadata.chunkIndex,
        ],
      },
      score: Math.max(...group.map(c => c.score)),  // 使用最高分
    };
  }

  /**
   * 智能填充缺失的中间 chunk
   */
  async fillMissingChunks(chunks) {
    const filled = [];

    for (let i = 0; i < chunks.length - 1; i++) {
      filled.push(chunks[i]);

      const current = chunks[i];
      const next = chunks[i + 1];

      // 检查是否有缺失的 chunk
      if (
        current.metadata.documentId === next.metadata.documentId &&
        next.metadata.chunkIndex - current.metadata.chunkIndex > 1
      ) {
        // 有缺失,获取中间的 chunk
        const missingChunks = await this.getMissingChunks(
          current.metadata.documentId,
          current.metadata.chunkIndex + 1,
          next.metadata.chunkIndex - 1
        );

        filled.push(...missingChunks);
      }
    }

    filled.push(chunks[chunks.length - 1]);
    return filled;
  }

  /**
   * 从数据库获取缺失的 chunk
   */
  async getMissingChunks(documentId, startIndex, endIndex) {
    return await vectorDB.getChunksByRange(documentId, startIndex, endIndex);
  }
}

// 使用示例
const merger = new ConsecutiveChunkMerger();

// 1. 检索
const retrieved = await vectorDB.search(query, 10);

// 2. 合并连续片段
const merged = merger.mergeConsecutiveChunks(retrieved);

// 3. 可选:填充缺失片段
const filled = await merger.fillMissingChunks(merged);

策略 11:场景细分和意图识别 🔥🔥🔥

原理:根据不同的场景和意图,检索不同的知识库或使用不同的策略。

实现代码

/**
 * 场景路由器
 * 根据意图路由到不同的知识库
 */
class SceneRouter {
  constructor() {
    // 定义不同场景的知识库
    this.knowledgeBases = {
      'product': {
        collection: 'product_docs',
        strategy: 'dense',  // 密集检索
      },
      'technical': {
        collection: 'technical_docs',
        strategy: 'hybrid',  // 混合检索
      },
      'policy': {
        collection: 'policy_docs',
        strategy: 'exact',  // 精确匹配
      },
      'faq': {
        collection: 'faq',
        strategy: 'semantic',  // 语义检索
      },
    };
  }

  /**
   * 识别场景
   */
  async detectScene(query) {
    const prompt = `
请判断以下查询属于哪个场景(只输出场景名称):

查询:${query}

场景选项:
- product: 产品功能、价格、使用方法
- technical: 技术问题、配置、开发
- policy: 公司政策、规则、流程
- faq: 常见问题

场景:`;

    const response = await callLLM(prompt, { maxTokens: 20 });
    const scene = response.trim().toLowerCase();
    
    return this.knowledgeBases[scene] || this.knowledgeBases['faq'];
  }

  /**
   * 路由检索
   */
  async route(query) {
    // 1. 识别场景
    const scene = await this.detectScene(query);
    console.log(`检测到场景: ${scene.collection}, 策略: ${scene.strategy}`);

    // 2. 根据场景选择检索策略
    let results;
    
    switch (scene.strategy) {
      case 'dense':
        results = await this.denseRetrieval(query, scene.collection);
        break;
      case 'hybrid':
        results = await this.hybridRetrieval(query, scene.collection);
        break;
      case 'exact':
        results = await this.exactMatch(query, scene.collection);
        break;
      case 'semantic':
      default:
        results = await this.semanticRetrieval(query, scene.collection);
    }

    return {
      scene: scene.collection,
      strategy: scene.strategy,
      results: results,
    };
  }

  /**
   * 不同的检索策略
   */
  async denseRetrieval(query, collection) {
    // 密集检索:返回更多结果
    return await vectorDB.search(query, { 
      collection, 
      topK: 10,
    });
  }

  async hybridRetrieval(query, collection) {
    // 混合检索:向量 + 关键词
    const vectorResults = await vectorDB.search(query, { collection, topK: 10 });
    const keywordResults = await vectorDB.keywordSearch(query, { collection, topK: 10 });
    
    return this.fuseResults(vectorResults, keywordResults);
  }

  async exactMatch(query, collection) {
    // 精确匹配:适合政策文档
    return await vectorDB.search(query, { 
      collection, 
      topK: 3,
      scoreThreshold: 0.9,  // 高阈值
    });
  }

  async semanticRetrieval(query, collection) {
    // 语义检索:常规方式
    return await vectorDB.search(query, { collection, topK: 5 });
  }
}

// 使用示例
const router = new SceneRouter();

const query = "专业版的价格是多少?";
const result = await router.route(query);
// {
//   scene: 'product',
//   strategy: 'dense',
//   results: [...]
// }

策略 12:自我检索增强生成 (Self-RAG) 🔥🔥

原理:模型自己判断是否需要检索,以及检索结果是否相关。

实现代码

/**
 * Self-RAG
 * 模型自主决定是否检索和使用检索结果
 */
class SelfRAG {
  /**
   * 判断是否需要检索
   */
  async needsRetrieval(query) {
    const prompt = `
判断以下问题是否需要检索外部知识库?

问题:${query}

判断标准:
- 需要检索:涉及具体事实、数据、最新信息
- 不需要检索:常识问题、推理问题、创意问题

回答(只输出"需要"或"不需要"):`;

    const response = await callLLM(prompt, { maxTokens: 10 });
    return response.includes('需要');
  }

  /**
   * 评估检索结果的相关性
   */
  async assessRelevance(query, retrievedDocs) {
    const assessments = [];

    for (const doc of retrievedDocs) {
      const prompt = `
评估文档与问题的相关性:

问题:${query}

文档:${doc.content.substring(0, 500)}

评估(只输出"相关"或"不相关"):`;

      const response = await callLLM(prompt, { maxTokens: 10 });
      const isRelevant = response.includes('相关') && !response.includes('不相关');

      assessments.push({
        ...doc,
        isRelevant: isRelevant,
      });
    }

    return assessments.filter(doc => doc.isRelevant);
  }

  /**
   * Self-RAG 完整流程
   */
  async generate(query) {
    console.log('🤔 Self-RAG 流程开始...');

    // 步骤1:判断是否需要检索
    const needsRetrieval = await this.needsRetrieval(query);
    console.log(`需要检索: ${needsRetrieval}`);

    if (!needsRetrieval) {
      // 直接生成
      return await this.directGenerate(query);
    }

    // 步骤2:检索文档
    console.log('🔍 检索相关文档...');
    const retrieved = await vectorDB.search(query, 10);

    // 步骤3:评估相关性
    console.log('📊 评估文档相关性...');
    const relevantDocs = await this.assessRelevance(query, retrieved);
    console.log(`相关文档: ${relevantDocs.length}/${retrieved.length}`);

    if (relevantDocs.length === 0) {
      console.log('⚠️  未找到相关文档,直接生成');
      return await this.directGenerate(query);
    }

    // 步骤4:基于相关文档生成
    const context = relevantDocs
      .map(doc => doc.content)
      .join('\n\n');

    const answer = await this.generateWithContext(query, context);

    // 步骤5:自我验证
    const isSupported = await this.verifyAnswer(query, answer, context);

    return {
      answer: answer,
      usedRetrieval: true,
      relevantDocsCount: relevantDocs.length,
      isSupported: isSupported,
      sources: relevantDocs,
    };
  }

  /**
   * 验证答案是否被文档支持
   */
  async verifyAnswer(query, answer, context) {
    const prompt = `
验证答案是否被参考文档支持:

问题:${query}

答案:${answer}

参考文档:
${context.substring(0, 1000)}

验证(只输出"支持"或"不支持"):`;

    const response = await callLLM(prompt);
    return response.includes('支持') && !response.includes('不支持');
  }
}

// 使用示例
const selfRAG = new SelfRAG();
const result = await selfRAG.generate("2024年的最新AI趋势是什么?");

策略 15:HyDE (假设性文档嵌入) 🔥🔥

原理:让模型生成一个"假设性的回答",然后用这个回答去检索,而不是用原始问题。

为什么有效

  • 问题和文档在向量空间中距离较远
  • 答案和文档在向量空间中更接近
  • 用假设答案检索,能找到更相关的文档

实现代码

/**
 * HyDE: 假设性文档嵌入
 */
class HyDE {
  /**
   * 生成假设性答案
   */
  async generateHypotheticalAnswer(query) {
    const prompt = `
请直接回答以下问题(不要说"我不知道",基于你的知识给出一个可能的答案):

问题:${query}

回答:`;

    const hypotheticalAnswer = await callLLM(prompt);
    return hypotheticalAnswer;
  }

  /**
   * HyDE 检索
   */
  async retrieve(query, topK = 5) {
    console.log('📝 生成假设性答案...');
    
    // 步骤1:生成假设答案
    const hypotheticalDoc = await this.generateHypotheticalAnswer(query);
    console.log(`假设答案: ${hypotheticalDoc.substring(0, 100)}...`);

    // 步骤2:用假设答案检索
    console.log('🔍 使用假设答案检索...');
    const results = await vectorDB.search(hypotheticalDoc, topK);

    return results;
  }

  /**
   * 多假设 HyDE
   * 生成多个假设答案,分别检索,合并结果
   */
  async multiHyDE(query, numHypotheses = 3, topK = 5) {
    console.log(`📝 生成 ${numHypotheses} 个假设答案...`);
    
    const hypotheses = [];
    
    for (let i = 0; i < numHypotheses; i++) {
      const hypothesis = await this.generateHypotheticalAnswer(query);
      hypotheses.push(hypothesis);
    }

    // 用每个假设答案检索
    const allResults = await Promise.all(
      hypotheses.map(hyp => vectorDB.search(hyp, topK))
    );

    // 合并和去重
    const merged = this.mergeResults(allResults.flat(), topK);
    return merged;
  }

  mergeResults(results, topK) {
    // 按文档ID去重,保留最高分
    const seen = new Map();
    
    results.forEach(result => {
      const id = result.id;
      if (!seen.has(id) || seen.get(id).score < result.score) {
        seen.set(id, result);
      }
    });

    // 排序并返回 topK
    return Array.from(seen.values())
      .sort((a, b) => b.score - a.score)
      .slice(0, topK);
  }
}

// 使用示例
const hyde = new HyDE();

// 单假设
const results1 = await hyde.retrieve("什么是量子计算?");

// 多假设(更鲁棒)
const results2 = await hyde.multiHyDE("什么是量子计算?", 3);

效果对比

方法 准确率 召回率 适用场景
直接检索 70% 65% 常规问题
HyDE 80% 75% 开放性问题
多假设 HyDE 85% 80% 复杂问题

策略 16:Fusion (融合多种检索方法) 🔥🔥🔥

原理:结合多种检索方法的优势,互补不足。

实现代码

/**
 * RAG-Fusion
 * 融合多种检索策略
 */
class RAGFusion {
  constructor() {
    this.methods = ['vector', 'bm25', 'hybrid', 'hyde'];
  }

  /**
   * 多策略检索
   */
  async multiStrategyRetrieve(query, topK = 5) {
    console.log('🔄 使用多种策略检索...');

    // 策略1:向量检索
    const vectorResults = await vectorDB.search(query, topK * 2);

    // 策略2:BM25 关键词检索
    const bm25Results = await this.bm25Search(query, topK * 2);

    // 策略3:HyDE 假设性检索
    const hydeResults = await new HyDE().retrieve(query, topK * 2);

    // 策略4:查询扩展检索
    const expandedQuery = await this.expandQuery(query);
    const expandedResults = await vectorDB.search(expandedQuery, topK * 2);

    // 融合结果
    const fused = this.fuseResults({
      vector: vectorResults,
      bm25: bm25Results,
      hyde: hydeResults,
      expanded: expandedResults,
    }, topK);

    return fused;
  }

  /**
   * 倒数排序融合 (Reciprocal Rank Fusion)
   */
  fuseResults(resultSets, topK) {
    const k = 60;  // RRF 参数
    const scores = new Map();

    // 对每种方法的结果计算 RRF 分数
    Object.entries(resultSets).forEach(([method, results]) => {
      results.forEach((result, rank) => {
        const rrf = 1 / (k + rank + 1);
        const id = result.id;

        if (!scores.has(id)) {
          scores.set(id, {
            id: id,
            content: result.content,
            rrfScore: 0,
            sources: [],
          });
        }

        const entry = scores.get(id);
        entry.rrfScore += rrf;
        entry.sources.push({
          method: method,
          rank: rank + 1,
          originalScore: result.score,
        });
      });
    });

    // 按 RRF 分数排序
    return Array.from(scores.values())
      .sort((a, b) => b.rrfScore - a.rrfScore)
      .slice(0, topK);
  }

  /**
   * 加权融合
   */
  weightedFusion(resultSets, weights, topK) {
    const scores = new Map();

    Object.entries(resultSets).forEach(([method, results]) => {
      const weight = weights[method] || 1.0;

      results.forEach(result => {
        const id = result.id;
        const weightedScore = result.score * weight;

        if (!scores.has(id)) {
          scores.set(id, {
            id: id,
            content: result.content,
            fusedScore: 0,
          });
        }

        scores.get(id).fusedScore += weightedScore;
      });
    });

    return Array.from(scores.values())
      .sort((a, b) => b.fusedScore - a.fusedScore)
      .slice(0, topK);
  }
}

// 使用示例
const fusion = new RAGFusion();

// RRF 融合
const results = await fusion.multiStrategyRetrieve(query, 5);

// 或使用加权融合
const weighted = fusion.weightedFusion(
  {
    vector: vectorResults,
    bm25: bm25Results,
  },
  {
    vector: 0.7,  // 向量检索权重
    bm25: 0.3,    // BM25 权重
  },
  5
);

策略 17:CRAG (纠错检索增强生成) 🔥🔥

原理:自动评估检索结果质量,如果质量不好,采用补救措施(网络搜索、重新检索等)。

实现代码

/**
 * CRAG: Corrective RAG
 * 纠错检索增强生成
 */
class CRAG {
  /**
   * 评估检索质量
   */
  async evaluateRetrievalQuality(query, documents) {
    const scores = [];

    for (const doc of documents) {
      const prompt = `
评估文档对回答问题的有用程度:

问题:${query}

文档:${doc.content.substring(0, 500)}

评分(0-10,只输出数字):`;

      const response = await callLLM(prompt, { maxTokens: 10 });
      const score = parseFloat(response) || 0;
      
      scores.push({
        ...doc,
        qualityScore: score,
      });
    }

    // 计算平均质量分
    const avgScore = scores.reduce((sum, doc) => sum + doc.qualityScore, 0) / scores.length;

    return {
      documents: scores,
      averageQuality: avgScore,
      isGood: avgScore >= 6,  // 阈值
    };
  }

  /**
   * 知识精炼
   * 从文档中提取最相关的片段
   */
  async refineKnowledge(query, documents) {
    const refined = [];

    for (const doc of documents) {
      const prompt = `
从以下文档中提取与问题最相关的1-2句话:

问题:${query}

文档:${doc.content}

提取的关键句子:`;

      const response = await callLLM(prompt);
      
      refined.push({
        original: doc.content,
        refined: response,
        score: doc.score,
      });
    }

    return refined;
  }

  /**
   * CRAG 完整流程
   */
  async generate(query) {
    console.log('🔍 CRAG 检索流程...');

    // 步骤1:初始检索
    const initialResults = await vectorDB.search(query, 10);

    // 步骤2:评估检索质量
    const evaluation = await this.evaluateRetrievalQuality(query, initialResults);
    console.log(`检索质量: ${evaluation.averageQuality.toFixed(1)}/10`);

    let finalDocuments;

    if (evaluation.isGood) {
      // 质量好:直接使用
      console.log('✅ 检索质量良好');
      finalDocuments = evaluation.documents
        .filter(doc => doc.qualityScore >= 5)
        .slice(0, 5);
    } else {
      // 质量差:采取补救措施
      console.log('⚠️  检索质量不佳,启动纠错流程...');

      // 补救措施1:查询重写
      const rewrittenQuery = await this.rewriteQuery(query);
      console.log(`重写查询: ${rewrittenQuery}`);

      // 补救措施2:重新检索
      const correctedResults = await vectorDB.search(rewrittenQuery, 10);

      // 补救措施3:知识精炼
      finalDocuments = await this.refineKnowledge(query, correctedResults);

      // 补救措施4:如果还是不行,使用网络搜索
      if (finalDocuments.length === 0) {
        console.log('🌐 触发网络搜索...');
        finalDocuments = await this.webSearch(query);
      }
    }

    // 步骤3:生成答案
    const context = finalDocuments
      .map(doc => doc.refined || doc.content)
      .join('\n\n');

    const answer = await this.generateAnswer(query, context);

    return {
      answer: answer,
      retrievalQuality: evaluation.averageQuality,
      usedCorrection: !evaluation.isGood,
      sources: finalDocuments,
    };
  }

  /**
   * 查询重写
   */
  async rewriteQuery(query) {
    const prompt = `
将以下问题改写得更清晰、更具体:

原问题:${query}

改写后:`;

    return await callLLM(prompt);
  }

  /**
   * 网络搜索(备用)
   */
  async webSearch(query) {
    // 实际使用时可以接入搜索 API
    console.log('网络搜索功能(示例)');
    return [];
  }
}

// 使用示例
const crag = new CRAG();
const result = await crag.generate("最新的 AI 发展趋势?");

三、生成优化

策略 9:上下文压缩过滤 🔥🔥🔥

原理:检索到的文档可能包含很多无关内容,压缩后只保留与查询相关的部分。

实现代码

/**
 * 上下文压缩器
 */
class ContextCompressor {
  /**
   * 方法1:基于 LLM 的压缩
   */
  async compressWithLLM(query, documents) {
    const compressed = [];

    for (const doc of documents) {
      const prompt = `
任务:从文档中提取与问题相关的内容

问题:${query}

文档:
${doc.content}

要求:
1. 只保留与问题相关的句子
2. 保持原文表达
3. 去除无关内容

提取的内容:`;

      const response = await callLLM(prompt);
      
      if (response.trim().length > 0) {
        compressed.push({
          original: doc.content,
          compressed: response,
          compressionRatio: response.length / doc.content.length,
        });
      }
    }

    return compressed;
  }

  /**
   * 方法2:基于相似度的压缩
   */
  async compressWithSimilarity(query, documents) {
    const queryEmbedding = await getEmbedding(query);
    const compressed = [];

    for (const doc of documents) {
      // 将文档分句
      const sentences = this.splitSentences(doc.content);
      
      // 计算每句与查询的相似度
      const sentenceScores = await Promise.all(
        sentences.map(async (sentence) => {
          const sentenceEmbedding = await getEmbedding(sentence);
          const similarity = this.cosineSimilarity(queryEmbedding, sentenceEmbedding);
          
          return {
            sentence: sentence,
            similarity: similarity,
          };
        })
      );

      // 保留相似度高的句子
      const relevant = sentenceScores
        .filter(s => s.similarity > 0.5)
        .sort((a, b) => b.similarity - a.similarity)
        .slice(0, 5)  // 最多保留5句
        .map(s => s.sentence);

      if (relevant.length > 0) {
        compressed.push({
          original: doc.content,
          compressed: relevant.join(' '),
          keptSentences: relevant.length,
        });
      }
    }

    return compressed;
  }

  /**
   * 方法3:混合压缩
   */
  async hybridCompress(query, documents, maxTokens = 2000) {
    // 1. 先用相似度快速筛选
    const similarityFiltered = await this.compressWithSimilarity(query, documents);
    
    // 2. 再用 LLM 精细压缩
    const llmCompressed = await this.compressWithLLM(query, similarityFiltered);
    
    // 3. 确保不超过 token 限制
    const final = this.truncateToTokenLimit(llmCompressed, maxTokens);
    
    return final;
  }
}

// 使用示例
const compressor = new ContextCompressor();

// 检索文档
const retrieved = await vectorDB.search(query, 10);

// 压缩上下文
const compressed = await compressor.hybridCompress(query, retrieved, 2000);

// 使用压缩后的上下文生成
const context = compressed.map(doc => doc.compressed).join('\n\n');

效果

  • ✅ 减少 token 使用 50-70%
  • ✅ 提升生成质量(减少噪音)
  • ✅ 加快响应速度

策略 10:用户反馈存储 🔥🔥

原理:收集用户反馈,改进检索和生成效果。

实现代码

/**
 * 用户反馈系统
 */
class FeedbackSystem {
  /**
   * 存储用户反馈
   */
  async storeFeedback(query, answer, sources, feedback) {
    const feedbackEntry = {
      query: query,
      answer: answer,
      sources: sources.map(s => s.id),
      feedback: {
        rating: feedback.rating,  // 1-5 星
        isHelpful: feedback.isHelpful,
        comments: feedback.comments,
      },
      timestamp: new Date().toISOString(),
    };

    // 存储到数据库
    await db.collection('feedback').insertOne(feedbackEntry);

    // 如果反馈好,增强这个查询-答案对
    if (feedback.rating >= 4) {
      await this.reinforceGoodExample(query, answer, sources);
    }

    // 如果反馈差,分析原因
    if (feedback.rating <= 2) {
      await this.analyzeFailure(query, answer, sources);
    }
  }

  /**
   * 增强好的示例
   */
  async reinforceGoodExample(query, answer, sources) {
    // 1. 将查询-答案对添加到训练数据
    await db.collection('training_data').insertOne({
      query: query,
      ideal_answer: answer,
      sources: sources,
      type: 'positive_feedback',
    });

    // 2. 提高相关文档的权重
    for (const source of sources) {
      await vectorDB.updateScore(source.id, 1.1);  // 增加10%权重
    }

    // 3. 存储查询变体(用于后续相似查询)
    const variants = await this.generateQueryVariants(query);
    for (const variant of variants) {
      await db.collection('query_cache').insertOne({
        query: variant,
        answer: answer,
        originalQuery: query,
      });
    }
  }

  /**
   * 分析失败案例
   */
  async analyzeFailure(query, answer, sources) {
    const analysis = {
      query: query,
      issue: await this.identifyIssue(query, answer, sources),
      suggestions: [],
    };

    // 生成改进建议
    if (sources.length === 0) {
      analysis.suggestions.push('需要添加相关文档');
    } else if (sources.length > 0 && !this.isRelevant(query, sources)) {
      analysis.suggestions.push('检索策略需要优化');
    }

    await db.collection('failed_queries').insertOne(analysis);
  }

  /**
   * 基于反馈优化检索
   */
  async optimizeRetrieval(query) {
    // 1. 查找相似的成功案例
    const similarSuccess = await db.collection('training_data').find({
      query: { $regex: query, $options: 'i' },
      type: 'positive_feedback',
    }).toArray();

    if (similarSuccess.length > 0) {
      // 2. 使用成功案例的检索策略
      return await this.replicateSuccessStrategy(similarSuccess[0], query);
    }

    // 3. 常规检索
    return await vectorDB.search(query);
  }
}

// 前端集成示例
/**
 * 反馈按钮组件
 */
function FeedbackButtons({ query, answer, sources }) {
  const handleFeedback = async (rating, isHelpful, comments) => {
    await fetch('/api/feedback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query,
        answer,
        sources,
        feedback: { rating, isHelpful, comments },
      }),
    });
  };

  return (
    <div className="feedback">
      <button onClick={() => handleFeedback(5, true, '')}>👍 有帮助</button>
      <button onClick={() => handleFeedback(1, false, '')}>👎 没帮助</button>
    </div>
  );
}

策略 13:知识图谱节点检索 🔥

原理:将知识组织成图结构,利用实体关系进行检索。

实现思路

/**
 * 知识图谱增强检索
 * (简化示例,实际使用 Neo4j 等图数据库)
 */
class KnowledgeGraphRAG {
  /**
   * 从文本中抽取实体和关系
   */
  async extractEntities(text) {
    const prompt = `
从以下文本中抽取实体和关系:

文本:${text}

输出格式:
实体:[实体1, 实体2, ...]
关系:[(实体1, 关系类型, 实体2), ...]

你的输出:`;

    const response = await callLLM(prompt);
    return this.parseEntitiesAndRelations(response);
  }

  /**
   * 构建知识图谱
   */
  async buildKnowledgeGraph(documents) {
    const graph = { nodes: [], edges: [] };

    for (const doc of documents) {
      const { entities, relations } = await this.extractEntities(doc.content);
      
      // 添加节点
      entities.forEach(entity => {
        if (!graph.nodes.find(n => n.name === entity)) {
          graph.nodes.push({
            name: entity,
            documents: [doc.id],
          });
        }
      });

      // 添加边
      relations.forEach(([source, relation, target]) => {
        graph.edges.push({
          source: source,
          target: target,
          relation: relation,
          document: doc.id,
        });
      });
    }

    return graph;
  }

  /**
   * 基于图的检索
   */
  async graphSearch(query, graph) {
    // 1. 识别查询中的实体
    const queryEntities = await this.extractEntities(query);
    
    // 2. 在图中查找相关节点
    const relevantNodes = graph.nodes.filter(node =>
      queryEntities.entities.some(qe => node.name.includes(qe))
    );

    // 3. 扩展到邻居节点(1-hop)
    const expanded = this.expandNodes(relevantNodes, graph, 1);

    // 4. 返回相关文档
    const documentIds = [...new Set(expanded.flatMap(n => n.documents))];
    return await this.getDocumentsByIds(documentIds);
  }
}

🎯 实施优先级

阶段 1:基础优化(1-2周)

必须实施:

  1. 文本切分优化 - 1天
  2. 上下文增强 - 1天
  3. 查询转换重写 - 2天
  4. 二次排序 - 3天

阶段 2:进阶优化(2-3周)

推荐实施:
5. ✅ 上下文压缩 - 3天
6. ✅ 场景细分 - 4天
7. ✅ Fusion 融合 - 4天
8. ✅ HyDE - 2天

阶段 3:高级优化(按需)

可选实施:
9. ✅ Self-RAG - 5天
10. ✅ CRAG - 5天
11. ✅ 知识图谱 - 2周+


📊 综合实施方案

完整 RAG Pipeline

/**
 * 企业级 RAG Pipeline
 * 集成多种优化策略
 */
class EnterpriseRAG {
  constructor(config) {
    this.textSplitter = new SmartTextSplitter(config.docType);
    this.contextEnhancer = new ContextEnhancer(2);
    this.queryRewriter = new QueryRewriter();
    this.reranker = new Reranker('hybrid');
    this.compressor = new ContextCompressor();
    this.router = new SceneRouter();
    this.hyde = new HyDE();
    this.fusion = new RAGFusion();
    this.crag = new CRAG();
  }

  /**
   * 文档处理流程
   */
  async ingestDocument(document, metadata) {
    // 1. 智能切分
    const chunks = this.textSplitter.split(document);
    
    // 2. 为 chunk 添加描述
    const descriptor = new ChunkDescriptor();
    const enriched = await descriptor.addDescriptionsToChunks(chunks);
    
    // 3. 上下文增强
    const enhanced = this.contextEnhancer.storeWithContext(enriched);
    
    // 4. 向量化并存储
    await vectorDB.store(enhanced, metadata);
    
    return {
      success: true,
      chunksCount: chunks.length,
    };
  }

  /**
   * 智能检索流程
   */
  async retrieve(query, options = {}) {
    // 1. 场景路由
    const scene = await this.router.detectScene(query);
    
    // 2. 查询转换
    const rewritten = await this.queryRewriter.rewrite(query);
    
    // 3. 多策略检索(Fusion)
    const fusionResults = await this.fusion.multiStrategyRetrieve(
      rewritten.rewritten,
      options.topK * 2
    );
    
    // 4. 重排序
    const reranked = await this.reranker.hybridRerank(
      query,
      fusionResults,
      options.topK
    );
    
    // 5. 连续片段合并
    const merger = new ConsecutiveChunkMerger();
    const merged = merger.mergeConsecutiveChunks(reranked);
    
    return merged;
  }

  /**
   * 完整的 RAG 生成流程
   */
  async generate(query, options = {}) {
    console.log('🚀 企业级 RAG 流程启动...');

    // 使用 CRAG 自动纠错
    const result = await this.crag.generate(query);
    
    // 如果质量不佳,使用 Self-RAG
    if (result.retrievalQuality < 6) {
      console.log('⚠️  切换到 Self-RAG...');
      const selfRAG = new SelfRAG();
      return await selfRAG.generate(query);
    }

    // 上下文压缩
    const compressed = await this.compressor.hybridCompress(
      query,
      result.sources,
      options.maxTokens || 2000
    );

    // 生成最终答案
    const context = compressed.map(doc => doc.compressed).join('\n\n');
    const answer = await this.generateAnswer(query, context);

    return {
      answer: answer,
      sources: result.sources,
      retrievalQuality: result.retrievalQuality,
      compressionRatio: this.calculateCompressionRatio(result.sources, compressed),
      pipeline: {
        scene: result.scene,
        usedCorrection: result.usedCorrection,
        strategiesUsed: ['fusion', 'rerank', 'compress', 'crag'],
      },
    };
  }
}

// 使用示例
const rag = new EnterpriseRAG({ docType: 'technical' });

// 添加文档
await rag.ingestDocument(document, { type: 'manual' });

// 查询
const result = await rag.generate("如何部署 Ollama?");
console.log(result.answer);

📈 效果对比

优化策略组合 准确率 响应时间 成本 推荐
基础 RAG 60% 2s -
+ 文本切分 + 上下文增强 75% 2s ✅ 入门
+ 查询转换 + 重排序 85% 4s ✅ 标准
+ Fusion + 压缩 90% 5s ✅ 进阶
+ Self-RAG + CRAG 95% 7s ⚠️ 高级

🎊 总结

快速上手路线

第1天:文本切分 + 上下文增强
第2-3天:查询转换 + 重排序
第4-5天:上下文压缩
第2周:Fusion + HyDE
第3周:Self-RAG + CRAG

性价比最高的组合

文本切分优化 (策略1)
+ 上下文增强 (策略3)
+ 查询转换 (策略6)
+ 二次排序 (策略7)
+ 上下文压缩 (策略9)
= 85% 准确率,中等成本

旗舰级完整方案

所有17种策略全部集成
= 95%+ 准确率,但成本高、复杂度高
推荐:大型企业、核心业务

更新时间:2026-01-26
文档版本:v1.0

🎯 从基础做起,逐步优化!

posted @ 2026-01-26 16:59  XiaoZhengTou  阅读(1)  评论(0)    收藏  举报