使用 Dart 绑定查询 Xberg 已注册 Post-Processor:listPostProcessors 实战指南
使用 Dart 绑定查询 Xberg 已注册 Post-Processor:listPostProcessors 实战指南
本篇技术指南以 xberg 仓库的 Dart 端示例文档(docs-site/src/snippets-generated/dart/plugin_api/post_processors_list.md)为核心,讲解如何通过 Dart 绑定调用 XbergBridge.listPostProcessors() 列出所有已注册的后处理器(Post-Processor)。读完本文,你将掌握后处理器注册机制在 Rust 内核中的实现方式、Dart 到 Rust 的桥接调用链,以及如何在 Dart 应用中安全地初始化和释放 Rust 运行时。
一、Post-Processor 是什么:理解文档提取流水线的最后一环
xberg 是一个以 Rust 为核心的 Polyglot 文档智能引擎,可从 106 种格式、140 种文件扩展名中提取文本、元数据、图片、表格与结构化数据。在提取流水线中,后处理器(Post-Processor) 是在文档完成基础提取之后、最终输出之前执行的一类插件,负责对已提取的 ExtractedDocument 做二次加工。
从源码结构看,xberg 将后处理器组织为独立的插件体系,注册表模块位于 crates/xberg/src/plugins/processor/registry.rs,核心 trait PostProcessor 定义于 crates/xberg/src/plugins/processor/trait.rs。每个后处理器实现两个关键方法:
process(&self, result: &mut ExtractedDocument, config: &ExtractionConfig):对提取结果做就地修改;processing_stage(&self) -> ProcessingStage:声明处理阶段(如Early、Middle、Late),用于控制多个处理器之间的执行顺序。
内置处理器按 feature gate 编译,声明于 crates/xberg/src/plugins/processor/builtin/mod.rs,包括:
| 处理器 | 功能 | 对应 feature |
|---|---|---|
PageClassificationProcessor |
页面分类 | classification |
ChunkClassificationProcessor |
块级分类 | classification |
SummarizationProcessor |
摘要生成 | summarization |
TranslationProcessor |
翻译 | translation |
CaptioningProcessor |
图像字幕 | captioning |
QrCodeProcessor |
QR 码识别 | qr-codes |
NerProcessor |
命名实体识别 | ner |
RedactionProcessor |
内容脱敏 | redaction |
每个内置处理器都在自己的子模块中实现 process 与 processing_stage,例如 crates/xberg/src/plugins/processor/builtin/qr.rs、crates/xberg/src/plugins/processor/builtin/ner.rs 等。这种"一个子模块 + 一个 feature gate"的组织方式,让非 OSS 目标(如 no-ort、wasm、android)可以在编译期干净地裁剪掉不支持的处理器。
二、为什么需要"列出已注册处理器":注册表查询的三种场景
后处理器注册表是全局共享的运行时状态。在以下场景中,你往往需要先查询当前注册表里到底有哪些处理器:
- 调试与诊断:配置了
summarization或ner等处理阶段,但输出里没有对应结果。此时先调用listPostProcessors()确认处理器是否真的注册成功——若 feature 未编译进二进制,注册会静默跳过。 - 插件编排:通过 plugin_api 系列接口注册了自定义 Dart 后处理器后,用列表接口核对注册结果是否如预期。
- 运行时自省:面向用户的工具(如 CLI、MCP server)需要向用户展示当前宿主支持哪些处理能力,避免配置不可用的处理阶段。
xberg 的 Rust 端在 crates/xberg/src/plugins/processor/registry.rs 提供了对应的原生实现:
pub fn list_post_processors() -> crate::Result<Vec<String>> {
use crate::plugins::registry::get_post_processor_registry;
let registry = get_post_processor_registry();
let registry = registry.read();
Ok(registry.list())
}
该函数获取全局注册表并加读锁,随后返回全部已注册处理器的名称列表。值得注意的是,文档注释明确说明了唯一的错误来源:注册表锁被毒化(poisoned)时返回 Err。这意味着 listPostProcessors() 本身是一个只读、无副作用的操作(文档 front-matter 中标明 side_effect: safe),不会修改任何运行时状态。
三、核心代码详解:Dart 侧调用链逐行拆解
关联文档 post_processors_list.md 给出的完整 Dart 示例如下:
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 result = await XbergBridge.listPostProcessors();
stdout.writeln(result);
} finally {
RustLib.dispose();
}
}
逐行拆解如下:
import 'dart:io':引入stdout,用于把查询结果打印到标准输出。如果你的应用是 Flutter UI 或 Web 环境,可以去掉该导入,改为把result渲染到界面或日志系统。import 'package:xberg/xberg.dart':引入公开 API。XbergBridge的静态方法定义在 packages/dart/lib/src/xberg.dart:static Future<List<String>> listPostProcessors() async { return await rust_bridge.listPostProcessors(); }import 'package:xberg/src/xberg_bridge_generated/frb_generated.dart' show RustLib:引入 flutter_rust_bridge 生成的运行时RustLib,这是 Dart 与 Rust 原生库之间的桥。await RustLib.init():初始化 Rust 侧运行时(加载动态库、建立 FFI 通道)。此步骤必不可少,任何桥接调用之前都必须先初始化。XbergBridge.listPostProcessors():真正的查询调用,返回Future<List<String>>——即所有已注册后处理器的名称列表。stdout.writeln(result):输出结果。List<String>的toString()输出形如[qr, ner, redaction, ...]。finally { RustLib.dispose() }:无论成功与否都释放 Rust 运行时。try/finally结构保证了资源安全释放,这在长生命周期进程中尤为重要(重复init而不dispose会累积原生资源)。
3.1 返回值语义
listPostProcessors() 的返回类型在 Dart 侧为 Future<List<String>>,对应 Rust 侧 Vec<String>。Dart 绑定层声明位于 packages/dart/lib/src/xberg_bridge_generated/lib.dart:
Future<List<String>> listPostProcessors() => ...;
每个元素是后处理器的注册名称。内置处理器通过 builtin/mod.rs 中的 BuiltinRegistration 类型注册,即 (&'static str, fn() -> crate::Result<()>)——字符串为展示名,函数指针为注册函数。例如 register_qr 注册 QrCodeProcessor、register_ner 注册 NerProcessor。
四、配套接口:注册、注销与清空
listPostProcessors() 并非孤立接口,它与注册表管理的其他操作配套使用。Dart 侧的全部相关 API 集中在 packages/dart/lib/src/xberg.dart:
| Dart API | 作用 | Rust 语义 |
|---|---|---|
registerPostProcessor(PostProcessorDartImpl impl) |
注册一个 Dart 实现的后处理器插件 | register_post_processor |
unregisterPostProcessor(String name) |
按名称注销指定插件 | unregister_post_processor |
clearPostProcessors() |
清空注册表中的全部后处理器 | clear_post_processors |
listPostProcessors() |
列出所有已注册名称 | list_post_processors |
配套的清空示例见 docs-site/src/snippets-generated/dart/plugin_api/post_processors_clear.md,其结构与列表示例一致,只是把调用换成 XbergBridge.clearPostProcessors()。典型的管理闭环是:注册 → 列表验证 → (可选)注销 → 列表确认 → (可选)清空。
4.1 注册失败容错机制
从 crates/xberg/src/plugins/processor/builtin/mod.rs 的 register_all 实现可以观察到注册阶段的容错设计:每个 (name, register) 对按顺序尝试,单个注册失败不会中断后续注册(修复自 issue #271 的问题),所有失败会被聚合为一个 Err 返回,同时每条失败都会通过 tracing::error! 单独记录日志。因此列表接口对注册诊断至关重要:某个处理器"静默缺席"往往正是注册失败的信号,配合日志即可定位根因。
五、端到端验证:测试如何证明接口行为
xberg 为 Dart 绑定提供了真实的端到端测试,见 e2e/dart/test/post_processor_management_test.dart。测试的关键结构如下:
setUpAll(() async {
_setEnv('CRAWLBERG_ALLOW_PRIVATE_NETWORK', 'true');
_setEnv('RUST_MIN_STACK', '16777216');
await RustLib.init();
...
});
test('Clear all post-processors and verify list is empty', () async {
await expectLater(XbergBridge.clearPostProcessors(), completes);
});
test('List all registered post-processors', () async {
final result = await XbergBridge.listPostProcessors();
expect(result, isNotNull);
});
从测试可以提取两条实用经验:
- 初始化前设置环境变量:测试通过 FFI 调用
setenv设置RUST_MIN_STACK(16 MB),避免深调用栈下 Rust 侧栈溢出;CRAWLBERG_ALLOW_PRIVATE_NETWORK用于允许测试访问私有网络资源。在真实应用中,若你的后处理器涉及网络调用(如 LLM 摘要),同样需要关注这些环境变量。 - 断言策略:列表接口的断言是
isNotNull,而非固定长度——因为注册表内容是构建配置(feature gates)决定的,不同构建产物中处理器数量不同。这也印证了列表接口的价值在于运行时自省而非静态约定。
六、进阶:从"列出"到"使用"——把名称与处理阶段对应起来
拿到处理器名称列表只是第一步。若你的 Dart 应用需要按名称判断某个处理器是否可用,可结合 Rust 端的注册表语义做进一步查询。从 processor/registry.rs 的结构可以推断,注册表内部以名称索引存储 Arc<dyn PostProcessor>,每个处理器还带有 ProcessingStage 信息(trait.rs 中 process 与 processing_stage 成对定义)。
实用建议:
- 在配置界面中,用
listPostProcessors()的结果动态渲染可用后处理器复选框,而不是硬编码处理器清单——这样当用户的自定义插件注册后,界面无需改代码即可感知; - 把
listPostProcessors()结果与doctor诊断(XbergBridge.doctor(config),见 packages/dart/lib/src/xberg.dart)配合使用:列表告诉你有哪些处理器,doctor告诉你哪些后端在当前主机上真正可用(模型未缓存或未编译的后端会报告Skip); - 注意 Dart 侧注册的自定义处理器与内置处理器共用同一全局注册表,因此列表结果会混合显示两者——这也是名称(而非类型)作为唯一标识的原因。
七、完整可运行示例与常见问题
7.1 打印结果并统计数量
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 processors = await XbergBridge.listPostProcessors();
processors.forEach((name) => stdout.writeln('- $name'));
stdout.writeln('Total: ${processors.length}');
} finally {
RustLib.dispose();
}
}
7.2 常见问题排查
| 现象 | 排查方向 |
|---|---|
| 列表为空 | 构建时未启用对应 feature(见 builtin/mod.rs 的 #[cfg(feature = ...)]),或注册失败被 register_all 聚合为错误 |
| 调用抛异常 | 检查是否遗漏 RustLib.init();若注册表锁被毒化(如此前某线程 panic),会返回错误 |
| 结果与预期不符 | 确认是否调用过 clearPostProcessors() 或 unregisterPostProcessor() 改变了全局状态;多绑定进程共享同一原生库时需注意并发修改 |
八、小结
XbergBridge.listPostProcessors() 是 Dart 开发者接入 xberg 插件体系最轻量的入口之一:一行查询即可自省全局后处理器注册表。其背后是 Rust 内核中加读锁遍历注册表的实现(registry.rs),配合 register / unregister / clear 三个配套接口,构成了完整的后处理器生命周期管理闭环。端到端测试(e2e/dart/test/post_processor_management_test.dart)证明了该接口在真实 FFI 环境下的可用性,也让这个"简单接口"成为诊断注册问题、编排插件流程的可靠起点。