首页 / 文章 / 实用指南:如何在您的系统中实现RAG(检索增强生成)

实用指南:如何在您的系统中实现RAG(检索增强生成)

《实用指南》操作流程详解:如何在该模式的应用中,为合同、校验规则以及团队使用的代码模板实现 RAG(检索增强生成)功能。

2530 词

可将此内容作为《如何在您的 Web 应用程序中实现 RAG(检索增强生成)》一文的操作员版重述:清晰的阶段划分、有序的代码模块以及便于交接时参考的恢复说明。 在“概览”阶段,若能将其视为可量化的界面来使用效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个故障案例以及对应的回滚说明。 应将配置信息与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放于一个位置,这样操作员无需查看整个系统结构即可进行审核。

了解 RAG 背后的架构

在“理解底层架构”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的内容。 必须引用实际作为答案依据的段落。如果没有引用,操作人员就无法区分是虚假信息还是索引缺失导致的问题。

User Question
      |
      v
Generate Query Embedding
      |
      v
Metadata Filtering
      |
      v
Vector Similarity Search
      |
      v
Top-K Relevant Documents
      |
      v
Context Construction
      |
      v
LLM / Gemini
      |
      v
Grounded Response + Sources

搭建嵌入式基础设施

在设置嵌入阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任主体,而非复杂的流程链。必须引用实际作为答案依据的段落;没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失所致。

const { PredictionServiceClient, helpers } =
  require('@google-cloud/aiplatform');
const PROJECT_ID = process.env.PROJECT_ID;
const client = new PredictionServiceClient({
  apiEndpoint: 'aiplatform.googleapis.com'
});
async function generateEmbedding(
  text,
  taskType = 'RETRIEVAL_DOCUMENT'
) {
  const endpoint =
    `projects/${PROJECT_ID}/locations/global/` +
    `publishers/google/models/gemini-embedding-001`;
  const instance = {
    content: text,
    task_type: taskType
  };
  const request = {
    endpoint,
    instances: [helpers.toValue(instance)]
  };
  const [response] = await client.predict(request);
  return response.predictions[0].embeddings.values;
}

生成嵌入时批量处理为何重要

在“批量处理为何重要”阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,并杜绝无声的半完成状态。 需引用实际作为答案依据的段落。没有引用的话,操作员就无法区分是幻觉还是索引缺失导致的错误。 在“批量处理为何重要”阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个操作员能够审核的地方,无需阅读全部内容。

aph。

const EMBEDDING_CONFIG = {
  maxSegmentsPerRequest: 100,
  maxTokensPerRequest: 18000,
  concurrency: 3,
  tokenEstimateDivisor: 3
};
function estimateTokens(text) {
  return Math.ceil(
    text.length / EMBEDDING_CONFIG.tokenEstimateDivisor
  );
}
function packIntoBatches(texts) {
  const batches = [];
  let currentBatch = [];
  let currentTokens = 0;
  for (const text of texts) {
    const tokens = estimateTokens(text);
    const exceedsCount =
      currentBatch.length >=
      EMBEDDING_CONFIG.maxSegmentsPerRequest;
    const exceedsTokens =
      currentTokens + tokens >
      EMBEDDING_CONFIG.maxTokensPerRequest;
    if (exceedsCount || exceedsTokens) {
      if (currentBatch.length > 0) {
        batches.push(currentBatch);
      }
      currentBatch = [text];
      currentTokens = tokens;
    } else {
      currentBatch.push(text);
      currentTokens += tokens;
    }
  }
  if (currentBatch.length > 0) {
    batches.push(currentBatch);
  }
  return batches;
}

使用 Firebase Firestore 进行向量搜索

在“使用 Firebase Firestore”阶段工作时,首先明确需求规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要优化的内容。 在调整提示词之前,先使用固定的问题集来衡量检索的召回率。仅仅更换提示词很难解决检索效果不佳的问题。

const { Firestore } = require('@google-cloud/firestore');
const firestore = new Firestore();
async function storeDocumentWithEmbedding(
  collectionPath,
  docId,
  text,
  embedding,
  metadata
) {
  const docRef =
    firestore.doc(`${collectionPath}/${docId}`);
  await docRef.set({
    text,
    embedding,
    ...metadata,
    createdAt: Firestore.FieldValue.serverTimestamp()
  });
}
async function findSimilarDocuments(
  collectionPath,
  queryEmbedding,
  limit = 5
) {
  const collectionRef =
    firestore.collection(collectionPath);
  const vectorQuery = collectionRef.findNearest({
    vectorField: 'embedding',
    queryVector: queryEmbedding,
    limit,
    distanceMeasure: 'DOT_PRODUCT'
  });
  const snapshot = await vectorQuery.get();
  return snapshot.docs.map(doc => ({
    id: doc.id,
    data: doc.data()
  }));
}

