首页 / 文章 / 扩展 RAG 系统:生产架构、性能与成本优化
← 返回
AI技术

扩展 RAG 系统:生产架构、性能与成本优化

✍️ zhirenhun 📅 2026/8/20 👁 207 阅读 ⏱ 52 分钟
扩展 RAG 系统:生产架构、性能与成本优化

本系列的前三部分介绍了生产环境中 RAG 系统失败的原因,以及数据基础的质量如何直接影响其后的一切。我们研究了文档摄入、解析、分块和元数据设计——这些层负责将原始信息转换为检索系统实际上可以使用的形式。

然后我们深入探讨了检索本身。我们看到,仅依赖向量搜索往往不足够,语义搜索和词法搜索如何互补,以及重排序如何将大量可能的匹配结果转化为少量高度相关的文档。

但即使检索再完美,如果大语言模型无法正确使用,也毫无意义。

这就是本部分的起点。

在第 4 部分,我们将从检索转向生成。我们将探讨系统找到正确的块之后会发生什么:如何在不丢失意义的情况下压缩上下文,如何构建使模型保持基础的提示,以及如何评估整个管道是否真的在工作。

我们将涵盖:

这些不是可选的优化。它们是将检索转化为人们可以信任的答案的层次。

生产 RAG 架构系列

  1. 为什么大多数 RAG 系统在生产环境中会失败:AI 搜索背后的隐藏架构问题

  2. 构建生产环境 RAG 管道:文档处理、分块和元数据设计

  3. 超越向量搜索:通过混合搜索和重排序构建更好的 RAG 检索

  4. 扩展 RAG 系统:生产架构、性能和成本优化 (您在此处)

  5. 评估生产环境 RAG 系统:指标、监控和常见失败模式

在本部分结束时,您将了解如何将检索到的上下文转化为可靠的答案,以及如何衡量您的系统是否真的在随时间改进。


第10章 — 上下文压缩

检索可以给你正确的碎片。

这并不意味着大语言模型应该看到所有这些碎片。

“这个块是相关的”和“这个块属于提示”之间存在差异。一个块可能相关,但仍然太长、太噪或充满无关句子。如果你把检索器找到的所有内容喂给模型,你会付出更多代码,等待更久,并且经常得到更差的答案。

这就是上下文压缩存在的原因。


实际问题

典型的 RAG 流水线可能会检索 10–20 个块。每个块可能包含 100–300 个词。这轻松达到数千个标记。

但答案通常仅取决于几句话。

剩余部分是:

如果模型看到所有这些内容,它就必须做额外的工作。它必须弄清楚你给它的上下文中什么才重要。这正是你的检索系统应该已经完成的工作。

这不仅仅是关于成本。而是关于信噪比。


为什么压缩很重要

压缩可以改善三个方面:

最后一点是最重要的。当上下文更清晰时,模型编造的空间更少。它需要调和的矛盾信息更少。它错误地抓住错误句子的机会更少。


具体示例

想象这些检索到的块:

Chunk 1:
"Customers can upgrade from Professional to Enterprise. Active invoices must be closed before upgrading. Contact billing if invoices remain open. For more information, see the billing policy."

Chunk 2:
"Billing support is available Monday to Friday. For payment issues, contact billing@example.com. Note that invoice processing may take up to 48 hours."

Chunk 3:
"Downgrading is allowed only if no active trials exist. See the cancellation policy for details. Customers on annual plans have different terms."

查询:

“企业客户能否直接从专业版计划升级,同时保持活跃发票?”

只有一句话真的重要:

“活跃发票必须在升级前关闭。”

其余的是上下文,但不是证据。压缩应保留这句话并删除其余部分。

没有压缩,模型会看到所有三个块。它必须弄清楚哪部分相关。这是额外的认知负荷。幻觉就从这里开始。


压缩实际上做了什么

上下文压缩并不是为了让文本变小而变小。它是关于保留证据并删除噪声。

