Chat with Your Local PDFs: Build a Llama 3.3 RAG App with LlamaIndex, Qdrant, and Streamlit【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub本文将以document-chat-rag项目为主线完整讲解如何用本地部署的 Meta AI Llama 3.3经 Ollama 提供服务构建一个 Chat with your Docs 的 RAG 文档问答应用PDF 文档上传后被切分、向量化并写入 Qdrant 向量库查询时由 LlamaIndex 检索相关片段并交给 Llama 3.3 生成带上下文的回答前端用 Streamlit 提供上传与流式聊天界面。读完本文你将掌握本地大模型 向量数据库搭建文档聊天机器人的完整环境配置、索引构建、提示词定制与流式 UI 实现并理解仓库中两套 NotebookQdrant 持久化版与 Streamlit 应用之间的工程差异。该教程来自 ai-engineering-hub 仓库中的document-chat-rag目录其核心目标非常明确在本地运行 Llama 3.3用 RAG 方式与自己的文档对话。核心源码为 app.py两个 Notebookrag_demo.ipynb 与 main_self_hosted_vectorDB.ipynb则演示了接入自托管 Qdrant 向量库的完整索引流程仓库内的示例文档为 docs/dspy.pdf。一、项目定位与整体架构1.1 这是一个什么样的应用README 对项目的描述只有一句话但技术含量很高利用本地运行的 Llama 3.3 构建 RAG 应用实现与你的文档对话并用 Streamlit 搭建 UI。结合 app.py 与两个 Notebook 的源码可以将它拆解为五层技术栈层次选型说明文档解析SimpleDirectoryReader读取目录下.pdf文件并解析成 LlamaIndex 的 Document/NodeEmbeddingBAAI/bge-large-en-v1.5HuggingFace 本地下载将文本块编码为稠密向量LLM 推理Llama 3.3llama3.3由 Ollama 在本地提供 OpenAI 兼容的 HTTP 服务负责根据检索上下文生成回答向量检索Qdrant自托管默认端口 6333存储向量与元数据供相似度检索Notebook 版本应用编排与 UILlamaIndexVectorStoreIndex QueryEngine Streamlit组装检索-生成链路提供上传与流式聊天界面从架构上看这属于典型的Retrieval-Augmented Generation检索增强生成范式私有文档不进 API、不上云模型权重与向量库都跑在本机适合对数据隐私敏感或需要离线使用的场景。1.2 仓库内的两条技术路线阅读源码后可以推断出目录内实际上演示了两种落地形态二者共享加载 PDF → 向量化 → 建索引 → 问答的主流程但存储方式不同Notebook 自托管向量库路线rag_demo.ipynb 与 main_self_hosted_vectorDB.ipynb 显式连接本地 QdrantQdrantClient(hostlocalhost, port6333)将向量写入名为chat_with_docs的 collection属于可持久化、可横向扩容的工程化方案。Streamlit 应用路线app.py 面向单次交互会话上传 PDF 后直接用VectorStoreIndex.from_documents(docs, show_progressTrue)在内存中建索引并把 query engine 缓存进st.session_state。它更关注快速开箱即用同一份 PDF 在会话内不重复索引。这两条路线在后续章节会分别展开便于你根据自己的需求单机演示 vs. 长期存储做选择。二、环境准备Ollama、Qdrant 与 Python 依赖README 提供了三条安装命令这是运行整个项目的硬性前提。下面逐一说明其作用与注意事项。2.1 安装 Ollama 并拉取 Llama 3.3# setup ollama on linux curl -fsSL https://ollama.com/install.sh | sh # pull llama 3.3:70B ollama pull llama3.3第一条命令通过官方安装脚本在 Linux 上安装 Ollama第二条命令下载 Llama 3.3Meta AI 的 70B 级指令模型README 与 Notebook 标题中写为Llama-3.3 by Meta AI。模型权重随后由 Ollama 的本地服务按需加载。拉取完成后请确认 Ollama 服务处于运行状态Linux 下安装脚本通常会注册为 systemd 服务。Ollama 默认在localhost:11434提供 REST API这正好是后续llama_index.llms.ollama.Ollama客户端连接的默认端点——因此 app.py 里只需要一行st.cache_resource def load_llm(): llm Ollama(modelllama3.3, request_timeout120.0) return llmrequest_timeout120.0是一个值得注意的参数70B 级模型在消费级 GPU 上首 token 生成较慢若不调高默认超时首次问答很容易报超时错误。2.2 用 Docker 启动 Qdrant 向量库docker run -p 6333:6333 -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage:z \ qdrant/qdrant参数拆解-p 6333:6333REST API 端口Python 端QdrantClient默认即连接 6333-p 6334:6334gRPC 端口供高性能客户端使用-v $(pwd)/qdrant_storage:/qdrant/storage:z将宿主机当前目录下的qdrant_storage挂载为 Qdrant 的数据目录容器重启后向量数据不丢失。:z后缀适用于 SELinux 环境如 Fedora/RHEL用于自动修正挂载目录的安全上下文若你的系统没有启用 SELinux去掉该后缀亦可正常使用。启动后可访问http://localhost:6333确认 Qdrant 存活之后 Notebook 中的QdrantClient(hostlocalhost, port6333)便能直接连接。补充Qdrant 属于该项目的可选增强。若你只想体验 Streamlit 版内存索引而不跑 Notebook可以跳过本节一旦运行 Notebookcreate_index会强制连接 Qdrant见第四节。2.3 安装 Python 依赖README 明确要求 Python 3.11 或更高版本并给出最小依赖集pip install streamlit ollama llama-index-vector-stores-qdrant结合两个 Notebook 的 import 段实际完整依赖还应包含以下组件README 只列出核心三个Notebook 中实际 import 了更多import nest_asyncio import qdrant_client from llama_index.core import Settings, PromptTemplate from llama_index.core import VectorStoreIndex, StorageContext, SimpleDirectoryReader from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.llms.ollama import Ollama from llama_index.postprocessor import SentenceTransformerRerank # 可选重排序 from llama_index.vector_stores.qdrant import QdrantVectorStore # Qdrant 存储适配器因此更稳妥的安装方式是pip install streamlit ollama llama-index-vector-stores-qdrant \ llama-index-embeddings-huggingface llama-index-postprocessor-sentence-transformers \ qdrant-client nest-asyncio sentence-transformers安装完成后embedding 模型BAAI/bge-large-en-v1.5与重排序模型cross-encoder/ms-marco-MiniLM-L-2-v2会在首次运行时自动从 Hugging Face 下载到本地缓存。三、离线Notebook索引流水线接入自托管 Qdrantrag_demo.ipynb 与 main_self_hosted_vectorDB.ipynb 结构一致均以单元测试式的步骤演示了从 PDF 到可问答索引的完整链路。以 main_self_hosted_vectorDB.ipynb 为主线其名称也点明了 self-hosted VectorDB 主题分为四步。3.1 在 Jupyter 中初始化import nest_asyncio # allows nested access to the event loop nest_asyncio.apply()LlamaIndex 的加载与检索流程依赖 asyncio 事件循环而 Jupyter 自身已在运行一个事件循环直接调用会发生冲突nest_asyncio.apply()允许嵌套访问事件循环这是在 Notebook 里跑 LlamaIndex的标准前置操作。3.2 指定文档目录# add your documents in this directory, you can drag drop input_dir_path ./docsNotebook 约定从本地docs目录读取 PDF拖进去即可。对应当前仓库docs/dspy.pdf 就是现成的示例文档——这也是 Notebook 最后用What exactly is DSPy?作为验证问题的原因。3.3 定义 Qdrant 索引工厂import qdrant_client from llama_index.core import StorageContext from llama_index.vector_stores.qdrant import QdrantVectorStore collection_name chat_with_docs client qdrant_client.QdrantClient( hostlocalhost, port6333, ) def create_index(documents): vector_store QdrantVectorStore(clientclient, collection_namecollection_name) storage_context StorageContext.from_defaults(vector_storevector_store) index VectorStoreIndex.from_documents( documents, storage_contextstorage_context, ) return index这一段是自托管向量库路线的核心QdrantVectorStore作为 LlamaIndex 与 Qdrant 之间的适配器负责把 embedding 结果与节点文本批量写入 Qdrant 的chat_with_docscollection首次写入时自动创建。StorageContext.from_defaults(vector_store...)告诉 LlamaIndex不要再把向量写进默认的本地存储改写到 Qdrant。3.4 配置 LLM 与 Embedding构建索引并问答# setup llm embedding model llm Ollama(modelllama3.3, request_timeout120.0) # 另一个可替换的 embedding 选择源码中保留了注释示例 # embed_model HuggingFaceEmbedding(model_nameSnowflake/snowflake-arctic-embed-m, trust_remote_codeTrue) embed_model HuggingFaceEmbedding(model_nameBAAI/bge-large-en-v1.5, trust_remote_codeTrue) Settings.embed_model embed_model # load data loader SimpleDirectoryReader( input_dirinput_dir_path, required_exts[.pdf], recursiveTrue, ) docs loader.load_data() # Creating an index over loaded data try: index create_index(docs) print(Using Qdrant collection) except Exception: index VectorStoreIndex.from_documents(docs, show_progressTrue) Settings.llm llm query_engine index.as_query_engine()这段代码里有三个实现细节值得展开required_exts[.pdf]recursiveTrue目录加载器只接受 PDF 扩展名并递归扫描子目录避免误入非 PDF 文件导致解析失败embedding 在索引前的关键顺序Settings.embed_model必须先于VectorStoreIndex.from_documents设置LlamaIndex 全局配置才会在节点切分后调用 bge 模型生成向量Settings.llm则在构造 query engine 前设置try/except 兜底若 Qdrant 未启动导致create_index抛错代码会自动降级为 LlamaIndex 默认的本地内存向量索引并继续运行——也就是说不依赖 Qdrant 也能把流程跑通只是失去了持久化能力。至于HuggingFaceEmbedding(..., trust_remote_codeTrue)bge 系列模型需要远程加载自定义的模型代码实现因此必须显式信任。上文截图来自 Streamlit 界面Notebook 阶段的真实运行反馈则以进度条形式呈现先Parsing nodes: 100%完成文档切分再Generating embeddings: 100%完成向量化最后直接打印 markdown 回答。3.5 进阶为查询链路加入重排序Rerankrag_demo.ipynb 额外演示了双阶段检索——向量召回后先用交叉编码器对候选片段重排序再送入 LLMfrom llama_index.core.postprocessor import SentenceTransformerRerank # 注rag_demo 使用更轻量的 llama3.2:1b 模型以降低资源门槛 llm Ollama(modelllama3.2:1b, request_timeout120.0) rerank SentenceTransformerRerank( modelcross-encoder/ms-marco-MiniLM-L-2-v2, top_n3 ) query_engine index.as_query_engine( similarity_top_k10, node_postprocessors[rerank] )参数含义similarity_top_k10向量检索阶段从 Qdrant 取回 10 个候选节点召回放宽保证不漏SentenceTransformerRerank(modelcross-encoder/ms-marco-MiniLM-L-2-v2, top_n3)用交叉编码器对 10 个候选逐一打分排序只保留最相关的 3 个进入上下文精排收紧保证质量modelllama3.2:1b提醒我们模型体积直接决定资源占用rag_demo面向轻量演示而选 1B 小模型main_self_hosted_vectorDB则面向精度使用 70B 的 llama3.3两者只是同一代码骨架的不同实例化。两个 Notebook 最终都以示例文档验证链路例如查询What exactly is DSPy?基于 docs/dspy.pdf 检索到的上下文给出符合原文的解答。四、定制问答提示词让模型引用上下文、不乱编两个 Notebook 与 app.py 使用了同一套定制化 QA 提示词模板这是本项目在生成质量上最值得借鉴的细节from llama_index.core import PromptTemplate 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 dont know the answer say I dont 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} )逐条解读设计意图{context_str}与{query_str}是 LlamaIndex 问答合成器预留的两个占位符分别被注入检索到的上下文与用户问题think step by step ... in a crisp manner引导模型逐步推理并给出简洁回答缓解只看到结论性片段就强行输出的跳步问题incase you dont know the answer say I dont know!显式授权模型拒绝回答这是抑制 RAG 幻觉知识库里没有就编造最直接的提示词手段update_prompts的 key 必须是response_synthesizer:text_qa_template这是 LlamaIndex 内部响应合成器的标准提示词键名写错则模板不会生效。五、Streamlit 聊天界面源码剖析app.py 在 README 描述的架构之上实现了完整的交互闭环整个文件约 170 行核心逻辑集中在三块。5.1 会话级文件缓存避免重复索引if id not in st.session_state: st.session_state.id uuid.uuid4() st.session_state.file_cache {} session_id st.session_state.id每次浏览器会话生成一个uuid并用它在st.session_state.file_cache里以f{session_id}-{文件名}为 key 缓存已构建好的 query engine。用户在单次会话内反复切换问题时相同 PDF 不会二次切分与向量化从而省去重复索引的开销。5.2 PDF 上传 → 临时目录 → 建索引uploaded_file st.file_uploader(Choose your .pdf file, typepdf) if uploaded_file: with tempfile.TemporaryDirectory() as temp_dir: file_path os.path.join(temp_dir, uploaded_file.name) with open(file_path, wb) as f: f.write(uploaded_file.getvalue()) if file_key not in st.session_state.get(file_cache, {}): loader SimpleDirectoryReader( input_dirtemp_dir, required_exts[.pdf], recursiveTrue, ) docs loader.load_data() llm load_llm() embed_model HuggingFaceEmbedding( model_nameBAAI/bge-large-en-v1.5, trust_remote_codeTrue ) Settings.embed_model embed_model index VectorStoreIndex.from_documents(docs, show_progressTrue) Settings.llm llm query_engine index.as_query_engine(streamingTrue) # ... 应用自定义 QA 模板同第四节... st.session_state.file_cache[file_key] query_engine实现要点file_uploader(typepdf)收到的上传流是BytesIO而SimpleDirectoryReader需要真实文件路径所以先落盘到tempfile.TemporaryDirectory()目录随with块退出自动清理加载、向量化、建索引全程复用与 Notebook 相同的组件bge-large-en-v1.5 VectorStoreIndex保证两套入口行为一致st.cache_resource装饰的load_llm()保证整个应用只初始化一次 Ollama 客户端。需要特别说明的是app.py 的索引默认存放在 LlamaIndex 内存向量存储中未引用 Qdrant 依赖这是与 Notebook 路线最本质的区别——Streamlit 版把向量持久化外包给了会话缓存。如果你希望 Streamlit 应用同样写入 Qdrant只需把 create_index 中QdrantVectorStore StorageContext的写法移植进 app.py 即可。5.3 流式回答与 PDF 内嵌预览with st.chat_message(assistant): message_placeholder st.empty() full_response 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)index.as_query_engine(streamingTrue)返回流式响应对象response_gen逐个产出 token 块Streamlit 通过反复markdown(full_response ▌)实现打字机效果。与此同时侧边栏用 base64 编码把上传的 PDF 内嵌进iframe实时预览display_pdf函数让用户可以对照原文核验回答——这正是 README 中与文档对话 Streamlit UI的用户可见形态。界面顶部右侧的 Clear ↺ 按钮则绑定reset_chat()清空st.session_state.messages与 context并主动gc.collect()回收显存/内存。六、运行与使用环境就绪Ollama 已拉取llama3.3、依赖已安装后启动方式如下# 方式一启动 Streamlit 图形界面 streamlit run app.py浏览器打开 Streamlit 分配的本地地址后在侧边栏Add your documents!区域上传一份 PDF等待 Indexing your document... 完成后侧边栏提示Ready to Chat!即可在右侧输入框提问并看到逐字输出的流式回答。# 方式二Notebook 逐格执行推荐先跑通全链路 # 打开 rag_demo.ipynb 或 main_self_hosted_vectorDB.ipynb # 将 PDF 放入 ./docs 目录后从第一个 cell 顺序运行到最后一个 cell。运行 Notebook 有一个来自项目源码的实用提醒写在 main_self_hosted_vectorDB.ipynb 末尾的 markdown 提示中如果要在同一环境中从 Notebook 切换到 Streamlit务必先通过 Restart 重启内核清空 GPU 显存——Notebook 加载的 70B 模型与 embedding 模型会长期占用显存直接再跑 Streamlit 极可能因显存不足而失败。Notebook 末尾也提供了!nvidia-smi命令用于核对显存占用情况。七、工程化要点与二次开发建议将 README、两个 Notebook 与 app.py 对照阅读后可以沉淀出几条对该项目乃至一般本地 RAG 应用都适用的经验区分演示与持久化两条索引路径Notebook 走 Qdrant适合需要跨会话复用索引、增量更新语料的场景app.py 走内存索引胜在零配置。二者用StorageContext一行切换可依据数据规模抉择。模型与资源的取舍llama3.370B回答质量高但显存开销大、首 token 慢因此request_timeout必须调大rag_demo里的llama3.2:1b适合资源受限环境快速验证。切换模型只需修改Ollama(model...)并执行ollama pull对应标签。召回质量的完整拼图similarity_top_k放宽召回→ 交叉编码器top_n精排压缩→ 定制 QA 模板强制不知道就说不知道三层联动是 Notebook 中已经验证的组合拳。embedding 模型可替换main_self_hosted_vectorDB.ipynb 中保留了Snowflake/snowflake-arctic-embed-m作为候选二者均需trust_remote_codeTrue更换 embedding 时若复用旧 collection注意清空重建避免向量维度不一致。至此你已具备复现 document-chat-rag 完整能力所需的全部信息从 Ollama/Qdrant/依赖三件套的环境准备到 Notebook 中切分 → 向量化 → 写入 Qdrant → 检索重排的索引流水线再到 Streamlit 中上传、建缓存、流式问答的交互实现。这套本地大模型 向量库 文档聊天的范式可以直接迁移到企业内部知识库、私有合同问答、论文阅读助手等场景。【免费下载链接】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),仅供参考
