概述
Claude Agent SDK 是 Anthropic 官方推出的 Python/TypeScript 库,让开发者能够以编程方式构建可自主执行任务的 AI Agent。它底层复用了 Claude Code CLI 的全部能力,包括工具调用、多轮对话循环、上下文管理等,开发者无需自己实现工具执行逻辑,Agent 可以开箱即用地读取文件、运行命令、搜索代码库、编辑代码等。
与直接调用 Anthropic Messages API 相比,Agent SDK 的核心差异在于:Messages API 需要开发者自己实现工具调用循环(发请求 -> 解析工具调用 -> 执行工具 -> 把结果塞回去 -> 再发请求),而 Agent SDK 把这个循环内置了,开发者只需要描述任务,Agent 自主决定调用哪些工具、何时停止。
本文介绍如何通过 OpenRouter 路由 API 请求来使用 Claude Agent SDK,从而在不直接使用 Anthropic API Key 的情况下,接入 Anthropic 及其他厂商的模型。
环境准备
- Python 3.10+
1. 安装依赖
uv add claude-agent-sdk python-dotenv
或使用 pip:
pip install claude-agent-sdk python-dotenv
Claude Code CLI 已自动打包在
claude-agent-sdk内,无需单独安装。
2. 获取 OpenRouter API Key
前往 OpenRouter 注册账号,在控制台创建 API Key。
3. 配置 .env
在项目根目录创建 .env 文件:
# OpenRouter 模型标识符,格式为 provider/model-name
MODEL=deepseek/deepseek-v3.2
# Anthropic SDK 通过以下三个变量识别 API 地址和鉴权信息
# 将 ANTHROPIC_BASE_URL 指向 OpenRouter,ANTHROPIC_AUTH_TOKEN 填入 OpenRouter API Key
# ANTHROPIC_API_KEY 必须存在但设为空字符串,否则 SDK 会尝试直连 Anthropic
ANTHROPIC_BASE_URL=https://openrouter.ai/api
ANTHROPIC_AUTH_TOKEN=sk-or-v1-your-key-here
ANTHROPIC_API_KEY=
# 禁止 Claude Code CLI 发送 Anthropic 专有 beta header(如 context-management),OpenRouter 不支持这些 header
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1
# 禁用 prompt 缓存,OpenRouter 部分模型不支持 Anthropic 缓存机制
DISABLE_PROMPT_CACHING=1
# 限制每次模型回复的最大 token 数
CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096
代码实现
完整代码如下:
import asyncio
import os
import sys
from dotenv import load_dotenv
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
PermissionResultAllow,
TextBlock,
ToolPermissionContext,
query,
)
load_dotenv()
def stderr_printer(line: str) -> None:
print(line, file=sys.stderr)
async def allow_all_tools(
tool_name: str, tool_input: dict, context: ToolPermissionContext
) -> PermissionResultAllow:
return PermissionResultAllow()
async def prompts():
yield {
"type": "user",
"message": {
"role": "user",
"content": "请先用 ls 查看当前目录,然后用 Read 读取 ./pyproject.toml 并简要分析。",
},
}
async def main() -> None:
async for message in query(
prompt=prompts(),
options=ClaudeAgentOptions(
system_prompt=(
"You are running in /Users/leo/Code/ai/claude/coding-agent. "
"Use paths relative to the current working directory."
),
allowed_tools=["Read", "Edit", "Bash"],
model=os.environ.get("MODEL"),
stderr=stderr_printer,
can_use_tool=allow_all_tools,
cwd=os.getcwd(),
add_dirs=[os.getcwd()],
extra_args={},
),
):
print(f"[{type(message).__name__}]")
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock) and block.text:
print(block.text)
if __name__ == "__main__":
asyncio.run(main())
下面逐段讲解。
导入与初始化
from claude_agent_sdk import (
AssistantMessage, # agent 的文字回复消息
ClaudeAgentOptions, # agent 启动选项
PermissionResultAllow, # 工具调用权限:允许
TextBlock, # 消息内容块:纯文本
ToolPermissionContext, # 工具调用的上下文信息
query, # 驱动 agent 对话循环的核心函数
)
load_dotenv()
load_dotenv() 在程序启动时读取 .env 文件,将其中的变量注入 os.environ。由于 SDK 底层的 Claude Code CLI 子进程会继承父进程的环境变量,.env 里配置的 ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN 等变量会自动传递给 CLI,无需额外处理。
工具调用权限回调
async def allow_all_tools(
tool_name: str, tool_input: dict, context: ToolPermissionContext
) -> PermissionResultAllow:
return PermissionResultAllow()
每次 agent 想调用一个工具前,SDK 都会调用 can_use_tool 回调询问是否允许。这里直接返回 PermissionResultAllow() 表示无条件放行。
如果需要限制某些工具,可以按 tool_name 判断:
async def allow_all_tools(tool_name, tool_input, context):
if tool_name == "Bash":
return PermissionResultDeny(reason="不允许执行 shell 命令")
return PermissionResultAllow()
注入用户消息
async def prompts():
yield {
"type": "user",
"message": {"role": "user", "content": "..."},
}
prompts 是一个异步生成器,用于向 agent 注入用户消息。单轮对话直接传字符串也可以:
await query(prompt="请分析当前项目结构", options=...)
使用异步生成器的优势在于支持多轮对话,可以在 agent 回复后继续 yield 下一条消息,甚至根据 agent 的回复动态决定下一步指令。
启动 agent 与处理消息流
async for message in query(
prompt=prompts(),
options=ClaudeAgentOptions(
system_prompt="...",
allowed_tools=["Read", "Edit", "Bash"],
model=os.environ.get("MODEL"),
stderr=stderr_printer,
can_use_tool=allow_all_tools,
cwd=os.getcwd(),
add_dirs=[os.getcwd()],
),
):
query() 是核心函数,返回一个异步生成器。agent 每完成一个步骤就 yield 一条消息,不会等全部完成再返回,因此输出是流式的。主要参数说明:
| 参数 | 说明 |
|---|---|
system_prompt | 给 agent 的全局指令,限定行为范围 |
allowed_tools | 工具白名单,不在列表中的工具会被拒绝 |
model | OpenRouter 模型标识符,格式为 provider/model-name |
stderr | 接收 CLI 进程 stderr 输出的回调,用于调试 |
can_use_tool | 工具调用权限回调 |
cwd | agent 的工作目录 |
add_dirs | 额外允许 agent 访问的目录列表 |
提取文本输出
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock) and block.text:
print(block.text)
query() 会 yield 多种类型的消息,完整的消息流大致如下:
SystemMessage # CLI 启动后发出的初始化信息(工作目录、工具列表、模型等)
AssistantMessage # agent 的文字回复(可能穿插多条)
ToolUseMessage # agent 调用工具及工具返回结果
AssistantMessage # 最终总结回复
ResultMessage # 对话结束标志
AssistantMessage.content 是内容块列表,类型有 TextBlock(文本)和 ThinkingBlock(推理模型的思维链)。这里只打印 TextBlock,跳过其他类型。
运行 python openrouter_agent.py 后,输出大致如下:
[SystemMessage]
[AssistantMessage]
[AssistantMessage]
根据`ls`和`pyproject.toml`的分析:
**目录结构**:
- 项目使用 `uv` 管理依赖(存在 `uv.lock`)
- 包含两个主脚本:`openrouter_agent.py` 和 `openrouter_anthropic.py`
- 有 `scripts/` 目录存放环境变量脚本
**pyproject.toml 分析**:
- 项目名称:`coding-agent`,版本 `0.1.0`
- 主要依赖:`anthropic`、`claude-agent-sdk`、`python-dotenv`
[ResultMessage]
其中 [SystemMessage] 和 [AssistantMessage] 等是我们打印的消息类型标记,实际回复内容由模型生成,每次运行可能略有不同。