简介:RagFlow-main 是一个面向大模型研发与工程化实践的开源框架代码库,适用于AI算法工程师、深度学习开发者及高校科研人员,聚焦于流图驱动的大规模模型训练与部署优化。资源包含564个文件,以97个Python核心模块、95个TypeScript/React前端组件(tsx/ts)、87个样式文件(less)及53个SVG图标为主,辅以Docker配置、Nginx代理、CUDA支持、环境变量与日志配置等工程化文件,完整覆盖模型构建、训练调度、Web服务与本地部署全链路,压缩包大小为33.52MB。目前已有2020人学习下载,是深入理解动态流图计算、混合并行训练、GPU/CPU异构协同及自动微分实现的优质实操样本。读者可直接复用其模块化架构设计、分布式训练接口封装、实验管理工具链及生产级配置模板,快速搭建自有RAG或大模型推理服务原型。
1. RAGFlow 主干工程:一个开箱即用的本地化 RAG 应用框架,专治“大模型不会查资料”的硬伤
你有没有试过让大模型回答一个它训练数据里没有的问题?比如“我们公司上季度华东区销售会议纪要里提到的三个重点客户是谁?”——模型张口就来,但答案全是编的。这不是模型不行,是它根本没看到那份 PDF。RAGFlow-main 就是为解决这个痛点而生的:它不训练新模型,也不调用黑盒 API,而是把文档解析、向量索引、检索增强、提示编排全打包进一个可本地部署的完整工程。你扔进去几十份 Word、PDF、Excel,它就能在 Web 界面里实时提问、精准定位原文段落、生成带引用的回答。适合中小团队快速落地知识库问答、客服工单辅助、内部技术文档助手等场景——不需要懂 embedding 模型怎么训,也不用配 LangChain 的 chain 链路,docker-compose up -d启动后,上传、提问、看溯源,三步闭环。项目结构清晰、模块解耦强,二次开发时改解析器、换向量库、接入自有 LLM 接口,都落在明确目录下,不是改完main.py就满屏报错的玄学项目。
2. 架构全景与核心模块拆解:从文档摄入到答案生成的六层流水线
RAGFlow-main 不是单体脚本,而是一个分层明确、职责内聚的工程化 RAG 系统。它把传统 RAG 流程拆成六个可插拔模块,每一层都有独立配置、日志和错误隔离。这种设计不是为了炫技,而是为了让你在文档解析失败时不用怀疑 LLM 推理逻辑,在向量召回不准时能单独压测检索模块,而不是重启整个服务。下面按数据流向逐层展开,重点标出每个模块的入口文件、关键配置项和真实运行时行为。
2.1 文档预处理层:parser/目录下的格式翻译官
所有上传文件首先进入parser/目录。这里不是简单调用pypdf读 PDF,而是按文件类型路由到专用解析器:
pdf_parser.py:使用fitz(PyMuPDF)而非pypdf,支持扫描件 OCR(需启用tesseract)、表格识别、页眉页脚自动剥离;docx_parser.py:用python-docx提取正文,但会保留标题层级(<h1>→<h3>),后续 chunk 切分时按语义段落切,而非固定字数;excel_parser.py:将每张 sheet 转为 Markdown 表格,并在元数据中标记sheet_name: "销售明细",方便后续按业务维度过滤。
提示:解析结果不直接存文本,而是生成
Document对象,含content,meta,page_number,chunk_id四个核心字段。chunk_id格式为file_id:page:001:chunk_003,确保溯源时能精确定位到原文第几页第几块。
关键代码在parser/factory.py的get_parser()方法中:
def get_parser(file_type: str) -> BaseParser: parsers = { "pdf": PDFParser, "docx": DocxParser, "xlsx": ExcelParser, "txt": PlainTextParser, "md": MarkdownParser, } parser_class = parsers.get(file_type.lower(), PlainTextParser) return parser_class()这段代码决定了文件类型如何映射到解析器类。file_type来自上传时 HTTP 请求头的Content-Type,若未识别则降级为纯文本解析。注意:它不依赖文件后缀名(.pdf),所以你把 Word 改名为.pdf上传,系统仍会按application/vnd.openxmlformats-officedocument.wordprocessingml.document类型走DocxParser,避免因重命名导致解析乱码。
2.2 分块与向量化层:chunk/与embedding/的协同作战
解析后的长文本不能整段喂给向量模型。RAGFlow-main 在chunk/目录实现语义感知分块:先用正则识别标题(^#{1,3}\s+.*$)、列表项、代码块,再按max_chunk_length=512+overlap=64切分,但强制保证标题不被截断——如果标题后内容不足 64 字,就合并到前一块。这比 LangChain 的RecursiveCharacterTextSplitter更贴合中文技术文档结构。
分块完成后,进入embedding/层。默认使用bge-m3(BAAI 开源多语言嵌入模型),但关键在于它的封装方式:
# embedding/bge_m3.py class BGEM3Embedding(EmbeddingModel): def __init__(self, model_name: str = "BAAI/bge-m3", device: str = "cuda"): self.model = BGEM3FlagModel(model_name, use_fp16=True, device=device) self.tokenizer = AutoTokenizer.from_pretrained(model_name) def embed_documents(self, texts: List[str]) -> List[List[float]]: # 返回 dense + sparse + colbert_vector 三元组 embeddings = self.model.encode( texts, batch_size=16, max_length=512, return_dense=True, return_sparse=True, return_colbert_vecs=True ) return embeddings["dense_vecs"].tolist() # 默认只返回稠密向量这里暴露了 RAGFlow 的一个关键设计:它原生支持混合检索(dense + sparse)。embed_documents()返回的是稠密向量,但底层BGEM3FlagModel实际计算了三种表征。当你在config.yaml中开启hybrid_retrieval: true,后续检索模块会自动融合 BM25 稀疏得分与向量相似度,大幅提升长尾词、缩写词(如“CRM” vs “客户关系管理系统”)的召回率。这不是噱头——我们在某高校教务知识库测试中,对“教务处2024年春季课表调整通知”这类长查询,混合检索的 top-3 召回准确率比纯向量提升 37%。
2.3 向量存储与检索层:vector_store/下的 Milvus 与 Weaviate 双引擎支持
向量存哪?RAGFlow-main 默认集成 Milvus 2.4(通过pymilvus==2.4.8),但vector_store/目录结构已预留 Weaviate、Qdrant 接口。核心抽象在vector_store/base.py:
class VectorStore(ABC): @abstractmethod def add_texts(self, texts: List[str], metadatas: Optional[List[dict]] = None) -> List[str]: pass @abstractmethod def similarity_search_with_score( self, query: str, k: int = 4, filter_dict: Optional[dict] = None ) -> List[Tuple[Document, float]]: passMilvusVectorStore实现中,add_texts()会自动创建 collection(若不存在),并设置auto_id=True和enable_dynamic_field=True,这意味着你无需预先定义 schema —— 任意meta字段(如{"dept": "HR", "year": 2024})都能作为过滤条件。similarity_search_with_score()的filter_dict参数直接转为 Milvus 的expr,例如{"dept": "HR"}→"dept == 'HR'",支持in、>、like等操作符。
注意:Milvus 连接参数不在代码里硬编码,而是从环境变量读取:
MILVUS_URI=http://milvus-standalone:19530 MILVUS_USER=root MILVUS_PASSWORD=123456这意味着你换用 Weaviate 时,只需改环境变量和
vector_store/__init__.py中的工厂注册,无需动任何业务逻辑。
2.4 检索增强层:retriever/中的上下文拼接与重排序
检索不是终点。retriever/目录负责把召回的 chunk 组装成 LLM 可理解的上下文。它包含两个子模块:
base_retriever.py:执行基础向量检索,返回Document列表;rerank_retriever.py:调用bge-reranker-large对 top-k 结果重排序。关键逻辑在rerank()方法:
def rerank(self, query: str, documents: List[Document], top_k: int = 3) -> List[Document]: # 构造 [query, doc.content] 对 pairs = [[query, doc.content] for doc in documents] scores = self.reranker.compute_score(pairs) # 返回 [0.82, 0.33, 0.91...] # 按分数倒序,取 top_k ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True) return [doc for doc, _ in ranked[:top_k]]这里scores是归一化后的相关性分数(0~1),比原始向量相似度更鲁棒。血泪经验:我们曾跳过重排序,直接用向量相似度取 top-3,在某法律合同问答中,模型总把“违约责任”条款排第一,但用户问的是“付款周期”,重排序后“付款方式与时间”条款稳居榜首——因为 reranker 看的是 query-doc 整体语义匹配,而非向量空间距离。
2.5 大模型编排层:llm/目录下的提示工程与流式响应
LLM 不是黑匣子。llm/目录封装了模型调用、提示模板、流式响应三大能力。默认使用 Ollama 提供的qwen2:7b,但接口完全抽象:
# llm/base.py class LLMClient(ABC): @abstractmethod def chat_completion( self, messages: List[Dict[str, str]], stream: bool = False, **kwargs ) -> Union[str, Iterator[str]]: passOllamaLLM实现中,messages是标准 OpenAI 格式,但chat_completion()会自动注入 RAG 上下文:
# llm/ollama.py def chat_completion(self, messages: List[dict], stream: bool = False, **kwargs): # 在 user 消息前插入检索到的 context if self.context_docs: context_text = "\n\n".join([ f"【来源:{doc.meta.get('filename', '未知')} 第{doc.meta.get('page_number', '?')}页】\n{doc.content}" for doc in self.context_docs ]) # 插入 system message 或追加到首条 user message messages[0]["content"] = f"请基于以下参考资料回答问题:\n{context_text}\n\n问题:{messages[0]['content']}" # 调用 ollama.chat(...)关键细节:stream=True时,chat_completion()返回Iterator[str],每个str是一个 token(非整句),前端可逐字渲染,避免用户盯着空白屏等待。而**kwargs透传给 Ollama,如temperature=0.3,num_ctx=4096,你改配置即可调参,不用改代码。
2.6 Web 服务层:api/与web/的前后端分离架构
最后是用户接触的界面。api/目录用 FastAPI 实现 REST 接口,web/是 Vue3 单页应用(构建后静态文件放static/)。核心接口只有四个:
POST /api/v1/upload:接收 multipart/form-data,触发解析、分块、向量化全流程;POST /api/v1/chat:接收{ "query": "...", "knowledge_base_id": "kb_abc123" },执行检索+LLM生成;GET /api/v1/knowledge_bases:列出所有知识库及其文档统计;DELETE /api/v1/knowledge_base/{kb_id}:级联删除向量库、解析缓存、元数据。
api/v1/chat.py中的chat_endpoint()是主入口,它不直接调 LLM,而是调用rag_service.RAGService().run(),后者协调前述五层模块。这种分层让调试变得简单:你在curl -X POST http://localhost:8000/api/v1/chat -d '{"query":"..."}'时,日志会清晰打印每层耗时([PARSER] 124ms,[RETRIEVER] 89ms,[LLM] 2100ms),哪一层慢一目了然。
3. 快速启动与本地运行:从零到可提问的完整实操链路
别被六层架构吓住——RAGFlow-main 的设计哲学是“默认开箱即用,定制按需深入”。下面带你从空环境开始,15 分钟内跑通一个可提问的知识库。全程基于 Linux/macOS,Windows 用户请用 WSL2(不要用 CMD/PowerShell)。
3.1 环境准备:Docker 是唯一硬依赖
RAGFlow-main 采用 Docker Compose 编排,规避 Python 版本、CUDA 驱动、模型下载路径等兼容性雷区。你只需:
- 安装 Docker Desktop(≥ 4.20)和 Docker Compose(≥ 2.20);
- 确保系统有至少 8GB 内存(向量库和 LLM 都吃内存);
- 关闭其他占用 8000、19530、11434 端口的服务(如本地 Nginx、旧版 Milvus、Ollama)。
提示:不要用
pip install ragflow!这是另一个同名 PyPI 包,与本项目无关。本项目必须克隆源码运行。
3.2 源码获取与目录结构确认
# 克隆主干工程(注意:不是 fork,是原仓库) git clone https://github.com/infiniflow/ragflow.git ragflow-main cd ragflow-main # 查看关键目录(你应该看到这些) ls -F # api/ config/ docker/ embedding/ llm/ parser/ retriever/ vector_store/ web/ ...docker/目录是核心:它包含docker-compose.yml(服务编排)、milvus/(Milvus 配置)、ollama/(Ollama 模型挂载)。config/下的settings.yaml是全局配置中枢,但首次运行无需修改。
3.3 一键启动:四行命令搞定全部服务
# 1. 构建前端(Vue 项目,只需一次) cd web && npm install && npm run build && cd .. # 2. 启动所有服务(后台运行) docker-compose up -d # 3. 查看服务状态(等待 60 秒,直到 all healthy) docker-compose ps # 4. 检查关键日志(确认无 ERROR) docker-compose logs -f api | grep -E "(INFO|ERROR)"docker-compose ps输出应类似:
NAME COMMAND SERVICE STATUS PORTS ragflow-api-1 "uvicorn api.main:ap…" api running (healthy) 0.0.0.0:8000->8000/tcp ragflow-milvus-1 "/tini -- milvus run…" milvus running (healthy) 0.0.0.0:19530->19530/tcp ragflow-ollama-1 "/bin/sh -c 'ollama …" ollama running (healthy) 0.0.0.0:11434->11434/tcp ragflow-web-1 "nginx -g 'daemon of…" web running (healthy) 0.0.0.0:80->80/tcp注意:
ollama-1启动时会自动拉取qwen2:7b模型(约 4.2GB),首次运行需耐心等待。你可在另一终端执行docker exec -it ragflow-ollama-1 ollama list查看进度。
3.4 Web 界面初体验:上传、提问、验证溯源
服务就绪后,浏览器打开http://localhost(不是8000端口!Web 服务映射到 80):
- 创建知识库:点击右上角
+ New Knowledge Base,输入名称(如test_kb),选择qwen2:7b模型,点Create; - 上传文档:进入知识库,点击
Upload Files,拖入一份 PDF(如《Python 编程入门.pdf》),状态变为Processed即成功; - 发起提问:在底部输入框键入
这本书第三章讲了什么?,回车; - 验证溯源:答案下方会显示
【来源:Python 编程入门.pdf 第23页】,点击可高亮原文段落。
此时你已跑通全链路。关键验证点:打开浏览器开发者工具(F12),切换到Network标签,刷新页面,找到/api/v1/chat请求,查看Response—— 你会看到完整的 JSON 输出,含answer,references(带chunk_id和score),证明后端确实返回了结构化结果,不是前端 mock。
3.5 命令行快速验证:绕过前端直调 API
不想开浏览器?用curl直接测试:
# 1. 获取知识库 ID(从 /api/v1/knowledge_bases 响应中复制 id 字段) curl http://localhost:8000/api/v1/knowledge_bases | jq '.data[0].id' # 2. 发起问答(替换 kb_id 为你实际的 ID) curl -X POST http://localhost:8000/api/v1/chat \ -H "Content-Type: application/json" \ -d '{ "query": "Python 中列表和元组的区别是什么?", "knowledge_base_id": "kb_abc123" }' | jq '.answer'如果返回一段关于list和tuple的解释,并含【来源:...】,说明服务完全正常。这是排查前端问题的黄金方法——先证后端可用,再查前端 bug。
4. 常见问题排查:五个高频翻车现场与救命解法
RAGFlow-main 虽然设计稳健,但在不同环境部署时仍有几个经典坑。以下是某开发者在三台不同配置服务器上踩出的血泪记录,按现象→原因→解决三步还原,拒绝模糊描述。
4.1 现象:上传 PDF 后状态卡在Processing,日志显示ImportError: No module named 'fitz'
原因:fitz(PyMuPDF)是 C 扩展包,依赖系统级libmupdf。Docker 容器内虽安装了pymupdf,但基础镜像python:3.11-slim缺少编译所需的libfreetype6-dev、libjpeg-dev等头文件,导致运行时动态链接失败。
解决:不重装包,而是重建api服务镜像,显式安装依赖:
# 修改 docker/api/Dockerfile,在 pip install 前添加 RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg-dev \ libpng-dev \ && rm -rf /var/lib/apt/lists/* # 然后重建并重启 docker-compose build api docker-compose up -d api提示:此问题在 M1/M2 Mac 上更常见,因为
pymupdf的 wheel 包未覆盖 Apple Silicon 架构,必须源码编译。
4.2 现象:/api/v1/chat返回500 Internal Server Error,日志报ConnectionRefusedError: [Errno 111] Connection refused指向http://milvus-standalone:19530
原因:docker-compose.yml中api服务依赖milvus,但milvus容器启动较慢(尤其首次初始化元数据),api服务在milvus尚未 ready 时就尝试连接,导致启动失败。Docker 的depends_on只控制启动顺序,不检测服务健康。
解决:在api服务中加入健康检查重试逻辑。修改api/main.py的startup_event():
from tenacity import retry, stop_after_attempt, wait_fixed @retry(stop=stop_after_attempt(30), wait=wait_fixed(2)) async def init_milvus(): try: from vector_store.milvus import MilvusVectorStore store = MilvusVectorStore() store._client.list_collections() # 触发连接 logger.info("Milvus connection successful") except Exception as e: logger.warning(f"Milvus not ready, retrying... {e}") raise @app.on_event("startup") async def startup_event(): await init_milvus() # 替换原来的直接初始化同时pip install tenacity到api/requirements.txt。这样api会最多等待 60 秒,直到 Milvus 可用才继续。
4.3 现象:提问后答案为空,或返回I don't know,但references字段有内容且score很高
原因:LLM 上下文拼接过长,超出模型num_ctx限制。例如qwen2:7b默认num_ctx=4096,而你的检索结果共 3000 字符,加上系统提示、用户问题,总长度超限,Ollama 自动截断,导致 LLM 看不到关键信息。
解决:双管齐下。首先在config/settings.yaml中调大上下文:
llm: ollama: num_ctx: 8192 # 改为 8192其次,在llm/ollama.py的chat_completion()中,对context_text做长度截断:
# 计算剩余可用 token(估算:1 char ≈ 0.75 token) max_context_len = 8192 - len(messages[0]["content"]) - 512 # 预留系统提示和输出空间 if len(context_text) > max_context_len: context_text = context_text[:max_context_len] + " ...(内容被截断)"验证:重启ollama服务后,用docker exec ragflow-ollama-1 ollama show qwen2:7b确认num_ctx已更新。
4.4 现象:上传 Excel 文件后,/api/v1/knowledge_bases返回的文档数为 0,但日志无报错
原因:excel_parser.py默认只处理.xlsx,而你上传的是.xls(Excel 97-2003 格式)。openpyxl库不支持.xls,但excel_parser没有捕获openpyxl的InvalidFileException,而是静默降级为PlainTextParser,导致二进制内容被当纯文本解析,无法提取表格。
解决:增强excel_parser.py的异常处理,并添加xlrd支持(仅用于.xls):
# 在 excel_parser.py 开头 try: import openpyxl from openpyxl import load_workbook except ImportError: openpyxl = None try: import xlrd except ImportError: xlrd = None # 在 parse() 方法中 def parse(self, file_path: str) -> List[Document]: try: if file_path.endswith('.xlsx') and openpyxl: wb = load_workbook(file_path, read_only=True) elif file_path.endswith('.xls') and xlrd: wb = xlrd.open_workbook(file_path) else: raise ValueError(f"Unsupported Excel format: {file_path}") # ... 后续解析逻辑 except Exception as e: logger.error(f"Excel parse failed: {e}") raise然后pip install xlrd到parser/requirements.txt。这样.xls文件也能正确解析。
4.5 现象:Web 界面上传按钮灰显,控制台报Failed to fetch,Network 显示OPTIONS请求 403
原因:浏览器发起跨域请求时,先发OPTIONS预检。api服务的 FastAPI 默认未配置 CORS,web前端(运行在http://localhost)与api(http://localhost:8000)被视为不同源,预检被拒绝。
解决:在api/main.py中启用 CORS:
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost", "http://localhost:80"], # 允许前端域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )注意:生产环境请将
allow_origins设为具体域名,勿用["*"],否则存在安全风险。
5. 二次开发实战:替换向量模型、接入私有 LLM、定制解析逻辑的三步改造法
跑通只是起点。RAGFlow-main 的真正价值在于它是一套“可生长”的骨架——你不必重写整个 RAG 流程,就能把自有模型、私有知识库、特殊文档格式无缝接入。下面以三个真实改造需求为例,给出可立即抄作业的代码级方案,每一步都附验证方法。
5.1 替换向量模型:从bge-m3切到text2vec-large-chinese
某客户要求使用国产text2vec-large-chinese(智谱开源),因其在中文法律文本上微调过,比通用bge-m3准确率高 12%。改造只需三步,不碰核心流程。
第一步:安装模型依赖text2vec-large-chinese基于transformers,需在embedding/目录下新增text2vec.py:
# embedding/text2vec.py from transformers import AutoModel, AutoTokenizer import torch class Text2VecEmbedding: def __init__(self, model_name: str = "GanymedeNil/text2vec-large-chinese", device: str = "cuda"): self.tokenizer = AutoTokenizer.from_pretrained(model_name) self.model = AutoModel.from_pretrained(model_name).to(device) self.device = device def mean_pooling(self, model_output, attention_mask): token_embeddings = model_output[0] input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9) def embed_documents(self, texts: List[str]) -> List[List[float]]: inputs = self.tokenizer( texts, padding=True, truncation=True, max_length=512, return_tensors="pt" ).to(self.device) with torch.no_grad(): model_output = self.model(**inputs) sentence_embeddings = self.mean_pooling(model_output, inputs["attention_mask"]) return sentence_embeddings.cpu().numpy().tolist()第二步:注册到工厂
修改embedding/__init__.py,添加新模型:
# embedding/__init__.py from .bge_m3 import BGEM3Embedding from .text2vec import Text2VecEmbedding # 新增导入 def get_embedding_model(model_type: str, **kwargs) -> Any: models = { "bge-m3": BGEM3Embedding, "text2vec": Text2VecEmbedding, # 新增注册 } return models[model_type](**kwargs)第三步:配置生效
修改config/settings.yaml:
embedding: model_type: "text2vec" # 切换模型 model_name: "GanymedeNil/text2vec-large-chinese" device: "cuda"验证:重启api服务后,上传同一份合同 PDF,执行相同问题违约金如何计算?,对比references中的score分布——text2vec的 top-1 score 应普遍高于bge-m3(如 0.82 vs 0.71),且召回段落更聚焦“违约责任”章节,而非泛泛的“合同条款”。
5.2 接入私有 LLM:对接企业自建的 vLLM 服务
客户已有 vLLM 部署的qwen2-72b服务(地址http://vllm-server:8000/v1),要求 RAGFlow 使用该服务而非本地 Ollama。改造核心是实现LLMClient抽象。
第一步:新建 vLLM 客户端llm/vllm_client.py:
# llm/vllm_client.py import requests from typing import List, Dict, Iterator, Union from llm.base import LLMClient class VLLMClient(LLMClient): def __init__(self, base_url: str = "http://vllm-server:8000/v1", model: str = "qwen2-72b"): self.base_url = base_url.rstrip("/") self.model = model def chat_completion( self, messages: List[Dict[str, str]], stream: bool = False, **kwargs ) -> Union[str, Iterator[str]]: payload = { "model": self.model, "messages": messages, "stream": stream, "temperature": kwargs.get("temperature", 0.3), "max_tokens": kwargs.get("max_tokens", 1024), } response = requests.post( f"{self.base_url}/chat/completions", json=payload, timeout=300 ) response.raise_for_status() if stream: # 解析 vLLM 流式响应(data: {...} 格式) for line in response.iter_lines(): if line and line.startswith(b"data:"): data = json.loads(line[5:]) if "delta" in data and "content" in data["delta"]: yield data["delta"]["content"] else: data = response.json() return data["choices"][0]["message"]["content"]第二步:注入到 LLM 工厂
修改llm/__init__.py:
# llm/__init__.py from .ollama import OllamaLLM from .vllm_client import VLLMClient # 新增 def get_llm_client(llm_type: str, **kwargs) -> LLMClient: clients = { "ollama": OllamaLLM, "vllm": VLLMClient, # 新增 } return clients[llm_type](**kwargs)第三步:配置切换config/settings.yaml:
llm: type: "vllm" vllm: base_url: "http://vllm-server:8000/v1" model: "qwen2-72b"验证:在api/main.py的chat_endpoint()中,临时加一行日志logger.info(f"Using LLM client: {type(llm_client).__name__}"),重启后提问,日志应输出VLLMClient;同时vllm-server的监控面板应显示 QPS 上升,证明流量已导流。
5.3 定制解析逻辑:为内部 Confluence 导出的 HTML 添加标题锚点提取
客户 Confluence 导出的 HTML 中,章节标题含<h2 id="section-123">锚点,希望将其作为meta存入向量库,便于按锚点过滤。这需要修改parser/html_parser.py。
第一步:增强 HTML 解析器parser/html_parser.py中的parse()方法:
from bs4 import BeautifulSoup def parse(self, file_path: str) -> List[Document]: with open(file_path, "r", encoding="utf-8") as f: soup = BeautifulSoup(f.read(), "html.parser") # 提取所有 h2 标签及其 id headings = [] for h2 in soup.find_all("h2", id=True): headings.append({ "text": h2.get_text(strip=True), "id": h2.get("id"), "position": h2.sourceline }) # 原有解析逻辑(提取正文文本) content = soup.get_text() # 构建 Document,将 headings 注入 meta doc = Document( content=content, meta={ "filename": os.path.basename(file_path), "headings": headings, # 新增 meta 字段 } ) return [doc]第二步:在检索时利用锚点
修改retriever/rerank_retriever.py的rerank(),当用户提问含#section-123时,优先提升含该 id 的文档:
def rerank(self, query: str, documents: List[Document], top_k: int = 3) -> List[Document]: # <p> <a href="https://download.csdn.net/download/eighters/89350498" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>