从零开始构建基于ChatGPT的嵌入式(Embedding)本地医疗客服问答机器人模型(看完就会,看到最后有惊喜)

news2024/11/23 20:44:11
1、前言

代码全部开源,GitHub地址为: github.com/aehyok/go-o…

前端完全也能搭建, 前端完全也能搭建, 前端完全也能搭建, 本文中我使用的是后端语言golang,来调用的所有外部接口,但它们均是restful api,所以如果你使用的是其他语言,那么是完全可以替换的,包括nodejs或者直接使用前端请求都是可以实现我的功能的。后面有机会会使用vue3来添加一个页面,现在主要通过postman或者apifox来调试接口,主要为了验证逻辑想法。

接下来首先来看看embeddings到底是什么吧

1.1、 官网的介绍是这样的

image.png

可以被应用于以下几种情况

-   搜索(根据查询字符串的相关性对结果进行排名)
-   聚类(根据相似性对文本字符串进行分组)
-   推荐(具有相关文本字符串的项目被推荐)
-   异常检测(识别关联度小的异常值)
-   多样性测量(对相似性分布进行分析)
-   分类(文本串按其最相似的标签进行分类)



本文将主要学习第一种情况:搜索,根据相关性进行排名。也可以理解成搜索完一定会有结果,但是相关性有可能很低,有可能需要用户加以甄别。出来的信息可能不是用户需要的答案。

1.2、 而chatgpt是这样跟我说的

image.png

1.3、最后我的一句话理解便是:它可以将文本转换为固定长度的连续向量。

比如我下面使用的 text-embedding-ada-002模型输出的向量维度便是1536,这个在官网是有描述的,大家可以认真看看), 同时它是可以将任意的文本转换为向量。

那么接下来我会根据我的思路把我整个的搭建流程和调试思路都展现出来,方便自己后面进行复习查阅,也方便可能需要的你。

2、架构流程图介绍

embedding (1).png

从上图可以比较清晰明了的知道大致要干什么了

  • 准备测试数据:测试数据可能很多一个很大的数组,慢慢通过调用ChatGPT接口进行转换数据,然后将转换后的向量数据存储到qdrant云数据库中,相当于本地数据了。
  • 根据查询返回结果:首先还是将要查询的字符串调用ChatGPT接口转换为向量数据,然后再将向量数据与向量数据库中的进行匹配相似度,匹配结束可以再通过GPT-3.5或者GPT-4的模型接口进行进一步的优化数据处理。

接下来就根据如下步骤一步步进行搭建

  • Qdrant云数据库的搭建
  • 准备测试数据并写入云数据库
  • 进行查询并返回结果
3、Qdrant云数据库的搭建
3.1、初识Qdrant

image.png

说白一点就是为了存储我自己的测试数据,不过它的重点是存储向量数据。

来到github上看了一下:github.com/qdrant/qdra… , 有点牛逼 而且是Rust写的。那就来试试玩玩呗。

3.2、创建云数据库

通过github可以直接到云官网: Vector Search Database | Qdrant Cloud

image.png

可以看到能免费创建一个免费套餐,拿来做个测试还是非常方便的。

针对图示的配置,可以永久免费使用,所以基本的测试是没问题了,可以好好的愉快玩耍。

找到左侧菜单Clusters然后右侧点击 Create,输入一个cluster名称(是不是可以翻译为集群名称??)。创建后等待一会儿在进行初始化。

image.png

点击上面的api-key 或者左侧 Access 都可以创建访问云数据库的链接和api-key。

image.png 记得复制好哟,这个跟ChatGPT生成的API-Key一样,只能看到一次,所以要保存好。

3.3、通过curl 接口访问

Swagger UI (qdrant.tech) 这个就是官方提供给我们的Swagger。可视化 RESTful Web Api

image.png

我是通过这个主要看接口以及接口参数,主要还是通过postman或者apifox等工具来测试接口,swagger这里好像没有配置api-key的地方?

image.png

ok可以看到我之前创建的 collect 还在,其实这个时候本来是要创建一个collect集合(在关系型数据库中可以叫做table表)。

