实用指南:使用实时 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。