☰
企业内部知识库问答:ChatGLM3-6B+LangChain+Faiss实战
2026/10/19 7:33:58 网站建设 项目流程

花了一段时间,终于把我们自己内部那套文档咨询系统跑通了。这套东西技术栈很常见,就是 ChatGLM3-6B + LangChain + Faiss 的组合,很多朋友都在做类似的方向,我今天把这套从零到一的搭建过程完整写出来。这里面的依赖安装、坑位排查、参数调整、效果优化,都会一步步说清楚,保证你照着做,不需要再从各种零散帖子里拼答案了。

1. 项目背景与目标:为什么要做企业内部知识库

1.1 企业文档检索的真实痛点

很多公司内部其实沉淀了大量文档:产品手册、技术方案、会议纪要、流程规范、历史项目总结。我见过不少团队,文档是存了不少,但基本躺在网盘或内部 wiki 里吃灰。真正遇到问题时,大家宁可去群里问,也不愿意翻文档,因为搜索靠文件名,光看标题根本不知道内容讲的是什么。后来有人想用通用大模型直接问答,但底模根本没有我们内部的内容,问个具体的项目背景,模型要么答不上来,要么一本正经地编。

这个项目要解决的就是两件事:一是把散落的文档变成可检索的结构化知识,二是让业务人员可以直接用自然语言提问,比如"去年那个客户项目的验收标准是什么"、"XX设备的告警阈值是多少"这类问题,系统直接给答案,并且能定位到原文出处。

1.2 为什么选择 ChatGLM3-6B + LangChain + Faiss

选型不是拍脑袋,而是结合了公司实际环境做的权衡。我们没法用在线商用 API,一方面数据安全要求严格,另一方面内网环境也不允许把文档内容传到外部服务,所以必须在本地私有化部署。

先看为什么选 ChatGLM3-6B。6B 参数量意味着普通单卡显卡可以跑起来,实测下来一张 24GB 显存的卡做推理完全够用,量化到 INT4 之后甚至只需要 6GB 左右的显存。中文语料表现比较稳,尤其在涉及行业术语、长文本理解上,比同样量级的开源模型效果好不少。更关键的是它对中文 prompt 的理解能力,企业内部问答系统指令复杂,这一步做不好后面全崩。

LangChain 负责把整个流程编排起来,包括文档加载、文本切分、向量化、检索、prompt 组装、模型调用,它相当于胶水层。Faiss 则是向量检索的利器,我们最开始试过用简单列表做暴力搜索,文档量到几百份后就明显变慢,换成 Faiss 后检索耗时降了一个数量级,而且它在 CPU 上性能就相当够用,不需要额外买 GPU。

下面是整体的技术架构,从用户输入到最终输出需要经过的环节:

用户提问 ↓ 问题向量化(Embedding 模型编码) ↓ 在 Faiss 索引中检索最相似的 top_k 文档片段 ↓ 将检索结果与问题一起组装进 Prompt ↓ ChatGLM3-6B 基于上下文生成回答 ↓ 返回答案并附带引用来源

整个链路的核心思路就是 RAG(检索增强生成)。陆奇那次演讲里的那句"大模型未来最重要的能力就是 knowledge recall,让 LLM 在推理的时候自由地 recall 相关资料",当时听完我挺震撼的。这个思路落地到工程上,就是先检索召回相关文档片段,再让大模型基于这些片段作答,既解决了模型不知道企业内部知识的问题,也抑制了幻觉——因为它只需要依据给定的内容来回答,而不是靠自己的臆测生成。

再对比一下几种常见方案的适用场景,方便你做技术选型时有个判断依据:

方案优点不足适用场景
在线 API 大模型效果好,部署零成本数据出域,无法定制公开信息场景,无隐私要求
全量微调开源大模型模型学会企业知识风格成本高、周期长、更新困难对话风格需要深度定制
RAG 检索问答部署快、更新即时、可溯源检索质量依赖切分与向量模型企业内部知识库、文档问答

2. 整体设计思路与方案选型

2.1 为什么 RAG 比微调更适合企业知识库

我见过很多团队一听知识库,第一反应就是要微调模型,这个思路的问题在于投入产出比太低。企业文档每天都在变,微调一次成本高、周期长,今天标注的数据明天可能就过期了,而且模型过拟合之后反而会忘记原有能力。RAG 的思路是把知识放在外部索引里,模型本身只负责理解和生成,文档更新只需要重新建立索引,过程秒级就能完成,不需要重新训练模型。

