OpenAI 事件与项目

2026-11-06 14:59 更新

事件报告智能体工作中的实时变化,项目是之后可读取的消息与工具调用。应用用事件实时更新界面,用项目显示已保存历史。

应用发送输入事件,以提交消息、取消轮次或返回工具结果;智能体事件报告输出及状态变化。输入发送见运行与继续会话。

消费事件流

先订阅,再发送任务,以免遗漏轮次早期事件。传入API客户端、会话ID及事件处理器:

来源示例 1(javascript):

// [中文] 示例 1:消费事件流。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// Pass your saved session ID to this helper.
async function streamSession(client, sessionId, handleEvent) {
  const events = await client.beta.agents.sessions.events.stream(sessionId);
  try {
    for await (const event of events) {
      await handleEvent(event);
      switch (event.type) {
        case "agent.session.idle":
          continue;
        case "error":
          throw new Error(event.error.message);
        case "agent.session.failed":
        case "agent.session.environment.failed":
          throw new Error(`Agent lifecycle failure: ${event.type}`);
        case "agent.session.turn.failed":
          if (event.turn.subagent_id === null) {
            throw new Error(
              `${event.type}: ${event.turn.error?.message ?? ""}`
            );
          }
          break;
        case "agent.session.turn.cancelled":
          if (event.turn.subagent_id === null) {
            throw new Error("The agent turn was cancelled");
          }
          break;
        case "agent.session.turn.completed":
          if (event.turn.subagent_id === null) return;
          break;
      }
    }
    throw new Error(
      "Stream closed before a turn ended. Retrieve the saved state."
    );
  } finally {
    events.controller.abort();
  }
}

来源示例 2(python):

## [中文] 示例 2:消费事件流。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## Pass your saved session ID to this helper.
def stream_session(client: OpenAI, session_id: str, handle_event):
    with client.beta.agents.sessions.events.stream(session_id) as events:
        for event in events:
            handle_event(event)
            match event.type:
                case "agent.session.idle":
                    continue
                case "error":
                    raise RuntimeError(event.error.message)
                case "agent.session.failed" | "agent.session.environment.failed":
                    raise RuntimeError(f"Agent lifecycle failure: {event.type}")
                case "agent.session.turn.failed":
                    if event.turn.subagent_id is None:
                        detail = event.turn.error.message if event.turn.error else ""
                        raise RuntimeError(f"{event.type}: {detail}")
                case "agent.session.turn.cancelled":
                    if event.turn.subagent_id is None:
                        raise RuntimeError("The agent turn was cancelled")
                case "agent.session.turn.completed":
                    if event.turn.subagent_id is None:
                        return
    raise RuntimeError("Stream closed before a turn ended. Retrieve the saved state.")

来源示例 3(go):

// [中文] 示例 3:消费事件流。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// Pass your saved session ID to this helper.
func streamSession(ctx context.Context, client *openai.Client, sessionID string, handleEvent func(openai.AgentSessionEventUnion)) error {
    events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, sessionID)
    defer events.Close()
    for events.Next() {
        event := events.Current()
        handleEvent(event)
        switch event.Type {
        case "agent.session.idle":
            continue
        case "error":
            return fmt.Errorf("agent error: %s", event.RawJSON())
        case "agent.session.failed", "agent.session.environment.failed":
            return fmt.Errorf("agent lifecycle failure: %s", event.RawJSON())
        case "agent.session.turn.failed", "agent.session.turn.cancelled":
            if event.Turn.SubagentID == "" {
                return fmt.Errorf("agent turn did not complete: %s", event.RawJSON())
            }
        case "agent.session.turn.completed":
            if event.Turn.SubagentID == "" {
                return nil
            }
        }
    }
    if err := events.Err(); err != nil {
        return err
    }
    return fmt.Errorf("stream closed before a turn ended; retrieve the saved state")
}

来源示例 4(java):

