首页
/ oh-my-pi 提交信息自动生成中的 summary-rewrite:基于 LLM 的单行摘要合规改写机制解析

oh-my-pi 提交信息自动生成中的 summary-rewrite:基于 LLM 的单行摘要合规改写机制解析

2026-09-09 19:41:29作者:毕习沙Eudora

导读

本文剖析 oh-my-pi 提交信息自动生成管线中一个关键但容易被忽略的环节:summary-rewrite 提示词(packages/coding-agent/src/commit/conventional/prompts/summary-rewrite.md)。当 LLM 生成的首版 conventional commit 摘要因时态、长度、标点等规则校验不通过时,系统会调用该提示词,让模型以"复制编辑"身份在尽量少改动原文的前提下将其改写为合规摘要。读完本文,你将掌握该提示词的完整约束体系、模板变量的来源与语义、底层校验规则如何驱动改写,以及它在"生成 → 校验 → 修复 → 重写 → 兜底"整条降级链中的准确位置,并可直接借鉴该设计思路搭建自己的 LLM 提交信息纠错环节。

一、它在流水线中的位置:一次"挽救性"的二次生成

oh-my-pi 的常规提交信息生成流程位于 packages/coding-agent/src/commit/conventional/generate.ts。核心入口 generateConventionalCommit 会根据改动规模选择快速工作流(fast)或标准工作流(analysis + summary)。标准工作流中,摘要的生成与挽救逻辑集中在 generateSummaryFromAnalysisgenerate.ts#L250-L314):

  1. 基于 holistic analysis 生成一份摘要草稿;
  2. 调用 validateSummaryQuality 校验;
  3. 校验失败先尝试 repairSummaryTense 做纯规则的时态修复;
  4. 规则修复不可行时,才调用 rewriteSummaryForCompliance 把草稿连同拒绝原因交给 summary-rewrite 提示词重写;
  5. 重写仍失败则落入 fallbackSummaryForCommit 确定性兜底。

也就是说,summary-rewrite摘要质量保障链上的第三道防线:它处理的是"语义正确但形式不合规"的草稿,属于 LLM 的二次生成,而非首轮生成。

调用处 rewriteSummaryForCompliancegenerate.ts#L330-L362)完整展示了该提示词如何被渲染与消费:

const prompts = renderConventionalPrompt("summary-rewrite", {
    commit_type: commitType,
    chars,
    draft,
    rejection,
});
const summary = await inference.complete(
    {
        operation: "summary-rewrite",
        role: "summary",
        promptFamily: "summary-rewrite",
        systemPrompt: prompts.system,
        userPrompt: prompts.user,
        toolName: "create_commit_summary",
        progressLabel: "Rewriting commit summary…",
    },
    response => stripTypePrefix(parseSummaryMarkdown(response.text)).trim(),
);

返回结果会再次经过 acceptSummary 校验(先验证、再尝试时态修复),只有最终通过 validateSummaryQuality 的文本才会被采用;否则返回空字符串,交由上层继续走兜底逻辑。这保证了"重写"本身也不会绕过规则。

二、提示词的主体:一个"复制编辑"角色的五条约束

summary-rewrite.md 的系统提示词将模型定位为 copy editor(复制编辑),强调这是一次纯粹的文本编辑任务,模型不需要理解底层代码变更,只需在保留原意的前提下让草稿满足全部约束。原文中的五条约束是:

  1. 以小写过去时动词开头(added、replaced、migrated、restructured 等),永远不要用提交类型 token 本身开头;
  2. 不超过用户消息中给定的字符上限
  3. 无类型/作用域前缀、无结尾句号、无引号、无 markdown 格式
  4. 尽可能少改动措辞,绝不添加草稿中不存在的信息
  5. 只修复被报告的问题,不做任何其他改动

这五条约束与校验层一一对应:第 1 条对应 validation.tsisPastTenseFirstWord(过去时首词检查)与 type_word_repetition(首词不得重复类型 token);第 2 条对应三级长度阈值;第 3 条对应 trailing_period、前缀剥离与 parseSummaryMarkdown 的解析契约;第 4、5 条则是工程化约束,防止模型借重写之机扩大信息量或引入新事实。

