← 返回教程库

用 Claude Agent SDK 写出你的第一个能跑的 Agent

最后更新 2026-06-25
你将学到
  • 理清"写代码型 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"),而是一个能自己决定调不调工具的循环。


扩展:SkillsMCP 放在哪里

你现在的 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 数字员工落地指南,或了解 数字员工搭建实战课。需要为企业落地方案,欢迎找我们聊 企业服务

📄 来源 / 自校链接

本文为学习整理,关键步骤与代码请结合下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为学习与落地整理,AI 工具与平台更新较快,关键步骤请结合官方最新资料验证。见免责声明