尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

open-code-review 架构深度解析:从按下回车到 JSON 输出的完整代码审查流水线

open-code-review 架构深度解析:从按下回车到 JSON 输出的完整代码审查流水线 open-code-review 架构深度解析从按下回车到 JSON 输出的完整代码审查流水线【免费下载链接】open-code-reviewFast, efficient, battle-tested at Alibabas scale. Hybrid architecture code review tool: deterministic pipelines LLM Agent, precise line-level comments, built-in multi-language ruleset (NPE, thread-safety, XSS, SQL injection), OpenAI Anthropic compatible.项目地址: https://gitcode.com/GitHub_Trending/op/open-code-review本指南以ocr review命令为线索完整剖析 open-code-review 的内部工作方式从命令行按下回车到终端出现审查结果 JSON中间经历了 diff 加载、文件过滤、语义分组、LLM 子任务调度、记忆压缩与注释后处理等阶段。读完本文你将理解每条审查结果是如何产生的能够根据实际场景挑选--effort、--concurrency、--max-tokens等启动参数并能借助文末的源码地图定位到具体实现文件进行诊断与二次开发。高层流水线总览ocr review的整体执行可以被抽象为一条单向流水线bootstrap启动→ diff provider差异提供→ filter rules过滤与规则→ semantic grouping语义分组→ subtask dispatch子任务分发→ output writer输出写入。流水线的编排逻辑集中在 internal/agent/ 包中核心文件包括agent.go —— 按组的任务分发与整体编排grouping.go —— 文件语义分组preview.go —— 文件过滤器util.go —— 辅助函数。而工具调用循环与记忆压缩位于相邻的 internal/llmloop/ 包loop.go、compression.go。从代码看两个最重要的入口分别是Agent.Run—— 流水线的起点agent.go。它依次完成 diff 解析、注入只读 DiffMap、文件过滤、语义分组、并发分发子任务、等待后台任务、固化 manifest 并落盘Agent.dispatchSubtasks—— 按文件组并发分发任务的调度器agent.go。diff provider三种 Git 差异模式diff 的获取与解析由 internal/diff/git.go 中的Provider结构体负责。其私有字段mode类型为Mode基于int的枚举决定三种模式分别对应 CLI 的三种 flag 组合模式触发条件结果Workspace不带任何 flag已暂存staged、未暂存unstaged与未跟踪untracked的全部改动Commit--commit sha/-c sha由sha引入的改动通过git show sha等价于 diffsha^..shaRange--from a --to bmerge-base(a, b)..b即从共同祖先到目标分支的改动每种模式底层调用的 git 命令见 git.goWorkspace先执行git diff HEAD若仓库尚无提交无 HEAD则回退到git diff --staged未跟踪文件通过git ls-files --others --exclude-standard枚举后直接从磁盘读取按整文件新增构造 diff——这正是提交前即可审查新文件的实现基础untrackedFileDiffsCommit模式执行git show并显式携带--diff-mergesfirst-parent对合并提交merge commit若不加此参数git 会输出diff --cc格式的 combined diff而ParseDiffText无法解析该格式导致合并提交静默产出零个可审查 diffRange模式先通过git merge-base计算共同祖先再执行git diff base to。每个 diff 记录包含旧路径与新路径、旧片段与新片段、增删行数、二进制文件标记以及重命名信息。DiffContextLines被固定为3git.go与 Git 默认上下文行数一致。目录级排除与 .gitignore不相关目录vendor/、node_modules/、target/等的剔除发生在 diff provider 层面早于单文件过滤。providerDirIgnoreDirsgit.go定义了这类目录前缀还包括.idea/、.vscode/、.svn/、.git/、.happypack/等filterDiffs在进入单文件过滤之前就移除这些 diff。此外 provider 还会读取仓库根目录的.gitignoreloadGitignorePatterns并按 git 的语义解析按文件顺序、最后匹配者生效、!前缀反转结论。目录级硬编码前缀是无条件黑名单.gitignore的!反向规则无法重新放行.git/或node_modules/。五级文件过滤whyExcludeddiff 加载完成后每个文件都会经过 whyExcluded 函数位于 internal/agent/preview.go。它返回以下枚举值之一binary — 文件是二进制文件 user_exclude — 命中 exclude 列表中的模式 unsupported_ext — 扩展名不在 supported_file_types.json 中 default_path — 命中内置的测试文件排除模式若文件未被排除则返回空值ExcludeNone。deleted不会由whyExcluded返回删除状态是在Preview()中稍后计算的——当保留文件的 diff 报告IsDeleted时才追加preview.go。检查按如下顺序执行preview.gobinary—— 二进制文件最先被丢弃user_exclude—— 项目配置的exclude模式永远拥有最高优先级user_include—— 如果过滤器配置了 include 模式且文件命中其中一条则立即保留返回空值跳过后面的unsupported_ext与default_path检查unsupported_ext—— 按允许扩展名列表过滤由 internal/config/allowlist/ 提供见supported_file_types.jsondefault_path—— 最后一道检查匹配内置的测试文件排除模式**/*_test.go、**/*.test.{js,jsx,ts,tsx}、**/__tests__/**、**/*_test.py、**/*_spec.rb、**/*.test.ets等。每个模式都以**/开头从而能命中任意目录层级。想在不消耗任何 token 的情况下查看完整过滤结果直接运行ocr review --preview它会输出每个文件的will_review与exclude_reason等结构化结果。完整算法说明参见 review-rules 文档中的文件如何被过滤。语义分组一次 LLM 调用完成文件聚类过滤完成后OCR 发起一次GROUPING_TASK调用internal/agent/grouping.go。这次调用只把文件元数据路径、状态、/-行数交给模型不含 diff 内容请模型把语义相关的文件归入同一组以便一起审查。典型的分组场景包括同一模块或同一函数涉及的文件、存在生产者—消费者关系的文件接口与实现、同一资源的 i18n / 配置变体以及同一目录下解决同一问题的多个文件。分组受以下限制与兜底逻辑约束每个文件恰好落入一个组单组最多maxFilesPerGroup 10个文件grouping.go若某组内 diff 的 token 总数超限则拆分为单文件组enforceGroupTokenBudgetgrouping.go若分组调用失败、返回空响应或文件只有一个OCR 回退为每组一个文件的逐文件分发toSingleFileGroups。从源码可以看到分组策略还包含本地化短路逻辑groupWithoutLLMgrouping.go当改动规模很小由GROUPING_MIN_FILES与GROUPING_BUNDLE_LINE_THRESHOLD两个阈值判定默认分别为 4 个文件与 200 行见 task_template.json时会跳过 LLM 分组调用直接本地聚合成一组标签为small change set或逐文件分组节省一次不必要的大模型往返。解析模型返回的 JSON 分组结果时parseGroupingResponse会先剥离 markdown 代码围栏再跳过未知路径与重复分配的文件最后为未被任何组覆盖的文件补建单文件组grouping.go。组级子任务规划阶段与主循环对每个文件组OCR 启动一个子代理subagent。每个子代理运行在独立的 goroutine 中并发数量受--concurrency限制默认 8见 agent.go并拥有自己独立的 LLM 消息缓冲区。每个子任务最多包含两个阶段。阶段一规划可选规划阶段是否执行由两个阈值共同决定常量定义见 template.go阈值数值见 task_template.json// template.PlanRequired(fileCount, totalChanged, maxFileChanged) PlanModeLineThreshold 50 // 组内单个文件的最大改动行数 PlanModeGroupLineThreshold 100 // 多文件组的累计改动行数 if maxFileChanged 50 { run plan } // 单文件大改动 if fileCount 2 totalChanged 100 { run plan } // 多个中等规模改动 otherwise { skip plan }两个阈值配合工作PLAN_MODE_LINE_THRESHOLD盯着组内最大的那个文件PLAN_MODE_GROUP_LINE_THRESHOLD盯着整组的累计改动量。第二个阈值被有意设置得更大避免规划阶段被无条件触发。对小 diff 而言规划只会徒增延迟而无收益因此会被静默跳过直接进入主循环。对较大 diffOCR 会执行一次携带PLAN_TASK的 LLM 调用该调用不传Tools字段模型在规划期间无法调用任何工具。规划期只读工具子集code_search、file_read_diff、file_find——即 tools.json 中plan_task标记为true的三个工具以纯文本形式通过{{plan_tools}}占位符嵌入由formatToolDefs函数生成让模型知晓后续将开放哪些能力。模型返回一份检查清单checklist该清单随后成为主提示词中{{plan_guidance}}的值。阶段二主循环多轮对话主循环基于MAIN_TASK提示词与模型进行多轮工具调用对话。完整工具集在规划期工具基础上追加了task_done、code_comment与file_read完整清单见 工具文档。整个主循环最多重复MAX_REVIEW_ROUNDS次该值由--effort控制low 1 轮medium 2 轮默认high 3 轮预设定义见 internal/config/template/effort.go。从第二轮开始规划结果被丢弃避免其限制查找的完整性而前几轮已确认的注释作为已发现内容上下文传入促使模型去查找新的问题。如果某一轮没有任何新发现或已确认注释数达到上限循环提前终止。主循环的伪代码实现见 internal/llmloop/loop.go 的RunMainTaskloop up to MAX_TOOL_REQUEST_TIMES (default 100): response llm.complete(messages, tools) if response.toolCalls is empty: nudge model with You did not successfully call any tools. Please try again or use task_done if finished. continue for each call: execute → collect result if any call was task_done: break addNextMessage(...) # may trigger compression循环共有五种退出条件模型调用了task_done耗尽了MAX_TOOL_REQUEST_TIMES默认 100 次工具请求连续三轮没有得到任何可用工具结果maxConsecutiveEmptyRounds 3loop.go上下文被取消addNextMessage返回false压缩无法将消息缓冲区降回警告阈值以下。一个值得补充的细节当工具请求预算耗尽条件 2时OCR 并不会直接放弃——runGraceRound会追加一轮宽限轮grace round仅开放code_comment与task_done两个工具给模型最后一次机会提交已发现但尚未报告的注释loop.go。无论以何种方式退出收集到的所有code_comment调用都会成为最终审查注释。记忆压缩三区策略长时间的工具调用循环会逐渐撑爆上下文窗口。OCR 采用三区frozen / compress / active策略管理这一问题当 token 预算达到MAX_TOKENS 200000task_template.json的阈值时触发。需要特别注意的是MAX_TOKENS只限制提示词prompt模型输出上限由独立的MAX_COMPLETION_TOKENS 16384控制因此通过--max-tokens调大提示词预算并不会扩大输出预算。阈值常量行为MAX_TOKENS 的 60%tokenSoftThreshold启动异步后台压缩当前循环继续执行。MAX_TOKENS 的 80%tokenWarningThreshold在发送下一条请求前执行同步压缩。两个常量定义于 internal/llmloop/compression.go。三个区域所谓round轮指一条 assistant 消息及其后紧跟的工具结果消息。partitionMessages从消息末尾向前遍历轮次保留所有能装入(0.80 × MAX_TOKENS) - reservedTokens预算的轮次其余更旧的内容构成压缩区。压缩区被序列化为 XMLbuildMessageXML生成message/content/reasoning结构连同MEMORY_COMPRESSION_TASK提示词交给模型返回的摘要被追加到最初的用户消息中包裹在previous_review_summary标签内。压缩之后messages frozen[2] compressed_user_msg active。核心实现如下compression.go// compression.go func (a *Agent) runCompression(ctx context.Context, msgs []llm.Message, filePath string) ([]llm.Message, error) { part : partitionMessages(msgs, a.args.Template.MaxTokens, 0) contextXML : buildMessageXML(msgs[part.frozenEnd:part.compressEnd]) // … call MEMORY_COMPRESSION_TASK … rebuilt[1] llm.NewTextMessage(role, currentText \n\nprevious_review_summary\nrawSummary\n/previous_review_summary) for i : part.compressEnd; i len(msgs); i { rebuilt append(rebuilt, msgs[i]) } return rebuilt, nil }异步与同步压缩异步模式下主循环在后台执行压缩的同时继续处理工具调用在下次 token 检查时tryApplyPendingCompression会把已完成的后台摘要替换进来并保留快照之后追加的消息。若后台任务尚未完成而 token 占比已经越过警告阈值循环会暂停并同步调用runCompression确保下一条请求一定放得进上下文。每个会话conversation的异步压缩任务由独立的compressionState管理避免多个并发子任务互相覆盖对方的压缩任务。注释处理流水线每次code_comment工具调用会产生一条或多条草稿注释。它们进入CommentWorkerPool固定大小的 goroutine 池避免主工具循环被后处理阻塞。完整链路为行号定位在处理器内—— 用滑动窗口算法把existing_code与 diff 对齐计算精确的start_line/end_line对齐失败时两者均为0。行号范围0是未锚定注释的隐式标记不单独保存标志位后续消费者通过检查start_line 0识别需要用户手动定位。重新定位任务可选兜底—— 若在非平凡 diff 上无法定位行号OCR 发起RE_LOCATION_TASK提示词请求模型重新锚定该片段这对被改写paraphrase过的existing_code尤为有效。从源码看同一文件内定位失败后还会先尝试跨文件重定位diff.RelocateAcrossFiles都失败才走 LLM 重新定位loop.go。审查过滤—— 主循环结束后并清空处理池后一次REVIEW_FILTER_TASKLLM 调用按 diff 校验已收集的注释删除可证明为错误的项此阶段的错误只记入日志并被忽略。再次定位行号—— 从Agent.Run返回后顶层命令对完整注释集重新执行diff.ResolveLineNumbers见 cmd/opencodereview/review_cmd.go以处理existing_code横跨多文件或在重新定位阶段被改写的注释。输出成型—— 根据--format渲染为文本或 JSON。Token 预算限制器在发起任何 LLM 调用之前OCR 就执行一道立即终止的检查tokenLimit : MaxTokens * 4 / 5 // 80 % if countMessagesTokens(messages) tokenLimit { record warning token_threshold_exceeded return nil // skip this group }这道检查把巨型 diff自动生成的 lock 文件、涉及数千行的重构挡在请求之前。被跳过的组以非致命警告形式写入 stdout并追加到 JSON 输出的warnings数组。第二道检查在filterLargeDiffs中若单个 diff 超过MAX_TOKENS的 80%在分组与分发之前就被丢弃agent.go。第三道防线位于分组内部——即前文提到的enforceGroupTokenBudget。此外如果配置了--max-tokens-budget聚合预算dispatchSubtasks在获取信号量之前还会做逐组的前瞻性预算核算已用 token 加上该组估算成本若超限则停止调度后续所有组已运行的组允许完成超支上限为在飞数量即 ≤ concurrency。模板与占位符internal/config/template/task_template.json 中定义了六个提示词键用途GROUPING_TASK将变更文件合并成语义相关的组。PLAN_TASK规划阶段——生成检查清单。MAIN_TASK审查主循环——执行code_comment调用。MEMORY_COMPRESSION_TASK生成压缩区摘要。REVIEW_FILTER_TASK主循环后阶段删除可证明为错误的注释。RE_LOCATION_TASK为existing_code匹配失败的注释重新锚定位置。每个提示词都是一组{role, prompt_file}引用指向模板目录下的.md文件例如{role: system, prompt_file: main_task_system.md}实际文件位于 internal/config/template/prompts/。加载时resolveConversation将引用解析为内存中的{role, content}消息随后针对每个组分别进行占位符替换占位符值{{system_rule}}按四层链解析出的规则文本。{{change_files}}其余变更文件不在当前组内的状态与路径。{{diffs}}当前组内全部文件的 diff每个文件包裹在file元素中整体包裹在review_files中。{{file_list}}仅用于GROUPING_TASK变更文件元数据清单——路径、状态、/-行数。{{plan_guidance}}规划阶段的结果跳过规划时被移除。{{confirmed_comments}}前几轮已确认的发现第一轮为空并被移除。{{plan_tools}}规划期工具定义纯文本由formatToolDefs生成用于PLAN_TASK的 system 提示词。{{requirement_background}}--background或--background-file的有效内容文件优先。{{current_system_date_time}}运行开始的本地时间戳格式YYYY-MM-DD HH:MM无秒、无时区见 agent.go 的time.Now().Format(2006-01-02 15:04)。{{context}}仅压缩时转为 XML 的消息用于生成摘要。{{path}}组键组内文件路径以逗号连接用于REVIEW_FILTER_TASK。{{comments}}累积的注释JSON用于REVIEW_FILTER_TASK。占位符替换在 agent.go 中实现。模板本身不能通过 CLI 覆盖要修改提示词需要直接编辑 task_template.json 并重新编译项目。--toolsflag 覆盖的是工具注册表替换 internal/config/toolsconfig 使用的 JSON而不是模板详见 工具自定义。占位符语法细节。上表中除RE_LOCATION_TASK外的所有占位符都使用双花括号{{…}}RE_LOCATION_TASK特殊之处在于它使用单花括号{diff}、{existing_code}、{suggestion_content}进行替换见 internal/diff/relocation.go。数据存储JSONL 会话日志每次审查都会以 JSONL 格式写入磁盘~/.opencodereview/sessions/encoded-repo-path/session-id.jsonl仓库路径不做 base64 编码encodeRepoPathinternal/session/persist.go把/和\替换为-把:替换为_从而得到文件系统安全的目录名。每一行是一个独立事件发送的提示词、LLM 响应、工具调用、工具结果、生成的注释等。Web 界面ocr viewer直接读取这些文件——没有数据库只有追加式日志。界面与事件 schema 的说明参见 会话查看文档。遥测启用遥测后agent 在流水线层面创建三类 spanreview.run覆盖整个任务diff.parse覆盖 diff 加载每个被审查的文件组生成一个subtask.execute.group.group-key。此外在每个决策点会创建短暂的event.namespan如plan.skipped、token.threshold.exceeded、subtask.error。LLM 请求与工具调用仅作为指标记录不作为 span。提示词与响应内容从不附加到遥测中OCR_CONTENT_LOGGING环境变量虽然已接线但目前不生效。完整 schema 见 遥测文档。刻意保留的人工决策以下决策是故意保持手动、不自动化的这在设计上保证了每组确定性与成本可预测端点发现没有 fallback。如果配置文件、环境变量与 rc 文件无法提供完整的(URL, token, model)三元组OCR 以非零码退出而不是尝试猜测。子代理错误被隔离但不重试。单个组的错误只产生一条警告其余组继续处理。重试应交给外部 CI 流水线而不是 agent 内部。跨文件推理被限制在组内。同一语义组的文件共享同一段 LLM 对话因此 agent 可以一起推理它们。其他组的文件只能通过file_read_diff/code_search工具调用访问没有共享上下文且不能作为注释目标main_task提示词要求模型只把上下文工具用于理解并忽略传入 diff 之外发现的问题。源码地图若想深入研读实现以下文件是最佳入口方面文件顶层命令分发cmd/opencodereview/main.goreviewflag 解析cmd/opencodereview/shared_flags.goAgent 编排internal/agent/agent.go、util.go语义分组internal/agent/grouping.go工具调用循环与记忆压缩internal/llmloop/loop.go、compression.goeffort 预设internal/config/template/effort.go文件过滤 / 预览internal/agent/preview.godiff 加载Git 模式internal/diff/git.go规则解析链internal/config/rules/system_rules.go工具注册表与实现internal/tool/LLM 端点解析internal/llm/resolver.goJSONL 会话写入internal/session/persist.goWeb 查看器internal/viewer/server.go构建与测试指引参见 参与开发文档。相关阅读工具文档 —— agent 循环调用的六个工具。审查规则 —— 每个文件的规则文本解析方式。会话查看 —— 查看本流水线记录的对话。【免费下载链接】open-code-reviewFast, efficient, battle-tested at Alibabas scale. Hybrid architecture code review tool: deterministic pipelines LLM Agent, precise line-level comments, built-in multi-language ruleset (NPE, thread-safety, XSS, SQL injection), OpenAI Anthropic compatible.项目地址: https://gitcode.com/GitHub_Trending/op/open-code-review创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表