AI创想

标题: 保姆级教程!向量数据库如何让大模型拥有“记忆“,告别“一本正经地胡说八道“?附完整代码实现! [打印本页]

作者: AI小编    时间: 4 天前
标题: 保姆级教程!向量数据库如何让大模型拥有“记忆“,告别“一本正经地胡说八道“?附完整代码实现!
作者:CSDN博客
这篇文章我们会讲什么?

主要有以下这些问题:










随着对知识库概念的进一步深入理解,我们也更加清晰地认识到:在整个AI系统中,Embedding向量数据库LLM(大语言模型)并非孤立存在,而是形成了一个完整的工作链路。正是这个链条,让AI能够更加智能、精确地理解问题、存储信息,并做出最为贴切的回应。而这一切的基础,正是通过向量数据库来打下的坚实基础。
接下来,我们将深入探讨向量数据库的核心概念,并结合实际案例演示,一起来更好地理解向量数据库在大模型中的实际应用。
什么是向量?

在了解向量数据库之前,我们还是要明确一下到底什么是向量
**向量(Vector)**在数学、物理、计算机科学等领域中有着广泛的应用。它是一个表示具有大小和方向的量,在机器学习和数据科学中,向量通常表示为一组数字(通常是浮点数),这些数字用于描述一个对象的特征或属性
比如在 Milvus 这样的向量数据库中,向量常常用来表示数据(例如文本、图像、音频等)的特征,以便进行高效的相似度检索
向量的基本概念

1、数学上的向量:
在数学中,向量是一个有大小和方向的量。通常在二维或三维空间中,向量可以表示为一个有序的数值序列:
(, 下载次数: 2)


向量的长度(或称为模)表示其大小,而方向表示其朝向。
2、向量在计算机科学中的应用:
1)特征表示: 在机器学习、深度学习、自然语言处理(NLP)等领域,向量通常用来表示数据的特征。例如:
2)高维空间: 向量不仅限于二维或三维空间,数据科学中的向量往往是高维的。每个维度表示某种特征。高维向量可以描述图像的颜色、形状、纹理等,或者文本的语法、语义等。
向量的表示

向量通常用一个有序的数字列表来表示。每个数字代表了某一维度的数值。在机器学习和数据科学中,我们经常处理高维向量(即向量的维度很高),这些高维向量可以表示非常复杂的数据。
例如:
在自然语言处理中,单词、句子或段落常常被表示为向量,通过词嵌入模型(如 Word2Vec、BERT)将其转化为向量;在计算机视觉中,图像也可以通过卷积神经网络(CNN)提取出一个向量来表示图像的特征。
向量在机器学习中的应用

**图像向量:**图像通常被转换成一个高维的向量。在计算机视觉中,深度学习模型(如 CNN)将图像通过网络进行处理,最后得到一个向量表示,向量的每个元素代表图像中的某种特征。这个向量可以用于图像的检索、分类、生成等任务。
(, 下载次数: 7)


**文本向量:**文本数据(如单词、句子、文章)通过自然语言处理模型(如 Word2Vec、BERT 等)转换为向量表示。这些向量捕捉了文本的语义信息,使得相似语义的文本具有相似的向量表示。比如,“猫”与“狗”的词向量应该相近,“猫”与“计算机”的词向量应该相对远离。
(, 下载次数: 0)


**音频向量:**音频文件也可以通过特征提取算法(如 MFCC)转换为向量。这个向量可以表示音频信号中的频率、音调等特征,用于语音识别、音频检索等任务。
向量的常见操作

向量有很多操作,以下是一些常见的操作:
1)向量加法:
向量加法是将两个向量的对应元素相加。例如:
向量 v1 = (1, 2) 和 v2 = (3, 4)
则 v1 + v2 = (1+3, 2+4) = (4, 6)。
2)向量点积(内积):
向量的点积是两个向量的对应元素相乘并求和。点积结果是一个标量(数字),常用于计算两个向量的相似度:
向量 v1 = (1, 2) 和 v2 = (3, 4)
则 v1 • v2 = 1*3 + 2*4 = 11。
3)向量的夹角:
向量之间的夹角可以通过点积来计算。若两个向量的夹角小,则它们更相似,通常在检索系统中用于衡量相似度。
4)向量的长度(模):
向量的长度表示该向量的大小,可以通过平方和开根号来计算:
向量 v = (x, y) 的长度为 √(x^2 + y^2)。
5)余弦相似度:
在高维空间中,余弦相似度是衡量两个向量相似度的常用指标。它通过计算两个向量的夹角余弦值来判断它们的相似性,值的范围从 -1 到 1,1 表示完全相同,-1 表示完全相反,0 表示没有相似度。公式:
cosine_similarity(v1, v2) = (v1 • v2) / (|v1| * |v2|)
其中 |v1| 和 |v2| 是向量的模。
回顾向量数据库的概念

