首页 › tutorials › Agent SDK编程式集成与多仓库批量自动化

概述

本教程聚焦 Claude Code 从"命令行工具"升级为"可编程运行时"的两大能力:Agent SDK(TypeScript/Python 编程式调用)与 Routines(保存的配置+多仓库工作集)。读者将学会用 30 行代码驱动 Claude 自主读文件、跑命令、改代码,并用 xargs/parallel 把同一套指令批量投放到几十个仓库并行执行。适用人群:已熟悉交互式 Claude Code、希望把它嵌入脚本/服务/批处理流水线的后端工程师、平台/SRE、研发效能团队。

前置条件

一、为什么需要编程式集成

交互式的 claude 适合人在回路(human-in-the-loop)的场景:你打字、它思考、你审核。但真实工程里有一类需求它力不从心——把 Claude 当成一个被调用的函数:

这类场景的共同点是:没有人在屏幕前敲回车。Claude 必须以子进程或库的形式被唤起、被喂参数、被收集输出、被关闭。Claude Code 提供了三层能力来满足它,复杂度递增:

层次 形态 适用 本章
L1 claude -p 无头模式 简单脚本、一次性任务 第一节
L2 Agent SDK(TS/Python) 服务化、流式、复杂编排 第二、三节
L3 Routines + 批处理 多仓库、可复用配置 第四、五节

下面逐层展开。

二、L1:-p 无头模式——最轻量的集成

-p(--print)让 Claude 不启动 TUI,而是接收一个 prompt、自主执行若干轮工具调用、把最终答案打印到 stdout 后退出。这是把 Claude 嵌入 shell 脚本最直接的方式。

2.1 最小可用调用

cd ~/repos/my-service
claude -p "用三句话总结这个仓库的架构,并指出最可能的技术债。"

预期输出:一段纯文本落到 stdout,进程退出码为 0。把输出重定向到文件即可留档:

claude -p "..." > review.txt

2.2 关键参数

claude -p \
  --output-format json \          # 结构化输出,便于 jq 解析
  --verbose \                     # json 模式下附带每一步的会话信息
  --max-turns 15 \                # 限制自主轮数,防止失控
  --allowedTools "Read,Grep,Glob,Bash"\  # 收紧权限白名单
  --model claude-sonnet-4-6 \     # 指定模型
  "为 src/ 下所有导出的函数补上 JSDoc 注释,只改注释不改逻辑"

参数说明:

2.3 流式输出解析(stream-json)

claude -p --output-format stream-json --verbose "列出所有 TODO 注释的位置" | \
  jq -r 'select(.type=="tool_use" and .name=="Grep") | .input.pattern'

每个流事件是一个独立 JSON 对象,按类型(system、assistant、tool_use、tool_result、result)过滤即可。这在构建"实时进度看板"时非常有用。

2.4 常见问题

现象 原因 解决
提示需要授权,脚本卡住 默认权限模式会等待用户确认 加 --permission-mode acceptEdits,或预先在 settings.json 配 allow 列表
退出码非 0 但看不出错 stderr 被吞了 用 2>err.log 单独捕获 stderr
JSON 解析失败 默认输出是纯文本 必须显式加 --output-format json
重复登录态丢失 每个子进程独立认证 用 ANTHROPIC_API_KEY 而非 OAuth,或确保共享同一 ~/.claude

三、L2:Agent SDK——把 Claude 当函数调用

当 -p 不够用时(比如需要把 Claude 嵌进一个长期运行的服务、需要在同一会话里多轮交互、需要拿到结构化对象而非解析文本),就轮到 Agent SDK 上场。它把 Claude Code 的整套运行时(工具系统、权限、会话、流式事件)封装成一个库函数 query()。

3.1 安装

# TypeScript / Node
npm install @anthropic-ai/claude-agent-sdk

# Python
pip install claude-agent-sdk

3.2 TypeScript 最小示例

