feat(knowledge): 语义检索打通——切片、混合打分、只搜已发布
CI / backend (push) Canceled after 0s

之前知识只能"存",不能"找"。这一版把链路补上:切片 → 向量 → 打分 → 带来源返回。

- chunk 表 + 迁移 0006:正文按标题归段切、代码块整块保留、超长按句切、
  相邻片重叠 80 字;RLS 与 GRANT 与其它租户表一致
- 向量提供方做成协议:内置 hash(字符二元组,零依赖可复现,作为基线)
  与 OpenAI 兼容端点两种,换实现不动业务代码
- 检索是混合打分:向量 0.6 + pg_trgm 0.4。专有编号、单号这类必须原样命中的词
  靠词法兜,换个说法表达的同一件事靠向量兜
- 三条纪律:只搜 published(过滤写在 SQL 里)、低于阈值宁可不返回、
  每条命中都带来源标题与 item_id
- 发布时自动建索引,改完正文用 POST /knowledge/{id}/reindex 重建
- Web 知识页加检索框与命中卡片;新增 e2e/retrieval.mjs 6 项

为什么不是 pgvector:本机 Docker 起不来、fnsvr-01 没有该扩展且明确不动。
先用 real[] + SQL 余弦把链路跑通,将来换列类型与索引即可,接口不变。

后端 79 passed,e2e smoke/knowledge/richtext/retrieval 全绿。
This commit is contained in:
dadao
2026-10-29 04:28:09 +08:00
parent d509f0c335
commit d079564d6c
16 changed files with 1126 additions and 22 deletions
+3 -4
View File
@@ -8,13 +8,12 @@
from __future__ import annotations
import uuid
from typing import Any
import pytest
from httpx import AsyncClient
import uuid
from app.config import get_settings
from app.services import signing
@@ -178,7 +177,7 @@ async def test_raw_url_uses_short_lived_signature(client: AsyncClient) -> None:
"""图片直链自带短期签名:不需要把登录令牌贴进 URL,也不需要 Authorization 头。"""
user, ws, item_id = await _ws_with_item(client)
body = b"\x89PNG\r\n\x1a\n signed png"
created = unwrap(await _upload(client, user, ws["id"], item_id, body, "签名图.png"))
unwrap(await _upload(client, user, ws["id"], item_id, body, "签名图.png"))
listed = unwrap(
await client.get(
@@ -225,4 +224,4 @@ async def test_raw_url_signature_is_workspace_scoped(client: AsyncClient) -> Non
f"{BASE}/workspaces/{ws_b['id']}/attachments/{created['id']}/raw?{query}"
)
assert crossed.status_code == 403
assert crossed.json()["code"] == 8003
assert crossed.json()["code"] == 8003