Towards Data Science

Larger Context Windows Don’t Fix RAG — So I Built a System That Does

8.5内容质量

TL;DR · AI 摘要

RAG系统无法正确执行数据聚合,作者通过实验发现即使增加上下文窗口也无法解决问题,并提出将计算查询从RAG中分离的解决方案。

核心要点

  • RAG系统无法正确执行数据聚合,即使增加上下文窗口到128k tokens也无法解决。
  • RAG将CSV数据行转换为纯文本,导致模型无法准确执行计算。
  • 将计算查询从RAG中分离,可以有效避免错误答案的产生。

结构提纲

按章节快速跳转。

  1. 作者在构建一个基于RAG的Q&A系统时发现,即使增加上下文窗口,系统仍然给出错误答案。

  2. 作者发现RAG系统在处理数据聚合查询时,无法正确计算,反而生成看似正确的错误答案。

  3. RAG系统将CSV数据行转换为纯文本,导致模型无法准确执行计算。

  4. 作者通过实验发现,增加上下文窗口到128k tokens后,系统仍然给出错误答案。

  5. 作者提出将计算查询从RAG中分离,以避免错误答案的产生。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • RAG系统的局限性
    • 数据聚合问题
      • RAG无法正确执行计算
      • 上下文窗口增加无改善
    • 解决方案
      • 分离计算查询

金句 / Highlights

值得收藏与分享的关键句。

  • The model gave me a beautiful breakdown by category. It looked legit. I added up the numbers it returned. It was less than half.

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • The RAG pipeline doesn’t truly understand structured data. All it does is take each CSV row and flatten it into plain text.

    第 3 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • RAG is a retrieval tool. It is not a calculation engine. Retrieval finds relevant fragments. Computation requires a full dataset scan.

    第 4 段

    ⬇︎ 下载 PNG𝕏 分享到 X
#RAG#LLM#数据聚合#AI系统设计
打开原文

更大的上下文窗口无法解决 RAG 问题 —— 所以我构建了一个能够解决的系统 | Towards Data Science

大型语言模型

更大的上下文窗口无法解决 RAG 问题 —— 所以我构建了一个能够解决的系统

我将上下文窗口扩大了五倍。每次都发生了意想不到的事情。

Emmimal P Alexander

2026年6月13日

15分钟阅读

分享

图片由作者生成,使用了ChatGPT(DALL·E)

TL;DR

  • 我构建了一个问答系统,并信任了一个正确率不足一半的RAG答案。
  • 我在10万行数据上,对7种查询类型和5种上下文大小进行了测量。
  • 解决方案:将计算查询完全从RAG中分离出来。

我信任了错误的数字

上个月,我全身心投入到为EmiTechLogic构建一个新功能上。现在,学习者可以上传他们自己的混乱CSV文件,并用普通英语询问有关数据的问题。这听起来非常适合RAG,所以我全力以赴——嵌入、检索、美观的响应。

最初的几个演示看起来非常棒。干净的表格、自信的数字、专业的格式。实际上,我在内部测试中开始信任这个系统。

然后我选择了一个数字进行再次确认。

数据集中的实际杂货支出:1,140,033.24美元。

模型给出了一个按类别划分的漂亮分解。看起来很真实。我加起来它返回的数字。

结果不到一半。

我坐在那里盯着屏幕,想着“这不可能是对的。”所以,我做了任何工程师都会做的事。我增加了上下文窗口。4k… 16k… 32k… 128k tokens。每次答案变得更长、更详细、更自信地错误。

就在这时,我终于明白了。这不是检索问题。我让一个检索系统对它只部分看到的数据进行大量计算。而模型没有表示它不确定或信息缺失,而是生成了看起来正确的精致、结构化的答案。

为什么RAG无法进行聚合

RAG流程并不真正理解结构化数据。它所做的只是将每个CSV行展平为纯文本。仅此而已。在模型眼中,一行看起来像这样:

code
"2019-01-01 grocery_pos 107.23 F NC Jennifer Banks ..."

对于像“按类别计算总支出?”这样的查询,RAG流程会这样做:

code
1. 分词:["total", "spend", "category"]
2. 通过关键词重叠对所有100,000行进行评分
3. 返回前N行作为序列化的纯文本
4. 要求LLM从该文本中进行求和和分组

第4步是系统失败的地方。LLM并没有运行SUM。它只是从文本块中进行模式匹配,并生成一个模仿聚合的响应。

