基于langchaingo实现知识库对接本地模型ollama的分步探索

前言

这篇就是通过一个简单的示例,结合 langchaingo 来实现一下自己开发 rag 应用的整个流程。

本文项目代码地址:langchaingo-ollama-rag[3]

img

正文

前文说到,rag 的核心流程大概有如下几步:

  • 先将知识库内容切分
  • 再把切分后的内容向量化存入向量数据库
  • 用户提问之后,先将问题在向量库中进行相似性检索,找出匹配度高的答案。
  • 然后把查询出来的结果,包装好 Prompt。
  • 最后调用大语言模型,让大语言模型基于上一步的结果进行分析并形成最终的答案,返回给用户。

接下来我就通过代码,来按照上边的流程,做下实践。

前置准备

关于如何配置 ollama 的模型,这里就不再赘述了,ollama 的用法详见我第一篇文章。

这里向量数据库使用的是 qdrant,可通过如下方式快速拉起测试环境。

$ docker pull qdrant/qdrant
$ docker run -itd --name qdrant -p 6333:6333 qdrant/qdrant

使用如下命令可创建一个集合:

$ curl -X PUT http://localhost:6333/collections/langchaingo-ollama-rag \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "vectors": {
      "size": 768,
      "distance": "Dot"
    }
  }'

使用如下命令可删除该集合:

$ curl --location --request DELETE 'http://localhost:6333/collections/langchaingo-ollama-rag'

先切分文档

文档我准备的内容取自个人之前发布的一篇小作:我这一生–一个显示器的故事[4]

主要代码如下:

// TextToChunks 函数将文本文件转换为文档块
func TextToChunks(dirFile string, chunkSize, chunkOverlap int) ([]schema.Document, error) {
 file, err := os.Open(dirFile)
 if err != nil {
  return nil, err
 }
 // 创建一个新的文本文档加载器
 docLoaded := documentloaders.NewText(file)
 // 创建一个新的递归字符文本分割器
 split := textsplitter.NewRecursiveCharacter()
 // 设置块大小
 split.ChunkSize = chunkSize
 // 设置块重叠大小
 split.ChunkOverlap = chunkOverlap
 // 加载并分割文档
 docs, err := docLoaded.LoadAndSplit(context.Background(), split)
 if err != nil {
  return nil, err
 }
 return docs, nil
}

这里将 chunkSizechunkOverlap 两个变量参数化,也是为了能够更加清晰地看到参数所代表的含义,以及对于整个流程的影响。

比如我默认情况下,执行效果如下:

$ go run main.go filetochunks
2024/04/17 23:03:32 INFO [转换文件为块儿成功,块儿数量:  40]

🗂 块儿内容==> 工程师对我虽然恩为再造,但我很长一段时间里并不感谢他,因为他既然塑我成异类,就应该把我留在他身边,那样我也会好过一些,然而或许是他马虎,把我丢在了旁的显示器中间,我的一生坎坷也正是自此开始。

几天后,我与其它四十九个同胞一起被货车拉到一个毫不起眼的地方,几个男人搬运着我们。

我转身问在路上认识的小杰:“小杰,他们这是要干什么?”

小杰转过脸:“你不知道吗?把我们装备到网吧里呀。”
🗂 块儿内容==> 小杰转过脸:“你不知道吗?把我们装备到网吧里呀。”

“网吧是干什么的?”

“网吧是让我们的爸爸赚钱的呀。怎么了?李尤?”

我不仅愤然:“他拿我们赚钱使,你还叫他爸爸,你是不是脑子进水了?”

......

通过上边切分之后,可以看出,单个 chunkSize 将决定单个块儿的内容大小,chunkOverlap 将决定有多少向前重复的内容。同理,当我调试时把块儿调大,那么最终块儿的数量就会减少:

$ go run main.go filetochunks -c 500      
2024/04/17 23:06:30 INFO [转换文件为块儿成功,块儿数量:  13]

块儿文本向量化

主逻辑代码如下:

// storeDocs 将文档存储到向量数据库
func storeDocs(docs []schema.Document, store *qdrant.Store) error {
 // 如果文档数组长度大于0
 if len(docs) > 0 {
  // 添加文档到存储
  _, err := store.AddDocuments(context.Background(), docs)
  if err != nil {
   return err
  }
 }
 return nil
}

执行如下命令可将切分后的文本块儿存入向量数据库:

$ go run main.go embedding
转换块儿为向量成功

获取用户输入并查询向量数据库

主逻辑代码如下:

// useRetriaver 函数使用检索器
func useRetriaver(store *qdrant.Store, prompt string, topk int) ([]schema.Document, error) {
 // 设置选项向量
 optionsVector := []vectorstores.Option{
  vectorstores.WithScoreThreshold(0.80), // 设置分数阈值
 }

 // 创建检索器
 retriever := vectorstores.ToRetriever(store, topk, optionsVector...)
 // 搜索
 docRetrieved, err := retriever.GetRelevantDocuments(context.Background(), prompt)

 if err != nil {
  return nil, fmt.Errorf("检索文档失败: %v", err)
 }

 // 返回检索到的文档
 return docRetrieved, nil
}

