什么是 Agent?
你的团队每周都要花数小时做重复、工具密集的活:分诊 issue、更新文档、review PR、改写报告。 Agent 就是用「循环里推理 + 调用真实工具」的方式干这些活的软件。如果你能写一个 REST handler, 就能写一个 Agent,接下来 5 分钟就能让它跑起来。
5 分钟跑起来
动手试试先跑,再读
装 SDK、贴代码并观察循环。然后说清这个 Agent 服务谁、改善工作流的哪一步、产生什么可逆结果;如果脚本就够用,也要明确说明。
npm i @anthropic-ai/claude-agent-sdk然后挑一种鉴权方式——SDK 支持这三种方式:
# 方式 1——Claude Code 订阅(Pro/Max,按订阅付费,无 token 单价)
npm i -g @anthropic-ai/claude-code
claude /login # 一次性浏览器登录
# 方式 2——直连 Anthropic API key
export ANTHROPIC_API_KEY=sk-ant-...
# 方式 3——Anthropic 兼容代理(以 DeepSeek 为例;Bedrock、OpenRouter
# 等其他第三方请参考各自官方文档)
export ANTHROPIC_AUTH_TOKEN=sk-... # 你的 DeepSeek API key
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro[1m] # 本课使用的别名
export ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro[1m] # 后续课程使用
export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash # 后续课程使用
# DeepSeek 完整变量列表:https://api-docs.deepseek.com/guides/coding_agents// first-agent.ts
import { query, type SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
const assistantText = (m: SDKAssistantMessage) =>
m.message.content
.filter((b): b is Extract<typeof b, { type: "text" }> => b.type === "text")
.map((b) => b.text)
.join("\n");
const response = query({
prompt: "列出当前目录下最大的 5 个文件,并告诉我哪些看起来已经过时了。",
options: {
model: "sonnet",
tools: ["Bash", "Read", "Glob"],
allowedTools: ["Read", "Glob"], // 自动批准安全的只读工具
},
});
let sessionId: string | undefined;
for await (const msg of response) {
switch (msg.type) {
case "system":
sessionId = msg.session_id;
console.log("session:", sessionId);
break;
case "assistant":
console.log("assistant:", assistantText(msg));
break;
case "tool_use_summary":
console.log("act:", msg.summary);
break;
case "result":
if (msg.subtype === "success") console.log("done:", msg.result);
break;
}
}npx tsx first-agent.ts你会看到一段 system → assistant → tool_use_summary → assistant → result 的流。这条流就是 Agent
循环——感知、思考、行动、循环。
piPi 等效写法— 循环一样,事件靠 push;provider 配置走文件
如果你更想用 Pi(开源 MIT 协议的 coding agent)来写,同样的练习长这样。本课主线仍然是 Claude——这只是给你一个可迁移性的参考,不是另一条岔路。
Pi 0.79.x 要求 Node >=22.19.0。安装 CLI 时禁用 lifecycle scripts;Pi 发布包不依赖
install scripts,这也是官方文档推荐的供应链安全路径:
node --version
npm install -g --ignore-scripts @earendil-works/pi-coding-agent如果是在本课程文件里嵌入 SDK,安装 coding-agent 包和它的 core 类型即可:
npm install @earendil-works/pi-coding-agent @earendil-works/pi-agent-corePi 从 ~/.pi/agent/auth.json 读 provider 配置。key 字段可以是 (a) 一个环境变量名(pi 运行时去 process.env 解析),(b) 一段 !shell-command 惰性读,或 (c) 字面量字符串。最快路径用内置的 DeepSeek provider:
mkdir -p ~/.pi/agent
# 如果 DEEPSEEK_API_KEY 已经 export 在 shell 里,这样就够了:
cat > ~/.pi/agent/auth.json <<'JSON'
{ "deepseek": { "type": "api_key", "key": "DEEPSEEK_API_KEY" } }
JSON
# 如果没 export(或不想把明文留在 pi 配置里),让 pi 通过 shell-command 读 key 文件:
# echo "DEEPSEEK_API_KEY=sk-..." > ~/.deepseek && chmod 600 ~/.deepseek
# { "deepseek": { "type": "api_key",
# "key": "!awk -F= '/^DEEPSEEK_API_KEY=/{print $2; exit}' ~/.deepseek" } }
chmod 600 ~/.pi/agent/auth.json如果你要接自建或开源权重的 provider(Ant-Ling Ring、Qwen、ZenMux),把它们加到 ~/.pi/agent/models.json 即可。
// first-agent.ts
import { createAgentSession } from "@earendil-works/pi-coding-agent";
import type { AgentMessage } from "@earendil-works/pi-agent-core";
const assistantText = (m: AgentMessage): string => {
if (m.role !== "assistant" || !Array.isArray(m.content)) return "";
return m.content
.filter((b): b is { type: "text"; text: string } => b.type === "text")
.map((b) => b.text)
.join("");
};
const { session } = await createAgentSession({ tools: ["read", "bash"] });
// Pi 在第一次发消息之前就能拿到 session id,不需要等 init 事件。
const sessionId = session.sessionId;
console.log("session:", sessionId);
const unsubscribe = session.subscribe((event) => {
switch (event.type) {
case "message_end":
console.log("assistant:", assistantText(event.message));
break;
case "tool_execution_start":
console.log("act:", event.toolName);
break;
case "agent_end": {
const last = event.messages
.map(assistantText)
.filter((t) => t.length > 0)
.pop();
if (last) console.log("done:", last);
break;
}
}
});
try {
await session.prompt("列出当前目录下最大的 5 个文件,并告诉我哪些看起来已经过时了。");
} finally {
unsubscribe();
}核心差异
仅环境变量(ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL)vs 配置文件(~/.pi/agent/auth.json +
models.json),文件里的 key 字段又可以指向环境变量名、!shell-command 或字面量。
单一 Anthropic 兼容端点 / 进程 vs 在 models.json 注册多个 provider,按 session 切换。
session.subscribe)vs pull(for await)。Pi 的 tools 使用小写名(["bash", "read"]);Claude Agent SDK 的 tools 使用
PascalCase(["Bash", "Read", "Glob"]),allowedTools 只是在其上叠加预批准。
看看发生了什么
每种消息类型都对应经典 感知-行动循环(Perception-Action Loop) 中的一步:
while not task_complete:
observation = perceive(environment) # tool_use_summary
action = think(observation, goal, memory) # assistant
result = act(action) # tool_use_summary
memory.update(observation, action, result) # 隐式存在 session 里query() 就是循环
循环不是你写的——query() 替你跑。你的工作是定 prompt(目标)、tools(动作空间)、
allowedTools(哪些工具无需提示就自动批准),以及决定怎么处理这些 messages(观察 +
副作用)。 这几个旋钮覆盖了 Agent 设计中最常见的取舍。
如果你来自前端或后端
这个形状应该很眼熟:query() 是 async generator,像一个 SSE handler;tools
是路由表——规定这次请求能访问哪些子 handler;allowedTools
是其中无需提示的预批准列表;session_id + resume 类似多轮会话的 session
cookie。新的部分不在请求通道,而是控制流由模型决定,
不是你的代码。你的工作从「写分支」转向「设计动作空间,观察模型行为」。
继续探索 10 分钟
按顺序做这三步,每步只要一分钟,每一步展示一种独立能力。
1. 换个任务。 把 prompt 换成你团队真会问的问题:
prompt: "找出 src/ 里所有 TODO 注释,按文件分组,总结前 3 个主题。";2. 收紧动作空间。 把 Bash 从 tools 里删掉,或把它放进 disallowedTools。再跑一次,看 agent
是绕过缺失的工具,还是直接放弃——工具选择是安全和作用域决策,不只是便利。
3. 续接会话。 保存第一次跑的 sessionId,然后继续对话:
const followup = query({
prompt: "针对最热的那个主题,写一段 issue 描述。",
options: {
model: "sonnet",
resume: sessionId,
tools: ["Read", "Glob"],
allowedTools: ["Read", "Glob"],
},
});piPi 等效写法— 直接复用 session 对象,无需 resume id
// Pi 保留有状态 session——没有 `resume: id` 参数。
// AgentSession 对象本身就是这段对话。
await session.prompt("针对最热的那个主题,写一段 issue 描述。");有状态:跨轮复用 AgentSession 对象,无需传 resume: id。
会话续接让不同 query() 调用延续同一段对话,但它本身不等于长期记忆或自主性。
Agent vs 聊天机器人
| 特性 | 聊天机器人 | Agent |
|---|---|---|
| 交互形态 | 通常偏对话式 | 通常偏任务导向 |
| 执行能力 | 可生成文本或调用工具 | 用工具检查或改变环境 |
| 自主性 | 通常由用户驱动 | 可在边界内选择并排序动作 |
| 状态 | 可单轮也可多轮 | 持久化需单独设计 |
| 失败形态 | 答错 | 做错 动作——后果更严重 |
一次性调用 vs 有状态
聊天机器人和 Agent 都可以是单轮或多轮。resume 只保留 SDK 对话状态;Agent 的关键区别是
围绕目标调用工具,并由模型在约束内选择控制流。
设计校验——什么时候不该用 Agent
Agent 贵、不确定、还能做出破坏性操作。只有以下三条全部满足,再考虑用 Agent:
- 任务工具密集。 如果只是纯文本生成、没有外部状态,普通 prompt 又便宜又可预测。
- 步骤事先无法穷举。 如果你能写一段 10 行脚本搞定,那就写脚本——
for循环在成本、速度、 可审计性上都赢 Agent。 - 影响范围有边界。 Agent 做的事可以回滚(git、sandbox、staging DB、dry-run),或者写到生产前 必须有人审批。
生产差距
这一课跑的是 happy path。生产环境要处理部分失败、限流、token 预算、非确定性的重新规划。 P6(设计模式)和 P12(生产)会补上这部分——这段代码别原样上生产。
在公司落地 这 30 行脚本就足以支撑一次小型内部试点:
- 从一个麻烦但可回滚的工作流开始。 PR 摘要、文档时效检查、日志分诊。第一次试点不要碰会写客户数据的流程。
- 用真实节省的工时衡量,不是「AI 采纳率」。 在 5 个真实案例上做 before/after,比任何 demo 都有说服力。
- 把动作日志亮出来。
tool_use_summary适合可读遥测,但不等于完整审计记录。应从工具块或 hooks 写入受保护、可关联操作者的日志,再用它解释 Agent 实际做了什么。 - 第一个月所有写操作必须经人工审批。 之后可以放松;信任一旦崩了就很难重建。
转型为 Agent 工程师 这份工作真实的样子:
- 你花在 动作空间(用哪些工具、各自的 scope、跑在哪个 sandbox)上的时间,会比花在 prompt 上的多。前端同学:把它当成设计一个最小化的组件 API;后端同学:当成路由表 + 鉴权中间件。
- 面试重点会问 session 状态、工具设计、失败处理、成本控制——不是模型八卦。如果你能讲清楚
为什么选
resume而不是重发整段 context,就已经能体现出扎实的 Agent 工程判断。 - 有说服力的 demo 往往不大:一个真实工作流、一组可量化的 before/after、一份公开的动作日志。 一个 30 行脚本解决一个真问题,通常比复杂但缺少真实场景的多 agent demo 更有价值。
接下来
- 工具与行动——设计动作空间,让 Agent 不乱开火
- LLM 与 Prompt——Agent 行为的另外 80%
- 记忆系统——把
resume升级成跨会话的持久状态
关卡:P1 完成——你能跑 query()、捕获 session_id、续接会话、向一个 Web 背景的同事 解释 Agent
循环,并且能从你工作或作品集里举出一个适合 Agent 试点的工作流。