Larger Context Windows Don’t Fix RAG — So I Built a System That Does
TL;DR · AI 摘要
RAG系统无法正确执行数据聚合,作者通过实验发现即使增加上下文窗口也无法解决问题,并提出将计算查询从RAG中分离的解决方案。
核心要点
- RAG系统无法正确执行数据聚合,即使增加上下文窗口到128k tokens也无法解决。
- RAG将CSV数据行转换为纯文本,导致模型无法准确执行计算。
- 将计算查询从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.
The RAG pipeline doesn’t truly understand structured data. All it does is take each CSV row and flatten it into plain text.
RAG is a retrieval tool. It is not a calculation engine. Retrieval finds relevant fragments. Computation requires a full dataset scan.
更大的上下文窗口无法解决 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行展平为纯文本。仅此而已。在模型眼中,一行看起来像这样:
"2019-01-01 grocery_pos 107.23 F NC Jennifer Banks ..."对于像“按类别计算总支出?”这样的查询,RAG流程会这样做:
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 + 比例 | 部分分母上比例未定义 |
这些查询并不独特或复杂。它们是任何分析师在查看新数据集时会提出的标准问题。这正是为什么这种失败如此关键的原因。
错误可观测性崩溃
这是引发这一切查询的完整基准测试输出。我在这里完整展示它,因为这些数字使问题无法被忽视。
真实结果(语义引擎)
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行。聚合是微不足道的。失败发生在你将这些查询路由到一个被设计成误解它们的系统时。
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 | 聚合动词 | total、how many、average、lowest、percentage | COMPUTATION 2 | 数值比较 | greater than 500、above $1,000、at least | 3 | 检索信号 | find、show me、list、fetch | RETRIEVAL 0 | 无匹配 | ambiguous | COMPUTATION — 更安全的默认值
如果没有任何层级匹配,默认路由到 COMPUTATION。这是有意为之。失败模式是不对称的:在聚合上错误的 RAG 答案是静默错误。无法解析查询的计算引擎会抛出错误。有疑问时,应明确失败。
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 行扫描。
[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
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 ✦