主要有三种策略:

  1. 过滤 – 删除不 useful 的整个块。

  2. 提取 – 只保留每个块中最相关的句子。

  3. 摘要 – 将剩余文本压缩成更短的形式。

大多数生产系统首先使用过滤,然后使用提取,只有在令牌预算极其紧张时才使用摘要。


过滤

过滤是最便宜的压缩形式。您根据查询对每个块进行评分,并删除低于阈值的内容。

这通常使用交叉编码器或轻量级重排器来完成。想法很简单:如果块不够相关,就根本不要发送给LLM。

from sentence_transformers import CrossEncoder

compressor = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

def filter_chunks(query, chunks, threshold=0.5):
    pairs = [[query, chunk["text"]] for chunk in chunks]
    scores = compressor.predict(pairs)

    filtered = [(chunk, score) for chunk, score in zip(chunks, scores) if score > threshold]
    filtered.sort(key=lambda x: x[1], reverse=True)

    return [chunk for chunk, score in filtered]

这是第一道防线。它会删除没有用的整个块。

提取

提取更精确。与其删除整个块,不如只保留每个块中最相关的句子。

当一个块同时包含相关和无关信息时,这很有用。你既不想丢失相关部分,也不想发送噪声。

一种简单的方法是将块分割成句子,对每个句子评分,然后保留得分最高的几个。

def extract_sentences(query, chunk, top_n=3):
    sentences = chunk["text"].split(". ")
    pairs = [[query, sentence] for sentence in sentences]
    scores = compressor.predict(pairs)

    ranked = sorted(zip(sentences, scores), key=lambda x: x[1], reverse=True)
    top_sentences = [sentence for sentence, score in ranked[:top_n]]

    return ". ".join(top_sentences)

这保留了证据并删除了块的其余部分。


摘要

摘要是最昂贵的选项。您要求LLM将上下文重写为更短的形式。

在这种情况下很有用:

  • 您有许多块,

  • 令牌预算紧张,

  • 或者上下文是重复的。

但这会带来成本。摘要可能会丢失细节。它可能会引入错误。它可能会改变含义。

这就是为什么大多数生产系统仅在必要时才使用摘要。

def summarize_context(query, chunks, llm):
    context = "\n\n".join([chunk["text"] for chunk in chunks])

    prompt = f"""
Summarize the following context in relation to this query: "{query}"

Context:
{context}

Keep only the information that is directly relevant to answering the query.
Remove any redundant or unrelated information.

Summary:
"""
    return llm.generate(prompt)

这很强大但代价高昂。请谨慎使用。


何时使用每种策略

过滤 应始终使用。它既便宜又有效。如果一个块不相关,则不要发送它。

提取 应在块较长或包含混合内容时使用。它在不丢失结构的情况下保留相关部分。

摘要 应在令牌预算紧张或您有许多相似的块时使用。虽然代价高昂,但可以节省大量令牌。


隐藏的问题:过度压缩

压缩可能过度。

如果你过于激进地压缩,你可能会:

  • 丢失重要细节,

  • 移除模型所需的上下文,

  • 或破坏信息的结构。

例如,如果你从包含多个条件的策略的块中只提取一句话,模型可能会错过全貌。

这就是为什么压缩应该进行调整,而不是最大化。


监控压缩质量

您应该跟踪压缩对您的指标的影响。

  • 忠实度是否提高?

  • 答案相关性是否提高?

  • 延迟是否降低?

  • 成本是否降低?

如果压缩改善了成本和延迟但损害了质量,那么你压缩得太多了。


示例:完整压缩流水线

def compress_context(query, chunks, llm=None, token_budget=2000):
    # Step 1: Filter chunks
    filtered = filter_chunks(query, chunks, threshold=0.4)

    # Step 2: Extract sentences from each chunk
    extracted = []
    for chunk in filtered:
        extracted_text = extract_sentences(query, chunk, top_n=3)
        extracted.append({"text": extracted_text})

    # Step 3: Check token budget
    total_tokens = sum(len(chunk["text"].split()) * 1.3 for chunk in extracted)

    if total_tokens > token_budget and llm:
        # Step 4: Summarize if over budget
        context = summarize_context(query, extracted, llm)
        return [context]

    return extracted

