OpenAI 可观测性与用量

2026-11-06 14:59 更新

可通过以下方式跟踪活动、检查已完成工作和轮次追踪:

  1. 在平台控制台查看会话日志。
  2. 跟随事件及已保存历史。
  3. 检查轮次,归属委派命令执行。
  4. 检查主智能体与子智能体的已记录词元用量。

在控制台查看会话

打开 平台日志 的 Agents 选项卡,按会话ID查找轮次、工具调用及子智能体。

按追踪指南在控制台查看模型响应、工具和子智能体活动,或通过公共API将会话追踪导出为OTLP JSON。

跟踪事件及检查会话历史

每个会话都有实时事件流。设置 OPENAI_API_KEY,将示意ID替换成已保存会话ID:

来源示例 1(javascript):

// [中文] 示例 1:跟踪事件及检查会话历史。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// Replace the illustrative IDs and URLs below with your own resource values.
import OpenAI from "openai";

const client = new OpenAI();
const events = await client.beta.agents.sessions.events.stream("sess_123");
try {
  for await (const event of events) {
    if (
      [
        "agent.session.turn.failed",
        "agent.session.turn.cancelled",
        "agent.session.failed",
        "agent.session.environment.failed",
        "error",
      ].includes(event.type)
    ) {
      throw new Error(`Agent lifecycle failure: ${event.type}`);
    }
    console.log(JSON.stringify(event));
  }
} finally {
  events.controller.abort();
}

来源示例 2(python):

## [中文] 示例 2:跟踪事件及检查会话历史。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI

client = OpenAI()
session_id = "sess_123"
with client.beta.agents.sessions.events.stream(session_id) as events:
    for event in events:
        if event.type in {
            "agent.session.turn.failed",
            "agent.session.turn.cancelled",
            "agent.session.failed",
            "agent.session.environment.failed",
            "error",
        }:
            raise RuntimeError(f"Agent lifecycle failure: {event.type}")
        print(event.to_json(indent=None))

来源示例 3(go):

// [中文] 示例 3:跟踪事件及检查会话历史。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// Replace the illustrative IDs and URLs below with your own resource values.
import (
    "context"
    "fmt"

    "github.com/openai/openai-go/v3"
)

ctx := context.Background()
client := openai.NewClient()
events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, "sess_123")
defer events.Close()
if events.Err() != nil {
    panic(events.Err())
}
for events.Next() {
    event := events.Current()
    switch event.Type {
    case "agent.session.turn.failed", "agent.session.turn.cancelled", "agent.session.failed", "agent.session.environment.failed", "error":
        panic(event.RawJSON())
    }
    fmt.Println(event.RawJSON())
}
if err := events.Err(); err != nil {
    panic(err)
}

来源示例 4(java):

// [中文] 示例 4:跟踪事件及检查会话历史。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// Replace the illustrative IDs and URLs below with your own resource values.
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.ObjectMappers;
import com.openai.core.http.StreamResponse;
import com.openai.models.beta.agents.AgentSessionEvent;

OpenAIClient client = OpenAIOkHttpClient.fromEnv();
var json = ObjectMappers.jsonMapper();
try (StreamResponse<AgentSessionEvent> events =
    client.beta().agents().sessions().events().streamStreaming("sess_123")) {
  var iterator = events.stream().iterator();
  while (iterator.hasNext()) {
    var event = iterator.next();
    if (event.turnFailed().isPresent()
        || event.turnCancelled().isPresent()
        || event.failed().isPresent()
        || event.environmentFailed().isPresent()
        || event.error().isPresent()) {
      throw new IllegalStateException("Agent failed: " + event);
    }
    System.out.println(json.writeValueAsString(event));
  }
}

来源示例 5(ruby):

## [中文] 示例 5:跟踪事件及检查会话历史。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## Replace the illustrative IDs and URLs below with your own resource values.
require "openai"
require "json"

