资讯动态

本地部署大模型不只是聊天!结合 Ollama 与向量数据库,打造一个离线 AI 助手

发布时间:2026/10/16 22:51:08 来源:尧图企业网站定制
引言当跑起来不再等于用起来过去两年本地大模型的部署门槛被 Ollama 这类工具几乎抹平了。一条ollama run qwen2.5:7b就能在笔记本上拉起一个能说会道的对话模型于是很多人兴冲冲地装好、问几个问题、截个图然后……就没有然后了。问题出在哪因为一个只会聊天的模型对你没有任何私有价值。它不知道你公司的运维手册写了什么不知道你上个月那份 PDF 技术报告里的结论更不知道你代码仓库里的接口约定。你问它我们的鉴权流程怎么走它只能一本正经地编一段听起来很专业的胡话——这就是幻觉。本地部署真正的价值从来不是能对话而是在数据不出内网的前提下让模型回答只有你才知道的问题。要做到这一点缺的那块拼图就是向量数据库 检索增强生成RAG。这篇文章不会停留在概念层面。我们会用 Ollama 提供推理与嵌入能力用 ChromaDB 做向量存储从零搭一条可用的离线 RAG 管线并把它真正变成每天能用的东西。一、为什么光有 Ollama 还不够1.1 大模型的三个先天缺陷把大模型当成一个知识库来用你会撞上三堵墙知识截止模型的参数在训练结束那一刻就冻结了。2024 年发布的模型不可能知道你 2025 年的内部规范。幻觉当模型不知道答案时它不会说我不知道而是基于概率分布生成一段最像答案的文本。在安全、运维、医疗场景下这种自信的错误极其危险。上下文窗口有限就算你把整个知识库塞进 prompt7B 模型的 32K 上下文也撑不住而且长上下文里模型对中间内容的注意力会显著衰减Lost in the Middle现象。1.2 微调还是 RAG很多人第一反应是那我微调一下不就行了。但微调有几个现实问题维度微调Fine-tuningRAG数据更新需重新训练成本高改一行文档即可事实准确性仍会幻觉且更难溯源可附带原文引用硬件要求7B 全参微调至少 24G 显存一张 8G 卡即可知识边界隐式编码在权重里显式存在向量库中结论很明确让模型学怎么说让检索负责说什么。这是 RAG 的核心分工。二、核心原理从文本到向量再从向量回到答案2.1 嵌入把语义压缩成坐标嵌入模型Embedding Model的作用是把一段文本映射成一个固定维度的稠密向量。比如nomic-embed-text输出 768 维bge-m3输出 1024 维。关键在于语义相近的文本在向量空间中的方向也相近。判断相近最常用的度量是余弦相似度sim(A,B)A⋅B∥A∥⋅∥B∥\text{sim}(A, B) \frac{A \cdot B}{\|A\| \cdot \|B\|}sim(A,B)∥A∥⋅∥B∥A⋅B​取值范围[−1,1][-1, 1][−1,1]越接近 1 越相似。为什么用余弦而不是欧氏距离因为余弦只关心方向不受文本长度影响——一段 20 字的摘要和一篇 2000 字的文档只要讲的是同一件事方向就应该一致。2.2 检索增强生成的完整链路一条工业级的 RAG 管线大致是这样文档加载 → 文本分块 → 批量嵌入 → 写入向量库 ↓ 用户提问 → 查询嵌入 → 相似度检索 → 重排→ 拼装 Prompt → LLM 生成其中重排Rerank是可选的第二阶段。向量检索本质上是一种粗筛它快但对细节语义的判别力有限。实践中常见的做法是先用向量召回 top-20再用交叉编码器如bge-reranker精排出 top-3 喂给模型。2.3 为什么选 Ollama ChromaDBOllama同时提供 LLM 和 Embedding 两种能力统一 HTTP 接口默认localhost:11434模型切换只需改一个字符串。ChromaDB轻量、嵌入式、支持持久化底层用 HNSW 做近似最近邻搜索。对个人和中小团队来说它比 Milvus / Qdrant 的运维成本低得多。三、实战搭一个离线知识助手3.1 环境准备# 安装 OllamaLinux / macOScurl-fsSLhttps://ollama.com/install.sh|sh# 拉取对话模型中文场景推荐 qwen2.5ollama pull qwen2.5:7b-instruct# 拉取嵌入模型英文/多语言选 nomic中文优先 bge-m3ollama pull nomic-embed-text# 验证服务是否就绪curl-shttp://localhost:11434/api/tags|head-c300Python 侧只需要三个依赖pipinstallollama chromadb pypdf提示如果你的数据全部是中文把nomic-embed-text换成bge-m3检索召回率通常有明显提升。嵌入模型和对话模型是独立的可以自由组合。3.2 构建索引管线下面这段代码完成了读 PDF → 分块 → 嵌入 → 入库的全过程。注意分块时我们做了重叠overlap避免语义在切点处被硬生生截断。# ingest.pyimportollamaimportchromadbfrompypdfimportPdfReaderfrompathlibimportPath EMBED_MODELnomic-embed-textDB_PATH./chroma_dbclientchromadb.PersistentClient(pathDB_PATH)collectionclient.get_or_create_collection(nameknowledge_base,metadata{hnsw:space:cosine},# 显式指定余弦距离)defembed_texts(texts:list[str])-list[list[float]]:批量嵌入。Ollama 会串行处理分批可避免超时。vectors[]foriinrange(0,len(texts),32):batchtexts[i:i32]respollama.embed(modelEMBED_MODEL,inputbatch)vectors.extend(resp[embeddings])returnvectorsdefchunk_text(text:str,size:int400,overlap:int80)-list[str]:按字符滑窗切分。中文 400 字约等于 300~500 token。chunks,start[],0whilestartlen(text):endstartsize chunktext[start:end].strip()iflen(chunk)30:# 丢弃过短的碎片chunks.append(chunk)startsize-overlapreturnchunksdefingest(pdf_path:str,source:strNone):readerPdfReader(pdf_path)sourcesourceorPath(pdf_path).name docs,metas,ids[],[],[]forpage_no,pageinenumerate(reader.pages):rawpage.extract_text()orforidx,chunkinenumerate(chunk_text(raw)):docs.append(chunk)metas.append({source:source,page:page_no1})ids.append(f{source}-{page_no}-{idx})ifnotdocs:print(f[skip]{pdf_path}未提取到文本可能是扫描件)returnvectorsembed_texts(docs)collection.add(documentsdocs,embeddingsvectors,metadatasmetas,idsids)print(f[ok]{source}: 写入{len(docs)}个片段)if__name____main__:forpinPath(./docs).glob(*.pdf):ingest(str(p))这里有个容易被忽略的细节元数据metadata必须存。source和page是后续做引用溯源的基础——没有它你的助手就只是另一个会编故事的模型。3.3 检索与生成问答环节是整个链路的高潮。核心思路是把检索到的片段拼进 system prompt并强制模型基于给定资料作答。# ask.pyimportollama CHAT_MODELqwen2.5:7b-instructTOP_K5SYSTEM_PROMPT你是一个严谨的内部技术助手。请严格遵守以下规则 1. 只依据【参考资料】回答问题不得引入外部知识 2. 如果资料中找不到答案直接回答根据现有资料无法回答禁止猜测 3. 回答时用 [1][2] 标注引用的资料编号。defretrieve(query:str,k:intTOP_K):q_vecollama.embed(modelnomic-embed-text,input[query])[embeddings][0]rescollection.query(query_embeddings[q_vec],n_resultsk)docsres[documents][0]metasres[metadatas][0]distsres[distances][0]returndocs,metas,distsdefask(query:str):docs,metas,distsretrieve(query)context\n\n.join(f[{i1}] (来源:{m[source]}第{m[page]}页)\n{d}fori,(d,m)inenumerate(zip(docs,metas)))user_msgf【参考资料】\n{context}\n\n【问题】{query}streamollama.![请添加图片描述](https://i-blog.csdnimg.cn/direct/728f844fd6f744d0a68e53bafe804cd6.gif)![请添加图片描述](https://i-blog.csdnimg.cn/direct/420dd3e9b6574ffabb74e844f0e936ec.png)![请添加图片描述](https://i-blog.csdnimg.cn/direct/6e3df8bf27044f139cd2e16e36731ed5.png)![请添加图片描述](https://i-blog.csdnimg.cn/direct/fbfc142b3a3d4f838f72815ac2ae1c4a.png)![请添加图片描述](https://i-blog.csdnimg.cn/direct/3046797c850444bca98db2b70c0dd4f3.jpeg)**更多硬核网安与AI工具包请扫码获取完整源码**chat(modelCHAT_MODEL,messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user_msg},],options{temperature:0.1},# 事实型任务调低温度streamTrue,)forpartinstream:print(part[message

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