实用指南:使用实时 API 处理数据密集型应用

本指南旨在为 AI 工程师提供一份实用的操作手册,帮助他们最大限度地发挥 OpenAI 实时 API 的效能,尤其是在处理数据密集型函数调用时。我们将重点关注语音到语音代理中常见的场景,这些场景需要顺畅高效地处理大量数据。

本文不会涵盖设置实时 API 解决方案的基础知识。相反,您将获得清晰的见解和可行的策略,以增强实时对话代理的性能和可靠性。它解决了在实时对话环境中处理大量数据所带来的独特挑战。

什么是实时 API?

在我们深入探讨之前,先快速回顾一下 API,以帮助新用户了解。OpenAI 实时 API 是一个较新的产品,支持低延迟的多模态交互,例如语音到语音对话和实时字幕。可以想象一下实时语音客户支持或电影实时字幕等场景。

什么是数据密集型函数调用?

代理需要访问工具和相关数据来执行其任务。例如,金融分析师代理可能会提取实时市场数据。在许多情况下,您的环境中已存在通过 API 公开此信息的服务。

从历史上看,API 的设计并非为了代理而考虑,并且通常会根据服务返回大量数据。作为工程师,我们经常使用函数调用来包装这些 API,以加速代理开发——这非常合理。既然已有现成的解决方案,为何还要重复造轮子?

如果不进行仔细优化,这些数据密集型函数调用会迅速压垮实时 API,导致响应缓慢甚至无法处理用户请求。

搭建场景

我们的示例围绕一个 NBA 球探代理,该代理调用多个函数来提供对即将到来的选秀新秀的深入分析。为了演示实时 API 交互的实用指南,我们使用了受 NBA 选秀新秀启发的、大型且真实的载荷。下面,您将看到在实时会话中定义的、单一的 searchDraftProspects 函数,以搭建场景。

// "嘿,查一下 2025 年选秀前十的控球后卫"
{
  "type": "session.update",
  "session": {
    "tools": [
      {
        "type": "function",
        "name": "searchDraftProspects",
        "description": "按年份搜索选秀新秀,例如,控球后卫",
        "parameters": {
          "type": "object",
          "properties": {
            "sign": {
              "type": "string",
              "description": "球员位置",
              "enum": [
                "Point Guard",
                "Shooting Guard",
                "Small Forward",
                "Power Forward",
                "Center",
                "Any"
              ]
            },
            year: { type: "number", description: "选秀年份,例如 2025" },
            mockDraftRanking: { type: "number", description: "预测选秀排名" },
          },
          "required": ["position", "year"]
        }
      }
    ],
    "tool_choice": "auto",
  }
}

searchDraftProspects 函数调用返回一个庞大的载荷。该示例的结构和大小均源自我们遇到的真实场景。

// 示例载荷
{
  "status": {
    "code": 200,
    "message": "SUCCESS"
  },
  "found": 4274,
  "offset": 0,
  "limit": 10,
  "data": [
    {
      "prospectId": 10001,
      "data": {
        "ProspectInfo": {
          "league": "NCAA",
          "collegeId": 301,
          "isDraftEligible": true,
          "Player": {
            "personalDetails": {
              "firstName": "Jalen",
              "lastName": "Storm",
              "dateOfBirth": "2003-01-15",
              "nationality": "USA"
            },
            "physicalAttributes": {
              "position": "PG",
              "height": {
                "feet": 6,
                "inches": 4
              },
              "weightPounds": 205
            },
            "hometown": {
              "city": "Springfield",
              "state": "IL"
            }
          },
          "TeamInfo": {
            "collegeTeam": "Springfield Tigers",
            "conference": "Big West",
            "teamRanking": 12,
            "coach": {
              "coachId": 987,
              "coachName": "Marcus Reed",
              "experienceYears": 10
            }
          }
        },
        "Stats": {
          "season": "2025",
          "gamesPlayed": 32,
          "minutesPerGame": 34.5,
          "shooting": {
            "FieldGoalPercentage": 47.2,
            "ThreePointPercentage": 39.1,
            "FreeThrowPercentage": 85.6
          },
          "averages": {
            "points": 21.3,
            "rebounds": 4.1,
            "assists": 6.8,
            "steals": 1.7,
            "blocks": 0.3
          }
        },
        "Scouting": {
          "evaluations": {
            "strengths": ["Court vision", "Clutch shooting"],
            "areasForImprovement": ["Defensive consistency"]
          },
          "scouts": [
            {
              "scoutId": 501,
              "name": "Greg Hamilton",
              "organization": "National Scouting Bureau"
            }
          ]
        },
        "DraftProjection": {
          "mockDraftRanking": 5,
          "lotteryPickProbability": 88,
          "historicalComparisons": [
            {
              "player": "Chris Paul",
              "similarityPercentage": 85
            }
          ]
        },
        "Media": {
          "highlightReelUrl": "https://example.com/highlights/jalen-storm",
          "socialMedia": {
            "twitter": "@jstorm23",
            "instagram": "@jstorm23_ig"
          }
        },
        "Agent": {
          "agentName": "Rick Allen",
          "agency": "Elite Sports Management",
          "contact": {
            "email": "rallen@elitesports.com",
            "phone": "555-123-4567"
          }
        }
      }
    },
    // ... 许多数千个 token 之后。
  ]
}

