尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Sandcastle implement-pr 工作流的结构化输出提取契约:`<output>` 标签 JSON 协议与源码实现解析

发布时间:2026/9/26 14:34:22

资讯中心
01
ARTICLE

Sandcastle implement-pr 工作流的结构化输出提取契约:`<output>` 标签 JSON 协议与源码实现解析

Sandcastle implement-pr 工作流的结构化输出提取契约:`<output>` 标签 JSON 协议与源码实现解析
【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载Sandcastlesandcastle.run()在编排沙箱化编码 Agent 时需要从 Agent 的自由文本输出中稳定地抽取机器可读结果。本文以 .sandcastle/agent-workflows/implement-pr/extraction.md 为骨架完整讲解 implement-pr 工作流的输出提取契约Agent 必须如何在响应的最后发射单个output块、块内 JSON 的三种字段语义与空数组规则以及该契约在 implement-pr.ts、run-with-extraction.ts 与 review-output.ts 中的落地实现。读完本文你将掌握如何为编码 Agent 设计可解析的输出协议并能复现 implement-pr 工作流从提取、校验、过滤到落盘的完整链路。一、extraction.md 契约全文Agent 必须遵守的输出协议extraction.md本身是一份面向 Agent 的指令文件extraction prompt它定义了 implement-pr 工作流对 Agent 最终回应的硬性约束。全文如下Emit a singleoutputblock as the last thing in your response.Do not change files. Do not run commands. Do not include text outside theoutputblock.output { threadReplies: [ { commentId: GraphQL node id from PR_COMMENTS_JSON, body: Markdown reply } ], newInlineComments: [ { path: relative/file.ts, line: 123, body: Markdown comment } ], topLevelComments: [ { body: Markdown comment } ] } /outputUse empty arrays when there are no replies or comments.逐条拆解这份契约可以提炼出四个关键约定1. 输出位置与唯一性单个output块、位于响应末尾。契约要求 Agent 把output作为最后发射的东西the last thing且全文只允许出现一个该标签。这样下游解析器无需在长文本中做启发式定位只需锚定最后一个标签即可。2. 行为禁令不修改文件、不运行命令、标签外不输出任何文本。三条禁令共同保证提取阶段extraction run是一个只读、只说话的会话——Agent 在这一轮的唯一任务就是把上一轮生产阶段produce run的结论整理成结构化 JSON而不是再次动代码。这从机制上把干活与汇报两个阶段彻底分离。3. JSON 结构三类 PR 反馈实体。契约给出了可复制的最小骨架三类字段的语义如下表字段类型语义关键字段约束threadReplies对象数组对已存在评论线程的回复commentId必须是来自PR_COMMENTS_JSON的 GraphQL node idbody为 Markdown 正文newInlineComments对象数组新插入的行内评论path为仓库内相对文件路径line为行号正整数body为 Markdown 正文topLevelComments对象数组PR 顶层评论不挂在具体行/线程上仅需body字段4. 空数组规则无回复/无评论时用空数组而不是省略字段或写 null。这条规则让下游校验保持简单——implementPrOutputSchema对缺失字段使用?? []兜底详见第四节但契约仍然要求 Agent 显式写出[]保证输出形状永远一致。值得说明的是该契约并非 implement-pr 独有。仓库内 explore/extraction.md、review/extraction.md、update-branch/extraction.md 都是同构的单output块 JSON协议区别仅在字段集例如 review/extraction.md 要求summary、inlineComments、replies三个字段。可以说标签包裹 固定 JSON 骨架 空数组约定是 Sandcastle Agent 工作流输出提取的通用模式。二、契约如何被接线implement-pr.ts 的两阶段调用链extraction.md不是一份被人工阅读的文档而是被 implement-pr.ts 在运行时读取并注入 Agent 会话的指令。核心接线代码位于 implement-pr.tsconst result await runWithExtraction({ name: implement-pr-${PR_NUMBER}, agent: claudeAgent(), sandbox: noSandbox(), logging: { type: stdout }, promptFile: path.join(import.meta.dirname, prompt.md), promptArgs: { PR_NUMBER, BRANCH, PR_TITLE: context.prTitle, ISSUE_NUMBER: context.issueNumber || (none), ISSUE_TITLE: context.issueTitle || (no linked issue), LINKED_ISSUE: context.linkedIssue, DIFF_TO_MAIN: context.diff, PR_COMMENTS_JSON: context.prCommentsJson, }, output: sandcastle.Output.object({ tag: output, schema: implementPrOutputSchema, }), extractionPrompt: fs.readFileSync( path.join(import.meta.dirname, extraction.md), utf8, ), });几个值得注意的细节extractionPrompt就是本文主角extraction.md被fs.readFileSync原样读入作为提取阶段extraction run的prompt传给 Agenttag: output与契约呼应Output.object的tag指定了要从 Agent stdout 中提取的 XML 标签名与extraction.md中要求的output完全一致schema: implementPrOutputSchema标签内的 JSON 会被解析后用 Standard Schema 校验详见第四节promptArgs中的PR_COMMENTS_JSON契约中commentId字段注明GraphQL node id from PR_COMMENTS_JSON正是由这里注入——Agent 只能回复 PR 对话中真实存在的线程 idnoSandbox()该工作流运行在无沙箱环境下Agent 只负责产生提交与评论内容不负责推送详见 prompt.md 的 Do not push 等禁令。生产阶段的提示词 prompt.md 定义了 Agent 的任务面处理 PR #{{PR_NUMBER}} 上未解决的评审反馈、链接 issue、当前与 main 的 diff、PR 评论 JSON并要求 Agent When complete, outputpromiseCOMPLETE/promise。而 extraction.md 定义的则是汇报面——把结果翻译成三类结构化评论。两者构成完整的指令体系。三、底层机制runWithExtraction 的两阶段生产 提取运行extraction.md之所以敢要求提取阶段不修改文件、不运行命令是因为工作流在机制上就把它设计成独立的一轮运行。核心实现见 run-with-extraction.tsexport async function runWithExtractionT( options: RunWithExtractionOptionsT, ): PromiseRunResult { output: T } { const { output, extractionPrompt, maxRetries 2, ...produceOptions } options; const produce await run(produceOptions); const sessionId produce.iterations.at(-1)?.sessionId; if (!sessionId) { throw new Error( Cannot extract structured output because the produce run had no session id., ); } const { promptArgs: _promptArgs, ...extractionOptions } produceOptions; const extraction await run({ ...extractionOptions, name: produceOptions.name ? ${produceOptions.name} (extract) : undefined, promptFile: undefined, prompt: extractionPrompt, resumeSession: sessionId, output: { ...output, maxRetries }, }); return { ...produce, output: extraction.output }; }机制拆解如下第一轮run(produceOptions)生产阶段使用prompt.md让 Agent 在沙箱/工作区里真正改代码、跑 typecheck、提交 commit。该轮不声明outputAgent 以自由文本形式输出取sessionId从生产轮次最后一次迭代的iterations.at(-1)?.sessionId拿到 Agent 会话 id。取不到时直接抛出 Cannot extract structured output... 错误说明该 Provider 不支持会话延续第二轮run(...)提取阶段以resumeSession: sessionId恢复同一个 Agent 会话但把promptFile替换为prompt: extractionPrompt即 extraction.md 全文并挂上output: Output.object({...})。此时 Agent 带着第一轮的全部上下文已做的修改、PR 对话但只被要求把结果整理成output块——因此无需再改文件或跑命令maxRetries 2若提取或校验失败最多再额外重试 2 次总计 3 次尝试每次重试都会恢复会话并把错误反馈给 Agent让它重新发射修正后的标签返回值{ ...produce, output: extraction.output }——调用方同时拿到生产阶段的commits等元数据与提取阶段的结构化output。从源码注释可以确认run-with-extraction.tsmaxRetries的默认值是2三次尝试它被转发给Output的内置重试机制。这也解释了为什么extraction.md的措辞如此强势last thing、no text outside一旦 Agent 违反契约导致 JSON 解析失败整个提取轮次就会触发重试消耗额外的迭代与 token。四、Schema 校验宽松解析 严格校验的平衡契约中的 JSON 骨架由 review-output.ts 中的implementPrOutputSchema负责校验。该 Schema 用standardSchema包装见 common.ts 的standardSchema辅助函数返回 Standard Schema v1 格式的校验器失败时产出issues数组。关键设计是字段别名兼容行内评论parseInlineComment同时接受path或file、body或comment作为键名review-output.ts因为不同 Agent 在生成 JSON 时键名可能漂移行号解析parseLine先要求line是正整数若line缺失则回退到lineRange字段用正则/\d/提取其首个数字作为行号review-output.ts。这允许 Agent 输出lineRange: 120-135这样的区间表示同时仍能落成单一锚点行顶层评论topLevelComments每个元素只要求非空字符串body空值兜底threadReplies、newInlineComments、topLevelComments均使用record.xxx ?? []兜底即使 Agent 违背契约省略了字段也不会直接崩坏。这套宽松解析、严格语义的策略配合 extraction.md 的空数组约定让最终落盘的数据结构始终是三个固定形状的数组。五、输出后处理可信过滤 兜底失败 结果落盘提取出的结构化输出并不会被直接使用——它要先经过两层可信度过滤再由 implement-pr.ts 落盘第一层回复过滤filterReplies。Agent 声称要回复的commentId必须真实存在于拉取到的未解决评审线程中。validReplyIds由 review-context.ts 构造它通过 GitHub GraphQL API 查询reviewThreads仅收集isResolved false的线程评论 id。不在集合中的回复会被丢弃并打印Dropping reply for commentId...警告review-output.ts。第二层行内评论过滤filterInlineComments。新行内评论的path/line必须落在当前 diff 内。diffLines由 diff-lines.ts 的parseDiffLines从git diff main...HEAD解析而来它跟踪 b/后的文件路径、 -a,b c,d 的起始新行号并把开头行与上下文行空格开头或空行计入可评论行集合。落在 diff 之外的评论文件不在 diff 中、或行号不在 hunk 内会被丢弃并打印警告review-output.ts。这一步非常关键——GitHub 不允许对未变更代码行发表行内评论过滤保证了后续提交流程不会被 API 拒绝。兜底失败判定。若 Agent 既没产生提交result.commits.length 0、又没有通过过滤的回复/行内评论/顶层评论工作流调用fail(...)终止并把原因写入failure_reason.txtimplement-pr.ts、common.ts。也就是说空输出被视为一种需要显式暴露的异常状态。结果落盘。过滤后的数据被写成四个文件目录由环境变量OUTPUT_DIR指定默认/tmp见 common.ts文件内容has_commits.txttrue/false标识 Agent 是否产生提交implement_thread_replies.json过滤后的threadRepliesimplement_new_inline_comments.json过滤后的newInlineCommentsimplement_top_level_comments.jsontopLevelComments最终控制台会打印四项统计commits / thread replies / inline comments / top-level comments 的数量供编排层或人工查看implement-pr.ts。六、与 Sandcastle 核心Output.object的底层联系tag: output与extraction.md中的output标签最终由 Sandcastle 核心模块 src/Output.ts 支撑。核心语义如下Output.object({ tag, schema, maxRetries })返回一个带_tag: object品牌的输出定义run()据此从 Agent stdout 中提取指定标签内容做fence-aware 的 JSON 解析即自动剥掉代码围栏再解析并交给 Standard Schema 校验器验证src/Output.tsmaxRetries的语义是首次之后的额外尝试次数每次重试都会恢复失败的 Agent 会话并反馈 token 高效的错误描述让 Agent 重新发射修正标签默认值为0src/Output.ts。在 implement-pr 工作流中该值被 run-with-extraction.ts 覆盖为2重试的前置条件重试要求 Agent Provider 支持会话恢复即provider.sessionStorage已填充——如 Claude Code、Codex、Pi。若请求了重试但 Provider 无法恢复会话run()会在入口处以明确错误失败src/Output.ts。这与 extraction.md 的约束形成闭环契约extraction.md约束 Agent 的说话方式Output.object约束解析器的提取方式implementPrOutputSchema约束数据的形状三层共同保证自由文本 → 稳定 JSON的可靠性。七、运行前提与实战建议基于源码可确认运行 implement-pr 工作流需要满足以下环境前提implement-pr.ts、review-context.ts环境变量 / 依赖用途PR_NUMBER必填目标 PR 编号BRANCH必填Agent 工作的分支名CLAUDE_CODE_OAUTH_TOKENclaudeAgent()使用 Claude Codeclaude-opus-4-8所需的 OAuth tokencommon.tsGH_REPO形如owner/repoGraphQL 查询reviewThreads时解析仓库归属ghCLI拉取 PR 视图、reviews、GraphQL 线程与 issue 信息OUTPUT_DIR可选结果文件输出目录默认/tmp对希望复用此模式的开发者几条实战建议提取契约必须与 schema 同步维护extraction.md中的字段名与implementPrOutputSchema的解析逻辑一一对应修改一侧务必同步另一侧若想容忍字段漂移可借鉴path/file、body/comment的别名策略过滤逻辑是生产可用性的关键不要直接信任 Agent 输出的commentId与行号务必像 review-output.ts 那样用真实线程 id 集合与 diff 行集合做二次校验否则后续 GitHub API 调用会失败把汇报与干活分成两轮runWithExtraction的生产 恢复会话提取模式让 Agent 既拥有完整上下文、又不至于在汇报阶段引入副作用是值得在自定义工作流中复用的架构。结语一份仅二十余行的 extraction.md背后串联着两阶段运行run-with-extraction.ts、标签提取与重试src/Output.ts、Schema 校验review-output.ts与可信过滤diff 解析 diff-lines.ts、线程集合 review-context.ts四层机制。理解这份契约就等于理解了 Sandcastle 如何让不可控的 Agent 自由文本变成可校验、可过滤、可落盘的结构化数据——这也是所有 Agent 自动化工作流可靠性的基石。赞分享【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载相关推荐sandcastle 结构化输出提取协议以 explore 工作流的 output 契约为例sandcastle 结构化输出提取协议以 explore 工作流的 output 契约为例 导读 本文围绕 .sandcastle/agent workfSandcastle 代码评审 Agent 的结构化输出协议从 output 块到 GitHub Review 载荷Sandcastle 代码评审 Agent 的结构化输出协议从 output 块到 GitHub Review 载荷 导读 本文围绕 sandcastle深入解析 Sandcastle implement-pr 工作流用 Agent 自动消化 PR 评审反馈的提示词设计与实现深入解析 Sandcastle implement pr 工作流用 Agent 自动消化 PR 评审反馈的提示词设计与实现 本篇文章围绕 Sandcastle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。