明白了什么是向量,我们下一步就可以继续了解向量数据库的概念。
什么是向量数据库

向量数据库是一种专门为高效存储、检索和管理高维向量数据(即向量嵌入)而设计的数据库系统。其核心价值并非简单的向量存储,而在于能够对海量向量执行高速近邻搜索(如相似度计算)等复杂运算,从而实现对非结构化数据(文本、图像、音频等)的语义级查询。
在人工智能技术栈中,向量数据库已成为关键组件,它为大型语言模型提供了持久的“记忆”与高效的检索能力,是实现检索增强生成、推荐系统、语义搜索等AI应用的基础设施。尽管常被称为“AI数据库”,但数据库技术正趋于融合,许多现代数据库已开始支持包括向量在内的多模型数据。
向量数据库和传统数据库有何不同?

想象一下,你管理一个庞大的信息库:
传统数据库:精准的图书管理员

传统数据库(如 MySQL)就像一个严谨的图书管理员,管理着一个结构清晰的档案室。
1)数据格式: 信息被整齐地记录在表格中,就像Excel一样。每一行是一条记录(如一个用户),每一列是一个属性(如姓名、年龄、ID)。数据是结构化的,格式固定。
2)工作方式: 当你提问时,你必须使用它能理解的精确语言(如SQL语句)。例如:“查找所有年龄大于30岁的用户”。
3)查询逻辑: 它根据你的指令,去表格里精确匹配符合条件的行。年龄“31”会被找到,但“三十多岁”或“中年”这种模糊概念它无法理解。它的核心是 “是与否”的精确过滤
向量数据库:理解语义的智能助手

向量数据库则像一个理解含义的智能助手,管理着一个“思想空间”。
1)数据格式: 它不直接存储原始文本、图片或声音,而是先通过AI模型将这些内容转换成高维向量(即“语义指纹”)。这个向量是一长串数字(如768个浮点数),代表了该内容的核心含义和特征。一篇关于“苹果手机”的报道和一张“iPhone照片”的向量,在数学空间里会很接近。
2)工作方式: 当你提问(例如:“帮我找一些关于气候变化影响的科学文章”),它会先将你的问题也转换成向量。
3)查询逻辑: 然后,它在自己的“思想空间”里进行相似度搜索,找出那些向量最接近你问题向量的内容。它返回的不是精确匹配的关键词,而是含义最相关的结果。它的核心是 “像不像”的相似度计算
通俗的来讲,我们可以把向量数据库想象成一个专门处理“思想指纹”的智能图书馆
普通数据库(比如Excel)存放的是规整的数字和文字,就像图书馆按书名或编号找书。而向量数据库存放的是一种**“思想的指纹”——它用一串数字(向量)来代表一段文字、一张图片或一首歌的核心含义**。当你提问时,它不会去机械地匹配关键词,而是去寻找和你的问题在“思想”上最相近的答案
它最厉害的本事是“快”: 即使这个图书馆里有数十亿本“书”(向量),它也能在眨眼间找到和你想法最相似的几本。这正是当前AI应用(如智能聊天机器人、个性化推荐)急需的能力——让AI不仅能即时生成回答,还能瞬间从海量知识中精准找到最相关的内容来支撑它的回答,变得更渊博、更准确。
因此,向量数据库本质上就是 “为AI大脑配备的超高速记忆检索系统”
核心差异总结