4、写入测试数据
4.1、准备测试数据

image.png

image.png

注意:以上数据来源于ChatGPT,仅供参考和测试使用

然而我想要的数据结构是json数组的,那么继续使用ChatGPT进行装逼

image.png

可以发现准备这一组测试数据,有一点不费吹灰之力的感觉,真是太爽了。

这里就是准备的json数组,总共13条简单的记录而已,主要是为了看一下效果

[    {        "title": "感冒",        "text": "感冒是一种由病毒引起的呼吸道感染。典型症状包括喉咙痛、流鼻涕、咳嗽、打喷嚏、头痛和发热。"    },    {        "title": "流感",        "text": "流感(Influenza)是一种由流感病毒引起的呼吸道感染。症状与感冒相似,但通常更严重,包括高热、寒战、喉咙痛、咳嗽、鼻塞、肌肉痛和乏力。"    },    {        "title": "肠胃炎",        "text": "肠胃炎是胃和肠道的炎症,通常由病毒、细菌或寄生虫感染引起。症状包括腹泻、呕吐、腹痛、恶心、发热和脱水。"    },    {        "title": "常见皮肤病",        "text": "如湿疹、皮炎、脓疱疮、疱疹等。症状可能包括红肿、瘙痒、干燥、脱皮和疼痛。"    },    {        "title": "头痛",        "text": "头痛有许多原因,如压力、紧张、缺水、缺乏睡眠等。头痛可能表现为钝痛、搏动痛、集中在头的某个部位等。"    },    {        "title": "过敏",        "text": "过敏是免疫系统对外来物质(过敏原)的异常反应。症状包括打喷嚏、流鼻涕、鼻塞、喉咙痛、眼睛痒、红肿和喘息。"    },    {        "title": "高血压",        "text": "高血压是血压持续升高的病状。许多高血压患者没有明显症状,但可能会引发头痛、眩晕、心悸和呼吸困难。"    },    {        "title": "糖尿病",        "text": "糖尿病是一种由于胰岛素分泌不足或细胞对胰岛素反应不良导致的血糖水平过高的疾病。症状包括频繁的小便、口渴、饥饿、疲劳、视力模糊、感染和伤口愈合缓慢。"    },    {        "title": "哮喘",        "text": "哮喘是一种慢性呼吸道炎症疾病,表现为气道对刺激物的过度反应。症状包括喘息、呼吸困难、胸闷和咳嗽。"    },    {        "title": "背痛",        "text": "背痛可能是由于肌肉拉伤、韧带损伤、关节炎、椎间盘问题等原因引起的。症状包括持续或间歇性的背部疼痛、僵硬和肌肉痉挛。"    },    {        "title": "关节炎",        "text": "关节炎是关节炎症的一个通用术语,可能是由于多种原因引起的,如磨损性关节炎、类风湿性关节炎等。症状包括关节疼痛、肿胀、僵硬和活动受限。"    },    {        "title": "痔疮",        "text": "痔疮是肛门或直肠血管的炎症或肿胀。症状包括肛门疼痛、瘙痒、肿胀、出血和可能的肛门突出物。"    },    {        "title": "眼疾",        "text": "如干眼症、结膜炎和近视等。症状可能包括眼睛干燥、瘙痒、红肿、分泌物和视力模糊。"    }]




4.2、go代码将测试数据转换为向量数据

这里暂时就要用到ChatGPT的接口了

image.png

看官网接口请求主要就两个参数,一个就是model 选择模型,我这里使用的是text-embedding-ada-002,另外一个input 就是我们要转换的数据字符串了,好了直接上代码看看

func GetEmbeddings(ctx *gin.Context) dto.ResponseResult {
    // 配置日志
    data, _ := ctx.GetRawData()
    var parameters map[string]interface{}
    // 包装成json 数据
    _ = json.Unmarshal(data, &parameters)

    input := parameters["input"].(string)
    // n := m["n"].(int)
    // size := m["size"].(string)
    var response = GetEmbeddingApi(input)
    var obj map[string]interface{}
    if err := json.Unmarshal(response, &obj); err != nil {
            panic(err)
    }
    fmt.Println("Body:", obj)
    return dto.SetResponseData(obj)
}

