大模型一本正经胡说八道,已经成了 AI 落地时最让人头疼的问题。你问它一个产品版本号,它能给你编一个不存在的“最新版”;你让它总结一份内部技术方案,它能从训练语料里拼出另一个公司的业务逻辑。但最近一段时间,如果你也在做 AI 应用,可能会有一个明显体感:很多 AI 应用“看起来没以前那么能编了”。
这不是模型一夜之间变诚实了,而是落地链路变了。过去是拿一个裸模型直接对话,模型凭参数里的记忆硬答;现在开源社区的做法是:把事实从模型参数里拆出来,放到外部基础设施里,让模型在回答前先去查资料、做检索、走图谱,最后只负责组织语言。
这篇文章不列“十大 AI 神器”,而是拆解 5 个真正能改变幻觉问题的开源落地基建:统一工作区、14MB 端侧模型、本地跑大模型、检索引擎、决策图谱。每一层我都会讲清楚它到底解决哪类“瞎编”、有哪些开源选择、怎么配置、有什么坑。读完你可以得到一套“先查后答、答完可溯源”的技术方案,而不是又多背了一堆模型名字。
1. 核心判断:不要指望模型记住一切,要让它查得到、查得准、说得出依据
理解 AI 为什么会瞎编,先要接受一个事实:大模型的生成机制,本质上是在做“下一个词的概率预测”。它没有数据库、没有浏览器、没有“二次确认”通道。模型遇到一个问题时,不是先去查资料,而是根据训练阶段见过的数据分布,生成最“像样”的回答。
这意味着一个残酷的现实:如果某个事实不在训练语料里,或者训练语料本身是错的,模型并不会诚实地回答“我不知道”,它会按照最相似的模式补全一段内容。所以“幻觉”不是模型的一个 bug,而是机制本身的副产品。
那开源社区怎么解决?核心思路是四个字:事实外置。模型只负责表达和推理,不负责记忆事实。事实放在知识库、向量数据库、知识图谱和业务规则引擎里。模型回答任何严肃问题之前,都先经过检索和查证,让“回忆式生成”变成“资料式生成”。
基于这个思路,就有了下面这套配合关系:
| 解决哪类“瞎编” | 对应层级 | 典型开源工具/方案 |
|---|---|---|
| 回答没有来源、上下文混乱 | 统一工作区 | Cherry Studio、AnythingLLM、Open WebUI |
| 不知道问题该走哪条处理链路 | 端侧模型 | ONNX Runtime、Transformers.js、量化后的 Embedding/分类模型 |
| 知识私有化、API 不透明、调试难 | 本地推理 | Ollama、llama.cpp、vLLM |
| 答案没有外部资料支撑 | 检索引擎 | Qdrant、Milvus、Meilisearch、Elasticsearch |
| 多跳关系推理容易跳步 | 决策图谱 | Neo4j、NebulaGraph、GraphRAG、LightRAG |
在这条链路里,“不瞎编”不是某一层单独完成的,而是五层配合后的结果。后面就从统一工作区开始,一层一层拆开看。
2. 第 1 个基建:统一工作区,把模型、知识库和对话上下文收拢到一处
2.1 你需要统一工作区吗
先看一个很典型的开发场景:你做 AI 应用选型,本地装了好几个大模型客户端,又接了几个在线 API。写代码时想参考公司内部规范文档,于是打开另一个知识库工具;想对比不同模型对同一问题的回答,又要复制粘贴到第三个工具里。问答是分散的,上下文是割裂的,最要命的是一段回答到底来自哪份资料,经常找不到出处。
这种碎片化会直接造成两种“幻觉”:
一是上下文幻觉。模型只看到你当前输入的问题,看不到之前的对话背景,于是顺着自己的猜测往下答。
二是来源幻觉。回答看起来很有道理,但你无法验证它是否真的来自某份文档。结果你把一段模型编的内容当成了事实,带进了代码、方案或报告里。
统一工作区解决的就是这个问题:把所有模型接入、知识库、对话历史、系统提示词、引用来源放到同一个界面和同一套会话上下文里。
2.2 开源工作区应该怎么选
我选型时会看三个关键能力:
- 是否能通过 OpenAI 兼容接口接入本地推理服务,比如 Ollama、vLLM。
- 是否内置知识库/RAG 能力,而不是只能聊天。
- 回答时是否展示引用来源,也就是能不能做到“答案可溯源”。
本地桌面端可以关注 Cherry Studio、AnythingLLM 这类开源项目;团队服务端可以关注 Open WebUI、Dify 这类可以部署到内网的工具。具体版本以项目官方发布为准,但配置逻辑是相通的。
2.3 最小配置示例:让本地模型进入工作区
以“桌面工作区 + 本地 Ollama”为例,操作路径通常是:
- 先安装并启动 Ollama,本地拉取一个模型,例如
qwen2.5:7b。 - 打开工作区软件的“模型服务”或“模型供应商”设置,填写 OpenAI 兼容地址:
http://127.0.0.1:11434/v1。 - 填一个 API Key 占位符,比如
ollama,多数兼容服务不会真正校验。 - 添加该模型,对话时选用这个本地模型作为生成模型。
- 创建或上传一份业务文档到内置知识库,然后在对话中开启“知识库引用”或“联网知识”选项。
完成后,同样的界面里既能看到用户问题,也能看到系统最终拼接了哪些文档片段。当 AI 给出的答案下方带了一串引用来源时,“这段回答到底从哪来”这个问题就变成了可验证的。
关于统一工作区,这里有句很关键的话:它不直接提升模型智商,但它让“回答前必须提供依据”成为工作流里的默认动作。这是反幻觉治理的第一步,也是成本最低的一步。
3. 第 2 个基建:14MB 端侧模型,它的角色不是“小号 ChatGPT”
3.1 先区分清楚:14MB 模型不负责“什么都知道”
看到“14MB 模型”时,很多人第一反应是用一个很小的大模型替代云端大模型。这个判断需要纠正。
如果在端侧生成开放领域的高质量长文本,14MB 几乎不可能完成。真正能做到“十几 MB”级别的开源模型,通常是两类:一类是量化压缩后的文本嵌入模型,常见体积在 10 到 30MB 之间;另一类是面向单一任务的轻量意图分类器、安全过滤器或文本路由模型。标题里的“14MB”,更准确的理解是一个量级,而不是某个特定模型恰好等于 14MB。
那么它和“AI 不瞎编”有什么关系?关系很大:大模型瞎编,很多时候是因为“问题被送错了处理管道”。
我举一个具体场景。用户问“请帮我查一下这个项目的兼容性说明”,如果直接把问题发给一个大模型,它可能凭借训练记忆生成一个看似完整、实际不存在的兼容性列表。如果先经过端侧的小模型做意图分类,判断出“这是一个需要检索业务文档的事实型问题”,然后程序把问题转去检索引擎,而不是直接让大模型凭记忆回答,那么幻觉从源头上就被拦截了一部分。
这就像公司前台:端侧小模型是分诊护士,大模型是专家。分诊护士不需要懂临床医学,她只需要判断“你这个症状该挂哪个科室”。科室判断对了,专家误诊的概率才会低。
3.2 端侧模型在反幻觉体系里的三个具体任务
第一个任务是意图路由。判断用户问题是“闲聊/通用知识”还是“需要查资料”,或者是“需要执行某个确定规则”。这是在决定调用哪条后续管道。
第二个任务是文本嵌入。把问题、文档片段转成向量,供检索引擎做召回。端侧嵌入模型的意义在于,文档可以完全离线处理,避免隐私数据传出本机。
第三个任务是重排序。检索引擎先召回候选片段,再在端侧用轻量 rerank 模型对候选片段重新排序,把真正相关的片段排在前面。“检索准”,模型才会少编。
3.3 端侧模型的落地形态和性能提示
端侧模型常见的落点包括浏览器、桌面端、IoT 设备。比如在浏览器里使用 ONNX Runtime Web 加载一个量化后的意图分类模型,用户输入问题后,先在浏览器本地完成一次分类,再把判断结果传给后台工作流。
模型量化是控制体积的关键手段。大致的估算关系是:模型体积 ≈ 参数量 × 每个参数的字节数。FP32 是 4 字节,FP16 是 2 字节,INT8 量化后是 1 字节。相比全精度存储,INT8 体积能缩小到原来的四分之一左右。这也是为什么一段时间以来,“十几 MB 端侧模型”越来越多见——并不是某个厂家忽然实现了魔法,而是量化、蒸馏和任务单一化这些常规工程手段综合作用的结果。
端侧模型选型建议遵循两条原则:
- 任务越单一越好。端侧小模型的成功,靠的是“把任务边界缩小”。你想让它同时做意图识别、实体抽取、情感分析、开放问答,它大概率会全部做砸。
- 先定推理框架,再定模型格式。浏览器端优先 ONNX 或 WebAssembly,桌面前端也可以用 llama.cpp 的 GGUF 格式。框架决定了模型能不能在目标硬件上跑起来,这一步要比纠结单个模型的精度更重要。
如果你把它当成“更弱的 ChatGPT”去问“帮我写一篇行业分析报告”,它当然会漏洞百出;但如果你把它放在流程第一道闸门的位置,让它把“需要查证的问题”和“不需要查证的问题”分开,那它就是在用最小成本减少模型的自作主张。
4. 第 3 个基建:本地跑大模型,从“黑盒 API”到“可审计底座”
4.1 本地推理和反幻觉到底有多强的关系
先给一个反常识的判断:本地跑大模型并不会天然减少幻觉。你把一个 7B 模型下载到本地,没有检索、没有知识库、没有工具调用,它一样会对着不知道的问题编答案,甚至因为模型能力弱于商业云端大模型,编得还更离谱。
那为什么“本地跑大模型”仍然算反幻觉基建?因为它把幻觉治理从“不可审计”变成了“可审计”。
使用外部黑盒 API 时,你看不到 Prompt 经过哪些处理,很难控制模型内部的采样参数,也不知道它是否真的只基于你提供的资料回答。而在本地部署场景下,你可以完全控制模型文件、系统提示词、上下文窗口、解码温度,也能自由接入检索到的资料片段进行压测。出问题时,可以逐步排查是模型问题、资料问题还是检索问题。
另外,数据敏感环境下的合规约束,往往逼着团队必须本地化部署。如果你连把内部文档发给外部 API 都不敢,那再强的云端模型也和你没有关系。本地方案至少给了你一个可掌控的底座。
4.2 Ollama 的基础用法
目前社区最常见的本地推理工具是 Ollama。它在 macOS、Linux、Windows 上都能用,对新手非常友好,本质是把 llama.cpp 等推理后端封装成了类似 Docker 的命令行体验。
官方提供了一键安装脚本,但我的建议是:先访问官网确认当前系统的安装方式和最新命令,再执行安装。如果只是体验流程,在测试机上可以这样启动:
curl -fsSL https://ollama.com/install.sh | sh安装完成后,从模型仓库拉取一个通用中英文模型:
ollama pull qwen2.5:7b拉取完成后直接进入交互式对话:
ollama run qwen2.5:7b这时你会进入一个类似终端的聊天界面。如果只是想验证模型能不能跑,直接输入“你好”就行。
4.3 通过 OpenAI 兼容接口接入业务系统
Ollama 启动后会监听本地端口,默认是127.0.0.1:11434。它提供了 OpenAI 兼容接口,因此很多开源工具可以直接把模型供应商地址填成http://127.0.0.1:11434/v1。
用命令行验证接口是否可用:
curl http://127.0.0.1:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:7b", "messages": [ {"role": "user", "content": "请介绍一下为什么大模型会产生幻觉?"} ], "stream": false }'如果返回 JSON 中包含choices字段,说明接口已经通了。有一点要提醒:端口默认只绑定本机回环地址,所以本地能访问,不代表局域网内其他机器能访问。如果想给团队内网提供服务,需要显式配置OLLAMA_HOST=0.0.0.0,但配置后一定要加反向代理鉴权,不要让裸的模型服务直接暴露。
4.4 本地部署时的常见硬件判断
不必一上来就追求 70B 级别的大模型。实际项目中,量化后的 7B 到 14B 模型在 RAG 场景里已经能承担相当一部分工作。硬件上大致可以参考:
- 纯 CPU + 16GB 内存:跑 1.5B 到 3B 的小模型体验一下没问题,7B 会非常吃力。
- 消费级 16GB 显存显卡:跑 7B 到 14B 模型的 4bit 量化版本比较现实。
- 32GB 显存及以上:通常可以跑 30B 级别或更大的模型,但具体需要看模型结构和上下文长度。
本地跑大模型只是一个底座。真正让模型少瞎编的,是接下来这个环节:给它外挂一个可以检索的资料库。
5. 第 4 个基建:开源检索引擎,用“外挂记忆”逼模型先查后答
5.1 为什么 RAG 能治“不懂装懂”
RAG 的流程并不复杂:先把文档切成多个片段,转成向量并存入检索引擎;用户提问时,把问题转成向量,在检索引擎里召回最相似的若干片段;再把片段作为上下文拼接到 Prompt 里,让模型只能基于这些片段作答。
这里的关键变化是:模型不再从“记忆分布”里找话,而是从“刚查到的资料片段”里组织答案。只要检索到的片段是业务事实,模型照抄片段里的关键信息,幻觉概率就会大幅下降。
但 RAG 也有自己的翻车方式。最常见的有两种:
第一种是“检索不到”。你想问的事实明明在文档里,但由于分块方式不合理、Embedding 模型不匹配或关键词差异,没有召回相关片段。模型没有资料可用,就只能硬编。第二种是“检索错了”。召回的片段看起来相似,实际上并不是用户真正需要的那个,模型可能把无关资料当成依据,一本正经地答错。
所以,RAG 不是简单把一个向量数据库接进去就结束。你需要调分块策略、选 Embedding 模型、加混合检索,甚至加一层 Rerank,才有可能做出稳定的效果。
5.2 开源检索引擎选型
| 项目 | 类型 | 适合场景 | 上手难度 |
|---|---|---|---|
| Qdrant | 向量数据库 | 中小规模 RAG、Docker 一键启动 | 低 |
| Milvus | 向量数据库 | 大规模向量、生产集群、复杂过滤 | 中 |
| Meilisearch | 全文检索引擎 | 搜索体验要求高、关键词匹配为主 | 低 |
| Elasticsearch | 全文/向量混合检索 | 已有 ES 技术栈、需要复杂查询 | 高 |
| sqlite-vec | 嵌入式向量检索 | 单机、轻量、原型验证 | 低 |
如果从零开始搭 RAG,我更推荐先选轻量方案。Qdrant 可以只用一行 Docker 命令启动,开发调试成本非常低。Meilisearch 则擅长全文检索,适合搜索场景。等到数据量真的到了千万级、查询复杂度明显提升时,再换 Milvus 这类重方案也不迟。
5.3 从零搭一个“本地模型 + 向量检索”最小链路
下面用 Ollama 生成向量,用 Qdrant 存储和检索,跑通一个最小 RAG 闭环。
先分别启动 Ollama 和 Qdrant。Qdrant 可以通过 Docker 启动:
docker run -d --name qdrant \ -p 6333:6333 \ -p 6334:6334 \ qdrant/qdrant然后为本地 Ollama 拉一个 Embedding 模型。Ollama 会默认从模型仓库拉取:
ollama pull nomic-embed-text下面的 Python 示例会把两个文档片段写入 Qdrant,再对用户问题进行检索:
import requests from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct # 前置条件: # 1. 本地已启动 Ollama,并已拉取 nomic-embed-text # 2. 本地已启动 Qdrant,端口 6333 def embed(text: str) -> list[float]: """调用本地 Ollama 的 /api/embed 接口,返回向量。""" resp = requests.post( "http://127.0.0.1:11434/api/embed", json={"model": "nomic-embed-text", "input": text}, ) return resp.json()["embeddings"][0] # 第 1 步:准备文档片段并生成向量 chunks = [ "统一工作区可以把 Ollama、知识库和对话上下文放到同一个界面。", "本地部署大模型时,需要根据显存选择量化后的模型,推荐 q4_K_M。", ] vectors = [embed(chunk) for chunk in chunks] # 第 2 步:写入 Qdrant client = QdrantClient("localhost", port=6333) # 如果集合不存在,先创建。维度取真实向量的长度,不要硬编码。 client.create_collection( collection_name="docs", vectors_config=VectorParams( size=len(vectors[0]), distance=Distance.COSINE, ), ) client.upsert( collection_name="docs", points=[ PointStruct(id=i, vector=vectors[i], payload={"text": chunks[i]}) for i in range(len(chunks)) ], ) # 第 3 步:检索用户问题 query_text = "我电脑显存不大,本地应该怎么选模型?" query_vector = embed(query_text) hits = client.search( collection_name="docs", query_vector=query_vector, limit=3, ) for hit in hits: print(f"score={hit.score:.3f} text={hit.payload['text']}")这段代码有几点需要留意。第一,不要硬编码向量维度,直接用len(vectors[0]),因为不同 Embedding 模型输出的维度不一样。第二,生产环境不要把超大文本一次性喂给 Embedding 模型,需要先做好切片。第三,如果检索评分普遍偏低,先检查 Embedding 模型和业务语言的匹配度,再考虑调整分片长度。
5.4 检索后的 Prompt 模板
检索到候选片段后,怎么把它们拼接给模型也很关键。推荐使用这样一套约束:
你是一个只能依据资料作答的助手。 规则: 1. 如果资料中包含答案,请引用编号后的资料原文作答。 2. 如果资料中没有答案,请明确回答“资料中没有找到相关内容”,不要补充自己的知识。 资料如下: [1] 文档片段 1 的内容 [2] 文档片段 2 的内容这套 Prompt 的作用是给模型一个“拒绝通道