再就是溯源问题。企业内部使用,回答必须能指向出处,领导和业务同事会追问"这个结论哪来的"。RAG 天然支持把检索到的文档片段和答案一起输出,引用来源清晰可见,这一点微调模型很难做到。比如模型微调之后你问"这个项目的验收流程是什么",它能把话背个大概,但你说不清是哪份文档提供的,这种"模糊记忆"在企业场景里基本不可用。

当然 RAG 也有自己的难点,主要集中在三个方面:文档切分不能破坏语义、向量化检索的准确率要够高、prompt 组装不能让模型被无关片段带偏。这三个问题在下面的实操部分都会有针对性解法。

2.2 LangChain 在项目里承担的角色

LangChain 很多时候被误解成一个"AI 框架",实际上它更像一个标准化的工具集。我们的项目里,它主要管这几件事。

文档加载阶段,它可以把不同格式的文档统一读进来,txt、markdown 直接读,pdf 有专门的加载器,Word 文档也支持。这段解决的是"怎么把硬盘里的文件变成内存里的文本"。

切分阶段是最需要花心思的环节。LangChain 的默认切分器是按固定字符数硬切,直接用在中英文混排的企业文档上效果非常差。我们最后改了切分器,把标点符号、换行符、章节结构都考虑进去,因为语义完整的句子被切断后,从 Faiss 里召回出来的片段会产生严重的信息残缺。这部分后面专门展开讲。

还有 Prompt 模板的组织,它把系统角色设定、检索到的参考内容、用户问题组合成一段结构完整的输入送给模型。这步不是简单字符串拼接,而是要设计好逻辑结构,让模型知道自己是个企业知识库助手,检索到的资料是唯一信息来源,回答格式也有明确约定。

2.3 Faiss 与向量检索的基础逻辑

Faiss 是 Facebook AI 团队开源的向量检索库,做的事情简单说就是:给每条文档片段生成一个向量(我们用的是 768 维),构建一个索引结构,查询时把问题编码成同样的向量,在索引里快速找到最相似的几个片段。

为什么要搞这么一套复杂的东西?直接关键词搜索不就行了?关键词搜索的局限在于它不理解语义。业务同事问"设备告警设置",文档里写的可能是"报警阈值参数调整",关键词匹配会失配,但向量检索能通过语义相似度把相关信息找出来,因为两边句子向量化后在语义空间里距离很近。

Faiss 提供了多种索引类型,我们实际使用的是 IndexFlatIP,这是精确的内积索引,本身没有压缩和加速优化,就是暴力计算余弦相似度,然后取 TopK。你可能会问,暴力搜索会不会太慢?其实对于万级别的文档片段量,IndexFlatIP 在 CPU 上的检索延迟大概只有几十毫秒,完全不是瓶颈。更高级的方案还有 IVF、HNSW 这类近似最近邻索引,它们通过倒排列表或图结构加速搜索,但会影响一点召回精度,我们的场景追求确定性,所以宁可牺牲一点速度,也要保证召回结果稳定可解释。

关于向量维度,这里有个容易被忽略的细节。向量模型输出的维度决定了 Faiss 索引结构的维度参数,嵌入模型选择 m3e-base,它的输出维度是 768 维,所有切分后的文本片段都会编码成这个维度的向量。如果换了模型,维度变了,Faiss 索引必须重建,所以选型时最好先把向量模型定下来。

3. 环境准备与模型部署

3.1 基础安装与依赖配置

实操第一步是环境准备,这块踩坑最多,我把最终稳定版本的依赖组合写在这里,你按这个装基本不会有版本冲突问题。

# Python 版本建议 3.9 到 3.11 python3.10 -m venv venv source venv/bin/activate # 核心依赖 pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu118 pip install transformers==4.39.3 pip install langchain==0.1.15 pip install langchain-community==0.0.30 pip install faiss-cpu==1.8.0 pip install sentence-transformers==2.5.1 pip install m3e-base --quiet # 这是一个中文 embedding 模型 pip install streamlit==1.35.0

我把 torch 单独拎出来装是因为直接pip install torch默认装的是 CPU 版,显卡完全用不上。用 PyTorch 官方源指定 cu118 版本,对应 CUDA 11.8。如果你显卡驱动的 CUDA 版本是 12.x,可以把 cu118 换成 cu121,版本对应不上会报错。

启动模型前先检查 CUDA 环境,nvcc --version或在 Python 里运行import torch; print(torch.cuda.is_available())。输出 True 才说明 GPU 可用,否则小心后续模型加载全是性能问题。

