分类 向量数据库 下的文章

上一节,我们已经把Milvus 向量数据库搭建完成,由于milvus\_cli 工具2年没更新,不是太完善,我们需要使用SDK 来操作Milvus数据库, 因为我们服务都是基于Golang 开发,这里我用Golang来测试Milvus 数据库的插入与查询以保证,代码对数据库的基本操作没问题

我们根据官方文档,自己写一个结构体 MilvusClient , 下面是完整代码

package main

import (
    "context"
    "fmt"
    "github.com/milvus-io/milvus-sdk-go/v2/client"
    "github.com/milvus-io/milvus-sdk-go/v2/entity"
    "log"
    "math/rand"
    "time"
)

// 定义 Milvus 客户端结构体
type MilvusClient struct {
    milvusClient client.Client
}

func main() {
    fmt.Println("进入main函数")

    MilvusClient, err := NewMilvusClient()
    if err != nil {
        fmt.Println("GetMilvusDb", err.Error())
        return
    }

    //MilvusClient.CreateMilvusDb()
    //MilvusClient.InsertData()
    MilvusClient.SearchResultData()
    //MilvusClient.CreateIndex()
    MilvusClient.DescribeIndex()
}

// 向量场创建一个索引
func (M *MilvusClient) CreateIndex() {

    var milvusClient = M.milvusClient
    // 创建 IndexIvfFlat 索引对象
    idx, err := entity.NewIndexIvfFlat(
        entity.L2,
        1024,
    )
    if err != nil {
        log.Fatal("fail to create ivf flat index parameter:", err.Error())
    }
    // 在 Milvus 中创建索引
    err = milvusClient.CreateIndex(
        context.Background(),
        "book",
        "book_intro",
        idx,
        false,
    )
    if err != nil {
        log.Fatal("fail to create index:", err.Error())
    }

}

// 此方法获取索引的详细信息
func (M *MilvusClient) DescribeIndex() {
    var milvusClient = M.milvusClient

    // 获取索引的详细信息
    indexInfo, err := milvusClient.DescribeIndex(
        context.Background(),
        "book",       // 指定集合名称
        "book_intro", // 指定向量字段名称
    )
    if err != nil {
        log.Fatal("fail to describe index:", err.Error())
    }
    log.Println(indexInfo)
}

// 集合加载到内存中
func (M *MilvusClient) ReleaseCollectionData() {
    var milvusClient = M.milvusClient
    // 释放集合的内存
    err := milvusClient.ReleaseCollection(
        context.Background(),
        "book",
    )
    if err != nil {
        fmt.Println("failed to release collection:", err.Error())
    }
}

// 集合加载到内存中
func (M *MilvusClient) LoadCollectionData() {

    var milvusClient = M.milvusClient

    err := milvusClient.LoadCollection(
        context.Background(),
        "book",
        false,
    )
    if err != nil {
        fmt.Println("failed to load collection:", err.Error())
    }
    fmt.Println("loading ok")
}

// 搜索数据
func (M *MilvusClient) SearchResultData() {
    var err error
    var milvusClient = M.milvusClient

    M.LoadCollectionData()

    defer M.ReleaseCollectionData()

    sp, _ := entity.NewIndexIvfFlatSearchParam( // NewIndex*SearchParam func
        2, // searchParam
    )

    searchResult, err := milvusClient.Search(
        context.Background(), // ctx
        "book",               // CollectionName
        []string{},           // partitionNames
        "",                   // expr
        []string{"book_id"},  // outputFields
        []entity.Vector{entity.FloatVector([]float32{0.1, 0.2})}, // vectors
        "book_intro", // vectorField
        entity.L2,    // metricType
        2,            // topK
        sp,           // sp
    )
    if err != nil {
        fmt.Println("failed search data:", err.Error())
    }
    fmt.Printf("%#v\n", searchResult)
    for _, sr := range searchResult {
        fmt.Println("id", sr.IDs)
        fmt.Println("Scores", sr.Scores)
    }
}

