逐日AI
第 1 周 · D4约 4 小时

OpenAI Agents SDK:agents、handoffs、guardrails、sessions、tracing

用官方 Agents SDK 把昨天手写的循环收进几行 API:多个 Agent 之间怎么交接、怎么在入口和出口设护栏、怎么记住多轮、怎么看清每一步。

今日目标 0/3

登录后可以勾选并保存进度。

今日目标

  1. 能用 Agent 与 run 或 Runner 写出一个带函数工具的最小 Agent,并说清 maxTurns 的作用
  2. 能用 handoffs 让分诊 Agent 把对话交给专家 Agent,并解释它与「一个大 Agent 加很多工具」的取舍
  3. 能写一个输入侧 guardrail 并捕获 tripwire 异常,说清输入侧与输出侧护栏的分工

昨天你手写了「请求工具 → 执行 → 回填」的一次来回。今天要解决的是:这个来回要转很多圈怎么办?活太杂一个搭档接不下来怎么办?怎么防止他接了不该接的活、说了不该说的话?读完做完,回到页面顶部把三条目标勾掉。

小白版讲解

Agent 与 Runner:把「模型 + 循环 + 工具」收进一个对象和一个函数

昨天的脚本里,「模型请求工具、你执行、回填、再问」这个循环只转了一圈。真实任务往往要转好几圈:先查清单,再查详情,再决定改什么。手写这个 while 循环不难,但每个项目都写一遍就烦了,而且要处理循环不停、工具报错、参数解析这些边角。OpenAI 的 Agents SDK 做的第一件事就是把这个循环收起来:Agent 对象装下指令和工具,run 函数替你转圈

回到搭档的类比:Agent 就是一位有明确岗位说明(instructions)和一套工具箱(tools)的搭档;run 就是你把活交给他、等他做完回来。他中间调了几次工具、想了几轮,你不用管,只要结果——除非圈数超了。

agent.ts
import { Agent, run, tool } from '@openai/agents'
import { z } from 'zod'
 
const listRoutes = tool({
  name: 'list_routes',
  description: '列出 TODO API 的全部路由',
  parameters: z.object({}),
  execute: async () => ['POST /todos', 'GET /todos', 'DELETE /todos/:id'],
})
 
const helper = new Agent({
  name: 'TODO API 助手',
  instructions: '你只回答与这个 TODO API 项目有关的问题,需要事实时先调工具。',
  model: process.env.OPENAI_MODEL,
  tools: [listRoutes],
})
 
const result = await run(helper, '项目里有哪些接口还没有测试?', { maxTurns: 5 })
console.log(result.finalOutput)

对照昨天:tool 助手(Python 是 function_tool 装饰器)替你把 zod schema 或类型注解变成昨天手写的那段 JSON Schema;execute 就是昨天的 runToolrun 里面藏着昨天的「筛 function_call、回填 function_call_output、再调一次」。它藏起来的正是你昨天亲手写过的东西,所以出问题时你知道该往哪层找。

maxTurns 是必须理解的一个参数。一「turn」是模型的一次调用,循环每转一圈算一次;超过上限 SDK 抛 MaxTurnsExceededError(TypeScript 默认 10)。它不是性能参数,是安全阀:一个把工具结果误读、反复调同一个工具的 Agent,没有这个阀会一直烧钱到你发现为止。生产里要按任务类型设:一问一答的助手 3 到 5 圈够了,要改多个文件的任务可能要 20 圈以上。TypeScript 里还有 Runner 类,new Runner({ ... }) 后调 runner.run(),用来给一批运行共用同一套配置(模型、追踪、默认的 maxTurns);单次跑用 run 就够。

函数工具:声明参数,SDK 替你生成 schema

工具是 Agent 的手。上一节的 list_routes 没有参数,真实工具几乎都有。声明方式两边都是「用你熟悉的类型系统写参数,SDK 转成 JSON Schema」:TypeScript 用 zod(注意 SDK 用的是 zod v4),Python 用函数签名的类型注解加 docstring。