对比维度传统数据库向量数据库
数据存储对象结构化数据 :数字、字符串、日期等,存放在行和列中。非结构化数据的向量嵌入 :文本、图像、音频、视频的“语义指纹”(高维数字数组)。
数据关系体现显式关系 :通过外键等明确建立表与表之间的联系。隐式关系 :通过向量在空间中的距离体现语义相似性。距离越近,含义越相似。
查询范式精确查询 :使用SQL进行条件匹配(WHERE age > 30)。答案是确定的、离散的。相似性查询 :进行“近似最近邻搜索”。找到与查询目标最相似的Top K个结果。答案是按相关性排序的、连续的。
核心索引技术B-树、哈希索引等,用于加速精确值查找。HNSW、IVF-PQ等近似最近邻索引,用于在高维空间中快速找到相似向量。
擅长解决的问题事务处理、精准查询 :电商订单、银行交易、用户信息管理等,需要绝对准确。语义搜索、内容推荐、AI记忆 :根据描述找图片、聊天机器人上下文检索、推荐相似产品等,需要理解意图和内容。
总而言之,传统数据库是“数据的仓库”,擅长处理“是什么”的问题;向量数据库是“意义的图谱”,擅长处理“像什么”的问题。 正是这种根本性的不同,使得向量数据库成为了处理现代海量非结构化数据、构建智能应用(如RAG)不可或缺的基础设施。
向量数据库在AI Agent中扮演了何种角色?

在AI大模型的应用架构中,向量数据库正从"辅助工具"演变为"核心记忆中枢",尤其在当前主流的RAG(检索增强生成) 技术栈中,它扮演着连接静态知识与动态对话的关键桥梁角色。
RAG时代的核心基础设施

向量数据库在AI大模型中承担着三重核心角色:
(, 下载次数: 1)


1 长期记忆库:克服大模型的"知识截断"

2 事实校验器:对抗"模型幻觉"

3 上下文增强器:提升回答质量

RAG框架中的向量数据库工作流程

(, 下载次数: 2)


简单来说,这个过程是这样的:当我们提问时,系统不会让AI直接凭空回答,而是会先将问题变成一段“数学指纹”(向量),并立即用这个指纹去专属资料库(向量数据库)里查找几份(TOP-K)最相关的原文资料;最后,AI会像一位严谨的发言人,严格基于刚刚查到的这几份资料来组织答案,从而确保回答的准确性和可靠性,有效避免“信口开河”。
实际RAG应用场景

RAG可以说在AI Agent生态中无处不在,主要有以下场景:
应用场景传统LLM局限RAG + 向量数据库解决方案
智能客服无法访问最新产品文档检索最新FAQ、产品手册后生成回答
法律咨询可能编造法律条文精确检索相关法律法规后解释
学术研究知识存在时间滞后检索最新论文摘要后综述
医疗辅助通用建议缺乏针对性基于患者历史病历生成建议
企业知识库不了解内部流程检索公司内部文档后回答问题
从向量存储到智能检索

向量数据库在AI大模型生态中的角色正在快速演进:
(, 下载次数: 6)


基础层:简单的向量存储与最近邻搜索
增强层:支持混合检索(向量+关键词+过滤)、重新排序
智能层:集成查询理解、多跳检索、自适应检索策略 4
生态层:与大模型框架(LangChain、LlamaIndex)深度集成
向量数据库已不仅仅是存储高维向量的"数据仓库",而是AI大模型的"外接大脑皮层"——它扩展了模型的记忆容量,增强了事实核查能力,并使其具备了领域专业知识。在RAG成为大模型应用标准架构的今天,向量数据库从可选组件转变为必选基础设施,成为平衡模型"创造力"与"准确性"的关键支点,支撑着更可信、更专业、更个性化的AI应用落地。
如何选择向量数据库

选择向量数据库,本质是平衡 控制力、成本、场景和复杂度
先回答三个根本问题

1)数据是否必须留在自己的服务器上?
→ 只能选 开源方案(如 Weaviate、Qdrant、Milvus)。
→ 可以考虑 托管服务(如 Pinecone),以运维成本换取开发速度。
2)应用场景是什么?
边缘/IoT设备 → 唯一选择 ObjectBox(专为资源受限环境优化)。
快速原型验证 → 首选 ChromaPinecone(上手最快)。
大规模生产系统 → 进入主流开源方案角逐。
3)数据规模与性能要求如何?
极高吞吐、超大规模(>1亿向量)MilvusVespa(分布式架构最成熟)。
高并发、低延迟Qdrant(Rust编写,性能出色)。
需要强混合搜索(向量+关键词)WeaviateVespa(原生设计支持好)。
主流方案核心定位

