API/sag_align

核心函数

sag_align

SAG ↔ OCTX 数据模型对齐:ID 映射、基数校验、字段长度检查、实体去重与结构化导出。

sag_align 提供 SAG 应用数据模型与 OCTX sag-structured/0.1 之间的对齐工具, 用于导入/导出场景下的 ID 映射、基数校验、字段长度检查、实体去重和结构化数据导出。

函数

函数作用
translate_id()UUIDv4 → UUIDv7(双向可逆映射)
revert_id()UUIDv7 → UUIDv4(过渡期向后兼容)
ensure_octx_ids()批量转换记录中的 UUIDv4 字段
default_sag_field_profile()返回默认 SAG 字段画像
validate_cardinality_for_sag()检查 Event 是否关联多个 Chunk
validate_field_lengths_for_sag()校验所有文本字段的 SAG 列长度限制
find_entity_dedup_matches()查找 Entity name 重复项
classify_ids()统计各 ID 类型的出现次数
validate_sag_structured()检查完整覆盖(Section 6 无孤立记录)
validate_sag_constraints()检查结构约束(Section 3-5)
write_structured_to_workspace()写入并验证完整结构化数据到 OCTX workspace

数据类型

类型说明
SagFieldProfile各字段的 SAG 数据库列长度上限
SagCardinalityIssueEvent 关联了 2+ 个 Chunk
SagFieldIssue某记录字段超出列长度
SagDedupMatch两个 Entity 因相同 name 被标记为重复
SagCoverageIssue完整覆盖规则违规
SagConstraintIssue结构约束违规
SagAlignmentReport聚合的对齐校验报告

translate_id()

python
def translate_id(uuid_v4: str) -> str

将 UUIDv4 映射为 UUIDv7(修改版本半字节 4→7)。结果是可逆的稳定身份, 可在 OCTX Package 内使用,之后用 revert_id() 恢复。

参数作用
uuid_v4有效的 UUIDv4 字符串(36 字符)

返回值:修改版本半字节后的 UUIDv7 字符串,小写。

异常:不是合法 UUIDv4 时抛出 ValueError

python
from octx.sag_align import translate_id

v7 = translate_id("550e8400-e29b-41d4-a716-446655440000")
# → "550e8400-e29b-71d4-a716-446655440000"

revert_id()

python
def revert_id(uuid_v7: str) -> str

反向 translate_id(),将 OCTX 格式 ID 恢复为 UUIDv4。 这是一个过渡期向后兼容工具,在 SAG 完全迁移到 UUIDv7 之前使用。

参数作用
uuid_v7版本半字节为 7 的 UUID 字符串

异常:不是有效的已转换 OCTX ID 时抛出 ValueError

python
from octx.sag_align import revert_id

v4 = revert_id("550e8400-e29b-71d4-a716-446655440000")
# → "550e8400-e29b-41d4-a716-446655440000"

ensure_octx_ids()

python
def ensure_octx_ids(
    records: Iterable[dict[str, Any]],
    *,
    id_fields: Collection[str] = {"id", "document_id", "chunk_id", "event_id", "entity_id", "parent_id"},
) -> Iterator[dict[str, Any]]

批量遍历记录,将 id_fields 集合中的 UUIDv4 字段自动转为 UUIDv7。 非字典对象、已是 UUIDv7、或非 UUID 的值原样通过。

参数作用
records字典记录的可迭代对象
id_fields关键字参数;需要转换的字段名集合
python
from octx.sag_align import ensure_octx_ids

records = [{"id": "550e8400-e29b-41d4-a716-446655440000", "title": "doc"}]
result = list(ensure_octx_ids(records))
# result[0]["id"] → "550e8400-e29b-71d4-a716-446655440000"

default_sag_field_profile()

python
def default_sag_field_profile() -> SagFieldProfile

返回默认的 SagFieldProfile 实例。

python
from octx.sag_align import default_sag_field_profile

profile = default_sag_field_profile()
# SagFieldProfile(id=36, name=512, entity_type=100, ...)

validate_cardinality_for_sag()

python
def validate_cardinality_for_sag(
    chunk_events: Iterable[dict[str, Any]],
    *,
    events: Collection[str] | None = None,
) -> list[SagCardinalityIssue]

检查哪些 Event 关联了 2+ 个 Chunk。SAG 数据模型每个 Event 只存一个来源 Chunk, 从 OCTX(允许 N:M)导入时需要处理这类情况。

