- 后端
- 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以 Rust 核心引擎为底座,为 Elixir 提供了一套通过 Rustler NIF 暴露的高层提取 API。本篇文章以仓库中自动生成的契约(contract)代码片段docs-site/src/snippets-generated/elixir/contract/api_extract_batch_uri_with_config.md为骨架,完整讲解 Elixir 中如何使用extract_batch一次性批量提取多个远程 URI 文档,并针对单个输入附加独立的提取配置(per-input config)。读完本文,你将掌握extract_batch的调用签名、ExtractInput的字段语义、逐输入配置与批次全局配置的合并规则,以及底层引擎的并发、缓存与错误隔离原理,可直接在自己的 Elixir 项目中落地。
一、示例片段解读:一次带配置的批量 URI 提取
原文档中的核心示例是一段完整的 Elixir 代码,展示了契约测试(contract test)的调用形态:
result = Xberg.extract_batch_async([%{"config" => %{"output_format" => "markdown"}, "kind" => "uri", "uri" => "https://example.com/pdf/fake_memo.pdf"}]) Enum.each(result.results, fn result -> IO.puts(result.content) end)这段代码包含三个关键信息:
- 输入列表:
extract_batch_async接收一个列表,列表中每个元素是一个 map,表示一个待提取文档。这里的输入声明了kind为"uri"、uri指向远程 PDF,并在config中通过"output_format" => "markdown"为该文档单独指定了输出格式。 - 批量返回:
result.results是本次批量提取的结果数组,与输入一一对应。 - 结果消费:用
Enum.each遍历每个result,直接输出其content字段——即该文档提取出的正文内容。
注意:片段中的extract_batch_async是 NIF 层的异步调用形态;在应用代码中,更常用的是高层封装Xberg.extract_batch/1。二者的关系见下文。
二、Elixir 批处理入口:Xberg.extract_batch/1与 NIF 层
2.1 高层 API 封装
在 packages/elixir/lib/xberg.ex 中,Xberg模块提供了关键字列表风格的高层函数:
@doc "Extract content from multiple bytes or URI inputs." @spec extract_batch(keyword()) :: {:ok, map()} | {:error, atom, String.t()} def extract_batch(opts \\ []) do Xberg.Native.extract_batch_async( case Keyword.get(opts, :inputs) do nil -> nil v when is_binary(v) -> v v -> Jason.encode!(v) end, case Keyword.get(opts, :config) do nil -> nil v when is_binary(v) -> v v -> Jason.encode!(v) end ) end它接受两个关键字参数:
| 参数 | 含义 | 说明 |
|---|---|---|
:inputs | 输入列表 | 每个元素为ExtractInput结构或 map(%{"kind" => ..., "uri" => ..., "bytes" => ..., "config" => ...}) |
:config | 批次级全局配置 | 所有输入共享的默认ExtractionConfig,可被单个输入内的config覆盖 |
函数返回{:ok, result_map}或{:error, reason, message}三元组。inputs/config既可以直接传二进制(JSON 字符串),也可以传 Elixir 数据结构,由封装层统一Jason.encode!/1序列化后送入 NIF。
2.2 NIF 层与原生实现
NIF 声明位于 packages/elixir/lib/xberg/native.ex:
def extract_batch_async(_inputs, _config), do: :erlang.nif_error(:nif_not_loaded)该模块基于RustlerPrecompiled加载xberg_nifcrate,并针对aarch64-apple-darwin、aarch64-unknown-linux-gnu、x86_64-unknown-linux-gnu、x86_64-pc-windows-msvc四个目标平台提供预编译产物。也就是说,Elixir 侧只是薄封装,真正的提取逻辑全部在 Rust 核心引擎中完成。
三、逐输入配置(Per-Input Config)的结构与字段
3.1ExtractInput:统一输入模型
Rust 侧的ExtractInput定义在 crates/xberg/src/core/config/extraction/types.rs:
pub struct ExtractInput { pub kind: ExtractInputKind, // "bytes" 或 "uri" pub bytes: Option<Vec<u8>>, // kind = "bytes" 时使用 pub uri: Option<String>, // 本地路径、file:// 或 HTTP(S) URL pub mime_type: Option<String>, // MIME 提示 pub filename: Option<String>, // 文件名提示,用于 MIME 探测与元数据 pub config: Option<FileExtractionConfig>, // 逐输入提取覆盖 }其中kind只有两种取值:Bytes(内存字节)与Uri(本地路径、file://URI 或 HTTP(S) URL)。本文示例使用的是Uri形态。
3.2FileExtractionConfig:可在单个输入上覆盖的字段
config字段的类型是FileExtractionConfig,定义于 crates/xberg/src/core/config/extraction/file_config.rs。它是"覆盖层":每个字段都是Option,只覆盖你显式指定的项,未指定的项沿用批次全局配置。可用字段包括(节选核心项):
| 字段 | 作用 |
|---|---|
output_format | 覆盖输出内容格式:plain、markdown、djot、html |
result_format | 覆盖结果形态:unified(默认)或element_based |
force_ocr | 强制对该文档执行 OCR |
disable_ocr | 对该文档禁用 OCR |
ocr_strategy/force_ocr_pages | 覆盖 OCR 页选择策略与强制页号(1 起始) |
chunking | 覆盖分块配置 |
images | 覆盖图片提取配置 |
pages | 覆盖页范围提取 |
language_detection | 覆盖语言检测 |
keywords | 覆盖关键词提取(需keywords-yake/keywords-rake特性) |
postprocessor | 覆盖后处理器 |
timeout_secs | 覆盖该文档的提取超时(秒),超时只影响该输入 |
mime_detection_policy | 覆盖 MIME 推断策略 |
enable_quality_processing | 覆盖质量后处理开关 |
include_document_structure | 覆盖文档结构输出开关 |
需要特别说明的是output_format:在全局配置ExtractionConfig中它的默认值是Plain(纯文本),可选Markdown、Djot(需 djot 特性)、Html,见 crates/xberg/src/core/config/extraction/core.rs。当设置为Markdown时,渲染器默认会对正文中的 Markdown 敏感字符(如行首-、#)做反斜杠转义(escape_markdown默认true),保证文本通过 CommonMark 解析器往返一致。
3.3 配置合并:批次全局配置 + 逐输入覆盖
逐输入配置与批次配置不是"二选一",而是叠加。引擎在 crates/xberg/src/engine/extract_impl.rs 的resolve_input_config中完成合并:
fn resolve_input_config(input: &ExtractInput, base_config: &ExtractionConfig) -> ExtractionConfig { let mut resolved = input .config .as_ref() .map(|overrides| base_config.with_file_overrides(overrides)) .unwrap_or_else(|| base_config.clone()); resolved.ensure_cancel_token(); resolved }with_file_overrides(定义于 crates/xberg/src/core/config/extraction/core.rs)逐字段检查覆盖层的Option,凡是Some的字段就替换全局配置中的对应值,None则保留全局值。因此:
- 批次全局配置负责"公共默认值"(如所有文档都输出 Markdown);
- 单个输入的
config负责"特例"(如某份扫描件需要force_ocr,某个 HTML 需要输出plain)。
在并发批量模式下,引擎还提供了resolve_input_config_arc优化:没有逐输入覆盖时直接Arc::clone共享全局配置,避免不必要的深拷贝;有覆盖时才构造新的Arc<ExtractionConfig>。
四、底层引擎:批量提取的执行链路
4.1 入口委托
Rust 核心的公开入口在 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委托给它执行。
4.2 引擎内部的五步流程
引擎实现(crates/xberg/src/engine/extract_impl.rs)中,extract_batch的执行大致分五步:
- 进度事件:发送
BATCH_PROGRESS_STAGE_START(进度 0.0)。 - 配置校验:调用
config.validate()校验 OCR、DPI、分块参数、置信度区间等;校验失败立即返回错误并发送BATCH_PROGRESS_STAGE_ERROR。 - 缓存查找:用
batch_content_cache_key计算批量缓存键——它把每个输入的bytes(或 URI 解析结果)、mime_type、filename与合并后的配置 JSON一起喂给 blake3 哈希。因此,逐输入配置的改变会改变缓存键,同一文档不同配置不会错误地共享缓存。 - 执行提取:
extract_batch_uncached根据运行时特性选择顺序执行(extract_batch_sequential)或并发执行(extract_batch_concurrent,基于 TokioJoinSet分发任务,并把线程预算按批次均分给各文档 worker)。 - 结果缓存:若所有输入都成功且无错误项,将序列化结果写入缓存;最后发送
BATCH_PROGRESS_STAGE_COMPLETE(进度 1.0)。
4.3 错误隔离与去重
批量语义上有两个重要设计:
- 错误隔离:单个输入失败不会拖垮整个批次。顺序模式中每个输入的错误被包装为
ExtractionErrorItem(包含index、code、error_type、source、message)追加到output.errors,其余输入继续处理。这对应了仓库中extract_batch_uri_partial_failure等契约测试覆盖的场景。 - URI 去重与递归:
initial_seen_urls收集输入中所有 HTTP(S) URI 作为已访问集合,initial_seed_hosts记录种子域名;批处理结束后通过follow_recursive_document_urls支持按配置递归跟进文档内链接(受UrlExtractionConfig/CrawlConfig控制)。
五、契约与测试验证:这段代码为什么"能跑"
原文档是 alef 工具链自动生成的契约片段(<!-- This file is auto-generated by alef -->),它与仓库中的真实契约定义、e2e 测试一一对应。
5.1 契约 JSON
契约定义在 fixtures/contract/api_extract_batch_uri_with_config.json:
{ "id": "api_extract_batch_uri_with_config", "call": "extract_batch", "input": { "inputs": [ { "kind": "uri", "uri": "$mock_url/pdf/fake_memo.pdf", "config": { "output_format": "markdown" } } ], "mock_responses": [ { "path": "/pdf/fake_memo.pdf", "status_code": 200, "headers": { "content-type": "application/octet-stream" }, "body_file": "../test_documents/pdf/fake_memo.pdf" } ] }, "assertions": [ { "type": "equals", "field": "results[0].mime_type", "value": "application/pdf" }, { "type": "min_length", "field": "results[0].content", "value": 10 }, { "type": "equals", "field": "results[0].metadata.output_format", "value": "markdown" } ] }这份契约精确规定了该批次的验收标准:
- 远程 mock 服务器返回
fake_memo.pdf(仓库测试文档,见 crates/xberg/test_documents/pdf); - 提取结果中
results[0].mime_type必须是application/pdf(说明 URI 输入走 HTTP 拉取后正确识别 MIME); results[0].content长度至少为 10(说明确实提取出了正文);results[0].metadata.output_format必须是markdown——这正是逐输入配置生效的直接证据。
5.2 Elixir e2e 测试
对应的 e2e 测试位于 e2e/elixir/test/contract_test.exs:
describe "api_extract_batch_uri_with_config" do test "api_extract_batch_uri_with_config" do inputs_json = Jason.encode!([%{"config" => %{"output_format" => "markdown"}, "kind" => "uri", "uri" => "$mock_url/pdf/fake_memo.pdf"}]) |> String.replace("$mock_url", inputs_mock_base_url) inputs_value = Jason.decode!(inputs_json) {:ok, result} = Xberg.extract_batch(inputs: inputs_value) assert Enum.at(result.results, 0).mime_type == "application/pdf" assert (is_binary(Enum.at(result.results, 0).content) && byte_size(Enum.at(result.results, 0).content) >= 10) || ... assert Enum.at(result.results, 0).metadata.output_format == "markdown" end end测试把$mock_url替换为 mock 服务器地址后调用Xberg.extract_batch(inputs: ...),并断言了与契约完全一致的三条结果。这意味着:只要你的环境能访问到文档 URI(本地路径、file://或 HTTP(S)),在本地复现这段代码即可获得可验证的输出。
六、实战建议:混合批次与输出格式选择
6.1 混合输入批次
批量接口的真正价值在于一个请求内混合不同类型、不同需求的文档。官方提取指南 docs-site/src/content/docs/guides/extraction.mdx 中的"Per-Input Configuration"一节给出了标准范式——批次全局配置设公共默认值,单个输入覆盖特例:
# 批次全局配置:所有文档默认输出 markdown # 单个输入:扫描件强制 OCR,HTML 页面改为纯文本 inputs = [ %{"kind" => "uri", "uri" => "report.pdf"}, %{"kind" => "uri", "uri" => "scan.tiff", "config" => %{"force_ocr" => true}}, %{"kind" => "uri", "uri" => "notes.html", "config" => %{"output_format" => "plain"}} ] {:ok, result} = Xberg.extract_batch(inputs: inputs, config: %{"output_format" => "markdown"})也可以混合bytes输入(内存中的文件内容),例如读取本地文件后以字节列表传入并给出filename提示,引擎会依据文件名辅助 MIME 探测。
6.2 结果字段速览
result.results中每个元素(ExtractionResult的单项)通常包含:
| 字段 | 含义 |
|---|---|
content | 提取出的正文(格式取决于output_format) |
metadata | 文档元数据,含output_format等本次生效的配置回显 |
mime_type | 探测到的 MIME 类型 |
summary/errors | 批次汇总信息与单项错误列表(非致命) |
6.3 使用前提
- 提取远程 HTTP(S) URI 需要引擎编译时启用
url-ingestion特性;本地路径与file://URI 不需要网络。 - 输出格式中的
djot需要djot特性,html需要相应渲染器特性;未启用的格式会被拒绝或回退,具体以你的构建配置为准。 - Elixir 包通过 RustlerPrecompiled 加载预编译 NIF,也可设置
XBERG_BUILD=1或开发环境(Mix.env() in [:dev])强制本地编译。
七、小结
以契约片段docs-site/src/snippets-generated/elixir/contract/api_extract_batch_uri_with_config.md为线索,我们完整梳理了 Elixir 侧批量 URI 提取的调用链:Xberg.extract_batch/1(高层封装)→Xberg.Native.extract_batch_async/2(Rustler NIF)→ Rust 核心extract_batch→ 引擎合并逐输入配置(with_file_overrides)→ 并发/顺序执行 → 缓存与错误隔离。核心要点是:逐输入配置通过ExtractInput.config(FileExtractionConfig)实现字段级覆盖,未指定的字段自动继承批次全局配置,这让你可以用一次extract_batch调用处理来源、格式、OCR 需求各不相同的文档集合,而契约与 e2e 测试(fixtures/contract/api_extract_batch_uri_with_config.json、e2e/elixir/test/contract_test.exs)为这段代码的正确行为提供了可重复验证的保证。
- 后端
- 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 C 绑定批量文档抽取实战:xberg_extract_batch 与 per-input config 逐条配置
xberg C 绑定批量文档抽取实战:xberg_extract_batch 与 per input config 逐条配置 本篇技术指南聚焦 xberg 的
后端AI 应用NLPXberg C 批量 URI 文档提取实战:ExtractBatchAsync 与按输入粒度覆盖配置(Per-Input Config)深度解析
Xberg C 批量 URI 文档提取实战:ExtractBatchAsync 与按输入粒度覆盖配置(Per Input Config)深度解析 本指南聚焦 X
后端AI 应用NLPxberg Dart 绑定批量 URI 提取实战:基于 extract_batch 与 per-input 配置逐文件控制输出格式
xberg Dart 绑定批量 URI 提取实战:基于 extract_batch 与 per input 配置逐文件控制输出格式 本篇指南讲解如何在 xber
后端AI 应用NLP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考