Skip to content

第五阶段:LlamaIndex 企业知识库(读 PDF / Word,注明出处) ​

上一阶段我们手写了 RAG。真实业务里的文档是 PDF、Word,需要解析、切分、建索引、注明出处——这些 LlamaIndex 都帮我们封装好了。这一节完成项目 1:企业知识库问答。

一、安装 ​

bash
pip install llama-index llama-index-llms-openai-like llama-index-embeddings-openai-like llama-index-readers-file
  • llama-index:核心框架
  • llama-index-llms-openai-like:接入 OpenAI 兼容的 LLM(我们的中转平台)
  • llama-index-embeddings-openai-like:接入 OpenAI 兼容的 Embedding(本地 LM Studio)
  • llama-index-readers-file:PDF / Word 解析插件,必须装! 缺了它 PDF 会被当成纯文本读,结果全是乱码(见 踩坑记录)。

把要问答的文档(PDF、Word、txt 等)放进项目下的 data/ 文件夹。

二、05_kb.py 完整代码 ​

python
import os
from dotenv import load_dotenv
from llama_index.core import (Settings, SimpleDirectoryReader, StorageContext,
                              VectorStoreIndex, load_index_from_storage)
from llama_index.core.node_parser import SentenceSplitter      # 文本切分器
from llama_index.llms.openai_like import OpenAILike             # OpenAI 兼容 LLM
from llama_index.embeddings.openai_like import OpenAILikeEmbedding  # OpenAI 兼容 Embedding

load_dotenv()  # 读取 .env 配置

# ===== 1. 全局设置:告诉 LlamaIndex 用哪个 LLM、哪个 Embedding、怎么切分 =====
Settings.llm = OpenAILike(
    model=os.getenv("LLM_MODEL"),        # 例如 deepseek-flash
    api_base=os.getenv("LLM_BASE_URL"),  # 中转平台地址
    api_key=os.getenv("LLM_API_KEY"),
    is_chat_model=True,                  # 使用 Chat 接口(必须设,否则走旧的补全接口)
    temperature=0,                       # 问答要稳定、忠实于资料
)
Settings.embed_model = OpenAILikeEmbedding(
    model_name="bge-m3",                 # 以 LM Studio 中显示的模型名为准
    api_base="http://localhost:1234/v1", # 本地 LM Studio
    api_key="lm-studio",                 # 本地服务不校验,随便填
    embed_batch_size=64,                 # 每批送 64 段文字去算向量,加快速度
)
# 每段约 512 个 token,相邻段重叠 50 个 token,避免一句话被切断后丢失上下文
Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)

STORAGE = "./kb_storage"   # 索引保存目录

# ===== 2. 建索引或加载已有索引 =====
if os.path.exists(STORAGE):
    # 已经建过:直接从磁盘加载,省去重新解析和计算向量
    storage_context = StorageContext.from_defaults(persist_dir=STORAGE)
    index = load_index_from_storage(storage_context)
    print("已加载现有索引")
else:
    # 第一次运行:读取 data 目录下的所有文档
    documents = SimpleDirectoryReader("./data").load_data()
    for doc in documents:
        # 打印每个文档(PDF 每页一个)的文件名、字数和前 300 字,检查解析是否正常
        print(doc.metadata.get("file_name"), len(doc.text), "字")
        print(doc.text[:300])
        print("-" * 40)
    # 切分 → 计算向量 → 建索引,show_progress 显示进度条
    index = VectorStoreIndex.from_documents(documents, show_progress=True)
    index.storage_context.persist(persist_dir=STORAGE)   # 保存到磁盘
    print("索引已建立并保存")

# ===== 3. 创建问答引擎:每次检索最相关的 3 段 =====
query_engine = index.as_query_engine(similarity_top_k=3)

# ===== 4. 循环提问 =====
while True:
    q = input("\n问题:")
    if q == "exit":
        break
    response = query_engine.query(q)
    print("【回答】", response)
    print("【出处】")
    for node in response.source_nodes:   # 检索到的原文片段
        meta = node.metadata
        print(f"  {meta.get('file_name')} 第{meta.get('page_label')}页 "
              f"相似度 {node.score:.3f}:{node.get_content()[:80]}")

三、.gitignore ​

文档可能涉及版权或隐私,索引里也含有原文,都不要上传:

bash
echo data/ >> .gitignore
echo kb_storage/ >> .gitignore
git add .
git commit -m "05 LlamaIndex 企业知识库"
git push

四、实测结果(真实运行数据) ​

测试文档:折叠键盘说明书.pdf,1 页、1263 字,被切成 3 段,约 2 秒建完索引。

问:怎么配对

打开电源开关(ON),红灯亮 10 秒后熄灭;按 Fn+A / S / D 打开 BT1 / BT2 / BT3 通道,点按 Connect 键;手机搜索配对名 BOW;连接成功后指示灯熄灭。

出处:折叠键盘说明书.pdf 第 1 页,相似度 0.558 / 0.484。

问:怎么保修

按三包规定:签收次日起 7 日内可退货或换货;8~30 日内可换货或维修;12 个月内免费维修。并给出了所需资料和服务热线。

回答带上了文件名和页码,用户可以回原文核对——这就是企业知识库“可追溯”的价值。

五、要点 ​

  • RAG 效果差,常常是文档解析出了问题,而不是模型不行。 建索引前先看打印出来的字数和前 300 字是否正常。
  • 换了文档后必须删除旧索引,否则程序会直接加载 kb_storage,仍然检索旧内容(PowerShell):
powershell
Remove-Item -Recurse -Force kb_storage
  • 扫描版 PDF、缺少解析插件等问题见 踩坑记录。

下一节预告 ​

进入项目 2:客服 / 工单 Agent,学习 Function Calling——让模型自己决定调用查订单、建工单等工具。

👉 下一阶段:Function Calling 工具调用