Skip to content

Latest commit

 

History

History
103 lines (69 loc) · 12.3 KB

File metadata and controls

103 lines (69 loc) · 12.3 KB

BanGD 设计说明

BanGD 是一个面向 BanDB 数据库引擎(Go)垂类的 AI PR 评审助手。它要解决的核心问题是:通用 AI 评审器(Copilot 等)只能查出"潜在运行问题"并给出最低级解法(异步改同步、无脑加锁、recover 兜 panic),停在症状层面;而数据库内核的改动需要从架构层面判断——识别根因、质疑所有权模型、在消除问题的同时考虑性能。

本文说明三项核心能力的实现,以及在模型选择、上下文获取、未来扩展三条主线上的设计思路,并贯穿讨论分析准确性、上下文理解、误报/漏报控制、响应速度与使用体验这五个关键因素。


一、三项核心能力

评审一次性产出结构化的三部分(ReviewResult,见 src/core/schema.ts):

能力 字段 说明
PR 变更总结 changeSummary 用数据库架构语言概括"动了系统哪一层、改了什么不变量/数据结构",而非逐行复述 diff
风险代码识别 findings[] + overallRisk 每条 finding 带文件/行号、严重度、类型(并发/内存/锁/存储/schema/性能/资源/错误处理/兼容);overallRisk(高/中/低)供 triage
Review 建议生成 findings[].architecturalSolution 每条按 问题根因 → 为什么低级解法不够 → 架构级方案 → 代价/收益 四段式给出建议
普通代码级问题 generalFindings[] 任何称职通用评审者都会指出的、有 diff 证据的确凿正确性/逻辑缺陷(逻辑反、边界、空指针、吞错、资源泄漏等),普通修法即可、不走四段式——见 §七

这个固定的四段式结构是质量的关键:它强制模型把"症状"推进到"根因"再到"架构方案",从输出格式上杜绝了"加个锁就完事"的低级答案。

findings(架构级)是 BanGD 的差异化价值;generalFindings(普通级)则确保它不把通用 bug 拱手让给 Copilot——两者并存,详见 §七。


二、模型选择

