本地文档问答实战:用 ModernBERT 向量模型与 Llama 3.2 构建全本地 RAG 应用(modernbert-rag)
2026/9/10 16:19:12 网站建设 项目流程

本地文档问答实战:用 ModernBERT 向量模型与 Llama 3.2 构建全本地 RAG 应用(modernbert-rag)

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

本篇文章围绕 ai-engineering-hub 仓库中的 modernbert-rag 示例展开,剖析一套完全运行在本机、可对 PDF 文档进行"即问即答"的 RAG(检索增强生成)应用:Embedding 由 ModernBERT(nomic-ai/modernbert-embed-base)承担,问答生成交给通过 Ollama 托管的本地 Llama 3.2,界面由 Streamlit 提供。读完本文,你将掌握基于 LlamaIndex + Ollama + Hugging Face Embedding 组装最小可用本地 RAG 的完整链路,包括环境搭建、源码级参数配置与流式问答 UI 的实现细节。

技术栈与项目定位

在仓库顶层 README.md 中,modernbert-rag被收录为 "Basic RAG" 基础示例,定位是"RAG with ModernBert embeddings"。与仓库中其他 RAG 变体(如 github-rag、document-chat-rag)相比,本示例的差异化点在于使用 ModernBERT 家族模型替换常见的中文/通用句子嵌入模型,并通过三项能力拼装出完整的本地问答闭环:

组件承担角色具体实现
ModernBERT文档与查询的向量化(Embedding)nomic-ai/modernbert-embed-base,经llama_index-embeddings-huggingface加载
Llama 3.2答案生成(LLM)Ollama 本地托管,经llama_index-llms-ollama接入
LlamaIndexRAG 编排文档加载(SimpleDirectoryReader)、向量索引(VectorStoreIndex)、查询引擎(Query Engine)
StreamlitWeb UI文件上传、PDF 预览、流式聊天界面

需要留意的是:原文档(modernbert-rag/README.md)描述为 "a locally Llama 3.2",即 LLM 完全运行在本地,不依赖外部 API;Embedding 模型首次运行需要从 Hugging Face Hub 下载权重。这也意味着整套系统对数据隐私友好,适合企业内部文档本地问答场景的原型验证。

运行原理:从 PDF 上传到流式问答的完整链路

从 rag-modernbert.py 的源码执行顺序看,整个应用的核心链路可以概括为五个阶段:

  1. 上传与落盘:用户在侧边栏选择 PDF,代码将文件字节流写入临时目录(tempfile.TemporaryDirectory);
  2. 加载与切分SimpleDirectoryReader从临时目录读取.pdf文件并完成文本抽取与切块(加载调用点);
  3. 向量化与建索引:先加载 ModernBERT Embedding 模型,再通过VectorStoreIndex.from_documents(docs, show_progress=True)把文档块编码为向量并构建内存索引(建索引调用点);
  4. 生成查询引擎:绑定 Llama 3.2 后执行index.as_query_engine(streaming=True),得到支持流式输出的查询引擎,并替换为自定义的 QA 提示词模板(查询引擎构建);
  5. 聊天问答:用户提问进入query_engine.query(prompt),检索相关文档块后交给 Llama 3.2 生成答案,答案以流式(response_gen)方式逐块渲染到聊天界面(流式渲染)。

整体是典型的"Retrieve → Augment → Generate"结构:检索质量由 ModernBERT 向量模型保证,生成质量由本地 LLM 与精心设计的提示词保证。

环境准备与依赖安装

原文档的环境搭建包含三个关键步骤,且其中藏着本示例最容易踩坑的版本约束,下面逐一展开。

3.1 创建虚拟环境,并安装 main 分支版 transformers

ModernBERT 模型较新,原文档明确提示:截至文档编写时,ModernBERT 需要从 transformers 仓库的 stable main 分支安装,下一个官方 release(4.48.x 之后)才会被打包进常规发行版。

