首页
/ oh-my-pi 提交分析 Map 阶段提示词深度解析:从文件 Diff 到事实观察的提取管线

oh-my-pi 提交分析 Map 阶段提示词深度解析:从文件 Diff 到事实观察的提取管线

2026-09-10 12:50:56作者:申梦珏Efrain

Map 阶段是 oh-my-pi(packages/coding-agent,一个 IDE 深度集成的 Coding Agent)自动提交分析 map-reduce 流水线中的第一环:它把一批文件 Diff 交给模型,只要求提取"有事实依据的观察",不要求分类、不要求归纳。本文以 map.md 为骨架,结合 map-reduce.tsprompts.tsmarkdown.tsdiff.ts 等源码,完整讲解该提示词的逐段语义、模板渲染机制、批次编排、输出解析与容错设计,帮助你理解并复用这套"先映射、后归约"的提交信息生成策略。

Map 阶段在提交分析流水线中的定位

oh-my-pi 的提交分析(conventional commit 生成)位于 packages/coding-agent/src/commit/conventional/,该目录下除 map.md 外,还维护了同一套提示词族:

  • analysis.md:完整 Diff 的一次性分类(含 scope 判定规则、changelog 元数据);
  • fast.md:轻量快速提交信息生成;
  • map.md:map 阶段,逐文件提取事实观察;
  • reduce.md:reduce 阶段,把观察合并为单一分类;
  • summary.mdsummary-rewrite.md:摘要生成与改写。

提示词族由 prompts.ts 统一加载,ConventionalPromptFamily 类型枚举了 analysis | fast | map | reduce | summary | summary-rewrite 六类,PROMPT_BY_FAMILY 通过 Vite 的 with { type: "text" }.md 作为文本导入:

import mapPrompt from "./prompts/map.md" with { type: "text" };

renderConventionalPrompt(family, context) 是渲染入口:它定位模板中的 <!-- USER --> 分隔符,分隔符之前的部分作为 system 提示,之后的部分作为 user 模板,交给 prompt.render(Handlebars 风格模板引擎)以 context 填充后返回 { system, user }。对 map 模板而言,system 部分是角色定义与提取规则,user 部分则是 <files> 循环与可选的 <related_files> 块。

触发条件:什么时候进入 map-reduce 流程

并非所有提交都会走 map-reduce。判定函数 shouldUseMapReduce(diff, config) 位于 map-reduce.ts,它基于 config.ts 中的 ConventionalGenerationConfig 计算:

export function shouldUseMapReduce(diff: string, config: ConventionalGenerationConfig): boolean {
	if (!config.mapReduceEnabled) return false;
	let totalTokens = 0;
	let hasIncludedFile = false;
	for (const file of includedFiles(parsePromptDiff(diff), config)) {
		hasIncludedFile = true;
		const tokens = file.tokenEstimate();
		if (tokens > MAX_FILE_TOKENS) return true;   // 单文件超过 50_000 tokens
		totalTokens += tokens;
		if (totalTokens >= config.mapReduceThreshold) return true;  // 累计达到阈值
	}
	return hasIncludedFile && totalTokens >= config.mapReduceThreshold;
}

关键阈值与默认值(见 config.tsDEFAULT_CONVENTIONAL_GENERATION_CONFIG):

配置项 默认值 含义
mapReduceEnabled true 是否启用 map-reduce 两阶段流程
mapReduceThreshold 5_000 全部有效文件的 token 估算累计达到该值即启用
mapBatchTokenBudget 16_000 单个 map 批次的 token 预算上限
excludedFiles 锁文件清单 .endsWith 匹配后从输入中剔除(如 Cargo.lockpackage-lock.jsonbun.lockuv.lock 等)

单文件 token 估算由 ConventionalFileDiff.tokenEstimate() 提供(diff.ts):(header + content 的码点长度) / 4 向下取整、最少为 1,是一个确定性的四字符一 token 近似。测试 commit-conventional.test.ts 中通过 mapReduceThreshold: 1 之类的极小值来强制走 map-reduce 路径验证行为。

提示词逐段解读:角色、指令、范围、输出格式与校验

map.md 的 system 部分由五个 XML 风格的语义块构成,逐段含义如下。