参数作用
chunk_eventsrelations/chunk-events.jsonl 的记录
events关键字参数;可选限制检查范围的 Event ID 集合

返回值:每个违规 Event 一条 SagCardinalityIssue,按 Event ID 排序。

python
from octx.sag_align import validate_cardinality_for_sag

issues = validate_cardinality_for_sag(chunk_events)
for issue in issues:
    print(issue.event_id, issue.chunk_ids)

validate_field_lengths_for_sag()

python
def validate_field_lengths_for_sag(
    chunks: Iterable[dict[str, Any]],
    events: Iterable[dict[str, Any]],
    entities: Iterable[dict[str, Any]],
    chunk_events: Iterable[dict[str, Any]],
    event_entities: Iterable[dict[str, Any]],
    *,
    profile: SagFieldProfile | None = None,
) -> list[SagFieldIssue]

检查五个 JSONL 文件中所有文本字段是否超过 SAG 数据库列长度上限。 长度 0 表示无限制(如 chunk.text)。

参数作用
chunksdata/chunks.jsonl 记录
eventsdata/events.jsonl 记录
entitiesdata/entities.jsonl 记录
chunk_eventsrelations/chunk-events.jsonl 记录
event_entitiesrelations/event-entities.jsonl 记录
profile关键字参数;自定义字段画像,默认使用 default_sag_field_profile()
python
from octx.sag_align import validate_field_lengths_for_sag

issues = validate_field_lengths_for_sag(chunks, events, entities, ce, ee)

find_entity_dedup_matches()

python
def find_entity_dedup_matches(
    entities: Iterable[dict[str, Any]],
    *,
    normalize: bool = True,
) -> list[SagDedupMatch]

查找 entitiesname 值完全相同的记录(NFC 归一化 + 可选 case folding)。 每个名称只报告第一对匹配。

参数作用
entitiesdata/entities.jsonl 记录
normalize关键字参数;为 True 时 NFC 归一化并忽略大小写比较
python
from octx.sag_align import find_entity_dedup_matches

matches = find_entity_dedup_matches(entities)

classify_ids()

python
def classify_ids(
    records: Iterable[dict[str, Any]],
    *,
    id_fields: Collection[str] = {"id", "document_id", "chunk_id", "event_id", "entity_id", "parent_id"},
) -> dict[str, int]

统计记录中各 ID 类型的出现次数。返回 {"octx": N, "v4": N, "other": N}

参数作用
records字典记录的可迭代对象
id_fields关键字参数;要检查的字段名集合
python
from octx.sag_align import classify_ids

stats = classify_ids(all_records)
# {"octx": 5, "v4": 2, "other": 0}

validate_sag_structured()

python
def validate_sag_structured(
    chunks: Iterable[dict[str, Any]],
    events: Iterable[dict[str, Any]],
    entities: Iterable[dict[str, Any]],
    chunk_events: Iterable[dict[str, Any]],
    event_entities: Iterable[dict[str, Any]],
    *,
    document_ids: Collection[str] | None = None,
) -> list[SagCoverageIssue]

检查 sag-structured/0.1 Section 6 完整覆盖规则 —— 确保整条链没有孤立记录:

  1. document_has_chunk — 每篇 Concept Document 至少有一个 Chunk
  2. chunk_in_relation — 每个 Chunk 至少出现在一条 Chunk-Event relation 中
  3. event_in_chunk_relation — 每个 Event 至少出现在一条 Chunk-Event relation 中
  4. event_in_entity_relation — 每个 Event 至少出现在一条 Event-Entity relation 中
  5. entity_in_relation — 每个 Entity 至少出现在一条 Event-Entity relation 中
参数作用
chunks, events, entities, chunk_events, event_entities五个 JSONL 文件的记录
document_ids关键字参数;可选,提供时检查 Rule 1
python
from octx.sag_align import validate_sag_structured

issues = validate_sag_structured(chunks, events, entities, ce, ee, document_ids=["doc-1"])
for issue in issues:
    print(issue.rule, issue.record_id)

validate_sag_constraints()

python
def validate_sag_constraints(
    chunks: Iterable[dict[str, Any]],
    events: Iterable[dict[str, Any]],
    entities: Iterable[dict[str, Any]],
    chunk_events: Iterable[dict[str, Any]],
    event_entities: Iterable[dict[str, Any]],
) -> list[SagConstraintIssue]

检查 sag-structured/0.1 Sections 3-5 的结构约束:

Chunks — 必填字段(id, document_id, ordinal, text)、text 非空、ordinal 同一 document_id 内唯一

