基于RAG与向量数据库构建私有化LLM知识库:从Wiki.js到智能问答的完整实践

发布时间:2026/8/10 11:04:40
基于RAG与向量数据库构建私有化LLM知识库:从Wiki.js到智能问答的完整实践 在探索大语言模型LLM应用落地的过程中你是否遇到过这样的困境团队内部积累了大量关于模型使用、API调用、最佳实践和故障排查的文档但它们散落在各个聊天记录、个人笔记和Confluence页面中当新成员加入或遇到一个棘手的问题时往往需要花费大量时间进行“知识考古”。一个集中、智能、易于查询的内部知识库成为了提升团队协作与研发效率的刚需。本文将为你详细介绍如何构建一个“托管式LLM Wiki”——一个专为技术团队设计的基于现代Web技术栈并能与LLM深度集成的知识管理解决方案。无论你是想快速搭建一个轻量级文档站还是希望打造一个能通过自然语言交互的智能知识中枢这里都有从零到一的完整实践路径。1. 背景与核心概念为什么需要LLM Wiki在深入技术细节之前我们有必要厘清几个核心概念以及这个项目所要解决的根本问题。1.1 LLM与知识管理的碰撞大语言模型LLM如GPT系列、Claude、通义千问等已经展现出强大的自然语言理解和生成能力。它们不再是遥不可及的实验室产物而是逐渐渗透到代码生成、文档撰写、问题解答等日常开发环节中的实用工具。然而LLM的“通用知识”与团队的“私有知识”之间存在鸿沟。LLM可能不知道你公司内部特定的API规范、项目独有的架构设计决策或是上周刚修复的那个诡异Bug的解决方案。LLM Wiki的核心思想就是构建一个桥梁将LLM的能力与团队内部的私有、结构化知识结合起来。1.2 什么是Hosted LLM Wiki“Hosted”意为“托管的”它强调了这个Wiki系统的部署和运维特性。一个Hosted LLM Wiki通常包含以下关键特征私有化部署代码和数据掌握在自己手中部署在团队可控的服务器或云环境保障了知识资产的安全与隐私。Wiki核心功能提供完整的知识创建、编辑、组织、版本控制和搜索功能就像Confluence或MediaWiki一样。LLM深度集成这不是简单的“给Wiki加个聊天机器人”。集成是双向的知识库增强LLMRAGWiki作为向量知识库当用户提问时系统先从中检索最相关的文档片段再连同问题和片段一起提交给LLM生成基于内部知识的精准回答。这就是检索增强生成RAG的核心流程。LLM赋能Wiki管理利用LLM自动为文档生成摘要、标签甚至辅助编写和校对内容提升知识运营的效率。1.3 核心应用场景与价值团队内部知识沉淀统一存放所有项目文档、技术规范、会议纪要和事故复盘报告。高效智能问答新同事可以直接提问“我们项目如何连接数据库”或“处理支付回调的注意事项是什么”系统能基于内部文档给出准确回答极大降低培训成本。开发支持集成到IDE或命令行工具中快速查询某个库的内部使用示例、某个微服务的接口定义。客户支持构建面向外部用户的智能帮助中心基于产品文档自动解答常见问题。接下来我们将从技术选型开始一步步搭建一个具备上述能力的系统。2. 技术选型与环境准备构建一个LLM Wiki涉及前端、后端、向量数据库、LLM接口等多个层面。以下是一个经过验证的、平衡了功能与复杂度的技术栈方案。2.1 技术栈说明前端 Wiki 界面Wiki.js。它是一个现代、开源、基于Node.js的Wiki系统界面美观支持Markdown、可视化编辑权限管理完善且API友好易于集成。后端与向量化服务PythonFastAPI。Python是AI生态的首选语言。FastAPI能快速构建高性能的RESTful API用于处理文档向量化、检索和与LLM的交互。向量数据库Chroma或Qdrant。两者都是轻量级、易于使用的开源向量数据库。Chroma更简单适合快速入门Qdrant性能更强功能更丰富。本文示例使用Chroma。嵌入模型text-embedding-ada-002(OpenAI) 或开源模型如BAAI/bge-small-zh-v1.5。负责将文本转换为向量。为演示方便我们使用OpenAI的嵌入API。大语言模型GPT-3.5-turbo或GPT-4(OpenAI API)或开源模型如Qwen、ChatGLM的API。本文使用OpenAI API进行演示。部署与容器DockerDocker Compose。用于容器化所有服务实现一键部署和环境统一。2.2 环境与版本说明请确保你的开发或服务器环境满足以下要求。版本号是关键不匹配可能导致依赖冲突。操作系统Linux (Ubuntu 20.04/22.04 LTS 推荐) 或 macOS。Windows建议使用WSL2。Docker版本 20.10.0 或更高。Docker Compose版本 1.29.0 或更高。Python版本 3.9 或 3.10用于后端服务开发。Node.jsWiki.js需要但Docker镜像中已包含无需本地安装。重要你需要准备一个有效的OpenAI API密钥并确保有足够的额度。2.3 项目结构预览在开始之前我们先规划整个项目的目录结构这有助于理解各个组件的关系。hosted-llm-wiki/ ├── docker-compose.yml # 主部署文件 ├── wiki-js/ # Wiki.js 配置目录 │ ├── config.yml # Wiki.js 配置文件 │ └── data/ # Wiki.js 数据卷 (挂载) ├── backend/ # 后端向量化与RAG服务 │ ├── Dockerfile │ ├── requirements.txt │ ├── app/ │ │ ├── main.py # FastAPI 主应用 │ │ ├── chroma_client.py # 向量数据库客户端 │ │ ├── embedding.py # 嵌入模型调用 │ │ └── llm_client.py # LLM调用封装 │ └── data/ # 向量数据库持久化目录 (挂载) └── .env.example # 环境变量示例文件3. 部署Wiki.js作为知识管理前端我们首先部署Wiki.js它将成为我们知识库的“门面”和内容管理核心。3.1 使用Docker Compose快速部署创建docker-compose.yml文件这是所有服务的编排定义。version: 3.8 services: wiki-js-db: image: postgres:15-alpine container_name: llm-wiki-db environment: POSTGRES_DB: wiki POSTGRES_USER: wikijs POSTGRES_PASSWORD: your_secure_db_password_here # 务必修改 volumes: - wiki-db-data:/var/lib/postgresql/data restart: unless-stopped networks: - llm-wiki-network wiki-js: image: ghcr.io/requarks/wiki:2.5 container_name: llm-wiki-frontend depends_on: - wiki-js-db environment: DB_TYPE: postgres DB_HOST: wiki-js-db DB_PORT: 5432 DB_USER: wikijs DB_PASS: your_secure_db_password_here # 与上面一致 DB_NAME: wiki volumes: - ./wiki-js/data:/var/wiki/data # 持久化上传文件等 - ./wiki-js/config.yml:/var/wiki/config.yml # 挂载自定义配置 ports: - 3000:3000 # 将容器3000端口映射到主机3000端口 restart: unless-stopped networks: - llm-wiki-network # 后端RAG服务将在下一节添加 # chroma-db: # ... networks: llm-wiki-network: driver: bridge volumes: wiki-db-data:关键配置解释wiki-js-db使用PostgreSQL作为Wiki.js的数据库。volumes将数据库数据(wiki-db-data)和Wiki.js配置(config.yml)、用户数据(data)持久化到宿主机避免容器重启后数据丢失。networks创建一个独立的Docker网络llm-wiki-network让服务间能通过服务名互相访问。3.2 配置Wiki.js创建wiki-js/config.yml文件。这是一个最简化的配置用于连接数据库。# Wiki.js 配置文件 port: 3000 bind: 0.0.0.0 db: type: postgres host: wiki-js-db port: 5432 user: wikijs pass: your_secure_db_password_here db: wiki ssl: false logLevel: info dataPath: ./data3.3 启动并初始化Wiki.js在项目根目录(hosted-llm-wiki)下运行命令启动服务docker-compose up -d等待几十秒后在浏览器中访问http://你的服务器IP:3000。你将看到Wiki.js的安装向导。大部分配置已通过config.yml和环境变量完成向导页面通常只需要你设置管理员账号邮箱和密码。请务必记住这个账号。完成设置后登录系统。你现在拥有了一个功能完整的Wiki站点可以开始创建页面、编写文档了。4. 构建后端RAG服务与集成LLM现在我们构建核心的“智能”部分——一个能够读取Wiki内容、进行向量化存储并响应智能问答的后端服务。4.1 创建后端服务项目结构进入backend目录创建必要的文件。1. 定义依赖 (requirements.txt):fastapi0.104.1 uvicorn[standard]0.24.0 openai1.3.0 chromadb0.4.18 langchain0.0.340 python-dotenv1.0.0 pydantic2.5.0 requests2.31.0 beautifulsoup44.12.2 # 可选用于解析HTML内容2. 编写Dockerfile (backend/Dockerfile):FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY ./app ./app CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]3. 创建应用核心代码: 首先创建环境变量管理文件.env(从.env.example复制并填写)OPENAI_API_KEYsk-your-openai-api-key-here OPENAI_EMBEDDING_MODELtext-embedding-ada-002 OPENAI_LLM_MODELgpt-3.5-turbo CHROMA_PERSIST_DIRECTORY/app/data/chroma_db WIKI_JS_API_BASEhttp://wiki-js:3000 WIKI_JS_API_TOKENyour_wiki_js_api_token # 需要在Wiki.js后台生成注意WIKI_JS_API_TOKEN需要在Wiki.js管理后台的API Access页面生成。4. 实现向量数据库客户端 (backend/app/chroma_client.py):import chromadb from chromadb.config import Settings import os from typing import List from .embedding import get_embedding_function class ChromaClient: def __init__(self, persist_directory: str): # 创建持久化客户端 self.client chromadb.PersistentClient( pathpersist_directory, settingsSettings(anonymized_telemetryFalse) # 禁用匿名数据收集 ) # 获取或创建集合类似于数据库的表 # 使用我们自定义的嵌入函数 self.collection self.client.get_or_create_collection( namewiki_knowledge, embedding_functionget_embedding_function() ) def add_documents(self, documents: List[str], metadatas: List[dict], ids: List[str]): 向向量库添加文档 if documents: self.collection.add( documentsdocuments, metadatasmetadatas, idsids ) print(fAdded {len(documents)} documents to Chroma.) def query(self, query_text: str, n_results: int 5) - List[dict]: 查询最相关的文档 results self.collection.query( query_texts[query_text], n_resultsn_results ) # 格式化返回结果 retrieved_docs [] if results[documents]: for i, doc in enumerate(results[documents][0]): retrieved_docs.append({ content: doc, metadata: results[metadatas][0][i], distance: results[distances][0][i] }) return retrieved_docs def delete_all(self): 清空集合用于测试或重置 self.client.delete_collection(namewiki_knowledge) self.collection self.client.get_or_create_collection( namewiki_knowledge, embedding_functionget_embedding_function() )5. 实现嵌入函数 (backend/app/embedding.py):from chromadb import EmbeddingFunction, Embeddings import openai import os from typing import List class OpenAIEmbeddingFunction(EmbeddingFunction): def __init__(self): api_key os.getenv(OPENAI_API_KEY) if not api_key: raise ValueError(OPENAI_API_KEY environment variable is not set) self.client openai.OpenAI(api_keyapi_key) self.model os.getenv(OPENAI_EMBEDDING_MODEL, text-embedding-ada-002) def __call__(self, input: List[str]) - Embeddings: # 调用OpenAI Embedding API response self.client.embeddings.create( modelself.model, inputinput ) # 提取嵌入向量 embeddings [item.embedding for item in response.data] return embeddings def get_embedding_function(): 返回嵌入函数实例 return OpenAIEmbeddingFunction()6. 实现LLM客户端 (backend/app/llm_client.py):import openai import os from typing import List, Dict, Any class LLMClient: def __init__(self): api_key os.getenv(OPENAI_API_KEY) if not api_key: raise ValueError(OPENAI_API_KEY environment variable is not set) self.client openai.OpenAI(api_keyapi_key) self.model os.getenv(OPENAI_LLM_MODEL, gpt-3.5-turbo) def generate_response(self, query: str, context: List[Dict[str, Any]]) - str: 基于检索到的上下文生成回答 # 构建系统提示词指导LLM如何利用上下文 system_prompt 你是一个专业的助手专门回答基于提供的内部知识库的问题。 请严格根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题请如实说明“根据现有知识库无法回答此问题”不要编造信息。 上下文信息 # 将检索到的上下文拼接起来 context_text \n\n.join([f[来源{doc[metadata].get(title, 未知)}]\n{doc[content]} for doc in context]) user_prompt f问题{query}\n\n请根据以上上下文回答。 messages [ {role: system, content: system_prompt context_text}, {role: user, content: user_prompt} ] try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperature0.1, # 低温度使输出更确定更依赖上下文 max_tokens1000 ) return response.choices[0].message.content except Exception as e: return f调用LLM时发生错误{str(e)}7. 实现主API应用 (backend/app/main.py):from fastapi import FastAPI, HTTPException, BackgroundTasks from pydantic import BaseModel from typing import List, Optional import os from dotenv import load_dotenv from .chroma_client import ChromaClient from .llm_client import LLMClient import requests import json # 加载环境变量 load_dotenv() app FastAPI(titleHosted LLM Wiki Backend, descriptionRAG服务后端API) # 初始化客户端 chroma ChromaClient(os.getenv(CHROMA_PERSIST_DIRECTORY, /app/data/chroma_db)) llm_client LLMClient() # 数据模型定义 class QueryRequest(BaseModel): question: str top_k: Optional[int] 5 class IndexRequest(BaseModel): page_id: Optional[str] None # 如果为空则同步所有页面 force: Optional[bool] False class QAResponse(BaseModel): question: str answer: str sources: List[dict] # 辅助函数从Wiki.js API获取页面内容 def fetch_wiki_page(page_id: str None): 从Wiki.js获取单个页面或所有页面内容 base_url os.getenv(WIKI_JS_API_BASE) token os.getenv(WIKI_JS_API_TOKEN) headers { Authorization: fBearer {token}, Content-Type: application/json } if page_id: # 获取单个页面 url f{base_url}/api/pages/{page_id} else: # 获取所有页面可能需要分页这里简化处理 url f{base_url}/api/pages try: response requests.get(url, headersheaders, timeout30) response.raise_for_status() return response.json() except Exception as e: print(fError fetching from Wiki.js: {e}) return None def process_and_index_page(page_data): 处理页面数据并索引到向量数据库 # 简化处理提取标题和内容 # 实际应用中可能需要解析Markdown/HTML进行更精细的文本分割chunking page_id page_data.get(id) title page_data.get(title, Untitled) content page_data.get(content, ) # 可能是Markdown或HTML # 简单的文本分割按段落或固定长度分割 # 这里为了演示将整个页面内容作为一个文档块 documents [content] metadatas [{ page_id: page_id, title: title, source: wiki-js, url: f/{page_id} # Wiki.js页面路径 }] ids [fwiki_page_{page_id}] chroma.add_documents(documents, metadatas, ids) return True # API端点 app.post(/index, status_code202) async def index_pages(req: IndexRequest, background_tasks: BackgroundTasks): 触发知识库索引异步 background_tasks.add_task(run_indexing, req.page_id, req.force) return {message: 索引任务已开始在后台运行} def run_indexing(page_id: str None, force: bool False): 实际执行索引的后台任务 print(f开始索引Wiki页面 page_id: {page_id}, force: {force}) if force: chroma.delete_all() print(已清空现有向量库。) pages_data fetch_wiki_page(page_id) if not pages_data: print(无法从Wiki.js获取数据。) return # 处理返回的数据结构 if page_id: # 单个页面 if process_and_index_page(pages_data): print(f成功索引页面: {pages_data.get(title)}) else: # 多个页面 if isinstance(pages_data, list): for page in pages_data: if process_and_index_page(page): print(f成功索引页面: {page.get(title)}) else: print(获取到的页面数据格式不符合预期。) print(索引任务完成。) app.post(/query, response_modelQAResponse) async def query_knowledge_base(req: QueryRequest): 查询知识库并获取智能回答 if not req.question or req.question.strip() : raise HTTPException(status_code400, detail问题不能为空) # 1. 检索相关文档 retrieved_docs chroma.query(req.question, n_resultsreq.top_k) if not retrieved_docs: return QAResponse( questionreq.question, answer知识库中暂无相关信息。, sources[] ) # 2. 调用LLM生成回答 answer llm_client.generate_response(req.question, retrieved_docs) # 3. 整理来源信息 sources [] for doc in retrieved_docs: sources.append({ title: doc[metadata].get(title, 未知标题), page_id: doc[metadata].get(page_id), relevance_score: 1 - doc[distance] # 简单转换距离越小越相关 }) return QAResponse( questionreq.question, answeranswer, sourcessources ) app.get(/health) async def health_check(): 健康检查端点 return {status: healthy, service: llm-wiki-backend} if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)4.2 更新Docker Compose以集成后端服务现在将后端服务和ChromaDB添加到docker-compose.yml中。version: 3.8 services: wiki-js-db: # ... 保持不变 ... wiki-js: # ... 保持不变 ... chroma-db: # 注意ChromaDB以服务模式运行但我们的客户端使用持久化模式。 # 这里我们仅作为独立服务运行实际存储使用本地卷。 image: chromadb/chroma:0.4.18 container_name: llm-wiki-chroma command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8001 ports: - 8001:8001 # 暴露端口可用于管理或其它客户端连接 volumes: - ./backend/data/chroma_db:/chroma/chroma_db # 持久化向量数据 environment: - IS_PERSISTENTTRUE - PERSIST_DIRECTORY/chroma/chroma_db restart: unless-stopped networks: - llm-wiki-network rag-backend: build: ./backend container_name: llm-wiki-backend depends_on: - chroma-db environment: - OPENAI_API_KEY${OPENAI_API_KEY:-your_key_here} - OPENAI_EMBEDDING_MODEL${OPENAI_EMBEDDING_MODEL:-text-embedding-ada-002} - OPENAI_LLM_MODEL${OPENAI_LLM_MODEL:-gpt-3.5-turbo} - CHROMA_PERSIST_DIRECTORY/app/data/chroma_db - WIKI_JS_API_BASEhttp://wiki-js:3000 - WIKI_JS_API_TOKEN${WIKI_JS_API_TOKEN:-your_token_here} volumes: - ./backend/data:/app/data # 挂载数据卷使向量库持久化 - ./backend/app:/app/app # 开发时挂载代码生产环境可移除 ports: - 8000:8000 # 后端API端口 restart: unless-stopped networks: - llm-wiki-network networks: llm-wiki-network: driver: bridge volumes: wiki-db-data:关键点chroma-db服务我们运行了Chroma的服务实例但我们的Python客户端(chromadb.PersistentClient)使用的是本地文件模式。这里运行服务主要是为了演示另一种连接方式并确保环境一致。数据通过卷./backend/data/chroma_db持久化。rag-backend服务构建我们的FastAPI应用。它依赖chroma-db服务虽然未直接使用其HTTP接口并挂载了相同的向量数据卷。环境变量通过${VARIABLE_NAME}语法从宿主机环境或.env文件读取。重要在运行前需要在项目根目录创建.env文件并填入正确的OPENAI_API_KEY和WIKI_JS_API_TOKEN。4.3 启动完整系统并测试生成Wiki.js API Token登录Wiki.js管理后台(http://localhost:3000)。进入管理-API访问。点击生成新令牌赋予它读取页面的权限复制生成的令牌。将令牌填入项目根目录的.env文件的WIKI_JS_API_TOKEN变量中。在Wiki.js中创建一些测试页面例如首页欢迎页面。项目部署指南描述如何部署项目的Markdown文档。API规范描述团队内部API设计规范的文档。启动所有服务# 在项目根目录执行 docker-compose down # 如果之前启动过先停止 docker-compose up -d --build # --build 会重新构建后端镜像使用docker-compose logs -f rag-backend查看后端日志确保启动无误。触发知识库索引 我们的Wiki内容已经更新需要将其同步到向量数据库。调用后端索引API# 同步所有页面 curl -X POST http://localhost:8000/index \ -H Content-Type: application/json \ -d {force: false}如果返回{message: 索引任务已开始在后台运行}则成功。查看后端容器日志确认索引过程。测试智能问答APIcurl -X POST http://localhost:8000/query \ -H Content-Type: application/json \ -d {question: 如何部署这个项目, top_k: 3}你应该会得到一个JSON响应包含基于你刚创建的项目部署指南页面内容生成的答案以及引用的来源信息。5. 前端集成与功能扩展至此核心后端服务已就绪。一个完整的系统还需要一个便于用户交互的前端。这里提供两种集成思路5.1 方案一在Wiki.js页面中嵌入问答组件推荐利用Wiki.js支持自定义JavaScript和HTML嵌入的特性我们可以在Wiki页面内直接集成一个问答窗口。在Wiki.js中创建一个新页面例如叫做智能问答。切换到“源代码”编辑器插入以下HTML/JS代码div idllm-qa-container h3 智能知识库问答/h3 p基于本Wiki所有内容进行智能回答。/p textarea idquestion-input placeholder请输入你的问题... rows3 stylewidth:100%; padding: 10px; margin-bottom: 10px;/textarea button onclickaskQuestion() stylepadding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;提问/button div idanswer-area stylemargin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; min-height: 100px; background-color: #f9f9f9; p答案将显示在这里.../p /div div idsources-area stylemargin-top: 15px; font-size: 0.9em; color: #666;/div /div script async function askQuestion() { const questionInput document.getElementById(question-input); const answerArea document.getElementById(answer-area); const sourcesArea document.getElementById(sources-area); const question questionInput.value.trim(); if (!question) { alert(请输入问题); return; } answerArea.innerHTML pem思考中.../em/p; sourcesArea.innerHTML ; try { const response await fetch(http://你的后端服务器IP:8000/query, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ question: question, top_k: 3 }) }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); // 显示答案 answerArea.innerHTML pstrong答案/strong/pdiv${data.answer.replace(/\n/g, br)}/div; // 显示来源 if (data.sources data.sources.length 0) { let sourcesHtml pstrong参考来源/strong/pul; data.sources.forEach(source { // 假设source.url是Wiki.js内的相对路径 sourcesHtml lia href${source.url} target_blank${source.title}/a (相关性: ${(source.relevance_score * 100).toFixed(1)}%)/li; }); sourcesHtml /ul; sourcesArea.innerHTML sourcesHtml; } } catch (error) { console.error(Error:, error); answerArea.innerHTML p stylecolor: red;请求失败: ${error.message}/p; } } /script注意将代码中的http://你的后端服务器IP:8000替换为你实际的RAG后端服务地址。如果Wiki.js和后端在同一台机器且通过Docker网络通信这里可以写http://rag-backend:8000但需要Wiki.js容器能解析此服务名它们在同一Docker网络llm-wiki-network中。更通用的做法是使用宿主机的公网IP或域名并确保端口可访问。保存页面。现在访问这个智能问答页面你就可以直接在Wiki内部进行提问了。5.2 方案二构建独立的问答Web应用如果你需要一个更独立、功能更复杂的界面例如支持对话历史、多轮追问可以单独构建一个前端应用使用Vue/React通过调用http://localhost:8000/queryAPI 来获取答案。这超出了本文范围但架构是清晰的。6. 常见问题与排查思路在搭建和使用过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案Wiki.js 无法启动数据库连接失败1. PostgreSQL容器未启动或启动慢。2.config.yml或环境变量中的数据库密码错误。3. 网络配置问题容器间无法通信。1. 运行docker-compose logs wiki-js-db查看数据库日志。2. 检查docker-compose.yml和config.yml中的密码是否一致。3. 确认所有服务都在同一个Docker网络 (llm-wiki-network) 中。访问http://localhost:3000超时或拒绝连接1. Wiki.js容器未成功启动。2. 端口3000被宿主机其他程序占用。1.docker-compose ps查看服务状态docker-compose logs wiki-js查看日志。2. 使用netstat -tuln | grep :3000检查端口占用或修改docker-compose.yml中的端口映射如8080:3000。后端RAG服务启动失败提示ModuleNotFoundError1.requirements.txt中的包未正确安装。2. Docker构建缓存问题。1. 进入后端容器docker exec -it llm-wiki-backend bash手动pip list检查。2. 使用docker-compose build --no-cache rag-backend重新构建镜像。调用/indexAPI 后日志显示无法从Wiki.js获取数据1. Wiki.js API Token 无效或权限不足。2. 后端服务无法访问Wiki.js的地址 (WIKI_JS_API_BASE)。3. Wiki.js服务本身未运行。1. 在Wiki.js后台重新生成Token并更新.env文件重启后端服务。2. 在后端容器内执行curl http://wiki-js:3000/health测试连通性。3. 确保WIKI_JS_API_BASE的值在容器网络内可访问使用服务名wiki-js。调用/queryAPI 返回答案但答案质量差或回答“无法回答”1. 索引未成功运行向量库为空。2. 文档分割策略不佳检索不到有效上下文。3. LLM提示词 (system_prompt) 需要优化。4. OpenAI API 调用失败或额度不足。1. 检查索引任务的日志确认页面内容已成功添加。2. 优化process_and_index_page函数实现更智能的文本分割如按标题、按固定长度重叠分割。3. 调整llm_client.py中的system_prompt使其更明确地要求基于上下文回答。4. 检查OpenAI API密钥和额度。嵌入或LLM调用速度慢1. 网络延迟访问OpenAI API。2. 文档块 (chunk) 太大或太多。3. 未使用批处理。1. 考虑使用国内可访问的LLM/嵌入模型API或部署开源模型。2. 优化文本分割控制每个chunk的大小如300-500字。3. 在add_documents时可以考虑批量处理减少API调用次数。7. 最佳实践与进阶优化建议将系统运行起来只是第一步要使其在生产环境中稳定、高效、易用还需要考虑以下方面7.1 知识库内容管理规范化文档结构在Wiki.js中建立统一的页面模板和分类如/技术文档/、/业务规范/、/运维手册/便于管理和检索。定期同步与增量更新目前的/indexAPI 是全量同步。应实现增量索引监听Wiki.js的webhook如果支持或在页面保存时触发单个页面的重新索引。文档预处理与清洗在索引前应去除Markdown/HTML中的无关标签、代码块除非代码是知识的一部分、图片链接等保留纯文本核心内容。7.2 检索增强生成RAG优化智能文本分割不要将整页文档作为一个块。使用langchain的RecursiveCharacterTextSplitter等工具按语义如标题或固定长度进行重叠分割能显著提升检索精度。# 示例使用Langchain进行文本分割 from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, length_functionlen, ) docs text_splitter.create_documents([full_text])元数据丰富为每个文本块添加丰富的元数据如所属页面、章节标题、标签、最后修改时间等。这有助于在检索时进行过滤和排序。混合检索结合向量检索语义相似度和关键词检索如BM25可以兼顾语义匹配和精确术语匹配效果往往更好。重排序初步检索出N个结果如20个后使用一个更精细的模型重排序器对它们进行再次排序只将最相关的几个如3个送入LLM生成答案可以降低成本并提升答案质量。7.3 系统性能与可观测性API限流与认证为后端/queryAPI 添加速率限制和API密钥认证防止滥用。异步处理索引大量文档时使用Celery或RQ等任务队列进行异步处理避免HTTP请求超时。日志与监控记录所有查询和索引操作监控API响应时间、Token消耗、错误率等指标。缓存策略对常见问题的答案进行缓存如使用Redis可以极大减少对LLM和向量数据库的调用提升响应速度。7.4 安全与权限环境变量管理切勿将API密钥等敏感信息硬编码在代码中。使用.env文件并在生产环境中使用安全的密钥管理服务如Vault、云厂商的密钥管理。网络隔离确保RAG后端、向量数据库等核心服务不直接暴露在公网。通过反向代理如Nginx暴露必要的API并设置防火墙规则。Wiki.js权限继承目前我们的RAG服务能读取所有Wiki页面。在实际应用中问答的权限应与Wiki.js的用户权限对齐。这需要更复杂的集成例如在查询时传递用户身份并在检索前后进行内容过滤。7.5 成本控制使用开源模型将嵌入模型和LLM替换为本地部署的开源模型如使用sentence-transformers库的模型或部署Qwen、ChatGLM等可以彻底消除API调用成本并保障数据隐私。这需要更强的GPU算力支持。优化提示词精心设计提示词让LLM的回答更简洁、精准减少不必要的Token消耗。设置用量告警如果使用商用API务必在云平台设置用量和费用告警。通过以上步骤你已经成功搭建了一个功能完整的、托管式的LLM Wiki系统。它不仅仅是一个静态的知识仓库更是一个能够理解团队内部知识并与之智能对话的“活”系统。从简单的文档管理到深度的智能问答这个框架为你提供了坚实的基础你可以根据团队的具体需求在检索质量、用户体验、系统架构等方面进行持续的迭代和优化。