resource_detection_traversal_metadata 模块技术文档
模块概述
resource_detection_traversal_metadata 模块是 OpenViking 解析系统中的元数据定义层,它为资源检测提供了统一的类型系统。在文档处理流水线中,系统需要在上层真正开始解析之前,先对输入的资源进行"体检"——判断它是什么类型、有多大、需要什么样的访问方式。这个模块正是为了解决这个"预处理"问题而设计的。
可以把整个资源检测系统想象成机场的值机系统:当一位旅客(资源)到达机场时,地勤人员首先需要判断:
- 这位旅客是本地人还是需要办理入境手续的外国人?(访问方式)
- 行李是否需要托运,还是可以随身携带?(大小规模)
- 这是一张单程票还是联程票,需要中转吗?(递归类型)
DetectInfo 就是这张"值机单",它将资源的多维度特征打包成一个结构化的对象,供下游的路由决策、策略选择和资源分配使用。
架构定位与数据流
┌─────────────────┐ ┌──────────────────────────┐ ┌─────────────────┐
│ 输入资源 │ ──▶ │ 资源检测/分类模块 │ ──▶ │ 解析器路由 │
│ (文件/URL/目录) │ │ (DetectInfo 生成) │ │ (选择合适parser)│
└─────────────────┘ └──────────────────────────┘ └─────────────────┘
│
▼
┌──────────────────────────┐
│ DetectInfo 元数据容器 │
│ ┌────────────────────┐ │
│ │ visit_type │ │ 如何访问资源
│ │ size_type │ │ 资源规模判断
│ │ recursive_type │ │ 是否需要递归
│ └────────────────────┘ │
└──────────────────────────┘
这个模块处于解析流水线的最前端,是连接输入源与具体解析器的桥梁。它的位置决定了它必须非常稳定——上游输入可能有各种奇怪的格式,下游解析器也有各自不同的需求,而 DetectInfo 作为中间层,需要提供一个足够抽象、足够通用的契约。
核心组件详解
VisitType —— 资源的访问方式
class VisitType(Enum):
# 可直接使用的内联内容,例如对话文本、JSON 字符串等
DIRECT_CONTENT = "DIRECT_CONTENT"
# 通过本地或网络文件系统工具访问,如本地文件、文件夹、压缩包等
FILE_SYS = "FILE_SYS"
# 需要下载才能访问,如网络文件、网页、远程代码仓库等
NEED_DOWNLOAD = "NEED_DOWNLOAD"
# OpenViking 预处理的上下文包,通常为 .ovpack 格式
READY_CONTEXT_PACK = "READY_CONTEXT_PACK"
设计意图:在真正读取资源之前,系统需要知道"从哪里获取"这些内容。一个直接传入的字符串和一个大容量的远程压缩包,它们被获取的方式完全不同。VisitType 回答的就是这个问题。
使用场景:
DIRECT_CONTENT:用户通过命令行直接粘贴的文本,或 API 传入的字符串内容FILE_SYS:本地文件系统上的文件或目录,此时系统可以直接用open()或PathAPI 访问NEED_DOWNLOAD:远程资源(URL、S3 对象、Git 仓库等),需要先触发下载流程READY_CONTEXT_PACK:这是 OpenViking 特有的优化形态——已经预先打包好的上下文包,可以跳过常规解析直接加载
设计权衡:选择用枚举而不是布尔标志位,是因为访问方式之间是互斥的,但未来可能会扩展(比如增加 STREAM_CONTENT 用于流式内容)。枚举提供了一种类型安全的方式来表达这种"多选一"的场景。
SizeType —— 资源的规模分类
class SizeType(Enum):
# 可以直接在内存中处理,例如小型文本片段
IN_MEM = "IN_MEM"
# 需要外部存储处理,例如多文件目录、大型文件等
EXTERNAL = "EXTERNAL"
# 内容过大无法处理,例如超过 X GB,可能导致系统崩溃或性能问题
TOO_LARGE_TO_PROCESS = "TOO_LARGE_TO_PROCESS"
设计意图:解析大文件和解析小文本需要完全不同的策略。一个 1MB 的 Markdown 文件可以直接读入内存用 LLM 处理,但一个包含 10 万个文件的代码仓库就需要分布式处理了。SizeType 让系统在开始工作之前就做好资源配置的决策。
为什么需要这个类型? 表面上看,文件大小可以通过 stat() 轻易获得,但这里的核心问题是:多大的文件算"大"取决于上下文。同一个 100MB 的文件,在内存充足的高端服务器上可能不算什么,但在受限的容器环境中就可能是灾难。SizeType 不是简单地存储一个数字,而是做出一个策略性的判断——这个资源应该用什么方式处理。
关键洞察:这是一个决策型而非信息型的字段。它不是告诉你"文件是 500MB",而是告诉你"这个资源应该走外部存储流程"。这种设计将判断逻辑(可能很复杂,需要考虑内存、并发、文件类型等因素)封装在检测层,下游只需要根据决策执行即可。
RecursiveType —— 递归处理需求
class RecursiveType(Enum):
# 单文件,无需递归处理
SINGLE = "SINGLE"
# 递归处理,例如目录中的所有文件、子目录中的所有文件等
RECURSIVE = "RECURSIVE"
# 需要先展开才能递归处理,例如压缩包、.ovpack 文件等
EXPAND_TO_RECURSIVE = "EXPAND_TO_RECURSIVE"
设计意图:系统需要知道输入是"一个东西"还是"一群东西"。一个 Markdown 文件可以直接喂给解析器,但一个目录需要递归遍历其中的每个文件。这个枚举告诉下游是否需要启动递归遍历逻辑。
EXPAND_TO_RECURSIVE 的独特价值:这个变体处理了一个有趣的边界情况——压缩包。压缩包在技术上是一个"单文件"(一个 .zip),但语义上它代表"一群文件"。如果检测逻辑发现输入是一个 .zip 文件,它会标记为 EXPAND_TO_RECURSIVE,告诉下游:"先解压缩,然后递归处理解压后的内容"。
这种设计避免了在解析器内部处理"是解压还是不解压"的复杂逻辑——一切都在检测阶段决定了。
DetectInfo —— 元数据容器
@dataclass
class DetectInfo:
visit_type: VisitType
size_type: SizeType
recursive_type: RecursiveType
设计哲学:这是一个纯数据容器,没有任何方法。它所做的一切就是将三个正交的资源特征打包在一起。为什么说这三个特征是正交的?因为:
- 访问方式(visit_type)和大小(size_type)无关——远程文件可能很小,本地文件也可能很大
- 递归需求(recursive_type)和其他两者也无关——一个远程的大目录既需要下载、也需要递归
这种正交设计使得 DetectInfo 可以被独立地理解和测试。任何新的检测逻辑只需要填充这三个字段,而不需要了解其他部分如何工作。
与其他模块的关系:从模块树可以看到,DetectInfo 与 resource_and_document_taxonomy 模块中的 ResourceCategory、DocumentType、MediaType 是互补而非重复的。简单来说:
- Taxonomy(分类法) 回答:"这是什么格式的文件?"(PDF、Markdown、音频、视频……)
- DetectInfo(元数据) 回答:"这个资源应该怎么访问和处理?"(从哪获取?多大?需要递归吗?)
两者是正交的维度,可以自由组合。
依赖关系与数据契约
上游:谁调用这个模块?
目前代码库中 DetectInfo 的直接使用案例较少,这表明这个模块可能处于规划中的架构,或者是为了未来的扩展预留的接口。这并不一定是问题——很多基础设施模块在初期只提供类型定义,实际的使用逻辑会随着系统演进逐步填充。
从设计意图推断,以下场景会使用 DetectInfo:
- 资源入口点(session、CLI 入口等):当用户传入一个路径或 URL 时,需要先检测它是什么类型的资源
- Parser Registry:在选择具体解析器之前,可能需要先根据 DetectInfo 做策略层的路由
- 资源分配器:根据 size_type 决定内存分配策略、并发策略等
下游:这个模块依赖什么?
它几乎不依赖任何东西——只有标准库的 dataclass 和 enum。这是一个刻意保持的极简依赖,目的是确保它可以安全地被任何模块导入,不会引入循环依赖。
数据契约
调用方创建 DetectInfo 时需要遵循的契约:
- 所有字段都是必需的:没有默认值,每个 DetectInfo 必须同时包含访问方式、大小和递归类型
- 枚举值必须有效:不能传入自定义字符串,必须使用预定义的枚举成员
- 不可变:dataclass 默认是可变的,但在使用中应该将其视为只读
设计决策与权衡
决策一:使用 Dataclass 而非普通类
选择 @dataclass 有几个实际好处:
- 自动生成的
__init__、__repr__、__eq__,减少样板代码 - 类型提示清晰,IDE 可以提供完整的自动补全
- 未来如果需要添加默认值或字段验证,改动成本低
决策二:三个独立的枚举而不是一个复合枚举
有人可能会设计一个单一的 ResourceType 枚举,包含诸如 LOCAL_SMALL_FILE、REMOTE_LARGE_DIRECTORY 这样的值。为什么没有这样做?
组合爆炸问题:如果将三个维度组合成单一枚举,理论上需要 4 × 3 × 3 = 36 种组合,但实际上很多组合是无意义的(比如 NEED_DOWNLOAD + IN_MEM 远程小文件 vs NEED_DOWNLOAD + EXTERNAL 远程大目录,语义完全不同)。使用三个独立枚举可以灵活组合,而且每个枚举的含义清晰明了。
扩展性:未来如果需要添加第四个维度(比如 access_permission: ReadOnly | ReadWrite),只需要添加一个新字段,而不需要修改现有的枚举值。
决策三:存储字符串而非枚举实例
在实际的序列化/反序列化场景中(JSON、数据库存储等),可能需要将枚举存储为字符串。这个设计选择在当前代码中没有体现,但这是一个值得注意的潜在扩展点。
已知限制与注意事项
空实现文件
检查 openviking/parse/resource_detector/ 目录会发现 visit.py、size.py、recursive.py 这些文件几乎是空的。这意味着实际的资源检测逻辑还没有实现。目前只有类型定义可用。
这对使用者意味着什么? 如果你现在试图使用这个模块,会发现只有类型定义可用,没有实际的检测实现。这意味着 DetectInfo 的使用者需要自己编写检测逻辑,或者等待后续的实现。
没有验证逻辑
DetectInfo 本身不验证其字段的合法性。例如,它不会检查 size_type = TOO_LARGE_TO_PROCESS 是否与 visit_type = DIRECT_CONTENT 兼容。这种验证应该由使用 DetectInfo 的业务逻辑来完成。
扩展点
对于希望扩展这个模块的开发者:
- 添加新的 SizeType 值:需要考虑新值是否真的代表一种新的"处理策略",而不仅仅是大小数字的阈值变化
- 添加新的 VisitType:需要考虑新类型是否真的需要不同的访问方式
- 添加新字段:可以向 DetectInfo 添加新的字段(如
encoding、mime_type等),但需要确保与现有字段正交
相关模块参考
- resource_and_document_taxonomy — 资源格式分类(文件类型、媒体类型)
- parser_abstractions_and_extension_points — 解析器抽象与扩展协议
- directory_scan — 目录预扫描模块(当前用于目录分类)
- registry — 解析器注册表(负责路由决策)
总结
resource_detection_traversal_metadata 模块是 OpenViking 资源处理流水线中的类型基础设施。它通过三个精心设计的枚举(VisitType、SizeType、RecursiveType)提供了一个正交、可扩展的资源元数据模型。
虽然目前实际的检测逻辑(判断一个文件应该是什么 VisitType、SizeType)尚未实现,但类型定义本身已经为未来的扩展奠定了良好的基础。这种"先定义接口再实现"的做法,使得系统的各个部分可以独立演进而不需要频繁修改契约。
对于新加入的开发者,理清这个模块与 resource_and_document_taxonomy 的区别是关键:一个是"怎么访问"(元数据),一个是"是什么格式"(分类)。两者共同构成了资源处理的完整认知框架。