向量数据库和嵌入模型

简介: 本文介绍了向量数据库和嵌入模型的概念及应用,重点探讨了两者在AI技术栈中的协作关系。向量数据库是一种用于存储高维向量数据的解决方案,支持相似性搜索而非传统的关系型数据库精确匹配。文中通过实例展示了如何使用阿里百炼的文本嵌入模型(text-embedding-v3)将文本向量化,并结合Qdrant向量数据库进行存储与检索。代码示例部分详细说明了从文本嵌入到向量存储及查询的完整流程,为开发者提供了实践参考。

各位读者朋友们,感谢您阅读到本文,我是笠泱,这是《人工智能&机器学习》系列第二期,本期分享向量数据库(Vector Database)和嵌入模型(Embedding Model)。

本期导语

关注AI、大模型的朋友们应该都听过用RAG(Retrieval-augmented Generation,检索增强生成)搭建知识库,RAG的基础组成之二就是向量数据库和嵌入模型,在当下的IT领域,至少有四类数据库是必须了解,以Mysql为代表的关系型数据库(RDBMS),以Redis、MongoDB为代表的NoSQL,以Amazon Aurora、TiDB、OceanBase为代表的NewSQL,再者就是向量数据库。

在正式阅读本文前,我们先来看几个问题:

1、什么是向量?

2、什么是文本/图片向量化?

3、什么是向量数据库?有哪些向量数据库?

4、将数据保存进向量数据库后,该如何检索查询?

5、向量数据库和Mysql的查询与什么区别和不同?

向量数据库

Vector是向量或矢量的意思,向量是数学里的概念,而矢量是物理里的概念,但二者描述的是同一件事。向量的准确定义:向量是用于表示具有大小和方向的量。具体而言,向量可以在不同的维度空间中定义,最常见的是二维和三维空间中的向量,但理论上也可以有更高维的向量。例如,在二维平面上的一个向量可以写作(x,y),这里x和y分别表示该向量沿两个坐标轴方向上的分量;而在三维空间里,则会有一个额外的z坐标,即(x,y,z)。

如我们所见,每个数值向量都有 x 和 y 坐标(或者在多维系统中是 x、y、z,...)。x、y、z... 是这个向量空间的轴,称为维度。对于我们想要表示为向量的一些非数值实体,我们首先需要决定这些维度,并为每个实体在每个维度上分配一个值。

例如,在一个交通工具数据集中,我们可以定义四个维度:“轮子数量”、“是否有发动机”、“是否可以在地上开动”和“最大乘客数”。然后我们可以将一些车辆表示为:

因此,我们的汽车Car向量将是 (4, yes, yes, 5),或者用数值表示为 (4, 1, 1, 5)(将 yes 设为 1,no 设为0)。向量的每个维度代表数据的不同特性,维度越多对事务的描述越精确,我们可以使用“是否有翅膀”、“是否使用柴油”、“最高速度”、“平均重量”、“价格”等等更多的维度信息。

每个向量都有一个长度和方向。例如,在这个图中,p 和 a 指向相同的方向,但长度不同。p 和 b 正好指向相反的方向,但有相同的长度。然后还有c,长度比p短一点,方向不完全相同,但很接近。

那么,哪一个最接近 p 呢?

如果“相似”仅仅意味着指向相似的方向,那么a 是最接近 p 的。接下来是 c。b 是最不相似的,因为它正好指向与p 相反的方向。如果“相似”仅仅意味着相似的长度,那么 b 是最接近 p 的(因为它有相同的长度), 接下来是 c,然后是 a。

由于向量通常用于描述语义意义,仅仅看长度通常无法满足需求。大多数相似度测量要么仅依赖于方向,要么同时考虑方向和大小。

在数学中向量间的相似度测量即相似度计算,有四种常见的计算方法,这里不展开讨论。

  • 欧几里得距离 Euclidean distance
  • 曼哈顿距离 Manhattan distance
  • 点积 Dot product
  • 余弦相似度 Cosine similarity

向量存储(Vector Database/VectorStore)是一种用于存储和检索高维向量数据的数据库或存储解决方案,它特别适用于处理那些经过嵌入模型转化后的数据。在向量数据库中,查询与传统关系数据库不同,它们执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,VectorStore返回与查询向量“相似"的向量。比如说在使用一个商城系统的向量数据库进行查询的时候,用户输入“北京”,其可能返回的结果会是 “中国、北京、华北、首都、奥运会” 等信息;输入“沈阳”,其返回结果可能会是“东北、辽宁、雪花、重工业”等信息。当然,返回的信息取决于向量数据库中存在的数据。用户可以通过参数的设置来限定返回的情况,进而适配不同的需求。

