ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Harness 工程学实战:用 Claude Agent SDK 把缺陷调查封装成专属智能体驭具的 18 节配置手册

AI Harness 工程学实战:用 Claude Agent SDK 把缺陷调查封装成专属智能体驭具的 18 节配置手册 1. 缺陷调查为什么需要一个专属驭具线上告警每天都会来但真正让人头疼的不是告警本身而是从「看到一条 Sentry 报错」到「写出一份能贴进工单的根因报告」之间那段重复劳动。值班工程师要打开 Sentry 看堆栈、翻 GitHub 找最近改动、查 Linear 有没有相关工单、再去 Vercel 确认是不是某次部署引入的最后把这些碎片拼成一段话。这套动作每天重复流程稳定输入输出清晰正是 AI Harness 最该接手的场景。AI Harness 这个词近一年被反复提起但底层定义其实很短它就是包在 AI Agent 外面、帮助模型更高效完成某一类工作的一层代码。译成「驭具」或「外壳」更贴切。这层代码可以是普通的 TypeScript 函数、Python 脚本、命令行循环也可以是嵌入了另一次模型调用的子流程关键是它位于模型和真实业务之间承担约束、编排、上下文注入与结果回收的职责。缺陷调查bug triage之所以是 Harness 的教科书场景因为它同时满足三个条件高频——线上告警每天若干条低歧义——调查路径收敛于拉代码、看提交、查文档、跑测试、写结论这几个固定动作有明确验收物——最终产物要么是一份调查记录要么是一个 Linear 工单要么是一个 GitHub PR格式明确、下游消费者明确、失败成本可控。直接用通用编码工具做这件事每次都要写「亲爱的 Agent请修这个 bug」并附上链接、解释意图意图会在每次提示中被模型重新解读同一份告警今天拿到的是「查日志」、明天变成「改代码」结果高度漂移。Harness 的反方向是把意图预编码进系统所有原本要写进提示的内容都被提前烧结进 harness 的系统提示词和适配器配置里启动一次调查只需要粘贴一条 Sentry 链接。这篇手册按 18 节路径展开从环境搭建到智能体运行闭环交付可复制的 settings.json 与 config.toml 骨架、TaoToken 统一 Key/API 通道接入步骤以及逐节验证动作。适合正在把缺陷调查这类重复工作流固化成专属智能体的工程师也适合想理解 Claude Agent SDK 工程化落地的团队。2. TaoToken 前置统一 Key 与 API 通道在写第一行 harness 代码之前先把模型调用通道准备好。Claude Agent SDK 需要一个能稳定访问 Claude 系列模型的入口TaoToken 提供统一的 Key 和 API 通道把模型调用、额度管理、密钥轮换收敛到一个地方harness 里只需要配置一个 Base URL 和一个 Key。先注册并拿到 Key。访问 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 完成注册进入控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面点「创建密钥」复制生成的 sk- 开头的字符串。这个 Key 只显示一次建议立刻存进密码管理器。拿到 Key 之后先确认通道可用。TaoToken 的 API 端点是 https://taotoken.net/api 不带任何 UTM 参数。你可以先用 curl 做一次最小验证curl https://taotoken.net/api/v1/messages \ -H x-api-key: sk-你的Key \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-sonnet-4-6, max_tokens: 64, messages: [{role: user, content: 回复 OK 两个字母}] }如果返回里能看到 content 数组和一段文本说明 Key 和通道都正常。这一步很重要因为后面 harness 报错时你要能快速区分是通道问题还是代码问题。接下来把 Key 写进环境变量不要硬编码进代码。在项目根目录创建.env文件# .env TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_BASE_URLhttps://taotoken.net/api ANTHROPIC_BASE_URLhttps://taotoken.net/api ANTHROPIC_API_KEYsk-你的KeyClaude Agent SDK 和 Claude Code 都认ANTHROPIC_BASE_URL与ANTHROPIC_API_KEY这两个变量把它们指向 TaoToken 的端点SDK 就会走统一通道。这样做的另一个好处是harness 代码里不需要出现任何密钥换 Key 只改.env。如果你用的是 Claude Code 命令行可以直接在 settings.json 里配置。Claude Code 的配置文件路径是~/.claude/settings.json全局或项目根目录的.claude/settings.json项目级。项目级配置优先适合把 harness 相关的模型配置和团队共享{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-6 }, permissions: { allow: [ Read, Grep, Glob ], deny: [ Write, Edit, Bash(rm:*), Bash(git push:*) ] } }这份 settings.json 做了两件事一是把模型通道指向 TaoToken二是把权限收窄到只读——这正是缺陷调查 harness 需要的 investigate-only 模式。deny列表里显式禁掉了写文件、编辑、删除和推送模型即便想改代码也越不过这道闸。如果你更习惯用 TOML 管理配置可以建一份config.toml作为 harness 自己的配置骨架和 settings.json 分工settings.json 管 Claude Code 运行时config.toml 管 harness 的业务参数。# config.toml [model] provider taotoken base_url https://taotoken.net/api model_id claude-sonnet-4-6 max_tokens 4096 [run] mode investigate-only timeout_minutes 10 max_tool_calls 8 [artifacts] root ./runs report_template incident-report.md [adapters.sentry] enabled true fields [title, stack, release, lastSeen] [adapters.github] enabled true verbs [touchesOf, prsForCommit, blameSnippet] [adapters.linear] enabled true verbs [createInvestigationTicket, findRelated]这份 config.toml 把模型、运行模式、工件路径、适配器能力都写死了。harness 启动时读它模型看不到「要不要改代码」这种选项因为mode investigate-only已经在配置层锁死。验证配置是否生效跑一条最小命令claude -p 读取当前目录的 config.toml用一句话总结 run.mode 的值如果输出里出现investigate-only说明 Claude Code 已经通过 TaoToken 通道连上模型并且能读到项目配置。这一步过了再往下写 harness 代码就不会在通道问题上浪费时间。3. 可复制配置settings.json 与 config.toml 骨架上一节把通道打通了这一节把两份配置文件补全成可以直接复制使用的骨架。缺陷调查 harness 的配置分两层Claude Code 运行时配置settings.json和 harness 业务配置config.toml。两层各管各的互不污染。先看完整的 settings.json。这份配置放在项目根目录的.claude/settings.json团队共享新人 clone 下来就能用{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-6, ANTHROPIC_SMALL_FAST_MODEL: claude-haiku-4-5 }, permissions: { allow: [ Read, Grep, Glob, WebFetch(domain:docs.sentry.io), WebFetch(domain:docs.github.com) ], deny: [ Write, Edit, MultiEdit, Bash(rm:*), Bash(git push:*), Bash(git commit:*), Bash(gh pr create:*) ], ask: [ Bash(git log:*), Bash(git blame:*) ] }, includeCoAuthoredBy: false, cleanupPeriodDays: 30 }这份配置的关键在permissions三段。allow里放只读工具和两个白名单域名模型可以读文件、搜代码、抓 Sentry 和 GitHub 的官方文档。deny里放所有写操作模型看不到这些工具也就不会尝试。ask里放 git log 和 git blame这两个命令虽然只读但会触发仓库扫描让模型先问一句更稳妥。ANTHROPIC_SMALL_FAST_MODEL指向一个更便宜的模型用于 harness 内部的辅助调用比如给工件生成标题、做字段归一化。主模型负责根因推理小模型负责杂活成本能压下来不少。再看 config.toml 的完整版。这份配置放在 harness 项目根目录是 harness 自己的业务参数# config.toml [model] provider taotoken base_url https://taotoken.net/api model_id claude-sonnet-4-6 small_model_id claude-haiku-4-5 max_tokens 4096 temperature 0.2 [run] mode investigate-only timeout_minutes 10 max_tool_calls 8 max_retries 2 session_per_run true [artifacts] root ./runs report_template incident-report.md write_sync true html_report true [adapters.sentry] enabled true base_url https://sentry.io/api/0 fields [title, stack, release, lastSeen, affectedUsers] max_frames 8 [adapters.github] enabled true base_url https://api.github.com verbs [touchesOf, prsForCommit, blameSnippet] max_commits 10 [adapters.linear] enabled true base_url https://api.linear.app/graphql verbs [createInvestigationTicket, findRelated] default_team ENG [adapters.vercel] enabled true base_url https://api.vercel.com verbs [getDeployment, getDeploymentLogs]这份 config.toml 里有几个参数值得展开。temperature 0.2是刻意压低的缺陷调查要的是稳定复现不是创意发散。session_per_run true保证每次调查独立会话上下文不跨 run 泄漏。write_sync true让工件同步落盘进程崩溃时不会丢证据。max_frames 8限制堆栈帧数量避免超长堆栈撑爆上下文。适配器部分每个都只暴露少量动词。Sentry 只读五个字段GitHub 只暴露三个查询动作Linear 只暴露建工单和查关联Vercel 只暴露查部署和查日志。这种「窄工具面」是 harness 稳定性的来源——模型能碰的东西越少行为方差越小。两份配置写好后用一条命令验证它们能被正确加载claude -p 读取 .claude/settings.json 和 config.toml列出 permissions.deny 里的所有条目和 adapters 下所有 enabledtrue 的适配器名预期输出应该包含 Write、Edit、MultiEdit、Bash(rm:)、Bash(git push:) 等 deny 条目以及 sentry、github、linear、vercel 四个适配器。如果输出缺项说明配置文件路径不对或 JSON/TOML 语法有误用python -m json.tool和python -c import tomllib; tomllib.load(open(config.toml,rb))分别校验。配置骨架到这里就完整了。接下来写 harness 的入口代码把这些配置读进来装配成一次可运行的调查流程。4. 验证请求从一条 Sentry 链接到一份报告配置就绪后写 harness 的最小可运行版本。这一节交付一个能跑通「粘贴 Sentry 链接 → 产出根因报告」的完整流程代码量控制在百行以内方便你按自己的场景改。先建项目结构。整个 harness 主体只有一小撮文件这是刻意的设计——文件越少越容易 review越不容易在迭代中失控bug-harness/ ├── .claude/ │ └── settings.json ├── config.toml ├── .env ├── package.json ├── src/ │ ├── index.ts # 入口解析参数 │ ├── run.ts # run 生命周期 │ ├── adapters/ │ │ ├── sentry.ts │ │ ├── github.ts │ │ ├── linear.ts │ │ └── vercel.ts │ ├── artifacts.ts # 工件落盘 │ └── report.ts # 报告渲染 └── runs/ # 工件输出目录先写适配器。Sentry 适配器只暴露一个方法把 issue URL 解析成结构化摘要// src/adapters/sentry.ts import { config } from ../config; export interface SentryDigest { fingerprint: string; title: string; exceptionType: string; exceptionMessage: string; topFrames: string[]; release: string; lastSeen: string; affectedUsers: number; } export async function digestFromUrl(url: string): PromiseSentryDigest { const issueId url.split(/issues/)[1]?.split(/)[0]; if (!issueId) throw new Error(无法从 URL 解析 issue ID: ${url}); const res await fetch( ${config.adapters.sentry.base_url}/issues/${issueId}/events/latest/, { headers: { Authorization: Bearer ${process.env.SENTRY_TOKEN} } } ); if (!res.ok) throw new Error(Sentry API ${res.status}: ${await res.text()}); const event await res.json(); const frames event.entries ?.find((e: any) e.type exception) ?.data?.values?.[0]?.stacktrace?.frames ?? []; return { fingerprint: event.groupID, title: event.title, exceptionType: event.metadata?.type ?? Unknown, exceptionMessage: event.metadata?.value ?? , topFrames: frames.slice(-config.adapters.sentry.max_frames).map( (f: any) ${f.filename}:${f.lineNo} in ${f.function} ), release: event.release?.version ?? unknown, lastSeen: event.dateCreated, affectedUsers: event.userCount ?? 0, }; }这个适配器做了三件事解析 URL、调 Sentry API、裁剪字段。它不返回完整 payload只返回调查需要的部分。max_frames从 config.toml 读默认 8 帧。GitHub 适配器暴露三个查询动作都只读// src/adapters/github.ts export interface CommitDigest { sha: string; message: string; author: string; date: string; } export async function touchesOf( file: string, since: string ): PromiseCommitDigest[] { const res await fetch( https://api.github.com/repos/${process.env.GH_REPO}/commits?path${file}since${since}, { headers: { Authorization: Bearer ${process.env.GH_TOKEN} } } ); if (!res.ok) throw new Error(GitHub API ${res.status}); const commits await res.json(); return commits.slice(0, 10).map((c: any) ({ sha: c.sha.slice(0, 8), message: c.commit.message.split(\n)[0], author: c.commit.author.name, date: c.commit.author.date, })); }入口文件把配置读进来装配 run// src/index.ts import { readFileSync } from fs; import { parse } from smol-toml; import { runInvestigation } from ./run; const config parse(readFileSync(./config.toml, utf-8)); const sentryUrl process.argv[2]; if (!sentryUrl) { console.error(用法: npx tsx src/index.ts sentry-issue-url); process.exit(1); } runInvestigation(sentryUrl, config).catch((err) { console.error(调查失败:, err.message); process.exit(1); });run.ts 是核心把适配器调用、模型推理、工件落盘串起来// src/run.ts import { digestFromUrl } from ./adapters/sentry; import { touchesOf } from ./adapters/github; import { writeArtifact } from ./artifacts; import { renderReport } from ./report; export async function runInvestigation(url: string, config: any) { const runId run-${Date.now()}; console.log([${runId}] 开始调查: ${url}); // 第一步拉证据确定性 const digest await digestFromUrl(url); console.log([${runId}] 证据收集完成: ${digest.title}); // 第二步查相关提交确定性 const suspectFile digest.topFrames[0]?.split(:)[0] ?? ; const commits suspectFile ? await touchesOf(suspectFile, digest.lastSeen) : []; console.log([${runId}] 找到 ${commits.length} 条相关提交); // 第三步模型推理非确定性 const report await renderReport({ digest, commits }, config); console.log([${runId}] 根因报告生成完成); // 第四步工件落盘确定性 const path await writeArtifact(runId, report, config); console.log([${runId}] 工件已写入: ${path}); return { runId, path, report }; }report.ts 负责调模型。这里用 Claude Agent SDK 的 query 接口把证据组装成 prompt// src/report.ts import { query } from anthropic-ai/claude-agent-sdk; export async function renderReport( evidence: { digest: any; commits: any[] }, config: any ): Promisestring { const prompt 你是一个缺陷调查助手。基于以下证据产出一份根因报告。 ## Sentry 证据 - 标题: ${evidence.digest.title} - 异常: ${evidence.digest.exceptionType}: ${evidence.digest.exceptionMessage} - 影响用户: ${evidence.digest.affectedUsers} - 最近发生: ${evidence.digest.lastSeen} - 堆栈: ${evidence.digest.topFrames.map((f: string) ${f}).join(\n)} ## 相关提交 ${evidence.commits.map((c: any) - ${c.sha} ${c.message} (${c.author}, ${c.date})).join(\n)} ## 输出要求 必须包含三段顺序不可调换 1. 症状摘要一句话 2. 根因判定列出候选假设按可能性排序每条附证据 3. 后续工件建议创建的 Linear 工单标题和描述 如果证据不足以判定根因在第二段明确写出「当前信息不足需要补充...」。; const result await query({ prompt, options: { model: config.model.model_id, maxTurns: 1, allowedTools: [], }, }); return result; }注意allowedTools: []——报告生成阶段不需要任何工具调用模型只负责把已有证据组织成文字。工具调用全部发生在证据收集阶段由确定性代码完成。这种「确定性收集 非确定性总结」的分工是 harness 稳定性的关键。跑一次完整调查npx tsx src/index.ts https://sentry.io/organizations/your-org/issues/1234567890/预期输出[run-1737000000000] 开始调查: https://sentry.io/... [run-1737000000000] 证据收集完成: editor mutation dropped [run-1737000000000] 找到 3 条相关提交 [run-1737000000000] 根因报告生成完成 [run-1737000000000] 工件已写入: ./runs/run-1737000000000/report.md打开runs/run-1737000000000/report.md应该能看到三段式报告。如果第二段出现了「当前信息不足」说明证据确实不够这是 harness 在诚实汇报不是 bug。到这里从一条 Sentry 链接到一份报告的闭环就跑通了。整个过程模型只参与了一次调用其余全是确定性代码。这就是 harness 与「套壳聊天」的区别——模型是流程里的一颗零件不是流程的中心。5. 常见报错排查401、local proxy failed、reading choices、OAuthharness 跑起来之后报错会集中在几个固定位置。这一节按真实报错信息逐条排查每条都给出定位方法和修复动作。401 Unauthorized / invalid x-api-key这是最常见的报错出现在模型调用阶段。完整报错通常长这样Error: 401 {type:error,error:{type:authentication_error,message:invalid x-api-key}}定位顺序先确认.env里的ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是否都被正确加载。Claude Agent SDK 读的是环境变量不是 config.toml。如果你在 config.toml 里写了 Key 但没写进.envSDK 读不到。验证方法node -e console.log(process.env.ANTHROPIC_BASE_URL, process.env.ANTHROPIC_API_KEY?.slice(0,8))预期输出https://taotoken.net/api sk-xxxxxx。如果 base_url 是空的说明.env没被加载检查是否用了 dotenv 或在启动命令前加了source .env。如果环境变量正确但依然 401用 curl 直接测通道curl -s https://taotoken.net/api/v1/messages \ -H x-api-key: $ANTHROPIC_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d {model:claude-sonnet-4-6,max_tokens:16,messages:[{role:user,content:hi}]}curl 通而 SDK 不通说明 SDK 版本或配置读取路径有问题。检查anthropic-ai/claude-agent-sdk版本旧版本可能不认ANTHROPIC_BASE_URL升级到最新版。local proxy failed / ECONNREFUSED 127.0.0.1完整报错Error: local proxy failed: connect ECONNREFUSED 127.0.0.1:8080这个报错说明 SDK 在尝试连本地代理而不是直连 TaoToken。原因通常是环境里残留了HTTP_PROXY或HTTPS_PROXY变量或者 settings.json 里配了proxy字段。排查env | grep -i proxy如果有输出在启动 harness 前清掉unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy同时检查.claude/settings.json里有没有proxy或httpProxy字段有就删掉。TaoToken 通道是直连的不需要任何代理配置。reading choices of undefined完整报错TypeError: Cannot read properties of undefined (reading choices)这个报错说明代码在按 OpenAI 的响应格式解析但实际拿到的是 Anthropic 格式。Anthropic 的响应是content数组OpenAI 是choices数组。如果你混用了两套 SDK或者手动拼了 HTTP 请求但按 OpenAI 格式解析就会撞上这个。定位搜代码里所有.choices确认它们只出现在真正调 OpenAI 格式接口的地方。Claude Agent SDK 的query()返回的是字符串不需要解析choices。如果你在 harness 里同时用了 OpenAI SDK 和 Claude SDK确保两边的 base_url 和 Key 分开配置不要共用ANTHROPIC_API_KEY。OAuth error / authentication failed完整报错Error: OAuth authentication failed: invalid_grant这个报错出现在 Claude Code 命令行场景说明 Claude Code 在尝试用 OAuth 登录而不是用 API Key。Claude Code 默认会走 OAuth 流程需要显式配置 API Key 才会走 Key 认证。修复在.claude/settings.json的env里显式写ANTHROPIC_API_KEY并且确保没有oauth相关字段。如果之前登录过 OAuth清掉~/.claude/下的凭据缓存rm -f ~/.claude/credentials.json然后重新用 API Key 配置。验证claude -p 回复 OK如果返回 OK 而不是跳转浏览器说明已经走 Key 认证。模型 ID 不存在 / model not found完整报错Error: 404 {type:error,error:{type:not_found_error,message:model: claude-sonnet-4.6 not found}}注意模型 ID 的写法。Anthropic API 用的是claude-sonnet-4-6连字符不是claude-sonnet-4.6点号。config.toml 和 settings.json 里的model_id都要用连字符格式。验证当前可用模型列表curl -s https://taotoken.net/api/v1/models \ -H x-api-key: $ANTHROPIC_API_KEY \ -H anthropic-version: 2023-06-01 | python -m json.tool返回的列表里挑一个填进配置。工件写入失败 / EACCES完整报错Error: EACCES: permission denied, open ./runs/run-xxx/report.md这是文件系统权限问题。检查runs/目录是否存在且可写mkdir -p runs touch runs/.test rm runs/.test如果容器环境确认挂载卷的权限。write_sync true时工件是同步写的目录不可写会直接抛错不会静默失败——这是好事避免你以为报告生成了其实没落盘。排查完这几类报错harness 基本能稳定运行。建议把 curl 验证通道这一步写进 README新人遇到 401 时先跑 curl能省掉大量猜测。6. 语义一致 CTA把驭具接到你的工作流harness 跑通之后下一步是把它接到团队的真实工作流里。缺陷调查的产出物最终要落到 Linear 工单、GitHub PR 或 Slack 频道这些下游动作需要额外的适配器和权限配置。如果你还在验证模型能力阶段想先确认 TaoToken 通道能稳定支撑 harness 的调用量可以直接在模型对话页面测试不同模型在缺陷调查 prompt 上的表现https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。把第 4 节的 report.ts 里的 prompt 粘进去换几个模型跑同一份证据对比根因判定的质量再决定 config.toml 里model_id填哪个。如果你打算把缺陷调查 harness 长期跑在团队里每天处理几十条告警建议用 Coding Plan 管理调用额度和密钥轮换https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。Coding Plan 适合长期编码和 Agent 场景额度按周期结算比按次调用更可控。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的配置示例和错误码说明。API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 可以创建多个 Key 分给不同 harness 使用一个 Key 泄露不影响其他。如果你用 Claude Code 作为 harness 的运行时ClaudeCodeAnthropic 配置页有专门的接入说明https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 。里面覆盖了 settings.json 的完整字段、权限配置、模型切换和第 3 节的骨架可以对照使用。把 harness 接到 Linear 的动作很简单在 run.ts 的最后加一步// 第五步创建 Linear 工单需要显式确认 if (process.env.AUTO_CREATE_TICKET true) { const ticket await createInvestigationTicket({ title: [自动调查] ${digest.title}, description: report, team: config.adapters.linear.default_team, }); console.log([${runId}] Linear 工单已创建: ${ticket.url}); }注意AUTO_CREATE_TICKET默认关闭。第一次跑建议手动确认报告质量稳定之后再打开自动建单。这是渐进放权的具体做法——先只读再写工件再建工单每一步都验证过再放开下一步。harness 的价值不在于它调用了多强的模型而在于它把模型、工具、权限、工件四件事缝成了一个能被团队日常使用的完整系统。缺陷调查只是第一个场景同样的骨架可以套到 PR 描述生成、发布前体检、客户工单分诊上。把工作流写下来、把约束写下来、把工件存下来剩下的交给模型。
返回列表