值得注意的是约束 1 的措辞 ——"never the commit type token itself"。这与校验器中的 type_word_repetition 检查(validation.ts#L259-L268)互为表里:例如类型是 feat 时,摘要不能以 "feat" 开头,因为 feat(scope): feat something 是语义重复。

三、输出格式契约:<summary> 标签与宽容解析

提示词要求模型不带代码围栏地返回如下格式:

<summary>rewritten text only</summary>

但模型输出往往不可控。为此消费端没有依赖严格解析,而是使用了宽容的多级解析器 parseSummaryMarkdownmarkdown.ts#L99-L117),依次尝试:

  • JSON 解析:若输出形如 {"summary": "..."} 或带 title/message 键,则直接取值;
  • 标签提取:宽容匹配 <summary>...</summary>extractTagLenient),不要求标签闭合规范;
  • 纯文本降级:剥离 # 标题标记、Title: 等标签前缀、成对包裹引号,再规整空白。

随后统一经过 stripTypePrefixmarkdown.ts#L40-L55)剥掉可能残留的 type(scope): 前缀并去掉结尾句号。该设计让模型即使输出 Markdown 代码围栏、JSON 或带前缀文本,也能被正确还原为摘要本体。测试 commit-conventional.test.ts#L160-L180 覆盖了 Title: Added JWT auth、含围栏的 <summary> 等多种形态,验证了解析器的健壮性。

四、用户消息模板变量:commit_type、chars、draft、rejection 从哪来

提示词文件以 <!-- USER --> 为界分成系统段与用户段。渲染机制见 prompts.tsrenderConventionalPrompt 用该分隔符切分模板,系统段原样使用,用户段交给 prompt.render(Handlebars 风格模板)填充上下文。六个提示词家族(analysis、fast、map、reduce、summary、summary-rewrite)共用这套渲染管线。

summary-rewrite 的用户段模板包含三个占位区:

<commit_metadata>
commit_type: {{ commit_type }}
max_summary_chars: {{ chars }}
</commit_metadata>

<draft_summary>
{{ draft }}
</draft_summary>

<rejection_reason>
{{ rejection }}
</rejection_reason>