这是一个结合了所有三种策略的简单流水线。


何时不应压缩

压缩并不总是正确的做法。

在以下情况下您应该小心:

  • 块包含代码,

  • 块包含表格,

  • 块已经很短,

  • 或者上下文已经很紧。

在这些情况下,激进的压缩可能会删除重要的结构或细节。


真正的教训

上下文压缩不是可选的优化。它是一种确保LLM看到正确证据而不仅仅是大量文本的方法。

如果检索是网,压缩就是移除不需要的鱼的那只手。

目标不是让上下文尽可能小。目标是让它尽可能有用。


第11章 — 提示构建

一个好的提示无法挽救糟糕的检索。

一个坏的提示会毁掉好的检索。

提示是一切汇聚的地方:查询、检索到的上下文、指令、输出格式和防护栏。如果这些部分中的任何一个薄弱,答案就会薄弱。但如果检索已经损坏,即使是最好的提示也只会让错误的答案听起来更有信心。


提示的作用

提示有三项任务:

  1. 使模型基于事实 – 明确表明答案必须来自提供的上下文。

  2. 构建答案 – 定义输出应呈现的形式。

  3. 设置防护栏 – 告诉模型在上下文不足时应如何处理。

这听起来很简单,但大多数生产提示在这些方面至少会失败一个。


为什么基础很重要

最常见的失败模式是模型忽略上下文,而是依赖自身知识回答。这就是为什么指令“仅使用提供的上下文回答”不是装饰性的。它是RAG忠实性的核心。

没有该指令,模型可能会:

  • 编造细节,

  • 混合不同文档中的策略,

  • 或者自信地陈述不在上下文中的内容。

这就是为什么基础是首要任务。


基本的RAG提示结构

You are a helpful assistant that answers questions based ONLY on the provided context.

Context:
{retrieved_chunks}

Question:
{query}

Instructions:
- Answer using only the information in the context.
- If the answer cannot be found, say "I don't have enough information."
- Cite the source document and section when possible.
- Keep the answer concise and direct.

Answer:

这是最小可行的形状。它告诉模型该做什么、不该做什么以及如何处理不确定性。


良好提示的结构

生产提示通常包含以下组件:

  1. 系统角色 – 定义助手的行为。

  2. 上下文 – 检索到的块。

  3. 问题 – 用户查询。

  4. 指令 – 如何回答。

  5. 输出格式 – 答案应呈现的形式。

  6. 防护栏 – 上下文不足时应采取的措施。

每个组件都很重要。


系统角色

系统角色设定基调。它告诉模型它是什么类型的助手。

You are a helpful assistant that answers questions based ONLY on the provided context.
You do not use outside knowledge.
You do not invent information.
If the answer is not in the context, you say so.

这不仅仅是填充文字。它是塑造整个生成过程的约束。


上下文

上下文是检索到的块,通常在压缩之后。

您如何格式化上下文很重要。

一种常见的做法是为每个块编号并包含元数据:

Context:

[1] Document: Pricing Policy, Section: Upgrading Plans
Customers can upgrade from Professional to Enterprise. Active invoices must be closed before upgrading.

[2] Document: Billing Policy, Section: Payment Terms
Invoices must be paid within 30 days. Failure to pay may result in service suspension.

[3] Document: Support Policy, Section: Contact
Billing support is available Monday to Friday. Contact billing@example.com.

这种格式使得模型更容易引用来源,并且您以后调试也更方便。


问题

问题应该清晰且与上下文隔离。

Question:
Can Enterprise customers upgrade directly from the Professional plan while keeping active invoices?

不要将问题与上下文混淆。保持它们分离。


说明

说明告诉模型如何回答。