向量数据库用于将您的数据与Al模型集成。在使用它们时的第一步是将您的数据加载到向量数据库中。然后,当要将用户查询发送到AI模型时,首先检索一组相似文档。然后,这些文档作为用户问题的上下文,并与用户的查询一起发送到Al模型。这种技术被称为检索增强生(RetrievalAugmentedGeneration, RAG )。

说人话核心就是将文本、图像和视频等等转换为一组浮点数数组(即向量)后进行存储的系统就是向量数据库,其查询与传统关系数据库不同,向量数据库执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,向量数据库返回与查询向量“相似"的向量。

能做向量数据库有如下产品

上图源于:https://docs.langchain4j.dev/integrations/embedding-stores/

嵌入模型

嵌入模型(Embedding Model)和向量数据库(Vector Database/Vector Store)是一对亲密无间的合作伙伴,也是 AI 技术栈中紧密关联的两大核心组件,两者的协同作用构成了现代语义搜索、推荐系统和 RAG(Retrieval Augmented Generation,检索增强生成)等应用的技术基础。

嵌入(Embedding)的工作原理是将文本、图像和视频转换为称为向量(Vectors)的浮点数数组。这些向量旨在捕捉文本、图像和视频的含义。嵌入数组的长度称为向量的维度(Dimensionality)。

嵌入模型(EmbeddingModel)是嵌入过程中采用的模型。当前EmbeddingModel的接口主要用于将文本转换为数值向量,接口的设计主要围绕这两个目标展开:

  • 可移植性:该接口确保在各种嵌入模型之间的轻松适配。它允许开发者在不同的嵌入技术或模型之间切换,所需的代码更改最小化。这一设计与Spring模块化和互换性的理念一致。
  • 简单性:嵌入模型简化了文本转换为嵌入的过程。通过提供如embed(String text)和embed(Document document)这样简单的方法,它去除了处理原始文本数据和嵌入算法的复杂性。这个设计选择使开发者,尤其是那些初次接触AI的开发者,更容易在他们的应用程序中使用嵌入,而无需深入了解其底层机制。

嵌入模型是一种机器学习模型,旨在在连续的低维向量空间中表示数据(例如文本、图像或其他形式的信息)。这些嵌入可以捕获数据之间的语义或上下文相似性,使机器能够更有效地执行比较、聚类或分类等任务。

假设你想描述不同的水果。你不用长篇大论,而是用数字来描述甜度、大小和颜色等特征。例如,苹果可能是[8,5.7],而香蕉是[9,7,4]。这些数字使比较或对相似的水果进行分组变得更容易。

代码演示

上述讲了理论概念,没有实际操作会不够具象化理解,嵌入模型选择阿里百炼平台的通用文本向量-v3(text-embedding-v3),当然你也可以选择其他平台的嵌入模型,如BAAI/bge-m3等

向量数据库选择Qdrant,使用Docker容器方式运行:

docker run -d  -p 6333:6333 -p 6334:6334 qdrant/qdrant 
#运行后浏览器访问http://localhost:6333/dashboard#/welcome 进入可视化页面

将吴青峰演唱的《我们都拥有海洋》歌词通过text-embedding-v3嵌入模型(1024维)向量化后存入Qdrant

最后,感谢您的阅读!系列文章会同步更新在微信公众号@云上的喵酱、阿里云开发者社区@云上的喵酱、CSDN@笠泱,您的点赞+关注+转发是我后续更新的动力!

附录:lanchain4j实现代码如下

EmbeddingModelConfig

package com.liyang.study.config;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @auther liyang
 * @create 2025-05-28 00:00
 */
