Ken-u/llm-wiki-engine

LLM Wiki Engine is a backend-only LLM knowledge compilation engine that turns raw documents into structured Markdown knowledge units via a two-stage CoT pipeline, then exposes search, RAG chat, agent, and feedback-driven refinement APIs on top of those pages.

★ 1Forks 0PythonGitHub ↗Compare

README

LLM Wiki Engine

纯后端 LLM 知识编译引擎 — Compile-time Knowledge Synthesis。

不同于传统 RAG(chunk → embedding → 检索原文),本引擎先将原始文档通过 LLM 两步编译 为结构化 Markdown 知识单元(entity / concept / source summary),再基于高质量知识页面提供检索、问答、Agent 与反馈修正 API。

完整栈(UI + 可选案例生成服务)请使用仓库根目录的 docker-compose.yml 或 ./start-dev.sh。本文档侧重 单独运行 engine 或 API 集成。

核心能力

  • 两步 CoT Ingest — Analysis → Generation → FILE 块解析 → LLM 辅助页面合并 → SHA256 增量缓存 → 步骤级 checkpoint
  • Git 仓库同步 — 项目级绑定远端仓库;拉取 raw/sources/ → 编译 → 提交推送 raw/ + wiki/;APScheduler 每日定时
  • 混合搜索 — BM25 + LanceDB 向量 + RRF 融合
  • RAG Chat (SSE) — 混合搜索 → 图谱 1-hop 扩展 → 流式响应
  • 反馈修正 — 对话质量评估 → 编译修复候选 → 人工审核 → 写回 Wiki(含本地 Git 快照回滚)
  • 多项目隔离 — 独立 disk_path + LanceDB;per-project ingest / git sync 串行锁
  • 多用户 — JWT + 项目成员角色;用户 API Token
  • 自定义 Agent — 多项目绑定、工具调用、公开 Chat 端点
  • 文档 — 多格式解析;raw/sources/ 递归列表与内容预览 API

快速开始

前置条件

  • Python >= 3.10
  • uv(推荐)或 pip
  • LLM API Key(OpenAI 兼容 / Ollama 等)
  • Git CLI(Git 同步与 feedback 写盘快照需要)

本地开发(推荐从仓库根目录)

# 在 llmwiki 根目录
./start-dev.sh

Engine 会使用 data/wiki 与 data/engine-db/engine.db。

仅启动 engine(本子目录)

cd llm-wiki-engine
uv sync
cp .env.example .env   # 可选,根目录 .env 亦可

# 建议指定数据目录(与 monorepo 一致)
export PROJECTS_DIR=../data/wiki
export DATABASE_URL=sqlite+aiosqlite:///$(pwd)/../data/engine-db/engine.db
mkdir -p ../data/wiki ../data/engine-db

uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

启动 Runtime(单知识库只读推理)

Runtime 是面向分发的本地只读查询入口,不启动 DB、用户系统、编译队列、反馈或 Git 同步。它读取 runtime-config.example.yaml 格式的本地配置,消费已经由完整 engine 生成的 wiki/、.llm-wiki/lancedb/ 和案例库 .llm-wiki/case-index/。

cd llm-wiki-engine
cp runtime-config.example.yaml runtime-config.yaml
# 编辑 runtime-config.yaml:knowledge.path、case_library.path、LLM/Embedding 配置

uv run python -m app.runtime_main --config ./runtime-config.yaml

默认地址:http://127.0.0.1:8012

Runtime 也可以直接从普通 zip bundle 启动。bundle 是 .llmwiki-bundle 或 .zip 文件,内部可包含 runtime-config.yaml、data/knowledge、data/cases 和 hooks。启动时 Runtime 会安全解压到本地缓存目录并加载配置:

uv run python -m app.runtime_main --bundle ./dist/customer.llmwiki-bundle

生成 bundle:

./scripts/build-runtime-bundle.sh \
  --knowledge ./data/knowledge \
  --cases ./data/cases \
  --config ./runtime-config.yaml \
  --hooks ./hooks \
  --output ./dist/customer.llmwiki-bundle