模型在大规模上难以处理数值精度[1],但真正的问题是呈现方式。模型会为您提供所有类别的详细分解。这是一个经典的陷阱。输出看起来专业。它模仿真实报告的结构如此之好,以至于你的大脑假设内容是有效的。你无法验证92%的数据是缺失的。

RAG是一个检索工具。它不是一个计算引擎。检索找到相关片段。计算需要对整个数据集进行扫描。当你使用RAG进行数学计算时,你会得到一个看起来权威的错误答案。这个区别是至关重要的。一个部分答案表明数据缺失。一个看起来完整的错误答案只是表明虚假的信心。

完整代码:https://github.com/Emmimal/context-window-engine/

第一个流程是一个 RAG 模拟。它模拟了朴素向量流程在五个上下文大小下传递给 LLM 的数据。我测试了五种上下文大小,从 5 行到 8,000 行不等,这相当于从 325 个标记扩展到 500,000 个标记。对于每种大小,我跟踪了三个指标:LLM 能看到的数据量、从该特定切片中计算出的总和,以及读者是否能够实际发现错误。

第二个流程是一个语义引擎,它执行相同的查询,以确定性方式对所有 100,000 行进行全扫描,并返回确切的正确答案。

查询处理工作流程的架构比较,将基于文本的 RAG 模拟检索与语义引擎中的结构化数据聚合进行对比。图片由作者提供。

该模拟不会重现 LLM 的确切输出。它保留的是关键的结构属性:将数据的一个部分切片输入到一个系统中,该系统返回完整的答案。正是这一属性导致了问题,而基准测试正是测量这一属性。

我选择了七种查询类型,以涵盖结构化数据系统可能遇到的每种聚合模式:

| 查询 | 操作 | 为何会破坏 RAG | |------|------|----------------| | 按类别计算总支出 | SUM + GROUP BY | 需要对 14 个组中的所有行进行求和 | | 按类别计算最高平均交易 | AVG + GROUP BY | 每个缺失的行都会改变平均值 | | 在 grocery_pos 上的总支出 | SUM + 分类过滤 | 过滤需要查看所有匹配的行 | | 有多少女性客户进行了交易 | COUNT + 过滤 | 部分扫描上的计数没有意义 | | 总支出中金额大于 500 美元的部分 | SUM + 数值比较 | 阈值逻辑需要完整数据 | | 总支出最少的州 | MIN + 在 50 个组上进行 GROUP BY | 只有在所有组都存在时才能找到最小值 | | 欺诈交易所占比例 | COUNT + 比例 | 部分分母上比例未定义 |

这些查询并不独特或复杂。它们是任何分析师在查看新数据集时会提出的标准问题。这正是为什么这种失败如此关键的原因。

错误可观测性崩溃

这是引发这一切查询的完整基准测试输出。我在这里完整展示它,因为这些数字使问题无法被忽视。

code
真实结果(语义引擎)
SUM(amt) GROUP BY category → 14 个组
  #1  grocery_pos               1,140,033.24
  #2  shopping_net                773,527.93
  #3  shopping_pos                725,766.14
  #4  gas_transport               648,804.24
  #5  home                        556,526.53
延迟时间:100.47ms | 扫描行数:100,000

RAG 模拟 —— 每个上下文大小下 LLM 接收到的内容

上下文               行数   覆盖率    部分总和  可检测错误?
tiny   (~325 tokens)     5   0.0050%         197.73  EASY
small  (~3K tokens)     50   0.0500%       2,003.56  MODERATE
medium (~32K tokens)   500   0.5000%      31,023.21  HARD
large  (~130K tokens) 2,000  2.0000%     140,093.16  VERY HARD
xlarge (~520K tokens) 8,000  8.0000%     569,368.22  NEAR IMPOSSIBLE

我盯着这些结果看了很久。最令人担忧的部分不仅仅是答案是错误的,而是随着上下文窗口的增大,错误变得越来越难以发现。

在 8,000 行时,错误率仍然超过 50%,但响应看起来却像一份专业的报告。你需要手动验证数字才能发现有什么不对。这就是我开始称之为“错误可观测性崩溃”的原因。我给模型提供的上下文越多,输出看起来就越有说服力,但并不一定更准确。

“部分和”这一列显示的是,如果大语言模型(LLM)将实际检索到的每一行中的金额值相加,得到的总和。而“可检测错误?”这一列则表示人类读者发现错误的可能性有多大。

当有5行数据时,部分和为197.73。正确的总和是1,140,033.24。这个错误显而易见。输出内容简短,数字明显错误,缺失的数据也一目了然。错误是显而易见的。