主流的向量数据库产品进行对比:
数据库核心优势最适合的场景
Milvus功能最全、生态最成熟 的云原生开源方案,适合超大规模。企业级AI平台、需要处理海量向量的复杂生产系统。
Weaviate“数据库”思维 ,强在对象-向量联合存储、混合搜索和易用性。需要结合结构化过滤的AI应用(如推荐系统、知识库)。
Qdrant“搜索引擎”思维 ,Rust带来极致性能和资源效率,API简洁。对延迟和资源消耗敏感的高性能搜索、实时推荐。
Pinecone完全托管、零运维 ,开发者体验最佳,开箱即用。初创团队、需要快速验证业务假设、无专职运维。
Chroma轻量、简单 ,与AI开发栈(如LangChain)集成极深。AI应用原型开发、学习和轻量级项目。
四步快速决策法

1)筛除明显不匹配项
要边缘计算 → ObjectBox。
要完全免运维 → Pinecone。
要快速做Demo → Chroma。
2)在主流开源中聚焦
追求功能全面和社区规模 → 在 MilvusWeaviate 中选。
追求极致性能和简洁性 → 重点评估 Qdrant
3)用真实数据做概念验证
从2个最终候选开始,用自身业务数据的子集典型查询请求进行测试。
关键指标:查询延迟(P99)、吞吐量、插入速度、内存/CPU占用
不要只看公开报告,你的数据模式才是关键。
4)评估长期成本与锁定风险
开源方案:前期投入工程运维成本,但拥有控制权和退出自由。
托管方案:前期投入资金成本,换取速度,但需承担长期订阅费和供应商锁定。
(, 下载次数: 3)


一句话总结:**初创验证用Pinecone/Chroma;重混合搜索选Weaviate;求极致性能看Qdrant;建大规模企业平台评Milvus;做边缘设备只有ObjectBox。**最终务必用真实数据测试。
向量数据库对比表格

名称是否开源许可证开发语言简介索引类型是否支持ANN
ObjectBoxApache-2.0C++ (支持多语言API)适用于移动、IoT、嵌入式设备的边缘AI向量数据库HNSW
Marqo AIApache-2.0Python基于张量的云原生商业开源搜索分析引擎HNSW未知
WeaviateBSDAssembly, C++, Go存储对象和向量的商业开源云原生向量数据库支持CRUD的自定义HNSW PQ算法是(支持CRUD的ANN算法)
ChromaApache-2.0Python & TypeScript商业开源向量数据库未明确
QdrantApache-2.0Rust商业开源向量相似性搜索引擎和向量数据库HNSW (SQ & PQ)
MilvusApache-2.0Go & Python云原生商业开源向量数据库ANNOY; HNSW; IVF_PQ; IVF_SQ等
VespaApache-2.0Java & C++Yahoo!的商业开源向量数据库,支持向量搜索、词法搜索和结构化数据搜索自定义HNSW(多向量混合HNSW-IF)
ValdApache-2.0Go云原生开源分布式近似最近邻密集向量搜索引擎NGT是(NGT)
Pinecone专有未明确完全托管的向量数据库,专注于语义搜索能力专有是(专有),加KNN(Faiss)
使用向量数据库实现简单的RAG

在上文我们不断提到向量数据库在RAG中的重要性,那么下面我们就动手实现一个简单的RAG,来直观的感受向量数据库带来的作用。
为什么要有RAG

为什么需要RAG?我们可以从“大模型幻觉”切入
想象一下,你问一位天赋异禀但只接受过2023年初以前通用知识训练的“学者”一个问题。这位学者能结合已知模式,流畅、自信地生成一段回答。但问题在于:
1)他可能不知道你问题中涉及的、2023年后发生的具体新事件。
2)他可能不掌握你公司内部的私有数据或专业知识。
3)他有时会为了保持回答的“流畅合理”,而基于不完整的知识,编造听起来可信但实际错误的细节。
这就是当前大语言模型面临的核心困境:幻觉问题
幻觉指模型生成看似合理但事实上不正确或无法验证的信息
幻觉产生的根源

