会话
会话为 Agents SDK 提供了一个持久化记忆层。向 Runner.run 提供任何实现 Session 接口的对象,SDK 就会处理其余工作。存在会话时,运行器会自动:
- 获取之前存储的对话项,并将其添加到下一轮输入之前。
- 在每次运行完成后持久化新的用户输入和助手输出。
- 保留会话以供后续轮次使用,无论您是使用新的用户文本调用运行器,还是从中断的
RunState恢复运行。
因此,无需手动调用 toInputList() 或在不同轮次之间拼接历史记录。TypeScript SDK 提供了两种实现:用于 Conversations API 的 OpenAIConversationsSession,以及用于本地开发的 MemorySession。由于它们共享 Session 接口,您可以接入自己的存储后端。如需了解 Conversations API 之外的实现方式,请查看 examples/memory/ 下的示例会话后端(Prisma、基于文件的后端等)。使用 OpenAI Responses 模型时,可使用 OpenAIResponsesCompactionSession 包装任何会话,通过 responses.compact 自动缩减存储的对话历史记录。
提示:要运行本页中的
OpenAIConversationsSession示例,请设置OPENAI_API_KEY环境变量(或在构造会话时提供apiKey),以便 SDK 调用 Conversations API。
如果希望 SDK 为您管理客户端记忆,请使用会话。如果您已经通过 conversationId 或 previousResponseId 使用 OpenAI 服务器管理的状态,通常无需再为同一份对话历史记录使用会话。
使用 OpenAIConversationsSession 与 Conversations API 同步记忆,也可以替换为其他任意 Session 实现。
import { Agent, OpenAIConversationsSession, run } from '@openai/agents';
const agent = new Agent({ name: 'TourGuide', instructions: 'Answer with compact travel facts.',});
// Any object that implements the Session interface works here. This example uses// the built-in OpenAIConversationsSession, but you can swap in a custom Session.const session = new OpenAIConversationsSession();
const firstTurn = await run(agent, 'What city is the Golden Gate Bridge in?', { session,});console.log(firstTurn.finalOutput); // "San Francisco"
const secondTurn = await run(agent, 'What state is it in?', { session });console.log(secondTurn.finalOutput); // "California"复用同一个会话实例,可确保智能体在每轮开始前都能收到完整的对话历史记录,并自动持久化新项目。切换到其他 Session 实现时,无需更改任何其他代码。
对于本地演示、测试或进程内聊天状态,MemorySession 可提供相同的接口,且无需与 OpenAI 通信:
import { Agent, MemorySession, run } from '@openai/agents';
const agent = new Agent({ name: 'TourGuide', instructions: 'Answer with compact travel facts.',});
const session = new MemorySession();const result = await run(agent, 'What city is the Golden Gate Bridge in?', { session,});
console.log(result.finalOutput);OpenAIConversationsSession 构造函数选项:
| 选项 | 类型 | 说明 |
|---|---|---|
conversationId | string | 复用现有对话,而不是按需创建对话。 |
client | OpenAI | 传入预先配置的 OpenAI 客户端。 |
apiKey | string | 创建内部 OpenAI 客户端时使用的 API 密钥。 |
baseURL | string | OpenAI 兼容端点的基础 URL。 |
organization | string | 请求使用的 OpenAI 组织 ID。 |
project | string | 请求使用的 OpenAI 项目 ID。 |
MemorySession 构造函数选项:
| 选项 | 类型 | 说明 |
|---|---|---|
sessionId | string | 用于日志或测试的稳定标识符。默认自动生成。 |
initialItems | AgentInputItem[] | 使用现有历史记录初始化会话。 |
logger | Logger | 覆盖用于调试输出的日志记录器。 |
MemorySession 将所有内容存储在本地进程内存中,因此会在进程退出时重置。
如果需要在构造会话之前预先创建对话 ID,请使用 startOpenAIConversationsSession(client?),并将返回的 ID 作为 conversationId 传入。
核心会话行为
Section titled “核心会话行为”运行器的会话使用方式
Section titled “运行器的会话使用方式”- 每次运行之前,运行器会检索会话历史记录,将其与新一轮输入合并,然后把合并后的列表传递给智能体。
- 非流式运行之后,运行器会调用一次
session.addItems(),持久化原始用户输入和最新一轮的模型输出。 - 对于流式运行,运行器会先写入用户输入,并在本轮完成后追加流式输出。
- 从
RunResult.state恢复运行时(用于审批或其他中断场景),请继续传入同一个session。恢复后的轮次会添加到记忆中,而无需重新准备输入。
当输出护栏拒绝可作为终止输出的函数工具结果时,运行器不会将被拒绝的原始结果添加到新的会话历史记录中。对于可识别的函数调用及其结果对,运行器会持久化一个可安全重放的占位项;对于归属关系无法确定的当前响应后缀,则会将其省略,而不是存储无法证明归属的输出。此保护机制不会撤销外部工具的副作用,也不会清除应用在会话流水线之外存储的数据。
历史记录的检查与编辑
Section titled “历史记录的检查与编辑”会话提供了简单的 CRUD 辅助方法,以便您构建”撤销""清空聊天”或审计功能。
import { OpenAIConversationsSession } from '@openai/agents';import type { AgentInputItem } from '@openai/agents-core';
// Replace OpenAIConversationsSession with any other Session implementation that// supports get/add/pop/clear if you store history elsewhere.const session = new OpenAIConversationsSession({ conversationId: 'conv_123', // Resume an existing conversation if you have one.});
const history = await session.getItems();console.log(`Loaded ${history.length} prior items.`);
const followUp: AgentInputItem[] = [ { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'Let’s continue later.' }], },];await session.addItems(followUp);
const undone = await session.popItem();
if (undone?.type === 'message') { console.log(undone.role); // "user"}
await session.clearSession();session.getItems() 返回存储的 AgentInputItem[]。调用 popItem() 可移除最后一个条目,这对于用户在重新运行智能体前修正内容很有用。
自定义存储与合并行为
Section titled “自定义存储与合并行为”实现 Session 接口,即可使用 Redis、DynamoDB、SQLite 或其他数据存储作为记忆后端。只需实现五个异步方法。
import { Agent, run } from '@openai/agents';import { randomUUID } from '@openai/agents-core/_shims';import { getLogger } from '@openai/agents-core';import type { AgentInputItem, Session } from '@openai/agents-core';
/** * Minimal example of a Session implementation; swap this class for any storage-backed version. */export class CustomMemorySession implements Session { private readonly sessionId: string; private readonly logger: ReturnType<typeof getLogger>;
private items: AgentInputItem[];
constructor( options: { sessionId?: string; initialItems?: AgentInputItem[]; logger?: ReturnType<typeof getLogger>; } = {}, ) { this.sessionId = options.sessionId ?? randomUUID(); this.items = options.initialItems ? options.initialItems.map(cloneAgentItem) : []; this.logger = options.logger ?? getLogger('openai-agents:memory-session'); }
async getSessionId(): Promise<string> { return this.sessionId; }
async getItems(limit?: number): Promise<AgentInputItem[]> { if (limit === undefined) { const cloned = this.items.map(cloneAgentItem); this.logger.debug( `Getting items from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); return cloned; } if (limit <= 0) { return []; } const start = Math.max(this.items.length - limit, 0); const items = this.items.slice(start).map(cloneAgentItem); this.logger.debug( `Getting items from memory session (${this.sessionId}): ${JSON.stringify(items)}`, ); return items; }
async addItems(items: AgentInputItem[]): Promise<void> { if (items.length === 0) { return; } const cloned = items.map(cloneAgentItem); this.logger.debug( `Adding items to memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); this.items = [...this.items, ...cloned]; }
async popItem(): Promise<AgentInputItem | undefined> { if (this.items.length === 0) { return undefined; } const item = this.items[this.items.length - 1]; const cloned = cloneAgentItem(item); this.logger.debug( `Popping item from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); this.items = this.items.slice(0, -1); return cloned; }
async clearSession(): Promise<void> { this.logger.debug(`Clearing memory session (${this.sessionId})`); this.items = []; }}
function cloneAgentItem<T extends AgentInputItem>(item: T): T { return structuredClone(item);}
const agent = new Agent({ name: 'MemoryDemo', instructions: 'Remember the running total.',});
// Using the above custom memory session implementation hereconst session = new CustomMemorySession({ sessionId: 'session-123-4567',});
const first = await run(agent, 'Add 3 to the total.', { session });console.log(first.finalOutput);
const second = await run(agent, 'Add 4 more.', { session });console.log(second.finalOutput);自定义会话可用于实施保留策略、添加加密,或在持久化之前为每轮对话附加元数据。
原子且幂等的自定义持久化
Section titled “原子且幂等的自定义持久化”对于能够以原子方式更新历史记录并记录操作标识符的后端,请实现可选的 SessionHistoryTransactionAwareSession 接口。其 applyHistoryTransaction(...) 方法接收稳定的 operationId,以及以下两种事务结构之一:
append_items追加一组历史记录项。replace_suffix仅当当前存储的后缀仍然匹配时,才替换预期的历史记录后缀。
请在同一个后端事务中持久化操作标识符和历史记录变更。使用相同事务重复提交同一操作标识符时,必须成功且不能重复应用事务。使用不同内容复用该标识符,或在预期后缀发生变化后应用 replace_suffix,都必须失败且不能更改历史记录。这样,运行器便能在重试和可恢复的输出护栏流程中安全地协调已持久化的输出。
MemorySession 实现了此契约,可作为参考。现有的 Session 实现即使不实现此接口也仍然有效,但不会启用事务感知的持久化路径。
使用运行上下文界定自定义会话范围
Section titled “使用运行上下文界定自定义会话范围”如果自定义会话需要使用活动的 RunContext 进行存储路由或添加元数据,请实现 RunContextAwareSession<TContext>,并将 acceptsRunContext 设置为 true。运行器会在一次运行期间的所有历史记录操作中传入同一个上下文实例,包括流式持久化和恢复运行。仅实现 Session 的会话会保留现有方法签名,并在没有额外参数的情况下被调用。
import { Agent, run, type AgentInputItem, type RunContext, type RunContextAwareSession,} from '@openai/agents';
type TenantContext = { tenantId: string;};
class TenantSession implements RunContextAwareSession<TenantContext> { readonly acceptsRunContext = true; private readonly itemsByTenant = new Map<string, AgentInputItem[]>();
async getSessionId(): Promise<string> { return 'shared-tenant-session'; }
async getItems( limit?: number, runContext?: RunContext<TenantContext>, ): Promise<AgentInputItem[]> { const items = this.getTenantItems(runContext); return limit === undefined ? [...items] : items.slice(-limit); }
async addItems( items: AgentInputItem[], runContext?: RunContext<TenantContext>, ): Promise<void> { this.getTenantItems(runContext).push(...items); }
async popItem( runContext?: RunContext<TenantContext>, ): Promise<AgentInputItem | undefined> { return this.getTenantItems(runContext).pop(); }
async clearSession(runContext?: RunContext<TenantContext>): Promise<void> { this.itemsByTenant.set(this.getTenantId(runContext), []); }
private getTenantItems( runContext: RunContext<TenantContext> | undefined, ): AgentInputItem[] { const tenantId = this.getTenantId(runContext); const items = this.itemsByTenant.get(tenantId) ?? []; this.itemsByTenant.set(tenantId, items); return items; }
private getTenantId( runContext: RunContext<TenantContext> | undefined, ): string { if (!runContext) { throw new Error('TenantSession requires a run context.'); } return runContext.context.tenantId; }}
const agent = new Agent<TenantContext>({ name: 'Assistant', instructions: 'Reply concisely.',});const session = new TenantSession();
await run(agent, 'Remember that my favorite color is green.', { context: { tenantId: 'tenant-a' }, session,});
await run(agent, 'What is my favorite color?', { context: { tenantId: 'tenant-a' }, session,});OpenAIResponsesCompactionSession 不会将运行上下文转发给其底层会话。如果需要结合这些能力,请为每个上下文作用域分别使用一个压缩会话实例。
历史记录与新项目的合并控制
Section titled “历史记录与新项目的合并控制”将 AgentInputItem 数组作为运行输入传入时,请提供 sessionInputCallback,以确定性的方式将其与存储的历史记录合并。运行器会加载现有历史记录,在调用模型之前调用您的回调,并将回调返回的数组作为本轮的完整输入传递给模型。此钩子非常适合用于裁剪旧项目、对工具结果去重,或仅突出显示您希望模型看到的上下文。
import { Agent, OpenAIConversationsSession, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents-core';
const agent = new Agent({ name: 'Planner', instructions: 'Track outstanding tasks before responding.',});
// Any Session implementation can be passed here; customize storage as needed.const session = new OpenAIConversationsSession();
const todoUpdate: AgentInputItem[] = [ { type: 'message', role: 'user', content: [ { type: 'input_text', text: 'Add booking a hotel to my todo list.' }, ], },];
await run(agent, todoUpdate, { session, // function that combines session history with new input items before the model call sessionInputCallback: (history, newItems) => { const recentHistory = history.slice(-8); return [...recentHistory, ...newItems]; },});对于字符串输入,运行器会自动合并历史记录,因此回调是可选的。仅当本轮输入已经是项目数组时,才会运行回调。
如果您还使用了 conversationId 或 previousResponseId,请确保回调结果中至少保留当前轮次的一个新项目。这些由服务器管理的 API 依赖当前轮次的增量。如果回调丢弃了所有新项目,SDK 会恢复原始的新输入并记录警告,而不是发送空增量。
审批与可恢复运行的处理
Section titled “审批与可恢复运行的处理”人机协作流程通常会暂停运行以等待审批:
import { Agent, MemorySession, Runner } from '@openai/agents';
const agent = new Agent({ name: 'Trip Planner', instructions: 'Plan trips and ask for approval before booking anything.',});
const runner = new Runner();const session = new MemorySession();
const result = await runner.run(agent, 'Search the itinerary', { session,});
if (result.interruptions?.length) { // ... collect user feedback, then resume the agent in a later turn. for (const interruption of result.interruptions) { result.state.approve(interruption); }
const continuation = await runner.run(agent, result.state, { session }); console.log(continuation.finalOutput);}从先前的 RunState 恢复时,新一轮内容会追加到同一份记忆记录中,以保留单一、连续的对话历史记录。大多数人机协作(HITL)审批检查点会通过 RunState 往返传递,同时由会话维护完整的对话历史记录。
当已批准的函数工具结果可能成为最终输出时,输出护栏会施加更严格的边界。如果 SDK 能够证明当前响应中生成项目的确切归属,当前 RunState 模式就会记录该归属,从而允许受支持且包含输出的审批检查点恢复运行,同时保持会话历史记录完整。在执行后续的模型、工具或会话副作用之前,SDK 会验证该归属。缺少归属信息、归属无效或归属存在歧义的旧快照和检查点仍会以关闭方式失败。当 SDK 报告此情况时,请使用安全输入开始新的运行;不要通过重放原始项目来绕过此限制。
高级功能:历史记录压缩
Section titled “高级功能:历史记录压缩”OpenAI Responses 历史记录的自动压缩
Section titled “OpenAI Responses 历史记录的自动压缩”OpenAIResponsesCompactionSession 可包装任意 Session,并使用 OpenAI Responses API 将较长的已存储历史记录替换为更短但等效的对话项列表。每次持久化一轮对话后,运行器都会将最新的 responseId 传给 runCompaction;当您的决策钩子返回 true 时,它会调用 responses.compact。根据 compactionMode,请求会基于最新的 Responses API 链或会话的当前项目构建。默认触发器会在累计至少 10 个非用户项目后执行压缩;您可以覆盖 shouldTriggerCompaction,改为根据 token 数量或自定义启发式规则作出决策。压缩返回后,包装器会清空底层会话,并使用精简后的项目列表重写会话。因此,请避免将其与 OpenAIConversationsSession 配合使用,因为后者使用的是不同的服务器管理历史记录流程。
import { Agent, MemorySession, OpenAIResponsesCompactionSession, run,} from '@openai/agents';
const agent = new Agent({ name: 'Support', instructions: 'Answer briefly and keep track of prior context.', model: 'gpt-5.4',});
// Wrap any Session to trigger responses.compact once history grows beyond your threshold.const session = new OpenAIResponsesCompactionSession({ // You can pass any Session implementation except OpenAIConversationsSession underlyingSession: new MemorySession(), // (optional) The model used for calling responses.compact API model: 'gpt-5.4', // (optional) your custom logic here shouldTriggerCompaction: ({ compactionCandidateItems }) => { return compactionCandidateItems.length >= 12; },});
await run(agent, 'Summarize order #8472 in one sentence.', { session });await run(agent, 'Remind me of the shipping address.', { session });
// Compaction runs automatically after each persisted turn. You can also force it manually.await session.runCompaction({ force: true });OpenAIResponsesCompactionSession 构造函数选项:
| 选项 | 类型 | 说明 |
|---|---|---|
client | OpenAI | 用于 responses.compact 的 OpenAI 客户端。 |
underlyingSession | Session | 用于清空并使用压缩项目重写的底层会话存储。演示场景下默认为内存会话,且不能是 OpenAIConversationsSession。 |
model | OpenAI.ResponsesModel | 用于压缩请求的模型。默认为 SDK 当前的默认 OpenAI 模型。 |
compactionMode | 'auto' | 'previous_response_id' | 'input' | 控制压缩使用服务器响应链还是本地输入项目。 |
shouldTriggerCompaction | (context) => boolean | Promise<boolean> | 基于 responseId、compactionMode、候选项目和当前会话项目的自定义触发钩子。 |
如果您已经使用 Responses API 响应 ID 串联各轮对话,compactionMode: 'previous_response_id' 会很有用。compactionMode: 'input' 则会根据当前会话项目重新构建压缩请求,适用于响应链不可用,或您希望以底层会话内容为事实来源的情况。
runCompaction(args) 选项:
| 选项 | 类型 | 说明 |
|---|---|---|
responseId | string | previous_response_id 模式下最新的 Responses API 响应 ID。 |
compactionMode | 'auto' | 'previous_response_id' | 'input' | 可选的单次调用模式覆盖项,用于覆盖已配置的模式。 |
store | boolean | 指示上次运行是否存储了服务器状态。 |
force | boolean | 跳过 shouldTriggerCompaction 并立即执行压缩。 |
OpenAIResponsesCompactionSession 会将通过同一个包装器实例发起的变更串行化。对 runCompaction()、addItems()、popItem() 和 clearSession() 的调用会按照调用顺序运行;即使某项操作被拒绝,队列也会继续处理,因此后续包装器变更不会与替换或回滚操作交错执行。成功的替换或回滚会使包装器缓存的历史记录与底层会话保持一致。如果替换和恢复均失败,请先恢复底层会话,再继续操作。此顺序保证无法协调其他包装器实例或对 underlyingSession 的直接变更;请在应用中协调这些访问路径。
自动压缩还会验证本次运行是否仍然拥有它所读取的历史记录快照。如果其他操作在自动压缩开始前更改了包装器的历史记录,SDK 会保留更新后的历史记录,并跳过此次压缩尝试。后续运行或显式调用 runCompaction() 时,可以压缩更新后的历史记录。
低延迟流式传输的手动压缩
Section titled “低延迟流式传输的手动压缩”压缩会清空并重写底层会话,因此 SDK 会等待压缩完成后才结束流式运行。如果压缩任务较重,最后一个输出 token 发出后,result.completed 可能仍会保持待定数秒。若要实现低延迟流式传输或更快开始下一轮对话,请禁用自动压缩,并在不同轮次之间(或空闲时)自行调用 runCompaction。
import { Agent, MemorySession, OpenAIResponsesCompactionSession, run,} from '@openai/agents';
const agent = new Agent({ name: 'Support', instructions: 'Answer briefly and keep track of prior context.', model: 'gpt-5.4',});
// Disable auto-compaction to avoid delaying stream completion.const session = new OpenAIResponsesCompactionSession({ underlyingSession: new MemorySession(), shouldTriggerCompaction: () => false,});
const result = await run(agent, 'Share the latest ticket update.', { session, stream: true,});
// Wait for the streaming run to finish before compacting.await result.completed;
// Choose force based on your own thresholds or heuristics, between turns or during idle time.await session.runCompaction({ force: true });您可以随时调用 runCompaction({ force: true }),在归档或交接之前缩减历史记录。使用 DEBUG=openai-agents:openai:compaction 启用调试日志,以追踪压缩决策。