Events — 必填字段(id, title, content)、title/content 非空、parent_id/level 一致性、子 Event level = parent.level + 1、父子关系无成环

Relations — (chunk_id, event_id) 和 (event_id, entity_id) 组合不得重复

引用完整性 — 所有 chunk_id / event_id / entity_id 引用必须存在

UUIDv7 格式 — 所有 ID 字段必须为标准小写 UUIDv7

python
from octx.sag_align import validate_sag_constraints

issues = validate_sag_constraints(chunks, events, entities, ce, ee)
for issue in issues:
    print(issue.code, issue.record_id)

write_structured_to_workspace()

python
def write_structured_to_workspace(
    workspace: str | Path,
    *,
    chunks: Iterable[dict[str, Any]] = (),
    events: Iterable[dict[str, Any]] = (),
    entities: Iterable[dict[str, Any]] = (),
    chunk_events: Iterable[dict[str, Any]] = (),
    event_entities: Iterable[dict[str, Any]] = (),
    profile: SagFieldProfile | None = None,
) -> SagAlignmentReport

将外部结构化记录写入 OCTX workspace:

  1. 所有 UUIDv4 ID 自动映射为 UUIDv7
  2. 执行基数字段长度校验和去重检查
  3. 将非空 stream 写入对应 JSONL 文件
  4. 返回聚合的校验报告

写入完成后 workspace 可直接用于 create_octx()

参数作用
workspace已有 OCTX workspace 路径
chunks / events / entities / chunk_events / event_entities关键字参数;各 stream 记录
profile关键字参数;自定义字段画像
python
from octx.sag_align import write_structured_to_workspace

report = write_structured_to_workspace(
    "my-workspace",
    chunks=[...],
    events=[...],
    entities=[...],
    chunk_events=[...],
    event_entities=[...],
)

SagFieldProfile

python
@dataclass(frozen=True)
class SagFieldProfile:
    id: int = 36            # UUID 字符串长度
    name: int = 512         # entity.name
    entity_type: int = 100  # entity.type
    title: int = 512        # event.title, document.title
    summary: int = 4096     # event.summary
    category: int = 128     # event.category
    description: int = 4096 # entity.description, relation description
    text: int = 0           # chunk.text / event.content(0 表示无限制)

SagCardinalityIssue

python
@dataclass(frozen=True)
class SagCardinalityIssue:
    event_id: str
    chunk_ids: tuple[str, ...]  # 该 Event 关联的所有 Chunk ID
    primary_hint: str | None = None  # 建议主 Chunk

SagFieldIssue

python
@dataclass(frozen=True)
class SagFieldIssue:
    path: str               # JSONL 文件路径(如 "data/entities.jsonl")
    line: int               # 1-indexed 行号
    record_id: str | None
    field: str              # 超限字段名
    value_length: int
    limit: int

SagDedupMatch

python
@dataclass(frozen=True)
class SagDedupMatch:
    entity_id_a: str
    entity_id_b: str
    name: str               # 匹配的 name 值
    same_package: bool = True  # 同一包内重复

SagCoverageIssue

python
@dataclass(frozen=True)
class SagCoverageIssue:
    rule: str               # 规则标识符(详见 validate_sag_structured)
    record_id: str | None   # 违规记录的 ID
    description: str        # 人类可读的说明

SagConstraintIssue

python
@dataclass(frozen=True)
class SagConstraintIssue:
    code: str               # 结构约束代码(如 chunk_text_empty)
    record_id: str | None   # 违规记录的 ID
    description: str        # 人类可读的说明

SagAlignmentReport

python
@dataclass
class SagAlignmentReport:
    cardinality_issues: list[SagCardinalityIssue]
    field_issues: list[SagFieldIssue]
    dedup_within: list[SagDedupMatch]
    coverage_issues: list[SagCoverageIssue]
    constraint_issues: list[SagConstraintIssue]
    id_translations: int        # UUIDv4→v7 转换次数
    unchanged_ids: int          # 保持不变的 ID 数

    # 属性
    has_cardinality_conflicts: bool  # cardinality_issues 非空?
    has_field_issues: bool           # field_issues 非空?
    has_dedup_within: bool           # dedup_within 非空?
    has_coverage_issues: bool        # coverage_issues 非空?
    has_constraint_issues: bool      # constraint_issues 非空?

    # 方法
    def merge(self, other: SagAlignmentReport) -> SagAlignmentReport
        # 合并两个报告(例如来自不同 Package)