- 后端
- AI 应用
- NLP
【免费下载链接】xberg
Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.
导读
当一批待提取的文档 URI 全部指向不存在的文件(如路径拼写错误、文件已被迁移或删除)时,Xberg 的批量提取 APIextract_batch不会抛出异常终止整个任务,而是把每个失败折叠为一条结构化错误,在返回的summary中给出精确的results与errors计数。本文以 Xberg 仓库中 Dart 绑定的一则官方夹具(fixture)为核心,逐行拆解这段 Dart 代码,并深入 Rust 引擎源码,说明"全部失败"场景下的内部行为、结果类型设计,以及在实际批量处理中如何利用summary与错误明细做容错与审计。
场景:一个"全数落空"的批量输入
本篇文章讲解的夹具位于 docs-site/src/snippets-generated/dart/batch/extract_batch_uri_all_missing.md,对应的 JSON 契约定义在 fixtures/batch/extract_batch_uri_all_missing.json。
该夹具的输入是两个指向不存在路径的 URI:
[ {"kind": "uri", "uri": "/nonexistent/a.pdf"}, {"kind": "uri", "uri": "/nonexistent/b.txt"} ]它同时给出了本次调用必须满足的三条断言(assertions):
| 断言 | 期望值 | 含义 |
|---|---|---|
not_error | 无错误 | 整个extract_batch调用本身成功返回,不会因单个输入失败而抛出异常 |
summary.results | 0 | 没有任何一条输入产出提取结果 |
summary.errors | 2 | 两条输入各产生一条错误记录 |
这正是批量 API 与单文件 API 的核心差异:调用级成功与条目级失败是分离的。两个输入全部失败,但外层调用依然返回一个结构化的ExtractionResult,其中summary.errors == inputs.length。
Dart 侧完整代码逐行解读
以下代码就是该夹具在 Dart 语言绑定下的可运行形态,它演示了从 JSON 构造输入、调用批量接口、读取汇总计数的完整链路:
import 'dart:convert'; import 'dart:io'; import 'package:xberg/xberg.dart'; import 'package:xberg/src/xberg_bridge_generated/frb_generated.dart' show RustLib; Future<void> main() async { await RustLib.init(); try { final inputs = await Future.wait((jsonDecode(r'[{"kind":"uri","uri":"/nonexistent/a.pdf"},{"kind":"uri","uri":"/nonexistent/b.txt"}]') as List<dynamic>).map((element) => createExtractInputFromJson(json: jsonEncode(element)))); final result = await XbergBridge.extractBatch(inputs); stdout.writeln(result.summary.results); stdout.writeln(result.summary.errors); } finally { RustLib.dispose(); } }逐行拆解:
- 导入依赖:
package:xberg/xberg.dart提供XbergBridge、ExtractInput等公开 API;frb_generated.dart中的RustLib是 flutter_rust_bridge 生成的运行时入口,负责初始化与释放与 Rust 核心的通信通道。 await RustLib.init():在使用任何 Xberg 能力之前必须先初始化桥接运行时;对应的RustLib.dispose()放在finally中,保证无论成功失败都会释放资源。- 构造输入:这里没有直接用 Dart 的
ExtractInput构造器,而是把一段 JSON 数组通过jsonDecode解析后,逐个用createExtractInputFromJson(json: jsonEncode(element))转成ExtractInput。这种"从 JSON 构造输入"的方式与 Rust 侧ExtractInput的 serde 反序列化一一对应,可以让前端代码与后端契约(fixture JSON)保持同构。每个输入元素的kind: "uri"表示这是一个路径/URL 输入,uri字段则给出具体位置。 - 调用批量接口:
XbergBridge.extractBatch(inputs)对应 Dart 封装在 packages/dart/lib/src/xberg.dart 中定义的方法extractBatch(List<ExtractInput> inputs, {ExtractionConfig? config})——未传config时默认使用{}对应的 Rust 侧默认配置。 - 读取汇总:
result.summary.results与result.summary.errors分别是成功结果数与错误条目数。本场景下输出两行:第一行0,第二行2。
引擎内部:失败条目如何被收集而不是抛错
Dart 绑定背后是 Rust 核心的统一公共 API。在 crates/xberg/src/core/extract/mod.rs 中,extract_batch把输入委托给进程级默认引擎:
pub async fn extract_batch(inputs: Vec<ExtractInput>, config: &ExtractionConfig) -> Result<ExtractionResult> { DEFAULT_ENGINE.extract_batch(inputs, config).await }真正的实现位于 crates/xberg/src/engine/extract_impl.rs 的extract_batch(L217 起)。它的关键设计包括:
- 先校验、再缓存:调用前先执行
config.validate();随后计算批量内容的缓存键,若命中缓存且反序列化成功则直接返回缓存结果。 - 仅"零错误"才写缓存:代码中只有当
output.errors.is_empty()时才会把结果写入缓存(见 extract_impl.rs)。本场景errors == 2,因此不会污染缓存——有失败项的批量结果不会被误当成干净数据复用。 - 两种执行路径:
- 在启用
tokio-runtime且非 wasm32 的目标上走extract_batch_concurrent(extract_impl.rs),通过tokio::task::JoinSet并发处理各输入; - 在 wasm32 或未启用
tokio-runtime时退化为extract_batch_sequential(extract_impl.rs),逐个await。 - 两条路径的失败处理策略完全一致:单项失败被捕获并追加进
output.errors,不会中断整批循环。以顺序路径为例,循环体内Err(error) => output.errors.push(error_item(index, source, &error)),处理完所有输入后调用output.refresh_counts()重新统计计数。
- 在启用
"文件不存在"这类错误在底层表现为XbergError::Io,其io::ErrorKind为NotFound——这一点在 crates/xberg/src/core/extractor/file.rs 的测试should_report_missing_file_before_invalid_ocr_configuration中有明确印证:即使同时配置了 OCR,缺失文件也会优先以 NotFound 报告。
结果类型:summary 与 error 条目的字段契约
批量调用的返回类型定义在 crates/xberg/src/core/config/extraction/types.rs:
ExtractionSummary(types.rs)是result.summary的类型,包含六个计数:
| 字段 | 类型 | 含义 | 本场景取值 |
|---|---|---|---|
inputs | usize | 调用方提交的输入总数 | 2 |
results | usize | 成功产出的提取结果数 | 0 |
errors | usize | 逐条错误数 | 2 |
remote_urls | usize | 解析为远程 HTTP(S) URL 的 URI 数 | 0 |
pages_crawled | usize | 被抓取/爬取的 HTML 页数 | 0 |
documents_downloaded | usize | 从 URL 下载的非 HTML 文档数 | 0 |
ExtractionResult(types.rs)除results、errors、summary外,还携带爬取过程信息(crawl_final_urls、crawl_redirect_count、crawl_unique_normalized_urls),便于审计 URL 跳转链路。
每条错误都是结构化的ExtractionErrorItem(types.rs):
| 字段 | 类型 | 含义 |
|---|---|---|
index | usize | 出错输入在原始请求中的下标 |
code | u32 | 稳定的数字错误码 |
error_type | String | 稳定的 snake_case 错误类别 |
source | String | 尽力而为的源标识(如 URI 原文) |
message | String | 人类可读的错误信息 |
这意味着你不仅知道"失败了 2 条",还能通过errors[i].index精确还原是哪两条、通过source看到原始 URI、通过code/error_type做程序化分类。
输入模型:kind 与 uri 的约束
Dart 侧createExtractInputFromJson转换出的结构,对应 Rust 侧ExtractInput(types.rs)。其中ExtractInputKind(types.rs)只有两种取值:
bytes:内存中直接给出的原始字节,配套字段bytes;uri:文件系统路径、file://URI 或 HTTP(S) URL,配套字段uri。
类型定义上"bytes要求bytes、uri要求uri",uri缺省时还会触发校验错误(见 crates/xberg/tests/core_integration.rs 的missing uri field should fail validation)。本场景两个输入都正确提供了kind: "uri"与uri,问题只在于路径指向的文件不存在,因此错误发生在提取阶段而非校验阶段。
与其他批量夹具的对照:判断"全部失败"的边界
同一目录下的系列夹具可以帮助你判断当前场景在整个批量错误矩阵中的位置(见 docs-site/src/snippets-generated/dart/batch):
extract_batch_uri_basic:全部 URI 存在,期望results == inputs、errors == 0;extract_batch_uri_partial_failure:部分 URI 存在、部分缺失,期望results + errors == inputs;extract_batch_empty_inputs:输入列表为空,summary.inputs == 0;extract_batch_uri_all_missing(本文场景):全部URI 缺失,期望results == 0、errors == inputs,且调用本身不报错。
这些行为在 Rust 集成测试中同样被覆盖,例如 crates/xberg/tests/batch_processing.rs 的test_batch_extract_all_fail:三个不存在的文件(txt/pdf/docx)批量提取后,断言"批量调用整体成功,但每个结果的 metadata.error 均非空"。
实战要点:如何把"全失败"变成可运维的信号
结合上述类型契约,在真实项目中处理"全部缺失"场景时建议:
- 不要用 try/catch 判断单个文件失败。
extract_batch的调用级错误只代表配置非法、取消或灾难性故障;文件缺失这种条目级错误永远出现在result.errors里。判断维度始终是summary.results、summary.errors与summary.inputs三者是否满足results + errors == inputs。 - 用
errors[i].index与source回填原始输入。批量输入在客户端往往有业务序号(文件名、数据库 ID),而错误项只携带下标,因此保持输入列表顺序不变即可精确映射"哪条输入失败了、失败原因是什么"。 - 对
code/error_type做程序化分类。不要靠解析message字符串判断错误类别,优先使用稳定的错误码与错误类型字段,例如把NotFound类错误与"超时""格式不支持"区分处理。 - 注意缓存语义。只有零错误的批量结果才会被写入提取缓存;包含失败项的批次每次都会重新执行。若你的批处理经常出现部分失败,可通过错误计数提前感知缓存未命中的开销。
- 配合单文件容错链路理解。批量内部的错误处理与
extract_uri_document等单条路径共用同一套ExtractionErrorItem契约,因此你可以先在小样本上逐个验证单文件行为,再放大到批量场景。
小结
extract_batch面对"全部 URI 缺失"时的行为可以概括为一句话:调用级成功、条目级全失败、失败信息结构化。通过 extract_batch_uri_all_missing.md 这段 Dart 示例,配合 fixtures/batch/extract_batch_uri_all_missing.json 的断言、engine/extract_impl.rs 的实现以及 types.rs 的类型定义,你可以在自己的 Dart/Flutter 应用中可靠地实现"批量提取 + 逐条容错 + 精确审计",让异常输入不再中断整条流水线。
- 后端
- AI 应用
- NLP
【免费下载链接】xberg
Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.
相关推荐
Xberg Dart 批量提取容错实战:用 extract_batch 优雅处理"部分失败"
Xberg Dart 批量提取容错实战:用 extract_batch 优雅处理"部分失败" 导读 在真实的生产环境中,批量文档提取永远不会"全对或全错":一批
后端AI 应用NLPXberg C 绑定批量提取容错指南:用 extract_batch 处理全部 URI 缺失的场景
Xberg C 绑定批量提取容错指南:用 extract_batch 处理全部 URI 缺失的场景 批量文档提取( extract_batch )是 Xberg
后端AI 应用NLPXberg Elixir 批量提取:URI 输入全部缺失时 extract_batch 的 summary 错误语义与失败隔离实践
Xberg Elixir 批量提取:URI 输入全部缺失时 extract_batch 的 summary 错误语义与失败隔离实践 本篇指南聚焦 Xberg E
后端AI 应用NLP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考