将元数据过滤与语义检索相结合

在处理“结合元数据过滤”阶段时,首先需明确相关约定:所需的输入参数、成功标志,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 建议采用小型、可测试的单元而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 在调整提示词之前,先使用固定的问题集来评估召回率。仅仅更换提示词很难改善较差的检索效果。

async function retrieveContext(
  queryText,
  selectedTopics = [],
  limit = 5
) {
  const queryEmbedding =
    await generateEmbedding(
      queryText,
      'RETRIEVAL_QUERY'
    );
let collectionRef =
    firestore.collection('knowledge_base');
  if (selectedTopics.length > 0) {
    collectionRef = collectionRef.where(
      'topics',
      'array-contains-any',
      selectedTopics
    );
  }
  const vectorQuery =
    collectionRef.findNearest({
      vectorField: 'embedding',
      queryVector: queryEmbedding,
      limit,
      distanceMeasure: 'DOT_PRODUCT'
    });
  const snapshot = await vectorQuery.get();
  return snapshot.docs.map(doc => {
    const data = doc.data();
    return {
      text: data.text,
      source: data.source,
      metadata: data.metadata || {}
    };
  });
}

将检索到的文档转化为模型上下文

在“将获取的文档转换”阶段工作时,首先需明确相关契约:所需的输入参数、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合约定。 应将此阶段视为输入与验证后输出之间的契约。为相关产物命名,定义成功检测标准,杜绝默许的部分完成情况。 缓存系统中稳定的指令和工具结构。重复发送相同的开头信息是导致资源浪费的常见原因。 在“将获取的文档转换”阶段工作时,首先需明确相关契约:所需的输入参数、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合约定。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一处,以便操作人员无需查看整个系统结构即可进行审计。

async function generateRAGResponse(
  userQuery,
  contextDocuments
) {
  const contextSection =
    contextDocuments
      .map((doc, index) => {
        return `
### Reference ${index + 1}
${doc.text}
Source: ${doc.metadata?.source || 'Unknown'}
`;
      })
      .join('\n');
const prompt = `
You are an AI assistant with access
to a knowledge base.
Use the provided context to answer
the user's question accurately.
CONTEXT:
${contextSection}
USER QUESTION:
${userQuery}
INSTRUCTIONS:
1. Answer using the provided context.
2. If the context is insufficient, say so clearly.
3. Cite the references used.
4. Do not invent information.
ANSWER:
`;
  const result =
    await genAI.models.generateContent({
      model: 'gemini-2.5-flash-lite',
      contents: prompt
    });
  return result.text.trim();
}

利用缓存与重试机制优化RAG

将“利用缓存优化RAG”这一环节视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一个理想的处理案例、一个失败案例以及回滚说明。 同时记录正常流程与故障恢复流程。重试机制、人工审核环节以及错误处理都属于产品功能的一部分,而非后续需要补充的内容。 应将分块策略与检索策略分开。当质量指标发生变化时,调整其中一项不应强制要求重新编写另一项。