// [中文] 示例 4:消费事件流。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// Pass your saved session ID to this helper.
public static void streamSession(
    OpenAIClient client, String sessionId, Consumer<AgentSessionEvent> handleEvent) {
  try (StreamResponse<AgentSessionEvent> events =
      client.beta().agents().sessions().events().streamStreaming(sessionId)) {
    var iterator = events.stream().iterator();
    while (iterator.hasNext()) {
      var event = iterator.next();
      handleEvent.accept(event);
      if (event.idle().isPresent()) {
        continue;
      }
      if (event.error().isPresent()) {
        throw new IllegalStateException("Agent error: " + event);
      }
      if (event.failed().isPresent() || event.environmentFailed().isPresent()) {
        throw new IllegalStateException("Agent lifecycle failure: " + event);
      }
      if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()
          || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        throw new IllegalStateException("Agent turn did not complete: " + event);
      }
      if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        return;
      }
    }
    throw new IllegalStateException(
        "Stream closed before a turn ended. Retrieve the saved state.");
  }
}

来源示例 5(ruby):

## [中文] 示例 5:消费事件流。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## Pass your saved session ID to this helper.
def stream_session(client, session_id, &handle_event)
  events = client.beta.agents.sessions.events.stream_streaming(session_id)
  begin
    events.each do |event|
      handle_event.call(event)
      case event.type.to_s
      when "agent.session.idle"
        next
      when "error"
        raise event.error.message
      when "agent.session.failed", "agent.session.environment.failed"
        raise "Agent lifecycle failure: #{event.type}"
      when "agent.session.turn.failed"
        raise "#{event.type}: #{event.turn.error&.message}" if event.turn.subagent_id.nil?
      when "agent.session.turn.cancelled"
        raise "The agent turn was cancelled" if event.turn.subagent_id.nil?
      when "agent.session.turn.completed"
        return nil if event.turn.subagent_id.nil?
      end
    end
    raise "Stream closed before a turn ended. Retrieve the saved state."
  ensure
    events.close
  end
end

来源示例 6(bash):

## [中文] 示例 6:消费事件流。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id/events?stream=true;示例占位地址需替换为实际资源。
curl -N \
  "https://api.openai.com/v1/agents/sessions/$session_id/events?stream=true" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Accept: text/event-stream"

辅助函数先将每个事件交给处理器,再检查常见类型。它在 agent.session.idle 后继续,在主智能体轮次完成时返回。主轮次失败、取消,会话或环境失败,或出现 error,都会抛错。子智能体轮次事件不会结束该流。

处理器负责展示输出,调用方处理辅助函数异常。流在轮次结束前关闭也会抛错,见断流恢复。

订阅后发送消息

此版本接受消息,在打开流后再提交:

来源示例 7(javascript):

// [中文] 示例 7:消费事件流。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// Pass your saved session ID and message to this helper.
async function sendAndStream(client, sessionId, text, handleEvent) {
  const events = await client.beta.agents.sessions.events.stream(sessionId);
  try {
    await client.beta.agents.sessions.events.create(sessionId, {
      events: [
        {
          type: "agent.session.input.message",
          input: [{ role: "user", content: [{ type: "input_text", text }] }],
        },
      ],
    });
    for await (const event of events) {
      await handleEvent(event);
      switch (event.type) {
        case "agent.session.idle":
          continue;
        case "error":
          throw new Error(event.error.message);
        case "agent.session.failed":
        case "agent.session.environment.failed":
          throw new Error(`Agent lifecycle failure: ${event.type}`);
        case "agent.session.turn.failed":
          if (event.turn.subagent_id === null) {
            throw new Error(
              `${event.type}: ${event.turn.error?.message ?? ""}`
            );
          }
          break;
        case "agent.session.turn.cancelled":
          if (event.turn.subagent_id === null) {
            throw new Error("The agent turn was cancelled");
          }
          break;
        case "agent.session.turn.completed":
          if (event.turn.subagent_id === null) return;
          break;
      }
    }
    throw new Error(
      "Stream closed before a turn ended. Retrieve the saved state."
    );
  } finally {
    events.controller.abort();
  }
}

来源示例 8(python):