默认情况下,如果传入 --config,打包器会把 bundle 内的 runtime-config.yaml 自动改写为:

knowledge:
  path: ./data/knowledge
case_library:
  path: ./data/cases

其中 case_library.path 只在传入 --cases 时改写。需要原样保留配置文件时,加 --keep-config。

Windows 使用:

scripts\build-runtime-bundle.bat --knowledge .\data\knowledge --config .\runtime-config.yaml --output .\dist\customer.llmwiki-bundle

如果 bundle 内没有 runtime-config.yaml,启动时需要额外传入外部配置:

uv run python -m app.runtime_main --bundle ./dist/customer.llmwiki-bundle --config ./runtime-config.yaml

外部配置可以用 ${RUNTIME_BUNDLE_DIR} 指向解压后的 bundle 内容,例如 ${RUNTIME_BUNDLE_DIR}/data/knowledge。

Runtime 提供:

GET  /api/status
POST /api/chat                  # SSE 流式 + 非流式
POST /api/search
GET  /api/wiki
GET  /api/wiki/{path}
GET  /api/cases/{case_id}
POST /api/cases/search
POST /api/indexes/rebuild        # target: knowledge | cases | all
GET  /v1/models
POST /v1/chat/completions       # 支持 stream=true

Runtime 自带的网页由相邻仓库 ../llm-wiki-ui 的 runtime 专用入口构建而来。若本地存在该目录,打包脚本会先执行 npm --prefix ../llm-wiki-ui run build:runtime 并刷新 app/runtime/ui_dist;若只想使用当前已生成的静态产物,可设置 SKIP_RUNTIME_UI_BUILD=1。

打包 Runtime 单文件

本地平台打包:

cd llm-wiki-engine
./scripts/build-runtime.sh

Windows 使用:

scripts\build-runtime.bat

产物位于 dist/runtime/<platform>/llm-wiki-runtime(Windows 为 .exe)。

Docker(单服务,开发用)

本子目录含独立 docker-compose.yml,仅启动 engine,数据卷为 ./projects:

cd llm-wiki-engine
cp .env.example .env
docker compose up -d

生产/联调请用根目录 docker compose(含 UI、持久化 data/)。

配置

环境变量 说明 默认值
LLM_API_KEY LLM API Key (空)
EMBEDDING_API_KEY Embedding API Key (空)
JWT_SECRET JWT 签名密钥 change-me-in-production
ADMIN_PASSWORD 管理员密码 admin
PROJECTS_DIR 项目磁盘根目录 ./projects(config 默认)
DATABASE_URL SQLite 连接串 sqlite+aiosqlite:///./data/engine.db
CONFIG_PATH config.yaml 路径 可选

config.yaml 是本地私有配置,不提交到 Git。首次使用请复制模板:

cp config.example.yaml config.yaml

模板示例:

llm:
  provider: "openai"
  model: "gpt-4o-mini"
  api_base: null

embedding:
  enabled: true
  provider: "openai"
  model: "text-embedding-3-small"
  dimensions: 1536

Admin API 可将部分配置写入 DB 覆盖 YAML。

API 概览

认证

POST /api/auth/register
POST /api/auth/login
GET  /api/auth/me
GET  /api/auth/api-token
POST /api/auth/api-token/regenerate

项目

POST   /api/projects
GET    /api/projects
GET    /api/projects/{id}
PATCH  /api/projects/{id}          # 含 Git 同步字段、案例库绑定、反馈开关
DELETE /api/projects/{id}
POST   /api/projects/{id}/members
GET    /api/projects/{id}/members

Git 同步(项目级)

POST /api/projects/{id}/git/test     # 测试仓库连接(owner)
POST /api/projects/{id}/git/sync     # 立即同步(成员)
GET  /api/projects/{id}/git/status   # 最近同步状态

PATCH 项目时可设置:git_repo_url、git_branch、git_username、git_auth_token(只写)、clear_git_auth_token、git_sync_enabled、git_sync_time 等。响应含 git_auth_configured,不返回 token 明文。

文档

