跳到正文
读到 0%
未登录时不会保存进度下一课:P1登录保存进度

TypeScript Agent 开发基础

掌握 Agent 开发的核心 TypeScript 模式:异步操作、流式响应、类型安全的 schema 以及支撑 Claude Agent SDK 的接口设计。

SDK 重点async/awaitfor await..ofZodstructured_output

为什么 Agent 开发要用 TypeScript?

Agent 开发需要处理异步操作、流式数据和结构化输出。TypeScript 提供了:

  • 类型安全:在编译时捕获错误,而非运行时
  • Async/Await:用简洁的语法处理 LLM 响应和工具调用
  • 流式支持:原生 for await..of 处理 Agent 消息
  • Schema 验证:Zod schema 确保结构化输出符合预期

Claude Agent SDK 本身用 TypeScript 写成,整套 API 都用到了这些特性。

模式 1:Async/Await 基础

Agent 操作本质上是异步的——LLM 调用、工具执行和文件 I/O 都需要时间。有了 TypeScript 的 async/await,异步代码也能写得像同步代码一样。

typescript
// @lesson-illustrative
// async/await 语法的通用示例——这里的 `apiCall` 与 `processResponse`
// 只是占位符函数,并非 SDK 的 `query()`。(真正的 Agent SDK 形态见下面的 Pattern 2。)

// 没有 async/await(回调地狱)
function oldStyle() {
  apiCall(prompt).then((response) => {
    processResponse(response).then((result) => {
      console.log(result);
    });
  });
}

// 使用 async/await(清晰易读)
async function modernStyle() {
  const response = await apiCall(prompt);
  const result = await processResponse(response);
  console.log(result);
}

核心规则:

  • 使用 await 的函数必须标记为 async
  • await 会暂停执行直到 Promise 完成
  • 错误处理使用标准的 try/catch 块
typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

async function runAgent() {
  try {
    const response = query({
      prompt: "What is 2+2?",
      options: { model: "sonnet", tools: [] },
    });

    // 处理流式响应
    for await (const message of response) {
      if (message.type === "result" && message.subtype === "success") {
        console.log(message.result);
      }
    }
  } catch (error) {
    console.error("Agent failed:", error);
  }
}
SDK 洞察:Query 返回 AsyncIterable

Claude Agent SDK 的 query() 函数返回的是 AsyncIterable,而非 Promise。所以可以流式接收响应,不必等整个输出完成。

模式 2:使用 for await..of 处理流式数据

Agent 的响应是逐步生成的。用 for await..of 循环,每到一条消息就处理一条:

typescript
import { query, type SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";

function getAssistantText(message: SDKAssistantMessage): string {
  return message.message.content
    .filter((block): block is Extract<typeof block, { type: "text" }> => block.type === "text")
    .map((block) => block.text)
    .join("\n");
}

const response = query({
  prompt: "List files and calculate their total size",
  options: { tools: ["Bash", "Read"], allowedTools: ["Read"] },
});

// 实时处理每条消息
for await (const message of response) {
  switch (message.type) {
    case "system":
      console.log(`Session: ${message.session_id}`);
      break;
    case "assistant":
      console.log(`Assistant: ${getAssistantText(message)}`);
      break;
    case "tool_use_summary":
      console.log(`Using tool: ${message.summary}`);
      break;
    case "result":
      if (message.subtype === "success") {
        console.log(`Final: ${message.result}`);
      }
      break;
  }
}

为什么流式很重要:

  • 用户体验:展示进度而非空白屏幕
  • 调试:实时观察 Agent 推理过程
  • 控制:可以提前取消长时间运行的操作
  • 内存:增量处理大型输出
SDK 洞察:消息类型

在入门循环里,最重要的消息类型通常是 system、assistant、tool_use_summary 和 result。SDK 为完整消息流提供了可辨识联合类型,所以按 message.type 判别依然类型安全。

模式 3:使用 Zod Schema 实现结构化输出

Agent 默认生成非结构化文本。用 Zod schema 可以约束输出,让它符合你的数据结构:

typescript
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";

// 定义预期的输出结构
const TaskSchema = z.object({
  title: z.string(),
  priority: z.enum(["low", "medium", "high"]),
  subtasks: z.array(z.string()),
  estimatedHours: z.number().positive(),
});

type Task = z.infer<typeof TaskSchema>;

const response = query({
  prompt: "Break down 'Build a chat app' into subtasks",
  options: {
    outputFormat: {
      type: "json_schema",
      schema: z.toJSONSchema(TaskSchema) as Record<string, unknown>,
    },
  },
});

for await (const message of response) {
  if (
    message.type === "result" &&
    message.subtype === "success" &&
    message.structured_output !== undefined
  ) {
    const task: Task = TaskSchema.parse(message.structured_output);
    console.log(`Task: ${task.title}`);
    console.log(`Priority: ${task.priority}`);
    console.log(`Subtasks: ${task.subtasks.length}`);
  }
}

Zod Schema 的优势:

  • 验证:如果 LLM 输出不匹配 schema,会抛出错误
  • 类型推断:z.infer<typeof Schema> 生成 TypeScript 类型
  • 文档化:一份 Schema 既是运行时验证器,也是编译时类型
Schema 细化

用 .refine() 落实真实的审批规则。先想清楚谁用这个结果、误拒的代价有多大,再定边界:

typescript
const BudgetSchema = z
  .object({
    amount: z.number(),
    currency: z.string(),
  })
  .refine((data) => data.amount > 0 && data.amount < 1000000, {
    message: "Budget must be between $0 and $1M",
  });

分别测试有效值、无效值和边界值,并判断错误信息能否帮用户改对输入。

模式 4:类型注解基础

TypeScript 类型注解能在编译时揪出类型不匹配,把 bug 挡在运行之前:

没有类型,bug 要到运行时才会暴露:

typescript
// @lesson-illustrative
// 未加类型——这段能编译,但只要调用方传入数字或 null,
// `content.substring` 就会在运行时炸掉,TypeScript 帮不上忙。
function summarize(content) {
  return content.substring(0, 100);
}

加上参数类型,错误就提前到了编译期:

typescript
// 加上类型——TypeScript 会拒绝任何传入非字符串的调用方。
function summarize(content: string): string {
  return content.substring(0, 100);
}

Agent 代码中常见的类型注解:

typescript
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

// 函数参数和返回类型
async function runAgent(prompt: string, tools: string[]): Promise<string> {
  const options: Options = { tools, allowedTools: tools };
  const response = query({ prompt, options });

  let finalContent: string = "";
  for await (const message of response) {
    if (message.type === "result" && message.subtype === "success") {
      finalContent = message.result;
    }
  }

  return finalContent;
}

// 变量类型推断(TypeScript 推断类型)
const config: Options = {
  model: "sonnet",
  maxTurns: 10,
};

// 显式类型注解(当推断需要帮助时)
const messages: SDKMessage[] = [];
类型推断 vs 显式类型

TypeScript 大多数时候能自己推断类型。以下三种情况要显式注解:函数参数(无法推断)、空数组/对象(有歧义)、复杂返回类型(写出来更清楚)。

模式 5:使用接口定义 Agent 数据

接口给 Agent 的数据结构定下契约,描述工具结果和 Agent 状态时尤其好用:

typescript
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";

// 工具结果接口
interface FileAnalysis {
  path: string;
  sizeBytes: number;
  lineCount: number;
  language: string;
}

// Agent 状态接口
interface AgentSession {
  sessionId: string;
  turnCount: number;
  toolCallsUsed: string[];
  memorySlots: Map<string, unknown>;
}

// 在 SDK 中使用接口
async function analyzeFile(filePath: string): Promise<FileAnalysis> {
  const response = query({
    prompt: `Analyze ${filePath}`,
    options: {
      tools: ["Read", "Bash"],
      allowedTools: ["Read"],
      outputFormat: {
        type: "json_schema",
        schema: z.toJSONSchema(
          z.object({
            path: z.string(),
            sizeBytes: z.number(),
            lineCount: z.number(),
            language: z.string(),
          })
        ) as Record<string, unknown>,
      },
    },
  });

  for await (const message of response) {
    if (
      message.type === "result" &&
      message.subtype === "success" &&
      message.structured_output !== undefined
    ) {
      return z
        .object({
          path: z.string(),
          sizeBytes: z.number(),
          lineCount: z.number(),
          language: z.string(),
        })
        .parse(message.structured_output);
    }
  }

  throw new Error("No result received");
}

接口 vs 类型:

  • Interface:可扩展,更适合对象形状,错误信息更清晰
  • Type:联合类型、工具类型、别名
typescript
// Interface(对象首选)
interface AgentConfig {
  model: string;
  maxTurns: number;
}

// Type(联合类型首选)
type MessageType = SDKMessage["type"];

// 扩展接口
interface AdvancedAgentConfig extends AgentConfig {
  temperature: number;
  topP: number;
}
为你的第一个 Agent 添加类型

选择一个小型团队工作流,创建带完整类型注解的 Agent:

  1. 为 Agent 配置定义一个 interface
  2. 编写一个接收类型化参数的 async function
  3. 使用 for await..of 处理响应流
  4. 为结构化输出添加 Zod schema

写明下游用户是谁,再举一种必须由 TypeScript 或 Zod 在运行前拦下的畸形输出。

综合运用

下面这个完整示例把 5 种模式都用上了:

typescript
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";

// 模式 5:配置接口
interface CodeReviewConfig {
  filePath: string;
  focusAreas: string[];
  maxIssues: number;
}

// 模式 3:输出的 Zod schema
const IssueSchema = z.object({
  severity: z.enum(["low", "medium", "high", "critical"]),
  line: z.number(),
  description: z.string(),
  suggestion: z.string(),
});

const ReviewSchema = z.object({
  summary: z.string(),
  issues: z.array(IssueSchema),
  rating: z.number().min(0).max(10),
});

type CodeReview = z.infer<typeof ReviewSchema>;

// 模式 1 & 4:带类型注解的异步函数
async function reviewCode(config: CodeReviewConfig): Promise<CodeReview> {
  const response = query({
    prompt: `Review ${config.filePath} focusing on: ${config.focusAreas.join(", ")}`,
    options: {
      model: "sonnet",
      tools: ["Read", "Grep"],
      allowedTools: ["Read", "Grep"],
      outputFormat: {
        type: "json_schema",
        schema: z.toJSONSchema(ReviewSchema) as Record<string, unknown>,
      },
    },
  });

  // 模式 2:使用 for await..of 处理流式数据
  for await (const message of response) {
    if (message.type === "tool_use_summary") {
      console.log(`Reading: ${message.summary}`);
    }
    if (
      message.type === "result" &&
      message.subtype === "success" &&
      message.structured_output !== undefined
    ) {
      return ReviewSchema.parse(message.structured_output);
    }
  }

  throw new Error("Review failed");
}

// 使用示例
const review = await reviewCode({
  filePath: "src/agent.ts",
  focusAreas: ["security", "performance"],
  maxIssues: 10,
});

console.log(`Rating: ${review.rating}/10`);
console.log(`Issues found: ${review.issues.length}`);
学习检查点

在继续学习 P1 Agent 基础之前,确认你能够:

  • 编写使用 await 的 async 函数
  • 使用 for await..of 处理 Agent 响应
  • 定义 Zod schema 并使用 z.infer<typeof Schema>
  • 为函数参数添加类型注解
  • 为结构化数据创建 interface

这些模式在每个 Claude Agent SDK 示例里都会出现——现在掌握,后面的路径会学得更快。

进阶提示

课程往后,你会碰到更高级的 TypeScript 模式,比如品牌类型、处理消息用的可辨识联合类型,以及严格的空值检查。它们都是在上面 5 个模式的基础上自然延伸出来的。

延伸阅读

有了这些 TypeScript 基础,你已准备好:

  • P1 · Agent 基础:在完整类型安全下构建第一个 Agent 循环
  • P2 · 工具使用:定义类型化的工具 schema 和结果处理器
  • P3 · 提示词:用 Zod schema 做结构化提示词工程
  • P4 · 记忆:为会话状态和记忆管理加上类型

Claude Agent SDK 默认你会 TypeScript——这 5 种模式是后面所有内容的基础。