func GetEmbeddingApi(input string) []byte {
    // 定义请求参数
    embeddingModel := EmbeddingModel{
            Model: "text-embedding-ada-002",
            Input: input,
    }
    // 定义请求地址
    url := utils.OpenAIUrl + `/v1/embeddings`
    // 将请求参数转换为json格式
    bytes, err := json.Marshal(embeddingModel)
    if err != nil {
            fmt.Println("Error:", err)
            // return dto.SetResponseFailure("调用openai发生错误")
    }
    // 定义请求
    req := fasthttp.AcquireRequest()
    defer fasthttp.ReleaseRequest(req)

    req.SetRequestURI(url)
    req.Header.SetMethod("POST")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer "+utils.OpenAIAuthToken)
    req.SetBody(bytes)
    // 定义响应
    resp := fasthttp.AcquireResponse()
    defer fasthttp.ReleaseResponse(resp)

    if err := fasthttp.Do(req, resp); err != nil {
            fmt.Println("Error:", err)
            // return dto.SetResponseFailure("调用openai发生错误")
    }

    fmt.Println("Status:", resp.StatusCode())

    return resp.Body()
}




我在代码里添加了详细的注释,对照代码看一下应该还是比较好理解的。

这里其实就是通过go语言调用restful 接口 https://api.openai.com/v1/embeddings 请求,因为下面查询的时候还需要将查询字符串转换为向量数据,所以我单独进行了封装可以在两个地方调用

4.3、循环上述方法将预准备的json测试数据全部转换为向量数据
// 解析请求参数
var jsonData []map[string]string
if err := c.Bind(&jsonData); err != nil {
        return dto.SetResponseFailure("error")
}
if len(jsonData) == 0 {
        return dto.SetResponseFailure("json is empty")
}
// 数据向量化
points := make([]Point, 0)
for _, v := range jsonData {
        // 获取文本内容
        input := v["text"]
        // 获取文本内容的向量
        response := GetEmbeddingApi(input)
        fmt.Println(response, "response----response")
        var embeddingResponse EmbeddingResponse
        json.Unmarshal(response, &embeddingResponse)
        points = append(points, Point{
                ID:      uuid.New().String(),
                Payload: v,
                Vector:  embeddingResponse.Data[0].Embedding,
        })
}



4.4、将上面准备好的向量数据数组全部写入向量数据库

现在向量数据通过ChatGPT接口转换好了,现在就需要将向量数据写入到Qdrant云数据库中。 下面主要是调用了CreatePoints方法,同样可以看看

pr := PointRequest{
        Points: points,
}

//存储
err := CreatePoints(utils.QdrantCollectName, pr)
if err != nil {
        // common.Logger.Error(err.Error())
        // c.JSON(http.StatusOK, common.Error(err.Error()))
        // return
        return dto.SetResponseFailure("数据上传发生错误")
}
// c.JSON(http.StatusOK, common.Success(nil))
return dto.SetResponseSuccess("数据上传成功")



其实相对来说我前面也写过的,就是来调用Restful api写入到云数据库。

func CreatePoints(collectionName string, pointRequest PointRequest) (err error) {
    response := &CommonResponse{}
    var reqBytes []byte
    reqBytes, err = json.Marshal(pointRequest)
    if err != nil {
            return
    }

    body, err := middleware.Send(http.MethodPut, collectionApi+"/"+collectionName+pointsApi+"?wait=true", reqBytes)
    if err != nil {
            return
    }
    err = json.Unmarshal(body, &response)
    if err != nil {
            return
    }
    if response.Result == nil {
            return errors.New(response.Status.(map[string]interface{})["error"].(string))
    }
    return
}



这里其实就是通过go语言调用restful 接口 https://ui.qdrant.tech/#/points/upsert_points (点击查看具体的接口详情) 请求

现在测试数据有了,向量数据库也有了,上一小节将测试数据转换为了向量数据,这里上面刚刚又写好了向量数据写入云数据库的接口。那么写入数据的基本完成了。