tool-with-params.ts
import { tool } from '@openai/agents'
import { z } from 'zod'
 
const todos = [{ id: 1, title: '给 POST /todos 补校验', done: false }]
 
export const markDone = tool({
  name: 'mark_done',
  description: '把某条 TODO 标记为完成',
  parameters: z.object({
    id: z.number().int().describe('TODO 的 id'),
  }),
  execute: async ({ id }) => {
    const todo = todos.find((t) => t.id === id)
    if (!todo) return `没有 id 为 ${id} 的 TODO`
    todo.done = true
    return `已完成:${todo.title}`
  },
})

两个工程习惯值得养成。第一,description 和参数描述是给模型看的接口文档,写得越像人话,模型选错工具、填错参数的概率越低——「把某条 TODO 标记为完成」比「更新 todo」好得多。第二,工具的返回值也是给模型看的,返回一句「没有 id 为 7 的 TODO」比抛异常好:抛异常会让 SDK 把错误文本回给模型,模型可能理解也可能不理解;一句清楚的话它一定能理解,并据此决定下一步。

工具有副作用时(上面的 mark_done 就改了数据),还要想清楚 D1 讲过的那件事:模型判断要调它,你的代码就真的执行了。SDK 层面可以用工具级的 guardrail(下面讲)或 needsApproval 这类机制加一道人工确认,原则和 Codex 的审批一样——不可逆的动作前要有人点头

handoffs:分诊 Agent 把对话整个交出去

活杂了怎么办?一个搭档什么都懂是不现实的。公司里的做法是前台分诊:你来问,前台判断这事归谁,然后把你整个人领到那位同事面前,之后你直接跟那位同事说,前台不再插手。Agents SDK 里这叫 handoff(交接)——注意和「工具」的区别:调工具是搭档替你去问了一句再回来告诉你;handoff 是搭档把对话交给另一位,之后由那位负责回答。

实现上,handoff 就是一个特殊的工具:给分诊 Agent 配一组 handoffs,SDK 会为每个目标 Agent 生成一个名为 transfer_to_<agent_name> 的工具;分诊 Agent 一调它,运行时就把对话(可以经过过滤)交给目标 Agent 继续。

handoff.ts
import { Agent, run } from '@openai/agents'
import { RECOMMENDED_PROMPT_PREFIX } from '@openai/agents-core/extensions'
 
const validationExpert = new Agent({
  name: 'validation_expert',
  instructions: '你负责 TODO API 的输入校验问题:schema 设计、错误响应形状。',
  model: process.env.OPENAI_MODEL,
})
 
const testingExpert = new Agent({
  name: 'testing_expert',
  instructions: '你负责 TODO API 的单元测试问题:用例设计、测试隔离。',
  model: process.env.OPENAI_MODEL,
})
 
const triage = Agent.create({
  name: 'triage',
  instructions: `${RECOMMENDED_PROMPT_PREFIX}
你是分诊台。校验相关交给 validation_expert,测试相关交给 testing_expert,其它自己简短回答。`,
  model: process.env.OPENAI_MODEL,
  handoffs: [validationExpert, testingExpert],
})
 
const result = await run(triage, 'POST /todos 的 title 为空时应该返回什么?')
console.log(result.lastAgent?.name) // validation_expert
console.log(result.finalOutput)

RECOMMENDED_PROMPT_PREFIX 是官方给的一段前缀,告诉模型「你身处一个多 Agent 系统,交接是正常操作」,实测能明显提高交接的准确率。result.lastAgent(Python 是 last_agent)告诉你最后是谁在回答——这是验证路由是否正确的最直接方法,实验里你会拿它做断言。需要更细的控制时用 handoff() 助手包一层:toolNameOverride 改工具名,onHandoff 在交接时回调(记日志、发通知),inputFilter 过滤交给下一位的历史(比如把之前的工具调用记录删掉,官方 extensions 里有 removeAllTools 这类现成过滤器),inputType 让模型在交接时附带结构化的元信息(比如「原因」「优先级」)。

