"""`support_kb_articles` 补三列,给「飞书服务台知识库」这一路语料落脚。 飞书服务台(Helpdesk)的知识库导出模板每行是一条 FAQ: `1 级分类 | 问题 | 答案 | 相似问题 | 语言`。前两列直接对上现有的 `category` / `title`,答案对上 `body`,剩下三样在现有表里无处安放: - `search_aliases` —— 「相似问题」。这是这张表里**检索价值最高**的一列: 同一件事员工的问法有五六种,而 FAQ 的标题只写了其中一种。丢掉它等于 把飞书机器人这些年攒下来的问法全扔了。存成**换行分隔的纯文本**(不是 JSON):关键词那一路 `ILIKE` 能直接打,语义那一路直接拼进 embedding 文本,前端就是一个 textarea —— 与飞书模板里「分行输入」的形状一致。 - `language` —— 导出里 78 条 zh / 46 条 en,且多为同一件事的中英对照。 管理员在列表里要能一眼看出「这两条不是重复条目,是一对翻译」。**本次 不参与排序**(embedding v3 本身跨语言可召回),只做展示与筛选。 - `source_category` —— FAQ 的原始一级分类(「网络 & 飞连VPN」/「Google Workspace」…)。`category` 只有 6 个固定值,映射过去必然丢信息;原标签 留着,管理员按业务分组浏览时才认得出来。 三列全部可空,老行(manual / ai_ticket / feishu_doc)不受影响,不需要回填。 Revision ID: 578 Revises: 577 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "578" down_revision = "577" branch_labels = None depends_on = None TABLE = "support_kb_articles" # (列名, DDL 类型) COLUMNS = ( ("search_aliases", "TEXT NULL"), ("language", "VARCHAR(8) NULL"), ("source_category", "VARCHAR(64) NULL"), ) def _has_column(conn, table: str, column: str) -> bool: return conn.execute( sa.text( "SELECT 1 FROM information_schema.columns " "WHERE table_schema = DATABASE() AND table_name = :t " "AND column_name = :c" ), {"t": table, "c": column}, ).scalar() is not None def upgrade() -> None: conn = op.get_bind() if not sa.inspect(conn).has_table(TABLE): return for name, ddl in COLUMNS: if not _has_column(conn, TABLE, name): op.execute(f"ALTER TABLE `{TABLE}` ADD COLUMN `{name}` {ddl}") def downgrade() -> None: conn = op.get_bind() if not sa.inspect(conn).has_table(TABLE): return for name, _ddl in COLUMNS: if _has_column(conn, TABLE, name): op.execute(f"ALTER TABLE `{TABLE}` DROP COLUMN `{name}`")