指导原则

1. 将庞大的函数分解为具有清晰角色和职责的小型函数

不言而喻——在构建函数调用时,您的首要任务是设计清晰、定义明确的函数。这使得修剪响应大小和避免压垮模型变得容易。每个函数调用都应易于解释,范围明确,并且只返回其目的所需的信息。函数之间重叠的职责不可避免地会引起混淆。

例如,我们可以将 searchDraftProspects 函数调用限制为仅返回每个新秀的通用详细信息(例如球员统计数据),从而大大减小响应大小。如果需要更多信息,新的 getProspectDetails 函数调用将提供扩展的详细信息。没有通用的解决方案;正确的方法取决于您的用例和数据模型。

{
  "tools": [
    {
      "type": "function",
      "name": "searchDraftProspects",
      "description": "按位置、选秀年份和预测排名搜索 NBA 选秀新秀,仅返回通用统计数据以优化响应大小。",
      "parameters": {
        "type": "object",
        "properties": {
          "position": {
            "type": "string",
            "description": "球员的篮球位置。",
            "enum": [
              "Point Guard",
              "Shooting Guard",
              "Small Forward",
              "Power Forward",
              "Center",
              "Any"
            ]
          },
          "year": {
            "type": "number",
            "description": "选秀年份,例如 2025"
          },
          "maxMockDraftRanking": {
            "type": "number",
            "description": "最大预测选秀排名(例如,前十名)"
          }
        },
        "required": ["position", "year"]
      }
    },
    {
      "type": "function",
      "name": "getProspectDetails",
      "description": "获取特定 NBA 新秀的详细信息,包括全面的统计数据、经纪人详细信息和球探报告。",
      "parameters": {
        "type": "object",
        "properties": {
          "playerName": {
            "type": "string",
            "description": "新秀的全名(例如,Jalen Storm)"
          },
          "year": {
            "type": "number",
            "description": "选秀年份,例如 2025"
          },
          "includeAgentInfo": {
            "type": "boolean",
            "description": "包含经纪人信息"
          },
          "includeStats": {
            "type": "boolean",
            "description": "包含详细的球员统计数据"
          },
          "includeScoutingReport": {
            "type": "boolean",
            "description": "包含球探报告详细信息"
          }
        },
        "required": ["playerName", "year"]
      }
    }
  ],
  "tool_choice": "auto"
}

2. 随着对话的进行,优化上下文

实时对话允许长达 30 分钟的会话——但滚动上下文窗口仅支持约 16,000 个 token(取决于模型快照,上下文窗口限制正在改进)。因此,您可能会注意到在长时间的交流中性能逐渐下降。随着对话的进行和更多函数调用的执行,对话状态会迅速扩展,其中包含重要信息和不必要的噪音——因此,专注于保留最相关的信息非常重要。这种方法有助于保持强大的性能并降低成本。

i) 定期总结对话状态

在对话进行过程中定期总结对话是减小上下文大小的绝佳方法——从而降低成本和延迟。

请参阅 Minhajul 关于在实时对话中实现自动摘要的精彩指南(链接)。