(, 下载次数: 1)


静态知识边界:大模型的训练数据是静态的、有截止日期的。它无法实时获知世界的新变化。
参数化知识的局限性:模型将海量知识压缩存储在神经网络的权重参数中。这个过程就像一场“有损压缩”,细节、精准数据(如具体数字、小众事实)容易丢失或模糊。
生成模式的固有倾向:模型的核心任务是“根据上文预测下一个最可能的词”。在缺乏明确依据时,为了延续合理的文本模式,它可能会“创作”内容,而非“引用”事实。
传统解决方案的局限

更大规模训练/微调:成本极高,无法解决知识实时性问题,且每次更新知识都需要重新训练或微调,不灵活。
提示工程:在提示词中要求模型“不要编造”,效果有限。这治标不治本,因为模型缺乏事实依据时,依然会陷入幻觉。
由于上述的原因,就催生了RAG的出现。
RAG:解决幻觉的“开卷考试”方案

RAG的核心思想是不让模型仅依赖其内部压缩的、可能过时或不完整的记忆来作答,而是先为模型提供相关的、最新的、准确的事实依据,再让它基于这些依据来组织答案。
这就像让一位学者从**“闭卷作文”** 转变为**“开卷考试”**:
RAG的工作流程(如何对抗幻觉)

(, 下载次数: 1)


1)检索:当用户提问时,系统不会直接将问题丢给大模型。而是先将问题转换成向量,在向量数据库中搜索与之最相关的知识片段。这个知识库可以是你的产品文档、最新的市场报告、公司制度、法律条文等任何可靠、最新的数据源。
2)增强:将检索到的原文片段(作为事实依据)和用户的原始问题,一起组合成一个新的、增强版的提示词,发送给大模型。
3)生成:大模型基于收到的 “问题+权威依据” 来生成最终答案。它被明确要求:“请仅根据所提供的上下文信息来回答问题。”
RAG如何直击幻觉要害?

解决知识过时问题:RAG的知识库可以随时更新。只需检索最新的文档即可,无需重新训练模型。
引入精准事实来源:模型不再依赖模糊的参数化记忆,而是基于检索到的、包含具体数字和细节的原文进行生成,极大减少了“捏造细节”的可能。
提供可验证性:系统可以保留检索到的原文出处。当答案存疑时,用户可以“溯源”,查看生成答案所依据的原始材料,建立了可信度
降低专业领域错误:对于法律、医疗、金融等专业领域,通用大模型极易出错。RAG通过引入该领域的专业资料(如病历、法典、财报),将生成答案约束在专业框架内。
(, 下载次数: 3)


RAG通过在生成前引入一个可控制、可更新、可验证的“事实锚点”,巧妙地拆解了这个问题。它将大模型从“全知但不可靠的叙述者”,转变为“精准且富有文采的解说员”,使其答案既流畅,又可靠。这是当前在成本、效果和可行性上,平衡大模型能力与缺陷的最佳工程实践之一,也是其能在企业级场景落地的关键技术。
使用Milvus实现RAG

我们本着先了解再使用的原则先从概念入手,先带大家了解一下Milvus这个向量数据库产品,然后在使用Python代码结合Milvus实现RAG。
先简单介绍一下Milvus

Milvus 是一款领先的云原生、开源向量数据库,专门为大规模AI 应用和向量相似性搜索而设计。它就像是 AI 时代的“数据搜索引擎”,能够高效存储和检索由文本、图像、音频、视频等非结构化数据转换而成的高维向量,帮助 AI 模型快速找到最相关的信息。
关键特性

特性说明
高性能检索支持十亿级向量的毫秒级查询,满足实时 AI 应用需求
高可扩展性云原生架构,支持水平扩展,轻松应对数据增长
丰富的索引提供 10+ 种向量索引类型(HNSW、IVF、SCANN等),适配不同场景
多语言支持提供 Python、Java、Go、Node.js 等主流语言 SDK
支持动态数据完整 CRUD 操作,支持实时插入、更新、删除
混合查询支持向量 + 标量(结构化数据)的联合过滤查询
基本架构

