chore(prep): shared groundwork for the Sprint-2 parallel batch
第二批(T6~T11)协调提交: - 迁移 0005:kb 七表(entity/relation/rule/chunk vector1024+tsv 生成列/ asset/color/gap),HNSW+GIN 索引,FK 级联 - ORM:七个 Kb* 模型(pgvector Vector/TSVECTOR/Computed) - 配置:检索与 embedding、华为 JWKS、短信全参数组 - 错误码:40110/40111/42910/42911/61000 - 骨架路由:kb.py + auth_sms.py(已注册);依赖:tencentcloud-sdk-python-sms + spike extra(onnxruntime/opencv);gitignore:models/ 与 spike 输出
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"""Knowledge base tables for the Sprint-2 parallel batch (架构文档 §6.2 kb 域).
|
||||
|
||||
七张表一次落齐,任务批(T6/T7/T8)不再写迁移:
|
||||
kb_entity / kb_relation / kb_rule → T6 实体图谱、T7 规则引擎
|
||||
kb_chunk (vector 1024 + tsv) → T8 混合检索
|
||||
kb_asset / kb_color → T6/T8
|
||||
kb_gap → T8 无结果缺口埋点
|
||||
|
||||
索引决策:
|
||||
- kb_chunk.embedding 用 HNSW(cosine)——万级数据下召回延迟远优于 IVFFlat,
|
||||
且不需要先灌数据再训练聚类(IVFFlat 的 num_lists 依赖数据量)。
|
||||
- tsv 用 GIN;tsvector 是生成列(PG12+),应用层不手工维护。
|
||||
|
||||
Reversible: 是。降级按依赖倒序 drop。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from pgvector.sqlalchemy import Vector
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "0005"
|
||||
down_revision: str | None = "0004"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"kb_entity",
|
||||
sa.Column("entity_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("entity_type", sa.String(length=32), nullable=False),
|
||||
sa.Column("name", sa.String(length=128), nullable=False),
|
||||
sa.Column("attrs", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("status", sa.String(length=16), nullable=False, server_default=sa.text("'draft'")),
|
||||
sa.Column("version", sa.Integer(), nullable=False, server_default=sa.text("1")),
|
||||
sa.Column("source", sa.String(length=255), nullable=True),
|
||||
sa.Column("license", sa.String(length=64), nullable=True),
|
||||
sa.Column("updated_by", sa.String(length=64), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("entity_id"),
|
||||
sa.UniqueConstraint("entity_type", "name", name="uq_kb_entity_type_name"),
|
||||
)
|
||||
op.create_index("ix_kb_entity_type_status", "kb_entity", ["entity_type", "status"])
|
||||
|
||||
op.create_table(
|
||||
"kb_relation",
|
||||
sa.Column("relation_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("from_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("rel_type", sa.String(length=64), nullable=False),
|
||||
sa.Column("to_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("weight", sa.Numeric(4, 3), nullable=False, server_default=sa.text("1")),
|
||||
sa.Column("confidence", sa.Numeric(4, 3), nullable=False, server_default=sa.text("1")),
|
||||
sa.Column("evidence", sa.String(length=255), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("relation_id"),
|
||||
sa.UniqueConstraint("from_id", "rel_type", "to_id", name="uq_kb_relation_triple"),
|
||||
sa.ForeignKeyConstraint(["from_id"], ["kb_entity.entity_id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["to_id"], ["kb_entity.entity_id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("ix_kb_relation_to", "kb_relation", ["to_id"])
|
||||
|
||||
op.create_table(
|
||||
"kb_rule",
|
||||
sa.Column("rule_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("rule_key", sa.String(length=64), nullable=False),
|
||||
sa.Column("category", sa.String(length=32), nullable=False),
|
||||
sa.Column("version", sa.Integer(), nullable=False, server_default=sa.text("1")),
|
||||
sa.Column("status", sa.String(length=16), nullable=False, server_default=sa.text("'draft'")),
|
||||
sa.Column("priority", sa.Integer(), nullable=False, server_default=sa.text("60")),
|
||||
sa.Column("conditions", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("effects", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("is_hard", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("confidence", sa.Numeric(4, 3), nullable=False, server_default=sa.text("0.9")),
|
||||
sa.Column("evidence", sa.String(length=255), nullable=True),
|
||||
sa.Column("author", sa.String(length=64), nullable=True),
|
||||
sa.Column("reviewer", sa.String(length=64), nullable=True),
|
||||
sa.Column("published_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("rule_id"),
|
||||
sa.UniqueConstraint("rule_key", name="uq_kb_rule_key"),
|
||||
)
|
||||
op.create_index("ix_kb_rule_status_priority", "kb_rule", ["status", "priority"])
|
||||
|
||||
op.create_table(
|
||||
"kb_chunk",
|
||||
sa.Column("chunk_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("entity_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("chunk_type", sa.String(length=32), nullable=False, server_default=sa.text("'text'")),
|
||||
sa.Column("text", sa.Text(), nullable=False),
|
||||
sa.Column("embedding", Vector(1024), nullable=False),
|
||||
sa.Column("embedding_model", sa.String(length=64), nullable=False),
|
||||
sa.Column("embedding_version", sa.String(length=16), nullable=False),
|
||||
sa.Column(
|
||||
"tsv",
|
||||
postgresql.TSVECTOR(),
|
||||
sa.Computed("to_tsvector('simple', text)", persisted=True),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("chunk_id"),
|
||||
sa.ForeignKeyConstraint(["entity_id"], ["kb_entity.entity_id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("ix_kb_chunk_entity", "kb_chunk", ["entity_id"])
|
||||
op.execute(
|
||||
"CREATE INDEX ix_kb_chunk_embedding_hnsw ON kb_chunk "
|
||||
"USING hnsw (embedding vector_cosine_ops)"
|
||||
)
|
||||
op.execute("CREATE INDEX ix_kb_chunk_tsv_gin ON kb_chunk USING gin (tsv)")
|
||||
|
||||
op.create_table(
|
||||
"kb_asset",
|
||||
sa.Column("asset_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("entity_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("asset_type", sa.String(length=32), nullable=False),
|
||||
sa.Column("cos_key", sa.String(length=255), nullable=False),
|
||||
sa.Column("spec_check", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("tryon_ready", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("review_status", sa.String(length=16), nullable=False, server_default=sa.text("'pending'")),
|
||||
sa.Column("license", sa.String(length=64), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("asset_id"),
|
||||
sa.ForeignKeyConstraint(["entity_id"], ["kb_entity.entity_id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("ix_kb_asset_entity", "kb_asset", ["entity_id"])
|
||||
op.execute(
|
||||
"CREATE INDEX ix_kb_asset_tryon_ready ON kb_asset (tryon_ready) WHERE tryon_ready"
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"kb_color",
|
||||
sa.Column("color_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("season_code", sa.String(length=16), nullable=False),
|
||||
sa.Column("role", sa.String(length=8), nullable=False),
|
||||
sa.Column("lab_l", sa.Numeric(6, 2), nullable=False),
|
||||
sa.Column("lab_a", sa.Numeric(6, 2), nullable=False),
|
||||
sa.Column("lab_b", sa.Numeric(6, 2), nullable=False),
|
||||
sa.Column("hex", sa.String(length=7), nullable=False),
|
||||
sa.Column("name_cn", sa.String(length=64), nullable=False),
|
||||
sa.PrimaryKeyConstraint("color_id"),
|
||||
)
|
||||
op.create_index("ix_kb_color_season", "kb_color", ["season_code"])
|
||||
|
||||
op.create_table(
|
||||
"kb_gap",
|
||||
sa.Column("gap_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("query_text", sa.Text(), nullable=False),
|
||||
sa.Column("filter_ctx", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("hit_count", sa.Integer(), nullable=False, server_default=sa.text("1")),
|
||||
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("gap_id"),
|
||||
)
|
||||
op.create_index("ix_kb_gap_query", "kb_gap", ["query_text"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_kb_gap_query", table_name="kb_gap")
|
||||
op.drop_table("kb_gap")
|
||||
op.drop_index("ix_kb_color_season", table_name="kb_color")
|
||||
op.drop_table("kb_color")
|
||||
op.execute("DROP INDEX IF EXISTS ix_kb_asset_tryon_ready")
|
||||
op.drop_index("ix_kb_asset_entity", table_name="kb_asset")
|
||||
op.drop_table("kb_asset")
|
||||
op.execute("DROP INDEX IF EXISTS ix_kb_chunk_tsv_gin")
|
||||
op.execute("DROP INDEX IF EXISTS ix_kb_chunk_embedding_hnsw")
|
||||
op.drop_index("ix_kb_chunk_entity", table_name="kb_chunk")
|
||||
op.drop_table("kb_chunk")
|
||||
op.drop_index("ix_kb_rule_status_priority", table_name="kb_rule")
|
||||
op.drop_table("kb_rule")
|
||||
op.drop_index("ix_kb_relation_to", table_name="kb_relation")
|
||||
op.drop_table("kb_relation")
|
||||
op.drop_index("ix_kb_entity_type_status", table_name="kb_entity")
|
||||
op.drop_table("kb_entity")
|
||||
Reference in New Issue
Block a user