那什么时候该拆多个 Agent 用 handoff,什么时候一个大 Agent 挂一堆工具更好?判据不是「工具多不多」,而是指令会不会互相打架。校验专家的指令里全是 zod 与错误形状,测试专家的指令里全是用例隔离与 mock——把这两套写进同一份 instructions,模型每次都要在两套上下文里切换,出错率上升,提示也越长越贵。指令彼此独立、上下文能明显分开的,拆;工具虽多但共享同一套背景知识的,不拆。还有一个硬约束:handoff 发生在同一次 run 里,输入侧 guardrail 只对链条上的第一个 Agent 生效,这引出下一节。

guardrails:入口查用户,出口查模型

搭档再能干,也有两种事不该发生:接了不该接的活(用户让 TODO API 助手写情书),说了不该说的话(回答里带出了数据库连接串)。前者要在入口拦,后者要在出口拦。Agents SDK 把这两道门叫 input guardrailoutput guardrail,统称 guardrails(护栏)。

一个 guardrail 就是一个函数:拿到输入(或输出)和上下文,返回一个判断,其中 tripwireTriggered(Python 是 tripwire_triggered)为真表示「拉响警报」。警报一响,run 立刻抛出 InputGuardrailTripwireTriggeredOutputGuardrailTripwireTriggered 异常,你在外面捕获、决定怎么回应用户。

guardrail.ts
import { Agent, run, InputGuardrailTripwireTriggered } from '@openai/agents'
import type { InputGuardrail } from '@openai/agents'
 
// 一个不花钱的护栏:纯规则判断,不调模型
const onTopicOnly: InputGuardrail = {
  name: 'on_topic_only',
  execute: async ({ input }) => {
    const text = typeof input === 'string' ? input : JSON.stringify(input)
    const offTopic = !/todo|接口|校验|测试|路由|api/i.test(text)
    return { outputInfo: { offTopic }, tripwireTriggered: offTopic }
  },
}
 
const triage = new Agent({
  name: 'triage',
  instructions: '你只处理 TODO API 项目的问题。',
  model: process.env.OPENAI_MODEL,
  inputGuardrails: [onTopicOnly],
})
 
try {
  const result = await run(triage, '帮我写一首情诗')
  console.log(result.finalOutput)
} catch (err) {
  if (err instanceof InputGuardrailTripwireTriggered) {
    console.log('这个问题与项目无关,我只回答 TODO API 相关的问题。')
  } else {
    throw err
  }
}

上面的护栏是纯规则,零成本、零延迟;更常见的是用一个便宜的小模型当护栏——在 execute 里再跑一个只做分类的小 Agent,判断「这是不是项目相关问题」。输入侧护栏和主 Agent 是并行跑的,所以它不会拖慢正常请求;一旦警报响起,主 Agent 那边昂贵的运行会被取消,这正是护栏省钱的方式:用一个便宜模型挡住那些本来要让贵模型白跑一趟的请求。

输入侧还是输出侧?分工很清楚。输入侧管「该不该做」:话题越界、明显的注入攻击、超出服务范围的请求,越早拦越省。输出侧管「能不能说」:泄露敏感信息、格式不合规、违反业务规则的建议,只有等模型说完才能查。两边的漏网情况也不同:输入侧拦不住「问题正常但回答跑偏」,输出侧拦不住「模型已经调了有副作用的工具」——所以有副作用的工具还得有第三种护栏,工具级 guardrail,围着每一次函数调用跑。三层加起来才是完整的防线。

记住那条硬约束:输入侧 guardrail 只在链条的第一个 Agent 上生效,输出侧只在产出最终回答的那个 Agent 上生效。所以护栏要挂在分诊 Agent 上,挂在专家 Agent 上的输入护栏永远不会跑。

sessions:多轮记忆交给 SDK 存

昨天讲了多轮历史的两条路:服务端存(previous_response_id)或自己带。Agents SDK 把这个选择包成了 session:你给 run 传一个 session 对象,SDK 在每次运行前从里面取历史、运行后把新条目写回去,你的代码不再碰 input 数组。