当有8,000行数据时,部分和达到569,368.22。此时,大语言模型已经看到了所有14个类别。它生成了一篇1,500字的报告,其中包含具体的数字,并使用了自信的语言。错误率高达50%,但隐藏在权威且结构良好的文字中。在没有外部参考的情况下,读者无法发现这个错误。

这是所有七个查询中都出现的模式:

上下文窗口

行数

数据集覆盖率

响应长度

可检测错误?

~325 tokens

5

0.005%

~50 words

是 — 显而易见是猜测

~3K tokens

50

0.050%

~150 words

可能

~32K tokens

500

0.500%

~400 words

困难

~130K tokens

2,000

2.000%

~800 words

非常困难

~520K tokens

8,000

8.000%

~1,500 words

几乎不可能

语义引擎

100,000

100%

<200ms

不适用 — 精确

我将这种现象称为“错误可观察性崩溃”。随着上下文的增长,信心也随之增长,但正确性并没有。

上下文的幻觉:在RAG和LLM系统中,更大的上下文窗口如何增加用户的信心并降低错误的可检测性,而不会提高实际准确性。图片由作者提供。

失败模式是不对称的,这使得它们更加危险:

一个错误的RAG答案看起来是正确的。它格式正确、具体且自信。一个失败的计算会抛出一个明确的错误。它是可见的。

一个失败是无声的,另一个是响亮的。随着上下文窗口达到数百万个token,无声的失败变得越来越难以检测[4]。系统在扩展时并不会变得更安全,它只是变得更有说服力。

语义引擎:证明正确答案可以快速得到

在我完全理解这个问题之前,我已经因为沮丧而匆忙拼凑出一个简单的语义引擎。我只是希望至少有一次能获得正确的答案。

这种方法最终被证明是简单的:将查询解析为正确的操作,并对整个数据集进行一次遍历。不需要嵌入、检索或猜测。

在实践中,它看起来像这样:

逻辑很简单。比如,对于一个查询“按类别计算总支出”,引擎会将其映射到一个直接的操作:SUM(amt) GROUP BY category。它对完整的100,000行数据集进行一次遍历。它累积分组的总和。没有检索。没有推理。没有部分扫描。它只访问每一行一次,并返回确切的结果。

这证明了正确的答案并不昂贵。基准查询在200毫秒内完成。样本大小:100,000行。聚合是微不足道的。失败发生在你将这些查询路由到一个被设计成误解它们的系统时。

code
from context_window_engine import compute_ground_truth, load_csv

rows = load_csv("data/credit_card_transactions.csv", max_rows=100_000)

gt = compute_ground_truth(
    query_label = "total by category",
    rows        = rows,
    agg_func    = "sum",
    agg_col     = "amt",
    group_col   = "category",
)
# gt.answer     → [(grocery_pos, 1140033.24), (shopping_net, 773527.93), ...]
# gt.latency_ms → 100.47

引擎支持 SUM、AVG、COUNT、MIN、MAX。可以处理分类和数值过滤器。包括 GROUP BY 和比率计算。没有外部依赖。每个操作都作为确定性函数在完整列表上运行。

引擎本身并不是产品。它是证明:正确答案可以在一秒内获得。不需要推理。真正的挑战是可靠地将查询路由到正确的位置。

解决方案不是更好的检索

停止尝试改进检索。如果一个查询需要100%的数据,8%的样本会失败。解决方案是将检索从循环中移除。

我们需要一个分类层。它位于管道之前,并做出一个二元判断:计算还是查找?

区别是显而易见的。“按类别统计总支出”需要全面扫描。“查找 Jennifer Banks 的交易”是一个简单的查找。标准 RAG 强制将两者都导向同一条路径。这就是设计缺陷。

QueryRouter 解决了这个问题。它检查每个传入的查询,并在任何检索开始之前将其路由到正确的路径。

基于意图的查询路由架构,将分析计算意图与语义信息检索管道分离。图片由作者提供。

分类器使用三个信号层级,按优先级排序。第一层级:聚合动词——total、how many、average、lowest、percentage。这些需要对完整数据集进行计算。第二层级:数值比较——greater than 500、above $1,000、at least。这些意味着先过滤再聚合,RAG 无法实现。第三层级:检索信号——find、show me、list、fetch。这些表示查找,语义相似性适用。