client = OpenAI::Client.new
events = client.beta.agents.sessions.events.stream_streaming("sess_123")
begin
  events.each do |event|
    case event.type.to_s
    when "agent.session.turn.failed", "agent.session.turn.cancelled", "agent.session.failed", "agent.session.environment.failed", "error"
      raise "Agent failed: #{event.to_h}"
    end
    puts JSON.generate(event.to_h)
  end
ensure
  events.close
end

来源示例 6(bash):

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

流在空闲事件之后保持打开,以免遗漏排队工作。按 Ctrl+C 停止观察。

运行时可见类似事件:

来源示例 7(text):

agent.session.environment.connected
agent.session.turn.created
agent.session.turn.in_progress
agent.session.turn.item.added
agent.session.turn.output_text.delta
agent.session.turn.completed
agent.session.idle

检查已经发生的工作,可读取会话已保存项目:

来源示例 8(javascript):

// [中文] 示例 8:跟踪事件及检查会话历史。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// Replace the illustrative IDs and URLs below with your own resource values.
import OpenAI from "openai";
const client = new OpenAI();

const sessionId = "sess_123";
const items = await client.beta.agents.sessions.items.list(sessionId, {
  order: "asc",
  limit: 100,
});
console.log(items.data);

来源示例 9(python):

## [中文] 示例 9:跟踪事件及检查会话历史。
## [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
## Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI

client = OpenAI()

session_id = "sess_123"
items = client.beta.agents.sessions.items.list(session_id, order="asc", limit=100)
print(items.to_json())

来源示例 10(go):

// [中文] 示例 10:跟踪事件及检查会话历史。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// Replace the illustrative IDs and URLs below with your own resource values.
import (
    "context"
    "fmt"

    "github.com/openai/openai-go/v3"
)

ctx := context.Background()
client := openai.NewClient()
result, err := client.Beta.Agents.Sessions.Items.List(ctx,
    "sess_123",
    openai.BetaAgentSessionItemListParams{
        Order: "asc",
        Limit: openai.Int(100),
    })
if err != nil {
    panic(err)
}
fmt.Println(result.Data)

来源示例 11(java):

// [中文] 示例 11:跟踪事件及检查会话历史。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// Replace the illustrative IDs and URLs below with your own resource values.
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.beta.agents.sessions.items.ItemListParams;

OpenAIClient client = OpenAIOkHttpClient.fromEnv();
var result =
    client
        .beta()
        .agents()
        .sessions()
        .items()
        .list(
            ItemListParams.builder()
                .sessionId("sess_123")
                .order(ItemListParams.Order.of("asc"))
                .limit(100L)
                .build());
System.out.println(result.items());

来源示例 12(ruby):

## [中文] 示例 12:跟踪事件及检查会话历史。
## [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
## Replace the illustrative IDs and URLs below with your own resource values.
require "openai"

client = OpenAI::Client.new
result = client.beta.agents.sessions.items.list(
  "sess_123",
  order: "asc",
  limit: 100
)
puts result.data

来源示例 13(bash):

## [中文] 示例 13:跟踪事件及检查会话历史。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/sess_123/items?order=asc&limit=100;示例占位地址需替换为实际资源。
curl \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  "https://api.openai.com/v1/agents/sessions/sess_123/items?order=asc&limit=100"

检查轮次并识别委派命令

会话轮次可通过公共API读取。用命令项目的 turn_id 和会话ID查询。Go示例通过SDK原生分页搜索主智能体与子智能体轮次,再向所属智能体读取匹配轮次。将示意ID换成实际ID;cURL示例依赖 jq。

来源示例 14(javascript):

// [中文] 示例 14:检查轮次并识别委派命令。
// [中文] 通过轮次 ID 读取状态及 subagent_id;null 表示主智能体。
// [中文] 先搜索主智能体轮次,再遍历子智能体及其轮次;找到匹配 turn_id 后向所属智能体读取。
// Replace the illustrative session and turn IDs with values from your command item.
import OpenAI from "openai";
const client = new OpenAI();