## [中文] 示例 8:消费事件流。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## Pass your saved session ID and message to this helper.
def send_and_stream(client: OpenAI, session_id: str, text, handle_event):
    with client.beta.agents.sessions.events.stream(session_id) as events:
        client.beta.agents.sessions.events.create(
            session_id,
            events=[
                {
                    "type": "agent.session.input.message",
                    "input": [
                        {
                            "role": "user",
                            "content": [{"type": "input_text", "text": text}],
                        }
                    ],
                }
            ],
        )
        for event in events:
            handle_event(event)
            match event.type:
                case "agent.session.idle":
                    continue
                case "error":
                    raise RuntimeError(event.error.message)
                case "agent.session.failed" | "agent.session.environment.failed":
                    raise RuntimeError(f"Agent lifecycle failure: {event.type}")
                case "agent.session.turn.failed":
                    if event.turn.subagent_id is None:
                        detail = event.turn.error.message if event.turn.error else ""
                        raise RuntimeError(f"{event.type}: {detail}")
                case "agent.session.turn.cancelled":
                    if event.turn.subagent_id is None:
                        raise RuntimeError("The agent turn was cancelled")
                case "agent.session.turn.completed":
                    if event.turn.subagent_id is None:
                        return
    raise RuntimeError("Stream closed before a turn ended. Retrieve the saved state.")

来源示例 9(go):