执行如下命令可得到如下结果:

$ go run main.go retriever -t 3
请输入你的问题: 规定
🗂 根据输入的内容检索出的块儿内容==> 果,转身笑脸同那小孩儿讲:“你换一台机器吧。”接着告诉伙计:“如果有人来玩这一台,你就说坏了。我明天找那个工程师来,他一定知道怎么回事儿!”
🗂 根据输入的内容检索出的块儿内容==> 利、物质的享受,却不去思考金钱的短暂,物质的虚无;旁的显示器似也承认并接受那规划,并坦率的说:“显示器嘛,不就是用来显示的。”至于显示什么,它们也全不管。身处浑浊之中,独我欲清何其难哉!我于是沉默了。
🗂 根据输入的内容检索出的块儿内容==> 我被成功改造,在后来的岁月里,我也渐渐变得健忘,有时候有人朝我讥吼“不自主,毋宁死啊”,“去他妈的规定”说完他们便笑作一团。而我也只是静静地看着他们发狂地吼,发疯地笑,不做一言。  后来偶然听说网吧里的电脑两年换一次新,我于是又像牢犯盼出狱那样地期待着更新换代。

将检索到的内容,交给大语言模型处理

主逻辑代码如下:

// GetAnswer 获取答案
func GetAnswer(ctx context.Context, llm llms.Model, docRetrieved []schema.Document, prompt string) (string, error) {
 // 创建一个新的聊天消息历史记录
 history := memory.NewChatMessageHistory()
 // 将检索到的文档添加到历史记录中
 for _, doc := range docRetrieved {
  history.AddAIMessage(ctx, doc.PageContent)
 }
 // 使用历史记录创建一个新的对话缓冲区
 conversation := memory.NewConversationBuffer(memory.WithChatHistory(history))

 executor := agents.NewExecutor(
  agents.NewConversationalAgent(llm, nil),
  nil,
  agents.WithMemory(conversation),
 )
 // 设置链调用选项
 options := []chains.ChainCallOption{
  chains.WithTemperature(0.8),
 }
 // 运行链
 res, err := chains.Run(ctx, executor, prompt, options...)
 if err != nil {
  return "", err
 }

 return res, nil
}

通过该方法拿到的结果,我这边调试下来,拿到的总是英文结果,各种调试 prompt,都没有成功,这里猜测可能是跟选用的模型有关系,暂时通过将得到的结果再次丢给大模型,做一次翻译来解决。

// Translate 将文本翻译为中文
func Translate(llm llms.Model, text string) (string, error) {
 completion, err := llms.GenerateFromSinglePrompt(
  context.TODO(),
  llm,
  "将如下这句话翻译为中文,只需要回复翻译后的内容,而不需要回复其他任何内容。需要翻译的英文内容是: \n"+text,
  llms.WithTemperature(0.8))
 if err != nil {
  return "", err
 }
 return completion, nil
}

我之所以怀疑可能是模型的因素,是因为我在调试这段翻译功能时发现,使用 mistral 模型总是很难直接把英文转换为中文,虽然功能上他给转换了,但是仍旧还会输出一些英文,所以应该是模型的原因。

经过两个方法的加持,接下来就是见证奇迹的时刻了:

$ go run main.go getanswer    
请输入你的问题: 这篇文章讲了什么
🗂 原始回答==>  This article talks about the speaker's inner struggle between adhering to truth and enjoying material pleasures. The speaker expresses frustration with the fact that they must conform to societal expectations of living according to parallel rules, and questions the meaning of their own existence in this world. They also reflect on the futility of reality and contemplate suicide as a means of escape from their suffering. However, they ultimately remain silent and continue to endure the pain.

🗂 翻译后的回答==> 这篇文章讲述了讲话者内心的斗争,他在追求真相和愉快的物质待遇之间犹豫不决。他表达了对社会对我们所定义生活中必须遵守平行原则的不满,并怀疑自己在这个世界中的意义。他还思考现实的无用和死亡作为逃离他的痛苦的方式。然而,最终他保持沉默并继续忍受痛苦。

如上得到的结果虽然不算很贴切,但感觉还算是相对沾边的,这就是我上篇文章提到的,当你掌握了整个概念,也学会了整个流程的玩法,最终得到的结果,可能只有实际预期的 50%不到。

那么如何通过优化来提高这个结果所达到的预期值呢,这就要从如上步骤的每一个细节,每一个参数开始调优,且这种调优并不是一劳永逸的,还需要结合原始文档的格式,内容等情况进行不同的调整。

如何学习AI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值