Native CLI i18n: The TrustGraph CLI has built-in translation support that dynamically loads language strings. You can test and use different languages by simply passing the --lang flag (e.g., --lang es for Spanish, --lang ru for Russian) or by configuring your environment's LANG variable. Automated Docs Translations: This PR introduces autonomously translated Markdown documentation into several target languages, including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew, Arabic, Simplified Chinese, and Russian.
20 KiB
| layout | title | parent |
|---|---|---|
| default | 结构化数据技术规范 (第二部分) | Chinese (Beta) |
结构化数据技术规范 (第二部分)
Beta Translation: This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
概述
本规范解决了在 TrustGraph 结构化数据集成初始实施过程中发现的问题和差距,如 structured-data.md 中所述。
问题陈述
1. 命名不一致:"对象" 与 "行"
当前实现方案在整个代码中使用 "对象" 术语 (例如,ExtractedObject,对象提取,对象嵌入)。 这种命名方式过于通用,容易造成混淆:
"对象" 在软件中是一个 overloaded 的术语 (Python 对象,JSON 对象等)。 正在处理的数据本质上是表格数据,即具有定义模式的表格中的行。 "行" 更准确地描述了数据模型,并且与数据库术语一致。
这种不一致性出现在模块名称、类名称、消息类型和文档中。
2. 行存储查询限制
当前的行存储实现方案存在重大的查询限制:
自然语言匹配问题: 查询难以处理真实世界数据的变化。 例如:
很难在包含 "CHESTNUT ST" 的街道数据库中找到关于 "Chestnut Street" 的信息。
缩写、大小写差异和格式变化会破坏精确匹配查询。
用户期望语义理解,但存储系统只提供字面匹配。
模式演化问题: 更改模式会导致问题: 现有数据可能不符合更新后的模式。 表结构的变化可能会破坏查询和数据完整性。 没有明确的模式更新迁移路径。
3. 需要行嵌入
与问题 2 相关,系统需要行数据的向量嵌入,以便:
在结构化数据上进行语义搜索 (找到 "Chestnut Street" 时,数据包含 "CHESTNUT ST")。 模糊查询的相似性匹配。 结合结构化过滤器和语义相似性的混合搜索。 更好的自然语言查询支持。
嵌入服务已指定,但尚未实施。
4. 行数据摄取不完整
结构化数据摄取管道尚未完全投入使用:
存在诊断提示,用于对输入格式进行分类 (CSV,JSON 等)。 使用这些提示的摄取服务尚未集成到系统中。 没有将预结构化数据加载到行存储的端到端路径。
目标
模式灵活性: 允许模式演化,而不会破坏现有数据或需要迁移。 命名一致性: 在整个代码库中采用 "行" 术语。 语义可查询性: 通过行嵌入支持模糊/语义匹配。 完整的摄取管道: 提供将结构化数据加载到存储的端到端路径。
技术设计
统一的行存储模式
之前的实现方案为每个模式创建了一个单独的 Cassandra 表。 这在模式演化时会导致问题,因为表结构的变化需要迁移。
新的设计使用一个统一的表来存储所有行数据:
CREATE TABLE rows (
collection text,
schema_name text,
index_name text,
index_value frozen<list<text>>,
data map<text, text>,
source text,
PRIMARY KEY ((collection, schema_name, index_name), index_value)
)
列定义
| 列 | 类型 | 描述 |
|---|---|---|
collection |
text |
数据收集/导入标识符(来自元数据) |
schema_name |
text |
此行所遵循的模式名称 |
index_name |
text |
索引字段名称,以逗号分隔的字符串表示复合索引 |
index_value |
frozen<list<text>> |
索引值列表 |
data |
map<text, text> |
行数据,以键值对形式存储 |
source |
text |
可选的 URI,链接到知识图谱中的来源信息。空字符串或 NULL 表示没有来源。 |
索引处理
每行数据会被存储多次,对于模式中定义的每个索引字段,都会存储一次。主键字段被视为索引,没有特殊的标记,以提供未来的灵活性。
单字段索引示例:
模式定义 email 为索引
index_name = "email"
index_value = ['foo@bar.com']
复合索引示例:
模式定义在 region 和 status 上创建复合索引
index_name = "region,status" (字段名称按排序方式连接)
index_value = ['US', 'active'] (值与字段名称的顺序相同)
主键示例:
模式定义 customer_id 为主键
index_name = "customer_id"
index_value = ['CUST001']
查询模式
无论使用哪个索引,所有查询都遵循相同的模式:
SELECT * FROM rows
WHERE collection = 'import_2024'
AND schema_name = 'customers'
AND index_name = 'email'
AND index_value = ['foo@bar.com']
设计权衡
优点:
模式更改不需要更改表结构
行数据对 Cassandra 隐藏 - 字段的添加/删除是透明的
所有访问方法都具有一致的查询模式
不需要 Cassandra 的二级索引(这在大型环境中可能会很慢)
整个过程中都使用 Cassandra 的原生类型(map,frozen<list>)
权衡: 写入放大:每个行插入 = N 次插入(每个索引字段一次) 重复行数据带来的存储开销 类型信息存储在模式配置中,应用程序层进行转换
一致性模型
该设计接受某些简化:
-
没有行更新:系统是只追加的。这消除了关于更新同一行的多个副本的一致性问题。
-
模式更改容错性:当模式发生更改(例如,添加/删除索引)时,现有行保留其原始索引。旧行将无法通过新索引发现。如果需要,用户可以删除并重新创建模式以确保一致性。
分区跟踪和删除
问题
借助分区键 (collection, schema_name, index_name),高效删除需要知道所有要删除的分区键。仅通过 collection 或 collection + schema_name 删除需要知道所有具有数据的 index_name 值。
分区跟踪表
一个辅助查找表跟踪哪些分区存在:
CREATE TABLE row_partitions (
collection text,
schema_name text,
index_name text,
PRIMARY KEY ((collection), schema_name, index_name)
)
这使得能够高效地发现用于删除操作的分区。
行写入器行为
行写入器维护一个已注册的 (collection, schema_name) 对的内存缓存。 在处理一行时:
- 检查
(collection, schema_name)是否在缓存中 - 如果未缓存(对于此对的第一个行):
查找模式配置以获取所有索引名称
将条目插入到
row_partitions中,对于每个(collection, schema_name, index_name)将该对添加到缓存中 - 继续写入行数据
行写入器还监视模式配置更改事件。 当模式发生更改时,相关的缓存条目将被清除,以便下一行触发使用更新的索引名称重新注册。
这种方法确保:
查找表写入仅针对每个 (collection, schema_name) 对发生一次,而不是针对每行。
查找表反映了数据写入时活动的索引。
导入过程中发生的模式更改可以正确处理。
删除操作
删除集合:
-- 1. Discover all partitions
SELECT schema_name, index_name FROM row_partitions WHERE collection = 'X';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = '...' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table
DELETE FROM row_partitions WHERE collection = 'X';
删除集合 + 模式:
-- 1. Discover partitions for this schema
SELECT index_name FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table entries
DELETE FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
行嵌入
行嵌入允许对索引值进行语义/模糊匹配,从而解决自然语言不匹配的问题(例如,在搜索“Chestnut Street”时找到“CHESTNUT ST”)。
设计概述
每个索引值都会被嵌入,并存储在向量存储中(Qdrant)。在查询时,查询内容也会被嵌入,找到相似的向量,然后使用相关的元数据来查找 Cassandra 中的实际行。
Qdrant 集合结构
每个 (user, collection, schema_name, dimension) 元组对应一个 Qdrant 集合:
集合命名: rows_{user}_{collection}_{schema_name}_{dimension}
名称会被清理(非字母数字字符替换为 _,转换为小写,数字前缀添加 r_ 前缀)
理由: 允许通过删除匹配的 Qdrant 集合来干净地删除一个 (user, collection, schema_name) 实例;维度后缀允许不同的嵌入模型共存。
哪些内容会被嵌入
索引值的文本表示:
| 索引类型 | 示例 index_value |
要嵌入的文本 |
|---|---|---|
| 单字段 | ['foo@bar.com'] |
"foo@bar.com" |
| 组合 | ['US', 'active'] |
"US active" (空格连接) |
点结构
每个 Qdrant 点包含:
{
"id": "<uuid>",
"vector": [0.1, 0.2, ...],
"payload": {
"index_name": "street_name",
"index_value": ["CHESTNUT ST"],
"text": "CHESTNUT ST"
}
}
| Payload Field | Description |
|---|---|
index_name |
此嵌入所代表的索引字段。 |
index_value |
原始值列表(用于 Cassandra 查找)。 |
text |
嵌入的文本(用于调试/显示)。 |
注意:user、collection 和 schema_name 可以从 Qdrant 集合名称中推断出来。|
查询流程
- 用户查询用户 U、集合 X、模式 Y 中的“Chestnut Street”。|
- 嵌入查询文本。|
- 确定与前缀
rows_U_X_Y_匹配的 Qdrant 集合名称。| - 在匹配的 Qdrant 集合中搜索最接近的向量。|
- 获取包含
index_name和index_value的有效负载的匹配点。| - 查询 Cassandra:|
SELECT * FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '<from payload>' AND index_value = <from payload> - 返回匹配的行
可选:按索引名称过滤
查询可以选择性地按 index_name 进行过滤,以便在 Qdrant 中仅搜索特定字段:
"查找所有匹配 'Chestnut' 的字段" → 搜索集合中的所有向量
"查找 'Chestnut' 匹配的 street_name" → 过滤 payload.index_name = 'street_name'
架构
行嵌入遵循 GraphRAG 使用的 两阶段模式(图嵌入、文档嵌入):
第一阶段:嵌入计算 (trustgraph-flow/trustgraph/embeddings/row_embeddings/) - 消耗 ExtractedObject,通过嵌入服务计算嵌入,输出 RowEmbeddings
第二阶段:嵌入存储 (trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/) - 消耗 RowEmbeddings,将向量写入 Qdrant
Cassandra 行写入器是一个独立的并行消费者:
Cassandra 行写入器 (trustgraph-flow/trustgraph/storage/rows/cassandra) - 消耗 ExtractedObject,将行写入 Cassandra
所有三个服务都从同一个流中读取,从而使它们彼此解耦。 这允许: 独立地扩展 Cassandra 写入与嵌入生成与向量存储 如果不需要,可以禁用嵌入服务 一个服务中的故障不会影响其他服务 与 GraphRAG 管道保持一致的架构
写入路径
第一阶段(行嵌入处理器): 接收到 ExtractedObject 时:
- 查找模式以找到索引字段
- 对于每个索引字段: 构建索引值的文本表示 通过嵌入服务计算嵌入
- 输出一个包含所有计算向量的
RowEmbeddings消息
第二阶段(行嵌入-写入-Qdrant): 接收到 RowEmbeddings 时:
- 对于消息中的每个嵌入:
从
(user, collection, schema_name, dimension)确定 Qdrant 集合 如果需要,创建集合(首次写入时延迟创建) 使用向量和有效载荷进行更新
消息类型
@dataclass
class RowIndexEmbedding:
index_name: str # The indexed field name(s)
index_value: list[str] # The field value(s)
text: str # Text that was embedded
vectors: list[list[float]] # Computed embedding vectors
@dataclass
class RowEmbeddings:
metadata: Metadata
schema_name: str
embeddings: list[RowIndexEmbedding]
删除集成
Qdrant 集合通过在集合名称模式上进行前缀匹配来发现:
删除 (user, collection):
- 列出所有与前缀
rows_{user}_{collection}_匹配的 Qdrant 集合 - 删除每个匹配的集合
- 删除 Cassandra 行分区(如上文所述)
- 清理
row_partitions条目
删除 (user, collection, schema_name):
- 列出所有与前缀
rows_{user}_{collection}_{schema_name}_匹配的 Qdrant 集合 - 删除每个匹配的集合(处理多个维度)
- 删除 Cassandra 行分区
- 清理
row_partitions
模块位置
| 阶段 | 模块 | 入口点 |
|---|---|---|
| 阶段 1 | trustgraph-flow/trustgraph/embeddings/row_embeddings/ |
row-embeddings |
| 阶段 2 | trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/ |
row-embeddings-write-qdrant |
行嵌入查询 API
行嵌入查询是与 GraphQL 行查询服务不同的 API:
| API | 目的 | 后端 |
|---|---|---|
| 行查询 (GraphQL) | 对索引字段进行精确匹配 | Cassandra |
| 行嵌入查询 | 模糊/语义匹配 | Qdrant |
这种分离保持了关注点的清晰: GraphQL 服务专注于精确、结构化的查询 嵌入 API 处理语义相似性 用户工作流程:通过嵌入进行模糊搜索以查找候选对象,然后进行精确查询以获取完整的行数据
请求/响应模式
@dataclass
class RowEmbeddingsRequest:
vectors: list[list[float]] # Query vectors (pre-computed embeddings)
user: str = ""
collection: str = ""
schema_name: str = ""
index_name: str = "" # Optional: filter to specific index
limit: int = 10 # Max results per vector
@dataclass
class RowIndexMatch:
index_name: str = "" # The matched index field(s)
index_value: list[str] = [] # The matched value(s)
text: str = "" # Original text that was embedded
score: float = 0.0 # Similarity score
@dataclass
class RowEmbeddingsResponse:
error: Error | None = None
matches: list[RowIndexMatch] = []
查询处理器
模块:trustgraph-flow/trustgraph/query/row_embeddings/qdrant
入口点:row-embeddings-query-qdrant
处理器:
- 接收带有查询向量的
RowEmbeddingsRequest - 通过前缀匹配找到合适的 Qdrant 集合
- 搜索最近的向量,并可选择使用
index_name过滤器 - 返回包含匹配索引信息的
RowEmbeddingsResponse
API 网关集成
网关通过标准的请求/响应模式公开行嵌入查询:
| 组件 | 位置 |
|---|---|
| 调度器 | trustgraph-flow/trustgraph/gateway/dispatch/row_embeddings_query.py |
| 注册 | 将 "row-embeddings" 添加到 request_response_dispatchers 中,位于 manager.py |
流程接口名称:row-embeddings
流程蓝图中的接口定义:
{
"interfaces": {
"row-embeddings": {
"request": "non-persistent://tg/request/row-embeddings:{id}",
"response": "non-persistent://tg/response/row-embeddings:{id}"
}
}
}
Python SDK 支持
SDK 提供了用于行嵌入查询的方法:
# Flow-scoped query (preferred)
api = Api(url)
flow = api.flow().id("default")
# Query with text (SDK computes embeddings)
matches = flow.row_embeddings_query(
text="Chestnut Street",
collection="my_collection",
schema_name="addresses",
index_name="street_name", # Optional filter
limit=10
)
# Query with pre-computed vectors
matches = flow.row_embeddings_query(
vectors=[[0.1, 0.2, ...]],
collection="my_collection",
schema_name="addresses"
)
# Each match contains:
for match in matches:
print(match.index_name) # e.g., "street_name"
print(match.index_value) # e.g., ["CHESTNUT ST"]
print(match.text) # e.g., "CHESTNUT ST"
print(match.score) # e.g., 0.95
命令行工具
命令:tg-invoke-row-embeddings
# Query by text (computes embedding automatically)
tg-invoke-row-embeddings \
--text "Chestnut Street" \
--collection my_collection \
--schema addresses \
--index street_name \
--limit 10
# Query by vector file
tg-invoke-row-embeddings \
--vectors vectors.json \
--collection my_collection \
--schema addresses
# Output formats
tg-invoke-row-embeddings --text "..." --format json
tg-invoke-row-embeddings --text "..." --format table
典型用法示例
行嵌入查询通常用作模糊到精确查找流程的一部分:
# Step 1: Fuzzy search via embeddings
matches = flow.row_embeddings_query(
text="chestnut street",
collection="geo",
schema_name="streets"
)
# Step 2: Exact lookup via GraphQL for full row data
for match in matches:
query = f'''
query {{
streets(where: {{ {match.index_name}: {{ eq: "{match.index_value[0]}" }} }}) {{
street_name
city
zip_code
}}
}}
'''
rows = flow.rows_query(query, collection="geo")
这种两步模式可以实现: 当用户搜索 "Chestnut Street" 时,找到 "CHESTNUT ST" 检索包含所有字段的完整行数据 结合语义相似性和结构化数据访问
行数据导入
暂定于后续阶段。将与其他导入更改一起设计。
实施影响
当前状态分析
现有的实现包含两个主要组件:
| 组件 | 位置 | 行数 | 描述 |
|---|---|---|---|
| 查询服务 | trustgraph-flow/trustgraph/query/objects/cassandra/service.py |
~740 | 整体式:GraphQL 模式生成、过滤器解析、Cassandra 查询、请求处理 |
| 写入器 | trustgraph-flow/trustgraph/storage/objects/cassandra/write.py |
~540 | 每个模式的表创建、二级索引、插入/删除 |
当前的查询模式:
SELECT * FROM {keyspace}.o_{schema_name}
WHERE collection = 'X' AND email = 'foo@bar.com'
ALLOW FILTERING
新的查询模式:
SELECT * FROM {keyspace}.rows
WHERE collection = 'X' AND schema_name = 'customers'
AND index_name = 'email' AND index_value = ['foo@bar.com']
关键变更
-
查询语义简化: 新的模式仅支持对
index_value的精确匹配。当前的 GraphQL 过滤器(gt、lt、contains等)要么: 变为对返回数据的后过滤(如果仍然需要) 被移除,转而使用嵌入 API 进行模糊匹配 -
GraphQL 代码紧密耦合: 当前的
service.py包含了 Strawberry 类型生成、过滤器解析以及 Cassandra 相关的查询。添加另一个行存储后端会重复大约 400 行的 GraphQL 代码。
提出的重构
重构分为两部分:
1. 提取 GraphQL 代码
将可重用的 GraphQL 组件提取到共享模块中:
trustgraph-flow/trustgraph/query/graphql/
├── __init__.py
├── types.py # Filter types (IntFilter, StringFilter, FloatFilter)
├── schema.py # Dynamic schema generation from RowSchema
└── filters.py # Filter parsing utilities
这实现了: 在不同的行存储后端中实现重用 更清晰的分层 更容易独立地测试 GraphQL 逻辑
2. 实现新的表模式
重构特定于 Cassandra 的代码,以使用统一的表:
写入器 (Writer) (trustgraph-flow/trustgraph/storage/rows/cassandra/):
使用单个 rows 表,而不是每个模式的表
写入 N 个副本到每行(每个索引一个)
注册到 row_partitions 表
更简单的表创建(一次性设置)
查询服务 (Query Service) (trustgraph-flow/trustgraph/query/rows/cassandra/):
查询统一的 rows 表
使用提取的 GraphQL 模块进行模式生成
简化的过滤处理(仅在数据库级别进行精确匹配)
模块重命名
作为“object”→“row”命名清理的一部分:
| 当前 (Current) | 新 (New) |
|---|---|
storage/objects/cassandra/ |
storage/rows/cassandra/ |
query/objects/cassandra/ |
query/rows/cassandra/ |
embeddings/object_embeddings/ |
embeddings/row_embeddings/ |
新模块
| 模块 (Module) | 目的 (Purpose) |
|---|---|
trustgraph-flow/trustgraph/query/graphql/ |
共享 GraphQL 工具 |
trustgraph-flow/trustgraph/query/row_embeddings/qdrant/ |
行嵌入查询 API |
trustgraph-flow/trustgraph/embeddings/row_embeddings/ |
行嵌入计算(第一阶段) |
trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/ |
行嵌入存储(第二阶段) |