- 后端
- 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 仓库中官方生成的 Ruby 代码片段为核心,讲解Xberg.extract_batch在“同一批次内部分输入成功、部分输入失败”场景下的真实行为:如何组织 URI 输入、如何通过summary.results与summary.errors统计成败,以及底层 Rust 引擎如何做到单条失败不中断整批。读完本文,你将掌握批量提取的部分失败判定方法与容错编码模式,并能对照仓库中的夹具与 e2e 测试验证自己的理解。
一、场景解读:一个批次里“部分失败”意味着什么
批量提取(batch extraction)的典型形态是:一次调用同时处理多个文档来源。与“整批要么全成功、要么抛异常”的事务式语义不同,Xberg 的extract_batch采用逐条隔离的语义——每个输入独立完成下载、解析与抽取,任何一条输入失败都不会中止其余输入的处理,更不会让整个调用抛出异常。
这一点在该场景的官方夹具定义中体现得很明确。fixtures/batch/extract_batch_uri_partial_failure.json 的assertions写明了三条断言:
"assertions": [ { "type": "not_error" }, { "type": "equals", "field": "summary.results", "value": 1 }, { "type": "equals", "field": "summary.errors", "value": 1 } ]即:调用本身不报错(not_error),但返回结果中成功数为 1、失败数为 1。这正是“部分失败”的核心语义——失败信息被收编进返回值,而不是以异常形式抛出。
二、最小可运行示例:官方生成的 Ruby 片段
场景的官方 Ruby 代码片段来自 docs-site/src/snippets-generated/ruby/batch/extract_batch_uri_partial_failure.md,其描述为“extract_batch with one valid URI input and one that downloads to an unparseable document”,完整代码如下:
require "xberg" result = Xberg.extract_batch([{ 'kind' => 'uri', 'uri' => 'https://example.com/text/plain.txt' }, { 'kind' => 'uri', 'uri' => 'https://example.com/pdf/corrupt_truncated.pdf' }]) puts result.summary.results.inspect puts result.summary.errors.inspect逐行拆解:
require "xberg":加载 Ruby 绑定。入口模块由 packages/ruby/lib/xberg.rb 与 packages/ruby/lib/xberg/native.rb 组成,后者会按当前 Ruby ABI(ruby_version+DLEXT)定位并加载原生扩展xberg_rb,因此调用方无需关心底层共享库路径。Xberg.extract_batch([...]):接收一个输入数组,数组元素为 Ruby Hash。本示例传入两个uri输入:第一个指向一个纯文本文件,第二个指向一个下载后无法解析的损坏 PDF(截断文件)。result.summary.results/result.summary.errors:从返回结果中读取汇总统计。summary是ExtractionSummary类型的对象(见下文第四节),results为成功条数,errors为失败条数。在本场景中应分别输出1和1。
需要注意的是,片段中的https://example.com/...是文档化占位地址。在仓库的实际 e2e 测试中,URI 会被替换为 mock 服务器地址(见第六节),以确保测试可离线、可重复运行。
三、输入结构:ExtractInput 与 kind 字段
extract_batch的输入数组元素在 Rust 核心中对应统一的ExtractInput结构,其关键字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
kind | ExtractInputKind | 输入来源类型:bytes(内存中的原始字节)或uri(本地路径、file://URI 或 HTTP(S) URL) |
bytes | Option<Vec<u8>> | 当kind = "bytes"时必须提供,携带原始字节内容 |
uri | Option<String> | 当kind = "uri"时必须提供,携带路径或 URL |
mime_type | Option<String> | MIME 类型提示,用于辅助文档类型判定 |
filename | Option<String> | 文件名提示,参与 MIME 检测与元数据 |
config | Option<FileExtractionConfig> | 单条输入级别的提取配置覆盖(如 OCR、分块、结果格式等) |
kind枚举只有两个取值:Bytes与Uri,见ExtractInputKind的定义。从源码注释看,bytes输入要求提供bytes字段,uri输入要求提供uri字段,二者互斥。
在 Ruby 侧,既可以像官方片段那样直接传 Hash({ 'kind' => 'uri', 'uri' => ... },也兼容符号键{ kind: "uri", uri: ... }),也可以使用类型化的构造器。仓库 README 给出了混合输入的写法,见 packages/ruby/README.md:
require 'xberg' bytes = File.binread("doc3.txt") inputs = [ Xberg::ExtractInput.new(kind: "uri", uri: "doc1.pdf"), Xberg::ExtractInput.new(kind: "uri", uri: "doc2.docx"), Xberg::ExtractInput.new( kind: "bytes", bytes: bytes, mime_type: "text/plain", filename: "doc3.txt" ), ] output = Xberg.extract_batch(inputs, Xberg::ExtractionConfig.new) output.results.each do |document| puts "Content length: #{document.content.length}" end值得注意:extract_batch的第二个参数是ExtractionConfig,在 Ruby 类型签名中为def self.extract_batch: (Array[ExtractInput] inputs, ExtractionConfig config) -> ExtractionResult(见 packages/ruby/sig/types.rbs)。当通过 Ruby 原生绑定调用时,未显式传 config 会走默认配置分支;更规范的写法是显式传入Xberg::ExtractionConfig.new。
四、返回结构:ExtractionResult 与 ExtractionSummary
extract_batch的返回值类型为ExtractionResult,在 Ruby 绑定中对应同名的Xberg::ExtractionResult。它包含四类信息:
results: Vec<ExtractedDocument>——成功抽取出的文档列表,按输入的发现顺序排列;errors: Vec<ExtractionErrorItem>——非致命(per-input)错误列表;summary: ExtractionSummary——本次操作的聚合统计;crawl_final_urls/crawl_redirect_count/crawl_unique_normalized_urls——URL 抓取相关的补充信息(仅当启用了 URL 抓取链路时才会填充)。
summary的类型是ExtractionSummary,字段语义如下:
| 字段 | 含义 |
|---|---|
inputs | 调用方提交的输入总数 |
results | 成功产出抽取结果的条数 |
errors | 出现单条错误的输入数 |
remote_urls | 解析为远程 HTTP(S) URL 的 URI 输入数 |
pages_crawled | 被抓取或解析的 HTML 页面数 |
documents_downloaded | 从 URL 下载并成功提取的非 HTML 文档数 |
Ruby 绑定的 RBS 类型定义在 packages/ruby/sig/types.rbs,ExtractionSummary公开了上述全部六个只读属性,因此result.summary.results、result.summary.errors可以直接在 Ruby 中访问。
单条错误项ExtractionErrorItem只有两个字段:source(尽力而为的来源标识,通常是 URI 或输入序号)与message(错误消息)。在 Ruby 中可通过result.summary.errors拿到条数,若要逐条查看失败原因,则遍历result.errors数组读取每个错误项的source与message字段。
五、源码纵深:Rust 核心的批量提取实现
理解部分失败语义的最直接方式是看核心实现链路。公开 API 的入口在 crates/xberg/src/core/extract/mod.rs:
pub async fn extract_batch(inputs: Vec<ExtractInput>, config: &ExtractionConfig) -> Result<ExtractionResult> { DEFAULT_ENGINE.extract_batch(inputs, config).await }它委托给全局引擎DEFAULT_ENGINE。引擎层的extract_batch在 crates/xberg/src/engine/mod.rs 中定义,其文档注释明确说明了两点实现事实:
- 批次处理尊重注入的
CacheBackend与ProgressSink两个可扩展点(seam); - 缓存规则:完全由字节组成的批次可以按“内容 + 配置”整体缓存;只要批次包含 URI 输入,或者存在单条错误,就不做缓存。也就是说,本场景(含 URI 输入且有部分失败)永远不会命中缓存,每次调用都会真实执行下载与解析。
真正的批量处理逻辑在 crates/xberg/src/engine/extract_impl.rs:
- 首先通过
ProgressSink发出BATCH_PROGRESS_STAGE_START事件(进度为 0.0); - 校验配置(
config.validate())并检查取消状态,若失败则发出BATCH_PROGRESS_STAGE_ERROR事件并整体返回错误; - 尝试按
batch_content_cache_key命中缓存,命中则发出BATCH_PROGRESS_STAGE_CACHE_HIT并直接返回缓存结果; - 否则进入
extract_batch_uncached,结束后根据结果有无错误决定是否写入缓存,并发出COMPLETE或ERROR事件。
extract_batch_uncached内部有两条执行路径:
- 顺序路径(
extract_batch_sequential,见 extract_impl.rs):逐个遍历输入,对每条调用extract_one(input, config, index);Ok时把结果追加进output.results,Err时通过error_item(index, source, &error)构造错误项并 push 进output.errors;最后调用refresh_counts()重算汇总计数,并处理递归文档 URL 跟进。单条输入的错误在这里被吞进output.errors,不会向上抛出——这正是部分失败语义的直接来源。 - 并发路径(
extract_batch_concurrent,见 extract_impl.rs):在启用tokio-runtime且非 wasm32 目标上使用tokio::task::JoinSet并行执行多条输入;wasm32(没有 OS 线程且 extractor future 为!Send)与未启用tokio-runtime的构建则退回顺序路径。
无论走哪条路径,最终都会调用refresh_counts()(types.rs),它用self.results.len()与self.errors.len()刷新summary.results与summary.errors。因此summary.results + summary.errors恒等于成功与失败输入数之和,这是判断部分失败是否“完全被记录”的可靠依据。
Ruby 原生绑定侧的入口位于 packages/ruby/ext/xberg_rb/src/lib.rs:extract_batch与extract_batch_async两个函数都会把 Ruby Hash 数组转换为核心的Vec<ExtractInput>,然后通过rt.block_on(async { xberg::extract_batch(...).await })同步等待结果,最后把ExtractionResult序列化回 Ruby 对象;模块函数extract_batch在 lib.rs 注册。
六、测试与夹具:这个场景如何被验证
该场景是 Xberg 的契约测试(contract/e2e)家族中的一员,可以从三个层面交叉验证:
1. 夹具定义:fixtures/batch/extract_batch_uri_partial_failure.json 定义了 mock 服务器的两条响应:
/text/plain.txt→ HTTP 200,content-type: text/plain,响应体来自 test_documents/text/plain.txt;/pdf/corrupt_truncated.pdf→ HTTP 200,content-type: application/pdf,响应体来自 test_documents/pdf/corrupt_truncated.pdf(一份故意截断、无法解析的 PDF)。
两个输入都能成功“下载”,但第二个下载得到的内容在解析阶段失败,从而构造出“下载成功、解析失败”的部分失败场景。
2. e2e 断言:e2e/ruby/spec/batch_spec.rb 中对应的测试将片段中的占位 URL 通过MOCK_SERVER_EXTRACT_BATCH_URI_PARTIAL_FAILURE(或回退到MOCK_SERVER_URL)环境变量替换为 mock 服务器地址,然后断言:
expect(result.summary.results).to eq(1) expect(result.summary.errors).to eq(1)这与夹具的assertions完全一致,构成了“文档示例 → 夹具 → 测试断言”的闭环。mock 服务器的统一运行方式可参考 scripts/e2e/run-with-mock-server.sh。
3. 兄弟场景对照:同一批量的其他片段提供了完整的失败语义谱系,适合对照阅读:
- extract_batch_uri_basic:全成功的 URI 批次;
- extract_batch_uri_not_found:单条 URI 不存在(本地路径
/nonexistent/a.pdf); - extract_batch_uri_all_missing:所有 URI 均不存在;
- extract_batch_bytes_invalid_mime、extract_batch_bytes_unsupported_mime:字节输入的 MIME 错误变体;
- extract_batch_empty_inputs:空批次边界。
另外,这些片段文件均由 alef 工具自动生成(文件头标注 “auto-generated by alef — DO NOT EDIT”),frontmatter 中的level: typecheck与side_effect: server表明它们服务于 Ruby 绑定的类型检查级 e2e 验证,属于持续回归的契约的一部分。
七、工程实践:部分失败批次的容错处理模式
基于上述语义,在实际业务中处理extract_batch的部分失败,建议遵循以下模式:
1. 先看摘要,再决定是否需要细查。调用后先读summary,用summary.errors快速判断批次健康度:
result = Xberg.extract_batch(inputs) if result.summary.errors.zero? # 整批成功,直接消费 result.results else warn "batch had #{result.summary.errors} failures out of #{result.summary.inputs} inputs" end2. 需要定位失败来源时,遍历result.errors。每个错误项携带source(来源标识)与message(错误消息),可用于日志告警或定向重试:
result.errors.each do |err| puts "failed source=#{err.source} message=#{err.message}" end3. 区分“部分失败”与“整体失败”。本场景证明:只要错误被收编进errors数组,extract_batch调用本身仍成功返回,因此业务侧应当把“summary.errors > 0”当作可预期的业务状态来处理,而不是依赖异常捕获。真正的整体失败(如配置非法、被取消)才会让调用抛出错误,对应核心实现中config.validate()失败即整体返回的路径。
4. 对失败输入做定向重试,而非整批重放。由于包含 URI 输入或存在单条错误的批次不会被缓存(见第五节),失败输入的重试会真实重新下载解析;建议仅将errors中记录的来源重新构造为新的单条或小批次输入再次调用,避免重复处理已成功的文档。
5. 关注 URL 类输入的附带成本。当批次包含远程 URL 时,summary.remote_urls、pages_crawled、documents_downloaded会提供抓取链路的统计;如果业务对网络耗时敏感,可以为批量提取配置UrlExtractionConfig(通过ExtractionConfig的url字段或单条输入的config覆盖)来控制抓取行为。
八、小结
Xberg.extract_batch的部分失败语义可以总结为一句话:调用整体成功、结果内部成败并存、失败明细收编于返回对象。通过官方生成的 Ruby 片段、fixtures/batch/extract_batch_uri_partial_failure.json 夹具与 e2e/ruby/spec/batch_spec.rb 断言的三角验证,以及 Rust 核心 extract_impl.rs 中“逐条隔离、错误入列、计数刷新”的实现事实,开发者可以在 Ruby 中放心地批量处理混合来源的文档,并用summary.results/summary.errors精确掌握每一次批次的成败分布。
- 后端
- 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 API 批量提取实战:extract_batch 的 URI 部分失败语义与容错设计
xberg C API 批量提取实战:extract_batch 的 URI 部分失败语义与容错设计 本篇技术指南以 xberg 官方 C API 测试夹具 e
后端AI 应用NLPXberg Python 批量 URL 提取的局部失败处理:extract_batch 与 ExtractionResult 错误语义实战
Xberg Python 批量 URL 提取的局部失败处理:extract_batch 与 ExtractionResult 错误语义实战 导读 本文围绕 Xb
后端AI 应用NLP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考