把企业资料、项目笔记或技术手册交给云端模型处理,往往会涉及隐私、网络稳定性和调用成本。Ollama 可以在本机运行大语言模型,LlamaIndex 则负责文档读取、切分、索引、检索与问答编排。两者组合后,可以搭建一套数据不离开本地、支持持续更新的 RAG 文档问答系统。本文以 Python 为例,梳理从环境安装到增量索引维护的完整实践。🚀
一、整体架构与处理流程
系统主要包含四层:本地文档是数据源;LlamaIndex 将文档解析为节点并生成向量;向量存储负责相似度检索;Ollama 根据检索结果生成答案。用户提问时,系统不会把全部文件塞进模型,而是先检索相关片段,再将问题与片段一并交给模型,从而降低上下文压力并提高回答针对性。
LlamaIndex 的索引由 Document 构建,内部以 Node 表示切分后的内容,VectorStoreIndex 是常见的 RAG 索引类型。其索引、检索器和查询引擎之间的关系可参考 LlamaIndex 索引文档。
二、准备 Ollama 与 Python 环境
先安装并启动 Ollama,然后拉取适合本机配置的对话模型与嵌入模型。例如可执行 ollama pull llama3.1 和 ollama pull nomic-embed-text。模型并非越大越好,内存有限时应优先选择较小参数版本,并适当控制上下文窗口。
创建虚拟环境后安装依赖:pip install llama-index llama-index-llms-ollama llama-index-embeddings-ollama。如果需要读取 PDF、Word 或使用 Chroma、Qdrant 等持久化向量库,再安装对应集成包。LlamaIndex 官方也提供了使用 Ollama 构建本地应用的 本地模型入门示例。
三、建立本地文档问答索引
假设资料统一放在 data 目录,核心初始化逻辑可以写成:
from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
Settings.llm = Ollama(model="llama3.1", request_timeout=300.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")
documents = SimpleDirectoryReader("data", filename_as_id=True).load_data()
index = VectorStoreIndex.from_documents(documents)
index.storage_context.persist(persist_dir="storage")
filename_as_id=True 很重要,它让文件路径成为相对稳定的文档 ID,为后续识别新增和修改文件提供依据。首次构建完成后应立即持久化索引,否则程序重启时仍需重新切分和计算全部向量。
查询阶段可通过 index.as_query_engine(similarity_top_k=4) 创建查询引擎,再调用 query_engine.query("项目的部署流程是什么?")。建议在提示词中明确要求“仅根据检索内容回答,依据不足时说明无法确定”,以减少模型凭空补充内容。📚
四、实现增量索引更新
最简单的增量方案是重新读取目录,再执行 index.refresh_ref_docs(documents)。当输入文档具有稳定 ID 时,该方法会插入新文档,并刷新 ID 相同但内容已经变化的文档,而未变化内容无需重复更新,具体行为见 文档管理说明。
documents = SimpleDirectoryReader("data", filename_as_id=True).load_data()
results = index.refresh_ref_docs(documents)
index.storage_context.persist(persist_dir="storage")
需要注意的是,刷新当前目录只能处理新增与修改,无法天然判断某个旧文件是否已从目录删除。生产环境可维护一份文档清单:扫描得到当前 ID 集合,与索引中已登记的 ID 集合比较;对缺失项调用 index.delete_ref_doc(doc_id, delete_from_docstore=True),再持久化存储。这样才能形成新增、修改、删除都覆盖的完整同步流程。🔄
更稳健的 IngestionPipeline 方案
文档较多时,推荐使用 IngestionPipeline,并配置切分器、嵌入模型、docstore 和 vector_store。docstore 会维护文档 ID 与内容哈希之间的映射:ID 相同且哈希未变化时跳过,哈希改变时重新处理;连接向量存储后还可进行 upsert。该机制可参考 Ingestion Pipeline 文档管理示例。
- 新增文件:生成新 ID,完成切分、向量化和写入。
- 修改文件:保持原 ID,通过内容哈希触发重新处理。
- 未变化文件:直接跳过,避免重复计算嵌入。
- 删除文件:由目录清单差异检测后显式删除关联节点。
五、实践中的关键优化
- 固定文档标识:不要使用每次运行都会变化的随机 ID,文件移动或重命名时还要制定映射策略。
- 合理切分内容:短片段可能缺少上下文,过长片段则降低检索精度。可先按标题或段落切分,再设置少量重叠。
- 保持嵌入模型一致:已有索引更换嵌入模型后,新旧向量不再处于同一表示空间,通常应完整重建索引。
- 保留来源元数据:在节点中保存文件名、章节和页码,让答案能够显示依据,便于人工核验。
- 加入更新锁:查询与索引写入并发时,应使用文件锁、任务队列或版本化索引,防止读取到不完整状态。
- 记录更新日志:至少保存处理时间、文档 ID、内容哈希、操作类型与异常信息,方便排查遗漏和重复数据。
总结
Ollama 与 LlamaIndex 的组合,能够在本地完成模型推理、文档索引和检索问答。原型阶段可使用 VectorStoreIndex 配合 refresh_ref_docs 快速实现增量刷新;资料规模扩大后,再升级为带 docstore、持久化向量库和删除检测的 IngestionPipeline。真正稳定的增量索引不仅要“更新新内容”,还要确保文档 ID 稳定、嵌入模型一致、删除操作可追踪,并让每条回答都能回溯到原始资料。✅