绕过LLM上下文限制:大规模代码审查的工程实践
方案状态持久化进度追踪跨Session结构化输出代码审查适用性RAG✅❌✅❌⭐⭐Agent记忆✅❌✅❌⭐⭐任务分解❌✅❌✅⭐⭐⭐状态外部化✅✅✅✅⭐⭐⭐⭐⭐这些方案各有优势,但都无法完美解决大规模代码审查的需求。我需要一个专门设计的解决方案。认清本质:LLM跨请求是无状态的,不要期望它在不同Session间"记住"任何东西状态外部化:将任务状态持久化到外部文件,LLM通过读写文件维护状态批次处理:
欢迎关注公众号:【冬瓜白】
一、问题背景
1.1 业务场景:TMF架构重构的代码审查
最近在参与对核心系统的架构重构中,需要将一个庞大的业务模块重构为符合TMF(Transaction Management Framework)四层架构的模块化设计。
原始代码规模(全链路):
| 层级 | 代码量 | 说明 |
|---|---|---|
| 入口Processor类 | 2,388行 | 前置校验、后置处理、流程编排 |
| 核心Service类 | 962行 | 核心业务逻辑、事务处理、数据持久化 |
| 直接依赖服务 | ~15,000行 | 55+个依赖服务(费用计算、库存、保险、订单等) |
| 间接调用链路 | ~30,000行 | 二级、三级依赖的业务逻辑 |
| 全链路总计 | ~50,000行 | 需要理解和审查的代码范围 |
重构后结构:
| 层级 | 文件数 | 说明 |
|---|---|---|
| Activity层 | 2 | 流程编排、事务边界管理 |
| Ability层 | 3 | 业务能力抽象 |
| Extension层 | 11 | 业务身份路由(到家/到店/寄修) |
| Step层 | 48 | 原子业务步骤 |
| Model层 | 1 | 上下文数据模型 |
| 总计 | 65个文件 | 需要逐一审查的新代码 |
复杂度数据:
- 校验逻辑:21+种,涉及参数校验、故障校验、权益校验、保险校验等
- 业务类型:3种,各有差异化逻辑
- 服务类型:11种,各有特殊处理
- 特殊业务场景:5种(涉及不同商品和履约方式的差异化处理)
- 依赖服务:55+个(@Autowired 40+,@DubboConsumer 12+,@Resource 5+)
- 方法映射:40+个方法需要逐一验证语义一致性
这次重构的核心挑战不在于代码编写,而在于如何确保重构后的代码与原代码在语义上完全一致——这需要对65个文件进行逐一审查,验证每个方法的迁移是否正确。
1.2 LLM上下文窗口的物理限制
用具体数据来量化这个问题:
审查任务规模分析:
核心代码量:
├── 原始Processor + Service:~3,350行
├── 重构后65个文件:~5,200行(平均80行/文件)
└── 小计:~8,550行
审查所需参照代码:
├── 直接依赖服务代码:~15,000行(需要理解调用关系)
├── TMF架构规范文档:~500行
└── 小计:~15,500行
全链路理解所需:
├── 间接依赖链路:~30,000行
└── 业务规则文档:~2,000行
总计:~56,000行代码需要在审查过程中被理解和参照
实际审查挑战:
即使只聚焦于核心的65个新文件和原始代码的对比,也面临巨大挑战:
- 单次对比:每个Step需要与原方法逐行对比,平均每个对比需要~200行上下文
- 累积上下文:审查到第30个文件时,前面的审查结论需要被记住
- 跨文件依赖:Step之间存在执行顺序依赖,需要全局视角
主流LLM上下文窗口(截至2025年):
| 模型 | 上下文窗口 | 约等于代码行数 |
|---|---|---|
| GPT-4 Turbo/GPT-4o | 128K tokens | 30,000-50,000行 |
| Claude Sonnet 3.5 | 200K tokens | 50,000-80,000行 |
| Claude Sonnet 4.5 | 200K-1M tokens | 50,000-250,000行 |
| 本地模型(Llama等) | 8K-32K tokens | 2,000-8,000行 |
注:Claude Sonnet 4.5 标准版为200K,Enterprise版可达500K,API可使用1M(beta)。代码行数换算基于Java代码平均每行约50-80字符、每token约4字符估算,实际因代码风格而异。
表面上看,Claude的200K上下文似乎足够。但实际情况远比这复杂:
- 有效上下文衰减:有研究表明,当上下文超过4K tokens时,LLM对中间位置信息的检索准确率可下降20%以上("Lost in the Middle"现象)
- 多轮对话累积:每轮对话都会消耗上下文,10轮对话后可用空间急剧减少
- 审查深度要求:代码审查需要逐行对比,不是简单的"看一眼"
实际测试结果:
在我的实践中,当尝试一次性审查超过15个文件时,LLM开始出现:
- 遗漏关键逻辑差异
- 直接报错:
Context limit exceeded unexpectedly. Please start a new session to continue.
1.3 问题本质:有状态任务 vs 无状态模型
这个问题的本质是一个阻抗不匹配:
┌─────────────────────────────────────────────────────────────┐
│ 代码审查任务特性 │
├─────────────────────────────────────────────────────────────┤
│ • 有状态:需要记住已审查内容、发现的问题、待验证项 │
│ • 长周期:可能跨越多个Session,甚至多天 │
│ • 累积性:后续审查依赖前序审查的结论 │
│ • 全局视角:需要从整体架构角度评估每个文件 │
└─────────────────────────────────────────────────────────────┘
↓ 阻抗不匹配
┌─────────────────────────────────────────────────────────────┐
│ LLM模型特性 │
├─────────────────────────────────────────────────────────────┤
│ • 跨请求无状态:单次请求内有上下文,但请求间不保留记忆 │
│ • 短周期:单次对话有上下文限制 │
│ • 瞬时性:Session结束后所有上下文丢失 │
│ • 局部视角:只能看到当前上下文中的内容 │
└─────────────────────────────────────────────────────────────┘
传统的解决思路是"把所有内容塞进上下文",但这在大规模任务中注定失败。我们需要一种新的思维方式。
二、行业解决方案分析
在深入我的解决方案之前,让我们先看看业界是如何应对这个问题的。
2.1 RAG(检索增强生成)
原理:将文档切片存入向量数据库,查询时检索相关片段注入上下文。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 代码文件 │ → │ 向量化 │ → │ 向量数据库 │
└──────────┘ └──────────┘ └──────────┘
↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户查询 │ → │ 相似检索 │ → │ 相关片段 │ → LLM
└──────────┘ └──────────┘ └──────────┘
适用场景:知识问答、文档搜索、基于相似性的代码片段查找
代码审查局限性:
- 代码审查通常需要完整的方法上下文和调用链,而RAG基于语义相似性检索,返回的往往是离散的代码片段,难以保证审查所需的结构完整性
- 跨文件依赖关系的处理能力有限:虽然可通过精心设计的切片策略(如包含调用关系)部分缓解,但对深度嵌套或复杂的调用链,仍容易丢失关键上下文
- 更适合“点查询”(如:这个函数是做什么的?),而非“面审查”(如:这10个文件重构后逻辑是否完全一致?)
2.2 Agent记忆系统
原理:为Agent配置外部记忆存储,在对话间持久化关键信息。
# 伪代码示例
class AgentMemory:
def __init__(self):
self.short_term = [] # 当前对话
self.long_term = VectorDB() # 持久化记忆
self.working = {} # 工作记忆
def remember(self, key, value):
self.long_term.store(key, value)
def recall(self, query):
return self.long_term.search(query)
适用场景:通用Agent任务、客服对话
代码审查局限性:
- 记忆检索依赖语义匹配的近似性,而代码审查需要精确的结构化状态(如:已审查文件列表、发现的特定问题编号)
- 缺乏任务进度追踪的天然机制:记忆是点状的,难以自动维护“已完成/待完成”的全局视图
- 在大规模、长周期的审查任务中,记忆的检索准确率可能随着信息量增加而下降,导致关键信息遗漏
2.3 任务分解(Task Decomposition)
原理:将大任务分解为小任务,逐个执行。
大任务:审查65个文件
├── 子任务1:审查Activity层(3个文件)
├── 子任务2:审查Ability层(3个文件)
├── 子任务3:审查Extension层(11个文件)
└── 子任务4:审查Step层(48个文件)
适用场景:可并行的独立任务、流水线式处理
代码审查局限性:
- 子任务间存在依赖关系时(如:Step层的审查依赖Ability层的设计结论),简单的线性分解可能导致信息断层
- 缺乏全局状态同步机制:每个子任务完成后,其结论和上下文通常不被自动传递给后续任务
- 任务中断与恢复较脆弱:一旦流程被打断,重新接续需要人工重新梳理上下文和进度
2.4 方案对比总结
| 方案 | 状态持久化 | 进度追踪 | 跨Session | 结构化输出 | 代码审查适用性 |
|---|---|---|---|---|---|
| RAG | ✅ | ❌ | ✅ | ❌ | ⭐⭐ |
| Agent记忆 | ✅ | ❌ | ✅ | ❌ | ⭐⭐ |
| 任务分解 | ❌ | ✅ | ❌ | ✅ | ⭐⭐⭐ |
| 状态外部化 | ✅ | ✅ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
这些方案各有优势,但都无法完美解决大规模代码审查的需求。我需要一个专门设计的解决方案。
三、实践方案:状态外部化模式
3.1 核心洞察
经过多次失败的尝试,我得出一个关键结论:
不要试图让LLM"记住"状态,而是让LLM"读写"状态。
这个思维转变带来了全新的设计思路:
传统思路:LLM内部状态 → 上下文窗口限制 → 任务失败
新思路: 外部状态文件 → LLM读取/更新 → 无限扩展
3.2 架构设计
┌─────────────────────────────────────────────────────────────────┐
│ 状态外部化架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │
│ │ Session 1 │ ──────→│ │ │
│ └─────────────┘ │ │ │
│ │ 状态文件(Markdown) │ │
│ ┌─────────────┐ │ │ │
│ │ Session 2 │ ──────→│ • 进度追踪文档 │ │
│ └─────────────┘ │ • 批次审查报告 │ │
│ │ • 问题发现记录 │ │
│ ┌─────────────┐ │ • 跨Session继续指南 │ │
│ │ Session N │ ──────→│ │ │
│ └─────────────┘ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.3 状态文件设计
状态文件是整个方案的核心,我设计了三层结构:
第一层:进度追踪文档
# XXX代码审查进度文档
## 当前审查位置
- 当前批次: Batch-5
- 当前文件: XxxValidateStep.java
- 上次审查时间: 2025-12-27
- 下次继续位置: Batch-5 第6个文件
## 审查批次规划
| 批次 | 内容 | 文件数 | 状态 |
|------|------|--------|------|
| Batch-1 | Activity层 | 3 | 🟢 已完成 |
| Batch-2 | Ability层 | 6 | 🟢 已完成 |
| Batch-3 | Extension层 | 8 | 🟢 已完成 |
| Batch-4 | Step层(1) | 10 | 🟢 已完成 |
| Batch-5 | Step层(2) | 10 | 🟡 进行中 |
| ... | ... | ... | ... |
第二层:批次审查报告
# Batch-X 审查报告
## 审查文件清单
| 文件 | 架构符合性 | 语义一致性 | 问题 |
|------|------------|------------|------|
| XxxStep.java | ✅ | ✅ | 无 |
| YyyStep.java | ⚠️ | ✅ | ARCH-001 |
## 发现的问题
### ARCH-001: 分层边界违规
- 文件: YyyStep.java
- 问题: 直接使用了Mapper,违反分层原则
- 建议: 封装到Service层
第三层:跨Session继续指南
## 跨Session继续指南
### 新Session提示词模板
请继续执行XXX代码审查任务。
进度文档位置:docs/analysis/XXX-审查进度.md
请先阅读该文档了解当前进度,然后继续审查。
### 现有Session继续
直接说:"继续审查下一个批次"
3.4 实际Prompt模板
为了确保方案的可复现性,这里分享实际使用的Prompt模板:
初始化Prompt:
你是一个代码审查专家,熟悉TMF四层架构。请执行以下任务:
1. 阅读进度文档:docs/analysis/XXX-审查进度.md
2. 理解当前审查状态和已完成的批次
3. 继续执行下一个待审查批次
4. 审查完成后更新进度文档和批次报告
审查重点:
- 架构符合性:是否符合TMF分层规范
- 语义一致性:与原代码逻辑是否完全一致
- 依赖完整性:所需的Service/Mapper是否正确注入
批次审查Prompt:
请审查以下文件,对比原代码验证语义一致性:
【原代码】
文件:XxxProcessor.java
方法:processXxx() 第120-180行
【新代码】
文件:XxxStep.java
完整内容:[粘贴代码]
【审查要求】
1. 逐行对比业务逻辑是否一致
2. 检查异常处理是否完整迁移
3. 验证依赖服务调用是否正确
4. 输出审查结论和发现的问题
跨Session恢复Prompt:
我需要继续之前的代码审查任务。
进度文档位置:docs/analysis/XXX-审查进度.md
请:
1. 读取进度文档,告诉我当前状态
2. 列出下一个待审查的批次内容
3. 等待我确认后开始审查
3.5 并发控制与版本管理
在多人协作或长周期任务中,状态文件的一致性需要额外关注:
单人场景:
- 依赖文件系统的原子写入即可
- 建议每次更新后立即保存
多人协作场景:
推荐方案:Git版本控制
├── 每次审查前:git pull 获取最新状态
├── 审查完成后:git commit + push 提交更新
└── 冲突处理:状态文件设计为追加式,减少冲突概率
状态文件设计原则:
• 进度文档:只更新"当前位置"字段,冲突概率低
• 批次报告:每批次独立文件,天然无冲突
• 问题记录:追加式写入,合并时取并集
断点恢复机制:
异常中断 → 读取最近检查点 → 验证状态完整性 → 从断点继续
↓
若状态不完整 → 回退到上一个完整检查点
3.6 工作流程
┌─────────────────────────────────────────────────────────────────┐
│ 审查工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 初始化阶段 │
│ ├── 分析任务规模 │
│ ├── 规划审查批次 │
│ └── 创建进度追踪文档 │
│ │
│ 2. 批次执行阶段(循环) │
│ ├── 读取进度文档,定位当前位置 │
│ ├── 加载当前批次文件 │
│ ├── 执行审查(架构+语义) │
│ ├── 生成批次报告 │
│ └── 更新进度文档 │
│ │
│ 3. 收尾阶段 │
│ ├── 汇总所有批次报告 │
│ ├── 生成最终审查报告 │
│ └── 输出行动项清单 │
│ │
└─────────────────────────────────────────────────────────────────┘
3.7 实战效果
使用这套方案,我成功完成了65个文件的代码审查:
| 指标 | 数值 |
|---|---|
| 总文件数 | 65 |
| 审查批次 | 9 |
| 跨Session次数 | 3 |
| 架构符合性 | 98%(2个轻微问题) |
| 语义一致性 | 100% |
关键发现示例:
- 事务边界验证:发现原
afterProcess()中的10个操作需要在事务内执行,验证新代码已正确迁移到processCommit() - 分层违规检测:发现2个Step直接使用了Mapper,违反了TMF分层原则
- 方法映射完整性:验证了40+个方法从原Processor到新Step的完整映射
四、方法论提炼:四种设计模式
从这次实践中,我提炼出四种可复用的设计模式:
4.1 模式一:状态外部化模式(State Externalization)
问题:LLM无状态,无法跨Session保持任务状态
解决方案:将任务状态持久化到外部文件,LLM通过读写文件维护状态
┌─────────────┐ 读取 ┌─────────────┐
│ LLM │ ←──────────── │ 状态文件 │
│ │ ──────────────→│ │
└─────────────┘ 更新 └─────────────┘
适用场景:任何需要跨Session持续的任务
4.2 模式二:批次处理模式(Batch Processing)
问题:大规模任务超出单次处理能力
解决方案:将任务分解为可管理的批次,每批次独立完成并记录
大任务 → [批次1] → [批次2] → ... → [批次N] → 汇总
↓ ↓ ↓
[报告1] [报告2] ... [报告N]
关键设计:
- 批次大小:8-12个文件/批次(经验值)
- 批次边界:按逻辑分组(如:同一层级、同一功能)
- 批次依赖:明确批次间的依赖关系
4.3 模式三:检查点模式(Checkpoint)
问题:长任务中断后无法恢复
解决方案:在关键节点保存检查点,支持从任意检查点恢复
## 检查点记录
| 检查点 | 时间 | 状态 | 可恢复 |
|--------|------|------|--------|
| CP-1 | 10:00 | Batch-1完成 | ✅ |
| CP-2 | 11:30 | Batch-3完成 | ✅ |
| CP-3 | 14:00 | Batch-5进行中 | ✅ |
恢复策略:
新Session启动 → 读取检查点 → 定位最近完成点 → 继续执行
4.4 模式四:协议驱动模式(Protocol-Driven)
问题:不同Session间的LLM行为不一致
解决方案:定义明确的交互协议,确保任何Session都能正确继续任务
## 跨Session协议
### 启动协议
1. 读取进度文档
2. 解析当前状态
3. 加载相关上下文
4. 继续执行
### 执行协议
1. 执行当前批次
2. 生成批次报告
3. 更新进度文档
4. 输出继续指南
### 终止协议
1. 汇总所有报告
2. 生成最终报告
3. 标记任务完成
五、深度思考:从"对话"到"协议"的范式转变
5.1 传统LLM交互的局限
传统的LLM交互是对话式的:
用户: 帮我审查这段代码
LLM: 好的,我发现以下问题...
用户: 继续审查下一个文件
LLM: 好的...(但可能已经忘记之前的上下文)
这种模式的问题:
- 隐式状态:状态隐藏在对话历史中
- 脆弱性:Session中断即状态丢失
- 不可预测:LLM行为依赖上下文理解
5.2 协议驱动的新范式
我提出的方案是协议驱动的:
用户: 执行审查协议,进度文档位置:xxx
LLM: [读取进度] → [执行批次] → [更新状态] → [输出报告]
用户: 继续
LLM: [读取进度] → [执行下一批次] → [更新状态] → [输出报告]
这种模式的优势:
- 显式状态:状态明确存储在外部文件
- 可恢复性:任何时候都能从检查点恢复
- 可预测性:LLM行为由协议定义,而非上下文推断
5.3 本质洞察
LLM在单次请求内通过注意力机制维护上下文,但跨请求是无状态的——这是设计状态外部化方案的关键认知。
一旦接受这个事实,我们就能设计出更健壮的人机协作模式:
┌─────────────────────────────────────────────────────────────────┐
│ 人机协作新范式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 人类角色: │
│ • 定义任务目标 │
│ • 设计状态结构 │
│ • 制定执行协议 │
│ • 监督执行过程 │
│ │
│ LLM角色: │
│ • 读取状态 │
│ • 执行协议定义的操作 │
│ • 更新状态 │
│ • 输出结构化结果 │
│ │
│ 状态文件角色: │
│ • 持久化任务状态 │
│ • 连接不同Session │
│ • 提供审计追踪 │
│ • 支持任务恢复 │
│ │
└─────────────────────────────────────────────────────────────────┘
5.4 更广泛的应用
这种模式不仅适用于代码审查,还可以应用于:
| 场景 | 状态文件内容 | 批次划分 |
|---|---|---|
| 大规模文档翻译 | 翻译进度、术语表、质量检查点 | 按章节/段落 |
| 数据迁移验证 | 迁移进度、校验结果、异常记录 | 按表/批次 |
| 系统重构 | 重构进度、依赖分析、测试状态 | 按模块/层级 |
| 安全审计 | 审计进度、漏洞清单、修复状态 | 按组件/风险等级 |
六、总结
6.1 核心要点
- 认清本质:LLM跨请求是无状态的,不要期望它在不同Session间"记住"任何东西
- 状态外部化:将任务状态持久化到外部文件,LLM通过读写文件维护状态
- 批次处理:将大任务分解为可管理的批次,每批次独立完成
- 协议驱动:定义明确的交互协议,确保跨Session的一致性
- 检查点机制:在关键节点保存状态,支持任务恢复
6.2 实践建议
对于需要使用LLM处理大规模任务的团队,我的建议是:
- 前期投入:花时间设计好状态文件结构和执行协议
- 批次规划:根据任务特点合理划分批次(建议8-12个文件/批次)
- 文档驱动:让Markdown文档成为人机协作的"共享内存"
- 持续改进:根据实践反馈优化协议和状态结构
6.3 展望
随着LLM能力的提升,上下文窗口会越来越大,但状态外部化的思想仍然有价值:
- 它提供了可审计性:所有状态变更都有记录
- 它支持协作:多人可以基于同一状态文件协作
- 它增强了可靠性:即使LLM出错,状态文件也能帮助恢复
6.4 AI时代的核心竞争力:思路比执行更重要
最后,我想分享一个更深层的思考:
在AI时代,你不需要知道每个设计模式"怎么执行",只需要知道"有这些思路可用"。
本文提炼的四种设计模式——状态外部化、批次处理、检查点、协议驱动——你不需要记住每个模式的实现细节。因为:
-
执行可以交给AI:当你面对类似问题时,只需告诉AI"我想用状态外部化模式来解决跨Session的状态管理问题",AI就能帮你实现具体细节。
-
思路才是稀缺资源:AI擅长执行,但不擅长"定义问题"和"选择方案"。知道"这类问题可以用批次处理模式解决",这个认知本身就是价值。
-
审查比编写更重要:在本文的实践中,我设计了状态文件结构和审查协议,AI执行了65个文件的具体审查。我的价值在于"设计"和"审查AI的输出",而非亲自执行每一步。
这正是本文"从对话到协议"范式转变的延伸:
传统开发者:学习模式 → 记住细节 → 亲自实现
AI时代开发者:了解模式 → 设计协议 → 指导AI执行 → 审查结果
架构师的核心竞争力正在从"会写代码"转向"会设计协议"。
希望这篇文章的价值不在于表达"状态文件的Markdown格式怎么写",而应该是"原来跨Session的大规模任务可以通过状态外部化来解决"。有了这个认知,下次遇到类似问题时,就知道该往哪个方向思考——具体实现,交给AI就好。
思考:与LLM协作的最佳方式,不是把它当作一个"聪明的助手",而是把它当作一个"强大的执行引擎"——你负责设计协议和管理状态,它负责高效执行。
更多推荐
所有评论(0)