session.ts
import { Agent, run, MemorySession } from '@openai/agents'
 
const helper = new Agent({
  name: 'helper',
  instructions: '你是 TODO API 项目的助手。',
  model: process.env.OPENAI_MODEL,
})
 
const session = new MemorySession() // 进程内存,重启即失
 
await run(helper, '我们的校验库是 zod。', { session })
const result = await run(helper, '刚才说的校验库是什么?', { session })
console.log(result.finalOutput) // 应该提到 zod

两边可选的存储不同:TypeScript 有进程内的 MemorySession 和用 OpenAI Conversations 接口托管的 OpenAIConversationsSession;Python 更丰富,SQLiteSessionRedisSessionSQLAlchemySessionMongoDBSession,还有一个 EncryptedSession 包装器给任何 session 加透明加密。Session 的接口只有四个动作:取历史、追加条目、弹出最后一条、清空。pop_item 这个看起来奇怪的方法有一个实际用途:用户说「刚才那句撤回」时,把最后一轮从历史里拿掉再重跑。

选 session 还是 previous_response_id?session 的历史在你手里(内存、SQLite、Redis),可以审计、可以裁剪、可以换模型重放;previous_response_id 的历史在 OpenAI 那边,请求最小但你看不到全貌。两者能同时用——SDK 也支持 previousResponseIdconversationId 这两种服务端方案。原则和昨天一样:面向用户的生产系统,历史自己至少落一份。

tracing:默认开着的追踪

最后一件事:这一切跑起来之后,你怎么知道分诊有没有交接对、护栏有没有误拦、哪一步花了最多 token?Agents SDK 的答案是 tracing,而且默认就是开的——每次 run 都会生成一条 trace,里面按层级记录 Agent 的每一轮、每次模型调用、每次工具调用、每次交接和护栏判断,传到 OpenAI 平台的 Traces 面板里可视化。

你只需要知道三个动作。:跑完一次真实运行,去平台的 Traces 页面找到它,展开看分诊 Agent 是在第几轮调了 transfer_to_validation_expert:环境变量 OPENAI_AGENTS_DISABLE_TRACING=1 全局关掉;TypeScript 里也可以在 run 配置里设 tracingDisabled。处理敏感数据或合规要求数据不出境时要关,或者用 setTraceProcessors 换成你自己的导出器,比如接到 OpenTelemetry。分组:默认每次 run 一条 trace;一段多轮对话想归到一起,用 withTrace(Python 是 with trace(...))把多次 run 包起来,或者设 groupId 用会话 id 关联。

tracing 是这门课里最容易被跳过、又最值钱的一节。没有它,多 Agent 系统出问题时你只能猜;有了它,「为什么这个问题被交给了测试专家」是一个三十秒能看清的事实。30 天课 D21 讲的评估与可观测,在这里已经有了一个开箱即用的起点。

源码导读

动手实验

🧪 D4 实验:双 Agent handoff 加一个 guardrail 的示例

代码位置:labs/codex-mastery/day-04-handoff-guardrail

验收标准:

  1. MOCK=1 pnpm start "title 为空应该返回什么" 输出里 lastAgentvalidation_expert;换成测试相关的问题时是 testing_expert
  2. MOCK=1 pnpm start "帮我写一首情诗" 打印的是护栏拦截的友好提示,不是异常栈;程序退出码为 0。
  3. --session 模式连问两句,第二句的回答里引用了第一句给的事实。
  4. 去掉 MOCK 跑一次真实运行,在 Traces 面板里能找到这条 trace,并看到 transfer_to_validation_expert 那一步。