通过运行接口来调试一下吧

image.png

5、开始查询数据

准备好查询数据,先通过##3.2将字符串转换为向量数据(也就是为什么进行封装上面的方法的原因),然后通过向量数据去查询云数据库,去查询相似度了

5.1、将查询字符串转换为向量数据

那么这里就先准备一下查询云数据库的接口

var message ChatMeMessage
if err := c.Bind(&message); err != nil {
        // c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        // return
}
response := GetEmbeddingApi(message.Text)

json.Unmarshal(response, &response)
fmt.Println(response, "response----response")
var embeddingResponse EmbeddingResponse
json.Unmarshal(response, &embeddingResponse)



这里其实就是通过go语言调用restful 接口 https://api.openai.com/v1/embeddings 请求

这里注意一下,我理解的正常的话只要云数据库有数据,就会返回数据的,无非相似度低一些而已。

5.2、根据向量来查询匹配相关性高的前三条记录

准备查询参数数据,然后到Qdrant云数据库进行查询

params := make(map[string]interface{})
params["exact"] = false
params["hnsw_ef"] = 128

sr := PointSearchRequest{
        Params:      params,
        Vector:      embeddingResponse.Data[0].Embedding,
        Limit:       3,
        WithPayload: true,
}

//查询相似的
res, err := SearchPoints(utils.QdrantCollectName, sr)
if err != nil {
        // common.Logger.Error(err.Error())
        // c.JSON(http.StatusOK, common.Error(err.Error()))
        // return
}



这里其实就是通过go语言调用restful接口 https://ui.qdrant.tech/#/points/search_points(点击查看具体的接口详情) 请求

5.3、通过chatGPT对查询的相关性数据进行优化

其实上面查询出来数据列出来就完事了,但是我上面也说了相关性的问题,那么这里我们可以通过ChatGPT对于查询返回的数据加工一下。

//组装本地数据
localData := ""
for i, v := range res {
        re := v.Payload.(map[string]interface{})
        localData += "\n"
        localData += strconv.Itoa(i + 1)
        localData += "."
        localData += re["title"].(string)
        localData += ":"
        localData += re["text"].(string)
}
messages := make([]ChatCompletionMessage, 0)
q := "使用以下段落来回答问题,如果段落内容与\"" + message.Text + "\"不相关就通过查询返回信息。"
q += localData

system := ChatCompletionMessage{
        Role:    "system",
        Content: "你是一个医院问诊客服机器人",
}

user := ChatCompletionMessage{
        Role:    "user",
        Content: q,
}

messages = append(messages, system)
messages = append(messages, user)
var chatResponse = GetChatCompletionsApi(messages)
var obj map[string]interface{}
if err := json.Unmarshal(chatResponse, &obj); err != nil {
        panic(err)
}
fmt.Println("Body:", obj)

// 最后我通过一个方法进行统一返回参数处理
return dto.SetResponseData(obj)




5.4、调试效果

image.png

这是我通过GPT-3.5模型的接口调试其返回结果并不是非常理想。但是如果通过GPT-4.0就完全可以达到我想要的结果了

image.png

当然了我这里演示的数据较少,仅用作演示效果,但是这种简单的问答模式加上最后GPT来润色优化有点好用了。而且还可以进行优化,比如问的问题是本地没有的,通过GPT回答后,可以进行操作,将当前问答回写到本地云数据库,这样下次再有类似的问答,就可以直接使用本地的数据了,这里仅仅提供一点点的我思考的逻辑,不一定是对的。

6、总结

这个对于我来说,理解起来还是蛮费劲的,主要是一开始没有抓到重点,其实现在把思路捋顺了,从应用的层面来看也就那么回事,当然了目前我的理解还是比较浅显的,有待机会进一步深入摸索,大数据训练模型。是不是可以考虑训练一个自己的AI虚拟人。当然还有另外一个Fine-Tunes 跟Embedding有没有关系,我得继续研究研究了,感觉上还是非常好玩的。

再次声明本文所有代码都已上传github github.com/aehyok/go-o…

本文主要参考:github.com/coderabbit2…, 也感谢大佬的及时回复解答我的疑惑。

