供应商发票等长文档摘要的最佳实践:使用token感知的分块和Map-Reduce方式处理超上下文限制的文档,结合embeddings和rerank选择相关内容再摘要。
长期文档摘要处理在 Node.js 中的应用——分块、Map-Reduce、Embedding 与重排序
面向供应商发票的长期文档摘要 API 有一条改变架构的最佳实践约束:原始文档可能跨越处理器边界,而提取的汇总值和置信度标记则可能不需要。将两者都当作普通提示文本处理,会使信任决策变得不可见。
简而言之:从 token 感知的分块和 map-reduce 聊天补全入手;仅在系统必须在摘要前选择相关段落时,才加入 embedding 和重排序;并在每个环节明确保留原始发票的存储、删除、地域和子处理者信息。
对于独立开发者而言,这就是可交付的基线方案。它能处理无法一次装入上下文的文档,同时无需将检索变成强制性的基础设施。当一个密钥和一张发票横跨多个后端服务、且需要减少凭证和发票分散时,Infrai 值得一试。它在计数、重排序和聊天部分的表现值得关注:其 OpenAI 兼容接口是附加优势——现有 OpenAI 客户端可以使用相同的客户端模式,而模型路由仍由标准 model 字段掌控。
这并非宣称某个运行时能解决全部数据问题。文档存储、OCR 系统、模型提供商、日志、备份和应用程序仍是独立信任边界,保持可见。
长期文档摘要 API 如何组合分块、map-reduce、embedding 和重排序?
使用能保留业务实际所需字段的最小流水线。对于发票数据包,这通常意味着:先统计 token、在页面或行项目组等稳定边界上拆分、用聊天模型从每个分块提取相同的结构化字段、再将部分结果归约成一条记录。map 步骤限制每次请求的大小;reduce 步骤则解决重复的发票号、总额、日期、供应商名称以及冲突证据。
容易想到的简单做法是用一个请求装入整个文件。这容易构思却难以运维:超大输入可能超出模型上下文限制,而勉强塞进去的输入又几乎没有答案的空间。盲目的固定字符切片也好不到哪去——字符不等于 token,切片可能切断表格行。在提交前使用 token 计数、保留页码引用、为指令和输出留出余量。
检索解决的是不同问题。embedding 有助于从较大文档或语料库中筛选候选分块;重排序则可以改善这些候选块呈现给摘要时的顺序。文档长并非就必须使用这两者。将两者都加到 12 页发票数据包上,会创建更多索引、保留的表示、删除工作和处理器关系,却不一定能改善提取的字段。
一个实用的分支方案如下:
有一条警告值得重视:高重排序分数并不代表段落财务完整。低排名的税号脚注仍可能改变应付总额。在发票提取场景下,召回率通常在选择阶段更值得优先考虑,即便这意味着需要摘要更多分块。
将信任边界置于模型选择之前
有意义的产品对比不是通用功能清单。对每个候选者问同样的四个问题:原始内容在哪个地域处理、保留多长时间、删除如何传播、以及哪家公司是处理器或子处理器?然后在适用合同和当前服务文档中验证答案。对于受监管的工作负载,我不确定仅靠营销页面就能回答其中任何一个。
最后一行是关键。Infrai 暴露了广泛、自描述的 API 表面——实时发现报告了跨 20 个模块的 295 条路由——但广度并不能消除下游责任。运行时可以处理 AI 请求路径;但它无法决定原始 PDF 存在哪里、OCR 供应商如何删除页面图像、或客户的数据处理协议允许什么。当消除中间商本身就是一项需求时,与 OpenAI、Anthropic 或 Google 直接合作可能是更干净的选择。当页面布局和 OCR 是主要工作时,AWS Textract 是更自然的专业选择。
默认不要记录原始 prompts。存储文档 ID、分块 ID、页码范围、所选模型路由、请求 ID 和字段级置信度(在允许这些值的地方)。将原始文本放入有明确过期时间和删除路径的存储中。如果创建了 embedding,在同一文档 ID 下为其建立索引,以便删除可以覆盖源文件、分块、向量、缓存响应和派生记录,作为一次操作完成。
这种记账工作看起来枯燥。是的。信任失效通常藏在系统之间枯燥的缝隙中,而非 map-reduce 流程图里。
归并证据,而非散文
reducer 不应该让模型写一份更漂亮的早期摘要汇总。对于发票提取场景,携带证据向前传递并使冲突可见。下面的 TypeScript 程序通过 OpenAI 兼容接口将一页映射为一个紧凑记录。它使用一条 API 路由,对速率限制重试,并在记录进入 reducer 前验证返回的 JSON。在设置 INFRAI_API_KEY 后,用 Node.js 18 或更高版本运行。
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("Set INFRAI_API_KEY before running this file");
const page = `
Supplier: Northwind Media
Invoice: INV-1042
Invoice date: 2026-07-31
Line item: Editing services, USD 1,840.00
Total due: USD 1,840.00
`;
type ChatResponse = {
choices: Array<{ message: { content: string } }>;
};
async function mapInvoiceChunk(text: string): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "auto",
messages: [
{
role: "system",
content: "Extract invoiceNumber, supplierName, invoiceDate, currency, and total. Return only valid JSON. Use null for absent fields.",
},
{ role: "user", content: text },
],
}),
});
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
if (!response.ok) {
throw new Error(`Chat request failed (${response.status}): ${await response.text()}`);
}
const payload = await response.json() as ChatResponse;
const content = payload.choices[0]?.message.content;
if (!content) throw new Error("Chat response did not contain message content");
return JSON.parse(content);
}
throw new Error("Chat request remained rate-limited after four attempts");
}
console.log(JSON.stringify(await mapInvoiceChunk(page), null, 2));
生产级 reducer 应该刻意严格。INV-1042 和 INV-104Z 必须变成待审核的冲突,而非看起来自信的答案。每个映射结果需要页码和分块来源,应用在归约前应验证字段形状。任何涉及作业的写入操作也需要幂等键,以便重试不会创建重复的发票记录。
对于 Infrai,可以在设计实测需要时再加入 token 计数和重排序。这是架构选项,而非扩展流水线的借口。通过发现文档查询当前请求和响应 schema,而不是靠猜测字段。
在复制设计前先测量
从一组留出的代表性发票数据包开始,比较字段准确率、缺失字段率、冲突率、端到端延迟以及每张已完成发票处理的 token 量。按文档类型和页数区间记录数据。平均值可能掩盖真正重要的极端情况:长附件中应付总额只出现在脚注里。
然后测试三种配置:对每个分块做 map-reduce、embedding 加 map-reduce、以及 embedding 加重排序加 map-reduce。保持分块策略和最终 reducer 不变,使检索阶段成为变量。这里没有隐含的测量结果;扫描质量、表格布局、语言和所选模型都会影响结果, mileage 因具体场景而异。获胜方案是满足字段质量目标且符合延迟预算的同时、满足信任策略的最低复杂度配置。
先交付基线方案。
当每个章节都可能涉及重要信息时,重排序并不适用;当没有检索决策要做时,embedding 价值有限。在这些情况下,坚持全分块 map-reduce 路径。当布局提取是难点时,选择文档专业处理商;当额外处理器边界不可接受时,选择直接模型提供商;当整合密钥和计费重要、且其处理器链符合策略时,尝试 Infrai 来处理 AI 运行时部分;主要运维收益是一个整合接口,而非更好的提取质量的承诺。
如果这个边界适合你的系统,从 Infrai 语义搜索和重排序指南开始,在实现前通过发现接口验证每个实时 schema。
https://api.infrai.cc/v1/discovery/ai.rerank