层级 | 信号 | 示例 | 路由 --- | --- | --- | --- 1 | 聚合动词 | totalhow manyaveragelowestpercentage | COMPUTATION 2 | 数值比较 | greater than 500above $1,000at least | 3 | 检索信号 | findshow melistfetch | RETRIEVAL 0 | 无匹配 | ambiguous | COMPUTATION — 更安全的默认值

如果没有任何层级匹配,默认路由到 COMPUTATION。这是有意为之。失败模式是不对称的:在聚合上错误的 RAG 答案是静默错误。无法解析查询的计算引擎会抛出错误。有疑问时,应明确失败。

python
from query_router import QueryRouter

router = QueryRouter(rows)

result = router.route("What is the total spend by category?")
# result.routed_to     → "COMPUTATION"
# result.answer.answer → [(grocery_pos, 1140033.24), ...]
# result.total_latency → ~250ms — classify + execute combined

result = router.route("Find transactions from Jennifer Banks")
# result.routed_to     → "RETRIEVAL"
# result.answer.safe   → True — RAG is appropriate

路由完整基准测试

我通过路由器运行了九个查询,以验证两种类型下的性能:七个聚合查询,旨在发送到语义引擎,两个查找查询,用于 RAG。

每条路由都是正确的。七个聚合查询命中了全面扫描引擎并返回了精确结果。两个查找查询正确触发了 RAG 路径。看看输出:高置信度分数,正确的模式匹配,延迟低于 130 毫秒——即使有 100,000 行扫描。

code
[1] ✓  COMPUTATION   "What is the total spend by category?"
     Tier 1 | matched='total' | confidence=0.97
     #1 grocery_pos      1,140,033.24  (102.57ms | 100,000 rows | exact)

[2] ✓ 计算 "哪个类别的平均交易金额最高?" Tier 1 | matched='highest' | confidence=0.97 71.91 (119.47ms | 100,000 rows | exact)

[3] ✓ 计算 "在grocery_pos上的总支出是多少?" Tier 1 | matched='total' | confidence=0.97 1,140,033.24 (49.96ms | 100,000 rows | exact)

[4] ✓ 计算 "有多少笔交易是由女性客户完成的?" Tier 1 | matched='How many' | confidence=0.97 54,641.00 (90.45ms | 100,000 rows | exact)

[5] ✓ 计算 "金额大于500的总支出是多少?" Tier 1 | matched='total' | confidence=0.97 1,274,269.60 (91.65ms | 100,000 rows | exact)

[6] ✓ 计算 "哪个州的总支出最低?" Tier 1 | matched='lowest' | confidence=0.97 lowest RI 2,125.60 (109.05ms | 100,000 rows | exact)

[7] ✓ 计算 "有多少比例的交易是欺诈的?" Tier 1 | matched='percentage' | confidence=0.97 0.9900% (87.35ms | 100,000 rows | exact)

[8] ✓ 检索 "查找Jennifer Banks的交易" Tier 3 | matched='Find' | confidence=0.85 RAG是合适的 — 不需要聚合

[9] ✓ 检索 "显示一个来自Texas的交易样本" Tier 3 | matched='Show me' | confidence=0.85 RAG是合适的 — 不需要聚合

路由准确性:9/9

code

9/9正确。如果聚合查询从未到达RAG,误差可观察性折叠是不可能的。

## 测试套件

该基准验证了九个特定查询。测试套件确保在更广泛的范围内具有可靠性:边界情况、格式错误的输入、缺失数据和常见的生产失败点。

引擎套件包含87个测试,分布在10个类别中。它涵盖了带有美元符号、逗号和科学记数法的浮点解析;在正常条件和空输入下所有五个聚合函数;所有五个数值过滤操作符;带有分类和数值过滤器组合的完整GROUP BY聚合;在每个上下文大小下的RAG模拟覆盖率指标;以及边界情况,包括空数据集、缺少列值的行和单行输入。

路由器套件包含72个测试,分布在5个类别中。它涵盖了所有三个层级模式,包括边界情况,如全大写查询和非常长的查询;对于每种支持的查询形式,将自然语言解析为类型化操作;针对所有七个基准查询的路由和执行正确性;以及一个对比套件,验证路由器答案与独立的基准计算结果匹配 — 确保路由器不会引入与引擎自身输出的任何偏差。

通过输入 python space -m space unittest space test_engine space -v 运行引擎测试。这将执行套件中的87个测试。

通过输入 python space -m space unittest space test_router space -v 运行路由器测试。这将执行套件中的72个测试。

在Python 3.9+上,所有159个测试都通过,且没有外部依赖。