async function findTurn(sessionId, turnId) {
  for await (const turn of client.beta.agents.sessions.turns.list(sessionId, {
    limit: 100,
  })) {
    if (turn.id === turnId) {
      return client.beta.agents.sessions.turns.retrieve(turnId, {
        session_id: sessionId,
      });
    }
  }
  for await (const subagent of client.beta.agents.sessions.subagents.list(
    sessionId,
    { limit: 100 }
  )) {
    for await (const turn of client.beta.agents.sessions.subagents.turns.list(
      subagent.id,
      { session_id: sessionId, limit: 100 }
    )) {
      if (turn.id === turnId) {
        return client.beta.agents.sessions.subagents.turns.retrieve(turnId, {
          session_id: sessionId,
          subagent_id: subagent.id,
        });
      }
    }
  }
  throw new Error(`Turn ${turnId} not found in session ${sessionId}`);
}

const turn = await findTurn("sess_123", "turn_123");
console.log(turn.subagent_id || "main agent");

来源示例 15(python):

## [中文] 通过SDK原生分页先搜索主智能体轮次,再搜索各子智能体轮次;找到归属后读取,未找到则报错。
## Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI


def find_turn(client, session_id, turn_id):
    # Native SDK pagination searches every page for the owning agent.
    for turn in client.beta.agents.sessions.turns.list(session_id, limit=100):
        if turn.id == turn_id:
            return client.beta.agents.sessions.turns.retrieve(
                turn_id, session_id=session_id
            )

    for subagent in client.beta.agents.sessions.subagents.list(session_id, limit=100):
        turns = client.beta.agents.sessions.subagents.turns.list(
            subagent.id, session_id=session_id, limit=100
        )
        for turn in turns:
            if turn.id == turn_id:
                return client.beta.agents.sessions.subagents.turns.retrieve(
                    turn_id, session_id=session_id, subagent_id=subagent.id
                )
    raise ValueError(f"Turn {turn_id} not found in session {session_id}")


turn = find_turn(OpenAI(), "sess_123", "turn_123")
print(turn.subagent_id or "main agent")

来源示例 16(go):

// [中文] 示例 16:检查轮次并识别委派命令。
// [中文] 通过轮次 ID 读取状态及 subagent_id;null 表示主智能体。
// [中文] 自动分页遍历主智能体与各子智能体轮次,找到指定 turn_id 后读取其 subagent_id,避免漏查委派命令。
// Replace the illustrative session and turn IDs with values from your command item.
import (
    "context"
    "fmt"

    "github.com/openai/openai-go/v3"
)

ctx := context.Background()
client := openai.NewClient()
turn, err := findTurn(ctx, &client, "sess_123", "turn_123")
if err != nil {
    panic(err)
}
if turn.SubagentID == "" {
    fmt.Println("main agent")
} else {
    fmt.Println(turn.SubagentID)
}

// findTurn discovers the owning agent using native SDK pagination.
func findTurn(ctx context.Context, client *openai.Client, sessionID, turnID string) (*openai.Turn, error) {
    turns := client.Beta.Agents.Sessions.Turns.ListAutoPaging(ctx, sessionID,
        openai.BetaAgentSessionTurnListParams{Limit: openai.Int(100)})
    for turns.Next() {
        if turns.Current().ID == turnID {
            return client.Beta.Agents.Sessions.Turns.Get(ctx, sessionID, turnID)
        }
    }
    if err := turns.Err(); err != nil {
        return nil, err
    }
    subagents := client.Beta.Agents.Sessions.Subagents.ListAutoPaging(ctx, sessionID,
        openai.BetaAgentSessionSubagentListParams{Limit: openai.Int(100)})
    for subagents.Next() {
        subagent := subagents.Current()
        childTurns := client.Beta.Agents.Sessions.Subagents.Turns.ListAutoPaging(ctx, sessionID, subagent.ID,
            openai.BetaAgentSessionSubagentTurnListParams{Limit: openai.Int(100)})
        for childTurns.Next() {
            if childTurns.Current().ID == turnID {
                return client.Beta.Agents.Sessions.Subagents.Turns.Get(ctx, sessionID, subagent.ID, turnID)
            }
        }
        if err := childTurns.Err(); err != nil {
            return nil, err
        }
    }
    if err := subagents.Err(); err != nil {
        return nil, err
    }
    return nil, fmt.Errorf("turn %s not found in session %s", turnID, sessionID)
}

