跳转到内容

会话

会话为 Agents SDK 提供了一个持久化记忆层。将任何实现 Session 接口的对象提供给 Runner.run,其余工作由 SDK 处理。存在会话时,运行器会自动:

  1. 获取之前存储的对话项目,并将其添加到下一轮输入之前。
  2. 每次运行完成后,持久化新的用户输入和助手输出。
  3. 保留会话以供后续轮次使用,无论您是使用新的用户文本调用运行器,还是从中断的 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 为您管理客户端侧记忆,请使用会话。如果您已经通过 conversationIdpreviousResponseId 使用由 OpenAI 服务器管理的状态,通常无需再为同一对话历史记录使用会话。


使用 OpenAIConversationsSessionConversations API 同步记忆,也可以换用任何其他 Session 实现。

将 Conversations API 用作会话记忆
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:

使用 MemorySession 管理本地状态
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 构造函数选项:

选项类型说明
conversationIdstring复用现有对话,而不是在需要时才创建对话。
clientOpenAI传入预配置的 OpenAI 客户端。
apiKeystring创建内部 OpenAI 客户端时使用的 API 密钥。
baseURLstringOpenAI 兼容端点的基础 URL。
organizationstring请求使用的 OpenAI 组织 ID。
projectstring请求使用的 OpenAI 项目 ID。

MemorySession 构造函数选项:

选项类型说明
sessionIdstring用于日志或测试的稳定标识符。默认自动生成。
initialItemsAgentInputItem[]使用现有历史记录初始化会话。
loggerLogger覆盖用于调试输出的日志记录器。

MemorySession 将所有内容存储在本地进程内存中,因此进程退出时会被重置。

如果需要在构造会话之前预先创建对话 ID,请使用 startOpenAIConversationsSession(client?),并将返回的 ID 作为 conversationId 传入。


  • 每次运行前,运行器会检索会话历史记录,将其与新一轮的输入合并,然后把合并后的列表传给智能体。
  • 非流式运行结束后,运行器通过一次 session.addItems() 调用,同时持久化原始用户输入和最新一轮的模型输出。
  • 对于流式运行,运行器会先写入用户输入,并在该轮完成后追加流式输出。
  • RunResult.state 恢复运行时(例如在审批或其他中断后),请继续传入同一个 session。恢复后的轮次会直接添加到记忆中,而无需重新准备输入。

当输出护栏拒绝最终的函数工具结果时,运行器不会将被拒绝的原始结果添加到新的会话历史记录中。对于已识别的函数调用及结果组合,运行器会持久化一个可安全重放的占位项;对于归属无法确认的当前响应后缀,则会将其省略,而不会存储。此保护机制不会撤销外部工具的副作用,也不会清除应用在会话管道之外存储的数据。


会话提供简单的 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() 可移除最后一个条目,这适用于在重新运行智能体之前更正用户输入。


实现 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 here
const 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 不会将运行上下文转发给底层会话。如果要组合使用这些功能,请为每个上下文作用域分别使用一个压缩会话实例。


AgentInputItem 数组作为运行输入传入时,可以提供 sessionInputCallback,以确定性的方式将其与已存储的历史记录合并。运行器会加载现有历史记录,在调用模型之前调用您的回调,并将返回的数组作为该轮的完整输入传给模型。此钩子非常适合用于裁剪旧项目、对工具结果去重,或仅突出显示您希望模型查看的上下文。

使用 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];
},
});

对于字符串输入,运行器会自动合并历史记录,因此回调是可选的。只有当本轮输入已经是项目数组时,才会运行该回调。

如果您还使用了 conversationIdpreviousResponseId,请在回调结果中至少保留当前轮次的一个新项目。这些由服务器管理的 API 依赖当前轮次的增量。如果回调移除了所有新项目,SDK 会恢复原始的新输入并记录警告,而不是发送空增量。


人机协作流程通常会暂停运行以等待审批:

使用同一会话恢复运行
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 会采用失败关闭策略,在产生后续模型、工具或会话副作用之前停止。因此,当附加会话时,某些包含输出的审批检查点无法从序列化状态恢复。SDK 报告此情况时,请使用安全输入开始新的运行;不要通过重放被拒绝的原始项目来绕过此限制。


OpenAI Responses 历史记录的自动压缩

Section titled “OpenAI Responses 历史记录的自动压缩”

OpenAIResponsesCompactionSession 可以装饰任何 Session,并使用 OpenAI Responses API 将较长的已存储历史记录替换为含义等效但更短的对话项目列表。每一轮持久化后,运行器都会将最新的 responseId 传给 runCompaction;当您的决策钩子返回 true 时,该方法会调用 responses.compact。根据 compactionMode,请求会基于最新的 Responses API 链或会话的当前项目构建。默认触发条件是在至少累积 10 个非用户项目后进行压缩;您可以覆盖 shouldTriggerCompaction,改为根据 token 数量或自定义启发式规则做出决策。压缩返回后,装饰器会清空底层会话,并使用缩减后的项目列表重写该会话。因此,请避免将其与 OpenAIConversationsSession 组合使用,因为后者采用不同的服务器端历史记录管理流程。

使用 OpenAIResponsesCompactionSession 装饰会话
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 构造函数选项:

选项类型说明
clientOpenAI用于 responses.compact 的 OpenAI 客户端。
underlyingSessionSession用于清空并使用压缩项目重写的底层会话存储。对于演示,默认为内存会话,且不得为 OpenAIConversationsSession
modelOpenAI.ResponsesModel用于压缩请求的模型。默认为 SDK 当前的默认 OpenAI 模型。
compactionMode'auto' | 'previous_response_id' | 'input'控制压缩使用服务器响应链还是本地输入项目。
shouldTriggerCompaction(context) => boolean | Promise<boolean>自定义触发钩子,根据 responseIdcompactionMode、候选项目和当前会话项目做出决策。

当您已经使用 Responses API 响应 ID 串联多个轮次时,compactionMode: 'previous_response_id' 很有用。compactionMode: 'input' 则会基于当前会话项目重新构建压缩请求,适用于响应链不可用,或希望以底层会话内容作为事实来源的情况。

runCompaction(args) 选项:

选项类型说明
responseIdstring用于 previous_response_id 模式的最新 Responses API 响应 ID。
compactionMode'auto' | 'previous_response_id' | 'input'可选的单次调用模式覆盖设置。
storeboolean表示上一次运行是否存储了服务器状态。
forceboolean绕过 shouldTriggerCompaction 并立即执行压缩。

OpenAIResponsesCompactionSession 会串行处理通过同一个包装器实例发出的变更。对 runCompaction()addItems()popItem()clearSession() 的调用会按调用顺序运行;即使某个操作被拒绝,队列仍会继续处理,因此后续包装器变更不会与替换或回滚操作交错。替换或回滚成功后,包装器缓存的历史记录会与底层会话保持一致。如果替换和恢复均失败,请先恢复底层会话再继续。此顺序保证不会协调其他包装器实例或对 underlyingSession 的直接变更;请在应用中协调这些访问路径。

压缩操作会清空并重写底层会话,因此 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 启用调试日志,以追踪压缩决策。