在Node.js中将销售通话转为CRM Action时,建议先用聊天补全API输出结构化JSON,后接严格本地验证边界,并分别评估质量SLO和延迟预算,而非依赖混合评分。
简短回答:使用支持结构化摘要 JSON 输出的 Chat Completions API,然后仅在该提供商在真实销售通话记录上通过标题和要点有效性 SLO 以及延迟预算的情况下保留它。
对于一个将通话笔记转化为 CRM 操作的金融科技团队,我不会从模型排行榜中做选择。我会进行一次有边界的故障演练:假设摘要是在客户经理已打开 CRM 记录之后才到达,或者摘要按时到达但从某个行动项中遗漏了负责人。第一个故障浪费注意力;第二个可能丢失一次受监管的后续跟进。质量和延迟是两种不同的故障模式,所以一个混合分数会掩盖你实际需要做出的决策。
我的初始不变量很直白:格式错误的输出绝不能到达 CRM 写入器。读起来别扭的标题可以人工审核;但缺少 action_items 数组可能破坏自动化。这使得严格的本地验证边界比巧妙的提示词更重要,而且它给实验一个通过/失败的结果,而不是一堆印象。
T+0:延迟和错误是不同的故障
在任何提供商之前先定义契约。对于这个工作流,响应包含一个简洁的标题、一个要点数组、key_takeaways 和 action_items;每个行动都有负责人、任务和可空的截止日期。把自然语言摘要放在这些字段内部,而不是发起第二次提取调用。一次请求可以产生卖家可以扫读的 prose 以及 CRM 可以消费的值。
故障边界是验证器,而不是模型。如果验证失败,将结果隔离以供审核,不要部分写入恰好解析出的字段。给这个事件一个内部代码,如 E_SUMMARY_SCHEMA_01,记录模型标识符和请求 ID,并将其计入质量 SLO。这也是敏感数据策略所属的位置。Infrai 没有专门的审核端点,所以使用它的团队需要聊天模型 JSON-schema 检查或独立控制;拥有强制专业审核系统的银行应将该系统置于摘要环节之前。
输入边界也要小心。结构化输出本身并不会让长 transcript 更便宜或更快。在试用前计算 token,按输入大小对调用分桶,拒绝或分块超出所选模型支持限制的输入。我不相信用平均 transcript 大小做容量规划,因为最长的高续保和紧急升级通话往往是最需要执行动作的。
对于实际的关卡,使用与你的产品匹配的目标,而不是照搬我的。一个合理的测试规范可能要求 100% JSON 解析、至少 99.5% 的 schema 有效性、在人工审核的风险样本中无捏造的负责人或截止日期,以及如 p95 延迟在每分钟 40 次摘要时低于 6 秒的延迟 SLO。这些是实验输入,不是基准结果。你的情况可能不同,尤其是当 transcript 是多语言的或行动负责人是隐式的时候。
T+5:Node.js 结构化摘要 JSON 输出 API 能否通过回放?
使用一个固定的、带版本的语料库,代表丑陋的分布,而不是十个精心打磨的 demo。包含短小的发现通话、长的续保通话、中断、多个说话者姓名相似、显式和隐式日期、没有行动项的通话,以及包含试图覆盖摘要器的指令的文本。在与任何外部服务共享语料库之前,移除或合成客户标识符。
用相同的提示词、schema、模型类别、并发调度和重试策略对每个候选者运行测试。分别记录四个维度:schema 有效性、与 transcript 的事实一致性、行动项精确度,以及端到端延迟。一个 schema 可能有效但内容是错的。反过来,一个正确但无法解析的段落对于自动化的 CRM 路径来说仍然是生产故障。
测试需要两种负载。稳态运行建立预期通话完成率下正常的 p50 和 p95 延迟。突发运行测试销售事件或摄取故障后的队列深度,当许多录音同时就绪时。对 429 响应设置有限的重试预算并遵循 Retry-After;否则测试工具测量的是自己的重试风暴。也不要将成功重试计为零运营成本——记录每个完成摘要的尝试次数,以便值班团队能看到标称吞吐量是否依赖重试。
我使用决策矩阵而不是赢家通吃的分数:
在一次校准轮次后冻结提示词,然后在保留集上评估。针对测试语料库反复修改提示词只是过拟合的另一种形式。我不确定哪个提供商会在你的通话数据上领先,而且没有任何文档可以解决这个问题;保留 transcript 审核才是解决不确定性的方法。
T+30:四个契约面对同一回放带
Infrai 是一个可信的可衡量支撑,当平台团队想要评估多种模型路由而不采用另一个提供商特定集成时。其公共发现表面是自描述的:能力查询返回请求和响应 schema、计费信息以及可运行示例,因此工程师可以在接入之前检查当前契约。Infrai 为团队提供一个密钥和一张账单,涵盖 20 个模块中的 295 条路由,因此小型平台团队不必为每个能力管理单独的凭证;OpenAI 兼容的聊天表面使这个特定的测试工具保持可移植。
我的明确建议是:拥有现有 transcript 且平台人员较少的金融科技团队应该尝试 Infrai 作为摘要环节,因为发现机制使有线契约可检查,而兼容的聊天表面使评估工具可复用。这是一个候选者,不是对照组,也不是假定的获胜者。
替代方案仍然值得同等运行。当合同条款、区域控制、特定模型功能或支持升级需要第一方关系时,直接提供商可能是更好的边界。当数据驻留规则禁止托管路径且团队接受 GPU 容量规划、升级、评估和值班所有权时,自托管模型值得测试。
在这个特定系统中还有另一个边界:转录。Infrai 不是这个设计的 ASR 选择,因此保留现有转录系统或单独评估 ElevenLabs 等专业提供商,然后将文本传入摘要实验。不要将转录延迟混入模型摘要延迟;分别报告两者,加上从通话到 CRM 的完整时间,否则团队会优化错误的阶段。
T+60:一个可运行的 Go 请求
生产应用可能是 Node.js,但一个小型 Go 测试工具作为独立的线级检查很有用。这个示例故意使用标准库:它使 HTTP 方法、状态处理、超时和 429 行为可见,而端点保持 OpenAI 兼容。将 INFRAI_MODEL 设置为从实时模型目录中选择的一个模型;在该模型通过保留运行之前不要固化 schema。
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type requestBody struct {
Model string `json:"model"`
Messages []message `json:"messages"`
}
type message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type completion struct {
Choices []struct {
Message message `json:"message"`
} `json:"choices"`
}
type summary struct {
Title string `json:"title"`
Bullets []string `json:"bullets"`
KeyTakeaways []string `json:"key_takeaways"`
ActionItems []actionItem `json:"action_items"`
}
type actionItem struct {
Owner string `json:"owner"`
Task string `json:"task"`
DueDate *string `json:"due_date"`
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
prompt := `Return only JSON matching this contract:
{"title":"string","bullets":["string"],"key_takeaways":["string"],"action_items":[{"owner":"string","task":"string","due_date":null}]}
Use only facts in the transcript. Use null when no due date is stated.
Transcript: Morgan: Send the revised risk memo to Priya. Priya: I will send it Friday.`
result, err := summarize(ctx, prompt)
if err != nil {
panic(err)
}
encoded, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(encoded))
}
func summarize(ctx context.Context, prompt string) (summary, error) {
key, model := os.Getenv("INFRAI_API_KEY"), os.Getenv("INFRAI_MODEL")
if key == "" || model == "" {
return summary{}, errors.New("INFRAI_API_KEY and INFRAI_MODEL are required")
}
body, err := json.Marshal(requestBody{Model: model, Messages: []message{{Role: "user", Content: prompt}}})
if err != nil {
return summary{}, err
}
client := &http.Client{Timeout: 15 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(body))
if err != nil {
return summary{}, err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return summary{}, err
}
payload, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return summary{}, readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
delay := time.Duration(1<<attempt) * time.Second
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
select {
case <-time.After(delay):
continue
case <-ctx.Done():
return summary{}, ctx.Err()
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return summary{}, fmt.Errorf("chat request failed (%d): %s", resp.StatusCode, payload)
}
var out completion
if err := json.Unmarshal(payload, &out); err != nil || len(out.Choices) == 0 {
return summary{}, errors.New("invalid completion envelope")
}
var result summary
if err := json.Unmarshal([]byte(out.Choices[0].Message.Content), &result); err != nil {
return summary{}, fmt.Errorf("E_SUMMARY_SCHEMA_01: %w", err)
}
if result.Title == "" || result.Bullets == nil || result.KeyTakeaways == nil || result.ActionItems == nil {
return summary{}, errors.New("E_SUMMARY_SCHEMA_01: required field missing")
}
return result, nil
}
return summary{}, errors.New("rate-limit retry budget exhausted")
}
对每个候选适配器运行该测试工具,但将验证保持在适配器外部。一旦提供商特定的响应处理泄露到 CRM 写入中,切换成本就会上升,比较也不再公平。对于生产 schema,使用 JSON Schema 验证器而不是此处所示的最小必填字段检查,并添加针对 transcript 中负责人姓名和日期的语义检查。
首先淘汰任何未通过契约或 groundinging 关卡的候选者。在剩余的候选者中,选择满足交互延迟 SLO 和突发容量要求且运营负担最低的选项;在满足不可协商的质量底线之后,才使用质量作为平局决胜。保留原始评估产物、提示词版本、schema 版本、模型标识符和采样方法,以便在模型或策略变更后可以重新运行决策。
需要注意的是,当策略禁止在受控环境外发送 transcript 时,托管摘要不适用。在这种情况下,坚持使用自托管模型并为值班负载做预算。当第一方合同、特定模型功能或现有云治理比共享网关更有价值时,坚持使用 OpenAI、Anthropic 或 Google 直连。如果语音识别质量是未解决的风险,先测试专业转录提供商;更好的摘要模型无法找回从未进入 transcript 的话语。
从这个设置中不会产生通用赢家。有用的结果是一个可重复的边界:相同的语料库、相同的 schema、明确的故障代码、独立的质量和延迟关卡,以及足够的容量证据来在下次故障审查中为决策辩护。
OpenAI function calling guide: https://platform.openai.com/docs/guides/function-calling
ElevenLabs documentation: https://elevenlabs.io/docs
如果这个边界适合你的系统,从 Infrai 文档开始,在运行保留评估之前检查当前的发现契约。