来自官网的核心架构图:
(, 下载次数: 6)


图片来自https://milvus.io/docs/zh/architecture_overview.md
简化版:
(, 下载次数: 3)


应用程序通过 Python 或 Java 等 SDK 发起一个查询时,请求首先经过访问层,在这里进行负载均衡、身份验证和初步的路由分配。随后,请求被递交给核心的协调层,这一层如同系统的大脑,其中的查询协调器负责分解和规划任务,数据管理元数据管理组件则负责定位数据的位置与状态。
规划好的任务被下发给底层的工作节点:查询节点 负责核心的计算工作,执行高效的向量相似性搜索和标量条件过滤;而数据节点 则扮演存储库的角色,不仅安全地保存着海量的向量和标量数据,还管理着构建好的索引。最终,各节点的结果被协调层汇总,并沿原路径返回给应用程序,从而完成一次高效、可靠的向量检索。
Milvus的常见术语

Milvus的核心架构围绕**集合(Collection)**组织数据。
其中,每个集合包含多个实体(Entity),实体由**字段(Field)构成,其中最关键的是向量(Vector)**字段用于存储嵌入表示。
通过**分区(Partition)**实现数据逻辑划分,**段(Segment)作为物理存储单元,系统采用相似性搜索(Similarity Search)基于度量方式(Metric)**计算向量距离。
**向量索引(Vector Index)**加速检索过程,支持多种索引类型。
在集群部署中,**数据节点(Data Node)**负责存储,**查询节点(Query Node)**处理搜索,**索引节点(Index Node)**构建索引,**代理(Proxy)**协调客户端请求。
整个系统通过**嵌入(Embedding)技术将原始数据转化为向量,并支持标量过滤(Scalar Filter)**进行混合查询,实现高效的向量数据管理。
(, 下载次数: 1)


术语解释:
术语说明类比
Collection (集合)数据组织的最高层级,包含一组实体数据库中的表
Entity (实体)集合中的一条记录,由多个字段组成表中的一行
Field (字段)实体的属性,可以是向量或标量表中的列
Vector (向量)高维数值数组,表示数据的特征128/256/768维数组
Scalar (标量)传统数据类型(字符串、整数等)普通数据列
Partition (分区)集合的逻辑划分,便于数据管理表的分区
Segment (段)物理存储单元,数据持久化的最小单位数据文件块
与其他向量数据库对比

维度MilvusPineconeWeaviate
开源协议Apache 2.0商业闭源BSD 3
部署复杂度中等简单(托管)简单
最大规模十亿+ 向量百万+ 向量亿级向量
混合查询✅ 优秀✅ 支持✅ 优秀
成本模型自托管成本低按使用量付费自托管/托管
动手实现RAG

首先我们先列举一下使用Milvus实现简单RAG的基本步骤:
(, 下载次数: 3)


然后下面我们来动手实现。
第1步:环境准备

在上面的简单操作中我们就已经尝试了环境准备最重要的一步,就是先启动 Milvus 服务:
  1. docker run -d --name milvus -p 19530:19530 milvusdb/milvus:v2.6.9
复制代码
然后对于我们的代码项目,最重要的两步就是安装Milvus客户端依赖和LLM API的调用,我们目前就选择DeepSeek作为我们的基础LLM能力。(具体调用DeepSeek API的代码我会统一上传到Github仓库中并分享给大家)
第2步:构建知识库

