用 Claude Agent SDK 写出你的第一个能跑的 Agent
- 理清"写代码型 Agent"和 Coze/Dify 拖拽的本质区别,知道各自适合什么场景
- 完成环境准备:装 Claude Agent SDK、配好 API key
- 复制并跑通一个最小可运行 Agent,亲眼看到它通过工具执行任务
- 给 Agent 加一个自定义工具(tool use),让它真的"会干活"
很多人卡在这一步:道理都懂,知道 Agent 会自己拆步骤、调工具、看结果再决定下一步,但就是迈不出"第一个能跑的 Agent"这道坎。网上的示例要么是 Coze 截图、要么是 LangGraph 那种装一堆依赖的重框架——其实用 Claude Agent SDK 裸写,第一个能跑的 Agent 就几十行代码,30 分钟内能跑通。
这一节我们不绕,直接带你写:装 SDK → 配 key → 跑最小 Agent → 加工具 → 拆开看懂四要素。中间每一步都给你"你应该看到什么",不让你在黑盒里瞎猜。
这篇适合谁:知道 AI Agent 是什么(1.1 节已讲),有基本 Python 基础,想第一次亲手用 SDK 写一个真能跑的 Agent,而不是拖拽搭积木。
写代码型 Agent vs 低代码拖拽:本质区别在哪
Coze、Dify 这类低代码平台能很快搭出 Agent,适合验证 idea、给不写代码的人用。但它们本质是帮你填参数,流程骨架是平台定的——你能组合平台提供的节点,但出了节点范围你就没法儿。
写代码型 Agent 不一样:
| 维度 | 低代码平台(Coze/Dify) | 写代码(Agent SDK) |
|---|---|---|
| 上手速度 | 快,几分钟拖出来 | 慢一点,要写代码 |
| 扩展性 | 受平台节点限制 | 想加什么工具就加什么 |
| 调试可见度 | 黑盒,出问题靠猜 | 代码透明,print 加哪都行 |
| 集成到自己系统 | 需要平台提供 API 或 webhook | 直接嵌进你的代码库 |
| 成本结构 | 有平台抽成,高流量贵 | 只付模型费,可控 |
| 学到什么 | 学了平台,换平台重学 | 学了编排本质,换框架触类旁通 |
结论很简单:验证想法、一次性任务、不写代码的同事用 → 低代码平台;长期维护、要集成、要真正理解 Agent → 写代码。 你正在读这节,说明你选择了后者,那就一步步来。
想深入了解为什么选写代码这条路,可以看 为什么专项写代码更可控,不锁定 这节的分析。
环境准备:装 SDK、配 key
第一步:装 SDK
# Python(推荐,本节示例全用 Python)
pip install anthropic
# 验证装好了
python -c "import anthropic; print(anthropic.__version__)"
装好后会打印版本号(具体版本以官方文档为准,截稿 2026-06)。
用 Node.js 也可以:
npm install @anthropic-ai/sdk,但本节示例用 Python,两套 API 结构基本一致。
第二步:配 API key
到 Anthropic Console 申请 API key,然后配进环境变量:
# macOS / Linux
export ANTHROPIC_API_KEY="sk-ant-..."
# Windows PowerShell
$env:ANTHROPIC_API_KEY = "sk-ant-..."
不想每次都 export,可以写进 .env 文件再用 python-dotenv 加载,或者直接写进 shell 配置文件(~/.bashrc / ~/.zshrc)。
关于模型名:调用时要传 model 参数,具体写哪个模型名,以 Anthropic 官方文档模型列表为准(截稿 2026-06)——官方随时更新,不要硬编码一个旧名字。
第一个最小可跑 Agent
下面这段代码是能复制粘贴直接跑的最小可跑 Agent,不做任何省略。它能接收一个问题,调用一个简单工具,返回结果。
import anthropic
import json
# ────────────────────────────────────────────────
# 1. 定义 Agent 能用的工具
# ────────────────────────────────────────────────
tools = [
{
"name": "get_current_time",
"description": "获取当前日期和时间,用来回答'现在几点'或'今天几号'一类的问题",
"input_schema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "时区,例如 'Asia/Shanghai',默认用本地时间"
}
},
"required": []
}
}
]
# ────────────────────────────────────────────────
# 2. 实现工具逻辑(真正执行的函数)
# ────────────────────────────────────────────────
def get_current_time(timezone: str = "Asia/Shanghai") -> str:
from datetime import datetime
import pytz
try:
tz = pytz.timezone(timezone)
now = datetime.now(tz)
return now.strftime(f"%Y-%m-%d %H:%M:%S ({timezone})")
except Exception:
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S (本地时间)")
TOOL_FUNCTIONS = {
"get_current_time": get_current_time,
}
# ────────────────────────────────────────────────
# 3. Agent 主循环:想 → 做 → 看,直到完成
# ────────────────────────────────────────────────
def run_agent(user_message: str) -> str:
client = anthropic.Anthropic() # 自动读取 ANTHROPIC_API_KEY 环境变量
messages = [{"role": "user", "content": user_message}]
print(f"\n用户:{user_message}")
while True:
# 让模型决定下一步(想)
response = client.messages.create(
model="claude-opus-4-5", # 以官方文档为准(截稿 2026-06)
max_tokens=1024,
tools=tools,
messages=messages,
)
print(f"模型 stop_reason: {response.stop_reason}")
# 任务完成:模型直接给出文字答案
if response.stop_reason == "end_turn":
final_text = "".join(
block.text for block in response.content
if hasattr(block, "text")
)
print(f"\nAgent 回答:{final_text}")
return final_text
# 模型要调工具(做)
if response.stop_reason == "tool_use":
# 把模型的决策加进对话历史
messages.append({"role": "assistant", "content": response.content})
# 执行每个工具调用
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
print(f" → 调用工具: {tool_name},参数: {tool_input}")
# 执行工具(看)
if tool_name in TOOL_FUNCTIONS:
result = TOOL_FUNCTIONS[tool_name](**tool_input)
else:
result = f"工具 '{tool_name}' 未找到"
print(f" ← 工具返回: {result}")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
})
# 把工具结果交回给模型,进入下一轮"想"
messages.append({"role": "user", "content": tool_results})
else:
# 意外的 stop_reason,退出
print(f"未预期的 stop_reason: {response.stop_reason}")
break
return "Agent 未能完成任务"
# ────────────────────────────────────────────────
# 4. 跑起来
# ────────────────────────────────────────────────
if __name__ == "__main__":
result = run_agent("现在上海是几点?今天是几号?")
print(f"\n最终结果:{result}")
装 pytz: pip install pytz(如果不想装,把 get_current_time 里的 pytz 部分改成 datetime.now() 也能跑,精度稍差)。
你应该看到什么
跑通后终端输出类似这样:
用户:现在上海是几点?今天是几号?
模型 stop_reason: tool_use
→ 调用工具: get_current_time,参数: {'timezone': 'Asia/Shanghai'}
← 工具返回: 2026-06-25 14:32:11 (Asia/Shanghai)
模型 stop_reason: end_turn
Agent 回答:现在上海时间是 2026 年 6 月 25 日 14:32,下午两点半多。
最终结果:现在上海时间是 2026 年 6 月 25 日 14:32,下午两点半多。
看到 tool_use → end_turn 这个顺序,说明 Agent 自己决定要调工具、调完再回答,而不是直接猜一个时间。这就是它和 Chatbot 的本质区别。
加一个"真会干活"的工具
上面那个 get_current_time 是最轻的例子,验证循环通了。现在给它加一个更有用的工具:查文件夹里有哪些文件,让 Agent 真的能帮你干活。
在 tools 列表里追加:
{
"name": "list_directory",
"description": "列出指定目录下的文件和子文件夹,用来了解目录结构",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "要列出的目录路径,例如 '/home/user/projects'"
}
},
"required": ["path"]
}
}
在 TOOL_FUNCTIONS 里加对应实现:
def list_directory(path: str) -> str:
import os
try:
entries = os.listdir(path)
if not entries:
return f"'{path}' 是空目录"
files = [e for e in entries if os.path.isfile(os.path.join(path, e))]
dirs = [e for e in entries if os.path.isdir(os.path.join(path, e))]
result = f"目录 '{path}' 包含:\n"
if dirs:
result += " 文件夹:" + ", ".join(dirs) + "\n"
if files:
result += " 文件:" + ", ".join(files)
return result
except PermissionError:
return f"没有权限访问 '{path}'"
except FileNotFoundError:
return f"路径 '{path}' 不存在"
TOOL_FUNCTIONS["list_directory"] = list_directory
然后试一句:run_agent("帮我看看 /tmp 目录下有什么文件")。Agent 会自己决定调 list_directory,拿到结果后用自然语言告诉你里面有什么。
口诀:工具 = 名字 + 描述 + 参数 schema + 执行函数。 模型只看前三个来决定要不要调、传什么参数;执行函数是你的代码,模型从不知道里面怎么实现的。
拆解:四要素在这段代码里在哪
读完 AI Agent 是什么 你知道 Agent 有四要素:模型、工具、记忆、评估。现在对照这段代码一一找到它们:
| 要素 | 在代码里是哪里 | 具体体现 |
|---|---|---|
| 模型(大脑) | client.messages.create(model=...) |
这一行每次把历史和工具列表一起发给大语言模型,拿回"下一步做什么" |
| 工具(手脚) | tools 列表 + TOOL_FUNCTIONS 字典 |
前者告诉模型"你能用什么",后者是真正执行的代码 |
| 记忆(笔记本) | messages 列表 |
每一轮都把之前的对话、工具调用、工具结果追加进去,下一轮模型能看到全部历史 |
| 评估(复盘) | stop_reason == "end_turn" 那条判断 |
模型自己判断"任务办完了"就给出 end_turn;如果还要继续就给 tool_use,代码据此决定继续循环还是收工 |
四要素全齐。这段几十行的代码,就是一个完整的 Agent,不是简化版,是真实可用的结构——生产里的差别只是工具更复杂、错误处理更严、加上持久化存储而已。
这就是 AI Agent 的 Hello World:不是 print("Hello"),而是一个能自己决定调不调工具的循环。
扩展:Skills 和 MCP 放在哪里
你现在的 Agent 工具全写死在代码里。随着工具越来越多,你会想用更好的扩展方式:
- Skills:把"怎么做某类活"写成
SKILL.md文件,Agent 按需加载,不占死上下文。适合"让它学会一套操作方法"。 - MCP(Model Context Protocol):给 Agent 接上外部系统(数据库、文件系统、API),通过标准化协议暴露工具。适合"让它连上某个系统"。
两者不是对立的,是分层组合:MCP 负责"连得上",Skills 负责"会做事"。本节先把裸 SDK 跑通,后续看 Skills vs MCP:两种扩展方式 深入了解两条路的差别。
新手常见坑:故障排查表
| 症状 | 原因 | 解法 |
|---|---|---|
anthropic.AuthenticationError / Invalid API key |
ANTHROPIC_API_KEY 没配或写错 | 确认 echo $ANTHROPIC_API_KEY 能打印出来,不是空;重新 export 或写进 .env |
pip install anthropic 报网络错误 |
国内 PyPI 访问慢 | 换镜像:pip install anthropic -i https://pypi.tuna.tsinghua.edu.cn/simple |
anthropic.NotFoundError: model not found |
模型名写错或该模型未开放 | 到 官方文档模型列表 复制当前可用模型名(截稿 2026-06),别猜 |
Agent 始终返回 stop_reason: end_turn,没调工具 |
工具描述不够清晰,模型判断不需要调 | 优化 description,让它更精确描述"什么情况该用这个工具";或在 user message 里明确提示"请使用工具查询" |
| 工具返回了结果但模型忽略,答案仍然是猜的 | tool_result 格式拼错,模型收到空内容 |
检查 tool_results 列表里每条的 tool_use_id 是否和 block.id 对应上;content 字段确认不是空字符串 |
RecursionError 或循环不停 |
stop_reason 一直是 tool_use,工具一直返回失败 |
加 max_rounds 计数器限制循环次数;同时在工具函数里捕获异常、返回描述性错误而不是抛异常 |
增量判断:描述写好,比提示词魔法更管用
一个新手最容易踩的坑不是代码写错,而是工具的 description 写得太模糊。
比如:
- 烂的:
"description": "获取时间"→ 模型不知道什么时候该用 - 好的:
"description": "获取当前日期和时间,用来回答'现在几点'或'今天几号'一类的问题"→ 模型知道命中场景
口诀:description 写给模型看,不是给人看。模型会根据 description 决定该不该调这个工具。 一个好 description 胜过十条 system prompt 魔法。这一点 Agent 的 Hello World 实战 里会进一步展开。
常见问题
Q:我现在每次跑都是新对话,Agent 不记得上次聊了什么,怎么加记忆?
这节的 messages 列表只在一次运行里保留,跑完就丢了。要跨次保留,需要把 messages 序列化存起来(文件、数据库均可),下次运行时加载回来。Agent 有多种记忆策略,会在后续阶梯里专门讲——你先把单次对话跑通,这已经是 90% 的情况。
Q:我可以接国产模型(DeepSeek、Qwen 等)而不是 Anthropic 吗?
可以,但那就不是 Claude Agent SDK 了——需要改成对应 SDK 或 OpenAI 兼容接口。结构是一样的(tools + message history + loop),只是客户端初始化和参数名略有差异。本节讲 Claude Agent SDK,换模型的方法留给你自己探索,原理是通的。
Q:写代码 Agent 和 子代理(subagent) 是什么关系?
你现在写的是单个 Agent,它自己调工具、自己完成任务。子代理 是更进一步的模式:一个主 Agent 把部分任务委派给另一个专门的子 Agent,子 Agent 在自己的独立会话里干完、只把结论交回来——这是上下文隔离的核心手段,后续 L3 以上阶梯会深入讲。
Q:完整代码里 model 参数我该写什么?
示例里写的是截稿时可用的模型名,以 Anthropic 官方文档模型列表 为准(截稿 2026-06)。官方会更新,建议定期核对,别把旧的模型名硬编码进生产代码。
小结
- 写代码型 Agent 和低代码平台的本质区别:前者你控制骨架,后者平台控制骨架。
- Claude Agent SDK 最小可跑 Agent 就几十行:
tools列表 +TOOL_FUNCTIONS字典 + 一个 while 循环。 - 四要素在代码里全找得到:
client.messages.create是模型、tools是手脚、messages是记忆、stop_reason判断是评估。 - 工具
description写好比提示词魔法更管用——模型靠 description 决定要不要调这个工具。
跑通了第一个 Agent,接下来去看 Skills vs MCP:两种扩展方式,了解怎么给它装更多能力;或者直接翻 AI Agent 智能体阶梯 里的后续节,从"加记忆"到"多 Agent 编排"一步一步来。
👉 看看 AI 数字员工落地指南,或了解 数字员工搭建实战课。需要为企业落地方案,欢迎找我们聊 企业服务。