python -m venv modernbert-env source modernbert-env/bin/activate pip install git+https://github.com/huggingface/transformers

这里把 pip 安装目标指向git+https://github.com/huggingface/transformers,即直接从 GitHub 拉取最新稳定分支源码构建。需要 Python 3.11 或更高版本。如果你在运行应用时遇到与 ModernBERT 架构或分词器相关的报错,首要排查项就是当前transformers是否满足该版本约束。

3.2 安装 Ollama 并拉取 Llama 3.2

LLM 通过 Ollama 在本地运行,安装与拉取模型同样只需两条命令:

# 在 Linux 上安装 ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取 llama 3.2 权重 ollama pull llama3.2

使用提示:确保 Ollama 服务处于运行状态(Linux 下安装后通常作为后台服务启动),并可用ollama list确认llama3.2已就绪,再启动 Streamlit 应用。

3.3 安装 Python 依赖及其角色映射

在原文档给出的依赖清单之上,结合源码中的 import 语句(文件头导入段),各依赖与代码用途的对应关系如下:

pip install streamlit ollama llama_index-llms-ollama llama_index-embeddings-huggingface
依赖源码中的用途
streamlitWeb 界面框架:文件上传、会话状态、聊天 UI
ollamaPython 侧连接本地 Ollama 服务
llama_index-llms-ollama提供llama_index.llms.ollama.Ollama类,把 Llama 3.2 接入 LlamaIndex
llama_index-embeddings-huggingface提供HuggingFaceEmbedding,加载 ModernBERT 模型

其中streamlitollama之外的llama_index-*是 LlamaIndex 官方的模型集成插件,会自动把llama_index.core等核心依赖带入环境中。此外,PDF 文本抽取依赖 LlamaIndex 内部的文档解析能力(SimpleDirectoryReader默认依赖pypdf一类解析库),如解析 PDF 报缺库错误,需补充相应解析依赖,这一点从源码选用required_exts=[".pdf"](加载参数)可以印证——应用只面向 PDF 输入。

启动并体验应用

依赖装齐后,在modernbert-rag目录下执行:

streamlit run rag-modernbert.py