// 插入数据
func (M *MilvusClient) InsertData() {
    var err error
    var milvusClient = M.milvusClient
    bookIDs := make([]int64, 0, 2000)
    wordCounts := make([]int64, 0, 2000)
    bookIntros := make([][]float32, 0, 2000)
    for i := 0; i < 2000; i++ {
        bookIDs = append(bookIDs, int64(i))
        wordCounts = append(wordCounts, int64(i+10000))
        v := make([]float32, 0, 2)
        for j := 0; j < 2; j++ {
            v = append(v, rand.Float32())
        }
        bookIntros = append(bookIntros, v)
    }
    idColumn := entity.NewColumnInt64("book_id", bookIDs)
    wordColumn := entity.NewColumnInt64("word_count", wordCounts)
    introColumn := entity.NewColumnFloatVector("book_intro", 2, bookIntros)

    _, err = milvusClient.Insert(
        context.Background(), // ctx
        "book",               // CollectionName
        "",                   // partitionName
        idColumn,             // columnarData
        wordColumn,           // columnarData
        introColumn,          // columnarData
    )
    if err != nil {
        fmt.Println("failed to insert data:", err.Error())
        return
    }
    //fmt.Println("xxx", idColumn)
}
func (M *MilvusClient) CreateMilvusDb() {
    var err error
    var milvusClient = M.milvusClient
    var (
        collectionName = "test"
    )
    schema := &entity.Schema{
        CollectionName: collectionName,
        Description:    "Test book search",
        Fields: []*entity.Field{
            {
                Name:       "book_id",
                DataType:   entity.FieldTypeInt64,
                PrimaryKey: true,
                AutoID:     false,
            },
            {
                Name:       "word_count",
                DataType:   entity.FieldTypeInt64,
                PrimaryKey: false,
                AutoID:     false,
            },
            {
                Name:     "book_intro",
                DataType: entity.FieldTypeFloatVector,
                TypeParams: map[string]string{
                    "dim": "2",
                },
            },
        },
        EnableDynamicField: true,
    }

    err = milvusClient.CreateCollection(
        context.Background(), // ctx
        schema,
        2, // shardNum
    )
    if err != nil {
        fmt.Println("创建数据库失败:", err)
        return
    }

    defer milvusClient.Close()
}

// Milvus 客户端对象,用于连接 Milvus 服务器
func NewMilvusClient() (m *MilvusClient, err error) {
    // Milvus 服务器的地址和端口
    host := "127.0.0.1"
    port := "19530"

    // 设置超时时间为10秒
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    // 创建一个 Milvus 客户端
    milvusClient, err := client.NewClient(ctx, client.Config{
        Address: fmt.Sprintf("http://%s:%s", host, port),
    })
    fmt.Println("sss")
    if err != nil {
        fmt.Println("sss", err.Error())
        fmt.Errorf("创建 Milvus 客户端失败: %v", err)
        return
    }

    // 使用 defer 语句来关闭 `milvusClient` 对象
    //defer milvusClient.Close()

    // 列出所有数据库
    dbs, err := milvusClient.ListDatabases(ctx)
    if err != nil {
        fmt.Errorf("获取数据库列表失败: %v", err)
        return
    }

    // 输出数据库列表
    fmt.Println("数据库列表:")
    for _, db := range dbs {
        fmt.Println("-", db)
    }
    var s = &MilvusClient{
        milvusClient: milvusClient,
    }

    return s, nil
}

代码的结构比较简单,主要包含一个 Milvus 客户端结构体,以及该结构体的若干方法,每个方法都对应了 Milvus SDK 中的一个操作。

上面各个方法的功能和实现

  1. NewMilvusClient: 创建 Milvus 客户端对象,用于连接 Milvus 服务器。
  2. CreateIndex: 在 Milvus 中创建索引。
  3. DescribeIndex: 获取索引的详细信息。
  4. ReleaseCollectionData: 释放集合的内存。
  5. LoadCollectionData: 加载集合数据到内存。
  6. SearchResultData: 在 Milvus 中搜索数据。

我们先创建一个表book

MilvusClient, err := NewMilvusClient()
    if err != nil {
        fmt.Println("GetMilvusDb", err.Error())
        return
    }
// 创建表结构
MilvusClient.CreateMilvusDb()

执行代码,得到效果


执行测试数据插入

