使用 Dart 绑定查询 Xberg 已注册 Post-Processor:listPostProcessors 实战指南

原创2026-09-27 23:57:491,260 阅读
文章标签:后端AI 应用NLP

使用 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)可以在编译期干净地裁剪掉不支持的处理器。

二、为什么需要"列出已注册处理器":注册表查询的三种场景

后处理器注册表是全局共享的运行时状态。在以下场景中,你往往需要先查询当前注册表里到底有哪些处理器:

  1. 调试与诊断:配置了 summarization 或 ner 等处理阶段,但输出里没有对应结果。此时先调用 listPostProcessors() 确认处理器是否真的注册成功——若 feature 未编译进二进制,注册会静默跳过。
  2. 插件编排:通过 plugin_api 系列接口注册了自定义 Dart 后处理器后,用列表接口核对注册结果是否如预期。
  3. 运行时自省:面向用户的工具(如 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);
});

从测试可以提取两条实用经验:

  1. 初始化前设置环境变量:测试通过 FFI 调用 setenv 设置 RUST_MIN_STACK(16 MB),避免深调用栈下 Rust 侧栈溢出;CRAWLBERG_ALLOW_PRIVATE_NETWORK 用于允许测试访问私有网络资源。在真实应用中,若你的后处理器涉及网络调用(如 LLM 摘要),同样需要关注这些环境变量。
  2. 断言策略:列表接口的断言是 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 环境下的可用性,也让这个"简单接口"成为诊断注册问题、编排插件流程的可靠起点。

登录后查看全文
xberg