ii) 定期提醒模型其角色和职责

数据量大的载荷会迅速填满上下文窗口。如果您注意到模型丢失了指令或可用工具的跟踪,请通过调用 session.update 定期提醒它其系统提示和工具——这可以使其专注于其角色和职责。

3. 数据处理和优化

i) 在函数调用中使用过滤,将数据量大的响应精简为回答问题所需的唯一必需字段

通常,函数调用返回的 token 越少,响应质量越好。当函数调用返回跨越数千个 token 的过大载荷时,就会出现常见陷阱。专注于在每个函数调用中应用过滤器,无论是在数据级别还是函数级别,以最小化响应大小。

// 过滤后的响应
{
  "status": {
    "code": 200,
    "message": "SUCCESS"
  },
  "found": 4274,
  "offset": 0,
  "limit": 5,
  "data": [
    {
    "zpid": 7972122,
      "data": {
        "PropertyInfo": {
            "houseNumber": "19661",
            "directionPrefix": "N ",
            "streetName": "Central",
            "streetSuffix": "Ave",
            "city": "Phoenix",
            "state": "AZ",
            "postalCode": "85024",
            "zipPlusFour": "1641"
            "bedroomCount": 2,
            "bathroomCount": 2,
            "storyCount": 1,
            "livingAreaSize": 1089,
            "livingAreaSizeUnits": "Square Feet",
            "yearBuilt": "1985"
          }
            }
            }
        ]
        // ... 
}

ii) 展平分层载荷——同时不丢失关键信息

API 调用返回的分层载荷有时可能包含重复的级别标题——例如“ProspectInfo”或“Stats”——这可能会增加额外的噪音,并使模型更难处理。在探索使数据更有效的方法时,您可以尝试通过删除一些不必要的标签来展平这些结构。这有助于提高性能,但请考虑对您的特定用例而言哪些信息是重要的。