对应调用方 rewriteSummaryForCompliance 传入的四个参数,语义如下:

  • commit_type:analysis 阶段判定的提交类型(feat / fix / refactor 等),用于让模型避开类型 token 并选用合适的过去时动词;
  • chars:可用的摘要字符额度。它由 generateSummaryFromAnalysis 预先算好 —— chars = Math.max(20, config.summaryGuideline - prefixLength),其中 prefixLengthtype: type(scope): 前缀的码点长度(generate.ts#L257-L258)。也就是说提示词里的限制是扣除前缀后的正文额度,并保底下限 20 字符;
  • draft:被判定不合规的草稿摘要(取首份失败的候选,经 stripTypePrefix 清洗);
  • rejection:校验器返回的错误信息拼接串(report.errors.map(issue => issue.message).join("; ")),例如 "Summary must start with a past-tense verb (ending in -ed/-d or irregular)"。

模型的任务就是基于 rejection 指出的具体问题修正 draft,这正是"只修复被报告的问题"约束得以落地的信息基础 —— 模型能确切看到自己哪里违规了。

五、裁判规则:校验层如何定义"合规"

要理解提示词为何这样写,需要先看它的裁判 —— validateSummaryContentvalidation.ts#L243-L294)及其支撑数据。合规定义包含四类检查:

5.1 过去时首词判定

isPastTenseFirstWordvalidation.ts#L88-L101)是核心难点。判定一个 token 是否为过去时动词,依赖 resources/validation_data.json 中的大规模词表:

  • past_tense 对照表:数百对"现在时 → 过去时"映射,如 add→added、rewrite→rewrote、run→ran、take→took,覆盖大量不规则动词(rewrite/rewrote、freeze/froze 等)与工程领域高频动词(rebuild/rebuilt、benchmark/benchmarked);
  • irregular_past 列表:left、felt、meant、sent、spent、lost、slept 等无法靠后缀推断的不规则过去式;
  • ed_blocklist:hundred、red、bed、need、seed、sacred 等以 -ed 结尾但并非过去式的单词;
  • d_blocklist:and、bad、had、good、food、should 等以 -d 结尾的干扰项。

判定逻辑是:命中对照表中的过去式、或以 -ed 结尾且不在 blocklist、或以元音+d 结尾(长度≥4)且不在 blocklist、或命中不规则列表,即视为过去时。此外还支持剥离 token 中的非字母后缀(如 re-add 拆出 re + add 再递归判定),防止标点粘连干扰首词识别。

5.2 类型重复、标点与内容质量

  • 首词不得与提交类型同名(type_word_repetition);
  • 摘要不得以句号结尾(trailing_period);
  • 命中 filler_words(comprehensive、various、seamless、robust、world-class 等营销空洞词)触发 warning;
  • 命中 meta_phrases("this commit"、"this change"、"some changes"、"refactored code" 等元描述短语)触发 warning —— 这正是提示词要求"描述变更本身"的校验依据。

5.3 长度三级阈值

长度校验(validateSummaryvalidation.ts#L188-L241)按第一行总长(类型+作用域+分隔符+摘要)设定三级阈值,默认值见 config.ts#L67-L86

级别 默认值 超限后果
summaryGuideline 72 warning:超出建议长度
summarySoftLimit 96 warning:超出软限制
summaryHardLimit 128 error:硬性超限,校验失败

注意 summary-rewritechars 用的是 summaryGuideline - prefixLength,即提示模型尽量压到建议值以内;但最终决定成败的是 summaryHardLimit。长度按 Unicode 码点(而非 UTF-16 单元)计算,由 text.tscodePointLength 实现,并对齐 Python 字符串长度语义,避免 emoji 或 CJK 字符导致字节数误判。

5.4 纯规则修复:repairSummaryTense

在校验失败进入重写前,还有一个零成本修复手段 repairSummaryTensevalidation.ts#L49-L55):如果首词在 presentToPast 映射中存在,直接替换为过去式即可通过校验。例如草稿 "replace dependencies with local implementations" 会被机械修复为 "replaced dependencies with local implementations"(该行为有测试覆盖,见 commit-conventional.test.ts#L182-L189)。只有这种修复不可行时(如首词根本不是动词、长度超标、或含禁止短语),才轮到 summary-rewrite 出场。

六、设计要点:为什么强调"最小改动"与"不新增信息"

对比同目录的初代摘要提示词 summary.md 可以更清楚 summary-rewrite 的分工差异:

  • summary.md 是"撰写者":基于 detail points 与 diff stat 综合提炼整个变更集的伞状描述,明确禁止照抄单一 detail point,并附有类型→动词参考表(feat→added/introduced、fix→corrected/resolved、refactor→restructured/migrated 等)和禁用词表(improved、enhanced、now 等);
  • summary-rewrite.md 是"编辑者":输入已有一份语义正确的草稿,不负责重新理解代码,只负责让形式合规。因此它明确写出 "Change as little wording as possible; never add information that is not in the draft",避免二次生成引入幻觉或偏离草稿原意 —— 这是生成类任务中最实用的防漂移手段。

两者的衔接在 generateSummaryFromAnalysis 中一目了然:首轮用 summary.md 生成 → 校验 → 失败则尝试规则修复 → 仍失败才用 summary-rewrite.md 重写。重写后的结果如果还是不行,最终由 fallbackSummaryForCommitgenerate.ts#L364-L395)兜底 —— 它内部也维护了一张类型→过去时动词映射表(feat→added、fix→fixed、refactor→restructured、docs→documented、perf→optimized、build/ci/chore→updated、style→formatted、revert→reverted),并结合 stat 中的文件路径生成确定性摘要。整条链路上每一次 LLM 尝试都有规则校验把关,确保最终提交信息要么合规、要么带着 validationError 返回给人工处理。

七、测试证据与配置可调性

自动化测试 commit-conventional.test.ts 直接验证了本机制:用例 "repairs a rejected generated summary before rewrite or fallback"(L229-L243)构造一个首轮输出 "correct null dereference"(现在时、不合规)的场景,断言管线依次执行 analysissummary 两次调用后产出合规的 "corrected null dereference"。同时 "uses one fast call for changes at the 200-line threshold"(L193-L212)验证了 200 行阈值的快速路径,说明摘要修复逻辑与工作流路由是解耦的。

涉及的重写行为由配置间接调控:maxRetries(默认 3)控制摘要生成的重试次数;三级长度阈值、mapReduceEnabled 等项可通过 CommitSettings 覆盖(见 config.ts#L88-L96conventionalGenerationConfig 合并逻辑)。如果你的团队希望摘要更短或更宽松,只需调整 summaryGuideline / summarySoftLimit / summaryHardLimit 即可,提示词中的 chars 会随之自动重算。

结语

summary-rewrite 提示词看似只有寥寥数行,却是 oh-my-pi 提交信息质量工程中"规则驱动 + LLM 修复 + 确定性兜底"三层防线的重要一环。它的可借鉴之处在于:把校验规则显式地喂给模型(通过 rejection_reason)、限定模型角色为纯文本编辑(最小改动、不新增信息)、用宽容解析消化不可控输出(标签/JSON/纯文本三级降级)、以及每次 LLM 输出都回归校验器。这套"先生成、后校验、再修复"的闭环,比单纯依赖模型自觉更可靠,值得在各类 LLM 文本生成后处理场景中复用。

<输出文章>

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
901
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
604
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23