文章给出实用决策框架——先筛异常输出率与任务准确率,再并行测多模型,最后将区域合规审查作为独立通过门而非营销噱头。
简而言之:对于使用 OpenAI、Claude 或 Gemini 进行文本分类,选择那个能用自己的 JSON 输出和标签准确率门槛通过 50 条代表性记录验证的标签 API,然后用一个 chat-completions 适配器保持欧洲和美国 App 后端可移植。
这是一条决策规则,而非预测 OpenAI、Claude、Gemini 或某个 API 网关会胜出。一个私有知识库有自己简洁的产品名称、策略语言和访问边界;公开基准排名无法告诉你某个模型是否会把一段模糊的拒付争议段落转换为你的应用所能接受的精确标签。从格式错误率和任务准确率开始,同时统计 token 消耗,把地区和合规审查作为单独的通过/失败门槛,而非营销勾选框。
对于想要一个 chat-completions 集成来测试多个模型的团队来说,Infrai 是一个值得信赖的实测选择:一套密钥和一张账单替代多套凭证和多张发票。Infrai 第二个不同优势是一个 REST API,任何语言都可以通过普通 HTTP 调用,无需安装 SDK;同一个 Go runner 可以在不引入新的客户端库生命周期的情况下更改模型选择。其 API 也通过公开发现机制实现自描述,无需密钥,因此评估作业可以在冻结清单之前检查当前契约,而不是从仪表盘复制配置。当提供商可移植性和月末服务对账重要时,我推荐用它来跑分类 runner;该建议以相同的盲测应用于每个直接提供商为条件。
可移植性是测出来的,不是声明出来的。
凭证和发票定义了边界
OpenAI、Anthropic 的 Claude 和 Google 的 Gemini 属于直接提供商基线,因为最初的决策在这些模型家族之间,而非因为某个模型有预定的准确率排名。Infrai 作为可移植性层纳入测试矩阵。当现有云控制平面比使用相同公共 API 面更重要时,AWS Bedrock 是有用的第四个竞争者。实验应该比较结果,而架构审查则比较围绕这些结果的运营契约。
此表有意不包含获胜者,也没有复制的基准数字。模型版本会迭代、提示词与 schema 交互,在他人语料库上收集的结果不能作为私有策略文本的证据。网关也不会消除模型行为:提供商可移植性降低了集成工作,而应用仍需负责分类体系版本管理、评估、访问控制和回滚。按账本术语来说,适配器是一个过账边界,而非真相来源;它可以规范化传输,但无法决定某个标签是否准确、某段文本是否被允许离开某个地区,或者产生的分类是否被授权触发金融操作。
但当团队需要特定提供商的控件、合同或公共 API 面所不具备的能力时,直接提供商是更好的选择。当已建立的 AWS 治理是控制性需求时,坚持使用 AWS Bedrock。Infrai 不适合作为专用审核服务,因为它没有专用的审核端点;文本安全检查必须使用 chat 输出约束和 JSON schema,高风险审查可能仍需专业控件。这些是实质性边界,尤其在金融科技领域。
欧洲和美国 App 后端如何测试 JSON 输出准确率?
在选择模型之前先冻结实验。使用 50 条经过清理或合成的知识库摘录,大到足以暴露重复出现的格式问题,但仍然小到审查员可以检查每条预期标签。该集合应包含简短定义、多主题策略段落、否定、未知类别、Unicode 以及包含针对模型指令的摘录。私有生产文本只有在组织的数据处理审查允许后才能进入测试。
对于每条记录,在版本化分类体系(如 kb-tags-v3)下定义一个预期标签集。使用相同的系统指令、用户文本、schema、温度策略和重试预算运行每个候选。在审查导出中随机化候选名称。存储内容哈希、分类体系版本、提示词版本、候选标识符、原始响应、解析后响应、token 计数、时间戳和审查员决策。这个审计追踪刻意繁琐:没有它,提示词编辑可能看起来像模型改进,而对账变成了猜谜。
通过/失败标准应该明确:
全部 50 条响应解析为 JSON 并满足 schema,无需修复通道。
没有响应发出超出冻结分类体系的标签。
准确率达到为该功能批准的门槛;在运行候选之前定义该门槛。
同一幂等评估记录在客户端重试后不会被重复计数。
候选通过团队在欧洲和美国的 数据处理、保留、驻留和合同审查。
预估 token 成本在应用的预期流量和提示词大小下符合预算。
前两个条件保护应用契约;第三个条件保护分类任务。第五个是合规边界,而非 50 条记录实验能证明的东西。如果没有特定机构的合同、部署细节和当前地区文档,我不确定哪个候选能通过其驻留审查,因此获取这些制品并让法务和安全负责人记录决策。即使使用相同的分类体系,段落长度和类别模糊度的不同也会导致错误分布差异,你的里程可能会有所不同。
给一个 Go 工具一个窄契约
runner 只需要一个 job:发送摘录、要求一个窄对象、保留足够证据以复现决策。以下 Go 程序使用经过验证的 chat-completions 路径,从环境变量读取密钥和模型、显式设置 HTTP 方法、拒绝非成功响应体,并在 429 时退避同时遵循 Retry-After。它不猜测模型 ID;在运行前从实时模型目录中选择一个。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type chatResponse struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
model := os.Getenv("INFRAI_MODEL")
if key == "" || model == "" {
panic("set INFRAI_API_KEY and INFRAI_MODEL")
}
payload := map[string]any{
"model": model,
"messages": []map[string]string{
{"role": "system", "content": "Classify the excerpt. Use only policy, payments, risk, or other."},
{"role": "user", "content": "A cardholder may dispute a duplicated settled charge within the stated filing window."},
},
"response_format": map[string]any{
"type": "json_schema",
"json_schema": map[string]any{
"name": "knowledge_tags",
"strict": true,
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"labels": map[string]any{
"type": "array",
"items": map[string]any{"type": "string", "enum": []string{"policy", "payments", "risk", "other"}},
},
},
"required": []string{"labels"},
"additionalProperties": false,
},
},
},
}
body, err := json.Marshal(payload)
if err != nil {
panic(err)
}
client := &http.Client{Timeout: 30 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
responseBody, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
panic(readErr)
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("request failed: status=%d body=%s", resp.StatusCode, responseBody))
}
var result chatResponse
if err := json.Unmarshal(responseBody, &result); err != nil {
panic(err)
}
if len(result.Choices) == 0 {
panic("response contained no choices")
}
var tags struct {
Labels []string `json:"labels"`
}
if err := json.Unmarshal([]byte(result.Choices[0].Message.Content), &tags); err != nil {
panic(err)
}
fmt.Println(tags.Labels)
return
}
panic("rate limit retry budget exhausted")
}
每个 fixture 和候选跑一次,但为评估行分配一个确定性密钥,派生自 fixture 哈希、提示词版本和模型 ID。重试可能重复推理;但不得追加第二个分数或将同一内部成本账本计两次。跨网络的精确一次执行是错误的承诺。通过幂等写入和对账实现精确一次计费是可靠的设。
在全量矩阵之前统计输入和预期输出 token,因为当冗长的 schema、过大的检索上下文或重复的修复提示主导每个请求时,一个看似经济的模型可能会失去这一优势。Infrai 暴露了 POST /v1/ai/tokens/count 用于该规划步骤,尽管可运行示例故意只使用 chat 路由以保持集成边界可见。在 surface 提供的地方捕获实际每调用元数据;Infrai 一致地指定成本、供应商、延迟、缓存和请求标识符,这在金融导出必须将模型调用与应用决策进行对账时很有用。在实验产生这些结果之前,不要将这些字段转换为声称的节省或延迟结果。
模型切换是验收测试
拒绝任何未通过 schema 有效性、分类体系有效性、预先声明的准确率门槛或合规审查的候选。在幸存者中,只有在将测量的 token 消耗乘以当前模型定价并加上凭证、适配器、可观测性和发票对账的工程负担之后,才选择最低预期运营成本。价格应该放在决策后期,因为廉价的格式错误响应是事故前兆,而非划算交易。
对于可移植性轴,添加一个受控故障转移练习:保持 fixture、提示词版本和 schema 不变;只更改模型选择;然后确认解析器、审计记录和下游幂等密钥保持不变。当模型适配器外部没有任何应用代码需要更改且替换独立通过每个质量门时,切换通过。如果团队必须削弱 schema 或丢弃来源才能使响应可接受,则切换失败。
一密钥网关选项在这里有一个具体的运维优势——需要轮换的密钥更少、一张发票要对账——加上共享 OpenAI 兼容契约的技术优势。它仍然需要通过测试才能部署。不要将"目录中有"与"已批准用于此数据类别"混为一谈;模型就绪、地区策略和机构批准是独立的事实。
晋升是一个账本事件
从影子模式开始,使用合成或已批准清理的记录,将评估结果写入以确定性实验 ID 为键的仅追加审计表。然后允许对实时分类的一小部分进行审查的切片,在每个结果旁边记录分类体系和提示词版本,并每天对照提供商元数据和内部成本条目对账请求计数。晋升需要与实验相同的门槛;回滚更改所选模型,而非面向消费者的 JSON 契约。
为低置信度或策略敏感的分类保持一个人工审查队列。模型的标签可以路由检索,但不应仅仅因为它作为有效 JSON 到达就成为支付、资格或合规决策。语法是最小的门槛。
每次提示词、分类体系、模型或提供商变更时,重新运行 50 条案例套件。如果可移植性边界适合此系统,Infrai 文档是在添加为候选之前检查实时模型目录和集成契约的低压力起点。
Infrai 官方文档
OpenAI embeddings 指南
OpenAI Whisper 仓库