我的个人博客:vue.tuokecat.com/blog

我的个人github:github.com/aehyok

我的前端项目:pnpm + monorepo + qiankun + vue3 + vite3 + 工具库、组件库 + 工程化 + 自动化
不断完善中,整体框架都有了
在线预览:vue.tuokecat.com
github源码:github.com/aehyok/vue-…

7、惊喜多多

当然有可能很多巨佬比我知道的还早

惊喜便是: Cursor | Build Fast一个目前免费使用GPT-4模型的生成代码,学习代码,修改代码,发现bug,处理bug的工具,可直接打开项目文件进行实操

惊喜便是:www.steamship.com如果你想调用GPT-4的接口,但苦于申请还在等待列表,那么你可以尝试一下这个,注意目前也是免费哟

惊喜便是:如果你想调用官方openai.com的接口,那么可以考虑一下这个第三方平台 api2d.com/r/186083,目前…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1995663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA开发——IP核的FIFO调用

一、简介 FIFO 的英文全称是 First In First Out,即先进先出。 FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递。它与 FPGA 内部的 RAM 和 ROM…

C语言 | Leetcode C语言题解之第330题按要求补齐数组

题目&#xff1a; 题解&#xff1a; int minPatches(int* nums, int numsSize, int n) {int patches 0;long long x 1;int index 0;while (x < n) {if (index < numsSize && nums[index] < x) {x nums[index];index;} else {x << 1;patches;}}retu…

网通设备有关工程师产品设计时如何选择适合的集成网口座呢?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;今天分享的是网通设备有关工程师产品设计时如何选择集成网口座呢&#xff1f; 下面我们一起来看看网通设备有关工程师产品设计时是选择集成网口座的 综合多年与网通产品或相关…

微调LLama 3.1——七月论文审稿GPT第5.5版:拿早期paper-review数据集微调LLama 3.1

前言 对于llama3&#xff0c;我们之前已经做了针对llama3 早7数据微调后的测评 去pk llama2的早7数据微调后&#xff0c;推理测试集中的早期paper&#xff1a;出来7方面review去pk gpt4推理测试集中的早期paper&#xff1a;7方面reviewground truth是早期paper的7方面人工rev…

13.3 正则表达式的应用

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

vue 打包时候的分包