MilvusClient.InsertData()

这里插入2000条数据


查询数据

我们查询向量字段book\_intro


到这里,数据库的操作基本没有什么问题,这里需要注意的是,Milvus数据库查询前需要先把数据加载到内存,查询完,释放掉内存

加载到内存
M.LoadCollectionData()

查询执行完释放内存
defer M.ReleaseCollectionData()

Golang操作Milvus基本没什么问题,下一步就是设计数据库,把向量数据写入进去。

最近开发了基于CHATGPT的AI给开发者使用,通过设置一些有趣的prompt 可以有一些很好的玩法,但在长期的使用过程中遇到一些问题,需要解决。

正好发现目前关于向量数据库的搭建开发资料极度稀缺,更没完整的教程,准备把这次开发过程记录下来。

普通AI遇到的问题
1.token超标,为了能让AI回复客户问题,有连续性,AI理解上面已经答过的问题,我们会把用户的每次回复都传给chatGPT,当连续交互几次,会出现次数过多,或者字数超出触发chatGPT的警告

chatGPT错误内容

2.记忆力失去,例如在10个会话前,我告诉AI,你是小美,等10次会话后,我再问AI,你叫啥,他不知道

3.放飞自我,有时候一些回答跟我们问的问题毫无相关,放飞自我。

4.学习能力,例如AI我问他一些最近微信小程序开发的规则,他是无法知道的,或者问某个大公司今年的财报等等,一些较新的内容。

5.私有文库能力,例如我们某汽车公司有一份产品使用说明,可能几千页,当我们遇到这款汽车遇到某个故障,想跟客服人员一样,立刻得到正确的结果,chatGPT是很难回答正确。

解决方案:向量数据库
遇到这些问题,行业里的解决方案就是使用向量数据库,向量数据库在图像搜索、自然语言处理、推荐系统已经非常成熟。 与传统的数据库(关系、非关系数据库)相比,你可以理解为传统数据库主要记录数据,而向量数据库主要记录特征,既然是特征这也就是说他很少会用到完全匹配说法,核心概念就是相似的特征,相似比例多少,想象目前行业的应用图片搜索,推荐系统是否都是搜索相似性特性。

向量数据库选择,Milvus、Zilliz、pinecone、腾讯云、阿里云,除了腾讯云的2023年08月发布,其他的都可以使用了。为了降低成本,我们就自己搭建Milvus,接下来教程也会教一步一步搭建Milvus向量数据库。

主要的场景

  • 搜索(其中结果按与查询字符串的相关性排序)
  • 聚类(其中文本字符串按相似性分组)
  • 建议(推荐带有相关文本字符串的项目)
  • 异常检测(识别出关系很小的异常值)
  • 多样性测量(分析相似性分布)
  • 分类(其中文本字符串按其最相似的标签进行分类)

我们这次用的是搜索场景

初步设想代码实现过程

整体实现思路
接下来的文章,基本会按照这个思路来逐步实现最终结果。

1. 安装和配置Milvus:

Milvus是一个开源向量数据库,用于存储和检索高维向量。首先,需要安装并配置Milvus,以便将知识库中的文本转换为向量,并能够高效地检索相关的信息。

这里会涉及到Milvus的使用,增删改查,Milvus相关工具的使用教程

2. 构建本地知识库:

创建一个本地知识库,其中包含问题和对应的答案。每个问题都应该有一个唯一的ID,并且答案可以是任何形式的文本。

本地文件处理,PDF切个,文件投喂AI,拿到向量值,写入数据库

3. 使用Golang开发问答系统的后端:

在Golang中编写一个后端服务,用于接收用户的问题,并通过ChatGPT和Milvus来查找最合适的答案。

实现第二步的后端代码

4. 集成ChatGPT API:

使用OpenAI的ChatGPT API,将用户的问题传递给ChatGPT模型,以获取对应的回答。你可以通过发送HTTP请求到ChatGPT API来实现这一点,我使用的模型是text-embedding-ada-002

拿到最终prompt 去跟AI提问

5. 文本向量化:

当用户提出问题时,需要对该问题进行向量化,以便与知识库中的问题向量进行匹配。使用ChatGPT模型的词嵌入(Word Embeddings)来将问题转换为向量。