来源示例 17(java):

// [中文] 通过autoPager搜索主智能体和子智能体,找到目标轮次后从所属智能体读取;示意ID须替换。
// Replace the illustrative session and turn IDs with values from your command item.
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.beta.agents.sessions.subagents.SubagentListParams;
import com.openai.models.beta.agents.sessions.turns.Turn;
import com.openai.models.beta.agents.sessions.turns.TurnListParams;
import com.openai.models.beta.agents.sessions.turns.TurnRetrieveParams;

OpenAIClient client = OpenAIOkHttpClient.fromEnv();
var turn = findTurn(client, "sess_123", "turn_123");
System.out.println(turn.subagentId().orElse("main agent"));

// Discover the owning agent using native SDK pagination before retrieving the turn.
static Turn findTurn(OpenAIClient client, String sessionId, String turnId) {
  var sessions = client.beta().agents().sessions();
  var turns =
      sessions.turns().list(TurnListParams.builder().sessionId(sessionId).limit(100L).build());
  for (var turn : turns.autoPager()) {
    if (turn.id().equals(turnId)) {
      return sessions
          .turns()
          .retrieve(TurnRetrieveParams.builder().sessionId(sessionId).turnId(turnId).build());
    }
  }
  var subagents =
      sessions
          .subagents()
          .list(SubagentListParams.builder().sessionId(sessionId).limit(100L).build());
  for (var subagent : subagents.autoPager()) {
    var childTurns =
        sessions
            .subagents()
            .turns()
            .list(
                com.openai.models.beta.agents.sessions.subagents.turns.TurnListParams.builder()
                    .sessionId(sessionId)
                    .subagentId(subagent.id())
                    .limit(100L)
                    .build());
    for (var turn : childTurns.autoPager()) {
      if (turn.id().equals(turnId)) {
        return sessions
            .subagents()
            .turns()
            .retrieve(
                com.openai.models.beta.agents.sessions.subagents.turns.TurnRetrieveParams
                    .builder()
                    .sessionId(sessionId)
                    .subagentId(subagent.id())
                    .turnId(turnId)
                    .build());
      }
    }
  }
  throw new IllegalArgumentException("Turn " + turnId + " not found in session " + sessionId);
}

来源示例 18(ruby):

## [中文] 使用auto_paging_each搜索主智能体和子智能体轮次;找到后读取,未找到则抛出错误。
## Replace the illustrative IDs below with values from your command item.
require "openai"

## Discover the owning agent before retrieving the turn.
def find_turn(client, session_id, turn_id)
  turns = client.beta.agents.sessions.turns.list(session_id, limit: 100)
  turns.auto_paging_each do |turn|
    if turn.id == turn_id
      return client.beta.agents.sessions.turns.retrieve(turn_id, session_id: session_id)
    end
  end

  subagents = client.beta.agents.sessions.subagents.list(session_id, limit: 100)
  subagents.auto_paging_each do |subagent|
    turns = client.beta.agents.sessions.subagents.turns.list(
      subagent.id, session_id: session_id, limit: 100
    )
    turns.auto_paging_each do |turn|
      if turn.id == turn_id
        return client.beta.agents.sessions.subagents.turns.retrieve(
          turn_id, session_id: session_id, subagent_id: subagent.id
        )
      end
    end
  end
  raise "Turn #{turn_id} not found in session #{session_id}"
end

