团队知识系统Knowledge-hub技术方案

Knowledge Hub 技术方案

企业 LLM 知识索引平台 — 完整技术设计与踩坑记录

版本: v1.1 | 更新日期: 2026-07-29

1. 项目概述

1.1 背景与目标

搭建企业内部 LLM 知识索引平台,持续采集、处理、索引公司内部知识数据,为 AI Agent、企业问答助手、代码助手等应用提供统一知识检索能力。

1.2 数据源

数据源 版本 说明
Confluence Server 6.7.1 私有化部署,SSO(Google 登录)认证
GitLab - 10 个 Java 代码仓库,统一使用 xxxxx 分支

1.3 部署环境

项目 规格
操作系统 Windows 10
CPU Intel i7-10700(8 核 16 线程)
内存 16GB
磁盘 500GB
已有基础设施 MySQL 8.0、Redis

1.4 设计原则

  1. CPU 优先:不依赖 GPU,所有计算(Embedding、tree-sitter 解析)均在 CPU 完成
  2. 稳定性优先:不追求高速处理,优先保证稳定
  3. 断点恢复:所有任务以 document 为单位记录状态,支持中断后继续
  4. 增量更新:所有数据同步支持增量模式(git diff / version 对比)
  5. 内存可控:避免一次性加载大量数据,batch 处理
  6. 所有耗时任务异步化:通过 Redis 队列解耦
  7. 统一进程:API + Worker 一体化部署,简化运维

2. 技术选型与决策

2.1 向量数据库:ChromaDB

选型对比:

方案 优点 缺点 结论
Qdrant 功能强大,支持分布式 需要独立部署服务端,资源占用高 ❌ 过重
Milvus Lite 轻量嵌入式 Windows 支持不完善 ❌ 兼容性风险
ChromaDB 嵌入式、零部署、Python 原生、支持持久化和 metadata 过滤 不适合超大规模 ✅ 最适合

选择理由:
- 嵌入式模式,无需额外服务进程
- 原生 Python API,集成简单
- 支持持久化到磁盘,重启不丢数据
- 支持 metadata filter,满足来源过滤需求
- 对于万级文档规模完全够用

2.2 Embedding 模型:bge-base-zh-v1.5

选型对比:

模型 参数量 维度 中文效果 CPU 推理
BGE-M3 560M 1024 优秀 较慢(~500ms/条)
Qwen Embedding 1.5B 1536 优秀 很慢,16GB 内存紧张
bge-base-zh-v1.5 102M 768 优秀 快速(~50ms/条)

选择理由:
- 参数仅 102M,占用约 400MB 内存,适合 16GB 机器
- 768 维向量,在中文语义理解上表现出色
- CPU 推理速度完全可接受(batch=16 约 0.8s)
- sentence-transformers 库开箱即用

2.3 代码解析:tree-sitter

选型对比:

方案 优点 缺点
正则匹配 简单 无法处理复杂嵌套、泛型等
JavaParser 准确的 Java AST 需要 JVM 运行时,Python 集成困难
tree-sitter 增量解析、多语言支持、纯 C 实现 Python 绑定 语法树较底层

选择理由:
- 纯 Python(C 扩展),不需要 JVM
- 支持 Java Class / Method / Interface 级别切分
- 可同时提取 package、import、extends、implements 等符号信息
- 性能极佳,解析 2000+ Java 文件只需秒级

2.4 任务队列:Redis List

不使用 Celery 的原因:
- Celery 引入的依赖和配置过重
- 本系统任务类型简单(sync / embed 两种)
- Redis List + 手动消费完全满足需求
- 更容易实现自定义的断点恢复和优雅停机

2.5 LLM 集成:公司自建思考型模型

配置项
API URL https://xxxx.com/v1/chat/completions
模型 Qwen3.5(配置可切换)
接口协议 OpenAI Compatible
限频 3 秒/次(RateLimiter 异步限频器)
特殊行为 content 字段可能为 null,答案在 reasoning_content

兼容的思考型模型行为:
- Qwen3.5:答案在 reasoning_content 字段,content 可能为 null
- Qwen 系列:可能在 content 中混入思考过程(<think> 标签或中文思考标记)
- 统一处理:LLMClient.chat() 优先取 content,为空回退到 reasoning_content;Chat API 额外做 _clean_thinking_content() 后处理


3. 系统架构设计

3.1 整体架构

┌───────────────────────────────────────────────────────────────────────────┐
│                        Knowledge Hub 统一进程                             │
│                                                                           │
│  ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐     │
│  │  FastAPI     │ │  Sync Worker │ │ Embed Worker │ │  MCP Server   │     │
│  │  (uvicorn)   │ │  (异步协程)  │ │  (异步协程)  │ │ (FastMCP/SSE) │     │
│  └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬────────┘     │
│         │                │                │                │              │
│  ┌──────┴────────────────┴────────────────┴────────────────┴──────┐       │
│  │                       asyncio Event Loop                       │       │
│  └──────┬─────────────────┬──────────────────┬────────────────────┘       │
│         │                 │                  │                            │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌──────┴───────┐                    │
│  │ APScheduler  │  │ Health Check │  │  Stop File   │                    │
│  │ (定时任务)    │  │ (10min 轮询)  │  │ (停机监控)   │                    │
│  └──────────────┘  └──────────────┘  └──────────────┘                    │
└───────────────────────────────────────────────────────────────────────────┘
         │                 │                  │
    ┌────┴────┐      ┌────┴────┐       ┌────┴────┐
    │  MySQL  │      │  Redis  │       │ ChromaDB │
    │ (元数据) │      │ (队列)  │       │ (向量)   │
    └─────────┘      └─────────┘       └──────────┘
         ↑                                    ↑
         └────────────────┬───────────────────┘
                          │
                    ┌─────┴──────┐
                    │ AI IDE     │
                    │ (Cursor    │
                    │  via MCP)  │
                    └────────────┘