代码在 labs/codex-mastery/day-04-handoff-guardrailstarter/ 挖了四个练习点,solution/ 是完整答案。MOCK 模式下不调模型,但护栏、路由、session 这些业务逻辑都真实执行——这正是 MOCK 的意义:验证你的编排逻辑,而不是验证模型。

  1. MOCK=1 pnpm start "有哪些接口",看到一个带函数工具的最小 Agent 给出回答,并打印 lastAgent
  2. 实现练习 1:把 helper 拆成 triage 加两个专家,用 handoffs 交接;跑校验类与测试类两个问题,确认 lastAgent 分别是两位专家。
  3. 实现练习 2 与 3:写 onTopicOnly 输入护栏挂到 triage 上,在 main 里捕获 InputGuardrailTripwireTriggered 打印友好提示;跑「帮我写一首情诗」确认被拦。
  4. 实现练习 4:--session 模式下用同一个 MemorySession 连跑两次,确认第二次记得第一次。
  5. 配好 key 与模型去掉 MOCK 跑一次,到 Traces 面板里找到这次运行,看交接发生在第几轮。

面试题

今天 3 道题在下方题库区,侧重多 Agent 拆分的判据、guardrail 的放置位置、会话记忆与可观测的工程取舍。展开后先看「分析过程」再看要点——照着推导练,比背要点管用。

检查清单与明日预告

  • 能用 Agent 与 run 或 Runner 写出一个带函数工具的最小 Agent,并说清 maxTurns 的作用
  • 能用 handoffs 让分诊 Agent 把对话交给专家 Agent,并解释它与「一个大 Agent 加很多工具」的取舍
  • 能写一个输入侧 guardrail 并捕获 tripwire 异常,说清输入侧与输出侧护栏的分工
  • 能说出「输入护栏只在第一个 Agent 上跑」这条约束对护栏放置位置的影响
  • 实验的 4 条验收标准全部通过
  • 3 道面试题不看要点也能答出至少 2 道

明天(D5)是收官:用同一个任务——给 TODO API 补输入校验和单元测试——分别走一遍 Codex 与 Claude Code,从交代方式、审批、验证、成本四个维度做对比,再把两家工具组合成「一个写、一个审」的日常工作流。把对比放在最后是有意的:前四天你已经知道 Codex 每一层是怎么工作的,明天看到两家的差异时,才能判断那是工作方式的差异还是能力的差异——这两种差异,应对方法完全不同。