export default defineConfig({plugins: [vue()],resolve: {alias: {: fileURLToPath(new URL(./src/, import.meta.url))}},// 分包&#xff0c;node_modules中的单独打包成名字为vendor的js文件build: {rollupOptions: {manualChunks(id) {if (id.includes(node_modules)) {r…

汽车测试,引领国际

汽车测试及质量监控博览会&#xff08;中国&#xff09;(Testing Expo China 2024 - Automotive )是引领世界的领先国际展会&#xff0c;将于 2024 年 8 月 28-30日在上海世博展览馆举行。展示汽车测试、开发和验证技术的各个方面&#xff0c;每年在中国、欧洲和美国举办&#…

Openssh升级到>=9.6版本导致sftp不兼容的解决办法

因为要修复漏洞&#xff0c;所以得升级openssh&#xff0c;但从9.6开始&#xff0c;默认编译出来的openssh&#xff0c;无法兼容之前的sftp客户端了&#xff0c;大概的错误信息就是算法协商不一致。 解决办法其实也简单&#xff0c;就2步&#xff1a; 1. 编译的时候&#xff0c…

Linux学习记录(五)-------三类读写函数

文章目录 三种读写函数1.行缓存2.无缓存3.全缓存4.fgets和fputs5.gets和puts 三种读写函数 1.行缓存 遇到新行&#xff08;\n&#xff09;,或者写满缓存时&#xff0c;即调用系统函数 读&#xff1a;fgets,gets,printf,fprintf,sprintf写&#xff1a;fputs,puts,scanf 2.无缓…

Flink Checkpoint expired before completing解决方法

在Flink消费Kafka日志的时候出现了这样的一则报错&#xff0c; JobManager报错如下&#xff1a; 2024-03-07 15:21:12,500 [Checkpoint Timer] WARN org.apache.flink.runtime.checkpoint.CheckpointFailureManager [] - Failed to trigger or complete checkpoint 181 for …

面试官:怎样设计一个分布式任务调度平台?

大家好&#xff0c;我是君哥。 在工作中&#xff0c;批量任务调度的需求经常会遇到&#xff0c;比如下面的几个场景&#xff1a; 数据迁移&#xff1a;从数据库 A 批量读取数据&#xff0c;加工后把数据写入数据库 B&#xff1b; 消息通知&#xff1a;运营商批量给客户发送短…

宠物空气净化器什么牌子好?希喂、美的测评推荐

家里养了两只猫&#xff0c;每天晚上和我入眠&#xff0c;早上睡醒过来就看到猫睡在我身边&#xff0c;这一刻幸福感爆棚。幸福感爆棚的同时&#xff0c;无力感也袭来。主要是因为虽然每天玩得都很开心&#xff0c;但是家里的变化让我不禁感慨这是真实存在的吗。一回到家就会发…

【Material-UI】Checkbox组件:受控模式详解

文章目录 一、什么是受控组件&#xff1f;二、受控模式的基本用法1. 核心概念2. 代码分析 三、受控组件的优势与应用场景1. 确保数据的一致性2. 简化复杂的表单逻辑3. 轻松实现状态回显 四、受控模式的最佳实践1. 状态管理2. 优化性能3. 处理异步数据 五、结论 在Web开发中&…

【数据结构-前缀哈希】力扣3026. 最大好子数组和

给你一个长度为 n 的数组 nums 和一个 正 整数 k 。 如果 nums 的一个 子数组 中&#xff0c;第一个元素和最后一个元素 差的绝对值恰好 为 k &#xff0c;我们称这个子数组为 好 的。换句话说&#xff0c;如果子数组 nums[i…j] 满足 |nums[i] - nums[j]| k &#xff0c;那么…

如何通过AquilaInsight快速查看每天有哪些异常/慢查询?

友情链接&#xff1a; AquilaInsight核心功能及角色概览Aquila的核心功能介绍DBA Service的核心功能介绍刚部署好Aquila Insight&#xff0c;第一次如何使用如何通过Aquila Insight快速定位一个查询为什么慢&#xff1f;Aquila 添加自定义监控信息和告警的示例当Quark/Incepto…

[器械财讯]威高血液净化:中国血液透析市场的领军企业冲刺IPO

一、IPO冲刺&#xff1a;威高血净迎来新进展 山东威高血液净化制品股份有限公司&#xff08;以下简称“威高血净”&#xff09;在2023年12月30日正式启动IPO后&#xff0c;于2024年8月2日迎来新进展&#xff0c;其审核状态在上海证券交易所更新为“已问询”。尽管2024年医药行…

没有获取淘宝API的资质怎么获取淘宝数据

淘宝是头部电商平台之一&#xff0c;每个自研商家或电商软件服务商想要开发电商管理功能模板就少不了要对接淘宝API。淘宝API是在淘宝开放平台提供的&#xff0c;自研商家和软件服务商接入淘宝开放平台需要经过一系列审核和申请流程&#xff0c;要求资质和相关资料符合对应的要…

Windows下,C# 通过FastDDS高效通信

目录 1、安装FastDDS 库2、使用IDL定义自己的数据格式3、生成DLL3.1 托管 &#xff08;Managed&#xff09;模式3.2 非托管 &#xff08;Unmanaged&#xff09;模式 -- 可用于Unity 代码示例 eprosima Fast DDS is a C implementation of the DDS (Data Distribution Service) …

【面试八股文】软件测试面试题汇总

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、你的测试职业发展是什么? 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有…

bug: 配置flyway.locations多个脚本位置不生效

文章目录 业务场景场景一场景二 业务场景 随着项目版本迭代&#xff0c;数据库结构也会变动。如果一个项目引用其他项目的jar包&#xff0c;并且需要执行对应jar包的flyway脚本&#xff0c;就需要配置flyway.locations 场景一 正常情况下&#xff0c;在一个项目中可以在yml文件…