使用chatGPT 的text-embedding-ada-002模型来把文档变成向量值

6. 利用Milvus进行快速检索:

将所有知识库中的问题向量加载到Milvus中,并设置适当的索引以加快检索速度。当用户提问时,将向量化的问题与Milvus中的向量进行相似度匹配,找到最相似的问题。

用户问题,先查询Milvus

7. 返回答案:

通过ChatGPT获得的回答可能不是最终的答案,因为ChatGPT是一个生成式模型,它可以自由生成文本。将ChatGPT生成的回答与Milvus中找到的答案结合起来,然后将最终的答案返回给用户。

8. 错误处理和反馈:

考虑到ChatGPT可能会产生错误的回答,实现一些错误处理机制,例如询问用户是否满意回答,并将用户的反馈用于改进系统。

9. 用户界面:

开发一个用户界面,允许用户输入问题,并从后端获取回答展示给用户。

代码流程
代码主要分为2个大流程,一个是数据训练,把客户上传的文档数据进行训练,生成特征数据保存到向量数据库,另一个用户提问搜索流程,下面是2个流程图

数据训练流程

数据查询流程

下一篇就是安装和配置Milvus,已经Milvus的使用相关问题。

隔了一个月才更新,最近一个月其他工作在忙, 这个项目也在继续推进,由于比较赶,就没写教程了。 我们来看下,这一个月的最终效果。

首先我们问标准AI “给些1000字的小说”
这个是AI给的小说内容。

当一个迷雾笼罩的夜晚,寂静的小镇上传来了一阵低沉的咆哮声。这个小镇平时安宁祥和,没有人能够想象到会发生什么令人心惊胆战的事情。

一直生活在小镇上的瑶瑶,是一个极具好奇心的女孩。听到咆哮声后,她的好奇心被激发了起来。她穿上厚实的外套,戴上帽子,决定去探个究竟。

迷雾浓密,视野极为有限。瑶瑶小心翼翼地走着,心中却不由自主地产生了一丝紧张。不时有树叶沙沙作响,又仿佛有什么不可名状的东西在暗处窥视着她。

在走了一段时间后,她终于看到了远处的一点亮光。她加快了脚步,希望能够尽快弄清楚这个声音的来源。当她靠近一座破旧的废弃建筑时,才发现声音的来源竟然是一只小猫。

这只小猫瘦弱的身体在夜风中颤抖着,发出了那咆哮声。瑶瑶心生怜悯,小心地靠近那只小猫。她发现小猫受伤了,可能是被什么东西袭击了。

瑶瑶忍不住揉了揉眼睛,为了确保她没有幻觉。这只小猫受伤的地方起了一道金色的闪电状痕迹,散发着微弱的光芒。奇怪的事情发生了,她感觉到自己的身体也在发出同样的光芒。

她小心翼翼地用手触摸那道金色闪电状的伤口,奇异的感觉传遍全身。突然,她感到自己像是被一股强大的力量包裹住了,她的身体悬浮在空中,远离了地面。

小猫也随着她一起悬浮了起来。瑶瑶不知道发生了什么,但她感到一种前所未有的力量在自己的体内涌动。她不再害怕,相反,她感到了一种前所未有的自信和勇气。

她开始控制这股力量,使自己在空中舞动起来,像一只自由翱翔的鸟儿一样。她满怀喜悦地舞动着,仿佛已经忘记了这个小镇和这个世界。

然而,她突然想到了小猫。她将目光投向小猫,小猫似乎也受到了她的力量的影响,它也在空中自由自在地飞舞着。瑶瑶不禁惊叹,这只小猫也有着与她相同的力量。

瑶瑶抱起小猫,感受着它和自己的力量相互交融。她意识到,这并不是一场巧合,而是命中注定的相遇。她和小猫将会一起去探索更广阔的世界,用他们的力量改变命运。

从此之后,瑶瑶和小猫一起飞翔在天空中,带给人们无限的希望和信仰。他们的存在让这个小镇重新焕发出生机和活力,人们开始相信奇迹的存在。