3.2 统一进程模型

所有服务在一个 Python 进程中运行(app.server.py),包括:

组件 运行方式 职责
FastAPI + uvicorn asyncio Task API 服务、Chat UI、Console
MCP Server (FastMCP) 挂载到 FastAPI /mcp 为 Cursor 等 AI IDE 提供 MCP 工具
Sync Worker asyncio Task 从 Redis 队列消费同步任务
Embed Worker asyncio Task 从 Redis 队列消费向量化任务
APScheduler asyncio 调度器 定时触发全量/增量同步、健康检查
Stop File Watcher asyncio Task 轮询检查停机文件

为什么统一进程?
- 避免多进程协调的复杂性
- Windows 上 signal 的行为不如 Linux,统一管理更简单
- 共享 Embedding 模型和 ChromaDB 实例,避免内存重复加载
- 简化部署:一个 start.bat 脚本搞定一切

备选启动方式:
- app/run_workers.py:仅启动 Sync Worker + Embed Worker + 调度器(不含 API 服务),适合需要分离 API 和 Worker 的场景
- app/main.py:仅启动 FastAPI 应用(含 MCP Server),通过 uvicorn app.main:create_app --factory 运行


4. 数据库设计

4.1 ER 关系

knowledge_source (数据源)
    1 ← N  knowledge_document (文档)
                1 ← N  knowledge_chunk (切片)
                1 ← N  code_symbol (代码符号)
    1 ← N  sync_task (同步任务)

4.2 表说明

表名 用途 关键字段
knowledge_source 数据源配置(Confluence/GitLab) source_type, config (JSON), status
knowledge_document 文档元数据,以 (source_id, external_id) 唯一标识 content_hash, version, status
knowledge_chunk 文档切片文本,带 FULLTEXT ngram 索引 content (MEDIUMTEXT), vector_id
sync_task 同步任务状态跟踪 task_type, status, progress
code_symbol Java 类符号信息(用于依赖图谱) fqn, extends_fqn, implements_fqn, imports
term_mapping 术语映射(中英文对照,可选) term_zh, term_en

4.3 关键设计决策

knowledge_chunk.content 使用 MEDIUMTEXT
- 初始使用 TEXT(64KB 限制)
- 部分 SQL 文件和大型 Java 类超过 64KB
- 踩坑后改为 MEDIUMTEXT(16MB 限制)

knowledge_chunk 上的 FULLTEXT 索引使用 ngram 解析器:
- MySQL 默认的 FULLTEXT 分词器不支持中文
- ngram 解析器可以正确索引中文内容
- 用于混合检索中的关键词匹配路径


5. 核心模块详解

5.1 配置管理

文件: app/config.py

使用 Pydantic Settings,自动从 .env 文件读取配置。

关键设计:

@model_validator(mode="before")
def _empty_strings_to_defaults(cls, values):
    """将 .env 中留空的数值/布尔字段移除,让 Pydantic 使用字段默认值"""

踩坑: .envPROXY_PORT=(空字符串)会导致 Pydantic 尝试将空字符串转为 int 从而报 ValidationError。通过 model_validator 在解析前将空字符串字段移除,使其回退到默认值。

路径处理:
- GITLAB_CLONE_DIRCHROMA_PERSIST_DIR 声明为 str 而非 Path
- 通过 @property 方法(gitlab_clone_pathchroma_persist_path)返回解析后的绝对路径
- 相对路径以项目根目录为基准

5.2 数据源连接器

5.2.1 Confluence 连接器

文件: app/connector/confluence.pyapp/connector/confluence_sso.py

认证方式: Cookie + SSO 自动续签

Confluence 6.7.1 不支持 API Token,只能使用 Cookie 认证。Cookie 来源于企业 SSO(Google 登录)。

SSO 自动续签流程:

Confluence API 请求
    ↓
_request() 发起请求
    ↓
_is_auth_failure() 检测响应
    ├── 401/403 → 认证失败
    ├── 404 (content API) → 可能是匿名用户看不到受限 Space
    ├── 200 + text/html → 被重定向到登录页
    └── 正常响应 → 返回
    ↓ (认证失败)
_try_sso_refresh()
    ↓
Playwright 无头浏览器
    ↓
Google SSO 登录(邮箱 → 密码 → 登录)
    ↓
抓取 Confluence Cookie
    ↓
更新 .env 文件 + 重建 HTTP 客户端
    ↓
重试原始请求

踩坑 #1 — test_connection 不检测 Anonymous:
- /rest/api/user/current 对未登录用户也返回 200,displayNameAnonymous
- 修复:在 test_connection() 中主动检测 Anonymous 并触发 SSO 续签

踩坑 #2 — Confluence 对匿名用户返回 404 而非 401:
- 当 Cookie 失效时,匿名用户请求受限 Space 的内容 API 返回 404(非 401/403)
- 修复:_request() 增加 treat_404_as_auth 参数,fetch_documents()get_all_spaces() 传入 True