面试题库

  • 什么时候该把一个 Agent 拆成多个、用 handoff 交接?什么时候「一个大 Agent 加很多工具」反而更好?When should you split one agent into several connected by handoffs, and when is a single agent with many tools the better design?
    国内高频海外高频进阶#agents-sdk#handoffs#architecture

    分析过程 · 先想清楚再作答

    1. 这题考的是拆分判据,不是会不会用 API。答「工具多了就拆」是最常见的错误,工具数量不是判据。
    2. 先说清 handoff 与工具的区别:调工具是替你去问一句再回来,handoff 是把对话整个交给另一个 Agent,之后由它负责;实现上 handoff 也是一个名为 transfer_to_xxx 的工具,但语义是转移控制权。
    3. 判据是「指令会不会互相打架」:两组任务需要的背景知识、约束、语气彼此独立且冲突时,塞进一份 instructions 会让模型反复切换上下文、提示越长越贵、出错率上升,这时拆;工具虽多但共享同一套背景的,不拆。
    4. 补拆分的代价:多一次模型调用(分诊那一跳)、路由可能错、输入护栏只在第一个 Agent 上跑、跨 Agent 的历史要靠 inputFilter 裁剪。
    5. 可预期的追问:分诊错了怎么办?用 RECOMMENDED_PROMPT_PREFIX 提高交接准确率,用 lastAgent 做回归断言,用 tracing 看交接发生在哪一轮,必要时让专家 Agent 也能交接回分诊台。

    How to reason about it · think before answering

    1. This tests your splitting criterion, not API fluency; 'split when there are many tools' is the common wrong answer.
    2. First separate handoffs from tools: a tool call fetches an answer and returns; a handoff transfers the whole conversation so the receiving agent owns it, even though it is implemented as a transfer_to_xxx tool.
    3. The criterion is whether instructions conflict: when two task groups need independent, clashing background, constraints and tone, one instruction block forces constant context switching, longer prompts and more errors, so split; many tools sharing one background do not justify a split.
    4. Name the costs: an extra model call for triage, possible misrouting, input guardrails only on the first agent, and history trimming across agents via inputFilter.
    5. Expect the follow-up: what if triage misroutes? Use RECOMMENDED_PROMPT_PREFIX, assert on lastAgent in regression tests, inspect the handoff turn in tracing, and allow experts to hand back.

    答题要点

    • handoff 转移的是对话控制权,工具调用只是取一次结果
    • 拆分判据是指令是否互相打架,不是工具数量
    • 拆的代价:多一跳、可能路由错、输入护栏只在第一个 Agent 生效
    • 用前缀提示、lastAgent 断言与 tracing 控制路由质量

    Key points

    • A handoff transfers conversational control; a tool call only fetches a result
    • Split on conflicting instructions, not on tool count
    • Costs: an extra hop, possible misrouting, input guardrails only on the first agent
    • Control routing quality with the recommended prefix, lastAgent assertions and tracing
  • guardrail 应该放在输入侧还是输出侧?各自的成本、能拦住什么、拦不住什么?Should guardrails sit on the input side or the output side? What does each cost, what does it catch, and what slips through?
    国内高频海外高频深入#agents-sdk#guardrails#safety

    分析过程 · 先想清楚再作答

    1. 题眼在「拦不住什么」。只说两边都要放而不说各自的漏网情况,就是没在生产里被漏网案例打过脸。
    2. 先给分工:输入侧管「该不该做」——话题越界、明显注入、超出服务范围,越早拦越省;输出侧管「能不能说」——泄露敏感信息、格式不合规、违反业务规则,只有模型说完才能查。
    3. 再说成本:输入护栏与主 Agent 并行跑,警报一响就取消主 Agent 的昂贵运行,所以用便宜小模型做输入护栏是省钱手段;输出护栏必须等主 Agent 跑完,省不了钱,只能防事故。
    4. 漏网情况:输入侧拦不住「问题正常但回答跑偏」;输出侧拦不住「模型已经调了有副作用的工具」——所以有副作用的工具需要第三层,围着每次函数调用跑的工具级护栏。
    5. 补一条 SDK 约束:输入护栏只在链条第一个 Agent 上跑,输出护栏只在产出最终回答的 Agent 上跑,挂错位置等于没挂。
    6. 可预期的追问:护栏最常见的失败模式是什么?太严而不是太松——正则黑名单误拦正常用户;上线前要有正常请求的回归集,误拦率是必看指标。

    How to reason about it · think before answering

    1. The crux is 'what slips through'; saying 'use both' without naming each side's blind spot signals no production incidents survived.
    2. Division of labor: input guardrails decide whether to act at all (off-topic, obvious injection, out of scope) and are cheapest early; output guardrails decide whether the answer may be said (leaks, format, policy) and can only run after generation.
    3. Cost: input guardrails run in parallel with the main agent and cancel its expensive run on a tripwire, so a cheap classifier there saves money; output guardrails wait for the full run and only prevent incidents.
    4. Blind spots: input cannot catch a normal question with a drifting answer; output cannot undo a side-effecting tool already called, hence a third layer of tool-level guardrails around each function call.
    5. Add the SDK constraint: input guardrails run only on the first agent, output guardrails only on the agent producing the final answer; misplaced guardrails never execute.
    6. Expect the follow-up: the common failure mode? Too strict, not too loose; regex blocklists over-block real users, so keep a regression set of legitimate requests and watch the false-block rate.

    答题要点

    • 输入侧管该不该做,越早拦越省;输出侧管能不能说,只能事后查
    • 输入护栏与主 Agent 并行、触发即取消,便宜模型在此省钱;输出护栏省不了钱只防事故
    • 输入侧漏「回答跑偏」,输出侧漏「已调有副作用的工具」,需工具级护栏补第三层
    • 输入护栏只在第一个 Agent 生效;常见失败是太严,需正常请求回归集

    Key points

    • Input side decides whether to act and is cheapest early; output side decides what may be said and only runs afterwards
    • Input guardrails run in parallel and cancel the main run, so cheap models save money there; output guardrails only prevent incidents
    • Input misses drifting answers, output misses side effects already taken; tool-level guardrails add the third layer
    • Input guardrails run only on the first agent; the common failure is over-blocking, so keep a regression set
  • Agents SDK 的 session 和 Responses API 的 previous_response_id 都能记住多轮,怎么选?tracing 在这里起什么作用?Both Agents SDK sessions and the Responses API's previous_response_id remember multi-turn state. How do you choose, and what role does tracing play?
    国内高频海外高频进阶#agents-sdk#sessions#tracing

    分析过程 · 先想清楚再作答

    1. 这题考的是对「状态放在谁手里」的敏感度,是 30 天课 D1「历史靠你自己搬」在 SDK 层的翻版。
    2. 拆法是问三件事:历史能不能审计、能不能裁剪或重放、能不能满足数据驻留要求。previous_response_id 的历史在服务端,请求最小、代码最简,但三个问题都答不好;session 的历史在你手里(内存、SQLite、Redis),三个都能做,代价是自己管存储。
    3. 结论:原型与内部工具用 previous_response_id 省事;面向用户的生产系统至少自己落一份历史,session 是现成的落法;两者可以同时用。
    4. session 的四个接口(取、追加、弹出最后一条、清空)里 pop_item 值得点出:用户撤回上一句时把最后一轮拿掉再重跑,这是自己持有历史才能做的事。
    5. tracing 的作用是让多 Agent 系统的行为可解释:默认开启,每次 run 一条,记录每轮、每次工具调用、交接与护栏判断;用 withTrace 或 group_id 把一段对话归到一起;敏感数据场景用环境变量关掉或换成自己的导出器。
    6. 可预期的追问:tracing 会不会把用户数据传出去?默认会传到平台面板,所以合规场景要么关、要么 setTraceProcessors 换成自己的后端。

    How to reason about it · think before answering

    1. This probes your sensitivity to who holds the state, the SDK-level echo of 'you carry the history yourself'.
    2. Ask three questions: can the history be audited, trimmed or replayed, and kept within data-residency rules? previous_response_id keeps history server-side with minimal requests but answers all three poorly; sessions keep it in your store and answer all three, at the cost of managing storage.
    3. Conclude: prototypes and internal tools take previous_response_id; user-facing production keeps its own copy, for which sessions are the ready-made path; both can coexist.
    4. Of the four session operations, pop_item deserves mention: removing the last turn to honor a user's undo is only possible when you own the history.
    5. Tracing makes multi-agent behavior explainable: on by default, one trace per run recording turns, tool calls, handoffs and guardrail results; group a conversation with withTrace or group_id; disable via env var or swap in your own exporter for sensitive data.
    6. Expect the follow-up: does tracing ship user data out? By default it goes to the platform dashboard, so regulated settings must disable it or replace the processors.

    答题要点

    • previous_response_id 历史在服务端,请求小代码简,但难审计、难裁剪、难满足数据驻留
    • session 历史在自己手里,可审计可重放,pop_item 支持撤回;生产至少自己落一份
    • tracing 默认开、每次 run 一条,记录每轮工具、交接与护栏,用 group_id 归组
    • 敏感数据场景用 OPENAI_AGENTS_DISABLE_TRACING 关掉或换成自己的导出器

    Key points

    • previous_response_id keeps history server-side, small and simple, but weak on audit, trimming and residency
    • Sessions keep history in your store, auditable and replayable, with pop_item for undo; production keeps its own copy
    • Tracing is on by default, one trace per run, capturing turns, tools, handoffs and guardrails, grouped via group_id
    • For sensitive data disable it with OPENAI_AGENTS_DISABLE_TRACING or swap in your own exporter

评论

登录后即可参与讨论

还没有评论,来说第一句。