瑶瑶和小猫的故事传遍了整个小镇,成为人们口口相传的童话。他们的名字成为了希望和奇迹的象征,激励着人们勇往直前。小镇从此发生了翻天覆地的变化,成为了一个繁荣兴旺的地方。

瑶瑶和小猫,就像是小镇的守护者,他们的超凡力量改变了整个小镇的命运。他们的故事将永远被人们传颂,直至永恒。

2. 我们把小说内容保存为txt文件

3.我们新增训练数据集,把这个txt给AI学习



4.学习完,查看文档分隔后的内容

5.我们测试文档记忆效果

我们可以看效果还是可以,文档提问基本都按照文档回复。

我们实现的是私域文档AI,由于我们不知道客户会问到什么问题,这时候如果遇到向量数据库匹配度非常低的问题,这时候,应该记录下来,让管理者添加这个问题相关答复。

例如问:主角的叔叔的儿子叫什么?目前小说没这个相关内容情况



我们增加一个他儿子的名称。


我们再次提问


我们可以看到,这样他能识别到我添加的小蔡。 这样就完善了文档的提问者,提出未知问题来完善文档。

Milvus的安装主要对内存要求比较高,如果你本机日常使用后,还剩余大于8G内存,可以本地安装,我本机不够内存,不建议本地安装,服务器购买2核8G,或者4核8G都行。

Milvus的本地安装教程网上有很多,我选择使用docker镜像安装。 阿里、腾讯等厂家有镜像,我们基于镜像创建好数据库即可。

因为我用ucloud 服务器比较多,这里教程基于ucloud 云主机搭建。打开控制台,创建主机这里注意,不要选AMD CPU,选Intel 就好了。

等个几分钟,显示运行中,就可以登录数据库了

Milvus登录进入是没有密码的,执行命令

[root@10-10-121-239 ~]# milvus_cli

会显示



  __  __ _ _                    ____ _     ___
 |  \/  (_) |_   ___   _ ___   / ___| |   |_ _|
 | |\/| | | \ \ / / | | / __| | |   | |    | |
 | |  | | | |\ V /| |_| \__ \ | |___| |___ | |
 |_|  |_|_|_| \_/  \__,_|___/  \____|_____|___|


Learn more: https://github.com/zilliztech/milvus_cli.


milvus_cli >

表示已经成功登录数据库,注意的是,这里显示并不跟redis或其他数据库一样,这里命令行进入,只是说明你进入了命令模式,并不代表你已经连接成功数据库,例如我们输入,显示集合

list collections


提示这个,我们输入connect ,即可连接数据库

milvus_cli > connect
Connect Milvus successfully.
+-------+-----------+
| Host  | 127.0.0.1 |
| Port  |   19530   |
| Alias |  default  |
+-------+-----------+

连接上,我们就可以执行 list collections。

milvus\_cli工具,看github 地址已经2年没更新了,不是很好用。例如AI给出的一些命令,基本不存在


这几个命令都不存在,大家使用的时候,尽量输入命令 --help 来看支持哪些命令

由于milvus\_cli 十分难用,这里建议使用开源可视化工具Attu

使用 docker 安装 Attu:

docker run -p 8000:3000 -e HOST_URL=http://{ attu IP }:8000 -e MILVUS_URL={milvus server IP}:19530 zilliz/attu:latest

其中, attu IP 表示运行 attu 环境的 IP 地址, milvus server IP 是运行 Milvus 环境的 IP 地址。

启动成功后的界面如下图所示,输入正确的 Milvus IP 及端口就能进入 Attu 了!

collection的创建和数据导入

create collection -c book -f book_id:INT64:book_id -f word_count:INT64:word_count -f book_intro:FLOAT_VECTOR:2 -p book_id import -c book 'https://raw.githubusercontent.com/milvus-io/milvus_cli/main/examples/user_guide/search.csv' 

官方参考文档:

[https://milvus.io/docs/v2.2.x/create\_collection.md](https://milvus.io/docs/v2.2.x/create\_collection.md)

[https://milvus.io/docs/insert\_data.md](https://milvus.io/docs/insert\_data.md)

这样,我们一个Milvus 的服务器环境就已经配置好了