import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  const messages = [{ role: "user", content: "列出 src/ 下所有 TypeScript 文件并统计每个文件的行数" }];

  // query() 是一个异步生成器,逐个 yield 事件
  for await (const message of query({
    prompt: "分析本仓库的测试覆盖率现状,给出提升到 80% 的优先动作清单",
    options: {
      cwd: "/home/me/repos/my-service",  // 工作目录,决定 Claude 能操作哪个仓库
      allowedTools: ["Read", "Grep", "Glob", "Bash"],  // 工具白名单
      maxTurns: 15,
      model: "claude-sonnet-4-6",
      permissionMode: "acceptEdits",
    },
  })) {
    // 每条 message 是一个流事件:assistant 文本、tool_use、tool_result、result...
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") process.stdout.write(block.text);
      }
    }
  }
}

main();

3.3 Python 最小示例

import anyio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        cwd="/home/me/repos/my-service",
        allowed_tools=["Read", "Grep", "Glob", "Bash"],
        max_turns=15,
        model="claude-sonnet-4-6",
        permission_mode="acceptEdits",
    )

    async for message in query(
        prompt="找出所有未处理的 Promise rejection 和 bare except,列出文件:行号",
        options=options,
    ):
        if getattr(message, "type", None) == "assistant":
            for block in message.message.content:
                if block.type == "text":
                    print(block.text, end="")

anyio.run(main)

3.4 三种使用形态

SDK 并非只有"一次性 prompt"一种用法,理解这三种形态决定了你如何编排:

形态 A:单次查询(one-shot)。一个 prompt,自主跑完,返回结果。对应上面的示例,最适合"分析类""报告类"任务。

形态 B:多轮对话(multi-turn)。保留 session_id,下一次 query() 时传入 resume,在同一上下文里继续追问。适合需要人工中途介入审核、再让它继续的场景:

# 第一轮:让它调研
result = await query(prompt="调研迁移到 ESM 的可行性", options=opts)
session_id = result....  # 从结果事件中取出

# (人工审核中间结论后)

# 第二轮:基于同一会话继续
await query(prompt="好,现在按你建议的方案动手改 package.json 和 tsconfig", 
            options={**opts, "resume": session_id})

形态 C:自定义工具(custom tools)。SDK 允许你注册宿主进程里的函数作为 MCP 工具,让 Claude 在执行过程中回调你的代码。例如把"查公司内部监控平台""查 Jira 工单"封装成工具,Claude 就能在分析代码时顺手拉取线上指标。这是把 Claude 与企业内部系统打通的关键闸门。

// 注册一个自定义工具:查内部 SLO 面板
const options = {
  mcpServers: {
    "slo-panel": {
      type: "stdio",
      command: "node",
      args: ["./slo-mcp-server.js"],  // 你自己实现的 MCP server
    },
  },
  // Claude 会像调用 Read/Bash 一样调用 slo-panel 暴露的工具
};

3.5 常见问题

四、L3:Routines——可复用的"配置+工作集"

当同一套指令要在多个仓库上反复跑时,把 prompt、工具白名单、模型、目标仓库列表打包成一个Routine,是最干净的复用方式。Routines 是 Claude Code 官方提供的"保存的配置"机制:一个 Routine = 一个 prompt + 一个或多个仓库 + 一组配置约束。

4.1 为什么需要 Routine

裸用 claude -p 的问题在于:参数散落在各处脚本里,改一次要改 N 个地方;不同人跑出的结果不一致(有人忘了加 --max-turns,有人忘了限制工具)。Routine 把这些固化下来,让"团队统一的代码体检流程"成为一个可分发、可版本化的对象。

4.2 定义与运行

Routines 通常通过 Claude Code 的配置文件或 claude 交互式命令创建/保存(具体语法以你本机版本为准)。一个 Routine 的逻辑结构如下:

# 概念性结构(实际以本机版本配置格式为准)
name: weekly-security-scan
prompt: |
  扫描本仓库的依赖清单与代码,找出已知 CVE、硬编码密钥、
  不安全的反序列化点。输出一份 Markdown 报告,按严重程度排序。
repositories:
  - /home/me/repos/billing-service
  - /home/me/repos/auth-gateway
  - /home/me/repos/notification-worker
options:
  allowedTools: [Read, Grep, Glob, Bash]
  maxTurns: 20
  model: claude-sonnet-4-6
  permissionMode: acceptEdits
  outputFormat: json

运行一个 Routine 等价于:对其中每一个仓库,用相同的 prompt 和 options 跑一次独立的 Claude 会话,并汇总结果。仓库之间的执行天然隔离——各自的工作目录、各自的上下文,互不污染。

4.3 Routine vs -p vs SDK 的取舍

需求 推荐方案
一次性、临时任务 claude -p
团队共享、重复执行的固定流程 Routine
需要嵌入自有服务、复杂事件处理 Agent SDK
需要自定义工具、与企业系统打通 Agent SDK + MCP

五、多仓库并行批处理实战

这是本教程的重头戏。目标:对一组仓库并行执行同一套 Claude 指令,收集结构化结果,控制并发与成本。

5.1 并发模型:每仓库一个独立进程

关键认知:Claude Code 的每次 -p 调用或 SDK query() 都是一个独立的 OS 进程/会话。这意味着多仓库天然适合用进程级并行——不存在共享状态、不需要锁、不会互相干扰。并发度只受两个约束:

  1. API 速率限制(RPM/TPM):并发太高会被 429 拒绝
  2. 本机 CPU/内存:每个 Claude 进程会持有一定上下文

经验值:中小仓库(< 10 万行)的"分析类"任务,并发 4~8 是一个安全的起点;写操作类任务降到 2~4。

5.2 方案 A:xargs 并行(最简单)

#!/usr/bin/env bash
set -euo pipefail

REPO_LIST="repos.txt"        # 每行一个仓库绝对路径
MAX_PARALLEL=4
OUT_DIR="reports/$(date +%Y%m%d)"
mkdir -p "$OUT_DIR"

# 关键:-P 控制并发数;每个仓库一个独立 Claude 进程
cat "$REPO_LIST" | xargs -I {} -P "$MAX_PARALLEL" bash -c '
  repo="$1"
  name=$(basename "$repo")
  out="'"$OUT_DIR"'/${name}.json"
  cd "$repo"
  claude -p \
    --output-format json \
    --max-turns 15 \
    --allowedTools "Read,Grep,Glob,Bash" \
    --permission-mode acceptEdits \
    "审查本仓库的安全性,列出高/中/低危问题各不超过 5 条,给出文件:行号" \
    > "$out" 2> "${out%.json}.err"
  echo "✓ $name (exit=$?)"
' _ {}

要点:

5.3 方案 B:GNU parallel(更强的控流)

#!/usr/bin/env bash
set -euo pipefail

OUT_DIR="reports/$(date +%Y%m%d)"
mkdir -p "$OUT_DIR"
export OUT_DIR

# parallel 支持 --jobs、--load(按系统负载限流)、--retries(失败重试)
parallel --jobs 4 --retries 1 --joblog "$OUT_DIR/joblog.txt" '
  repo={}
  name=$(basename "$repo")
  cd "$repo"
  claude -p --output-format json --max-turns 15 \
    --allowedTools "Read,Grep,Glob" \
    "生成本仓库的架构概述(500 字内)" \
    > "'"$OUT_DIR"'/${name}.json"
' :::: repos.txt

# joblog 记录每个任务的退出码、耗时,便于事后审计

GNU parallel 相比 xargs 的优势:--joblog 自动记录每任务状态、--retries 自动重试被 429 打断的任务、--bar 显示进度条。

5.4 方案 C:SDK 编排(最灵活)