3.2 ChatGLM3-6B 模型加载与量化选择

模型文件从 Hugging Face 下载,量大约 12GB。常见做法是直接克隆仓库,但网络不理想的话容易中断,可以用国内镜像站加速下载,下载完之后把目录重命名并放进一个专门的models文件夹里。

模型加载代码可以直接用 Hugging Face 官方示例改成适合自己的方式,核心是基座模型要拿对话模型,注意类名权限控制,要用支持对话的模型类,然后加载 tokenizer 和 model,设置设备映射。

模型加载之后,第一步要做的不是直接推理,而是用一段测试输入验证 GlM 的接入是否正常。如果输出正常,再继续跑 RAG 链路,避免后面出错时分不清是模型的问题还是 LangChain 链路的问题。

这个模型的上下文窗口上限是 8K 左右,这直接影响后续 prompt 设计的约束。检索回来的片段不能无限塞给它,要设置一个上限,比如最多取 4 个片段,每个片段控制在 200 字以内,加上 system prompt 和问题,总输入长度用 tokenizer 统计一下,免得超限报错或内容被截断。这个数字很关键,后面写检索逻辑和 prompt 模板时都要围绕它做限制。

显存方面做一个量化实测评估。纯 FP16 精度加载大概需要 12-14GB 显存,如果你有 24GB 的卡,可以全精度跑,效果最稳定。但如果只有 16GB 或更小显存,就必须量化到 INT4,显存占用可以降到 6-8GB。官方提供了量化脚本和预量化模型文件,直接用load_in_4bit=True就能完成。量化之后的效果损失在问答场景里很小,可接受范围之内。

4. 文档加载与切分策略实操

这一步是整套系统的灵魂。同一份文档,切得好不好直接决定检索质量,我试过很多种方式,把踩过的坑和经验都放这里。

4.1 多格式文档的统一加载方案

企业内部文档格式五花八门,txt、markdown、pdf、docx 都有,LangChain 每个格式提供对应加载器,先看一下常用加载方式:

from langchain_community.document_loaders import ( TextLoader, UnstructuredMarkdownLoader, PyPDFLoader, Docx2txtLoader, ) # txt 加载时指定编码,避免中文乱码 text_loader = TextLoader("docs/产品手册.txt", encoding="utf-8") # pdf 加载 pdf_loader = PyPDFLoader("docs/技术方案.pdf") # word 加载 docx_loader = Docx2txtLoader("docs/会议纪要.docx") # 批量加载整个目录下的所有文档 import os all_docs = [] for root, dirs, files in os.walk("docs"): for file in files: file_path = os.path.join(root, file) if file.endswith(".txt"): all_docs.extend(TextLoader(file_path, encoding="utf-8").load()) elif file.endswith(".pdf"): all_docs.extend(PyPDFLoader(file_path).load()) elif file.endswith(".docx"): all_docs.extend(Docx2txtLoader(file_path).load())

这里有个很关键的坑:一个 pdf 文件加载之后返回的是一个 Document 对象列表,每个对象对应一页,page_content 就是该页的文本,metadata 里有 source 页码等信息。不要对整个 pdf 只做一个 Document,否则切分器会把跨页的语义弄碎。

编码问题是中文文档的高频坑,最好的习惯是一律指定encoding="utf-8"。如果遇到 "UnicodeDecodeError",说明原文件可能是 GBK 编码,可以解码失败时换个编码方式再处理,比如在TextLoader里传encoding="gbk"。

4.2 中文文档切分:为什么默认切分器会翻车

整个项目里我花时间最多的就是这一环节。LangChain 的RecursiveCharacterTextSplitter按固定字符数切分,默认是 1000 个字符。英文文档按空格和标点切还算可以,但中文文档是按字排列的,没有天然的词边界,直接按字符硬切产生大量语义残缺。比如:

"该设备运行时如果温度超过 80 度,系统会自动触发报警机制,通知运维人员及时处理。"(原句)

切成两段后可能是:

"该设备运行时如果温度超过 80 度,系统会自动触发报警机制,"(第一段)

"通知运维人员及时处理。"(第二段)

检索时用户问"报警后怎么通知",按向量相似度算,第二段可能被召回,但第一段里"报警机制"这个关键信息反而因为更多无关内容稀释了权重,结果就是模型拿到的上下文不完整。

