把工作区里其余在制品一并入库,主要是工作台产品化的推进:
后端:新增 capability_definition / project / my_app_center / office_skill
接口与 action_definition / skill_definition / project / user_app_center
模型,config 加路由健康上报。
前端:新增 frontend/src/skills(Office 技能与 workbuddy 复刻)、
项目管理、应用中心、能力目录页,以及配套 api / store / config;
聊天侧新增 SpecialistChip / SpecialistPanel / SkillStrip / AppChatRail
等组件。
清理:移除旧 views/tools 下的单页工具(已并入工作台)、_frozen 冻结组件、
cmd/inspect_oa_debug 调试入口,以及两份调试笔记。
其它:文档与启动脚本同步。
(这批改动与上一提交的 SY23 工作并行进行,此前已在同一工作区内交织。)
Co-Authored-By: Claude Code <noreply@anthropic.com>
17 KiB
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" |
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" |
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,直接返回(跳过重复解析写入)。
写入逻辑
// 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 代码,已完成)
internal/model/knowledge_source.go— 知识源表internal/model/knowledge_chunk.go— 知识块表(来源扩展:media_file_id/knowledge_source_id 二选一)internal/model/media_file.go— 素材表(status + extract_status 字段)api/knowledge.go— scan / audit / status / ingestSource(md 解析 + 摄入)api/media.go— 上传 / 审批 / 提取管线 / 预览api/knowledge_pipeline.go— 意图分类 / FAQ / 向量检索 / LLM 兜底api/knowledge_index.go— 知识索引重建(Go 原生)internal/store/— DB 初始化 + 自动建表config/ai_config.json— AI 路由配置(embed_gen / llm_fallback)