踩坑 #3 — Playwright 选择器失效:
- Google 登录页的 HTML 结构变化,input[type="email"] 选择器找不到元素
- 修复:使用多选择器 input#identifierId, input[name="identifier"], input[type="email"]

踩坑 #4 — 代理配置:
- Confluence 部署在内网,开发机需要 HTTP 代理才能访问
- httpx.AsyncClient 和 Playwright 均需配置代理

5.2.2 GitLab 连接器

文件: app/connector/gitlab.py

同步策略: git clone + git pull(而非 API 逐文件获取)

选择理由:
- Git 天然支持版本管理
- git diff 可以精确识别变更文件
- 增量更新极其高效
- 适合大型代码仓库

文件过滤规则:
- 包含:.java, .sql, .xml, .yaml, .yml, .md, .py, .js, .ts, .properties, .json
- 忽略目录:target, node_modules, log, logs, build, dist, .git, .idea, .vscode, __pycache__

踩坑 #1 — 路径解析问题:
- GITLAB_CLONE_DIR 配置为相对路径 data/repos
- Worker 运行时工作目录不确定,导致 仓库目录不存在 错误
- 修复:在 config.py 中使用 @property 确保返回绝对路径

踩坑 #2 — 分支名错误:
- 初始配置所有仓库使用 master 分支
- 实际项目使用 xxxxx 分支
- 修复:更新数据库中所有仓库的分支配置,重新 clone

5.3 文档解析与切片

5.3.1 Confluence 文档解析

流程:

XHTML 正文 → BeautifulSoup 预处理 → markdownify 转 Markdown → 标题级切分

踩坑 — markdownify 参数冲突:
- markdownify 同时接收 HTML 字符串和 soup 参数时行为异常
- 修复:先用 BeautifulSoup 解析,再将 soup 对象传给 markdownify

5.3.2 Java 代码切分

两级策略:
1. tree-sitter 优先:按 Class/Method 级别精确切分
2. 正则回退:tree-sitter 失败时,使用正则按空行和方法签名切分

切片参数:
| 参数 | 值 | 说明 |
|------|------|------|
| CHUNK_TARGET_SIZE | 384 | 目标切片字符数 |
| CHUNK_MAX_SIZE | 512 | 最大切片字符数 |
| CHUNK_MIN_SIZE | 32 | 最小切片字符数 |
| CHUNK_OVERLAP | 64 | 切片重叠字符数 |

踩坑 — 大文件处理:
- 部分 SQL 文件超过 100KB,超出 TEXT 列限制
- chunk_code_generic 增强:先按空行分割,如果仍然过大则按换行分割

5.3.3 Java 符号分析

文件: app/parser/java_analyzer.py

使用 tree-sitter 对 Java 源码进行静态分析,提取:

信息 说明
package_name 包名
imports 所有 import 语句的全限定名
fqn 类的全限定名(package.ClassName
extends_fqn 继承的父类
implements_fqn 实现的接口列表
field_types 成员变量引用的类型
method_param_types 方法参数/返回值类型
annotations 类级注解

踩坑 — 从 chunk 提取符号不完整:
- 初始方案:在 sync_worker 的切片过程中从每个 chunk 提取符号
- 问题:chunk 只包含部分代码,缺少 package、import 等上下文
- 修复:改为从 git clone 目录读取完整的原始 Java 文件进行分析

5.4 向量化与存储

5.4.1 Embedding 服务

文件: app/embedding/embedding_service.py

模型: BAAI/bge-base-zh-v1.5

配置
设备 CPU
批大小 16
最大长度 512 tokens
输出维度 768
内存占用 ~400MB

踩坑 — HuggingFace 模型下载失败:
- hf-mirror.com(国内镜像)不可达
- 修复:设置环境变量 HF_ENDPOINT=https://huggingface.co

5.4.2 ChromaDB 存储

文件: app/vector/chroma_store.py

  • 嵌入式模式,持久化到 data/vectors/
  • 集合名:knowledge_chunks
  • 支持 metadata 过滤(source_type、repo、space 等)

踩坑 — ChromaDB HNSW 索引损坏:
- Worker 非正常退出(如进程被杀)导致 HNSW 索引文件损坏
- 重启后出现 Access Violation / Segfault
- 修复:删除 data/vectors/ 目录,触发全量重新向量化
- 预防:实现优雅停机机制,确保 chroma.close() 被调用

5.5 检索引擎

5.5.1 查询增强

文件: app/retrieval/query_enhancer.py

使用公司 LLM(Qwen3.5)对用户查询进行扩展:

原始查询:"贷款审批流程"
    ↓ LLM 生成变体
变体 1:"loan approval process"
变体 2:"借款审核工作流"
变体 3:"放款审批步骤"

限频: 3 秒/次(公司 LLM API 限制)

踩坑 — Qwen3.5 响应结构特殊:
- content 字段经常为 null
- 实际答案在 reasoning_content 字段中(包含大量思考过程)
- 修复:解析时优先使用 content,为 null 则回退到 reasoning_content,再过滤思考标签

踩坑 — LLM 返回不相关变体:
- LLM 的 reasoning_content 中包含大量思考过程文本
- 直接解析会将思考内容误认为查询变体
- 修复:严格解析 LLM 输出,只提取 JSON 数组中的变体

5.5.2 混合检索 (Hybrid Retrieval)

文件: app/retrieval/hybrid_retriever.py

三阶段流程:

用户查询
    ↓
┌─────────────────┬─────────────────┐
│  向量检索路径    │  关键词检索路径   │
│                 │                 │
│ query embedding │ 中文关键词提取   │
│       ↓         │       ↓         │
│ ChromaDB 近邻   │ MySQL FULLTEXT  │
│  (余弦相似度)    │  (ngram 匹配)   │
└────────┬────────┴────────┬────────┘
         ↓                 ↓
    Reciprocal Rank Fusion (RRF)
         ↓
    语义重排 (Semantic Rerank)
         ↓
      Top-K 结果

RRF 融合公式:

# 向量路径:RRF 分数 × 原始相似度分数加权
weighted_score = (1 / (60 + rank + 1)) × raw_similarity_score

# 关键词路径:标准 RRF 分数
keyword_score = 1 / (60 + rank + 1)

# 合并:同一 chunk 的各路径分数累加
score(d) = Σ weighted_score_i(d)

向量路径的 RRF 会乘以原始相似度分数,让高相关度的结果即使排名稍低也能获得合理分数。

多查询向量检索:
- 原始查询权重 1.0,LLM 生成的变体查询权重 0.8
- 同一 chunk 被多个变体命中时取最高分
- 低于 SEARCH_SIMILARITY_THRESHOLD(默认 0.5)的结果直接过滤

语义重排:
- 对 RRF 融合后的 Top 结果
- 优先从 ChromaDB 获取已存储的文档向量(一次 IO)
- 若 ChromaDB 取不到向量,降级为对候选内容做 batch encode
- 使用 query embedding 与 doc embedding 的余弦相似度重新排序
- 综合 RRF 分数(0.4)和语义分数(0.6)
- 仅关键词匹配的结果(无向量分数)降权至 0.3 × rrf_norm

踩坑 — 空类排名靠前:
- 一个空的 Java 类(只有 class 声明)在搜索结果中排名第一
- 原因:向量相似度高但内容无意义;RRF 未充分过滤
- 修复:
1. 增加向量检索的相似度阈值过滤
2. 改进中文关键词提取算法
3. 修复语义重排中的 numpy 计算问题

5.6 代码智能分析

5.6.1 代码符号表

表: code_symbol

从 Java 源码中提取类级别的结构化信息,存储到数据库,作为代码依赖图谱的基础。

提取流程:

Git 仓库原始 Java 文件
    ↓
tree-sitter AST 解析
    ↓
提取: package, imports, class, extends, implements,
      field_types, method_param_types, annotations
    ↓
写入 code_symbol 表

5.6.2 代码依赖图谱引擎

文件: app/retrieval/code_graph.py

核心类: CodeGraphEngine

BFS 依赖追踪算法:

输入: 入口类名(如 InsDisbursementStatusMachine)
    ↓
1. 从 code_symbol 表查找入口类
2. BFS 队列初始化: [(入口类, depth=0)]
3. 循环:
   a. 弹出队首类
   b. 提取依赖: extends + implements + imports + field_types + method_param_types
   c. 过滤: 只保留项目内的类(排除 java.*, spring.*, lombok.* 等)
   d. 从原始文件读取完整源码
   e. 将未访问的依赖类加入队列 (depth+1)
4. 直到: 队列为空 || 达到 max_depth || 达到 max_nodes
    ↓
输出: CodeGraphResult (节点列表 + 关系信息 + 源码)

跨模块依赖追踪:
- 由于 10 个仓库的所有 Java 类符号都存储在同一张 code_symbol 表中
- BFS 可以自然地跨仓库追踪依赖(如 asetloan-installment 中的类引用 asetloan-common 中的类)

5.6.3 智能意图识别

两阶段识别用户查询中的代码类名:

Stage 1 — 正则匹配(快速路径):

# 直接从查询中提取类名
"分析 InsDisbursementStatusMachine 的工作流程"
→ ["InsDisbursementStatusMachine"]

Stage 2 — LLM 辅助识别(业务语言):

用户: "我想知道订单打款状态机的工作流程"
    ↓
1. 快速判断: _query_needs_code_analysis() 检测业务关键词 → 需要代码分析
2. 业务关键词映射: "打款" → ["Disbursement", "Capital", "Fund", "Payment"]
                   "状态机" → ["StatusMachine", "StateMachine", "Status", "State"]
3. 仓库名识别: _extract_repo_name() 检测 "asetloan-xxx" 模式
4. 候选符号搜索: 逐关键词单独查 code_symbol 表 (LIKE '%Disbursement%')
   → 在 Python 中合并,按命中关键词数排序(目标仓库的符号优先)
5. 构造 LLM Prompt(含历史对话上下文 + 仓库提示):
   "以下是候选 Java 类列表,用户想了解'订单打款状态机',
    请选出最相关的入口类..."
6. LLM 返回: ["InsDisbursementStatusMachine"]
   → 如果 LLM 失败,回退到 _fallback_select_classes() 基于后缀权重选择入口类
    ↓
7. 触发 CodeGraphEngine 追踪依赖
8. 将依赖图谱 + 源码作为上下文发送给 LLM 分析

结合对话历史:
- smart_detect_code_classes() 接受最近 10 条对话历史
- 历史消息会被格式化并加入 LLM 提示词中
- 帮助 LLM 理解上下文关联(如用户追问某个之前提到的类)

踩坑 — LLM 候选类排序不佳:
- MySQL LIKE 查询使用 OR 连接多个条件时,默认排序不可控
- 修复:对每个关键词单独查询,在 Python 中合并并按命中关键词数排序
- 增强:支持从查询中提取仓库名(如 asetloan-installment),优先搜索该仓库的符号

踩坑 — Qwen3.5 JSON 解析困难:
- LLM 返回的 JSON 类名数组常常嵌在 reasoning_content 的大段文本中
- 修复:增强 _parse_class_names_from_llm() 函数
- 尝试所有 [...] 结构并选最佳(按有效类名数排序)
- 回退到正则搜索反引号和引号中的类名
- 增加 _fallback_select_classes() 回退策略:LLM 失败时基于后缀权重(如 Machine=10Service=8)自动选择入口类

5.7 Chat API 与前端

5.7.1 OpenAI 兼容 Chat API

文件: app/api/chat.py

端点:
- POST /v1/chat/completions — Chat 对话
- GET /v1/models — 返回模型列表(OpenAI 兼容,供 NextChat 等前端发现模型)

CORS 配置:
- 允许所有来源(allow_origins=["*"]),支持 NextChat 等外部前端跨域访问

完整处理流程:

用户消息
    ↓
1. 意图识别(代码类检测)
   ├── Stage 1: 正则匹配类名
   └── Stage 2: LLM 辅助识别(含历史上下文)
    ↓
2. 构建上下文
   ├── 检测到代码类 → CodeGraphEngine 追踪依赖 → 代码图谱上下文
   └── 普通查询 → Hybrid Retrieval 混合检索 → 知识库上下文
    ↓
3. 构造 LLM Prompt
   ├── 系统提示词 + 检索结果 + 用户问题
   └── 代码分析专用提示词(如有代码图谱)
    ↓
4. 调用公司 LLM (Qwen3.5)
   ├── 流式 (stream=true) → SSE 推送
   └── 非流式 → 直接返回
    ↓
5. 响应后处理
   ├── 过滤 <think>...</think> 思考内容
   └── 追加参考来源链接

关键配置:
| 参数 | 值 | 原因 |
|------|------|------|
| max_tokens | 32192 | 思考型模型的思考过程消耗大量 token |
| httpx timeout | 180s | 长推理可能需要 2-3 分钟 |

思考型模型兼容:
- Chat API 内置 _clean_thinking_content() 函数
- 检测 Qwen/Deepseek 等模型在 content 中输出的推理过程
- 通过答案边界标记("根据知识库"、"综上所述"等)提取最终答案
- 支持 <think> 标签、--- 分隔线等多种格式的思考内容清理

踩坑 — 回答被截断:
- 初始 max_tokens=2048,思考型模型的 thinking 过程会消耗大量 token
- 实际回答内容被截断
- 修复:提升到 32192,同时增加超时到 180s

踩坑 — 流式响应缺少参考信息:
- 流式 SSE 推送时,参考链接应该在回答结束后追加
- 修复:在流式响应的最后一个 chunk 后追加包含参考链接的额外 chunk

5.7.2 内置 Chat UI

文件: frontend/index.html

纯 HTML/CSS/JS 实现,零依赖,通过 FastAPI 直接 serve。

特性:
- ChatGPT 风格深色主题界面
- 多会话管理(新建/切换/删除)
- 历史会话本地存储(localStorage)
- Markdown 渲染(代码高亮、表格、列表)
- 流式响应实时展示
- 响应式布局支持移动端

踩坑 — emptyState DOM 元素丢失:
- renderMessages() 使用 innerHTML 更新聊天容器
- 导致 emptyState DOM 元素被销毁
- 修复:更新前先将 emptyState 移出容器,更新后再放回

踩坑 — 流式传输中切换/删除会话:
- 流式传输过程中切换或删除会话导致状态混乱
- 修复:添加 isStreaming 标志位,阻止传输中的会话操作

5.7.3 管理控制台

前端: frontend/console.html

功能:
- 系统状态总览(文档数、向量数、待处理数等)
- 数据源 CRUD(添加/编辑/删除 GitLab/Confluence)
- 数据源健康状态展示(✅ 正常 / ❌ 异常 / ⏳ 未检查)
- 增量更新开关(启用/停用自动同步)
- 手动触发同步任务
- 手动触发健康检查
- 最近任务状态监控(30 秒自动刷新)

5.7.4 REST API 全景

管理控制台的功能由以下后端 API 模块支撑:

模块文件 路径前缀 主要端点
app/api/search.py /api POST /api/search — 统一知识检索
app/api/source.py /api GET/POST/PUT/DELETE /api/sources — 数据源 CRUD
app/api/task.py /api POST /api/tasks/triggerGET /api/tasks — 任务管理
app/api/admin.py /api GET /api/admin/status — 系统状态
GET /api/admin/health — 服务健康检查
GET/POST /api/admin/health/sources — 数据源健康状态
POST /api/admin/code-graph — 代码依赖图谱查询
GET /api/admin/symbols/stats — 代码符号统计
POST /api/admin/symbols/rebuild — 重建代码符号索引
app/api/chat.py POST /v1/chat/completionsGET /v1/models — OpenAI 兼容

5.8 MCP Server(AI IDE 集成)

文件: app/mcp/server.py

端点: GET /mcp/sse(SSE 传输协议)

作用: 将知识库的检索、代码图谱、RAG 对话能力封装为 MCP (Model Context Protocol) 工具,供 Cursor 等 AI IDE 直接调用。通过 FastMCP 创建,挂载到 FastAPI 应用的 /mcp 路径。

Cursor 配置示例:

{
  "mcpServers": {
    "knowledge-hub": {
      "url": "http://10.0.136.158:8080/mcp/sse"
    }
  }
}

提供的 MCP 工具:

工具名 用途 关键参数
knowledge_search 知识库混合检索(向量+全文+RRF+重排) query, top_k, use_enhancement
code_graph_trace Java 类依赖图谱追踪 class_name, max_depth, max_nodes
knowledge_chat 基于知识库的 RAG 对话(自动检索+代码分析+LLM 回答) question, chat_history
system_status 系统状态概览(数据源/文档/向量/队列等统计)

knowledge_chat 完整流程:

用户问题
    ↓
1. 代码类检测(两阶段:正则快速 → LLM 智能识别)
2. 代码图谱追踪(如有代码类,BFS 追踪依赖链)
3. 知识检索(HybridRetriever 混合检索)
4. 构造 LLM 消息
   ├── 有代码图谱 → 综合模式提示词(文档+代码双视角)
   └── 无代码图谱 → 普通知识库提示词
5. 调用 LLM 生成回答
6. 附加参考来源链接
    ↓
返回完整答案

设计要点:
- 使用全局单例延迟初始化 _retriever_llm_code_graph,避免重复创建资源
- knowledge_chatmax_tokens 设为 32192,适配思考型模型
- 挂载时禁用 DNS 重绑定保护(enable_dns_rebinding_protection=False),允许非 localhost 的客户端连接
- 如果 mcp 包未安装,MCP Server 挂载失败不影响其他功能

5.9 任务调度与 Worker

5.9.1 调度器

文件: app/scheduler/scheduler.py

任务 触发方式 说明
daily_sync CronTrigger(hour=2, minute=0) 每日 02:00 全量同步
incremental_sync CronTrigger(hour="*/4", minute=30) 每 4 小时增量同步
health_check IntervalTrigger(minutes=10) 每 10 分钟数据源健康检查

所有定时任务仅对 status='active' 的数据源触发。

5.9.2 Sync Worker

文件: app/worker/sync_worker.py

处理流程:

Redis 队列 (sync)
    ↓ dequeue
获取 sync_task + knowledge_source
    ↓
根据 source_type 选择 Connector
    ├── Confluence: test_connection → fetch_documents → fetch_document_detail → parse → chunk
    └── GitLab: clone_or_pull → walk files → parse → chunk
    ↓
对每个文档:
  1. 检查 content_hash 是否变化
  2. 删除旧 chunks
  3. 解析 + 切片 → 写入 knowledge_chunk
  4. 提取 Java 符号 → 写入 code_symbol
  5. commit 当前文档
  6. 推送 embed 任务到 Redis 队列
    ↓
更新 sync_task 状态

踩坑 — Sync 与 Embed 竞态条件:
- Sync Worker 批量写入 chunks 但尚未 commit
- Embed Worker 已从队列取到 embed 任务,但查不到 chunks
- 修复:在每个文档处理完 chunk 后立即 session.commit(),然后再 enqueue embed 任务

踩坑 — 空内容文档卡在 pending 状态:
- 一些 .properties 文件内容为空,chunk_count = 0
- Embed Worker 反复补偿但无法处理
- 修复:如果 chunk_count = 0,直接标记为 indexed

踩坑 — SQLAlchemy 事务回滚传播:
- 单个文档处理异常未 rollback,导致后续文档的 session.add() 报错:
This Session's transaction has been rolled back due to a previous exception
- 修复:在异常处理中加 await session.rollback()

5.9.3 Embed Worker

文件: app/worker/embed_worker.py

处理流程:

Redis 队列 (embed)
    ↓ dequeue
获取 knowledge_document
    ↓
查询关联的 knowledge_chunk
    ↓
批量 Embedding (batch_size=16)
    ↓
写入 ChromaDB (upsert)
    ↓
更新 chunk.vector_id
    ↓
更新 document.status = 'indexed'

补偿机制:
- 队列为空但仍有 pending 文档时,启动补偿处理
- 将 pending 文档重新 enqueue 到 embed 队列

进度日志:

Embedding 进度: [12/2976] 0.4% | 当前文档: ApplicationConfig.java

5.10 健康检查

文件: app/health/source_checker.py

功能:
- 启动时立即检查所有数据源连接状态
- 每 10 分钟定时轮询
- 支持手动触发(API POST /api/admin/health/check

检查策略:
| 数据源类型 | 检查方式 | 并发模式 |
|-----------|---------|---------|
| Confluence | test_connection()(含 Anonymous 检测和 SSO 续签) | 串行(共享 Cookie,避免并发续签冲突) |
| GitLab | test_connection()git ls-remote) | 并行(限制并发 5 个) |

结果缓存:
- 检查结果缓存在内存 dict 中
- API GET /api/admin/health/sources 直接返回缓存
- Console 前端展示健康状态 badge


6. 数据流与处理管线

6.1 完整数据流

┌────────────────────────────────────────────────────────────────────────────┐
│ 数据采集                                                                   │
│                                                                            │
│  Confluence ──REST API──→ XHTML 正文                                       │
│  GitLab     ──git clone──→ 源码文件                                        │
│                    ↓                                                       │
│ 文档解析                                                                   │
│                                                                            │
│  XHTML → BeautifulSoup → markdownify → Markdown                           │
│  Java  → tree-sitter → Class/Method 切片                                   │
│  其他  → 通用分割(按空行/换行)                                             │
│                    ↓                                                       │
│ 结构化存储                                                                 │
│                                                                            │
│  knowledge_document (元数据 + content_hash)                                │
│  knowledge_chunk (切片文本 + FULLTEXT 索引)                                 │
│  code_symbol (Java 符号关系)                                               │
│                    ↓                                                       │
│ 向量化                                                                     │
│                                                                            │
│  bge-base-zh-v1.5 → 768 维向量 → ChromaDB (upsert)                        │
│                    ↓                                                       │
│ 检索 & 推理                                                                │
│                                                                            │
│  混合检索 (向量 + FULLTEXT + RRF + 语义重排)                                │
│  代码图谱 (BFS 依赖追踪 + 跨模块源码聚合)                                    │
│  LLM 生成 (Qwen3.5 RAG 回答)                                          │
│                    ↓                                                       │
│ 对外服务                                                                   │
│                                                                            │
│  Chat API (OpenAI 兼容 /v1/chat/completions) ← NextChat / Chat UI          │
│  MCP Server (/mcp/sse) ← Cursor / AI IDE                                  │
│  REST API (/api/search, /api/admin) ← Console / 外部系统                   │
└────────────────────────────────────────────────────────────────────────────┘

6.2 增量更新机制

Confluence:
- 比较 knowledge_document.version 与 API 返回的 version.number
- 版本不同 → 重新获取详情 → 重新切片 → 重新向量化

GitLab:
- git pull --ff-only(失败则 fetch + reset --hard
- git diff old_head HEAD 获取变更文件列表
- 仅处理 added / modified 文件


7. 优雅停机与容错机制

7.1 停机触发方式

方式 说明
Ctrl+C / SIGINT 终端中断
SIGTERM 系统信号
scripts\stop.bat 创建停止文件
data/.stop_worker 手动创建停止文件

7.2 停机流程

停机信号
    ↓
_do_shutdown()
    ├── stop_sync_worker()    → 设置标志位,循环自然退出
    ├── embed_worker.stop()   → 设置标志位,等待当前 batch 完成
    ├── uvicorn.should_exit   → API 停止接收新请求
    └── _shutdown_event.set() → 通知主循环
    ↓
等待 Worker 完成当前任务(最多 30 秒)
    ↓
超时则强制取消
    ↓
清理资源
    ├── stop_scheduler()    → 停止 APScheduler
    ├── chroma.close()      → 持久化 ChromaDB 数据
    ├── redis_queue.close() → 关闭 Redis 连接
    └── close_engine()      → 关闭 MySQL 连接池

Windows 特殊处理:
- Windows 不支持 loop.add_signal_handler()
- 使用 signal.signal(SIGINT/SIGTERM, handler) + loop.call_soon_threadsafe() 替代

7.3 容错机制

场景 处理方式
单个文档处理失败 记录错误,继续处理下一个文档
Embedding 编码失败 增加失败计数器,不中断整体流程
Redis 连接断开 自动重连(5 秒重试)
Confluence Cookie 过期 SSO 静默续签
ChromaDB 索引损坏 删除 data/vectors,触发全量重建
端口被占用 捕获 SystemExit(code=3),日志提示并优雅退出

8. 运维工具与脚本

8.1 启动方式

入口文件 用途 说明
app/server.py 统一启动(推荐) API + Worker + 调度器一体化运行
app/run_workers.py 独立 Worker 仅启动 Sync Worker + Embed Worker + 调度器(不含 API)
app/main.py 纯 API uvicorn app.main:create_app --factory,仅启动 FastAPI(含 MCP)

8.2 运维脚本

scripts/ 目录下提供了多个运维和诊断工具:

脚本 用途
scripts/diagnose_search.py 检索诊断工具:排查某篇文档为何未出现在检索结果中。检查文档 status、chunk 切片、vector_id、ChromaDB 向量、向量检索排名、FULLTEXT 检索命中等全链路环节
scripts/rebuild_symbols.py 重建所有 Java 文件的代码符号索引(code_symbol 表)
scripts/reset_all.py 重置所有数据(清空 MySQL 表 + 删除 ChromaDB 向量 + 清空 Redis 队列)
scripts/confluence_login.py 手动触发 Confluence SSO 登录并获取 Cookie
scripts/test_frontend.py 前端功能测试
scripts/init_db.sql 数据库初始化 SQL(建表)

诊断脚本使用示例:

conda activate knowledge_hub
cd e:\workspace\python\knowledge_hub
python scripts/diagnose_search.py --doc-id 6941 --query "关于asetloan-push的工作流程"

9. 踩坑记录与解决方案

9.1 环境与部署

# 问题 原因 解决方案
1 PowerShell mysql < init_db.sql 报重定向错误 PowerShell 不支持 < 重定向 改用 pymysql 在 Python 中执行 SQL
2 pymysql 连接 SSL 报错 ASN1: NOT_ENOUGH_DATA MySQL 服务端 SSL 配置异常 pymysql.connect(ssl_disabled=True)
3 pymysql 执行多语句 SQL 后报 Unknown database 需要 CLIENT.MULTI_STATEMENTS 并消费所有 result set client_flag + while cursor.nextset(): pass
4 conda create 参数冲突 --prefix-n 不能同时使用 去掉 --prefix
5 .envPROXY_PORT=(空字符串)导致 ValidationError Pydantic 无法将空字符串转 int 增加 model_validator 预处理空字符串
6 服务启动报 OSError: address already in use 旧进程未退出 start.bat 中加 taskkill 清理旧进程

9.2 Redis 连接

# 问题 原因 解决方案
7 unknown command HELLO 老版本 Redis 不支持 RESP3 协议 redis.asyncio.Redis(protocol=2)
8 Timeout reading 频繁出现 连接池默认参数不适合长任务 增加 socket_timeoutretry_on_timeout

9.3 数据处理

# 问题 原因 解决方案
9 Data too long for column 'content' TEXT 列最大 64KB,大 SQL 文件超限 改为 MEDIUMTEXT
10 Embed Worker 报 文档无切片 Sync 和 Embed 竞态:chunk 未 commit 就 enqueue 了 每个文档 chunk 写入后立即 commit
11 11 篇文档永远 pending 空内容文件(如 .properties)chunk_count=0 chunk_count=0 直接标记 indexed
12 Session transaction rolled back 异常后未 rollback,污染后续操作 await session.rollback()
13 Git 仓库拉取错误分支 数据库中分支名配置为 master 统一改为 xxxxx 分支,重新 clone
14 仓库目录不存在 相对路径在不同工作目录下解析不同 Config 中 @property 确保绝对路径

9.4 LLM 集成

# 问题 原因 解决方案
15 LLM chat() 返回 NoneType has no attribute strip Qwen3.5 的 content 字段为 null 优先 content,为空则回退 reasoning_content
16 回答中包含 <think> 思考过程 Qwen3.5 特有的推理标签 正则过滤 <think>...</think> 等标签
17 回答被截断 max_tokens=2048 不够,思考过程占用大量 token 提升到 32192 + 超时 180s
18 查询变体包含不相关内容 LLM reasoning_content 被误当变体 严格解析 JSON 数组格式
18b Qwen 模型在 content 中混入思考过程 Qwen 系列有时不分离推理过程 _clean_thinking_content() 检测思考标记并提取最终答案

9.5 Confluence

# 问题 原因 解决方案
19 Playwright net::ERR_CONNECTION_TIMED_OUT Confluence 在内网,需要代理 配置 HTTP 代理
20 SSO 脚本找不到 input[type="email"] Google 登录页 HTML 结构变化 使用多选择器回退
21 test_connection 返回 Anonymous 但未触发续签 /rest/api/user/current 对匿名用户也返回 200 检测 displayName=="Anonymous" 后主动续签
22 受限 Space 请求返回 404 匿名用户看不到受限 Space 对 content API 的 404 也视为认证失败
23 SSO 脚本无输出 Python stdout 缓冲 functools.partial(print, flush=True)

9.6 前端

# 问题 原因 解决方案
24 emptyState 元素消失 innerHTML 更新销毁了 DOM 元素 更新前移出,更新后放回
25 流式传输中切换会话导致状态混乱 无并发保护 添加 isStreaming 检查
26 流式回答没有参考链接 SSE 推送完成后未追加引用 在流式结束后追加参考 chunk

9.7 向量存储

# 问题 原因 解决方案
27 ChromaDB 启动崩溃 (Access Violation) HNSW 索引文件损坏(非正常退出) 删除 data/vectors/ 重建;实现优雅停机
28 HuggingFace 模型下载失败 hf-mirror.com 不可达 HF_ENDPOINT=https://huggingface.co

9.8 代码分析

# 问题 原因 解决方案
29 Java 符号提取不完整 从 chunk 提取缺少 package/import 上下文 改从 git clone 目录读取完整原始文件
30 LLM 意图识别选不出正确类 候选列表排序不佳,高相关类排名靠后 按关键词多维命中数排序候选
31 LLM 返回的 JSON 无法解析 Qwen3.5 将 JSON 嵌在 reasoning 文本中 搜索所有 [...] 结构,选最佳匹配

10. 性能与资源考量

10.1 内存占用估算

组件 占用
Python 运行时 ~100MB
bge-base-zh-v1.5 模型 ~400MB
ChromaDB(13000 向量) ~200MB
FastAPI + uvicorn ~50MB
合计 ~750MB

在 16GB 内存的机器上完全可以承受。

10.2 处理速度参考

操作 速度
Java 文件 tree-sitter 解析 ~2000 文件/分钟
Embedding (batch=16, CPU) ~200 chunks/分钟
Confluence 页面同步 ~10 页/秒(受限于 API 响应)
GitLab git pull 秒级(增量)
混合检索 ~500ms/查询

10.3 数据规模

当前已索引数据:

指标 数量
数据源 11 个(10 GitLab + 1 Confluence)
文档 ~3000 篇
切片 ~13000 个
向量 ~13000 条
代码符号 ~2000+ 个

11. 未来扩展方向

11.1 数据源扩展

  • Jira / Linear(需求管理)
  • Notion(文档协作)
  • 钉钉/飞书文档
  • 更多 Git 平台(GitHub、Gitee)

11.2 检索能力增强

  • 引入 BGE-Reranker 做精排
  • 支持多轮对话上下文理解
  • 支持文档级别的权限控制

11.3 代码分析增强

  • 支持更多语言(Python、Go、TypeScript)
  • 方法级别的调用链追踪
  • 代码变更影响分析

11.4 运维能力

  • Docker 容器化部署
  • 数据备份自动化
  • 监控告警(如 Prometheus + Grafana)
  • 多用户权限管理

11.5 模型升级

  • 当 GPU 可用时,切换到更大的 Embedding 模型(BGE-M3)
  • 接入更强的 LLM 获得更好的回答质量

评论 (0)

暂无评论,快来抢沙发吧~