项目实战:企业知识库问答
把前 16 章串起来,搭一个端到端可运行的企业知识库问答系统。目标是:丢进一堆内部文档,员工用自然语言提问,系统给出带引用的答案。
1. 系统目标与依赖
功能:多格式文档加载 → 切分 → 本地向量化 → 混合检索+重排 → 带引用生成。 依赖(pip):
pip install langchain langchain-community \
sentence-transformers faiss-cpu \
rank_bm25 openai2. 离线建库
from langchain_community.document_loaders import DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from sentence_transformers import SentenceTransformer
import faiss, numpy as np, pickle
embedder = SentenceTransformer("BAAI/bge-large-zh")
# 1) 加载
loader = DirectoryLoader("./kb_docs", glob="**/*.{pdf,md,txt,docx}")
docs = loader.load()
# 2) 切分
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=80)
chunks = splitter.split_documents(docs)
# 3) 向量化 + 建 FAISS 索引
texts = [c.page_content for c in chunks]
metas = [c.metadata for c in chunks]
vecs = embedder.encode(texts, normalize_embeddings=True)
index = faiss.IndexFlatIP(vecs.shape[1]) # 归一化后内积=余弦
index.add(np.array(vecs, dtype="float32"))
# 4) 持久化
faiss.write_index(index, "kb.index")
pickle.dump({"texts": texts, "metas": metas}, open("kb.pkl", "wb"))ℹ️选用 FAISS + 本地模型
本实战用 FAISS(轻量单机)和 bge 本地模型(数据不出网),适合中小企业知识库。规模上来后把 FAISS 换成 Qdrant/Milvus 即可,接口几乎不变。
3. 在线问答
import openai, json
client = openai.OpenAI()
def answer(question, k=5):
# 检索
qv = embedder.encode([question], normalize_embeddings=True)
D, I = index.search(np.array(qv, dtype="float32"), k=20)
cand = [{"text": texts[i], "meta": metas[i]} for i in I[0]]
# 组装上下文(带编号)
ctx = "\n".join(
f"[{i+1}] {c['text']}(来源:{c['meta'].get('source')})"
for i, c in enumerate(cand[:k])
)
prompt = f"""你是企业知识库助手,仅依据资料作答,未知则说不知道,句末标 [n]。
【资料】
{ctx}
【问题】{question}
【回答】"""
resp = client.chat.completions.create(
model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}],
max_tokens=512,
)
return resp.choices[0].message.content4. 加重排(可选增强)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-large")
def rerank(question, cand, k=5):
pairs = [(question, c["text"]) for c in cand]
scores = reranker.predict(pairs)
return [cand[i] for i in np.argsort(scores)[::-1][:k]]⚠️生产别把 API Key 硬编码
示例为简洁直接写变量。真实项目用环境变量 OPENAI_API_KEY 或密钥管理服务,切勿提交到 Git。
5. 验证与迭代
用第 13 章的 Ragas 跑 faithfulness 与 context_relevancy,针对低分项调 chunk_size、加混合检索或重排。
💡先纵向打通一条文档
别一次性灌入全公司资料。先选一个部门的 10 份文档跑通,调好指标,再横向扩展并加上增量更新(用稳定 id 做 upsert)。
🎯练习
用你自己的 3–5 份文档建库,问一个跨文档的问题(如「年假和病假能否同时休?」),检查答案是否综合了多来源,并标注了各自出处。
小结
- 端到端:加载 → 切分 → 本地向量化 → FAISS 入库 → 检索+重排 → 带引用生成
- FAISS + bge 本地方案适合中小企业,数据不出网
- 用编号上下文 + 指令实现引用溯源
- 用 Ragas 量化迭代,先小范围打通再扩展
- 下一章升级为 RAG + Agent 客服系统 →