POST /api/projects/{id}/documents/upload
GET  /api/projects/{id}/documents              # 递归列出 raw/sources/
GET  /api/projects/{id}/documents/content/{path} # 预览正文(PlainText)

编译(Ingest)

POST   /api/projects/{id}/ingest
POST   /api/projects/{id}/ingest/{job_id}/retry
GET    /api/projects/{id}/ingest/status
GET    /api/projects/{id}/ingest/history
DELETE /api/projects/{id}/ingest/{job_id}

Wiki / 搜索 / Chat

GET  /api/projects/{id}/wiki
GET  /api/projects/{id}/wiki/overview
GET  /api/projects/{id}/wiki/graph
GET  /api/projects/{id}/wiki/{path}
PUT  /api/projects/{id}/wiki/{path}
POST /api/projects/{id}/search
POST /api/projects/{id}/chat              # SSE
GET  /api/projects/{id}/conversations

反馈

GET  /api/projects/{id}/feedback
POST /api/projects/{id}/feedback/{task_id}/review
POST /api/projects/{id}/feedback/{task_id}/apply
...

知识检索 API(OpenAI 兼容)

提供 /v1/chat/completions 端点,供外部系统(如案例生成 Fact Agent)以标准 OpenAI SDK / LiteLLM 方式查询项目知识库。

GET  /v1/models                 # 列出所有启用的虚拟模型名
POST /v1/chat/completions       # 知识检索补全(非流式)

项目配置 API(owner 权限):

GET   /api/projects/{id}/knowledge-api
PATCH /api/projects/{id}/knowledge-api
POST  /api/projects/{id}/knowledge-api/regenerate-token

快路径与慢路径

请求到达 /v1/chat/completions 后,引擎按以下优先级尝试路由:

  1. 快路径(无 LLM,低延迟) — 满足以下任一条件时触发:

    • 用户消息匹配短查询启发式(什么是 X、X 的定义、≤6 词的纯名词短语等)
    • 在 wiki/entities/ 或 wiki/concepts/ 下按 slug 或 frontmatter title 精确命中
    • BM25 搜索在概念/实体页中 top-1 score 超过阈值

    快路径直接截取命中页面的"定义 / 概述 / 简介"章节返回,不调用 LLM。

  2. 慢路径(Agent tool-calling loop) — 当快路径未命中时自动触发:

    • 消息是复杂问题(多句描述、含问号、超 6 词)
    • 或虽是简短查询但知识库中没有对应的 entity/concept 页面

    慢路径复用项目的 knowledge Agent(自动创建),执行完整的 search_wiki → read_wiki_page → grep_raw 工具循环,最终将 Agent 生成的文本作为 choices[0].message.content 返回。

    关键约束:慢路径始终设置 include_ticket_project=False,即使项目绑定了案例库也不暴露 search_ticket_cases / read_ticket_case 工具,防止与外部案例生成服务产出的案例形成循环引用。

调用示例

curl -X POST "http://engine:8000/v1/chat/completions" \
  -H "Authorization: Bearer lwu_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-virtual-model-name",
    "messages": [{"role": "user", "content": "EDLA 和 GMS 有什么差异"}]
  }'

限制:不支持客户端传入 tools/tool_choice/functions(返回 400);暂不支持 stream: true。

Agent / Admin

/api/agents/...
/api/public/agents/{id}/chat    # 公开 SSE
/api/admin/...                  # 系统配置(admin)

案例库与 Case Index

案例库使用一等项目类型 case_library,不会混入主 wiki 的普通搜索索引。它通过主项目的 ticket_project_id 字段绑定,并由专用 case index 提供结构化检索:

  • 创建项目时可传 as_case_library: true;如同时传 main_project_id,会创建案例库并绑定到主项目。
  • Project.ticket_project_id 指向另一个 project_type == "case_library" 的项目。
  • owner 可通过 PATCH /api/projects/{id} 设置或清空 ticket_project_id。
  • 设置绑定时会校验当前用户也能访问目标案例项目。
  • 不允许项目绑定自己,不允许知识库绑定普通知识库,也不允许案例库再绑定另一个案例库,避免递归链。
  • 案例库上传或 Git 同步后,可设置 case_index_auto_rebuild 自动重建索引;否则会标记为 stale,需手动重建。