良好的说明:

  • 具体,

  • 可操作的,

  • 并且覆盖边界情况。

Instructions:
- Answer using only the information in the context.
- If the answer cannot be found, say "I don't have enough information to answer this question from the provided documents."
- Cite the source document and section when possible.
- Keep the answer concise and direct.
- Do not repeat the context verbatim.

请注意针对缺失信息的明确指示。这一点至关重要。


输出格式

输出格式取决于您的使用场景。

对于聊天机器人:

Answer in 2-3 sentences.

对于结构化的API:

Answer in JSON format:
{
  "answer": "...",
  "citations": [
    {"document": "...", "section": "..."}
  ]
}

对于技术助理:

Answer in bullet points.
Include code examples when relevant.

格式应匹配产品,而非模型。


防护栏

防护栏是安全网。

它们告诉模型在以下情况时应做什么:

  • 上下文不足时,

  • 问题不明确时,

  • 或者答案需要外部知识。

Guardrails:
- If the context does not contain the answer, say "I don't have enough information."
- If the question is ambiguous, ask for clarification.
- Do not provide legal, medical, or financial advice.
- Do not speculate.

这些不是可选的。它们是知道自身极限的系统与自信地幻觉的系统之间的区别。


示例代码

def build_prompt(query, chunks):
    # Format context with metadata
    context_parts = []
    for i, chunk in enumerate(chunks, 1):
        metadata = chunk.get("metadata", {})
        doc = metadata.get("title", "Unknown")
        section = metadata.get("section", "Unknown")
        context_parts.append(f"[{i}] Document: {doc}, Section: {section}\n{chunk['text']}")

    context = "\n\n".join(context_parts)

    prompt = f"""
You are a helpful assistant that answers questions based ONLY on the provided context.
You do not use outside knowledge.
You do not invent information.
If the answer is not in the context, you say so.

Context:

{context}

Question:
{query}

Instructions:
- Answer using only the information in the context.
- If the answer cannot be found, say "I don't have enough information to answer this question from the provided documents."
- Cite the source document and section when possible.
- Keep the answer concise and direct.
- Do not repeat the context verbatim.

Output format:
- Answer in 2-3 sentences.
- Include citations with document title and section.

Answer:
"""
    return prompt

这是基本形状。生产提示通常会添加更多防护措施,但核心思想是相同的。


常见提示错误

错误 1:没有基础指令

Bad:
Context: {context}
Question: {query}
Answer:

模型没有指令保持在上下文中。它将根据自身知识回答。

错误2:未处理缺失信息

Bad:
- Answer using only the information in the context.

如果上下文没有答案,模型会猜测。

错误3:没有输出格式

Bad:
- Answer the question.

模型不知道答案应该多长或应该使用什么格式。

错误 4:上下文过多

如果您发送 10 个未压缩的块,模型必须在噪声中寻找信号。这就是幻觉开始的时候。


测试提示

提示应像代码一样进行测试。

构建一个小型查询和预期答案的数据集。对它们运行您的提示。检查:

  • 模型是否保持基于事实?

  • 它是否能处理缺失的信息?

  • 它是否遵循输出格式?

  • 它是否正确引用来源?

这不是可选的。这是您捕获提示回归的方法。


核心教训

提示不是用来修复检索的地方。而是用来确保您已有的检索被正确使用的地方。

如果上下文良好,良好的提示会使答案更好。

如果上下文不好,良好的提示只是让错误的答案更明显。

提示不是魔法。它是一组指令。和任何指令一样,只有在基础坚固时才有效。


第 12 章 — 评估

您无法改进未衡量的事物。

这就是本章的全部内容。

大多数 RAG 系统在生产环境中失败并不是因为组件损坏,而是因为没有人知道它们何时在变差。评估是将主观“感觉更好”转化为客观“更好”的一层。

没有评估,您就是在盲目飞行。您更改分块策略。您切换嵌入模型。您调整提示。然后呢?您查看几个查询并说‘看起来更好’?这不是工程。这就是猜测。