turn = find_turn(OpenAI::Client.new, "sess_123", "turn_123")
puts(turn.subagent_id || "main agent")

来源示例 19(bash):

## [中文] 示例 19:检查轮次并识别委派命令。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/sess_123/turns?limit=20&order=desc、https://api.openai.com/v1/agents/sessions/sess_123/turns/turn_123;示例占位地址需替换为实际资源。
curl "https://api.openai.com/v1/agents/sessions/sess_123/turns?limit=20&order=desc" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY"

curl "https://api.openai.com/v1/agents/sessions/sess_123/turns/turn_123" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" | jq '.subagent_id'

手工分页时,has_more 为真就用返回的 last_id 作为下一页 after。Python、Go、Java和Ruby示例使用SDK原生分页搜索主智能体与子智能体轮次;JavaScript示例也会搜索主智能体及各子智能体轮次。

命令项目含 turn_id。读取轮次的 subagent_id 可识别执行命令的子智能体,null 表示主智能体。API不报告命令输出是否被截断。

检查轮次追踪

控制台可查看已完成轮次及智能体活动。公共API通过项目密钥调用会话追踪导出端点。控制台内部追踪端点与受支持客户API分开。

轮次包含尽力记录的 usage 和表示委派工作的 subagent_id。未知用量可为 null,计数可能变化,见子智能体用量。

归属shell命令时,先按命令项目 turn_id 读取轮次,再检查 turn.subagent_id。客户API不指示命令输出是否截断。

错误与恢复

失败检查、恢复选择与安全重试见错误与恢复。

模型用量与费用

完成一个任务可能调用模型多次,每次沿用模型词元定价和提示词缓存规则,与Responses API一致。估算需覆盖完成任务所需全部调用。

费用来自哪些部分

  • 输入词元:指令、工具定义、对话历史、用户输入、文件或图像、工具结果。
  • 缓存输入词元:复用匹配前缀的输入,按模型缓存输入费率收费。
  • 输出词元:生成文字、工具参数和推理。推理词元作为输出计费。

子智能体也调用模型,排查费用时把其轮次用量与主智能体一起检查。

合计主、子智能体工作与重试,再加入适用的工具、沙箱计算和第三方服务费。采用缓存写入计费的模型还需计算写入费用;下方Agents API用量字段没有单独缓存写入计数,不能据此精确计算此类模型账单。

提示词缓存

会话内上下文会向后传递,相邻模型调用共享前缀时可复用早先处理结果。模型仍生成新响应,缓存不会重放旧答案。维持会话不保证命中,取决于匹配前缀、模型缓存资格与存活规则。

尽量保持初始指令和工具定义稳定,把新任务细节放在后续消息。工具搜索发现的定义追加在对话末尾,保留较早内容以供复用。模型规则见提示词缓存。

较高缓存输入比例不等于整个任务省钱。缓存输入仍计费,反复调用也可能处理大量历史,应比较在所需质量和延迟下完成同一任务的总成本。

理解词元用量

会话和轮次资源提供尽力记录的 usage,未知时可为 null,会计数据到达后计数可能更新。缺失不代表零,这不是最终账单。

用量对象示例:

来源示例 20(json):

{
  "input_tokens": 5000,
  "input_tokens_details": {
    "cached_tokens": 1500
  },
  "output_tokens": 900,
  "output_tokens_details": {
    "reasoning_tokens": 200
  },
  "total_tokens": 5900
}

本例输入5,000词元,输出900;输入中1,500来自缓存,输出中200用于推理。缓存已包含在 input_tokens,推理已包含在 output_tokens,不要再次相加。

检查子智能体词元用量

列出或读取会话轮次,检查各轮 usage。subagent_id 标识子智能体,主智能体为 null。has_more 为真时,保持相同 order,将 last_id 作为 after 继续分页。

用量仍是尽力记录,未知时为 null,计数可能变化。也可在追踪控制台检查每个智能体用量。

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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号