@Configuration(proxyBeanMethods = false)
public class EmbeddingModelConfig
{
    @Bean
    public EmbeddingModel embeddingModel()  //EmbeddingModel 向量接口
    {
        return OpenAiEmbeddingModel.builder()
        .apiKey(System.getenv("LANGCHAIN4J_KEY"))
        .modelName("text-embedding-v3")
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        //              .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings")
        .build();
    }
    @Bean
    public QdrantClient qdrantClient() {
        QdrantGrpcClient.Builder grpcClientBuilder =
        QdrantGrpcClient.newBuilder("127.0.0.1", 6334, false);
        return new QdrantClient(grpcClientBuilder.build());
    }
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        return QdrantEmbeddingStore.builder()
        .host("127.0.0.1")
        .port(6334)
        .collectionName("test-qdrant")
        .build();
    }
}

EmbeddinglController

package com.liyang.study.controller;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.EmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import dev.langchain4j.model.embedding.EmbeddingModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import dev.langchain4j.data.embedding.Embedding;
import java.util.Vector;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
/**
 * @auther liyang
 * @create 2025-05-28 00:00
 */
@RestController
@Slf4j
public class EmbeddinglController
{
    @Resource
    private EmbeddingModel embeddingModel;
    @Resource
    private QdrantClient qdrantClient;
    @Resource
    private EmbeddingStore<TextSegment> embeddingStore;
    /**
     * 文本向量化测试,看看形成向量后的文本
     * http://localhost:9012/embedding/embed
     * @return
     */
    @GetMapping(value = "/embedding/embed")
    public String embed()
    {
        String prompt = """
                我们都拥有海洋的歌词如下:
                如果这是 再不返回的夏天
                好好的告别 和重要的人说再见
                无关那些 催人启程的祝愿
                这世界太吵 不代表你的声音不珍贵
                去哪 让我选择
                如果 只有岸没有海
                那我 长奔跑的脚踝
                到底是为什么
                为剧烈的心动
                跑起来去见 喜欢的我
                兴趣使然的
                怎么判断有用 谁来规定无用
                完美的旅程
                为何每一站 是一样的路牌
                没有 石头 所以 所以 没有 悟空
                就让我自己走吧
                穿着 球鞋 我的球鞋
                迎着 暴雪 我的暴雪
                朝着 山野 要去见
                所有我喜欢的一切
                就让我自己走吧
                哪一代 什么浪 都不是 我的模样
                别对我说 去成为谁的模样
                未来不在 沙滩上 我本该拥有海洋
                我本该拥有海洋 就让我自己走吧
                孤岛极光 虚拟城邦 星球流浪
                不管会遇见什么 hello hello hello
                你可见过 两片相同的树叶
                人不可复写 生命是原创的情节
                去问明天 渴望怎样一张脸
                它说是少年 它说和我一样毫无经验
                不懂 天高地厚
                是否 是短暂的自由
                多久 像风筝爱天空
                怎能害怕 坠落
                人生中的考场
                我会勇敢地 给出回答
                亲手署名吧
                有时也会考砸 不抄谁的答案
                挥手告别吧
                记得二十岁 流着泪的笑脸
                前路 卡关 借来 借来 加血 加防
                就让我自己走吧
                穿着 球鞋 我的球鞋
                迎着 暴雪 我的暴雪
                朝着 山野 要去见
                所有我喜欢的一切
                就让我自己走吧
                哪一代 什么浪 都不是 我的模样
                别对我说 去成为谁的模样
                未来不在 沙滩上 我本该拥有海洋
                我本该拥有海洋 就让我自己走吧
                孤岛极光 虚拟城邦 星球流浪
                我将走向 无垠远方
                我本该拥有海洋 就让我自己走吧
                我不遗忘 我的模样
                不管答案是什么 hello hello hello
                我们都拥有海洋
                """;
        Response<Embedding> embeddingResponse = embeddingModel.embed(prompt);
        System.out.println(embeddingResponse);
        return embeddingResponse.content().toString();
    }
    /** http://localhost:9012/embedding/createCollection
     * 新建向量数据库实例和创建索引:test-qdrant
     * 类似mysql create database test-qdrant
     */
    @GetMapping(value = "/embedding/createCollection")
    public void createCollection()
    {
        var vectorParams = Collections.VectorParams.newBuilder()
                .setDistance(Collections.Distance.Cosine)
                .setSize(1024)
                .build();
        qdrantClient.createCollectionAsync("test-qdrant", vectorParams);
    }
    /*
    http://localhost:9012/embedding/add
     往向量数据库新增文本记录
     */
    @GetMapping(value = "/embedding/add")
    public String add()
    {
        String prompt = """
                我们都拥有海洋的歌词如下:
                如果这是 再不返回的夏天
                好好的告别 和重要的人说再见
                无关那些 催人启程的祝愿
                这世界太吵 不代表你的声音不珍贵
                去哪 让我选择
                如果 只有岸没有海
                那我 长奔跑的脚踝
                到底是为什么
                为剧烈的心动
                跑起来去见 喜欢的我
                兴趣使然的
                怎么判断有用 谁来规定无用
                完美的旅程
                为何每一站 是一样的路牌
                没有 石头 所以 所以 没有 悟空
                就让我自己走吧
                穿着 球鞋 我的球鞋
                迎着 暴雪 我的暴雪
                朝着 山野 要去见
                所有我喜欢的一切
                就让我自己走吧
                哪一代 什么浪 都不是 我的模样
                别对我说 去成为谁的模样
                未来不在 沙滩上 我本该拥有海洋
                我本该拥有海洋 就让我自己走吧
                孤岛极光 虚拟城邦 星球流浪
                不管会遇见什么 hello hello hello
                你可见过 两片相同的树叶
                人不可复写 生命是原创的情节
                去问明天 渴望怎样一张脸
                它说是少年 它说和我一样毫无经验
                不懂 天高地厚
                是否 是短暂的自由
                多久 像风筝爱天空
                怎能害怕 坠落
                人生中的考场
                我会勇敢地 给出回答
                亲手署名吧
                有时也会考砸 不抄谁的答案
                挥手告别吧
                记得二十岁 流着泪的笑脸
                前路 卡关 借来 借来 加血 加防
                就让我自己走吧
                穿着 球鞋 我的球鞋
                迎着 暴雪 我的暴雪
                朝着 山野 要去见
                所有我喜欢的一切
                就让我自己走吧
                哪一代 什么浪 都不是 我的模样
                别对我说 去成为谁的模样
                未来不在 沙滩上 我本该拥有海洋
                我本该拥有海洋 就让我自己走吧
                孤岛极光 虚拟城邦 星球流浪
                我将走向 无垠远方
                我本该拥有海洋 就让我自己走吧
                我不遗忘 我的模样
                不管答案是什么 hello hello hello
                我们都拥有海洋
                """;
        TextSegment segment1 = TextSegment.from(prompt);
        segment1.metadata().put("author", "吴青峰");
        segment1.metadata().put("price", "priceless");
        Embedding embedding1 = embeddingModel.embed(segment1).content();
        String result = embeddingStore.add(embedding1, segment1);
        System.out.println(result);
        return result;
    }
    @GetMapping(value = "/embedding/query1")
    public void query1(){
        Embedding queryEmbedding = embeddingModel.embed("我们都拥有海洋歌词说了什么").content();
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .maxResults(1)
                .build();
        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);
        System.out.println(searchResult.matches().get(0).embedded().text());
    }
    @GetMapping(value = "/embedding/query2")
    public void query2(){
        Embedding queryEmbedding = embeddingModel.embed("我们都拥有海洋的演唱者是谁").content();
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .filter(metadataKey("author").isEqualTo("吴青峰"))
                .maxResults(1)
                .build();
        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);
        System.out.println(searchResult.matches().get(0).embedded().text());
    }
}

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
|
26天前
|
消息中间件 人工智能 自然语言处理
DeepWiki × LoongCollector:AI 重塑开源代码理解
本文探讨了开源项目LoongCollector的复杂性及其对开发者带来的挑战,介绍了DeepWiki作为AI驱动的智能文档生成工具如何解决这些问题。DeepWiki通过结构化文档、交互式流程图和核心数据结构解析,帮助开发者快速理解项目架构与逻辑。同时,其内置的AI对话助手可实时解答技术疑问,提供场景化指导,如问题排查、源码学习路径制定及开发指导。文章还展示了DeepWiki在优化LoongCollector插件开发、提升社区贡献效率方面的实际应用,并展望了AI重构开源协作范式的未来潜力。
401 43
|
1月前
|
机器学习/深度学习 存储 人工智能
浅入浅出——生成式 AI
团队做 AI 助理,而我之前除了使用一些 AI 类产品,并没有大模型相关的积累。故先补齐一些基本概念,避免和团队同学沟通起来一头雾水。这篇文章是学习李宏毅老师《生成式 AI 导论》的学习笔记。
240 27
浅入浅出——生成式 AI
|
27天前
|
人工智能 安全 API
Higress MCP Server 安全再升级:API 认证为 AI 连接保驾护航
Higress MCP Server 新增了 API 认证功能,为 AI 连接提供安全保障。主要更新包括:1) 客户端到 MCP Server 的认证,支持 Key Auth、JWT Auth 和 OAuth2;2) MCP Server 到后端 API 的认证,增强第二阶段的安全性。新增功能如可重用认证方案、工具特定后端认证、透明凭证透传及灵活凭证管理,确保安全集成更多后端服务。通过 openapi-to-mcp 工具简化配置,减少手动工作量。企业版提供更高可用性保障,详情参见文档链接。
330 42
|
18天前
|
人工智能 自然语言处理 Prometheus
不懂 PromQL,AI 智能体帮你玩转大规模指标数据分析
PromQL AI 智能体上线。本文将从自然语言生成 PromQL 实践视角,探讨如何构建知识库、与大模型进行交互、最终生成符合需求的 PromQL 语句。本文还介绍了在 MCP 和云监控控制台下使用 AI 智能体的用例。
164 48
|
1月前
|
消息中间件 运维 监控
加一个JVM参数,让系统可用率从95%提高到99.995%
本文针对一个高并发(10W+ QPS)、低延迟(毫秒级返回)的系统因内存索引切换导致的不稳定问题,深入分析并优化了JVM参数配置。通过定位问题根源为GC压力大,尝试了多种优化手段:调整MaxTenuringThreshold、InitialTenuringThreshold、AlwaysTenure等参数让索引尽早晋升到老年代;探索PretenureSizeThreshold和G1HeapRegionSize实现索引直接分配到老年代;加速索引复制过程以及升级至JDK11使用ZGC。
366 82
加一个JVM参数,让系统可用率从95%提高到99.995%
|
21天前
|
机器学习/深度学习 人工智能 自然语言处理
AI Agent
本文介绍了AI Agent的概念及其在云计算3.0时代的焦点地位,强调了其与大语言模型(LLM)的紧密联系。AI Agent由控制端(Brain)、感知端(Perception)和行动端(Action)组成,能够通过LLMs实现复杂的任务分解、记忆管理及工具使用。文章探讨了单代理、多代理及人机交互的应用场景,并分析了钢铁侠中贾维斯的现实版——微软开源JARVIS项目。此外,还提及了国内外多个开源平台及Python在AI领域的核心地位,同时提出了关于智能代理发展的开放问题,如安全性、群体智能演化及代理服务化等。最后提供了丰富的参考资料以供深入研究。
219 15
AI Agent
|
1月前
|
人工智能 监控 安全
面对MCP"工具投毒",我们该如何应对
本文探讨了MCP(Model Context Protocol)的安全风险与防护措施。MCP作为AI系统与外部工具交互的标准框架,虽提升了插件兼容性,但也带来了“工具投毒”等安全威胁。攻击者可通过篡改工具描述,诱导模型执行非授权操作,如读取敏感文件。文章详细分析了攻击原理,并通过复刻实验展示了如何利用MCP客户端/服务器代码实现此类攻击。为应对风险,提出了基于大模型智能评估和eBPF技术的两种安全可观测方案:前者通过内置评估模板检测潜在威胁,后者实时监控系统运行时行为,结合两者可有效提升MCP系统的安全性。
647 92
面对MCP"工具投毒",我们该如何应对
|
19天前
|
传感器 人工智能 自动驾驶
生成式AI应用于自动驾驶:前沿与机遇
近期发表的一篇综述性论文总结了生成式AI在自动驾驶领域的应用进展,并探讨了自动驾驶与机器人、无人机等其它智能系统在生成式AI技术上的交叉融合趋势
64 10
|
12天前
|
容器
50.[HarmonyOS NEXT RelativeContainer案例七] 均匀分布的底部导航栏:水平链布局技术详解
底部导航栏是移动应用中最常见的导航元素之一,它通常包含多个均匀分布的图标或按钮,用于在应用的主要功能之间切换。在HarmonyOS NEXT中,RelativeContainer组件提供了强大的链式布局(Chain)功能,能够轻松实现元素的均匀分布,非常适合底部导航栏的实现。本教程将详细讲解如何利用RelativeContainer的水平链布局功能实现一个美观、均匀分布的底部导航栏。
119 72