o2o模式发展战略规划


最近开发了基于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的使用相关问题。
上一节,我们已经把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 中的一个操作。
上面各个方法的功能和实现
我们先创建一个表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基本没什么问题,下一步就是设计数据库,把向量数据写入进去。
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 的服务器环境就已经配置好了
隔了一个月才更新,最近一个月其他工作在忙, 这个项目也在继续推进,由于比较赶,就没写教程了。 我们来看下,这一个月的最终效果。
首先我们问标准AI “给些1000字的小说”
这个是AI给的小说内容。
当一个迷雾笼罩的夜晚,寂静的小镇上传来了一阵低沉的咆哮声。这个小镇平时安宁祥和,没有人能够想象到会发生什么令人心惊胆战的事情。
一直生活在小镇上的瑶瑶,是一个极具好奇心的女孩。听到咆哮声后,她的好奇心被激发了起来。她穿上厚实的外套,戴上帽子,决定去探个究竟。
迷雾浓密,视野极为有限。瑶瑶小心翼翼地走着,心中却不由自主地产生了一丝紧张。不时有树叶沙沙作响,又仿佛有什么不可名状的东西在暗处窥视着她。
在走了一段时间后,她终于看到了远处的一点亮光。她加快了脚步,希望能够尽快弄清楚这个声音的来源。当她靠近一座破旧的废弃建筑时,才发现声音的来源竟然是一只小猫。
这只小猫瘦弱的身体在夜风中颤抖着,发出了那咆哮声。瑶瑶心生怜悯,小心地靠近那只小猫。她发现小猫受伤了,可能是被什么东西袭击了。
瑶瑶忍不住揉了揉眼睛,为了确保她没有幻觉。这只小猫受伤的地方起了一道金色的闪电状痕迹,散发着微弱的光芒。奇怪的事情发生了,她感觉到自己的身体也在发出同样的光芒。
她小心翼翼地用手触摸那道金色闪电状的伤口,奇异的感觉传遍全身。突然,她感到自己像是被一股强大的力量包裹住了,她的身体悬浮在空中,远离了地面。
小猫也随着她一起悬浮了起来。瑶瑶不知道发生了什么,但她感到一种前所未有的力量在自己的体内涌动。她不再害怕,相反,她感到了一种前所未有的自信和勇气。
她开始控制这股力量,使自己在空中舞动起来,像一只自由翱翔的鸟儿一样。她满怀喜悦地舞动着,仿佛已经忘记了这个小镇和这个世界。
然而,她突然想到了小猫。她将目光投向小猫,小猫似乎也受到了她的力量的影响,它也在空中自由自在地飞舞着。瑶瑶不禁惊叹,这只小猫也有着与她相同的力量。
瑶瑶抱起小猫,感受着它和自己的力量相互交融。她意识到,这并不是一场巧合,而是命中注定的相遇。她和小猫将会一起去探索更广阔的世界,用他们的力量改变命运。
从此之后,瑶瑶和小猫一起飞翔在天空中,带给人们无限的希望和信仰。他们的存在让这个小镇重新焕发出生机和活力,人们开始相信奇迹的存在。
瑶瑶和小猫的故事传遍了整个小镇,成为人们口口相传的童话。他们的名字成为了希望和奇迹的象征,激励着人们勇往直前。小镇从此发生了翻天覆地的变化,成为了一个繁荣兴旺的地方。
瑶瑶和小猫,就像是小镇的守护者,他们的超凡力量改变了整个小镇的命运。他们的故事将永远被人们传颂,直至永恒。2. 我们把小说内容保存为txt文件
3.我们新增训练数据集,把这个txt给AI学习
4.学习完,查看文档分隔后的内容
5.我们测试文档记忆效果
我们可以看效果还是可以,文档提问基本都按照文档回复。
我们实现的是私域文档AI,由于我们不知道客户会问到什么问题,这时候如果遇到向量数据库匹配度非常低的问题,这时候,应该记录下来,让管理者添加这个问题相关答复。
例如问:主角的叔叔的儿子叫什么?目前小说没这个相关内容情况
我们增加一个他儿子的名称。
我们再次提问
我们可以看到,这样他能识别到我添加的小蔡。 这样就完善了文档的提问者,提出未知问题来完善文档。