案例库索引 API:

GET  /api/projects/{id}/case-index/status
POST /api/projects/{id}/case-index/rebuild
POST /api/projects/{id}/case-index/rebuild/tasks
GET  /api/projects/{id}/case-index/rebuild/tasks/current
GET  /api/projects/{id}/case-index/rebuild/tasks/{task_id}
POST /api/projects/{id}/case-index/search
GET  /api/projects/{id}/case-index/cases/{case_id}

专用索引产物位于案例库项目目录的 .llm-wiki/case-index/:

  • manifest.json:索引状态、来源数量、案例数量、chunk 数、embedding 配置与错误摘要。
  • cases.jsonl:按案例存储 case_id、标题、领域、标签、问题摘要、根因、解决方案、处理步骤等结构化字段。
  • keyword.sqlite:FTS5 关键词索引。
  • lancedb/case_chunks_v1:案例章节 chunk 的向量索引。

主 Agent 与案例库的运行时链路:

flowchart TD
    User["用户问题"] --> AgentAPI["Agent Chat API<br/>/api/agents/{agent_id}/chat<br/>/api/public/agents/{id}/chat"]
    AgentAPI --> LoadMain["加载 AgentProject<br/>得到主项目列表 main_projects"]
    LoadMain --> FindTicket{"主项目是否绑定<br/>ticket_project_id?"}

    FindTicket -- "否" --> MainOnly["ToolContext<br/>main_projects + ticket_project=None"]
    FindTicket -- "是" --> LoadTicket["加载被绑定的 Project<br/>作为 ticket_project"]
    LoadTicket --> WithTicket["ToolContext<br/>main_projects + ticket_project"]

    MainOnly --> ToolDefsMain["暴露主 wiki 工具<br/>search_wiki<br/>read_wiki_page<br/>get_wiki_index<br/>get_project_purpose<br/>grep_raw/read_raw"]
    WithTicket --> ToolDefsAll["暴露主 wiki 工具<br/>+ 案例库工具<br/>search_ticket_cases<br/>read_ticket_case"]

    ToolDefsMain --> LLM["LLM 工具调用循环<br/>run_agent_turn"]
    ToolDefsAll --> LLM

    LLM --> SearchWiki["search_wiki<br/>搜索主项目 wiki"]
    SearchWiki --> MainHybrid["BM25 wiki/**/*.md<br/>+ LanceDB wiki_chunks_v2<br/>+ RRF 融合"]
    MainHybrid --> ReadWiki["read_wiki_page<br/>读取主 wiki 页面全文"]

    LLM --> NeedCase{"问题涉及历史案例<br/>故障经验/类似 issue/处理先例?"}
    NeedCase -- "是,且有 ticket_project" --> SearchTicket["search_ticket_cases<br/>搜索专用案例索引"]
    SearchTicket --> TicketHybrid["FTS5 keyword.sqlite<br/>+ LanceDB case_chunks_v1<br/>+ RRF 融合"]
    TicketHybrid --> ReadTicket["read_ticket_case<br/>读取结构化案例详情或指定章节"]
    NeedCase -- "否或未绑定" --> Final

    ReadWiki --> Final["基于工具结果生成回答"]
    ReadTicket --> Final
    Final --> Cite["引用 wiki 页面<br/>若使用案例库则说明参考了案例库"]

    OpenAICompat["/v1/chat/completions<br/>知识检索 API"] -.-> NoTicket["include_ticket_project=False<br/>不暴露案例库工具"]
    NoTicket -.-> ToolDefsMain
Loading