我的解决思路是写一个自定义切分器,逻辑是:优先按章节标题切(比如看到"第 X 章"、"X、"这类标题就分一段),再按句号、感叹号、问号、分号切分,然后按长度阈值合并小句,切分优先级从大到小是章节标题 → 换行 → 句号 → 逗号家族 → 固定长度兜底。

import re def smart_split_chinese(text, max_chunk=500, min_chunk=100): # 1. 先按章节标题粗切 chapter_pattern = r'第[一二三四五六七八九十百千0-9]+[章节篇部分]' parts = re.split(f'({chapter_pattern})', text) # 重新拼接标题与正文 chapters = [parts[i] + parts[i+1] for i in range(0, len(parts)-1, 2)] final_chunks = [] for chapter in chapters: # 2. 按换行切 paragraphs = re.split(r'\n', chapter) for para in paragraphs: para = para.strip() if not para: continue # 3. 按句子切分 sentences = re.split(r'(?<=[。!?;])', para) current_chunk = "" for sent in sentences: if len(current_chunk) + len(sent) <= max_chunk: current_chunk += sent else: if len(current_chunk) >= min_chunk: final_chunks.append(current_chunk) current_chunk = sent if current_chunk: final_chunks.append(current_chunk) return final_chunks

切分参数需要根据场景调整。经验值:min_chunk=100, max_chunk=500效果比较理想,500 字大约是正常语言模型可以一次性处理完的语义块。如果 max_chunk 太长,一个片段里掺杂多个主题,检索相关性会被稀释;太短了,一个关键信息可能不完整。

4.3 保留元数据:给每个片段打上身份标签

加载文档时 LangChain 会在 metadata 里自动填 key 信息,比如 source(文件路径)、page 等。切分之后,metadata 默认是不保留的,需要手动传递给每个切分块。我们当时的做法是读文件时手动补充了 metadata,把每个切分后的 chunk 的 source 信息补上,对应的代码逻辑是:

chunks_with_meta = [ Document(page_content=chunk, metadata={ "source": doc.metadata.get("source", "unknown"), "chunk_index": idx }) for idx, chunk in enumerate(smart_split_chinese(doc.page_content)) ]

这一步的意义在于最终返回答案时,你可以把"这段回答来自哪个文件"一并展示给用户。没有这层信息,企业内部问答系统就失去了可信度,业务人员只看到一个答案而不知道来源,基本不会当真。

5. 向量化与 Faiss 索引构建

5.1 Embedding 模型选择:中文场景需要专业选手

向量化是整个系统的语义理解核心,推荐使用的是 m3e-base 模型,它是中文社区里效果比较成熟的 embedding 模型,输出 768 维向量,在语义相似度计算上表现稳定。使用 Hugging Face 提供的 sentence-transformers 库可以很方便地加载。

from sentence_transformers import SentenceTransformer embedding_model = SentenceTransformer("moka-ai/m3e-base") text = "设备温度过高触发告警" vectors = embedding_model.encode([text]) print(vectors.shape) # (1, 768)

为什么不用 OpenAI 的 embedding 模型?原因和不用商用大模型一样,数据不能出内网,必须本地跑。为什么不用 BGE 系列或者其他模型?m3e-base 的加载依赖库安装可能比较多,但效果确实值得。两个模型都可以在本地跑,效果差异更多体现在场景上,BGE 系列在检索任务上成绩普遍不错,不过我们实测下来 m3e-base 在短文本段落检索上更均衡。

还需要强调的一点是,问题和文档片段向量化必须使用同一个 embedding 模型。如果问题用 m3e-base,文档用别的模型,那 Faiss 里的向量空间都不同,检索就等于瞎蒙。项目里要把 embedding 模型封装成通用的编码函数,链路中每个环节都调用它。

5.2 批量向量化与 Faiss 索引构建

文档切分完之后,接下来就是把所有 chunk 批量向量化并写入 Faiss 索引。有几个性能细节值得注意,比如批量编码,SentenceTransformer 的 encode 支持传入列表,写个循环逐条编码也可以,但会慢很多。我们在内部测试时一次性编码 5000 条文本,单卡 GPU 大约耗时 100 秒,CPU 就没法忍了,大概需要 5 分钟以上,所以有 GPU 最好用 GPU。