角色声明(<role>

<role>Expert code analyst extracting grounded observations from a batch of file diffs.</role>

把模型限定为"从一批文件 Diff 中提取有依据观察的资深代码分析师"。这里的关键词是 grounded(有依据):后续所有规则都在强化"只写 Diff 里看得见的事实"这一约束,为 reduce 阶段的可信合成打下基础。

提取指令(<instructions>

指令块是 map 阶段的行为核心,逐条拆解:

  1. 只提取能被当前文件 Diff 支撑的事实观察,保持精确Extract only factual observations supported by each current file diff. Be precise.)——这是唯一性原则,观察必须落在当前文件的 Diff 上;
  2. <related_files> 仅用于解析名称或引用,不得为这些文件添加观察Use <related_files> only to resolve names or references; do not add observations about those files.)——related_files 只是上下文,不是观察来源;
  3. <files> 中每个 <file>,返回 0~5 条观察——给出数量上限,防止对单文件过度展开;
  4. 使用"过去式动词 + 具体对象 + 可选目的"的句式——统一时态与句式,便于 reduce 阶段做字符串级归并;
  5. 每条观察控制在 100 字符以内
  6. 覆盖该文件中有意义的变更,省略格式化、纯注释与 import 顺序调整——过滤低信号变更;
  7. 属于同一整体的相关编辑应合并,但不得猜测或过度概括
  8. 对用户可见的行为变更(新能力、默认值变更、修复、移除)要明确且事实性地标注
  9. 不得提及 commit 类型、scope、changelog 或任何 reduce 阶段的分类——map 与 reduce 职责严格分离,map 只产出原料。

