news 2026/9/28 7:13:23

Elixir 调用 Xberg 批量提取远程 URI 文档:逐输入配置(Per-Input Config)实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Elixir 调用 Xberg 批量提取远程 URI 文档:逐输入配置(Per-Input Config)实战指南
  • 后端
  • 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.

项目地址:https://gitcode.com/gh_mirrors/kr/xberg
点击查看免费下载

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)

这段代码包含三个关键信息:

  1. 输入列表:extract_batch_async接收一个列表,列表中每个元素是一个 map,表示一个待提取文档。这里的输入声明了kind为"uri"、uri指向远程 PDF,并在config中通过"output_format" => "markdown"为该文档单独指定了输出格式。
  2. 批量返回:result.results是本次批量提取的结果数组,与输入一一对应。
  3. 结果消费:用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的执行大致分五步:

  1. 进度事件:发送BATCH_PROGRESS_STAGE_START(进度 0.0)。
  2. 配置校验:调用config.validate()校验 OCR、DPI、分块参数、置信度区间等;校验失败立即返回错误并发送BATCH_PROGRESS_STAGE_ERROR。
  3. 缓存查找:用batch_content_cache_key计算批量缓存键——它把每个输入的bytes(或 URI 解析结果)、mime_type、filename与合并后的配置 JSON一起喂给 blake3 哈希。因此,逐输入配置的改变会改变缓存键,同一文档不同配置不会错误地共享缓存。
  4. 执行提取:extract_batch_uncached根据运行时特性选择顺序执行(extract_batch_sequential)或并发执行(extract_batch_concurrent,基于 TokioJoinSet分发任务,并把线程预算按批次均分给各文档 worker)。
  5. 结果缓存:若所有输入都成功且无错误项,将序列化结果写入缓存;最后发送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.

项目地址:https://gitcode.com/gh_mirrors/kr/xberg
点击查看免费下载

相关推荐

上一篇:OpenResume代码覆盖率工具集成:Jest与Istanbul配置
下一篇:EdgeGPT终极指南:3种对话风格模式对比与选择技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:13:20

从零构建AI工程系统:可交付、可监控、可回滚的实战框架

1. 这不是调包&#xff0c;是亲手造轮子&#xff1a;从零构建AI工程系统的实战真相“AI Engineering from Scratch”——看到这个标题&#xff0c;很多人第一反应是&#xff1a;“又要学Python&#xff1f;又要装CUDA&#xff1f;又要配环境&#xff1f;”其实完全不是。我带过…

作者头像 李华
网站建设 2026/9/28 7:13:02

AI工程从零到上线:系统思维、模型部署与监控实战

我把自己两年多来围绕 AI Engineering 攒下的笔记整理成了一个项目&#xff0c;名字就叫 ai-engineering-from-scratch。起因很实际&#xff1a;团队里能在 Jupyter Notebook 里调出漂亮 AUC 的人不少&#xff0c;但能把模型稳定送上线、出问题能十分钟内定位的人&#xff0c;掰…

作者头像 李华
网站建设 2026/9/28 7:12:42

Python+OpenCV双目视觉测尺寸:从标定到三维换算的完整实战

简介&#xff1a;这是一份面向计算机、通信、人工智能、自动化等专业学生与从业者的双目视觉测量项目资料&#xff0c;以Python结合OpenCV实现被摄物体尺寸的非接触式测量&#xff0c;可作为毕业设计、课程大作业或期末课程设计的参考方案&#xff0c;也适合具备一定基础后在此…

作者头像 李华
网站建设 2026/9/28 7:12:33

Prompt Engineering实战:用TaoToken统一Key打通结构化Prompt工程化链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 7:11:50

用Dify打造hindsight复盘助手:从后见之明偏差到自动化经验重放

hindsight这个词我一直觉得直接翻译成“事后诸葛亮”有点委屈它。英文里的hindsight&#xff0c;本意是“回看过去时的理解”&#xff0c;它是一面镜子&#xff0c;让你看清自己当时究竟漏掉了什么、哪里被认知盲区遮住了。真正的问题在于&#xff0c;这面镜子大多数人不会主动…

作者头像 李华
网站建设 2026/9/28 7:11:17

S7-1500博图产线例程精读:从OB/FB架构到通信报警实战

第一次真正看懂西门子S7-1500生产线例程&#xff0c;是在一个汽车零部件焊装项目上。那会儿我已经写了两三年单机设备程序&#xff0c;自认为对博图&#xff08;TIA Portal&#xff09;熟得很&#xff0c;结果打开总控程序还是被震了一下——不是指令用得有多花哨&#xff0c;而…

作者头像 李华