"""每天回填 安全 FAQ 的语义索引. Revision ID: 563 Revises: 562 写入时的后台嵌入救不了两种情况,而这两种恰好是这个库的常态: ① **种子迁移**。答案库的条目主要由迁移批量落库(381 的 55 条、528 的 15 条、 562 的这一批),那条路径上没有任何应用代码可以挂钩后台任务。 ② **fire-and-forget 的正常失败**。写入时的嵌入是后台任务:进程重启、厂商限流、 当时 Key 还没配好,都会静默留下一条没有向量的条目 —— 而它的症状是「员工换个 说法就搜不到」,不是任何一条报错。 所以补齐这件事需要一个会自己重跑的东西。05:40 排在 05:10 的答案库过时标记之后: 过时标记可能会有人紧接着去改条目,改完的条目在下一轮被重嵌,顺序对得上。 没配 AI/LLM Key 时是干净的空跑(embedded=0),不报错。 """ from __future__ import annotations import uuid import sqlalchemy as sa from alembic import op revision = "563" down_revision = "562" branch_labels = None depends_on = None _KEY = "auditor.security_faq_embed_daily" _DISPLAY = "安全 FAQ · 语义索引回填" _DESCRIPTION = ( "每天 05:40(Asia/Shanghai)把还没有向量、或向量是用旧嵌入模型算的员工可见" "答案条目补齐,让 安全 FAQ 的语义检索覆盖整个库。\n" "【为什么需要 cron】条目主要由种子迁移批量落库,那条路径上没有应用代码可以" "挂钩;写入时的后台嵌入又是 fire-and-forget,进程重启 / 厂商限流 / Key 当时" "没配好都会静默留下一条搜不到的条目。\n" "逐条提交:算完一个向量就落库,中途厂商挂掉不会回滚已经付过费的向量;整批" "零进展就停,不空转。\n" "没配 AI/LLM Key 时是干净的空跑(embedded=0),语义不可用时 安全 FAQ 仍按" "词法检索工作。" ) # 05:40:排在 05:10 的答案库过时标记之后。 _CRON = "40 5 * * *" def upgrade() -> None: op.get_bind().execute( sa.text( "INSERT INTO scheduled_jobs " "(id, name, handler_key, display_name, description, " " trigger_type, cron_expr, timezone, enabled, config, " " created_at, updated_at) " "SELECT :id, :name, :k, :dn, :desc, 'cron', :cron, " " 'Asia/Shanghai', 1, '{}', NOW(), NOW() " "FROM DUAL WHERE NOT EXISTS (" " SELECT 1 FROM scheduled_jobs WHERE handler_key = :k)" ), { "id": str(uuid.uuid4()), "name": _KEY, "k": _KEY, "dn": _DISPLAY, "desc": _DESCRIPTION, "cron": _CRON, }, ) def downgrade() -> None: op.get_bind().execute( sa.text("DELETE FROM scheduled_jobs WHERE handler_key = :k"), {"k": _KEY}, )