- 后端
- 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 的 Elixir 绑定中,Xberg.extract_batch/1允许一次调用同时提取多份文档,其中kind: "bytes"的输入直接把内存字节交给核心引擎处理。本文以仓库内自动生成的示例片段 extract_batch_bytes_invalid_mime.md 为骨架,讲解当字节输入的mime_type无效(如application/x-nonexistent)时,批量提取为什么不会整体失败、底层 Rust 核心如何校验 MIME 类型,以及 Elixir 侧如何组织输入与读取结果。读完你可以直接照搬这段代码,并理解批量提取的"单条失败不拖垮整批"的容错语义。
批量 bytes 提取与 MIME 的角色
extract_batch与单文档extract的区别在于:它接收一个输入数组(inputs),每个元素都可以是bytes(内存字节)或uri(文件路径 / URL),一次调用并发处理多条,返回一个统一的批结果对象。批量场景最常见的需求是:内存中已经有一批文件字节(例如从 S3 拉取、从上传请求解析),希望不落盘直接提取。
每个 bytes 输入由三个字段构成:
| 字段 | 类型 | 含义 |
|---|---|---|
kind | string | 输入种类,固定为"bytes"(另一类是"uri") |
bytes | 整数数组 / binary | 文档的原始字节;数组形式为 0~255 的十进制字节值 |
mime_type | string | 声明的 MIME 类型,例如text/plain、application/pdf |
mime_type在这里起着双重作用:一是作为提取器(extractor)路由的依据,决定用哪个解析器处理这份文档;二是当调用方提供它时,核心会先做一次校验。如果声明值与字节内容不符,或者根本不被支持,批量调用会如何处理?这正是本示例要验证的行为。
原文档示例:传入无效 MIME 类型的批量调用
文档中的核心代码片段如下(来自 extract_batch_bytes_invalid_mime.md,由 alef 的e2e generate流水线自动生成,属于docs-site/src/snippets-generated/elixir/batch/用例族):
result = Xberg.extract_batch_async([%{"bytes" => [72, 101, 108, 108, 111], "kind" => "bytes", "mime_type" => "application/x-nonexistent"}]) IO.inspect(result)逐字段拆解这份输入:
bytes数组[72, 101, 108, 108, 111]解码为 ASCII 字符串"Hello"——一个 5 字节的纯文本内容;kind为"bytes",表示按内存字节处理,不涉及文件系统;mime_type为"application/x-nonexistent",这是一个语法上合法、但不存在于 xberg 支持列表中的 MIME 类型(x-nonexistent并非注册类型)。
对应的 fixture 定义在 fixtures/batch/extract_batch_bytes_invalid_mime.json,其中断言类型为not_error——即批量调用本身不应抛出错误。这就是本用例的核心结论:单条输入的 MIME 类型无效,并不会让整个extract_batch调用失败。
两种调用形态:高层 keyword API 与底层 async 绑定
文档片段中直接调用的是Xberg.extract_batch_async/1,这是 Rustler NIF 绑定的底层形态,第一个位置参数就是 inputs 数组。而 Elixir 包对外提供的高层 API 是Xberg.extract_batch/1,其实现位于 packages/elixir/lib/xberg.ex#L112-L126:
@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可以看到,高层extract_batch接受 Elixir keyword list,从:inputs与:config两个键取值,map/list 型参数经Jason.encode!/1序列化为 JSON 字符串后传给 NIF。因此文档片段等价于如下更惯用的写法:
{:ok, result} = Xberg.extract_batch( inputs: [ %{"bytes" => [72, 101, 108, 108, 111], "kind" => "bytes", "mime_type" => "application/x-nonexistent"} ] ) refute is_nil(result)这正是仓库中 e2e 测试 e2e/elixir/test/batch_test.exs#L58-L65 的实际写法:它断言调用匹配{:ok, result}且结果不为nil,确认无效 MIME 类型不会导致整批报错。
如果你偏好强类型风格,绑定也提供了Xberg.ExtractInput结构体(见 packages/elixir/lib/xberg/extract_input.ex),包含kind、bytes、uri、mime_type、filename、config六个字段,并通过Jason.Encoder实现自动序列化(nil字段会被剔除)。
底层 MIME 校验机制:源码视角
"无效 MIME 类型"在 xberg 核心中其实分两种情况,理解这个区别是读懂本用例的关键。校验入口是 crates/xberg/src/core/mime.rs#L1050-L1066 中的validate_mime_type:
- 先解析语法:调用方提供的 MIME 字符串先用
mimecrate 解析,若语法非法(例如缺少/分隔、参数残缺),直接返回XbergError::UnsupportedFormat; - 再比对支持集合:取解析结果的
essence(如application/json; charset=utf-8的 essence 是application/json),在SUPPORTED_MIME_TYPES集合中做大小写不敏感的查找,命中则返回规范化后的标准 MIME 字符串,否则同样报UnsupportedFormat。
两条原则体现在 mime.rs 的单元测试中:
- 语法非法必须被拒绝(crates/xberg/src/core/mime.rs#L2632-L2641):
application//json、application/json; charset、application/json, text/plain、未闭合引号的参数化 MIME 都会校验失败; - 带参数的 MIME 按 essence 规范化(crates/xberg/src/core/mime.rs#L2620-L2629):
"Application/JSON; Charset=UTF-8"被规范为"application/json"; - 合法但不支持的类型同样报错(crates/xberg/src/core/mime.rs#L2644-L2646):
application/unknown校验失败。
回到本用例:application/x-nonexistent语法合法(有/、无畸形参数),但不在支持集合内,因此属于**"合法但不支持"**。与之形成对照的用例是 error_invalid_mime_format.md:单文档extract传入语法完全非法的"not-a-mime"时,fixture(fixtures/error/error_invalid_mime_format.json)的断言是error,且 Elixir 侧代码用try ... rescue捕获异常。也就是说:语法非法 → 直接报错;语法合法但不支持 → 在批量语境下被容错处理。
批量容错语义:not_error 与 summary
那么"容错"具体落到哪个层面?从 fixture 断言(not_error)和 e2e 测试({:ok, result})可以确认:整个批调用返回成功,失败被记入批结果的统计字段,而不是把异常抛给调用方。批结果结构在相邻用例的 e2e 测试中被直接断言过:
- 空输入(
extract_batch_empty_inputs):length(result.results) == 0,说明结果对象包含results列表; - URI 全部缺失(
extract_batch_uri_all_missing):result.summary.results == 0且result.summary.errors == 2,说明结果对象包含summary统计(results成功数、errors失败数),且即使全部条目都失败,批调用依旧返回{:ok, result}; - URI 部分失败(
extract_batch_uri_partial_failure):summary.results == 1、summary.errors == 1,证明成功与失败的条目在 summary 中分别计数。
由此可以推断,本用例(字节输入 + 不支持 MIME)的典型处理路径是:该条目的提取失败被记入summary.errors,批次整体仍以{:ok, result}返回,调用方通过读取summary和逐条results来区分成败。这种"聚合错误"设计让批量提取天然适合流水线场景——一批 100 份文档中有 1 份格式异常,不应该让其余 99 份的提取结果全部丢失。
用例矩阵:同目录的周边片段
extract_batch_bytes_invalid_mime并非孤例,它属于 docs-site/src/snippets-generated/elixir/batch/ 中一整组"批量字节提取"用例,对照阅读可以拼出完整的容错边界:
| 片段 | 输入特征 | 行为要点 |
|---|---|---|
| extract_batch_bytes_happy.md | text/plain+text/html双文档 | 正常路径,length(result.results) >= 1 |
| extract_batch_bytes_invalid_mime.md | application/x-nonexistent | 本文主角:不报错,批调用成功 |
| extract_batch_bytes_unsupported_mime.md | application/x-unknown,字节为"data" | 同样不报错,结果非 nil |
| extract_batch_bytes_mixed_format.md | application/x-unknown,字节为 PDF 占位内容 | 未知 MIME + 非文本字节也保持优雅降级 |
| extract_batch_empty_inputs.md | 空数组 | results为空列表,不崩溃 |
这些用例的 e2e 断言都集中在 e2e/elixir/test/batch_test.exs(batch 分类),fixture 集中在 fixtures/batch/,由 alef 的 e2e 流水线统一生成与校验,属于 xberg 跨语言契约测试的一部分——同样的用例在 Rust、Python、Node.js 等十余种绑定下以等价代码存在。
实战建议与错误处理模式
基于以上机制,在实际项目中使用extract_batch处理字节输入时,建议遵循以下模式:
1. 优先让核心自动探测,而不是拍脑袋写 MIME
mime_type只在你有可靠来源时才显式给出(例如 HTTPContent-Type头、对象存储元数据)。对于来源不可信或缺失的场景,核心具备字节级探测能力(detect_mime_type_from_bytes,见 crates/xberg/src/core/mime.rs#L1418,含 ZIP/OLE2/OOXML 包结构识别、JSON/XML 词汇表判断等),可以比"手写"的声明更可靠。给出错误 MIME 的代价是:提取器路由错误,例如把 PDF 声明成text/plain,得到的将是乱码式的文本结果。
2. 用模式匹配统一处理批结果
case Xberg.extract_batch(inputs: inputs) do {:ok, output} -> IO.inspect(output.summary, label: "batch summary") Enum.each(output.results, fn item -> IO.puts(item.content) end) {:error, reason} -> IO.puts(:stderr, "Extraction failed: #{inspect(reason)}") end注意{:error, reason}分支对应的是批次级异常(如参数结构本身非法、NIF 调用失败),而单个条目的解析失败只会体现在summary.errors计数上,不会走这个分支。
3. 显式声明 MIME 时注意规范化差异
validate_mime_type会把带参数的 MIME 按 essence 规范化后再匹配支持集合(大小写不敏感)。因此"Application/JSON; Charset=UTF-8"与"application/json"等价;而"not-a-mime"、"application//json"这类畸形字符串会在校验阶段直接失败。如果你的上游可能产生畸形 MIME,可以在进入extract_batch前先用一次Xberg.extract的单文档用例(如 error_invalid_mime_format.md 的try/rescue模式)验证数据源的质量。
4. 结合安装与运行环境
Elixir 绑定通过 Rustler NIF 预编译分发,要求 Elixir 1.14+ 与 Erlang/OTP 26+,在mix.exs中加入{:xberg, "~> 1.3.0"}后执行mix deps.get即可(详见 packages/elixir/README.md)。
小结
extract_batch对无效 MIME 类型的处理体现了 xberg 批量 API 的容错哲学:语法合法但不支持的 MIME 类型,只影响对应条目的成败,并计入summary.errors;批次本身始终返回{:ok, result}。这一行为由 crates/xberg/src/core/mime.rs 的validate_mime_type(语法解析 + 支持集合比对)与批结果聚合结构共同保证,并由 fixtures/batch/extract_batch_bytes_invalid_mime.json 的not_error断言和 e2e/elixir/test/batch_test.exs 的 e2e 用例固化下来。实际接入时,只需按本文的输入结构与模式匹配模式组织代码,即可安全地在流水线中批量处理来源复杂的内存文档。
- 后端
- 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 Elixir 批量提取实战:extract_batch 对未知 MIME 输入的优雅容错处理
xberg Elixir 批量提取实战:extract_batch 对未知 MIME 输入的优雅容错处理 在真实的数据管道中,待处理文档往往来自多个来源、多种格
后端AI 应用NLP从空 MIME 看 xberg 字节提取:类型解析、校验与错误处理实战
从空 MIME 看 xberg 字节提取:类型解析、校验与错误处理实战 本文基于 xberg 官方 e2e 测试夹具 extract_bytes_input_e
后端AI 应用NLPxberg 批处理提取实战:在 Dart 中处理无效 MIME 类型的 extract_batch 调用
xberg 批处理提取实战:在 Dart 中处理无效 MIME 类型的 extract_batch 调用 本篇技术指南围绕 xberg 开源仓库中 Dart 绑
后端AI 应用NLP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考