import faiss import numpy as np all_chunks = [] # 假设这是前面切分得到的所有 Document 列表 texts = [chunk.page_content for chunk in all_chunks] vectors = embedding_model.encode(texts, batch_size=32, show_progress_bar=True) vectors = np.array(vectors).astype("float32") # 创建 Faiss 索引并添加向量 dimension = vectors.shape[1] index = faiss.IndexFlatIP(dimension) faiss.normalize_L2(vectors) index.add(vectors) # 保存索引与文档对应关系 faiss.write_index(index, "db/faiss_index.bin") with open("db/chunks.txt", "w", encoding="utf-8") as f: for chunk in all_chunks: f.write(f"{chunk.page_content}\t{chunk.metadata.get('source', '')}\n")

这里有个小细节,faiss.normalize_L2(vectors)对向量做 L2 归一化,把内积变成余弦相似度。这样检索结果排序依据是语义方向的一致性,而不是向量长度——文本片段的长度差异会导致未归一化向量的模长差异,直接把原始内积当相似度,长文本会天然占便宜,归一化之后就公平了。

faiss 索引保存之后,还要单独保存一份 chunk 与 metadata 的映射文件。因为 faiss.index 只存向量数值,不存原文,查询拿到的是向量在索引里的位置 id,必须靠这个 id 去 chunks.txt 里查原文和来源。这两个文件是配套的,缺一个系统就跑不通,重建时两个文件要一起重新生成。

5.3 检索链路:从问题到 TopK 候选片段

用户提问时,系统把问题编码成向量,在 faiss 索引里搜索 top_k 相似向量,然后返回候选片段原文。这块代码是整个问答系统里最频繁调用的路径,每次提问都会跑一次,性能直接决定用户体验。

def retrieve(query, top_k=4): query_vector = embedding_model.encode([query]).astype("float32") faiss.normalize_L2(query_vector) distances, indices = index.search(query_vector, top_k) results = [] for idx, distance in zip(indices[0], distances[0]): if idx != -1 and distance >= 0.3: # 阈值过滤,太低相似度的不要 content, source = chunk_data[idx] results.append({ "content": content, "source": source, "score": float(distance) }) return results

这里的distance实际上是余弦相似度,范围在 -1 到 1 之间,越接近 1 越相似。top_k 和阈值这两个参数很重要,异步更新时可以参考下面的原则:top_k 默认是 4,但可以根据文档库的规模动态调整,测试下来内部文档知识碎片比较多时,top_k 调到 6 会比 4 更稳,因为模型看到更多候选之后,回答覆盖面更广,但也注意不要超过上下文窗口的上限。阈值 0.3 是经验值,设置太低会把一堆无关片段送进 prompt,干扰模型的理解;设置太高可能一个结果都检不到。业务文档丰富度不同,阈值需要通过实际测试调优。

这里有一个繁琐但非常有必要的设计:不能简单返回所有 topK 片段。如果某个片段和问题根本不相关,那它进到 prompt 里就是在给模型喂噪声,模型可能被带偏。所以检索时做一个相关性过滤非常关键。

6. 问答链路设计与系统集成

6.1 Prompt 模板:让模型理解它的角色

检索到候选片段后,剩下的工作就是把这些片段和用户问题组装成一段结构化的指令,交给 ChatGLM3-6B 生成回答。Prompt 的好坏直接影响回答质量,这块值得仔细打磨。

from langchain.prompts import PromptTemplate prompt_template = PromptTemplate( input_variables=["context", "question"], template="""你是一个企业内部的智能助手,请严格基于以下文档片段内容来回答用户问题。 要求: 1. 如果文档片段中包含答案,请直接给出明确回答,并用"根据以下资料"注明。 2. 如果文档片段中没有答案,请直接说"资料库中暂未找到相关信息",不要编造。 3. 回答时尽量使用文档中的原始表述,不要过度混淆信息。 文档片段: {context} 用户问题:{question} 请给出回答:""", )

这里有几个非常重要的设计取舍。首先系统角色落在"内部助手",限定了它的身份边界,模型输出的语气和风格比较受控。其次强调了两遍"不要编造",这是针对大模型幻觉问题的强约束,给它说清楚不知道就说不知道,而不是硬着头皮编。最后要求尽可能用文档原始表述,这样输出更贴合企业内部用语习惯,而不是通用模型那种"书面化表达"。

如果检索结果为空,直接把"资料库中暂未找到相关信息"返回给用户,不再调用模型。

6.2 组装 LangChain 对话链路并落地 Web 界面

下面把模型调用和检索逻辑组合起来,用 LangChain 的ConversationRetrievalQAChain或手写逻辑都可以。我们最终用手写的方式,因为这样可以自由控制每一步的行为,比如检索失败时的输出处理、上下文长度的动态管理。