## 诚实的限制

这个解决方案并不完美。目前它只能处理单个CSV文件。实际生产数据集通常很混乱,包含需要连接的多个表 —— 我故意将范围保持较小,因为我首先想要一个能够端到端实际工作的解决方案。

路由器目前仍然相当基础(基于正则表达式)。我早些时候尝试过一个小型基于LLM的分类器,但它的表现不稳定,还增加了延迟,因此我回到了简单的方法。有时候,平淡无奇的解决方案才是最好的。

在基准测试中,我模拟了RAG的响应,而不是实际调用API。这些模式仍然成立,但使用GPT-4o或Claude 3.5时,效果可能会略有不同。

需要CSV格式。引擎可以直接从CSV文件加载结构化数据。目前不支持数据库连接、Parquet文件和其他表格格式。

## 这会带来哪些变化

添加一个路由层几乎不增加任何成本。将一个查询与65个正则表达式模式进行匹配仅需微秒级时间。语义引擎在扫描一个包含10万行的数据集时,增加的时间不到200毫秒。总的额外开销甚至小于一次嵌入调用的时间。

作为回报,你将获得对每个聚合查询的确定性答案。现在,每个总计、每个计数和每个百分比都来自完整的扫描,而不是基于8%数据的自信近似。RAG仍然处理它真正擅长的事情:检索特定记录、呈现相关段落、回答那些语义相似性是合适工具的查询问题。

RAG并没有被破坏。它只是被要求进行计算,而它无法做到这一点。危险的不是它失败,而是它令人信服地失败。无论有多少上下文,这一点都不会改变。

你可以尝试像这样输入:

首先,使用 `git clone` 命令后接URL `https://github.com/Emmimal/context-window-engine/` 克隆仓库。完成后,通过输入 `cd context-window-engine` 进入目录。最后,在终端中运行 `python demo.py` 启动项目。

## 参考文献

[1] Levy, M., Jacoby, A., & Goldberg, Y. (2024). Same task, more tokens: The impact of input length on the reasoning performance of large language models. In Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers), pages 15339–15353, Bangkok, Thailand. Association for Computational Linguistics. https://doi.org/10.18653/v1/2024.acl-long.818

[2] Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W.-t., Rocktäschel, T., Riedel, S., & Kiela, D. (2020). Retrieval-augmented generation for knowledge-intensive NLP tasks. Advances in Neural Information Processing Systems, 33, 9459–9474. https://doi.org/10.48550/arXiv.2005.11401

[3] Gao, Y., Xiong, Y., Gao, X., Jia, K., Pan, J., Bi, Y., Dai, Y., Sun, J., Guo, Q., Wang, M., & Wang, H. (2023). Retrieval-augmented generation for large language models: A survey. arXiv preprint arXiv:2312.10997. https://doi.org/10.48550/arXiv.2312.10997

[4] Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2023). Lost in the middle: How language models use long contexts. Transactions of the Association for Computational Linguistics, 12, 157–173. https://doi.org/10.1162/tacl_a_00638

[5] Koshorek, O., Granot, N., Alloni, A., Admati, S., Hendel, R., Weiss, I., Arazi, A., Cohen, S.-N., & Belinkov, Y. (2025). Structured RAG for answering aggregative questions. arXiv preprint arXiv:2511.08505. https://doi.org/10.48550/arXiv.2511.08505

## 声明

所有基准数据均来自在 Python 3.12.6、Windows 11、仅使用 CPU(无 GPU)上实际运行的结果。该基准测试使用了“信用卡交易欺诈检测”数据集(Kartik Gajjar, Kaggle, 2020),这是一个由 Brandon Harris 创建的 Sparkov 交易模拟器生成的合成数据集,采用 CC0(公共领域)许可,可在 kaggle.com/datasets/kartik2112/fraud-detection 获取。RAG 基线模拟了检索过程并建模了置信度信号 —— 没有实际调用任何 LLM API。无需任何外部 API 密钥即可复现本文中的任何结果。本文中描述的所有代码均由我编写并测试。

作者

查看 Emmimal P Alexander 的所有文章

数据工程

深入探讨

LLM

LLM 应用

RAG

分享本文

- 在 Facebook 上分享

- 在 LinkedIn 上分享

- 在 X 上分享

Towards Data Science 是一个社区出版物。提交你的见解,以触达全球受众,并通过 TDS 作者支付计划获得报酬。

将 href 更新为你的实际提交 URL

为 TDS 写作

✦ 结束 CTA ✦