评估为什么重要

评估为您提供:

  • 基准,

  • 回归检测,

  • 质量阈值,

  • 以及一种比较更改的方法。

它将 RAG 从一个黑箱转变为您可以实际改进的系统。


核心指标

大多数生产系统会跟踪一小组指标:

  • 忠实度 – 答案是否基于上下文?

  • 答案相关性 – 答案是否解决了问题?

  • 上下文精度 – 检索到的上下文中有多少是相关的?

  • 上下文召回 – 检索是否找到了正确的内容?

  • 延迟 – 每一步需要多长时间?

  • 成本 – 每个查询多少个标记?

这些指标涵盖检索和生成两方面。


忠实度

忠实度衡量答案中的陈述是否得到上下文的支持。

如果模型说出上下文中不存在的内容,忠实度会下降。这就是捕捉幻觉的指标。

例如:

Context:
"Active invoices must be closed before upgrading."

Answer:
"Yes, customers can upgrade while keeping active invoices."

Faithfulness: Low (the answer contradicts the context)

高忠实度得分意味着模型保持基于事实。低得分意味着它在编造或与事实相悖。


答案相关性

答案相关性衡量答案是否实际解决了问题。

模型可能忠实但不相关。例如,它可能忠实地重复上下文而不回答查询。相关性能够捕捉到这一点。

Question:
"Can Enterprise customers upgrade directly from the Professional plan while keeping active invoices?"

Answer:
"Customers can upgrade from Professional to Enterprise. Active invoices must be closed before upgrading."

Relevancy: High (the answer addresses the question)

Answer:
"Active invoices must be closed before upgrading. For more information, contact billing."

Relevancy: Medium (partial answer, no direct yes/no)

Answer:
"Billing support is available Monday to Friday."

Relevancy: Low (does not address the question)

上下文精度

上下文精度衡量检索到的上下文中有多少是相关的。

如果你检索了 10 个块但只有 1 个相关,精度就很低。这意味着你在浪费令牌并让模型产生困惑。

Retrieved: 10 chunks
Relevant: 2 chunks
Precision: 0.2

高精度意味着你的检索是聚焦的。低精度意味着你发送了太多噪声。


上下文召回

上下文召回衡量的是是否检索到了正确的内容。

如果答案存在于你的语料库中但检索未能找到它,召回率就会很低。这是一个检索问题,而不是生成问题。

Total relevant chunks in corpus: 5
Retrieved relevant chunks: 3
Recall: 0.6

高召回意味着你的检索正在找到正确的内容。低召回意味着你遗漏了它。


延迟和成本

延迟和成本是运营指标,但它们很重要。

  • 延迟 – 每个步骤需要多长时间?

    • 检索时间
    • 重新排序时间
    • 压缩时间
    • 生成时间
  • 成本 – 每个查询多少个标记?

    • 输入标记
    • 输出标记
    • 每个查询的总成本

如果你的系统准确但每个查询需要 10 秒,用户不会使用它。如果它准确但每个查询花费 1 美元,你将无法扩展。


带有 Ragas 的示例代码

from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall

# Your dataset should have:
# - question
# - answer
# - contexts (list of retrieved chunks)
# - ground_truth (optional, for some metrics)

results = evaluate(
    dataset=your_dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)

print(results)

这是最小的设置。生产系统会添加更多指标,但这四个是核心。


构建黄金数据集

评估需要一个包含预期答案或相关上下文的查询数据集。

大多数团队会构建 100–200 个示例,涵盖:

  • 常见查询,

  • 边界情况,

  • 困难查询,

  • 以及已知的失败模式。

此数据集成为每次更改的基线。

from datasets import Dataset

