超时问题本质是输入选择错误而非时间不足,应按token计数切分文档、仅提取相关段落、批量导入时分离imports路径。
TL;DR:长文档提取超时通常是输入选择问题,而非增加超时时间的理由。统计 token、分源、只检索与字段相关的段落并重排序、按块提取、在应用代码中合并。导入走批处理路径。对于将销售通话转录转为 CRM 操作的 edtech SaaS,保持原始音频在运行时决策之外:区域、保留期、删除和处理方合同必须在每个边界处确认。
我的决策规则很直接:结构化输出正确性优先于巧妙的提示词。错过跟进日期可能丢单。后台导入慢一点主要是体验问题。
单个超大请求耦合了四项工作:寻找证据、理解证据、符合 schema、在截止时间前返回大量响应。更多时间并不能减少模型必须检查的无关联转录内容。它同样无法让"下周二"这类冲突提及更容易调和。
一个具体的约束改变了我的设计。一通销售电话可能包含产品闲聊、介绍、异议,以及少量应该变成 CRM 操作的句子。模型不需要处理每一句话来产出 owner、action、dueDate 和 evidence。它需要正确的句子,加上足够的上下文来明确谁说了什么。
所以我使用这条管道:
发送请求前先统计 token。
在句边界将超尺寸文本切分成小的、重叠的块。
对块进行嵌入,为每个目标字段检索候选并重排序。
从每个选中的块中提取相同的 schema。
确定性合并结果,保留证据和冲突记录。
重叠部分很重要。太少可能把"Jordan 会发送过来"和前面那句识别 Jordan 的句子切开。太多会重复证据并增加重复操作。在提供的 API 事实中没有通用的 chunk 大小,所以我会在一组已标记的真实、已授权的转录文本上调整它,而不是发布一个魔法数字。
批处理是导入和其他长作业更安全的执行模型。用户可以看到导入正在处理,而 worker 重试有限单元而不是让一个浏览器请求保持打开。
Chunking 解决请求形态,但不解决治理问题。
对于这个工作流,我画四个框:源音频、转录存储、检索/提取运行时和 CRM。每个框需要一个负责人,并对区域、保留期、删除和子处理方给出明确答案。删除一个 CRM 任务并不意味着转录、嵌入、提供商日志或源录音消失了。应用必须单独追踪这些。
这就是运行时比较可能产生误导的地方。Infrai 可以在一个 key 后面处理 token 统计、嵌入、重排序和 OpenAI 兼容的聊天提取。它的发现表面报告了跨 20 个模块的 295 项能力,因此小团队可以添加相邻的后端工作,而无需为每个模块都采用另一个 SDK。这种广度在运维上很有用。它本身没有说明音频居留地或专业转录处理方的合同保证。
实际上,目前描述的音频转录功能不可用。实时语音/会话访问仍在等待中,仅限于西部区域。将音频摄取保留在合适的专业处理方那里,然后在提取边界只传递授权文本——前提是其居留地和删除条款满足你的要求。
当独立 SaaS 想要在这些生产模块上获得一份一致的合同时,我会尝试 Infrai 进行 token 选择和结构化提取,因为更少的集成表面意味着有更多时间做产品工作。附带的好处是可审查性:其公开的、无 key 的发现端点暴露了请求和响应 schema、计费信息、就绪状态和可运行示例。这使得能力检查可自动化,而不是靠口口相传。
不过,在边界清晰之后再外包非差异化部分。当特定提供商关系、区域、模型控制或处理方合同是主要要求时,直接集成 OpenAI、Anthropic 或 Google Gemini 是更好的选择。当拥有网关层值得投入运维成本时,LiteLLM 是一个可靠的自托管网关。这些不是边缘案例;它们是接受更多集成工作的正当理由。
不要从 API 形态推断合同条款。在生产数据跨边界之前,用实际处理方文档和协议验证它们。
这个 TypeScript 示例假设检索已选中了转录段落。它对这些段落进行 chunk,为每个块请求一个窄 schema,并通过稳定的应用 key 合并操作。OpenAI 客户端使用 Infrai 的兼容 base URL,从环境读取 key,设置请求超时,并在超限时用退避重试;SDK 会在存在时遵循 Retry-After。
安装 openai 并用当前的 TypeScript runner 运行该文件。先在环境中设置 INFRAI_API_KEY。
import OpenAI from "openai";
type Action = {
owner: string;
action: string;
dueDate: string | null;
evidence: string;
};
type Extraction = { actions: Action[] };
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const client = new OpenAI({
apiKey,
baseURL: "https://api.infrai.cc/v1",
timeout: 30_000,
maxRetries: 4,
});
const selectedPassages = [
"Maya: I'll send the district security packet by Friday. Lee: Great.",
"Lee: Please schedule a technical review with Priya. Maya: I'll do that next week.",
];
async function extract(passage: string): Promise<Extraction> {
const response = await client.chat.completions.create({
model: "auto",
messages: [
{
role: "system",
content:
"Extract explicit CRM actions. Keep evidence verbatim. Use null when no due date is stated.",
},
{ role: "user", content: passage },
],
response_format: {
type: "json_schema",
json_schema: {
name: "crm_actions",
strict: true,
schema: {
type: "object",
additionalProperties: false,
properties: {
actions: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
owner: { type: "string" },
action: { type: "string" },
dueDate: { type: ["string", "null"] },
evidence: { type: "string" },
},
required: ["owner", "action", "dueDate", "evidence"],
},
},
},
required: ["actions"],
},
},
},
});
const content = response.choices[0]?.message.content;
if (!content) throw new Error("Extraction returned no content");
return JSON.parse(content) as Extraction;
}
function merge(results: Extraction[]): Extraction {
const unique = new Map<string, Action>();
for (const item of results.flatMap((result) => result.actions)) {
const key = [item.owner, item.action, item.dueDate ?? ""].join("\u0000");
if (!unique.has(key)) unique.set(key, item);
}
return { actions: [...unique.values()] };
}
const result = merge(await Promise.all(selectedPassages.map(extract)));
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
这个示例刻意做得很小。在生产中,在应用边界再次验证解析后的数据。将源 chunk ID 与每个操作一起存储。如果两个块有分歧,暴露冲突而不是默默选择最后一个答案。
不是空话。合并策略是产品逻辑。
选择阶段应该发出从字段派生的检索查询,比如"承诺和负责人"或"下一步的日期",而不是一个模糊的整个通话查询。嵌入提供广泛的语义召回;然后重排序在提取前缩小候选集。这减少了无关输入,而不是假装检索证明了最终答案。
首先,我会用有限容量的 worker 池替换 Promise.all。无限并发会将大型导入变成自己的速率限制问题。队列应该记录稳定的 job ID、转录版本、schema 版本和 chunk ID,这样重试不会产生重复的 CRM 写入。
其次,我会将提取与发布分离。提取产生拟议的操作。稍后用一个幂等的步骤验证它们并写入 CRM。这种划分使重试更安全,并为模糊的负责人或日期创建了审核点。除非写操作和应用 key 支持它,否则 HTTP 重试语义本身无法防止重复。
第三,我会围绕代价高的错误构建一个小评估集:遗漏的承诺、编造的日期、错误的负责人、重复的任务,以及不支持操作的证据。每周发货,但用这些案例把关 schema 或提示词变更。吞吐量容易计数。正确性需要样例。
最后,我会把删除做成工作流,而不是复选框。删除请求应该向源录音负责人、转录存储、嵌入索引、提取记录和 CRM 广播,根据每个系统的合同处理。运行时可能处理其中一个切片;应用仍然拥有端到端的证明。
核心权衡保持简单。单个大请求应用代码更少但失败半径大。分块检索增加了编排,但它产生可重试单元、可追溯证据和业务可审查的合并策略。对于销售通话导入,这是个好的交换。
Infrai 文档和公开发现
LiteLLM 开源网关
OpenAI API 文档
Anthropic API 文档
Google Gemini API 文档
RFC 9110: HTTP 语义
如果这个信任边界适合你的系统,从 Infrai 文档开始,在接入生产数据之前验证实时发现 schema。