Agent 回复问题时走工具调用链路:

  1. Agent 通过 AgentProject 取得可访问的主项目列表。
  2. agent_toolcall_chat() 从这些主项目中查找第一个已绑定的 ticket_project_id,加载对应案例库项目。
  3. 运行时构造 ToolContext(main_projects=..., ticket_project=...)。
  4. 如果 ticket_project 存在,才会向 LLM 暴露 search_ticket_cases 和 read_ticket_case。
  5. 系统提示词会引导模型:当问题涉及历史案例、具体故障经验、类似 issue、处理先例时,可主动调用 search_ticket_cases。
  6. 使用案例库作答时,案例引用必须使用工具返回的纯数字 ID,例如 [[558753]];read_ticket_case.case_id 也会容忍并归一化 case_558753、CASE-558753、#558753 等常见 LLM 误写。

search_ticket_cases 的实现逻辑:

if name == "search_ticket_cases":
    if ctx.ticket_project is None:
        return {"error": "Ticket wiki not configured for this project."}
    query = arguments.get("query", "")
    limit = min(arguments.get("limit", 3), 5)
    manifest = load_manifest(ctx.ticket_project.disk_path)
    if manifest is None or not manifest.is_ready:
        return {"error": "Case index is not built or not ready. Rebuild the case index first."}
    results = await search_cases(ctx.ticket_project.disk_path, query, limit=limit)
    return {"source_type": "ticket_case_index", "results": results}

它对案例库项目执行专用 hybrid search:

  • keyword.sqlite 的 FTS5 返回 case_id、匹配章节和 rank。
  • lancedb/case_chunks_v1 向量搜索返回 case_id、章节、chunk 文本和距离。
  • RRF 在案例级别融合关键词与向量结果。
  • 返回 case_id、title、domain、problem_summary、root_cause、resolution、matched_sections、score。

read_ticket_case 的实现逻辑:

if name == "read_ticket_case":
    if ctx.ticket_project is None:
        return {"error": "Ticket wiki not configured for this project."}
    case_id = normalize_case_id(arguments.get("case_id", ""))
    section = arguments.get("section") or arguments.get("session") or arguments.get("section_name")
    return read_case(ctx.ticket_project.disk_path, case_id, section=section)

它从 cases.jsonl 定位案例源文件,并返回 Agent 友好的结构化内容:

  • 不返回本地 source_path 或 raw_text_hash,避免模型继续读取 raw 文件。
  • 不传 section 时返回可用章节列表和章节摘要字典。
  • 传 section 时按章节名模糊匹配,并返回最多 6000 字的该章节内容。
  • section 兼容旧误写参数 session 和 section_name。

因此,主 wiki Agent 并不是后端自动把案例库内容拼进 prompt,而是根据绑定关系额外暴露案例库工具;是否搜索案例库由 LLM 在工具循环中决定。例外是 /v1/chat/completions 知识检索 API:慢路径会显式禁用案例库工具(include_ticket_project=False),防止案例生成流程形成循环引用。

项目磁盘布局

每个项目在 PROJECTS_DIR/<uuid>/:

purpose.md
raw/sources/          # 原始文档(上传 / Git 同步)
wiki/                 # 编译产物
.llm-wiki/            # ingest-cache、LanceDB、checkpoints、case-index 等

测试

cd llm-wiki-engine
uv sync
uv pip install pytest pytest-asyncio   # 若 venv 未带 dev 依赖
uv run pytest -v

项目结构

llm-wiki-engine/
├── pyproject.toml
├── config.example.yaml
├── Dockerfile
├── docker-compose.yml      # 仅 engine 单服务
├── .env.example
└── app/
    ├── main.py
    ├── config.py
    ├── database.py
    ├── auth/
    ├── projects/           # CRUD + git_sync.py + 调度注册
    ├── documents/
    ├── ingest/
    ├── embedding/
    ├── search/
    ├── wiki/
    ├── chat/
    ├── agents/
    ├── case_index/         # 案例库解析、索引构建、搜索与读取 API
    ├── knowledge/          # 知识检索(快路径 + 慢路径编排)
    ├── runtime/            # 单知识库只读 Runtime API、配置、静态 UI
    ├── openai_compat/      # /v1/ OpenAI 兼容端点
    ├── feedback/
    ├── admin/
    └── llm/

License

MIT

Contributors

Ken-u

Issues