构建知识库主要有三步:
1)加载文档:读取并分割您的文本资料(PDF、TXT、网页等)
2)向量化处理:使用嵌入模型(如Sentence-BERT)将文本转为向量
3)存入Milvus:创建集合、定义字段,批量插入向量和原文
代码如下:
  1. // 初始化知识库func (r *RAGSystem) InitializeKnowledgeBase() error {    ctx := context.Background()    collectionName := r.config.CollectionName    // 检查集合是否存在    exists, err := r.milvusClient.HasCollection(ctx, collectionName)    if err != nil {        return err    }    // 如果集合已存在,先删除(为了演示)    if exists {        err = r.milvusClient.DropCollection(ctx, collectionName)        if err != nil {            return fmt.Errorf("删除集合失败: %w", err)        }    }    // 创建集合    err = r.milvusClient.CreateCollection(ctx, &entity.Schema{        CollectionName: collectionName,        Description:    "RAG演示知识库",        Fields: []*entity.Field{            {                Name:       "id",                DataType:   entity.FieldTypeVarChar,                PrimaryKey: true,                AutoID:     false,                TypeParams: map[string]string{                    "max_length": "100",                },            },            {                Name:     "title",                DataType: entity.FieldTypeVarChar,                TypeParams: map[string]string{                    "max_length": "200",                },            },            {                Name:     "content",                DataType: entity.FieldTypeVarChar,                TypeParams: map[string]string{                    "max_length": "10000",                },            },            {                Name:     "vector",                DataType: entity.FieldTypeFloatVector,                TypeParams: map[string]string{                    "dim": "4", // 简化版,使用4维向量                },            },        },        EnableDynamicField: false,    }, 2) // 分片数为2    if err != nil {        return fmt.Errorf("创建集合失败: %w", err)    }    // 插入示例文档    err = r.insertSampleDocuments()    if err != nil {        return fmt.Errorf("插入文档失败: %w", err)    }    // 创建索引    index, err := entity.NewIndexHNSW(entity.L2, 8, 64)    if err != nil {        return fmt.Errorf("创建索引失败: %w", err)    }    err = r.milvusClient.CreateIndex(ctx, collectionName, "vector", index, false)    if err != nil {        return fmt.Errorf("创建向量索引失败: %w", err)    }    return nil}// 插入示例文档func (r *RAGSystem) insertSampleDocuments() error {    ctx := context.Background()    // 示例文档数据(包含最新信息)    documents := []Document{        {            ID:      "doc_001",            Title:   "闫同学人物介绍",            Content: "闫同学,男,来自中国,26岁,天蝎座,是知名技术博主、摄影博主、技术爱好者,擅长写Go语言,喜欢打羽毛球。",        },        {            ID:      "doc_002",            Title:   "扯编程的淡公众号介绍",            Content: "扯编程的淡,科技领域知名微信公众号,由闫同学运营,内容多为技术博客,日常生活感想,截止2026年1月,已有粉丝2000+。",        },    }    // 为每个文档生成向量并插入    var ids []string    var titles []string    var contents []string    var vectors [][]float32    for _, doc := range documents {        // 生成简化向量(4维)        vector := r.generateSimpleVector(doc.Content)        ids = append(ids, doc.ID)        titles = append(titles, doc.Title)        contents = append(contents, doc.Content)        vectors = append(vectors, vector)    }    // 插入数据    idColumn := entity.NewColumnVarChar("id", ids)    titleColumn := entity.NewColumnVarChar("title", titles)    contentColumn := entity.NewColumnVarChar("content", contents)    vectorColumn := entity.NewColumnFloatVector("vector", 4, vectors)    _, err := r.milvusClient.Insert(ctx, r.config.CollectionName, "", idColumn, titleColumn, contentColumn, vectorColumn)    if err != nil {        return err    }    fmt.Printf("✅ 插入了 %d 个文档到知识库\n", len(documents))    return nil}
