从 0 学习 Alibaba Open Code Review(六):JSON 输出结构与 Markdown 报告设计

从 0 学习 Alibaba Open Code Review(六):JSON 输出结构与 Markdown 报告设计 一、前言前面几篇已经把 OpenCodeReview 的几个核心点串起来了第一篇跑通最小 Demo知道ocr review能审查 Git diff。第二篇从 CLI 入口追踪ocr review的执行流程。第三篇理解 Git diff 如何决定审查范围。第四篇理解.opencodereview/rule.json如何影响规则加载。第五篇分析 Agent 工具调用理解file_read、file_find、file_read_diff、code_search、code_comment的作用。这一篇继续往后看OpenCodeReview 审查完成后结果是如何以 JSON 形式输出的JSON 里有哪些字段这些字段又怎样支撑后续的 Markdown 报告、GitHub Actions、PR 评论和自己的 Code Review Agent MVP这篇文章的目标不是只会执行一条命令而是把ocr review --format json背后的数据结构看懂。二、本篇学习目标本篇主要解决 5 个问题ocr review --format json输出了什么summary、tool_calls、comments分别表示什么评论里的start_line、end_line是怎么来的JSON 输出和普通终端输出有什么区别为什么 JSON 输出是后续 Agent 项目的基础三、先确认当前 Demo 的 Review 输入进入练习仓库cd D:\agent\open-code-review-main\ocr-practice-demo先执行 preview看当前 Git diff 会审查哪些文件ocr review--preview输出的关键信息是Preview: 2 file(s) changed | 45 -1 Will review (2): [M] s01/src/user.js 4 -1 [A] outputs-review-result.json 41 -0这里有一个小细节outputs-review-result.json也被纳入了本次 diff。原因是它本身是新增文件并且还没有被 Git 忽略或提交。也就是说OpenCodeReview 是基于 Git diff 判断审查范围的只要文件出现在 diff 里就有可能进入审查范围。所以后面真正做工程化项目时生成物最好放到outputs/目录并通过.gitignore或规则 exclude 排除掉否则下一次 Review 可能会把上一次生成的 JSON 报告也当作代码变更来审查。四、生成 JSON 审查结果OpenCodeReview 支持用--format json输出结构化结果ocr review--format json ocr-review-result.json也可以把输出保存到自己的结果文件里ocr review--format json outputs-review-result.json本文使用本地已有的两份结果样例进行分析D:\agent\open-code-review-main\ocr-practice-demo\ocr-review-result.json D:\agent\open-code-review-main\ocr-practice-demo\outputs-review-result.json其中一份 JSON 的整体结构如下{status:success,summary:{files_reviewed:2,comments:2,total_tokens:65929,input_tokens:61803,output_tokens:4126,cache_read_tokens:35328,elapsed:1m33s},tool_calls:{total:14,by_tool:{code_comment:2,code_search:3,file_find:1,file_read:6,file_read_diff:2}},comments:[{path:s01/src/user.js,content:**SQL Injection Vulnerability**: ...,suggestion_code:db.query(UPDATE users SET email $1 WHERE id $2, [email, userId]);,existing_code:db.query(\UPDATE users SET email \ email \ WHERE id \ userId);,start_line:37,end_line:37}]}可以看到JSON 输出不是一段自然语言总结而是分成了三个核心部分summary本次审查的统计信息。tool_callsAgent 工具调用统计。comments具体代码审查意见。这三个部分正好对应一个 Code Review Agent 最关心的三类数据运行成本、执行过程、审查结论。五、从源码定位 JSON 输出入口前面第二篇已经追过ocr review的 CLI 入口这一篇继续往输出方向追。和最终输出相关的核心文件是cmd/opencodereview/shared.go cmd/opencodereview/output.go internal/model/review.go internal/diff/resolver.go其中shared.go里有一个关键函数funcemitRunResult(ctx context.Context,ag ResultProvider,comments[]model.LlmComment,startTime time.Time,outputFormat,audiencestring,q*quietHandle,)error{commentsdiff.ResolveLineNumbers(comments,ag.Diffs())duration:time.Since(startTime)ifoutputFormatjsonlen(comments)0ag.FilesReviewed()0{returnoutputJSONNoFiles()}ifoutputFormatjson{returnoutputJSONWithWarnings(comments,ag.Warnings(),ag.FilesReviewed(),ag.TotalInputTokens(),ag.TotalOutputTokens(),ag.TotalTokensUsed(),ag.TotalCacheReadTokens(),ag.TotalCacheWriteTokens(),duration,ag.ProjectSummary(),ag.ToolCalls())}outputTextWithWarnings(comments,ag.Warnings())returnnil}这段逻辑说明了三件事最终输出前会先调用diff.ResolveLineNumbers补齐评论行号。如果--format json最终会走outputJSONWithWarnings。如果不是 JSON则走普通的文本输出outputTextWithWarnings。所以可以画出一条简化链路ocr review ↓ Agent Run ↓ 生成 comments ↓ ResolveLineNumbers 补齐行号 ↓ outputJSONWithWarnings ↓ stdout 输出 JSON六、JSON 顶层结构 jsonOutput在cmd/opencodereview/output.go里JSON 输出对应的结构体是jsonOutputtypejsonOutputstruct{Statusstringjson:statusMessagestringjson:message,omitemptySummary*jsonSummaryjson:summary,omitemptyToolCalls*jsonToolCallsjson:tool_callsComments[]model.LlmCommentjson:commentsWarnings[]agent.AgentWarningjson:warnings,omitemptyProjectSummarystringjson:project_summary,omitempty}字段含义可以这样理解字段含义status本次运行状态例如success或skippedmessage补充提示信息没有时省略summary本次审查的统计数据tool_callsAgent 工具调用统计comments具体审查评论列表warnings非致命警告project_summary项目级总结review模式下一般为空scan场景更常见这里有一个细节很多字段带了omitempty。这意味着字段为空时不会出现在 JSON 里。例如没有 warning 时不会输出warnings没有项目总结时不会输出project_summary。七、summary本次审查的运行统计summary对应源码里的jsonSummarytypejsonSummarystruct{FilesReviewedint64json:files_reviewedCommentsint64json:commentsTotalTokensint64json:total_tokensInputTokensint64json:input_tokensOutputTokensint64json:output_tokensCacheReadTokensint64json:cache_read_tokens,omitemptyCacheWriteTokensint64json:cache_write_tokens,omitemptyElapsedstringjson:elapsed}本地样例summary:{files_reviewed:2,comments:2,total_tokens:65929,input_tokens:61803,output_tokens:4126,cache_read_tokens:35328,elapsed:1m33s}这些字段对工程化很有价值字段说明可以用来做什么files_reviewed实际审查的文件数量判断本次 Review 范围大小comments生成的评论数量判断是否发现问题total_tokens总 token 消耗估算成本input_tokens输入 token判断上下文是否过大output_tokens输出 token判断模型生成量cache_read_tokens命中缓存读取的 token判断缓存收益cache_write_tokens写入缓存的 token判断缓存写入情况elapsed审查耗时用于 CI 耗时统计如果后面要做自己的 Agent 项目这些字段可以直接进入运行报告例如## Review Summary - Files reviewed: 2 - Comments: 2 - Total tokens: 65929 - Elapsed: 1m33s八、tool_callsAgent 工具调用统计第五篇已经分析过 OpenCodeReview 的工具调用机制。第六篇要关注的是这些工具调用最后也会进入 JSON 输出。源码中的结构是typejsonToolCallsstruct{Totalint64json:totalByToolmap[string]int64json:by_tool}本地样例tool_calls:{total:14,by_tool:{code_comment:2,code_search:3,file_find:1,file_read:6,file_read_diff:2}}这说明本次审查过程中Agent 一共调用了 14 次工具file_read调用 6 次说明模型多次读取文件上下文。file_read_diff调用 2 次说明模型读取了 diff 内容。code_search调用 3 次说明模型主动搜索了相关代码。file_find调用 1 次说明模型查找过文件。code_comment调用 2 次说明最终产出了 2 条评论。这也是 OpenCodeReview 和“把 diff 直接丢给大模型”的核心区别之一它不是单轮 Prompt而是带工具调用轨迹的 Agent 审查流程。对自己的项目来说tool_calls可以用于生成执行过程摘要## Tool Calls | Tool | Count | |---|---:| | file_read | 6 | | file_read_diff | 2 | | code_search | 3 | | file_find | 1 | | code_comment | 2 |九、comments真正的审查结果comments是最重要的部分因为它包含了真正需要开发者处理的问题。本地样例中一条评论大概长这样{path:s01/src/user.js,content:**SQL Injection Vulnerability**: The email and userId parameters are directly concatenated into the SQL query string...,suggestion_code:db.query(UPDATE users SET email $1 WHERE id $2, [email, userId]);,existing_code:db.query(\UPDATE users SET email \ email \ WHERE id \ userId);,start_line:37,end_line:37}每个字段的作用如下字段含义path问题所在文件路径content审查意见正文suggestion_code建议修改后的代码可能为空existing_code被审查的原始代码片段start_line问题开始行号end_line问题结束行号对后续工程化来说comments是最重要的数据源。因为 Markdown 报告、PR 评论、风险分级、人工确认、审查历史基本都要围绕comments展开。十、LlmComment评论数据模型comments的元素类型是model.LlmComment定义在internal/model/review.gotypeLlmCommentstruct{Pathstringjson:pathContentstringjson:contentSuggestionCodestringjson:suggestion_code,omitemptyExistingCodestringjson:existing_code,omitemptyStartLineintjson:start_lineEndLineintjson:end_lineThinkingstringjson:thinking,omitempty}这里可以注意几个点path和content是最基础的评论信息。suggestion_code和existing_code是可选字段空时不会输出。start_line和end_line是定位评论位置的关键。thinking也是可选字段默认不会总是出现。如果后面要把评论发布到 GitHub PR至少需要这几个字段path start_line / end_line content如果还想做自动修复建议就需要额外使用existing_code suggestion_code十一、评论行号是怎么来的这一点非常关键。模型生成评论时不一定总能稳定给出准确行号。因此 OpenCodeReview 在最终输出前会做一次行号解析commentsdiff.ResolveLineNumbers(comments,ag.Diffs())ResolveLineNumbers定义在internal/diff/resolver.go它的核心思路是先根据 diff 建立文件路径到 diff 的映射。对每条评论优先用existing_code去 diff hunk 里匹配。如果 diff hunk 里匹配不到再回退到新文件完整内容里逐行匹配。匹配成功后补齐start_line和end_line。源码注释里也明确说明// ResolveLineNumbers populates StartLine/EndLine on each comment by matching// the ExistingCode against the corresponding files diff hunks (primary), or// falling back to scanning the full new-file content line-by-line.这说明existing_code不只是展示给用户看的字段它还是定位行号的重要依据。所以如果后续我们自己设计 Agent 输出格式也应该保留existing_code否则后面做 PR 行内评论会困难很多。十二、JSON 输出和普通文本输出的区别OpenCodeReview 默认终端输出更适合人直接看而 JSON 输出更适合程序继续处理。对比项普通文本输出JSON 输出阅读对象人程序和人都可以结构化程度较弱强是否适合二次处理不适合适合是否适合生成 Markdown需要解析文本直接解析字段是否适合 GitHub Actions一般很适合是否适合 Agent MVP不适合很适合后续做自己的项目时更建议基于 JSON 输出而不是解析终端文本。终端文本适合学习和调试JSON 才适合工程化。十三、warnings 和 status 怎么理解JSON 顶层还有两个容易忽略的字段status和warnings。正常审查成功时{status:success}如果没有可审查文件源码里会走outputJSONNoFilesfuncoutputJSONNoFiles()error{out:jsonOutput{Status:skipped,Message:No supported files changed.,Comments:[]model.LlmComment{},ToolCalls:jsonToolCalls{ByTool:map[string]int64{},},}...}结合outputJSONWithWarnings的源码status常见可以分成几类状态出现场景success正常完成没有 warningcompleted_with_warnings审查完成但存在非致命 warningcompleted_with_errors审查过程中部分子任务失败但整体仍输出了结果skipped没有可审查文件warnings用来记录非致命问题。例如某个文件读取失败、某些上下文无法获取、某些工具调用不完整等。它不一定导致整个 Review 失败但应该在报告里保留下来。如果自己生成 Markdown 报告可以加一个 warning 区域## Warnings - xxx warning message这样 CI 里即使 Review 没有完全失败也能看到潜在问题。十四、从 JSON 生成 Markdown 报告理解 JSON 结构后下一步就很自然写一个脚本读取 JSON然后生成 Markdown 报告。最小报告结构可以这样设计# AI Code Review Report ## Summary - Status: success - Files reviewed: 2 - Comments: 2 - Total tokens: 65929 - Elapsed: 1m33s ## Tool Calls | Tool | Count | |---|---:| | file_read | 6 | | file_read_diff | 2 | | code_search | 3 | | file_find | 1 | | code_comment | 2 | ## Findings ### 1. s01/src/user.js:37-37 **Issue** SQL Injection Vulnerability... **Existing Code** js db.query(UPDATE users SET email email WHERE id userId); **Suggestion** js db.query(UPDATE users SET email $1 WHERE id $2, [email, userId]); 这个 Markdown 报告就是后续 Agent 项目的第一个可交付物。它比直接看 JSON 更适合放到 GitHub Actions artifact、README 示例、博客截图和简历项目展示里。十五、Markdown 报告的字段映射从 JSON 到 Markdown 的映射关系可以这样设计JSON 字段Markdown 位置statusSummary 里的运行状态summary.files_reviewedSummary 里的文件数量summary.commentsSummary 里的问题数量summary.total_tokensSummary 里的 token 消耗summary.elapsedSummary 里的耗时tool_calls.by_toolTool Calls 表格comments[].pathFindings 标题comments[].start_lineFindings 标题里的行号comments[].end_lineFindings 标题里的行号comments[].contentIssue 正文comments[].existing_codeExisting Code 代码块comments[].suggestion_codeSuggestion 代码块warningsWarnings 区域这个映射越稳定后面的工程越容易扩展。十六、为什么 JSON 输出适合做 Agent MVP如果只看 OpenCodeReview 本身它已经可以完成 AI Code Review。但如果想做一个能写进简历的 Agent 项目只调用 CLI 还不够。我们需要把它封装成一个自己的工作流Git diff ↓ Run OpenCodeReview ↓ Parse JSON ↓ Normalize Findings ↓ Generate Markdown Report ↓ Save History / GitHub Actions Artifact这里的关键节点就是Parse JSON。因为 JSON 把 OpenCodeReview 的运行结果变成了标准输入数据我们可以继续做很多事情生成 Markdown 报告。根据comments做风险分级。根据tool_calls生成 Agent 执行轨迹。根据summary.total_tokens做成本统计。根据path/start_line/end_line发布 GitHub PR 评论。根据历史 JSON 做审查质量评估。第六篇不是孤立的一篇而是从“源码学习”进入“自研 Agent 项目”的转折点。十七、后续 Agent 项目可以怎么设计基于这一篇的 JSON 输出第一版 MVP 可以设计成Git diff ↓ Run OCR ↓ Parse JSON ↓ Generate Markdown Report对应目录结构可以是open-code-review-agent-mvp ├── agent_graph.py ├── tools │ ├── git_tools.py │ ├── ocr_tools.py │ └── report_tools.py ├── outputs │ ├── review-result.json │ └── review-report.md ├── README.md └── requirements.txt模块职责可以先保持简单模块职责git_tools.py检测当前 Git diff 和变更文件ocr_tools.py调用ocr review --format json并保存结果report_tools.py解析 JSON 并生成 Markdown 报告agent_graph.py串联整个流程第一版不要一上来就做多 Agent、RAG、MCP、Tracing。更合理的顺序是先把 JSON 到 Markdown 跑通再逐步加 LangGraph、风险分级、GitHub Actions、PR 自动评论。十八、本篇用到的命令本文实际用到的命令如下cd D:\agent\open-code-review-main\ocr-practice-demoocr review--previewGet-Content.\ocr-review-result.jsonGet-Content.\outputs-review-result.json源码定位命令Select-String-Path D:\agent\open-code-review-main\cmd\opencodereview\output.go-Patterntype jsonSummary|type jsonToolCalls|type jsonOutput|outputJSONWithWarnings|outputJSONNoFiles-Context 0,25Select-String-Path D:\agent\open-code-review-main\cmd\opencodereview\shared.go-PatternemitRunResult|ResolveLineNumbers|outputJSONWithWarnings|outputTextWithWarnings-Context 0,18Get-ContentD:\agent\open-code-review-main\internal\model\review.goGet-ContentD:\agent\open-code-review-main\internal\diff\resolver.go十九、本篇总结这一篇主要学习了 OpenCodeReview 的 JSON 输出结构。核心结论有 5 个ocr review --format json输出的是结构化审查结果不是普通日志文本。JSON 顶层主要包含status、summary、tool_calls、comments、warnings、project_summary。summary记录文件数量、评论数量、token 消耗和耗时适合做运行统计。tool_calls记录 Agent 工具调用次数适合展示 Agent 执行轨迹。comments是最核心的审查结果可以直接用于 Markdown 报告、PR 评论和风险分级。从源码看最终输出前还有一个重要步骤comments diff.ResolveLineNumbers(comments, ag.Diffs())也就是说OpenCodeReview 会尽量根据existing_code和 diff 内容补齐评论行号。这也是为什么 JSON 里的existing_code、start_line、end_line对后续工程化非常重要。二十、下一篇计划下一篇可以正式进入自己的项目从 0 学 OpenCodeReview设计一个 Code Review Agent MVP下一篇重点不再只是读源码而是开始设计自己的 Agent 工程Git diff ↓ Run OCR ↓ Parse JSON ↓ Generate Markdown Report