默认 Claude Opus 4.x,可配置(action.ymlmodel 输入)。

  • 为什么是 Opus:垂类内核评审的本质是多步因果推理——从"并发 panic"推到"所有权模型错误",再推到"双表设计"。这种推理深度正是大模型与小模型的分水岭;小模型恰恰会给出我们要规避的"低级解法"。准确性优先,所以默认选最强推理档。
  • 为什么是 Claude:代码与架构推理能力强;原生支持 tool-use 强制结构化输出prompt caching,这两点直接服务于准确性和响应速度(见下)。
  • 结构化输出而非解析自由文本AnthropicLlmClient 用一个 submit_review 工具,把 ReviewResult 的 JSON Schema 作为 input_schema 强制模型按结构产出,再由 core 用 Zod 校验(ReviewResultSchema.parse)。这样端到端类型安全、零正则解析、无格式抖动——这是误报控制的第一道闸:格式错误的"幻觉结果"在解析边界就被拒绝。
  • 分档空间:模型是注入参数,未来可做分档——用便宜快速的档(如 Haiku)做变更总结/初筛,用 Opus 做深度 finding,在速度/成本与深度间取舍。
  • Provider 可配置(开发/生产分离)AnthropicLlmClient 支持 baseURL,可指向 Anthropic 兼容端点(如 DeepSeek 的 https://api.deepseek.com/anthropic)。开发/冒烟用便宜的兼容模型打通链路,生产用 Opus 保证点评质量——前者验证"链路通不通",后者决定"点评准不准",二者不可混淆。prompt caching 是 Anthropic 专有能力,对兼容 provider 自动关闭(shouldUsePromptCaching)。

三、上下文获取方式

只看 diff 会让评审退化成通用 linter。 架构级判断需要 diff 之外的上下文:被改函数的调用方/被调用方、相关数据结构定义、并发上下文。BanGD 的上下文策略分三层:

  1. 固定领域知识(已实现):系统提示词 = 资深内核工程师人设 + 评审 rubric(9 个维度,每条带"该追问的架构方向")+ few-shot 范例(并发 panic→双表、WAL 顺序→组提交,刻意跨维度防过拟合)。这是"垂类"的来源,约占评审质量的 80%。
  2. PR 上下文(已实现):通过 PrContext 端口取 diff 与 PR 元信息;并读取被改动文件的完整内容,让评审看到 diff 之外的同文件上下文(其它方法、同文件类型定义)。
  3. 周边相关代码(已实现,src/core/related.ts:在评审前增加一轮模型主导的上下文规划——模型依据改动代码里已出现的 import / 标识符,列出需要补读的未改动文件(被引用的 struct/interface 定义、持同一把锁的调用方、同 package 承载相同不变量的文件),core 通过 readFile 拉取(命中即附入上下文,未命中返回 null,代价极低),再进入评审。这把评审从"看 diff"升级为"看系统",是准确性与上下文理解的最大增量。
    • 刻意的取舍:只做一轮规划、复用单次 generateStructured 端口(不改成多轮交错 tool-use),并对补读文件数(MAX_RELATED_FILES)与字符预算(RELATED_CHAR_BUDGET)设硬上限——以一次未缓存的额外调用换取关键上下文,成本可控。补读的文件路径随 ReviewOutcome.relatedFiles 暴露并写入评论页脚/日志,让评审"补读了哪些文件来做架构判断"可被直接看到。规划失败永不致命(返回空,评审照常进行)。

Prompt caching 与速度:第 1 层(系统提示词+rubric+范例)是大块且稳定的内容,AnthropicLlmClient 在该块上打 cache_control 断点,使每个 PR 只有 diff 这条尾巴是未缓存的,显著降低延迟与成本——这是响应速度的主要手段。


四、五个关键因素如何被照顾

  • 分析准确性:领域 rubric + few-shot 锚定深度;四段式输出强制推到根因与架构方案;结构化输出 + Zod 校验杜绝格式幻觉。
  • 上下文理解:固定领域知识 + PR 元信息已覆盖;readFile 端口为"读周边代码"的 agentic 扩展铺好路;未来叠加 RAG(设计文档/历史评审)。
  • 误报/漏报控制
    • 降误报:每条 finding 必须给出"为什么低级解法不够 + 代价/收益",用论证成本过滤噪音;并明确允许"只需小修就照实说",避免为显高级强行架构化。对抗式验证已落地src/core/verify.ts):产出后对每条 finding 派 N 个独立"反驳者",各自在变更上下文下判断它是真实隐患还是误报(证据不足时倾向判误报),严格多数否决才丢弃——单个过激反驳者无法误杀真 finding,调用失败一票按"未否决"计(绝不静默压制)。票数 verify_votes 可配(默认 3,置 0 关闭;无 finding 时零开销);被过滤条数随 droppedFindings 暴露在页脚与日志,可度量。
    • 降漏报:rubric 的多维度保证覆盖广度;few-shot 多样性避免视角单一。后续可加多轮 loop-until-dry
    • 度量:两者都依赖评测语料集(历史 PR + 专家评审)来量化 precision/recall——这是把"感觉准"变成"可度量"的关键工程。
  • 响应速度:prompt caching(固定块全部命中);MVP 单轮单次调用;max_tokens 有界;模型分档空间。深度与速度的取舍点明确留给配置与分档。
  • 使用体验:结构化 Markdown 评论——顶部风险徽章(🔴🟡🟢)+ 变更总结,下面逐条 finding 带严重度 emoji;overallRisk 支持快速 triage;以 GitHub Action 形态运行,CI 内零额外搭建;模型可配置。

五、架构决策:为什么核心与触发解耦

核心评审逻辑(src/core/gather context → call LLM → Zod validate只依赖两个注入端口 LlmClientPrContext,不 import 任何 Octokit / @actions/*。GitHub Action 只是实现这两个端口的薄壳(src/shell/)。

这一个决策同时满足三件事:

  • 可演进:从 Action 升级到常驻 Probot App,只是换一层壳,core 不动。
  • 可测试:core 用假端口做纯单元测试,无需网络——这也是"每个功能必带测试"这条铁律能落地的前提。
  • anyFinding/ReviewResult 类型由 Zod z.infer 推出,模型输出在解析边界被校验,全链路类型安全(eslint 强制 no-explicit-any)。

六、未来扩展方向

按"性价比/对质量的杠杆"排序:

  1. Agentic 周边代码读取(最大质量杠杆,已落地第一版,见 §三.3):当前是单轮规划 + 有界拉取。后续可做多轮(看到补读内容后再决定是否继续探索,loop-until-enough)、或升级为模型交错发起 read_file 的真正 tool-use 循环;并可引入仓库文件树(listFiles,限定到 diff 触及的目录以控 token)提高补读命中率。
  2. 评测语料集 + rubric 迭代:用历史 PR + 专家评审度量 precision/recall,数据驱动地打磨 rubric 与范例。
  3. 对抗式验证已落地,见 §四"降误报",src/core/verify.ts):对每条 finding 派多个"反驳者",严格多数否决则丢弃,系统性压低误报。架构级与普通级 finding 走同一套对抗式复核(见 §七)。后续可做视角分化(让每个反驳者从不同失败维度切入,而非同质重复),进一步提升过滤质量。
  4. RAG 知识库:索引 BanDB 设计文档、历史评审决策、数据库系统模式,增强上下文理解。
  5. 行级 inline 评论:用 pulls.createReview 把建议落到具体代码行,而非单条汇总评论,提升使用体验。
  6. Probot App 形态:常驻服务,支持记忆、增量评审、跨仓统计(端口已使其成为换壳工作)。
  7. 多智能体流水线:找隐患 / 验证可行性 / 综合,分工提升深度与可靠性。
  8. 模型分档与增量评审:便宜档初筛 + 强档深评;只重评新增 commit,进一步压低成本与延迟。

七、通用评审生态位(generalFindings)

架构级 findings 是 BanGD 的差异化价值,但只产出架构级问题会留下一个缺口:那些任何称职通用评审者都会指出的普通 bug(分支条件写反、off-by-one、空指针、吞错、资源未释放、明显竞态写法)如果 BanGD 不报,就等于把这块生态位拱手让给 Copilot。所以评审同时产出第二类结果 generalFindings——普通代码级问题。

设计上与架构级刻意区分:

  • 结构更轻generalFinding 只有 file/line/severity/category/title/description/suggestion(见 src/core/schema.ts),不走四段式——这些问题不值得"根因→低级解法不够→架构方案→代价/收益"的论证,指出位置 + 普通修法即可。
  • 自带 few-shot:与每个架构维度一样,普通问题也有专门的范例(prompts/examples/general-findings.md),示范"该报什么"与"绝不该报什么"(红线)。它不随维度门控,每次评审恒定注入,落在 prompt-cache 的稳定块里,边际成本近乎为零。
  • 质量红线(写进系统提示词):只报有 diff 证据、能定位具体出错点的确凿正确性/逻辑问题;不报风格/命名/格式/"建议加测试"这类 nits;不与架构级 finding 重复;最多约 6 条,没有就返回空数组。红线的目的是不让 BanGD 沦为嘈杂的通用 linter——宁缺毋滥。
  • 同样经过对抗式复核generalFindingsfindings 走同一套多反驳者、严格多数否决的验证(verifyGeneralFindings,反驳提示词换成"是否为真实正确性缺陷"),把普通级误报也压下去。
  • 投递方式不同:架构问题以 Issue 跟踪(是需长期跟进的设计问题);普通问题只在 PR 汇总评论里内联列出(是即时的 bug 提示,随 diff 失效,不必建 Issue)。
  • 可降级generalFindings 在 schema 里 default([]),模型遗漏该字段时不让整条评审降级为解析失败。

这样 BanGD 在一次评审里既占住通用评审生态位(不漏普通 bug),又保住垂类架构深度这一差异化优势。