范围界定(<scope>

Include: functions, methods, types, API changes, behavior/logic changes,
error handling, performance, security.
Exclude: import reordering, whitespace/formatting, comment-only changes,
debug statements.

白名单(函数/方法/类型、API 变更、行为或逻辑变更、错误处理、性能、安全)与黑名单(import 重排、空白与格式、纯注释、调试语句)并用,让模型在"什么值得写"上有明确裁决标准。

输出格式(<output_format>

map 阶段的输出是每文件一个 # 标题 + - 子弹列表的 Markdown:

# src/config.rs
- added TOML configuration loading
- changed default timeout to 30s

# src/main.rs
- changed CLI parsing to accept config paths

# src/empty.rs

配套规则:

  • 每个输入文件恰好一个 # 标题,path 必须与 <file path="..."> 中完全一致;
  • 观察以 - 子弹列在标题下;
  • 必须覆盖每一个输入文件;某文件没有相关观察时,只输出其 # 标题、不带任何子弹——这个"空标题"约定非常关键,它是后续解析器判定"文件已处理"的信号。

验证清单(<verification>

三条自检:每条观察都能被该文件的 Diff 直接支撑;没有观察只依赖 <related_files>;没有重复、琐碎或分类导向的观察。模板末尾还有一句总纲:Observations only. Reduce phase handles classification and synthesis.(只产出观察,分类与综合由 reduce 阶段负责)。

模板变量与 Handlebars 渲染

map.md 的用户段位于 <!-- USER --> 之后,包含两个 Handlebars 块:

<files>
{{#each files}}
<file path="{{path}}">
{{diff}}
</file>
{{/each}}
</files>
{{#if context_header}}
<related_files>
{{ context_header }}
</related_files>
{{/if}}

渲染时注入两个变量:files(形如 { path, diff } 的对象数组)与可选的 context_header(related_files 文本)。在 map-reduce.tsmapFileBatch 中可以看到实际装配:

const promptFiles = files.map(file => ({ path: file.filename, diff: renderFileDiffForBatch(file, budget) }));
const prompts = renderConventionalPrompt("map", { files: promptFiles, context_header: contextHeader });

renderFileDiffForBatch(file, budget) 会按批次 token 预算裁剪单个文件的 Diff:若文件 token 估算与字节数都在预算内则完整保留,否则克隆文件、调用 truncate 截断内容(保留头部 15 行、尾部 10 行,中间以 ... (truncated N lines) ... 占位,参见 diff.ts)。

related_files 上下文的构造逻辑

ContextHeaders 类(map-reduce.ts)负责为每个批次生成"同一次变更中的其他文件"概览,其核心是 inferFileDescriptionmap-reduce.ts)这个基于文件名与内容启发式的分类器:

  • 文件名含 testtest file
  • prompt / systemprompt template
  • 后缀 .mddocumentation
  • config 或后缀 .toml/.yaml/.ymlconfiguration
  • errorerror definitions;含 typetype definitions
  • mod.rs/lib.rsmodule exportsmain.rs/main.go/main.pyentry point
  • 内容含 class /def /fn implementation;含 struct /enum type definitions;含 async /awaitasync code;否则兜底 source code

headerForFiles(currentFiles) 取出不在当前批次中的其他文件,按 additions + deletions 行数降序排列,最多展示 MAX_CONTEXT_FILES = 20 个,超出时追加 ... and N more files 提示。特别地,当提交文件总数超过 100 时,直接退化为一行 (Large commit with N total files),避免超大提交生成巨型上下文。这些相关文件描述正是 map 提示词中"只用来解析名称或引用"的那部分上下文。

批次编排与并发执行

mapPhasemap-reduce.ts)是 map 阶段的运行时编排,值得注意的工程细节:

  • 二进制文件短路isBinary 的文件不进入模型调用,直接产出固定的 Binary file changed. 观察并保留其增删行数与状态;
  • 预算计算effectiveMapBudget(totalTokens, config.mapBatchTokenBudget) 先按 totalTokens * 5 / (16 * 4) 估算理想批次预算,再夹在 MIN_MAP_BATCH_TOKENS = 4_000 与用户配置的 mapBatchTokenBudget(默认 16_000)之间;
  • 贪心分批buildLlmFileBatches 过滤掉二进制文件后,用 buildBatchesForIndices 做 token(预算 ×1)与字节(预算 ×4)双预算的贪心装箱;单文件超过预算时独占一个批次;
  • 并发度MAP_PHASE_CONCURRENCY = 16mapWithConcurrency 用固定数量 worker 的竞速模式(worker 循环取下一个 index)处理批次,保证长尾批次不被阻塞;
  • 进度反馈:每个批次调用 inference.complete 时携带 progressLabel(如 Mapping batch 2/5 (3 files)…),toolNamecreate_file_observationspromptFamily"map"

输出解析:宽容的 Markdown 契约

map 提示词要求"不带 fences 输出",但模型输出往往不守规矩。解析器 parseFileObservationsMarkdownmarkdown.ts)对多种形态做了宽容处理:

  • JSON 形态{"files":[{path, observations}]} 或纯数组均可解析,字段兼容 path/fileobservations/details
  • Markdown 形态:逐行扫描,行首可匹配 # 标题、file: /file = /`` 反引号包裹等变体(正则 ^(?:#+\s*)?(?:file\s*[:=-]\s*)??(.+?)?\s*:??$),且候选必须含 /.才被视为文件标题,避免把普通句子误判为路径;子弹行支持- * • – +以及有序列表1.`;
  • 解析结果会去掉每条观察末尾的句号(stripTrailingPeriod),统一为无句号事实句。

随后 mapResponseToObservationsmap-reduce.ts)把解析结果映射回文件顺序,匹配策略分三档:

  1. 候选路径与文件名完全相等
  2. 文件名basename 唯一时按 basename 匹配;
  3. 最后按路径后缀匹配(pathSuffixMatches)。

若模型因 max_tokens/length 停止且某文件无观察,会回填 Updated <basename>. 兜底观察;完全无匹配的文件同样回填兜底;批次结果全部返回后,任何缺失观察的文件会抛出 Missing map observation for <filename> 错误——保证 reduce 阶段拿到的观察集合与输入文件一一对应。

观察如何进入 reduce 阶段

runMapReducemap-reduce.ts)在 map 完成后,调用 renderObservationsMarkdown 把观察渲染成 reduce 的输入:

# src/foo.rs (+12/-4)
- added retry logic for network calls
- changed default timeout to 30s

未修改文件会标注状态(added/deleted/renamed)与 +N/-N 行数(map-reduce.ts),然后连同 condenseStat 处理过的 git stat、scope 候选与类型定义一起注入 reduce.md 模板,由 reduce 阶段产出单一 # type(scope): summary 标题、3~6 条细节与可选的 Fixes: issue 引用。至此完成"map 提取事实观察 → reduce 综合分类"的完整闭环,最终分析结果会用于生成 conventional commit 消息。

小结与复用建议

map.md 的设计精髓可以提炼为三条原则:

  1. 职责分离:map 只做"逐文件、逐条、有依据"的事实提取(0~5 条/文件、≤100 字符/条、过去式句式),分类、scope、changelog 全部留给 reduce,从提示词层面杜绝模型在原料阶段就下结论;
  2. 契约优先:明确的 # 文件 + - 子弹 输出格式、空标题表示"无观察"的约定,配合 markdown.ts 的宽容解析与三档路径匹配,让输出稳定可机器消费;
  3. 资源可控:token 估算驱动触发阈值(默认 5_000)、批预算(默认 16_000)、并发 16、单文件 50_000 token 上限与按优先级截断,保证超大提交也可控地完成分析。

如果你需要在自己的 Agent 流水线中复用这套模式,可以直接参考 map.md 的提示词结构,配合 map-reduce.ts 的分批与并发编排,以及 markdown.ts 的解析器;若要调整触发灵敏度,只需修改 config.tsmapReduceThresholdmapBatchTokenBudget 等设置。相关行为在 commit-conventional.test.ts 中有配套测试可参考。

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527