跳到主要内容

规划阶段:把论文压成一份冻结的复现计划

这章讲 Phase 5:怎么把一篇长论文(或一段需求)变成一份结构化的 initial_plan.txt——它列出要建哪些文件、每个文件干什么、优先级如何。这份计划之后会被冻结,整个实现阶段都照着它走。

1. 它要解决的小问题

直接把「整篇论文 + 写代码吧」丢给模型,结果往往是:文件结构混乱、漏掉关键算法、或者上下文太长被截断。DeepCode 的思路是先想清楚再动手:产出一份明确的「文件清单 + 每文件职责 + 优先级」的计划,把「设计」和「实现」彻底分开。

2. 思路/直觉:为什么计划要「冻结」

计划一旦生成并通过校验,就写进 initial_plan.txt,实现阶段只读不改。好处:

  • 实现 agent 永远有一个稳定的「全集」——它随时能算出「哪些文件还没实现」(见第 03 章的退出条件)。
  • 计划是单一事实来源,实现阶段每次清空对话后都把它重新塞回上下文(第 04 章)。

这呼应整个项目的设计哲学:靠落盘的稳定产物传递状态

3. 主线:run_code_analyzer 的三段式

规划核心在 run_code_analyzer(workflows/agent_orchestration_engine.py:591)。它分三段:

第一段:决定要不要分段。 文档超过阈值(5 万字符)就先做文档分段,产出一份紧凑的、确定性的上下文喂给 planner,避免一次塞整篇:

# 示意,非源码:分段阈值判定(对应 utils/llm_utils.py:89 should_use_document_segmentation)
threshold = 50_000 # size_threshold_chars
if len(doc_text) > threshold:
use_segmentation = True # 太长 → 先分段成紧凑上下文
else:
use_segmentation = False # 够短 → 传统全文规划

但注意它的注释强调的一条原则(:594-:599):无论是否分段,最终计划永远由「单一权威 planner」生成,不做多 planner 并行——避免 fan-out 死锁、保持规划架构一致。分段只是用来准备上下文,不是用来并行出多份计划。

第二段:带重试的生成循环。 用一个 planner agent(server_names=[],纯 LLM 不带工具),最多重试 3 次(max_retries)。每次生成后做两道检查(:740-:748):

  • _assess_output_completeness(result) —— 完整性打分(0~1)。
  • validate_plan_text(result) —— 结构校验(必需小节是否齐全)。

只有 完整性 ≥ 0.8 且校验通过,才认定成功、写盘、返回(:750-:772):

# 示意,非源码:成功判定(对应 :750)
if completeness_score >= 0.8 and plan_validation["valid"]:
write_planning_meta(paper_dir, {"status": "success", ...})
return result

不达标就调参重试(_adjust_params_for_retry 加 max_tokens、改温度,:800-:805),并记下「目前最好的那次不合格结果」(best_invalid_result)备用。

第三段:多级降级,而不是直接崩。 三次都没出合格计划时,它不是抛异常了事,而是降级(:839-:869):

三次重试都不合格?


拿「最好的那次不合格结果」做 fallback_source
│ 太短(<500字符)?

退而用 分段上下文 / 论文前 6000 字符


coerce_text_to_minimal_plan() ← 把自由文本套进「最小计划 schema」
│ 套完能通过 validate_plan_text?
├─ 能 → 当成 success 返回(source="coerced_from_freeform")
└─ 不能 → 写 error meta,raise

这一串「重试 → 取最优不合格 → schema 包装 → 实在不行才 raise」是规划阶段最体现工程韧性的地方。

4. 深入:几个关键细节

① 分段模式更「卷」。 分段时给 planner 的 max_iterations=5、温度 0.2;传统全文时 max_iterations=2、温度 0.3(:640-:642)。分段意味着 planner 要多轮拼合,所以给更多迭代、更低温度求稳。

② 「延迟到工具」也算降级信号。 如果 planner 输出表现为「我需要某个不可用的工具才能继续」(_is_deferred_planning_output,:269:774),直接判定本轮无望,改用分段上下文兜底。

③ 复用已有计划(续跑)。 Phase 5 的外层 orchestrate_code_planning_agent(:1208)先调 is_existing_plan_usable:如果任务目录里已有可用的 initial_plan.txt,直接复用、跳过整个规划——这是断点续跑的一环(:1226-:1243)。

④ 自适应 prompt。 分段 vs 传统用不同的 prompt 模板(get_adaptive_prompts),论文分析/规划各有 _TRADITIONAL 后缀的版本(见 prompts/code_prompts.py:1234 起)。

5. 巧妙之处

  • 「单一权威 planner」而非多 planner 投票。 作者明确选择不并行(:596-:599),理由是一致性和避免死锁——一个反直觉但务实的取舍。
  • 完整性打分 + 结构校验双闸门。 不只看「有没有输出」,而是量化「够不够完整、结构对不对」,这才敢据此重试/降级。
  • 降级到 schema 包装。 与其让流水线崩在规划,不如把「最好的那坨自由文本」硬塞进最小计划骨架——保证下游有计划可读。

6. 边界与局限

  • 计划质量决定一切。 计划冻结后实现阶段不回头改设计;如果计划本身漏了某个核心算法,实现阶段不会补。
  • 完整性打分是启发式的。 _assess_output_completeness 是基于结构/长度的近似,不是语义正确性保证。
  • 5 万字符阈值是硬编码默认值(utils/llm_utils.py:80),可配置但默认固定。

7. 横向对比

很多编码 agent 把「规划」和「执行」混在同一个对话里(模型边想边写)。DeepCode 把规划独立成阶段并冻结产物,更接近「先写设计文档、评审通过、再开工」的工程流程——甚至内置了 run_plan_review_gate 审计闸门(第 01 章)。这是「计划-执行分离」流派的代表实现。

8. 代码地图

主题文件符号
规划核心(重试+降级)workflows/agent_orchestration_engine.pyrun_code_analyzer
规划阶段外层(复用/校验)同上orchestrate_code_planning_agent
单 planner 生成同上_generate_plan_with_single_agent
完整性打分同上_assess_output_completeness
分段阈值判定utils/llm_utils.pyshould_use_document_segmentation
自适应 promptutils/llm_utils.pyget_adaptive_prompts
计划校验/降级/元信息workflows/planning_runtime.pyvalidate_plan_text / coerce_text_to_minimal_plan / is_existing_plan_usable / write_planning_meta
规划 prompt 模板prompts/code_prompts.pyCODE_PLANNING_PROMPT / CODE_PLANNING_PROMPT_TRADITIONAL