🏠

存储层 schema 与查询范围模块

本文档是 storage_schema_and_query_ranges 父模块的概述文档,介绍该模块的设计意图和核心组件。详细内容请参阅各子模块文档。

模块概述

storage_schema_and_query_ranges 是 OpenViking 存储层的核心子模块,负责定义向量数据库集合的 schema 结构以及用于过滤和范围查询的表达式抽象。该模块位于存储层的最前端,为上层业务逻辑提供数据类型定义和查询条件构建的抽象接口。

从功能职责角度看,这个模块扮演着数据模型契约定义者的角色:它定义了集合应该包含哪些字段、这些字段的类型是什么、以及如何通过表达式来筛选符合特定条件的数据记录。所有的向量检索、标量排序、数据删除等操作都依赖于这里定义的 schema 和表达式类型。

核心组件

该模块包含两个主要的子模块:

1. collection_schemas (集合 schema 定义)

collection_schemas 子模块提供了预定义的集合 schema 模板和初始化逻辑。最核心的是 CollectionSchemas 类,它定义了统一上下文集合(Unified Context Collection)的 schema 结构。这个集合用于存储所有的上下文数据,包括:

  • 资源的向量嵌入(dense vector 和 sparse vector)
  • 元数据字段如 uri、type、context_type、level
  • 时间戳字段 created_at 和 updated_at
  • 分层信息字段 parent_uri、level(用于区分 L0/L1/L2 摘要/概览/详情层级)
  • 业务字段 name、description、tags、abstract

这个 schema 设计体现了 OpenViking 对上下文数据的多层次组织方式:同一个资源可以有不同的抽象层级,检索时可以根据 level 字段选择合适的粒度。

2. expr (过滤表达式 AST)

expr 子模块定义了用于构建查询过滤条件的抽象语法树(AST)。这是一个纯数据类型模块,不包含任何业务逻辑,仅定义了一组不可变的数据类(dataclass)来表示不同的过滤表达式类型。这些表达式类型包括:

  • 逻辑运算:And(且)、Or(或)
  • 比较运算:Eq(等于)、In(包含于)、Range(范围)、Contains(字符串包含)
  • 时间范围:TimeRange(专门用于时间字段的范围查询)
  • 原始表达式:RawDSL(用于传递后端特定的 DSL 结构)

这种设计模式在数据库查询构建器中非常常见,称为组合式查询构建(Composable Query Building)。通过将查询条件表示为不可变的数据结构,可以方便地进行组合、修改和序列化,而不用担心意外的副作用。

数据流关系

┌─────────────────────────┐     ┌──────────────────────────┐
│   上层业务代码           │     │   向量数据库适配器        │
│   (检索、删除等操作)     │────▶│   (CollectionAdapter)    │
└─────────────────────────┘     └───────────┬──────────────┘
                                            │
                                            ▼
                                  ┌─────────────────────┐
                                  │   _compile_filter   │
                                  │   (表达式编译)       │
                                  └──────────┬──────────┘
                                             │
                                             ▼
                                  ┌─────────────────────┐
                                  │   expr 模块         │
                                  │   FilterExpr        │
                                  │   (表达式定义)       │
                                  └─────────────────────┘

上层业务代码通过构造 FilterExpr 表达式对象来描述查询条件,然后传递给 CollectionAdapter.query()CollectionAdapter.delete() 等方法。适配器内部调用 _compile_filter() 方法将这些表达式编译为后端兼容的字典格式,最终传递给向量数据库的搜索接口。

设计决策

2.1 表达式不可变性

所有表达式类型都使用了 frozen=True 的 dataclass,这意味着实例创建后其属性不可修改。这是一个有意为之的设计选择,原因有两点:

第一,在多线程环境下,不可变对象天然是线程安全的,不需要加锁保护。由于这些表达式可能被多个协程同时使用(特别是在异步检索场景中),这个特性避免了潜在的并发问题。

第二,不可变性使得表达式可以被安全地缓存和复用。当构建复杂的过滤条件时,可以预先创建基础表达式组件,然后根据需要组合成更大的表达式,而不用担心原始组件被意外修改。

2.2 多格式兼容性

_compile_filter() 方法接受三种形式的过滤条件:FilterExpr 对象、普通的 dict,或者 None。这种设计体现了对不同使用习惯的兼容:

  • 使用表达式对象:类型安全,IDE 支持好,适合复杂的动态查询构建
  • 使用普通字典:简单直接,适合已知结构的手写查询
  • 使用 None:表示不添加过滤条件,返回所有结果

编译方法内部对三种形式一视同仁,最终都转换为后端需要的字典格式。这种宽松输入、标准化输出的模式降低了调用方的使用门槛。

2.3 RawDSL 的必要性

RawDSL 类型的引入是为了解决一个现实问题:不同的向量数据库后端可能支持不同的查询 DSL 语法。即使上层尝试用统一的表达式类型来描述查询,总会有一些后端特有的查询能力无法被完全抽象。通过允许直接传递原始 DSL 字典,调用方可以在需要时绕过抽象层,直接利用特定后端的高级特性。

与其他模块的关系

该模块被以下模块依赖:

  • collection_adapters_abstraction_and_backends:使用 expr 模块定义的表达式类型进行查询编译
  • vectorization_and_storage_adapters:在执行向量检索时传递过滤条件
  • retrieval_and_evaluation:检索模块需要使用这些表达式来构建查询条件

该模块依赖的核心类型包括:

  • datetime:TimeRange 表达式需要处理时间类型的起始和结束值
  • typing.Any:表达式中的 value 和 values 字段需要接受任意类型的比较值

使用建议

对于新加入的开发者,建议按以下方式使用这个模块:

  1. 优先使用表达式对象:当需要构建动态查询条件时,优先构造 AndOrEq 等表达式对象,而不是直接写字典。这样可以获得更好的类型检查和 IDE 支持。

  2. 注意 TimeRange 的边界语义TimeRange 的 start 采用闭区间(gte),end 采用开区间(lt)。这与 Python 的 slice 语义一致:包含起始时间,不包含结束时间。

  3. 了解 Range 的可选参数Range 表达式支持 gte/gt/lte/lt 四个可选参数,可以自由组合。但需要注意,同时设置 gte 和 gt 会产生语义冲突(前者是闭区间,后者是开区间),这种情况下的行为是未定义的。

  4. 利用空值优化AndOr 表达式在编译时会自动过滤掉 None 和空字典条件。这种设计允许代码构造可能为空的复合条件,而不需要额外的判空逻辑。

文档导航

On this page