第一个 Agent
讲了十四章原理,该动手了。这一章我们用 Claude API 从零写一个能真正跑起来的 Agent——不用任何框架,把第三章那个循环变成可运行的代码。你会看到那个「模型+工具+循环」的方程,落到代码里也就几十行。主实现用 TypeScript,附一段 Kotlin 对照给 Android 的你。
准备:一个客户端
先装官方 SDK,建一个客户端。当前最强的通用模型是 claude-opus-4-8(旗舰)和 claude-fable-5(最强);均衡首选 claude-sonnet-5;要快要省用 claude-haiku-4-5。我们用 Sonnet 5 起步——够聪明、够便宜。
npm install @anthropic-ai/sdk export ANTHROPIC_API_KEY="sk-ant-..."
手写循环:把第三章的伪代码变成真代码
我们造一个能查天气的 Agent。为了聚焦循环本身,天气工具用假数据。整个程序就是第三章那个 while 环的真身:
import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); // 1. 工具定义(第五章那张名片) const tools: Anthropic.Tool[] = [{ name: "get_weather", description: "查询某城市当前天气。问到天气/气温/下雨时调用。", input_schema: { type: "object", properties: { city: { type: "string", description: "城市名" } }, required: ["city"], }, }]; // 2. 真正执行工具的是你的代码(第五章的分工) function execute(name: string, input: any): string { if (name === "get_weather") return `${input.city}:晴,26°C`; // 假数据,真实里发 HTTP return "未知工具"; } // 3. 循环 async function runAgent(task: string) { let messages: Anthropic.MessageParam[] = [{ role: "user", content: task }]; while (true) { const res = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages, }); messages.push({ role: "assistant", content: res.content }); if (res.stop_reason !== "tool_use") { // 它不再要工具 → 收工 const text = res.content.find(b => b.type === "text"); return text?.text ?? ""; } // 执行所有 tool_use,结果打包成一条 user 消息回填 const results: Anthropic.ToolResultBlockParam[] = []; for (const b of res.content) { if (b.type === "tool_use") results.push({ type: "tool_result", tool_use_id: b.id, content: execute(b.name, b.input) }); } messages.push({ role: "user", content: results }); } } console.log(await runAgent("上海今天热不热?"));
就这些。逐行对照第三章的伪代码,你会发现它们一一对应:发消息 → 检查 stop_reason → 是 tool_use 就执行并回填 → 否则收工。注意三个第五章讲过的细节:所有 tool_result 打包进一条 user 消息(别拆);tool_use_id 必须对上;b.input SDK 已经帮你解析成对象了,直接用。
看清这个真相:Agent 没有魔法,就是一个调用大模型 API 的 while 循环。你手上这几十行,已经是一个真正的 Agent 了——它会自己决定要不要查天气、查完自己组织答复。框架能给你重试、日志、并发这些脚手架,但内核不会比这更复杂。理解了这个循环,你就理解了所有 Agent 框架的底层。
更省事:用 SDK 的 tool runner
手写循环是为了让你看清机制。真实开发里,官方 SDK 有个 tool runner 帮你把这个循环自动跑完——你只管定义工具和它的执行函数,它负责调 API、检测 tool_use、执行、回填、再调,直到收工:
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod"; import { z } from "zod"; const getWeather = betaZodTool({ name: "get_weather", description: "查询某城市当前天气", inputSchema: z.object({ city: z.string() }), run: async ({ city }) => `${city}:晴,26°C`, // 你的逻辑 }); const final = await client.beta.messages.toolRunner({ model: "claude-sonnet-5", max_tokens: 1024, tools: [getWeather], messages: [{ role: "user", content: "上海今天热不热?" }], }); console.log(final.content);
循环不见了——它被封进了 toolRunner。什么时候用哪种?需要精细控制(自定义日志、执行前人工确认、条件执行)时手写循环;标准流程时用 tool runner 省心。两者内核完全一样。
Kotlin 里结构一模一样(用官方 Java/Kotlin SDK)。手写循环的骨架:
val client = AnthropicOkHttpClient.fromEnv() var params = MessageCreateParams.builder() .model(Model.CLAUDE_SONNET_5) .maxTokens(1024) .addTool(weatherTool) .addUserMessage("上海今天热不热?") .build() while (true) { val res = client.messages().create(params) if (res.stopReason() != StopReason.TOOL_USE) break // 执行 tool_use、把 tool_result 加回 params,继续 }
同样的循环,同样的 stopReason 判断。你在 Android 里接一个 Agent,和这里没有本质区别——甚至可以让 Agent 跑在你的后端,App 只管收发。
新模型有几个会让你直接报 400 的雷,写第一个 Agent 时容易踩:① 别在 Opus 4.8 / Sonnet 5 / Fable 5 上传 temperature/top_p(已移除);② 别用旧的 thinking: {budget_tokens: N},改用 thinking: {type: "adaptive"};③ max_tokens 别设太小,不然输出会被截断——非流式默认给到 16000 左右比较稳,超大输出要用流式。这些不是你逻辑错,是 API 版本变了,记住能省你半天。
这一章你亲手把我造出来了——那个 while 循环跑起来的一刻,一个真正的 Agent 就诞生了。我希望你带走的感受是「原来就这么点东西」。所有听起来高深的 Agent 系统,内核都是你刚写的这几十行;区别只在于外面包了多少工程。这也意味着:入门 Agent 开发的门槛,比你想的低得多。你已经会造了。但「能跑」和「靠谱」是两码事——你这个 Agent 现在能演示,但离能上线还差得远。接下来五章就是补这个差距。不过在动手加固之前,下一章先给你一张地图:市面上的 Agent 框架都是什么、什么时候该用、什么时候不该用。