def chat(query, history=None): # 1. 检索候选 candidates = retrieve(query, top_k=4, score_threshold=0.3) if not candidates: return "资料库中暂未找到相关信息", [] # 2. 拼接上下文,控制长度 context_parts = [c["content"] for c in candidates] context = "\n\n".join(context_parts) # 如果 context 太长,按 token 截断 max_context_tokens = 1500 context = truncate_by_tokens(context, max_context_tokens) # 3. 构造 prompt prompt = prompt_template.format(context=context, question=query) # 4. 调用模型 response, history = model.chat(tokenizer, prompt, history=history or []) # 5. 附加引用来源 sources = list(set([c["source"] for c in candidates])) return response, history, sources

我们最终用 Streamlit 搭了一个极简的 Web 页面:左侧展示文档库的统计信息(文档数量、索引情况),右侧是问答对话框。Streamlit 本身不需要写前端代码,写点 Python 界面基本就出来了,非常适合内部工具快速落地。

import streamlit as st st.set_page_config(page_title="企业内部知识库问答", layout="wide") st.title("企业内部知识库问答系统") if "history" not in st.session_state: st.session_state.history = [] question = st.text_input("请输入你的问题:") if st.button("提交问题"): answer, history, sources = chat(question, st.session_state.history) st.session_state.history = history st.write("**回答:**", answer) st.write("**引用来源:**") for src in sources: st.write(src)

这个界面很朴素,但内部使用完全够了。如果后面要接企业微信、钉钉机器人,也是换一层外壳的事,核心的检索问答链路不需要动。

7. 常见问题排查与优化经验

7.1 高频问题速查表

问题现象可能原因解决方案
加载模型时显存不足 (OOM)模型全精度加载,显存超限启用 INT4 量化,或用load_in_4bit=True
检索出来的片段全是无关内容向量模型和切分策略不匹配检查是否定时同模型编码,调整切分策略
回答内容"一本正经地胡说"Prompt 约束不够,或上下文丢失强化"没有答案就直接说没有"的指令,检查阈值过滤
中文文档乱码或加载失败编码格式不符指定encoding="utf-8",必要时encoding="gbk"
检索速度慢文档数较多或向量维度较高改用 IVF 或 HNSW 索引,但注意召回率影响
同一问题多次回答不稳定相似片段太多,排序不稳定调整 top_k 和相似度阈值,清理文档库冗余内容

7.2 检索质量优化经验谈

想提升检索精度,这里有一个更深层的点:文档去重。企业内部经常出现同一主题的多版本文档,比如"技术方案 v1"、"技术方案 v2(最终版)"、"技术方案 2024 修订"等。如果不做处理,检索时会同时召回多个版本,互相矛盾,模型就会非常混乱。建议在入库前做一个简单的去重规则:按标题和章节名去重,人为保留最新的一份,或者把旧版本移出知识库目录。

另外还有一个很有用的小技巧:文档内容里如果包含表格,切分器会把表格内容拼成奇怪的纯文本,检索效果非常差。对于关键表格,需要单独整理成 markdown 格式的文本再入库,或者用专门针对表格的加载器处理,我实测下来手动转换成 markdown 更可靠一些。

7.3 RAG 系统的边界与后续扩展

最后想说一下这套系统的边界问题。RAG 能回答"文档里写了什么"的问题,但回答不了"为什么"这类语义深层的问题。比如文档里写了"系统报警温度是 80 度",它能准确回答阈值是多少,但如果问"为什么要设 80 度而不是 70 度",文档里没写,系统就会答不上来。这不是 bug,而是知识库的固有边界——它只做知识的召回,不做知识的推理创造。

我自己在实际使用中最大的体会是:这套系统的效果天花板,由三个因素共同决定:文档的规范程度、切分的细腻程度、prompt 设计的合理性。模型本身只是最后一步的"发言人"。与其花大量时间换更强的底模,先把文档整理好、把切分和检索的调参工作做扎实,性价比要高得多。

如果之后文档量涨到几十万篇,Faiss 的索引策略需要往 IVF-PQ 这类压缩索引迁移,同时在 LangChain 链路中加一层 rerank(重排序)模块,先用 Faiss 粗召回 50 条,再用交叉编码器模型对候选精排取 Top4,这样能在保持精度的情况下支撑更大的知识库规模。这套扩展路径我后面也准备实践一下,到时候再开一篇详细讲讲。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询