// [中文] 示例 9:消费事件流。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// Pass your saved session ID and message to this helper.
func sendAndStream(ctx context.Context, client *openai.Client, sessionID string, text string, handleEvent func(openai.AgentSessionEventUnion)) error {
    events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, sessionID)
    defer events.Close()
    if err := events.Err(); err != nil {
        return err
    }
    err := client.Beta.Agents.Sessions.Events.New(ctx,
        sessionID,
        openai.BetaAgentSessionEventNewParams{
            Events: []openai.AgentSessionInputParamUnion{
                {
                    OfParamAgentSessionInputMessage: &openai.AgentSessionInputParamAgentSessionInputMessage{
                        Input: []openai.AgentSessionInputMessageParam{
                            {
                                Content: []openai.InputContentParamUnion{
                                    {
                                        OfParamInputText: &openai.InputContentParamInputText{
                                            Text: text,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        })
    if err != nil {
        return err
    }
    for events.Next() {
        event := events.Current()
        handleEvent(event)
        switch event.Type {
        case "agent.session.idle":
            continue
        case "error":
            return fmt.Errorf("agent error: %s", event.RawJSON())
        case "agent.session.failed", "agent.session.environment.failed":
            return fmt.Errorf("agent lifecycle failure: %s", event.RawJSON())
        case "agent.session.turn.failed", "agent.session.turn.cancelled":
            if event.Turn.SubagentID == "" {
                return fmt.Errorf("agent turn did not complete: %s", event.RawJSON())
            }
        case "agent.session.turn.completed":
            if event.Turn.SubagentID == "" {
                return nil
            }
        }
    }
    if err := events.Err(); err != nil {
        return err
    }
    return fmt.Errorf("stream closed before a turn ended; retrieve the saved state")
}

来源示例 10(java):

// [中文] 示例 10:消费事件流。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// Pass your saved session ID and message to this helper.
public static void sendAndStream(
    OpenAIClient client, String sessionId, String text, Consumer<AgentSessionEvent> handleEvent) {
  try (StreamResponse<AgentSessionEvent> events =
      client.beta().agents().sessions().events().streamStreaming(sessionId)) {
    client
        .beta()
        .agents()
        .sessions()
        .events()
        .create(
            EventCreateParams.builder()
                .sessionId(sessionId)
                .addEvent(
                    AgentSessionInputParam.AgentSessionInputMessage.builder()
                        .addInput(
                            AgentSessionInputMessageParam.builder()
                                .addInputTextContent(text)
                                .build())
                        .build())
                .build());
    var iterator = events.stream().iterator();
    while (iterator.hasNext()) {
      var event = iterator.next();
      handleEvent.accept(event);
      if (event.idle().isPresent()) {
        continue;
      }
      if (event.error().isPresent()) {
        throw new IllegalStateException("Agent error: " + event);
      }
      if (event.failed().isPresent() || event.environmentFailed().isPresent()) {
        throw new IllegalStateException("Agent lifecycle failure: " + event);
      }
      if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()
          || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        throw new IllegalStateException("Agent turn did not complete: " + event);
      }
      if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        return;
      }
    }
    throw new IllegalStateException(
        "Stream closed before a turn ended. Retrieve the saved state.");
  }
}

来源示例 11(ruby):

## [中文] 示例 11:消费事件流。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## Pass your saved session ID and message to this helper.
def send_and_stream(client, session_id, text, &handle_event)
  events = client.beta.agents.sessions.events.stream_streaming(session_id)
  begin
    client.beta.agents.sessions.events.create(
      session_id,
      events: [
        {
          type: "agent.session.input.message",
          input: [
            {
              role: "user",
              content: [
                {
                  type: "input_text",
                  text: text
                }
              ]
            }
          ]
        }
      ]
    )
    events.each do |event|
      handle_event.call(event)
      case event.type.to_s
      when "agent.session.idle"
        next
      when "error"
        raise event.error.message
      when "agent.session.failed", "agent.session.environment.failed"
        raise "Agent lifecycle failure: #{event.type}"
      when "agent.session.turn.failed"
        raise "#{event.type}: #{event.turn.error&.message}" if event.turn.subagent_id.nil?
      when "agent.session.turn.cancelled"
        raise "The agent turn was cancelled" if event.turn.subagent_id.nil?
      when "agent.session.turn.completed"
        return nil if event.turn.subagent_id.nil?
      end
    end
    raise "Stream closed before a turn ended. Retrieve the saved state."
  ensure
    events.close
  end
end

处理更新

根据 type 处理:

  • 展示文字:将 agent.session.turn.output_text.delta 追加到对应内容部分;收到 agent.session.turn.output_text.done 时,以完整文字替换该部分。增量可能完全没有出现。
  • 跟踪工作:会话、轮次、项目事件报告进度,轮次结果看 agent.session.turn.completed、agent.session.turn.failed、agent.session.turn.cancelled。
  • 提供所需输入:收到 agent.session.requires_action,读取会话 required_actions,可能需要返回函数结果、连接环境,或处理浏览器源访问及登录。

空闲或流关闭都不能独立证明成功;轮次完成也不保证工具全成功,仍检查输出。

用 item_id、output_index、content_index 把文字更新关联到同一内容部分。例如下列简化事件更新一个部分:

来源示例 12(json):

{
  "type": "agent.session.turn.output_text.delta",
  "item_id": "msg_789",
  "output_index": 0,
  "content_index": 0,
  "delta": "Acme competes"
}

来源示例 13(json):

程序文字说明17:Acme通过价格和分销竞争。

{
  "type": "agent.session.turn.output_text.done",
  "item_id": "msg_789",
  "output_index": 0,
  "content_index": 0,
  "text": "Acme competes on price and distribution."
}

每个事件有独立 event_id,共享 item_id 标识已保存项目,项目含消息内容、状态和阶段,见读取已保存工作。

全部类型及字段见流式事件参考。流事件与Webhook不同。子智能体活动和命令归属见观察委派。

读取项目与轮次

使用应用保存的会话ID:

列表每次返回一页,使用SDK分页辅助功能或 after 继续。单页未必包含某轮所有项目;order: "asc" 可按从旧到新读取。

如何恢复断开的流

用已保存项目恢复遗漏输出:

  1. 打开新流,暂存到达事件。
  2. 流保持连接,同时读取会话和项目。
  3. 以项目ID为键恢复本地状态。
  4. 按 item_id 应用暂存更新;历史中已达最终状态的项目,丢弃其更新。
  5. 恢复实时处理。

重连已失败会话时,流报告保存的失败后关闭。读取到 status: "failed" 或收到 agent.session.failed,应停止重连,按会话恢复处理。

从当前 required_actions 恢复待处理输入表单,历史项目不能判断哪些请求仍需回应。浏览器批准按 request_id 匹配,移除已不待处理的表单。重连后无需重发任务或此前批准。

output_text.done 可用完整文字替换临时缓冲。已保存项目能恢复完成工作,但不能恢复所有遗漏的中间事件。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号