浏览器会自动打开本地 Streamlit 地址(默认http://localhost:8501)。参考源码,界面布局如下:

  • 左侧边栏:标题 "Add your documents!",提供 PDF 上传控件;上传后显示 "Indexing your document..." 进度提示,索引完成后出现绿色 "Ready to Chat!" 状态,并在侧边栏内嵌 PDF 预览(侧边栏逻辑);
  • 主区域:标题 "Chat with Docs, powered by ModernBert and Llama-3.2",右上角提供 "Clear ↺" 按钮用于清空对话与释放内存,底部为聊天输入框(主区域布局)。

仓库内还附带了一段演示视频 modernbert-demo.mp4,展示从上传文档到对话的完整操作过程,可作为上手前的直观参考。

源码级实现剖析:关键参数与调用链

下文以 rag-modernbert.py 为准,逐层讲解各环节的实现要点,方便你在自己的项目中按需调整。

4.1 模型的加载:超时、缓存与 trust_remote_code

LLM 通过@st.cache_resource装饰器做资源级缓存(load_llm),保证每次 Streamlit 脚本重跑时不会重复创建连接:

@st.cache_resource def load_llm(): llm = Ollama(model="llama3.2", request_timeout=120.0) return llm

request_timeout=120.0是关键调参项:本地模型在长上下文或弱机器上生成较慢,如果单次请求超过 120 秒会超时,可依硬件情况适当放大。

Embedding 模型在每次新建索引时加载(模型加载行):

embed_model = HuggingFaceEmbedding( model_name="nomic-ai/modernbert-embed-base", trust_remote_code=True, cache_folder='./hf_cache' )

三个参数含义如下:

  • model_name="nomic-ai/modernbert-embed-base":指定 ModernBERT 嵌入模型。当前仓库(2026-09 快照)的 transformers 发行版已原生支持 ModernBERT 架构,因此可以指定模型名称直接使用,而无需再像原文档编写时那样依赖 main 分支安装 transformers;
  • trust_remote_code=True:该模型来自 HF Hub 且需要执行其自定义代码才能正确构造分词器/模型。安全性提醒:该选项会运行远端仓库中的代码,生产环境务必改为使用经过审查的自定义模型配置或镜像;
  • cache_folder='./hf_cache':权重下载缓存目录。默认情况下首次运行会联网下载 ModernBERT 权重,第二次起从本地缓存加载,可显著缩短启动时间。

加载完成后,通过Settings.embed_model = embed_model将模型写入 LlamaIndex 全局配置,后续建索引的编码过程即自动使用该 ModernBERT 模型(Settings 配置)。

4.2 文档解析与向量索引:只看 PDF、带进度条

上传文件先以临时目录方式落盘(临时目录随with块退出自动清理,避免长期占用磁盘),随后交给SimpleDirectoryReader

loader = SimpleDirectoryReader( input_dir=temp_dir, required_exts=[".pdf"], recursive=True ) docs = loader.load_data() index = VectorStoreIndex.from_documents(docs, show_progress=True)
  • required_exts=[".pdf"]:只处理 PDF,其他文件被忽略;
  • recursive=True:递归扫描子目录(对临时目录场景而言是防御性配置);
  • VectorStoreIndex.from_documents(...):内部依次完成文本切块、调用Settings.embed_model批量编码、构建内存向量索引,show_progress=True让长文档的索引过程在终端可见。

4.3 查询引擎与自定义提示词:让回答"先思考再作答"

索引构建后生成查询引擎(对应代码段):

Settings.llm = llm query_engine = index.as_query_engine(streaming=True) qa_prompt_tmpl_str = ( "Context information is below.\n" "---------------------\n" "{context_str}\n" "---------------------\n" "Given the context information above I want you to think step by step to answer the query in a crisp manner, incase case you don't know the answer say 'I don't know!'.\n" "Query: {query_str}\n" "Answer: " ) qa_prompt_tmpl = PromptTemplate(qa_prompt_tmpl_str) query_engine.update_prompts( {"response_synthesizer:text_qa_template": qa_prompt_tmpl} )

这段代码体现了 LlamaIndex 提示词注入的标准写法:text_qa_template是响应合成阶段(response synthesizer)渲染"上下文 + 问题"的模板,{context_str}{query_str}是两个内置占位符。本示例在提示词中加入了三层约束:

  1. 限定信息来源:强调只依据给定上下文作答,减少幻觉;
  2. 要求逐步思考("think step by step"):引导模型进行链式推理;
  3. 兜底机制:不确定时直接回答 "I don't know!",避免强行编造。

4.4 会话状态与多文档缓存:避免重复索引

Streamlit 每次交互都会自上而下重跑脚本,若每次都重新加载模型、重建索引会非常耗时。源码用两处机制解决(会话状态初始化):

if "id" not in st.session_state: st.session_state.id = uuid.uuid4() st.session_state.file_cache = {}
  • 每个浏览器会话生成唯一uuid4()作为session_id
  • 文档以f"{session_id}-{uploaded_file.name}"为键存入file_cache字典,命中缓存时直接复用查询引擎(缓存命中逻辑);
  • 若需为同一会话上传多份文档,每个文件键都有独立的查询引擎实例,理论上可支持按文档并行维护多个索引(从当前代码结构看,聊天主区域绑定的是最近一次赋值的query_engine,多文档的自由切换还需自行扩展)。

会话历史st.session_state.messages保存用户与助手的消息列表,脚本重跑后仍可完整回显历史对话(历史渲染)。

4.5 流式聊天 UI 与清空会话

聊天的核心在"模拟打字机"式的流式渲染(完整聊天逻辑):

streaming_response = query_engine.query(prompt) for chunk in streaming_response.response_gen: full_response += chunk message_placeholder.markdown(full_response + "▌") message_placeholder.markdown(full_response)

由于建查询引擎时启用了streaming=Truequery()返回的是流式响应对象,其response_gen生成器逐块吐出文本;每收到一块就整体重绘一次并追加光标符号,最后一帧去掉光标完成整句渲染。st.chat_message("user"/"assistant")负责按角色分区展示消息气泡。

"Clear ↺" 按钮调用reset_chat()(重置函数):清空消息历史与上下文,并显式执行gc.collect()回收内存,避免长时间对话导致内存膨胀。

4.6 PDF 预览与异常兜底

上传后侧边栏会内嵌展示当前 PDF(display_pdf 函数):将文件内容做 base64 编码后放入<iframe>data:application/pdf;base64,...数据源,无需额外 PDF 查看组件即可预览。同时,上传处理块整体被try/except包裹,解析失败时会在侧边栏显示 "An error occurred" 并通过st.stop()终止本次脚本执行(异常处理)。

关键参数速查表

位置参数取值(示例默认)作用
load_llm()model"llama3.2"指定 Ollama 托管的 LLM 模型名,须与ollama pull一致
load_llm()request_timeout120.0(秒)单次 LLM 请求超时阈值,弱机/长文档可调大
Embedding 加载model_name"nomic-ai/modernbert-embed-base"ModernBERT 嵌入模型,决定检索质量
Embedding 加载trust_remote_codeTrue允许加载并执行 HF Hub 仓库自定义代码
Embedding 加载cache_folder'./hf_cache'权重本地缓存目录,二次加载免下载
SimpleDirectoryReaderrequired_exts[".pdf"]限定参与索引的文件类型
as_query_enginestreamingTrue开启流式响应,配合response_gen逐块输出
上传控件type"pdf"Streamlit 文件上传器只接受 PDF

注意事项与实践建议

综合原文档说明与源码实现,运行本应用时有几点值得留意:

  1. 依赖版本是最大变量:ModernBERT 曾依赖 transformers 的 main 分支版本(原文档明确标注 4.48.x 之前的约束);如今该架构已合入正式发行版。若更换环境后遇到加载失败,应优先核对 transformers 与llama_index-embeddings-huggingface的版本组合;
  2. 本地资源占用:Embedding 模型(首次需联网下载)与 Llama 3.2 均在本地运行,问答时对内存/显存有一定占用;reset_chat()中的gc.collect()说明开发者已把长会话的内存回收纳入考量;
  3. 先上传再提问:从代码结构看,聊天主区域直接引用上传流程中生成的query_engine,若跳过上传直接输入问题,该变量尚未定义,因此正确使用顺序是先上传文档、等待 "Ready to Chat!" 提示后再提问;
  4. 索引为内存态VectorStoreIndex建立在进程内存中,重启应用即失效,需重新上传;如需持久化可在此基础上扩展 LlamaIndex 的存储持久化或接入向量数据库(如仓库中 fastest-rag-milvus-groq 一类的方案);
  5. 扩展方向:在保持"上传文档 → 索引 → 对话"主流程不变的前提下,可将file_cache的键控粒度做得更细以实现真正的多文档选择问答,或将 PDF 解析替换为对 Word、网页等多格式支持(只需调整required_exts与对应解析器)。

小结

modernbert-rag 是一个结构清晰、可端到端运行的本地 RAG 入门示例。它把 LlamaIndex 的文档加载、ModernBERT 向量索引、Ollama 托管的 Llama 3.2 与 Streamlit 流式界面组合在一起,仅用一个 Python 文件便演示了 RAG 应用从文件上传到逐字流式回答的全流程。对照仓库源码逐一调整其中的超时参数、缓存目录、文件类型过滤与提示词模板,你就能快速将其改造成贴合自身业务文档的本地问答原型。

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询