一、什么是 AI Agent?
在进入 Agents SDK 之前,我们先回答一个最基础的问题。
普通的 AI 对话 vs AI Agent
你平时用 ChatGPT 或 Claude 聊天,是这样的:
你 → AI → 回答文本AI 只能动嘴,不能动手。
而 AI Agent 是这样的:
你 → AI → 思考 → 调用工具(查数据库、发邮件、读写文件...) → 返回结果Agent 能感知环境、使用工具、自主决策。 它不再是一个只会”说”的聊天机器人,而是一个能”做事情”的智能助手。
一个类比
| 角色 | 类比 | 能力 |
|---|---|---|
| 普通 AI 对话 | 电话客服 | 只能口头解答问题 |
| AI Agent | 上门维修工 | 能带着工具箱到你家解决问题 |
二、什么是 OpenAI Agents SDK?
OpenAI Agents SDK 是 OpenAI 官方推出的轻量级框架,帮你快速构建 Agent 应用。
为什么需要它?
没有框架时,你需要自己处理:
- 怎么让 AI 调用工具?
- 工具调用后怎么把结果喂回给 AI?
- AI 说”我要调用工具 A”时,怎么解析这个意图?
- 多个 Agent 之间怎么协作?
Agents SDK 把这些都封装好了,你只需要关注业务逻辑。
它支持什么语言?
- TypeScript(
@openai/agents) - Python(
openai-agents)
本文以 TypeScript 为例。
安装
npm install @openai/agents设置 API Key:
export OPENAI_API_KEY="sk-your-key-here"NOTE你需要一个 OpenAI API Key。可以在 platform.openai.com 获取。
三、核心概念速览
先对全局有个印象,后面会逐一展开。
┌─────────────────────────────────────────────┐│ 你的应用 ││ ││ ┌─────────┐ ┌─────────┐ ││ │ Agent A │────▶│ Agent B │ Handoff ││ └────┬────┘ └─────────┘ 交接控制权 ││ │ ││ │ 调用工具 ││ ▼ ││ ┌──────────┐ ┌──────────┐ ┌────────┐ ││ │ 搜索工具 │ │ 数据库 │ │ MCP │ ││ └──────────┘ └──────────┘ └────────┘ ││ ││ Runner ─── 执行引擎,运行上述循环 ││ Guardrails ─── 安全守卫,检查输入输出 ││ Context ─── 共享数据,在工具间传递 │└─────────────────────────────────────────────┘| 概念 | 一句话解释 |
|---|---|
| Agent | 一个配置好的 AI,有名字、指令、工具 |
| Runner | 执行引擎,运行 Agent 直到得到最终结果 |
| Tools | Agent 能使用的工具(搜索、数据库、自定义函数等) |
| Handoffs | Agent 之间的交接,把对话控制权交给另一个 Agent |
| Guardrails | 安全检查,在输入/输出阶段拦截不当内容 |
| Context | 共享数据,让所有工具和守卫都能访问 |
四、Agent —— 你的第一个智能体
最简示例
import { Agent, Runner } from '@openai/agents';
// 创建一个 Agentconst agent = new Agent({ name: '小助手', instructions: '你是一个友好的助手,用中文回答问题。',});
// 运行它const runner = new Runner();const result = await runner.run(agent, '什么是机器学习?');
console.log(result.finalOutput);这就是一个完整的 Agent 程序。三步走:
- 创建 Agent —— 告诉它叫什么、怎么行事
- 创建 Runner —— 准备执行引擎
- 运行 —— 传入用户输入,获取结果
Agent 的配置项
const agent = new Agent({ // 必填 name: '助手名称', // 用于日志和追踪
// 推荐填写 instructions: '系统提示词', // 告诉 Agent 怎么行事
// 可选 model: 'gpt-4.1', // 使用的模型,默认 gpt-4.1 tools: [], // 可用的工具列表 handoffs: [], // 可交接的 Agent 列表 outputType: undefined, // 结构化输出 schema inputGuardrails: [], // 输入守卫 outputGuardrails: [], // 输出守卫});动态指令
有时候提示词需要根据上下文变化。instructions 可以是一个函数:
const agent = new Agent({ name: '客服助手', instructions: (context) => { const user = context.getContext('userInfo'); return `你是客服助手。当前用户:${user.name},会员等级:${user.level}。请根据用户等级提供对应的服务。`; },});五、Runner —— 让 Agent 跑起来
Runner 做了什么?
Runner 的工作是一个循环:
第 1 轮:把用户输入发给 AI ↓ AI 回复说:"我需要调用搜索工具" ↓ Runner 执行搜索工具,把结果喂回给 AI ↓第 2 轮:AI 基于搜索结果继续回复 ↓ AI 说:"根据搜索结果,答案是..." ↓ 没有更多工具调用 → 结束,返回最终结果三种运行方式
const runner = new Runner();
// 1. 同步运行 —— 等待完整结果const result = await runner.run(agent, '你好');
// 2. 流式运行 —— 实时获取输出(适合打字机效果)const streamResult = runner.runStreamed(agent, '写一首诗');for await (const event of streamResult) { if (event.type === 'text_delta') { process.stdout.write(event.data); }}
// 3. 单轮运行 —— 只执行一步,不自动循环const turnResult = await runner.runTurn(agent, '你好');运行参数
const result = await runner.run(agent, userInput, { context: { userId: '123' }, // 传递给工具的共享数据 maxTurns: 10, // 最多循环 10 轮(防止死循环)});TIP设置
maxTurns是个好习惯。如果 Agent 陷入了工具调用的死循环,maxTurns会强制终止。
六、Tools —— 给 Agent 装备工具
没有工具的 Agent 只能”纸上谈兵”。加上工具,它就能真正干活了。
SDK 提供 四大类工具:
1. 托管工具(OpenAI 提供的)
最简单的工具,一行代码启用:
import { webSearch } from '@openai/agents/tools';
const agent = new Agent({ name: '搜索助手', tools: [webSearch()], // 启用网页搜索});可用的托管工具:
| 工具 | 用途 |
|---|---|
webSearch() | 搜索互联网 |
fileSearch(['vector_store_id']) | 在上传的文件中搜索(RAG) |
codeInterpreter() | 执行 Python 代码 |
imageGeneration() | 生成图片 |
2. 自定义函数工具(最常用)
这是你自己写的函数,Agent 可以调用它:
import { tool } from '@openai/agents';import { z } from 'zod';
// 定义一个天气查询工具const weatherTool = tool({ name: 'get_weather', description: '获取指定城市的当前天气', parameters: z.object({ city: z.string().describe('城市名称,如"北京"'), }), execute: async ({ city }) => { // 这里调用真实的天气 API const res = await fetch(`https://api.weather.com?city=${city}`); const data = await res.json(); return `${city}当前温度 ${data.temp}°C,${data.condition}`; },});
const agent = new Agent({ name: '天气助手', instructions: '帮用户查天气,回答要简洁。', tools: [weatherTool],});关键点:
name和description帮助 AI 理解什么时候该调用这个工具parameters使用 Zod 定义参数类型(TypeScript 类型安全)execute是实际执行的函数
Zod 是什么?Zod 是一个 TypeScript 优先的数据校验库。它让你用一种简洁的方式定义数据结构,同时自动获得类型推断。本文所有代码中的
z.object()、z.string()都来自 Zod。
3. Agent 即工具
把一个 Agent 当作另一个 Agent 的工具来用:
const translateAgent = new Agent({ name: '翻译专家', instructions: '将任何内容翻译为英文,只输出翻译结果。',});
const mainAgent = new Agent({ name: '主助手', instructions: '你是一个万能助手。需要翻译时使用翻译工具。', tools: [translateAgent.asTool()], // 把翻译 Agent 当工具用});4. MCP 服务器工具
MCP(Model Context Protocol)是一种标准协议,让你的 Agent 接入外部工具服务:
import { MCPServerStdio } from '@openai/agents/mcp';
const fileServer = new MCPServerStdio({ name: '文件服务', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', './data'],});
const agent = new Agent({ name: '文件助手', tools: [fileServer], // Agent 现在可以读写 ./data 目录});关于 MCPMCP 是 Anthropic 提出的一种开放协议,让 AI 应用能标准化地接入各种外部工具和数据源。你可以把它理解为”AI 的 USB 接口”——只要工具支持 MCP,Agent 就能直接使用。
七、Handoffs —— Agent 之间的交接
为什么需要交接?
想象一个客服系统:
用户:"我想退货" → 路由 Agent 判断这是售后问题 → 交接给售后 Agent
用户:"怎么安装?" → 路由 Agent 判断这是技术问题 → 交接给技术支持 Agent实现方式
const afterSalesAgent = new Agent({ name: '售后客服', instructions: '你处理退货、换货、退款等售后问题。',});
const techAgent = new Agent({ name: '技术支持', instructions: '你处理产品使用、安装等技术问题。',});
const triageAgent = new Agent({ name: '路由客服', instructions: `你是客服路由。根据用户问题选择合适的客服:- 退货/换货/退款 → 交给售后客服- 安装/使用/故障 → 交给技术支持- 其他问题 → 自己回答`, handoffs: [afterSalesAgent, techAgent], // 可交接的目标});Handoff vs Agent-as-Tool
这两种模式容易混淆,核心区别是谁掌握控制权:
Handoff(交接): Agent A ──交出控制权──▶ Agent B 后续对话全部由 B 处理
Manager(Agent-as-Tool): Agent A ──调用──▶ Agent B ──返回结果──▶ Agent A A 始终掌控全局,B 只是 A 的"工具人"| Handoff 交接 | Manager 模式(asTool) | |
|---|---|---|
| 控制权 | 转交给目标 Agent | 主 Agent 保持控制 |
| 后续对话 | 目标 Agent 全权处理 | 主 Agent 决定下一步 |
| 适合场景 | 不同领域分工明确 | 需要协调多个结果 |
什么时候用哪个?
- 用 Handoff:每个 Agent 负责完全不同的领域,用户进来后不需要再回到主 Agent
- 用 Manager:主 Agent 需要综合多个 Agent 的结果做决策
八、Guardrails —— 安全守卫
Agent 有工具就能干活,但干的事安不安全?Guardrails 就是安全检查员。
三种守卫位置
用户输入 ──▶ [输入守卫] ──▶ Agent 处理 ──▶ [输出守卫] ──▶ 返回结果 │ [工具守卫](调用工具前后)输入守卫:检查用户输入
import { guardrail } from '@openai/agents';
const noHarmfulContent = guardrail({ name: '有害内容检测', execute: async (input) => { const isHarmful = await checkContent(input); if (isHarmful) { return { tripwire: true, reason: '输入包含不当内容' }; } return { tripwire: false }; },});
const agent = new Agent({ name: '安全助手', inputGuardrails: [noHarmfulContent],});当守卫触发(tripwire: true)时,Runner 会抛出 GuardrailTripwireTriggered 异常,阻止执行继续。
输出守卫:检查 Agent 回复
const noSecretLeak = guardrail({ name: '敏感信息检查', execute: async (output) => { if (containsSecret(output)) { return { tripwire: true, reason: '输出包含敏感信息' }; } return { tripwire: false }; },});
const agent = new Agent({ name: '安全助手', outputGuardrails: [noSecretLeak],});工具守卫:检查工具调用
直接在工具定义中添加校验:
const dbTool = tool({ name: 'query_database', description: '查询数据库', parameters: z.object({ sql: z.string() }), execute: async ({ sql }) => { return executeSQL(sql); }, // 工具守卫:执行前检查 SQL validateInput: async (input) => { if (input.sql.toLowerCase().includes('drop')) { throw new Error('禁止执行 DROP 操作'); } if (input.sql.toLowerCase().includes('delete') && !input.sql.includes('where')) { throw new Error('DELETE 必须带 WHERE 条件'); } },});WARNINGGuardrails 不是万能的。它们是最后一道防线,不应该替代其他安全措施(如数据库权限控制、API 认证等)。
九、Context —— 跨工具共享数据
问题场景
你的 Agent 有多个工具,它们都需要知道”当前用户是谁”:
工具 A(查订单)需要 userId工具 B(查余额)需要 userId工具 C(发通知)需要 userId总不能每个工具都让用户提供一遍吧?
解决方案:RunContext
// 1. 定义上下文类型interface AppContext { userId: string; userName: string; permissions: string[];}
// 2. 工具中访问上下文const orderTool = tool({ name: 'query_orders', description: '查询当前用户的订单', parameters: z.object({ keyword: z.string().optional() }), execute: async ({ keyword }, context) => { // context 的类型自动推断为 RunContext<AppContext> console.log(`为用户 ${context.userId} 查询订单`);
if (!context.permissions.includes('order_read')) { throw new Error('无权限查看订单'); }
return queryOrders(context.userId, keyword); },});
// 3. 运行时传入上下文const result = await runner.run(agent, '查看我的订单', { context: { userId: 'user_123', userName: '小明', permissions: ['order_read', 'balance_read'], },});Context 本质上就是依赖注入——在运行时注入共享数据,所有工具和守卫都能访问。
十、结构化输出
有时候你不想要一段文字,而是要一个结构化的数据对象。
使用 outputType
import { z } from 'zod';
const analysisAgent = new Agent({ name: '情感分析', instructions: '分析用户文本的情感倾向。', outputType: z.object({ sentiment: z.enum(['正面', '负面', '中性']), confidence: z.number().min(0).max(1), keywords: z.array(z.string()), summary: z.string(), }),});
const result = await runner.run(analysisAgent, '这个产品太棒了,推荐购买!');
// result.finalOutput 的类型是:// { sentiment: '正面', confidence: 0.95, keywords: ['棒', '推荐'], summary: '...' }console.log(result.finalOutput.sentiment); // "正面"TIP使用
outputType后,SDK 会在内部自动做 JSON 解析和类型校验。如果 AI 返回的格式不对,会自动重试。
十一、状态管理 —— 让 Agent 记住上下文
默认情况下,每次 runner.run() 都是独立的。但真实应用需要多轮对话。
方法一:手动传递历史(最灵活)
const result1 = await runner.run(agent, '我叫小明');// Agent 回复:"你好小明!"
const result2 = await runner.run(agent, '我叫什么名字?', { history: result1.history, // 把上一轮的对话历史传进去});// Agent 回复:"你叫小明。"方法二:使用 Session(最简单)
import { Session } from '@openai/agents';
const session = new Session();
// Session 自动管理对话历史await session.run(agent, '我叫小明');await session.run(agent, '我叫什么名字?'); // 它记得你叫小明两种方法怎么选?
| 场景 | 推荐 |
|---|---|
| 快速原型、简单应用 | Session |
| 需要精确控制历史内容 | 手动传递 history |
| 需要持久化到数据库 | 手动传递,自己存取 |
十二、实战:构建一个多 Agent 客服系统
把前面学到的所有知识串起来,做一个完整的示例。
需求描述
构建一个智能客服系统,能够:
- 自动路由到不同的专业 Agent
- 查询订单和天气
- 校验用户权限
- 返回结构化的回复
完整代码
import { Agent, Runner, tool, guardrail,} from '@openai/agents';import { z } from 'zod';
// ==========================================// 1. 定义上下文类型// ==========================================interface AppContext { userId: string; permissions: string[];}
// ==========================================// 2. 定义工具// ==========================================const queryOrders = tool({ name: 'query_orders', description: '查询用户订单', parameters: z.object({ status: z.enum(['全部', '待发货', '已发货', '已完成']).optional(), }), execute: async ({ status }, context) => { if (!context.permissions.includes('order_read')) { return '错误:无权限查看订单'; } // 模拟查询 return `用户 ${context.userId} 的${status ?? '全部'}订单:订单 #001(已发货)、订单 #002(待发货)`; },});
const getWeather = tool({ name: 'get_weather', description: '查询城市天气', parameters: z.object({ city: z.string().describe('城市名称'), }), execute: async ({ city }) => { return `${city}今天晴,25°C,适合出行`; },});
// ==========================================// 3. 定义守卫// ==========================================const inputGuard = guardrail({ name: '输入检查', execute: async (input) => { if (input.length > 500) { return { tripwire: true, reason: '输入过长,请控制在 500 字以内' }; } return { tripwire: false }; },});
// ==========================================// 4. 定义专业 Agent// ==========================================const orderAgent = new Agent<AppContext>({ name: '订单客服', instructions: '你处理订单相关问题,如查询订单、物流追踪等。用简洁友好的语气回答。', tools: [queryOrders],});
const infoAgent = new Agent<AppContext>({ name: '信息助手', instructions: '你处理生活信息类问题,如天气查询等。用简洁的语气回答。', tools: [getWeather],});
// ==========================================// 5. 定义路由 Agent(主入口)// ==========================================const routerAgent = new Agent<AppContext>({ name: '智能客服', instructions: `你是智能客服的路由中心。根据用户问题选择合适的客服:- 订单/物流/退货相关 → 交给订单客服- 天气/生活信息 → 交给信息助手- 简单问候/闲聊 → 自己回答请快速判断,不要犹豫。`, handoffs: [orderAgent, infoAgent], inputGuardrails: [inputGuard], outputType: z.object({ answer: z.string(), routedTo: z.string().optional(), }),});
// ==========================================// 6. 运行// ==========================================async function main() { const runner = new Runner();
// 场景 1:订单查询 const result1 = await runner.run(routerAgent, '我的订单到哪了?', { context: { userId: 'user_001', permissions: ['order_read'] }, }); console.log('场景 1:', result1.finalOutput); // { answer: '...', routedTo: '订单客服' }
// 场景 2:天气查询 const result2 = await runner.run(routerAgent, '北京今天天气怎么样?', { context: { userId: 'user_001', permissions: ['order_read'] }, }); console.log('场景 2:', result2.finalOutput);
// 场景 3:简单问候 const result3 = await runner.run(routerAgent, '你好!', { context: { userId: 'user_001', permissions: ['order_read'] }, }); console.log('场景 3:', result3.finalOutput);}
main();运行流程解析
以场景 1 “我的订单到哪了?” 为例:
1. Runner 检查输入守卫 → 通过2. Router Agent 收到消息 → 判断这是订单问题3. Router Agent 触发 Handoff → 交接给订单客服4. 订单客服收到消息 → 决定调用 query_orders 工具5. Runner 执行 query_orders → 查询数据库 → 返回结果6. 订单客服基于结果生成回复7. Runner 检查输出守卫 → 通过8. 返回结构化结果 { answer: '...', routedTo: '订单客服' }十三、总结与学习路径
核心概念一图总结
Agent(智能体) ├── instructions ── 灵魂:告诉它怎么做事 ├── tools ── 双手:让它能干活 ├── handoffs ── 社交:让它能交接 ├── guardrails ── 底线:让它别乱来 ├── outputType ── 模具:让输出有结构 └── context ── 记忆:让工具共享数据
Runner(执行引擎) ├── run() ── 同步运行 ├── runStreamed() ── 流式运行 └── runTurn() ── 单步运行
Tools(工具)四大类 ├── 托管工具 ── OpenAI 提供的 ├── 函数工具 ── 你自己写的 ├── Agent-as-Tool ── 拿 Agent 当工具 └── MCP 工具 ── 接入外部服务
状态管理 ├── history ── 手动管理 └── Session ── 自动管理推荐学习路径
第 1 步:跑通最简示例(Agent + Runner) ↓第 2 步:加一个自定义函数工具 ↓第 3 步:实现两个 Agent 的 Handoff ↓第 4 步:加上 Guardrails 安全检查 ↓第 5 步:用 Context 传递用户信息 ↓第 6 步:用 outputType 实现结构化输出 ↓第 7 步:构建完整的多 Agent 系统进阶方向
掌握基础后,可以继续探索:
- Streaming:实时流式输出,适合打字机效果的 UI
- Tracing:可视化 Agent 的调用链,用于调试和监控
- Voice Agent:
RealtimeAgent实现实时语音交互 - Sandbox Agent:在隔离环境中安全执行代码
- MCP 生态:接入越来越多的 MCP 工具服务器