data = {
    "question": [
        "Can Enterprise customers upgrade directly from the Professional plan while keeping active invoices?",
        "How do I reset my password?",
        "What is the refund policy for annual plans?"
    ],
    "answer": [
        "No. Active invoices must be closed before upgrading from Professional to Enterprise.",
        "Go to Settings → Security → Reset Password. You'll receive an email with a link.",
        "Annual plans are non-refundable. You can downgrade at the end of the billing cycle."
    ],
    "contexts": [
        ["Active invoices must be closed before upgrading."],
        ["Go to Settings → Security → Reset Password."],
        ["Annual plans are non-refundable."]
    ],
    "ground_truth": [
        "No. Active invoices must be closed before upgrading.",
        "Go to Settings → Security → Reset Password.",
        "Annual plans are non-refundable."
    ]
}

dataset = Dataset.from_dict(data)

这是起点。您会随着时间的推移对其进行扩展。


生产监控

评估不会在部署后停止。

生产系统应:

  • 采样真实查询,

  • 通过评估运行它们,

  • 随时间跟踪指标,

  • 并在质量下降时发出警报。

这就是您在用户之前发现回归的方式。

def monitor_production(queries, answers, contexts):
    for query, answer, context in zip(queries, answers, contexts):
        score = evaluate_single(query, answer, context)
        log_metric(score)

        if score["faithfulness"] < 0.7:
            alert("Low faithfulness detected")

这是基本思想。您在生产环境中跟踪指标,并在它们低于阈值时发出警报。


设置阈值

阈值取决于您的使用场景,但以下是一些粗略的指导原则:

  • 忠实度: 生产环境中 0.8 或更高

  • 答案相关性: 0.7 或更高

  • 上下文精确度: 0.5 或更高

  • 上下文召回率: 0.7 或更高

这些不是普遍适用的。它们是起点。


评估循环

评估不是一次性的事情。它是一个循环:

  1. 构建黄金数据集。

  2. 运行评估。

  3. 识别薄弱点。

  4. 进行更改。

  5. 重新运行评估。

  6. 如果指标改善则部署。

  7. 在生产环境中监控。

  8. 重复。

这就是您随时间推移实际改进 RAG 系统的方式。


常见的评估错误

错误 1:没有黄金数据集

没有基线,您无法进行评估。如果您没有查询和预期答案的数据集,您只是在猜测。

错误 2:仅在简单查询上进行评估

如果您的数据集仅包含简单查询,您将无法捕获边缘情况。应包含困难查询、模糊查询以及已知的失败模式。

错误 3:未在生产环境中进行监控

仅在开发环境中进行评估是不够的。您需要在生产环境中监控真实查询以捕捉回归。

错误 4:忽略延迟和成本

准确性不是唯一的指标。如果您的系统准确但太慢或太昂贵,则无法扩展。


核心教训

评估不是可有可无的。它是使 RAG 工程成为可能的层。

没有它,您只是在猜测。

有了它,您实际上可以构建一个随时间改进的东西。

评估将 RAG 从一个黑箱转变为您可以衡量、改进和信任的系统。


继续系列

本文重点介绍了扩展生产 RAG 系统:大规模架构、摄取和检索管道、向量数据库性能、缓存、异步处理和成本优化。这些技术共同帮助 RAG 系统在数据和流量增长时保持快速、可靠且成本高效。

但是,如果你无法衡量它是否真的在正确工作,那么一个可扩展的系统毫无意义。

在下一篇文章中,我们将从基础设施转向评估。我们将探讨如何在生产环境中衡量 RAG 质量,哪些指标重要,如何监控回归,以及需要警惕的故障模式。

接下来:

第5部分 — 生产环境 RAG 系统的评估:指标、监控和常见故障模式

我们将涵盖:

  • RAG 评估指标(忠实度、相关性、精确度、召回率)

  • 构建黄金数据集

  • 生产监控与告警

  • 常见故障模式及如何捕捉它们

  • 设定质量阈值

  • 持续评估循环

通过本系列的学习,您将拥有一套完整的工程框架,用于设计、构建、扩展和评估生产级的检索增强生成(RAG)系统。

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

ai llm rag vectordatabase