// 展平后的载荷
{
  "status": {
    "code": 200,
    "message": "SUCCESS"
  },
  "found": 4274,
  "offset": 0,
  "limit": 2,
  "data": [
    {
      "prospectId": 10001,
      "league": "NCAA",
      "collegeId": 301,
      "isDraftEligible": true,
      "firstName": "Jalen",
      "lastName": "Storm",
      "position": "PG",
      "heightFeet": 6,
      "heightInches": 4,
      "weightPounds": 205,
      "hometown": "Springfield",
      "state": "IL",
      "collegeTeam": "Springfield Tigers",
      "conference": "Big West",
      "teamRanking": 12,
      "coachId": 987,
      "coachName": "Marcus Reed",
      "gamesPlayed": 32,
      "minutesPerGame": 34.5,
      "FieldGoalPercentage": 47.2,
      "ThreePointPercentage": 39.1,
      "FreeThrowPercentage": 85.6,
      "averagePoints": 21.3,
      "averageRebounds": 4.1,
      "averageAssists": 6.8,
      "stealsPerGame": 1.7,
      "blocksPerGame": 0.3,
      "strengths": ["Court vision", "Clutch shooting"],
      "areasForImprovement": ["Defensive consistency"],
      "mockDraftRanking": 5,
      "lotteryPickProbability": 88,
      "highlightReelUrl": "https://example.com/highlights/jalen-storm",
      "agentName": "Rick Allen",
      "agency": "Elite Sports Management",
      "contactEmail": "rallen@elitesports.com"
    },
        ...
 }
 ```

**iii) 尝试不同的数据格式**

您组织数据的方式直接影响模型处理和总结 API 响应的效果。根据我们的经验,像 JSON 或 YAML 这样清晰的、基于键的格式比 Markdown 等表格格式更能帮助模型准确地解释数据。特别是大型表格往往会压垮模型——导致输出不够流畅和准确。不过,尝试不同的格式以找到最适合您用例的方法是值得的。

```yaml
status:
  code: 200
  message: "SUCCESS"
found: 4274
offset: 0
limit: 10
data:

  - prospectId: 10001
    data:
      ProspectInfo:
        league: "NCAA"
        collegeId: 301
        isDraftEligible: true
        Player:
          firstName: "Jalen"
          lastName: "Storm"
          position: "PG"
          heightFeet: 6
          heightInches: 4
          weightPounds: 205
          hometown: "Springfield"
          state: "IL"
        TeamInfo:
          collegeTeam: "Springfield Tigers"
          conference: "Big West"
          teamRanking: 12
          coachId: 987
          coachName: "Marcus Reed"
      Stats:
        gamesPlayed: 32
        minutesPerGame: 34.5
        FieldGoalPercentage: 47.2
        ThreePointPercentage: 39.1
        FreeThrowPercentage: 85.6
        averagePoints: 21.3
        averageRebounds: 4.1
        averageAssists: 6.8
        stealsPerGame: 1.7
        blocksPerGame: 0.3
      Scouting:
        strengths:

          - "Court vision"
          - "Clutch shooting"
        areasForImprovement:

          - "Defensive consistency"
      DraftProjection:
        mockDraftRanking: 5
        lotteryPickProbability: 88
      Media:
        highlightReelUrl: "https://example.com/highlights/jalen-storm"
      Agent:
        agentName: "Rick Allen"
        agency: "Elite Sports Management"
        contactEmail: "rallen@elitesports.com"

4. 在数据量大的函数调用后,跟进提示性提示

底层模型通常难以从数据量大的响应顺利过渡到准确的答案。为了提高处理复杂数据时的流畅性和准确性,请在函数调用后立即提供一个函数调用提示。这些提示会指导模型完成特定任务——教会它如何解释关键字段和特定领域的数值。

以下示例说明了一个有效的提示。

// 函数调用提示
let prospectSearchPrompt = `
解析 NBA 新秀数据,并提供简洁、引人入胜的回复。

总体指南

- 扮演 NBA 球探专家的角色。
- 突出关键优势和显著属性。
- 使用对话式语言。
- 提及相同的属性一次。
- 忽略 ID 和 URL。

球员详情

- 以对话方式说明身高(例如“六英尺八英寸”)。
- 将体重四舍五入到最接近的 5 磅。

统计数据和选秀信息

- 将统计数据四舍五入到最接近的整数。
- 使用通用术语表示选秀排名(例如“前五顺位”)。
经验

- 将球员称为大一新生、大二学生等,或提及职业经验。
- 地点和球队提及家乡城市/国家。
- 以对话方式描述球队。

跳过(除非明确要求)

- 确切的出生日期
- ID
- 经纪人/联系方式
- URL

示例

- “Jalen Storm,一位来自伊利诺伊州斯普林菲尔德、身高六英尺四英寸的动态控球后卫,场均得到 21 分。”
- “他以关键时刻的投篮能力而闻名,预计将成为前五顺位选秀。”

重要提示:严格根据提供的数据进行回复,不要编造细节。
`;

在实践中,我们首先将函数调用结果附加到对话中。然后,我们从实时 API 发出一个包含提示的响应。瞧——模型能够顺利处理所有信息。

// 为模型添加新的对话项
const conversationItem = {
  type: 'conversation.item.create',
  previous_item_id: output.id,
  item: {
    call_id: output.call_id,
    type: 'function_call_output',
    output: `Draft Prospect Search Results: ${result}`
  }
};

dataChannel.send(JSON.stringify(conversationItem));

// 发出包含提示的模型的响应
const event = {
  type: 'response.create',
  conversation: "none",
  response: {
    instructions: prospectSearchPrompt # 函数调用提示
  }
};

dataChannel.send(JSON.stringify(event));

总结

使用实时 API 构建有效的代理是一个持续探索和适应的过程。

关键建议摘要

  • 过滤数据: 只包含与用户请求或模型下一步直接相关的字段和详细信息。其余的进行修剪。
  • 展平并简化结构: 减少深度嵌套或重复的数据。以一种便于模型和人类扫描的方式呈现信息。
  • 优先选择清晰、结构化的格式: 使用具有一致字段名称和最少噪音的 JSON(或 YAML)。避免使用大型表格或 Markdown 来处理数据量大的响应。
  • 使用提示性提示指导模型: 在返回大量数据后,跟进一个有针对性的提示,详细说明模型应提取或总结的内容。

请记住——实验至关重要。实时模型在不断改进,我们将继续分享技巧,帮助您充分利用实时 API。