之前知识只能"存",不能"找"。这一版把链路补上:切片 → 向量 → 打分 → 带来源返回。
- 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:
+39
-1
@@ -377,7 +377,45 @@ Query:`doc_type`(默认 `postmortem`)。来源可以是任务或工单;
|
||||
**工单验收通过时,后端会自动调用这条逻辑**(`generated_by=rule`,`doc_type=postmortem`,
|
||||
正文开头固定一句"这份草稿由系统根据来源自动整理,请先核对再发布"),所以用户不需要记得点。
|
||||
|
||||
### 5.7 知识的错误码
|
||||
### 5.7 检索 `GET /knowledge/search`
|
||||
|
||||
Query:
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `q` | 必填,1–200 字 |
|
||||
| `limit` | 1–30,默认 10 |
|
||||
| `mode` | `hybrid`(默认)/ `vector` / `keyword`,后两个用于对比效果 |
|
||||
|
||||
返回 `{query, mode, items[]}`,每项:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `item_id` / `title` | 来源知识,前端"打开原文"用 |
|
||||
| `doc_type` / `heading` | 类型与所在小节 |
|
||||
| `idx` / `content` | 第几片、切片正文 |
|
||||
| `score` | 加权总分,低于 `SEARCH_MIN_SCORE`(0.06)的结果**不返回** |
|
||||
| `vector_score` / `lexical_score` | 向量分与 trigram 词法分,便于排查"为什么没搜到" |
|
||||
|
||||
三条约定:
|
||||
|
||||
1. **只检索 `published`**。草稿是还没被人认可的话,进检索等于把猜测当结论引用出去——过滤写在 SQL 里,调用方改不了。
|
||||
2. **结果必须带来源**,能点回原文。这是 D3(知识必须有来源)的延伸:不给"出处不存在"的答案。
|
||||
3. **宁可返回空,也不凑数**。检索失败比误导便宜。
|
||||
|
||||
发布(`5.5` 的 `publish`)时会自动重建索引,用户发布完立刻能搜到;
|
||||
但改完正文不会自动重建,需要调用下面这条。
|
||||
|
||||
### `POST /knowledge/{item_id}/reindex` — 重建切片
|
||||
|
||||
返回 `{item_id, chunk_count, searchable}`。`searchable=false` 表示内容已建好切片但还不是 `published`,
|
||||
搜不到——这不是错误。
|
||||
|
||||
实现:切片存 `chunk` 表(`content` + `embedding real[]`),相似度用 SQL 函数 `app_cosine()` 计算。
|
||||
本机没有可用的 pgvector 实例(Docker 起不来,fnsvr-01 明确不动),所以先用 `real[]` 把链路跑通;
|
||||
将来换 pgvector 只改列类型与索引,接口不变。换 embedding 模型(`EMBEDDING_PROVIDER`)后
|
||||
**必须整体重建**,否则新旧向量混在一张表里,分数没法比较。
|
||||
|
||||
### 5.8 知识的错误码
|
||||
|
||||
| 码 | HTTP | 含义 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -272,21 +272,32 @@
|
||||
| 3.5 | 手动沉淀:任务/工单详情里"沉淀成知识" | 0.5 | [x] 有 e2e |
|
||||
| 3.6 | 手记:没有来源的知识(唯一允许 `source_kind=manual` 的入口) | 0.5 | [x] 手记页 + 列表 |
|
||||
| 3.7 | 检索:标题/正文/摘要子串 + 类型/状态筛选 + 游标分页 | 1.0 | [x] trigram 索引在 0002 已建 |
|
||||
| 3.8 | 切片与向量索引(`chunk` 表、pgvector HNSW) | 2.0 | [ ] 见下方说明 |
|
||||
| 3.9 | 安卓知识列表 | 1.0 | [ ] 与工单一起延后到编译链就绪 |
|
||||
| 3.10 | 文档与 e2e | 1.0 | [x] `web/e2e/knowledge.mjs` 7 项断言绿 |
|
||||
| 3.8 | 切片与向量索引(`chunk` 表、pgvector HNSW) | 2.0 | [x] **用 `real[]` + SQL 余弦先跑通**(见下方说明) |
|
||||
| 3.8.1 | 切片:按标题归段、代码块整块保留、超长按句切、相邻片重叠 80 字 | 0.5 | [x] `app/domain/chunking.py` |
|
||||
| 3.8.2 | 向量:`hash`(字符二元组)与 `openai` 两种提供方,换实现不动业务 | 0.5 | [x] `app/services/embedding.py` |
|
||||
| 3.8.3 | 混合检索:向量 0.6 + trigram 0.4,低于阈值不返回,只认 `published` | 1.0 | [x] 5 个用例 + `e2e/retrieval.mjs` 6 项 |
|
||||
| 3.8.4 | 索引生命周期:发布自动建、改正文需 `reindex`、换模型整体重建 | 0.5 | [x] `POST /knowledge/{id}/reindex` |
|
||||
| 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) |
|
||||
| 3.10 | 文档与 e2e | 1.0 | [x] `knowledge.mjs` 7 项 + `retrieval.mjs` 6 项 |
|
||||
|
||||
### M3 完成定义(DOD)
|
||||
- [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
|
||||
- [x] 每条知识都能追到来源(除手记),点开能看到来源事项
|
||||
- [x] 非管理员发布被拒;跨租户看不到别人的知识
|
||||
- [ ] 向量切片与语义检索(需要独立 pgvector 实例真正跑起来)
|
||||
- [x] 切片与语义检索跑通(`real[]` + SQL 余弦;pgvector 仍待独立实例)
|
||||
- [ ] 真实用两周:新同事靠知识库自己解决过一次问题(需要真实团队)
|
||||
|
||||
**关于 3.8 的说明**:本机 Docker 起不来、fnsvr-01 上也没有 pgvector 扩展,
|
||||
所以 `deploy/docker-compose.vector.yml` 与 `VECTOR_DATABASE_URL` 只有配置,**没有跑过**。
|
||||
按"写完不算完成"的规矩,它在真跑起来之前保持未勾。切片表设计要点已写进
|
||||
`2.Design/PRODUCT-PLAN.md` 的 D8,落地时不要改 `EMBEDDING_DIM`(改了要重算全量)。
|
||||
**关于 3.8 的说明(2026-10-29 已落地,但与计划不同)**:计划里写的是 pgvector + HNSW,
|
||||
实际本机 Docker 起不来、fnsvr-01 上也没有 pgvector 扩展且用户明确要求别动那台机器,
|
||||
所以先用 `chunk` 表(`embedding real[]`)+ SQL 函数 `app_cosine()` 把整条链路跑通:
|
||||
切片 → 向量(内置 hash 提供方)→ 混合打分(向量 0.6 + trigram 0.4)→ 阈值过滤 → 带来源返回。
|
||||
`deploy/docker-compose.vector.yml` 与 `VECTOR_DATABASE_URL` 保留,等向量实例真起来之后再切过去,
|
||||
换 pgvector 只改列类型与索引,应用层接口不变。
|
||||
|
||||
两条必须记住的约束:
|
||||
1. **内置 hash 向量不是语义模型**,只是"零依赖、可复现"的基线;上线前换 `EMBEDDING_PROVIDER=openai`
|
||||
并整体重建(`reindex_workspace`),否则新旧向量混表、分数不可比。
|
||||
2. **检索只认 `published`**,草稿永远搜不到——这条写在 SQL 的 WHERE 里,不靠调用方自觉。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user