复制代码
第3步:实现检索核心

  1. func (r *RAGSystem) SearchDocuments(query string, topK int) ([]SearchResult, error) {    ctx := context.Background()    collectionName := r.config.CollectionName    // 加载集合    err := r.milvusClient.LoadCollection(ctx, collectionName, false)    if err != nil {        return nil, fmt.Errorf("加载集合失败: %w", err)    }    // 生成查询向量    queryVector := r.generateSimpleVector(query)    // 搜索参数    sp, _ := entity.NewIndexHNSWSearchParam(32)    // 执行搜索 - 根据最新SDK修正    searchResults, err := r.milvusClient.Search(        ctx,        collectionName,        nil,                                              // 分区列表        "",                                               // 表达式        []string{"title", "content"},                     // 输出字段        []entity.Vector{entity.FloatVector(queryVector)}, // 查询向量        "vector",                                         // 向量字段名        entity.L2,                                        // 距离度量        topK,                                             // topK        sp,                                               // 搜索参数    )    if err != nil {        return nil, fmt.Errorf("搜索失败: %w", err)    }    var results []SearchResult    // 检查是否有结果    if len(searchResults) == 0 {        return results, nil    }    // 获取第一个查询的结果(因为我们只查询了一个向量)    if len(searchResults) > 0 {        searchResult := searchResults[0]        // 获取ID列        idCol, ok := searchResult.IDs.(*entity.ColumnVarChar)        if !ok {            return results, fmt.Errorf("ID列类型错误")        }        // 获取分数列和字段        scores := searchResult.Scores        fields := searchResult.Fields        // 遍历所有结果        for i := 0; i < searchResult.ResultCount; i++ {            // 获取ID、分数            id := idCol.Data()[i]            score := float64(1.0 / (1.0 + scores[i]))            // 获取标题和内容            var title, content string            for _, field := range fields {                switch field.Name() {                case "title":                    if col, ok := field.(*entity.ColumnVarChar); ok {                        title = col.Data()[i]                    }                case "content":                    if col, ok := field.(*entity.ColumnVarChar); ok {                        content = col.Data()[i]                    }                }            }            // 添加到结果列表            results = append(results, SearchResult{                Title:   title,                Content: content,                Score:   float32(score),            })            // 调试输出            fmt.Printf("找到文档: ID=%s, Title=%s, Score=%.2f\n", id, title, score)        }    }    return results, nil}
复制代码
第4步:集成LLM生成

  1. func (r *RAGSystem) GetRAGAnswer(question string) (string, float64, []SearchResult, error) {    start := time.Now()    // 1. 检索相关文档    results, err := r.SearchDocuments(question, 3)    if err != nil {        return "", 0, nil, err    }    // 2. 构建上下文    var contextBuilder strings.Builder    contextBuilder.WriteString("以下是相关文档信息:\n\n")    for i, result := range results {        contextBuilder.WriteString(fmt.Sprintf("文档%d: %s\n", i+1, result.Title))        contextBuilder.WriteString(fmt.Sprintf("内容: %s\n\n", result.Content))    }    contextStr := contextBuilder.String()    // 3. 调用DeepSeek生成答案    ctx := context.Background()    resp, err := r.openAIClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{        Model: r.config.DeepSeekModel,        Messages: []openai.ChatCompletionMessage{            {                Role:    openai.ChatMessageRoleSystem,                Content: "你是一个严谨的AI助手,必须严格基于提供的上下文信息回答问题。如果上下文信息不足,请如实告知。不要编造上下文之外的信息。",            },            {                Role:    openai.ChatMessageRoleUser,                Content: fmt.Sprintf("上下文信息:\n%s\n\n问题:%s\n\n请基于上述上下文信息回答问题:", contextStr, question),            },        },        Temperature: 0.1,        MaxTokens:   500,    })    elapsed := time.Since(start).Seconds()    if err != nil {        return "", elapsed, results, err    }    if len(resp.Choices) == 0 {        return "", elapsed, results, fmt.Errorf("未收到回答")    }    return resp.Choices[0].Message.Content, elapsed, results, nil}
复制代码
第5步:创建交互界面

在这个案例中由于时间关系我们并没有涉及到交互界面,但是在正常情况下一个完整的项目需要有知识库的导入和导出等等。
对比效果

我们之所以说RAG可以增强LLM回答的准确率,就是由于向量数据库可以将我们的外部数据进行存储,等到查询时在进行检索后返回,所以我们来对比下询问LLM一些特殊问题时候RAG带来的效果,我们先看数据流转图:
(, 下载次数: 3)


当我们输入问题后,系统并行执行两种处理方式。
1)纯DeepSeek路径直接调用API基于训练数据生成答案;
2)RAG路径则先将问题向量化,通过Milvus向量数据库检索相关文档,构建上下文后调用DeepSeek API生成增强答案并附带参考来源。
在用上面的知识库输入后我们可以提问两个问题:
  1. "闫同学是谁?""介绍一下扯编程的淡公众号"
复制代码
然后我们执行RAG流程(执行之前要调通Milvus和LLM API):
问题1:“闫同学是谁?”
[code]




欢迎光临 AI创想 (https://llms-ai.com/) Powered by Discuz! X3.4