当 xargs/parallel 不够时——例如要按仓库大小动态分配并发、要在结果间做去重合并、要对失败仓库做差异化重试——就用 SDK 自己写编排器:

import asyncio, json, os
from pathlib import Path
from claude_agent_sdk import query, ClaudeAgentOptions

REPOS = Path("repos.txt").read_text().splitlines()
MAX_CONCURRENCY = 4
PROMPT = "审查安全性,按严重程度列出问题,输出 JSON:{issues:[{severity,file,line,desc}]}"

async def scan_one(repo: str, sem: asyncio.Semaphore):
    async with sem:
        opts = ClaudeAgentOptions(
            cwd=repo,
            allowed_tools=["Read", "Grep", "Glob", "Bash"],
            max_turns=15,
            model="claude-sonnet-4-6",
            permission_mode="acceptEdits",
        )
        final_text = ""
        try:
            async for msg in query(prompt=PROMPT, options=opts):
                if getattr(msg, "type", None) == "result":
                    final_text = msg.result
            return {"repo": Path(repo).name, "ok": True, "raw": final_text}
        except Exception as e:
            return {"repo": Path(repo).name, "ok": False, "error": str(e)}

async def main():
    sem = asyncio.Semaphore(MAX_CONCURRENCY)  # 信号量控并发
    results = await asyncio.gather(*[scan_one(r, sem) for r in REPOS])

    # 按严重程度排序输出总报告
    sev_order = {"high": 0, "medium": 1, "low": 2}
    all_issues.sort(key=lambda x: sev_order.get(x.get("severity", "low"), 3))
    Path("reports/summary.json").write_text(json.dumps(all_issues, indent=2, ensure_ascii=False))
    print(f"完成:{len(REPOS)} 个仓库,{len(all_issues)} 条问题")

asyncio.run(main())

这段编排器的关键设计:

5.5 成本与限流控制

批量跑最容易翻车的就是账单和 429。三条铁律:

  1. 永远设 maxTurns。一次失控的自主循环可以烧掉几十美元。
  2. 用 JSON 输出读取 cost_usd,跑完后累加,超出预算阈值自动熔断:
total=$(cat reports/*.json | jq -s '[.[].cost_usd // 0] | add')
echo "本次总花费: $total USD"
  1. 指数退避重试。429 是批量场景的家常便饭,parallel 用 --retries,SDK 自己写 tenacity 装饰器:
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=2, min=4, max=60),
       retry=lambda e: "429" in str(e) or "529" in str(e))
async def scan_one_with_retry(repo, sem):
    ...

六、Mermaid:批量自动化整体架构

flowchart TB
    subgraph 输入
        R[repos.txt<br/>仓库清单]
        P[Prompt / Routine<br/>统一指令]
        C[配置: maxTurns,<br/>allowedTools, model]
    end

    subgraph 编排层
        SEM{并发控制器<br/>Semaphore -P 4}
        DISP[任务分发器<br/>xargs / parallel / SDK]
    end

    subgraph 执行层 每仓库一个独立进程
        W1[Claude 进程 1<br/>repo-A 独立会话]
        W2[Claude 进程 2<br/>repo-B 独立会话]
        W3[Claude 进程 3<br/>repo-C 独立会话]
        WN[Claude 进程 N<br/>repo-N 独立会话]
    end

    subgraph 结果层
        J1[repo-A.json<br/>含 cost_usd]
        J2[repo-B.json]
        J3[repo-C.json]
        AGG[汇总器<br/>jq / Python]
        RPT[总报告<br/>summary.json]
    end

    R --> DISP
    P --> DISP
    C --> DISP
    DISP --> SEM
    SEM --> W1
    SEM --> W2
    SEM --> W3
    SEM --> WN
    W1 --> J1
    W2 --> J2
    W3 --> J3
    WN --> AGG
    J1 --> AGG
    J2 --> AGG
    J3 --> AGG
    AGG --> RPT

    style SEM fill:#ffe4b5
    style AGG fill:#e0ffe0
    style RPT fill:#e0ffe0

图中虚线框住的"并发控制器"是整个架构的命门:它决定了你是 4 倍速跑完还是被 429 雪崩。

七、实战案例:跨仓库依赖漏洞巡检机器人

把前面所有概念串起来,构建一个可投入生产的"每周依赖漏洞巡检"机器人。它每周一早上对一组仓库做依赖审计,按严重程度汇总,并把高危项提成交付物。

7.1 目录结构

vuln-patrol/
├── repos.txt                  # 待巡检仓库清单
├── routine.yaml               # 巡检指令与配置(Routine 思路)
├── run.sh                     # xargs 并行入口(方案 A)
├── orchestrator.py            # SDK 编排入口(方案 C,含重试与汇总)
└── reports/
    └── 20260615/
        ├── billing-service.json
        ├── auth-gateway.json
        └── summary.md

7.2 repos.txt

/home/me/repos/billing-service
/home/me/repos/auth-gateway
/home/me/repos/notification-worker
/home/me/repos/inventory-api
/home/me/repos/search-service

7.3 巡检 Prompt(保存为固定文本,确保可复现)

你是依赖安全审计员。请完成以下步骤:
1. 读取本仓库的依赖清单文件(package.json / requirements.txt / go.mod / pom.xml 等,按实际存在为准)
2. 用 Grep 搜索代码中是否使用了已知有漏洞的模式(eval、pickle.loads、不安全的反序列化、硬编码密钥)
3. 对每个发现,给出:severity(high/medium/low)、file、line、description、suggested_fix
4. 严格输出 JSON:{"repo":"<仓库名>","issues":[...],"scanned_files":N}
不要输出 JSON 以外的内容。

7.4 orchestrator.py(核心,含熔断与汇总)

import asyncio, json, sys
from pathlib import Path
from datetime import date
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from claude_agent_sdk import query, ClaudeAgentOptions

REPOS = [l.strip() for l in Path("repos.txt").read_text().splitlines() if l.strip()]
PROMPT = Path("audit_prompt.txt").read_text()
MAX_CONCURRENCY = 4
BUDGET_USD = 5.0  # 整批预算上限

class RateLimited(Exception): pass

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=2, min=4, max=60),
    retry=retry_if_exception_type(RateLimited),
)
async def audit_one(repo: str, sem: asyncio.Semaphore):
    async with sem:
        opts = ClaudeAgentOptions(
            cwd=repo,
            allowed_tools=["Read", "Grep", "Glob", "Bash"],
            max_turns=20,
            model="claude-sonnet-4-6",
            permission_mode="acceptEdits",
        )
        result_text, cost = "", 0.0
        async for msg in query(prompt=PROMPT, options=opts):
            t = getattr(msg, "type", None)
            if t == "result":
                result_text = getattr(msg, "result", "")
                cost = getattr(msg, "cost_usd", 0.0) or 0.0
            if t == "system" and "429" in str(getattr(msg, "subtype", "")):
                raise RateLimited(repo)
        return {"repo": Path(repo).name, "cost": cost, "raw": result_text}

async def main():
    sem = asyncio.Semaphore(MAX_CONCURRENCY)
    spent = 0.0
    all_issues = []

    tasks = [audit_one(r, sem) for r in REPOS]
    for coro in asyncio.as_completed(tasks):  # 先完成先处理,便于提前熔断
        res = await coro
        spent += res["cost"]
        if spent > BUDGET_USD:
            print(f"⚠️ 预算熔断:已花费 ${spent:.2f} > ${BUDGET_USD}", file=sys.stderr)
            # 取消尚未完成的任务
            for t in tasks:
                t.cancel()
            break
        try:
            data = json.loads(res["raw"])
            for iss in data.get("issues", []):
                iss["repo"] = res["repo"]
                all_issues.append(iss)
        except json.JSONDecodeError:
            print(f"⚠️ {res['repo']} 输出非 JSON,跳过", file=sys.stderr)

    # 生成 Markdown 总报告
    sev = {"high": [], "medium": [], "low": []}
    for i in all_issues:
        sev.setdefault(i.get("severity", "low"), []).append(i)

    md = [f"# 依赖漏洞巡检报告 {date.today()}\n",
          f"扫描仓库 {len(REPOS)} 个,发现问题 {len(all_issues)} 个,花费 ${spent:.2f}\n"]
    for level in ["high", "medium", "low"]:
        md.append(f"\n## {level.upper()} ({len(sev[level])})\n")
        for i in sev[level]:
            md.append(f"- **{i['repo']}** `{i['file']}:{i.get('line','?')}` — {i['description']}")

    out = Path(f"reports/{date.today()}/summary.md")
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text("\n".join(md))
    print(f"报告已生成:{out}")

asyncio.run(main())

7.5 定时调度

用 cron 每周一早 7 点跑(注意避开整点,减少与全网定时任务的撞车):

# 每周一 06:17 跑依赖巡检
17 6 * * 1 cd /home/me/vuln-patrol && /usr/bin/python3 orchestrator.py >> logs/cron.log 2>&1

7.6 预期产出

八、实战避坑清单

经过多轮批量任务验证,以下是最常踩的坑:

  1. cwd 必须显式指定。SDK 不传 cwd 会用宿主进程目录,批量时极易把不同仓库的结果写到同一处。每个任务务必把自己的仓库路径作为 cwd。
  2. 写操作类任务要单独限流。"分析类"(只读)并发可以到 8,"改代码类"并发降到 2~3,并强烈建议 --permission-mode plan 先出方案、人工确认后再 acceptEdits 执行。
  3. 结构化输出要兜底。即使 prompt 里写了"只输出 JSON",模型偶尔也会加前言。永远 try/except 解析,解析失败降级为"标记需人工复核"。
  4. 会话隔离≠认证共享。每个进程的会话独立,但认证(API Key / OAuth token)是共享的。并发越高,单个 key 的 RPM 压力越大——必要时分账户/分 key 池。
  5. maxTurns 是安全阀不是性能参数。设太小任务完不成,设太大一次失控破产。先小批量试跑,观察"成功任务"的 num_turns 中位数,再设为它的 1.5~2 倍。
  6. 结果幂等性。批量任务可能重跑,prompt 要写成幂等的("在文件开头添加注释"而不是"添加注释"),避免重复执行产生重复内容。

九、延伸阅读

十、训练营适用性评估

维度 评估
适用训练营类型 研发效能(DevEx)、平台工程、SRE 自动化、AI 工程化进阶营
学员基础要求 已掌握交互式 Claude Code,熟悉一门脚本语言(Python/Shell),理解进程与并发
不适用 零基础营(应先修"入门篇")、纯前端营(cwd/并发概念较远)
建议教学时长 4 课时:L1 -p(1h)+ L2 SDK(1.5h)+ L3 Routines(0.5h)+ 批量实战(1h)
实操占比 建议 60% 以上,每层配一个可跑通的 mini 任务
考核点 能独立写出带并发控制、成本熔断、结果汇总的批量编排器

参考来源

  1. Agent SDK overview - Claude Code Docs — 官方 Agent SDK 总览,TypeScript/Python 编程式调用入口
  2. Automate work with routines - Claude Code Docs — 官方 Routines 机制:保存配置 + 多仓库工作集
  3. Best practices for Claude Code - Claude Code Docs — 官方最佳实践:环境、prompt、子 Agent 编排
  4. Claude Code Batch Processing Complete Guide — -p 无头模式与批量命令的系统化对照指南
  5. Claude Agent SDK Complete Guide — 实现层级的 SDK 集成深度指南,含多轮编排与自定义工具

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入