# BE05 — 知识入库体系总设计(Knowledge Ingest) > **版本:V1.3 | 当前实现规范** > **技术栈**:Go 后端 + SQLite + Vue3。 > **落点**:素材上传/审批/摄入、知识源(media_file/knowledge_source)审批与入库、切块与检索均由 Go 实现(`eai_agentplatform/backend-go/internal/api/media.go`、`knowledge.go`、`knowledge_pipeline.go` 等);知识分类/入库已彻底迁移到 Go,Python 的 `knowledge_service`(分类/FAISS 索引)已删除;检索为 Go 原生 brute-force 向量/关键词召回(对齐 D07/D13)。 --- ## 1. 模块职责 知识入库体系负责把两类知识源,统一经过「审批前置」流入三张消费表,最终支撑「可浏览 / 可考试 / 可 AI 检索」。 | 知识源类型 | 载体 | 审批单元 | 流入表 | |-----------|------|---------|--------| | **非结构化素材** | PPT/PDF/Word/视频/图片 | 逐文件 | knowledge_chunk(+ 预览) | | **结构化知识** | 知识源 md(docs/knowledge_source/) | 逐文档 | product / question / knowledge_chunk | **核心原则(与 P02/P04 对齐)**:所有知识只有 `approved` 才生效;`pending` / `rejected` 一律不解析、不进库、前台不可见。 --- ## 2. 完整目录结构 ``` eai_agentplatform/ ├── docs/ │ └── knowledge_source/ # 知识源文档(权威源,纳入 git 版本管理) │ ├── README.md # 格式契约 + 答案契约 + 审批状态机 │ ├── 01_通用规则.md │ ├── 02_资本咨询类.md │ ├── 03_资质认定辅导类.md │ ├── 04_AI咨询与实施类.md │ └── 05_企业级AI工具与平台.md │ ├── backend-go/ │ ├── config/ai_config.json # AI 路由配置(含 embed_gen/llm_fallback) │ ├── internal/ │ │ ├── api/ │ │ │ ├── media.go # 素材上传/审批/预览/提取管线 │ │ │ ├── knowledge.go # 知识源扫描/审批/摄入/解析 │ │ │ ├── knowledge_pipeline.go # 知识检索/分类/问答管线 │ │ │ └── knowledge_index.go # 知识索引重建(Go 原生) │ │ ├── model/ │ │ │ ├── media_file.go # 素材表 │ │ │ ├── knowledge_source.go # 知识源文档表 │ │ │ ├── knowledge_chunk.go # 知识块表 │ │ │ └── question.go # 考试题目 │ │ └── store/ # DB 初始化 + GORM 连接 │ └── data/ │ ├── eai_agentplatform.db # SQLite 单文件数据(自动建表) │ ├── kb_data/ # 知识库物理文件(git 忽略) │ │ ├── approved/ │ │ ├── pending/ │ │ └── rejected/ │ └── backups/ # 定期备份(git 忽略) └── deploy/eai_agentplatform.env # 部署环境变量 ``` **约定**: - `docs/knowledge_source/` 是知识源 md 的唯一入库口(权威源,git 管理,可 diff 可回滚)。 - 运行时摄入**直接读取**该目录,不复制(单一事实源,避免双份漂移)。 - 素材物理文件存储在 `data/kb_data/`(git 忽略),分为 `approved/pending/rejected` 三个子目录。 - 数据库为 SQLite 单文件(`data/eai_agentplatform.db`),GORM 首次启动自动建表。 --- ## 3. 两条流程的状态机 ### 流程 A:非结构化素材 ``` 员工上传 ──▶ media_file(pending) ──审批──▶ approved ──▶ 异步转换 管理员上传 ──▶ media_file(approved) ──▶ 立即异步转换 │ ▼ ┌──────────────────────────┐ │ 文档: LibreOffice→PDF→ │ │ pdftotext 提取→切片 │ │ 视频/图片: 仅预览 │ └──────────┬───────────────┘ ▼ knowledge_chunk ``` ### 流程 B:结构化知识源(知识源 md) ``` 知识源 md(docs/knowledge_source/) │ ▼ 【扫描】POST /api/knowledge/scan │ 解析 front-matter → 为每个 md 建 knowledge_source 记录 ▼ knowledge_source(pending) ──审批──▶ approved ──▶ 解析摄入 │ │ │ ├─→ product(status=active) │ ├─→ question(status=active) │ └─→ knowledge_chunk(source=knowledge_source) │ └─驳回──▶ rejected(理由必填,不生效) ``` **对称性**:素材「审批通过→转换提取」,知识源「审批通过→解析摄入」。两条通道最终都汇入 `knowledge_chunk` 供 AI 检索。 --- ## 4. 数据模型 ### 4.1 `knowledge_source` 表 ``` table: knowledge_source columns: id uint GORM primary key (auto-increment) title string(256) 文档标题 file_path string(512) unique 相对路径(docs/knowledge_source/ 下) category string(64) general/capital_consulting/qualification_counseling/ai_consist/ai_tools_platform domain string(16) company / product / sales(默认 product) source_version string(32) 源版本,如 V1.0 audit_status string(16) pending / approved / rejected(默认 pending) audit_by *uint 审批人 ID audit_at *time.Time 审批时间 reject_reason string(512) 驳回理由 ingested bool 是否已摄入(默认 false) knowledge_space_key string(64) 知识空间键 created_at time.Time updated_at time.Time indexes: idx_ks_status (audit_status) idx_ks_category (category) ``` ### 4.2 `knowledge_chunk` 表(知识块,AI 检索最小单元) ``` table: knowledge_chunk columns: id uint GORM primary key media_file_id *uint 二选一(素材来源) knowledge_source_id *uint 二选一(知识源来源) source_type string(32) pdf/doc/md(来源类型标识) source_id string(64) 来源 ID 字符串 knowledge_space_key string(64) 知识空间键 chunk_index int 块序号 content text 内容正文 created_at time.Time indexes: idx_mf (media_file_id) idx_ks (knowledge_source_id) idx_key (knowledge_space_key) ``` ### 4.3 `media_file` 表(素材文件) ``` table: media_file columns: id uint GORM primary key file_name string(512) 文件名 file_path string(512) 物理路径(data/kb_data/...) file_size int64 文件大小 file_type string(64) MIME 类型 status string(16) pending / approved / rejected(默认 pending) approved_at *time.Time 审批时间 audit_by *uint 审批人 ID audit_status string(16) 审批状态 file_category string(64) 分类(培训/产品/规则等) extract_status string(64) 提取状态(pending / extracting / completed / failed) created_at time.Time updated_at time.Time indexes: idx_status (status) idx_category (file_category) ``` --- ## 5. 模块清单 | # | 模块 | 状态 | 职责 | 落点 | |---|------|------|------|------| | 1 | 上传模块 | ✅ | 直传 + 分片(>100MB) | `api/media.go` | | 2 | 转换模块 | ✅ | LibreOffice→PDF,pdftotext→文本 | `api/media.go` | | 3 | 提取切片模块 | ✅ | PDF→文本→段落切片 | `api/media.go` | | 4 | 摄入模块 | ✅ | 解析知识源 md → product/question/chunk | `api/knowledge.go` | | 5 | 审批模块 | ✅ | 素材审批 + 知识源审批 | `api/media.go` + `api/knowledge.go` | | 6 | 检索模块 | ✅ | brute-force 向量/关键词召回 | `api/knowledge_pipeline.go` | | 7 | 索引重建 | ✅ | 知识索引异步重建(Go 原生) | `api/knowledge_index.go` | | 8 | 问答管线 | ✅ | 意图分类 → FAQ 匹配 → 向量/关键词检索 → LLM 兜底 | `api/knowledge_pipeline.go` | --- ## 6. API 端点 ### 知识源管理 | 端点 | 方法 | 职责 | |------|------|------| | `POST /api/knowledge/scan` | 管理员 | 扫描 `docs/knowledge_source/*.md`,为新增/变更的 md 建 `knowledge_source` 记录(status=pending)。已存在且未摄入的记录跳过。 | | `GET /api/knowledge/audit-list?status=&page=&size=` | 管理员 | 按 `audit_status` 过滤,分页返回知识源列表。 | | `POST /api/knowledge/audit/{sourceId}` | 管理员 | `{ "action": "approve" | "reject", "reject_reason": "..." }`。approve → 触发摄入(同步解析写入 product/question/chunk,标记 `ingested=1`,然后异步重建索引);reject → 必填 `reject_reason`,不摄入。 | | `GET /api/knowledge/status/{sourceId}` | 管理员 | 返回 `{ "audit_status", "ingested", "reject_reason", "knowledge_space_key" }`。 | ### 素材管理 | 端点 | 方法 | 职责 | |------|------|------| | `POST /api/media/upload` | 通用 | 文件上传(直传) | | `POST /api/media/upload/init` | 通用 | 分片上传初始化(>100MB) | | `POST /api/media/upload/chunk` | 通用 | 分片上传分片 | | `POST /api/media/upload/complete` | 通用 | 分片上传完成 | | `POST /api/media/audit/{fileId}` | 管理员 | `{ "action": "approve" | "reject" }`。approve → 触发异步提取;reject → 素材标记 rejected。 | | `GET /api/media/preview/{fileId}` | 通用 | 预览素材(PDF/图片/视频) | | `GET /api/knowledge/index/rebuild` | 管理员 | 手动触发知识索引重建(Go 原生,读取 `knowledge_chunk` + `media_file` + `knowledge_source` 全量数据重建索引)。返回 `{ "rebuild": true, "chunk_count": N, "service_enabled": false }`。 | --- ## 7. 摄入逻辑(`api/knowledge.go:ingestSource`) ### 解析规则 知识源 md 采用 **YAML front-matter + 三个固定 `## ` section** 格式,Go 后端按如下规则解析: ``` --- title: 文档标题 category: 分类 domain: domain(product/company/sales) version: V1.0 --- ``` 然后按 `## ` 二级标题切分三个 section: | Section 名 | 内容 | 解析 | |-----------|------|------| | `结构化产品数据` | 每个 `### code name` 键值对列表 → product | 解析 `code`/`name`/`category`/`tags`/`description`/`pricing`/`commission_recommend`/`commission_negotiate`/`public_course_bonus`/`version_risk`/`report_rules` | | `AI 检索知识` | 每个 `### 标题` 段落 → knowledge_chunk | 段落正文写入 content,source_type=md | | `考试题目` | 每个 `### Qn` 字段列表 → question | 解析 `type`/`stem`/`options`(JSON)/`answer`(JSON)/`explanation`/`domain` | ### 断点摄入 `ingestSource` 先检查 `src.Ingested`:若已为 true,直接返回(跳过重复解析写入)。 ### 写入逻辑 ```go // ingestSource 伪代码 func ingestSource(src *model.KnowledgeSource) ([3]int, error) { data, _ := os.ReadFile(filepath.Join(Cfg.KnowledgeSourceDir, src.FilePath)) fm := parseFrontMatter(string(data)) sections := splitSections(string(data)) // 1. 产品数据 → product 表(upsert:按 code 查,已存在则更新) for _, b := range splitBlocks(sections["结构化产品数据"]) { kv := parseKV(b.body) p := model.Product{Code: kv["code"], Name: kv["name"], ...} store.DB.Save(&p) // upsert by code } // 2. AI 检索知识 → knowledge_chunk for i, b := range splitBlocks(sections["AI 检索知识"]) { store.DB.Create(&model.KnowledgeChunk{ KnowledgeSourceID: &src.ID, SourceType: "md", SourceID: strconv.FormatUint(uint64(src.ID), 10), Content: b.body, }) } // 3. 考试题目 → question for _, b := range splitBlocks(sections["考试题目"]) { q := model.Question{...} store.DB.Create(&q) } } ``` ### 断点约束 解析失败(front-matter 缺失、section 缺块、字段缺失)静默跳过对应块,不抛错中断。每个 section 的解析独立于其他 section。 --- ## 8. 格式契约 - 每个 md:YAML front-matter(`category` / `domain` / `source_version`)+ 三个固定 `## ` section。 - 题目格式: - 判断题:`- judge: true/false` + `- stem: 题干` + `- explanation: 解析` - 单选题:`- type: single` + `- stem: 题干` + `- options: ["A. ...", "B. ..."]` + `- answer: [1]` + `- explanation: 解析` - 多选题:`- type: multiple` + `- stem: 题干` + `- options: [...]` + `- answer: [1,2]` + `- explanation: 解析` - 选项格式:`- 选项文本`(Go 端自动编号 A/B/C/D...) - 已生成 5 个知识源:`01_通用规则` + `02/03/04/05` 四大分类。 --- ## 9. 知识检索管线(`api/knowledge_pipeline.go`) 知识检索采用 **四层管线**,逐层降级: ``` 用户提问 │ ▼ ① 意图分类器(heuristicKnowledgeIntent) │ 关键词规则匹配 → intent: invalid / smalltalk / out_of_scope / faq / document │ 命中 smalltalk/out_of_scope → 直接返回答案,管线终止 │ ▼ ② FAQ 匹配(matchKnowledgeFAQ) │ 问题归一化 + 相似问题匹配 + 关键词重叠评分 │ 最佳分 >= 70 → 返回标准答案,管线终止 │ ▼ ③ 向量检索(vectorRetrieveCitations) │ 1) 调用 embed_gen 路由嵌入 query + 候选块(brute-force) │ 2) 计算余弦相似度,降序排序 │ 3) 最高分 >= 0.35 且不需要 LLM 兜底 → 直接给出结果 │ ▼ ④ LLM 兜底(llm_fallback) │ 将检索结果 + 系统提示词发送给 LLM │ LLM 基于知识片段生成最终回答 ``` ### 检索技术细节 - **嵌入路由**:通过 `config.GetRoute("embed_gen")` 获取嵌入模型配置,调用 Ollama embedding API。 - **向量检索**:brute-force 全量候选块嵌入 + 余弦相似度排序(对齐 D07/D13),不依赖外部向量库。 - **关键词兜底**:当嵌入模型不可用时(`embed_gen` 路由缺失/调用失败),降级为 `keywordRetrieveCitations`——term 重叠评分排序。 - **空间过滤**:支持按 `knowledge_space_key` 过滤候选块(如选择某知识库后再提问)。 - **直接回答判定**:`canDirectAnswerFromVector` 判定:最高分 ≥ 0.35 且不含 LLM 兜底关键词 → 直接返回。 - **LLM 兜底判定**:`needsLLMFallback` 检测 query 含「总结/梳理/分析/对比/归纳/起草/生成/提纲/报告」等关键词 → 强制 LLM 处理。 --- ## 10. 知识索引重建(`api/knowledge_index.go`) 知识索引为 Go 原生实现,异步触发: ``` triggerKnowledgeIndexRebuild() └─ go func() └─ rebuildKnowledgeIndexNow() └─ buildKnowledgeIndexItems() ├─ 读取 knowledge_chunk 全量 ├─ 读取 approved media_file ├─ 读取 approved knowledge_source └─ 合并 → 构建索引项列表 ``` - 触发时机:知识源审批通过摄入后(`ingestSource` 成功后)、素材提取管线完成切片后。 - 锁机制:`knowledgeIndexRebuildLock` 防止并发重建。 - 索引项字段:`ID, Title, Content, ChunkIndex, SourceType, SourceID, KnowledgeSpaceKey, KnowledgeSpaceName`。 - 管理员也可通过 `POST /api/knowledge/index/rebuild` 手动触发重建。 --- ## 11. 实现清单(Go 代码,已完成) - [x] `internal/model/knowledge_source.go` — 知识源表 - [x] `internal/model/knowledge_chunk.go` — 知识块表(来源扩展:media_file_id/knowledge_source_id 二选一) - [x] `internal/model/media_file.go` — 素材表(status + extract_status 字段) - [x] `api/knowledge.go` — scan / audit / status / ingestSource(md 解析 + 摄入) - [x] `api/media.go` — 上传 / 审批 / 提取管线 / 预览 - [x] `api/knowledge_pipeline.go` — 意图分类 / FAQ / 向量检索 / LLM 兜底 - [x] `api/knowledge_index.go` — 知识索引重建(Go 原生) - [x] `internal/store/` — DB 初始化 + 自动建表 - [x] `config/ai_config.json` — AI 路由配置(embed_gen / llm_fallback)