const embeddingCache = new Map();
async function getCachedEmbedding(text, taskType) {
  const cacheKey = `${taskType}:${text}`;
  if (embeddingCache.has(cacheKey)) {
    return embeddingCache.get(cacheKey);
  }
  const embedding =
    await generateEmbedding(text, taskType);
  embeddingCache.set(cacheKey, embedding);
  return embedding;
}
async function retryWithBackoff(
  fn,
  maxRetries = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (
        error.code === 429 ||
        error.message.includes('rate limit')
      ) {
        const delay =
          Math.pow(2, attempt) * 1000;
        await new Promise(resolve =>
          setTimeout(resolve, delay)
        );
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

检索多种类型的上下文

将“检索多种类型阶段”视为可度量的对象来处理时效果最佳。在扩大范围之前,先记录一份成功的案例、一个失败案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

async function retrieveMultiContext(
  queryText,
  options = {}
) {
  const {
    includeDefinitions = true,
    includeExamples = true,
    includeHistorical = false,
    topics = []
  } = options;
const queryEmbedding =
    await generateEmbedding(
      queryText,
      'RETRIEVAL_QUERY'
    );
  const contextPromises = [];
  if (includeDefinitions) {
    contextPromises.push(
      findSimilarDocuments(
        'definitions',
        queryEmbedding,
        3
      ).then(documents => ({
        type: 'definitions',
        documents
      }))
    );
  }
  if (includeExamples) {
    contextPromises.push(
      findSimilarDocuments(
        'examples',
        queryEmbedding,
        5
      ).then(documents => ({
        type: 'examples',
        documents
      }))
    );
  }
  const contexts =
    await Promise.all(contextPromises);
  return contexts;
}

通过监控RAG而非猜测质量来评估

将“监控型RAG替代阶段”视为可度量的工作面时,其效果最佳。在扩大范围之前,先记录一份理想案例、一个失败案例以及回滚说明。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的半完成状态。 将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应强制重新编写另一项。 将“监控型RAG替代阶段”视为可度量的工作面时,其效果最佳。在扩大范围之前,先记录一份理想案例、一个失败案例以及回滚说明。 将配置置于应用程序代码之外。环境文件、密钥存储和功能标志应集中存放于一处,以便操作人员无需查看整个系统结构即可进行审计。

class RAGMetrics {
  constructor() {
    this.metrics = {
      totalQueries: 0,
      averageLatency: 0,
      retrievalAccuracy: [],
      errors: []
    };
  }
logQuery(
    query,
    contextCount,
    latency,
    sources
  ) {
    this.metrics.totalQueries++;
    const previousLatency =
      this.metrics.averageLatency *
      (this.metrics.totalQueries - 1);
    this.metrics.averageLatency =
      (previousLatency + latency) /
      this.metrics.totalQueries;
    console.log({
      query: query.substring(0, 100),
      contextCount,
      latency,
      sourceCount: sources.length,
      timestamp: new Date().toISOString()
    });
  }
  logRetrievalAccuracy(
    retrievedDocs,
    relevantDocs
  ) {
    const retrievedIds =
      new Set(retrievedDocs.map(d => d.id));
    const relevantIds =
      new Set(relevantDocs.map(d => d.id));
    const intersection =
      new Set(
        [...retrievedIds]
          .filter(id => relevantIds.has(id))
      );
    const precision =
      intersection.size / retrievedIds.size;
    const recall =
      intersection.size / relevantIds.size;
    this.metrics.retrievalAccuracy.push({
      precision,
      recall
    });
  }
}

真正的生产环境RAG流程

对于“真实生产 RAG”阶段,在修改代码之前需明确输入内容、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 必须注明实际用于支撑答案的原文段落。如果没有引用依据,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

DOCUMENT INGESTION
                    |
                    v
          Clean + Split Documents
                    |
                    v
            Generate Embeddings
                    |
                    v
       Firestore + Metadata Storage
                    |
                    |
USER QUERY --------+
                    |
                    v
           Query Embedding
                    |
                    v
       Authorization + Filters
                    |
                    v
          Vector Similarity Search
                    |
                    v
          Relevant Context
                    |
                    v
          Prompt Construction
                    |
                    v
              Gemini / LLM
                    |
                    v
       Answer + Sources + Metrics

作为高级工程师应重点关注的内容

在“明确关注点”阶段,应在修改代码之前确定输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任模块,而非复杂的流程链。必须引用实际作为答案依据的段落;没有引用的话,操作人员就无法区分是虚假信息还是索引缺失导致的错误。

结论:构建可用于生产环境的RAG系统

结论:在构建可用于生产的阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。 结论:在构建可用于生产的阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于操作人员能够审核的位置。

无需阅读整个图表。

操作检查清单

在处理操作检查清单阶段时,首先写下相关约定:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。

在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免从演示环境过渡到共享环境时出现意外费用。

在调整提示词之前,先使用固定的问题集测试召回率。仅仅更换提示词往往无法改善较差的检索效果。

在更改提示词或模型之前,先锁定一个基准数据集。同时改变系统和评估标准会掩盖功能退化的问题。

只要预算允许,就在持续集成过程中使用测试数据而非真实的付费 API 来添加能够检测关键路径的冒烟测试。

应优先选择小型、可测试的单元,而非结构复杂的脚本。当某个步骤出错时,故障应能指向单一责任模块,而非混乱的流程链。

在推广该技术栈之前,需冻结版本、为关键流程记录标准输出日志,并确认回滚步骤。共享环境需要设置访问速率限制、进行租户身份验证,同时明确密钥轮换的负责人。与其追求花哨的一次性演示,不如注重扎实的可靠性。

关于9607363b4f86的批注:请将服务提供商密钥移出代码仓库,设定单次会话的令牌上限,并将日志存储在评估用示例文件旁,以便后续模型更换时保持数据可比性。