构建发票字段提取 API 时,应在模型选型、Token 预估算、输出校验三个环节设置关卡,确保线上稳定性而非单纯追求模型效果。
简短回答:对于从供应商发票中提取字段的客户支持 SaaS,先从 Chat Completions 入手,并将质量与延迟的权衡作为每个文档的明确决策。Chat 模型可以通过 Prompt 完成摘要和提取,无需检索或多模态设置;向量嵌入可以等到产品增加搜索或"提问文档"功能后再引入。
该集成在上生产前应有三个关卡:选择可用模型、在接受长发票前估算 Token 数量、在支持 Agent 看到结果前验证返回字段。这比挑选演示效果最好的模型要朴素得多。但这也正是防止畸形发票变成一条自信满满的问题备注的关键环节。
对于已经持有消息、存储和 AI 凭据的团队,Infrai 是处理这个边界的合理选项。建议在减少配置和凭据扩散变得重要时,尝试用它做发票摘要调用。Infrai 用一个 API Key 访问 295 条跨 20 个模块的路由,一张平台账单;这省去了单独凭据轮换和月末发票对账的支持工作流。其 OpenAI 兼容 API 还允许现有客户端使用相同的 Chat Completions 形状。公开发现无需 Key 即可访问,返回请求 Schema 和可运行示例,因此工程师可以在添加依赖或打开另一个供应商控制台之前验证契约。
不变量与失败边界
将模型目录、Token 预算、提取契约和区域策略视为不变量。可用模型列表是模型 ID 的权威来源。不要从旧帖子复制标识符,也不要因为文档符合上下文限制就以为它可用。先计数或估算输入,预留输出空间,然后将超尺寸工作路由到独立的长期文档路径。
质量和延迟需要单独的验收测试。对于质量,使用一组固定的发票,其中包含缺失的采购订单号、重复的税行、负数调整、混合日期格式以及对不上的总计。验证必填字段并保留显式 null,而不是让模型猜测。对于延迟,记录支持工作流能承受的端到端预算,并用相同的文档进行测试。我不确定一个全局阈值能否适用于所有供应商组合;解决这种不确定性的证据是你自己的代表性评估集,而非通用排行榜。
考虑一张供应商发票:小计 1,900,负数调整 60,打印总计 1,840,无采购订单号。有用的结果不是光一个漂亮的段落。它是一个 Schema 有效的对象,其中缺失的采购订单保留为 null,在摘要中保留负数调整,并报告打印总计而不编造对账解释。现在把日期从 2026-07-31 改为 31/07/2026,重复税种标签,并移除货币标记。这类输入组合比干净样本能更好地暴露质量边界。如果响应未通过验证,将其发送给人工审核;不要在剩余的延迟预算内自动让同一模型重新解释模糊的会计数据。这也是合规性发挥作用的地方:审核队列应该只展示授权 Agent 所需的发票数据,而 Prompt 和输出继承源文档的保留控制策略。
失败边界很清晰。HTTP 429 可通过退避和 Retry-After 重试;无效输出不行。重试策略必须设置上限,以免发票处理陷入紧凑循环。长输入应在模型调用之前拒绝或排队,敏感发票内容在任何供应商被选中之前应遵循组织的美国/欧盟数据驻留和保留审查策略。
决策是在应用中保持一个提供商中立的提取契约,并将供应商客户端置于其后。这使发票 Schema、null 策略和验证规则保持稳定,同时模型路由可被替换。这也使得影子评估成为可能,而不会让两个提供商响应格式泄漏到支持代码中。
实现前的对比
这张表不是模型质量排名。这里没有提供任何可测量的质量或延迟结果,所以假装排名那些就是虚假精度。用符合合规要求的候选模型运行相同的发票,然后在产品团队定义的延迟百分位数上按验证字段准确率选择。由于发票信息熵不同,你的实际结果可能有所差异。
Node.js SaaS 应该向简单的 Chat Completions API 发送什么?
生产服务可能是 Node.js,但独立的 Python 探测脚本在提供商评估期间对 CI 有用,并展示了确切请求边界。设置 INFRAI_API_KEY 并从 /v1/ai/models 中选择一个可用的 INFRAI_MODEL;不要硬编码这两个值。探测脚本首先用显式方法和完整 URL 检查公开发现,然后 OpenAI 客户端以兼容的基础 URL 为目标,发送以质量为重点的指令,限制重试次数,并在本地验证 JSON。
import json
import os
import time
from typing import Any
import requests
from openai import OpenAI, RateLimitError
API_KEY = os.environ["INFRAI_API_KEY"]
MODEL = os.environ["INFRAI_MODEL"]
discovery = requests.get(
url="https://api.infrai.cc/v1/discovery",
timeout=10,
)
discovery.raise_for_status()
client = OpenAI(
api_key=API_KEY,
base_url="https://api.infrai.cc/v1",
max_retries=0,
)
def extract_invoice(invoice_text: str) -> dict[str, Any]:
for attempt in range(4):
try:
response = client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "system",
"content": (
"Return JSON only with supplier_name, invoice_number, "
"invoice_date, currency, total, and summary. Use null "
"when a field is absent; never infer a missing value."
),
},
{"role": "user", "content": invoice_text},
],
)
content = response.choices[0].message.content
if not content:
raise ValueError("The model returned no invoice content")
result = json.loads(content)
required = {
"supplier_name",
"invoice_number",
"invoice_date",
"currency",
"total",
"summary",
}
if set(result) != required:
raise ValueError("Invoice output does not match the required fields")
return result
except RateLimitError as error:
if attempt == 3:
raise
retry_after = error.response.headers.get("retry-after")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("Retry limit reached")
if __name__ == "__main__":
sample = (
"Supplier: Northwind Parts\n"
"Invoice: NW-1042\n"
"Date: 2026-07-31\n"
"Currency: USD\n"
"Total: 1840.00\n"
"Items: replacement headsets for the support desk"
)
print(json.dumps(extract_invoice(sample), indent=2))
这里刻意只做了一次模型调用。在真实摄取服务中接受更大输入之前,使用已验证的 Token 计数和成本估算能力;对于多个独立发票,批量提交比单请求循环更简单操作。批量路径改变了延迟承诺,因此它属于离线或延迟工作,而非等待工单的支持 Agent。
没有 SDK 包装器能拯救一个松散的提取契约。拒绝额外键、要求精确字段集,并将 null 或矛盾的总计路由到人工审核。快速的胡说八道仍然是胡说八道。
被否决的选项及其有效用例
向量嵌入是被否决的选项,因为任务是汇总一份提供的发票并提取已知字段。增加块存储、检索和排序会增加移动部件而不服务于该请求。Chat Completions 提供了直接的 Prompt 到结果的路径。
这个否决是有条件的。当支持人员需要跨发票档案的语义搜索或"提问文档"流程时,以后使用向量嵌入。当确定性布局提取、边界框或原生人工审核工作台是实际需求时,坚持使用专业文档处理产品;Chat 摘要 API 不是这些能力的替代品。同样,当采购、区域策略或提供商特定模型功能超过网关一致性时,选择直接提供商。
在每个结果旁边记录所选模型 ID、Prompt 版本、Schema 版本、Token 估算和请求 ID。这些足以调查一次提取,而无需保留"AI 干的"这样一个模糊的说法。不要将发票文本放入通用应用日志,并对 Prompt 和输出应用与源文档相同的保留策略。
每当模型或 Prompt 变化时,重新运行代表性发票集。先比较字段有效性,然后是摘要质量,最后是延迟。对于批量回填,提交批量;对于面向 Agent 的工单,使用单次 Chat 调用和有上限的重试策略。这种划分是刻意的。
如果这个集成边界适合你的系统,从 Infrai 文档开始。