5 分钟内搭建一个免费问答机器人:Milvus + LangChain

搭建一个好用、便宜又准确的问答机器人需要多长时间?

答案是 5 分钟。只需借助开源的 RAG 技术栈、LangChain 以及好用的向量数据库 Milvus。必须要强调的是,该问答机器人的成本很低,因为我们在召回、评估和开发迭代的过程中不需要调用大语言模型 API。只有在最后一步——生成最终问答结果的时候会调用到 1 次 API。

如有兴趣深入了解问答机器人背后的技术,可以查看 GitHub 上的源代码(https://github.com/zilliztech/akcio)。本文完整代码可通过 Bootcamp (https://github.com/milvus-io/bootcamp/blob/master/bootcamp/RAG/readthedocs_zilliz_langchain.ipynb)获取。

在正式开始前,我们先复习一下 RAG。RAG 的主要用途是为了给生成式 AI 输出的文本提供支撑。换言之,RAG 就是通过事实、自定义数据以减少 LLM 幻觉。具体而言,在 RAG 中,我们可以使用可靠可信的自定义数据文本,如产品文档,随后从向量数据库中检索相似结果。然后,将准确的文本答案作为“上下文”和“问题”一起插入到“Prompt”中,并将其输入到诸如 OpenAI 的 ChatGPT 之类的 LLM 中。最终,LLM 生成一个基于事实的聊天答案。

alt

RAG 的具体流程:

  1. 准备可信的自定义数据和一个 Embeding 模型。

  2. 用 Encoder 对数据进行分块并生成 Embedding 向量,将数据和元数据保存在向量数据库中。

  3. 用户提出一个问题。使用第 1 步中相同的 Encoder 将问题转化为 Embedding 向量。

  4. 用向量数据库进行语义搜索来检索问题的答案。

  5. 将搜索答案文本块作为“上下文”和用户问题结果,形成 Prompt。将 Prompt 发送给 LLM。

  6. LLM 生成答案。

01.获取数据

首先介绍一下本次搭建过程中会用到的工具:

Milvus 是一款开源高性能向量数据库,可简化非结构化数据搜索流程。Milvus 可存储、索引、搜索海量 Embedding 向量数据。

OpenAI 主要开发 AI 模型和工具,其最出名的产品为 GPT。

LangChain 工具和 wrapper 库能够帮助开发人员在传统软件和 LLM 中构建一座桥梁。

我们将用到产品文档页面,ReadTheDocs 是一款开源的免费文档软件,通过 Sphinx 生成文档。

Download readthedocs pages locally.DOCS_PAGE="https://pymilvus.readthedocs.io/en/latest/"wget -r -A.html -P rtdocs --header="Accept-Charset: UTF-8" $DOCS_PAGE

上述代码将文档页面下载到本地路径rtdocs中。接着,在 LangChain 中读取这些文档:

#!pip install langchain
from langchain.document_loaders import ReadTheDocsLoader
loader = ReadTheDocsLoader("rtdocs/pymilvus.readthedocs.io/en/latest/",features="html.parser")
docs = loader.load()

02.使用 HTML 结构切分数据

需要确定分块策略、分块大小、分块重叠(chunk overlap)。本教程中,我们的配置如下所示:

  • 分块策略 = 根据 Markdown 标题结构切分。

  • 分块大小 = 使用 Embedding 模型参数 MAX_SEQ_LENGTH

  • Overlap = 10-15%

  • 函数 =

    Langchain HTMLHeaderTextSplitter 切分markdown 文件标题。

    Langchain RecursiveCharacterTextSplitter 将长文切分。

from langchain.text_splitter import HTMLHeaderTextSplitter, RecursiveCharacterTextSplitter
Define the headers to split on for the HTMLHeaderTextSplitter
headers_to_split_on = [("h1", "Header 1"),("h2", "Header 2"),]
Create an instance of the HTMLHeaderTextSplitter
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
Use the embedding model parameters.
chunk_size = MAX_SEQ_LENGTH - HF_EOS_TOKEN_LENGTH
chunk_overlap = np.round(chunk_size * 0.10, 0)
Create an instance of the RecursiveCharacterTextSplitter
child_splitter = RecursiveCharacterTextSplitter(chunk_size = chunk_size,chunk_overlap = chunk_overlap,length_function = len,)
Split the HTML text using the HTMLHeaderTextSplitter.
html_header_splits = []
for doc in docs:splits = html_splitter.split_text(doc.page_content)for split in splits:# Add the source URL and header values to the metadatametadata = {}new_text = split.page_contentfor header_name, metadata_header_name in headers_to_split_on:header_value = new_text.split("¶ ")[0].strip()metadata[header_name] = header_valuetry:new_text = new_text.split("¶ ")[1].strip()except:breaksplit.metadata = {**metadata,"source": doc.metadata["source"]}# Add the header to the textsplit.page_content = split.page_contenthtml_header_splits.extend(splits)
Split the documents further into smaller, recursive chunks.
chunks = child_splitter.split_documents(html_header_splits)
end_time = time.time()
print(f"chunking time: {end_time - start_time}")
print(f"docs: {len(docs)}, split into: {len(html_header_splits)}")
print(f"split into chunks: {len(chunks)}, type: list of {type(chunks[0])}") 
Inspect a chunk.
print()
print("Looking at a sample chunk...")
print(chunks[1].page_content[:100])
print(chunks[1].metadata)
alt

本段文本块都有文档作为支撑。此外,标题和文本块也保存在一起,标题可以后续使用。

03.生成 Embedding 向量

最新的 MTEB 性能测试结果显示,开源 Embedding/召回模型和 OpenAI Embeddings (ada-002)效果相似。下图中分数最高的小模型是bge-large-en-v1.5,本文将选择这个模型。

alt

上图为 Embedding 模型排名表,排名最高的是voyage-lite-01-instruct(size 4.2 GB, and third rankbge-base-en-v1.5(size 1.5 GB)OpenAIEmbeddingtext-embeddings-ada-002 排名第 22。

现在,我们来初始化模型;

#pip install torch, sentence-transformers
import torch
from sentence_transformers import SentenceTransformer
Initialize torch settings
DEVICE = torch.device('cuda:3' if torch.cuda.is_available() else 'cpu')
Load the encoder model from huggingface model hub.
model_name = "BAAI/bge-base-en-v1.5"
encoder = SentenceTransformer(model_name, device=DEVICE)
Get the model parameters and save for later.
MAX_SEQ_LENGTH = encoder.get_max_seq_length() 
EMBEDDING_LENGTH = encoder.get_sentence_embedding_dimension()

接着,使用模型生成 Embedding 向量,将所有数据整合成 dictionary。

chunk_list = []
for chunk in chunks:# Generate embeddings using encoder from HuggingFace.embeddings = torch.tensor(encoder.encode([chunk.page_content]))embeddings = F.normalize(embeddings, p=2, dim=1)converted_values = list(map(np.float32, embeddings))[0]# Assemble embedding vector, original text chunk, metadata.chunk_dict = {'vector': converted_values,'text': chunk.page_content,'source': chunk.metadata['source'],'h1': chunk.metadata['h1'][:50],'h2': chunk.metadata['h1'][:50],}chunk_list.append(chunk_dict)

04.在 Milvus 中创建索引并插入数据

我们将原始文本块以 vectortextsourceh1h2的形式存储在向量数据库中。

alt

启动并连接 Milvus 服务器。如需使用 serverless 集群,你需要在连接时提供ZILLIZ_API_KEY

#pip install pymilvus
from pymilvus import connections
ENDPOINT=”https://xxxx.api.region.zillizcloud.com:443”
connections.connect(uri=ENDPOINT,token=TOKEN)

创建 Milvus Collection 并命名为 MilvusDocs。Collection 类似于传统数据库中的表,其具备 Schema,定义字段和数据类型。Schema 中的向量维度应该与 Embedding 模型生成向量的维度保持一致。与此同时,创建索引:

from pymilvus import (FieldSchema, DataType, CollectionSchema, Collection)
1. Define a minimum expandable schema.
fields = [FieldSchema(“pk”, DataType.INT64, is_primary=True, auto_id=True),FieldSchema(“vector”, DataType.FLOAT_VECTOR, dim=768),]
schema = CollectionSchema(fields,enable_dynamic_field=True,)
2. Create the collection.
mc = Collection(“MilvusDocs”, schema)
3. Index the collection.
mc.create_index(field_name=”vector”,index_params={“index_type”: “AUTOINDEX”,“metric_type”: “COSINE”,}

在 Milvus/Zilliz 中插入数据的速度比 Pinecone快!

Insert data into the Milvus collection.
insert_result = mc.insert(chunk_list)
After final entity is inserted, call flush
to stop growing segments left in memory.
mc.flush() 
print(mc.partitions)
alt

05.提出问题

接下来,我们就可以用语义搜索的力量来回答有关文档的问题。语义搜索在向量空间中使用最近邻技术来找到最匹配的文档,以回答用户的问题。语义搜索的目标是理解问题和文档背后的含义,而不仅仅是匹配关键词。在检索过程中,Milvus 还可以利用元数据来增强搜索体验(在 Milvus API 选项expr=中使用布尔表达式)。

Define a sample question about your data.
QUESTION = "what is the default distance metric used in AUTOINDEX?"
QUERY = [question]
Before conducting a search, load the data into memory.
mc.load()
Embed the question using the same encoder.
embedded_question = torch.tensor(encoder.encode([QUESTION]))
Normalize embeddings to unit length.
embedded_question = F.normalize(embedded_question, p=2, dim=1)
Convert the embeddings to list of list of np.float32.
embedded_question = list(map(np.float32, embedded_question))
Return top k results with AUTOINDEX.
TOP_K = 5
Run semantic vector search using your query and the vector database.
start_time = time.time()
results = mc.search(data=embedded_question, anns_field="vector", # No params for AUTOINDEXparam={},# Boolean expression if anyexpr="",output_fields=["h1", "h2", "text", "source"], limit=TOP_K,consistency_level="Eventually")
elapsed_time = time.time() - start_time
print(f"Milvus search time: {elapsed_time} sec")
alt

下面是检索结果,我们把这些文本放入 context 字段中:

for n, hits in enumerate(results):print(f"{n}th query result")for hit in hits:print(hit)
Assemble the context as a stuffed string.
context = ""
for r in results[0]:text = r.entity.textcontext += f"{text} "
Also save the context metadata to retrieve along with the answer.
context_metadata = {"h1": results[0][0].entity.h1,"h2": results[0][0].entity.h2,"source": results[0][0].entity.source,}
alt

上图显示,检索出了 5 个文本块。其中第一个文本块中包含了问题的答案。因为我们在检索时使用了output_fields=,所以检索返回的输出字段会带上引用和元数据。

id: 445766022949255988, distance: 0.708217978477478, entity: {'chunk': "...# Optional, default MetricType.L2 } timeout (float) –An optional duration of time in seconds to allow for theRPC. …",'source': 'https://pymilvus.readthedocs.io/en/latest/api.html','h1': 'API reference','h2': 'Client'}

06.使用 LLM 根据上下文生成用户问题的回答

这一步中,我们将使用一个小型生成式 AI 模型(LLM),该模型可通过 HuggingFace 获取。

#pip install transformers
from transformers import AutoTokenizer, pipeline
tiny_llm = "deepset/tinyroberta-squad2"
tokenizer = AutoTokenizer.from_pretrained(tiny_llm)
context cannot be empty so just put random text in it.
QA_input = {'question': question,'context': 'The quick brown fox jumped over the lazy dog'}
nlp = pipeline('question-answering', model=tiny_llm, tokenizer=tokenizer)
result = nlp(QA_input)
print(f"Question: {question}")
print(f"Answer: {result['answer']}")
alt

答案不是很准确,我们用召回的文本提出同样的问题试试看:

QA_input = {'question': question,'context': context,}
nlp = pipeline('question-answering', model=tiny_llm, tokenizer=tokenizer)
result = nlp(QA_input)
Print the question, answer, grounding sources and citations.
Answer = assemble_grounding_sources(result[‘answer’], context_metadata)
print(f"Question: {question}")
print(answer)
alt

答案准确多了!

接下来,我们用 OpenAI 的 GPT 试试,发现回答结果和我们自己搭建的开源机器人相同。

def prepare_response(response):return response["choices"][-1]["message"]["content"]
def generate_response(llm, temperature=0.0, #0 for reproducible experimentsgrounding_sources=None,system_content="", assistant_content="", user_content=""):response = openai.ChatCompletion.create(model=llm,temperature=temperature,api_key=openai.api_key,messages=[{"role": "system", "content": system_content},{"role": "assistant", "content": assistant_content},{"role": "user", "content": user_content}, ])answer = prepare_response(response=response)# Add the grounding sources and citations.answer = assemble_grounding_sources(answer, grounding_sources)return answer
Generate response
response = generate_response(llm="gpt-3.5-turbo-1106",temperature=0.0,grounding_sources=context_metadata,system_content="Answer the question using the context provided. Be succinct.",user_content=f"question: {QUESTION}, context: {context}")
Print the question, answer, grounding sources and citations.
print(f"Question: {QUESTION}")
print(response)
alt

07.总结

本文完整展示了如何针对自定义文档搭建一个 RAG 聊天机器人。得益于 LangChain、Milvus 和开源的 LLM,我们轻而易举实现了对制定数据进行免费问答。在检索过程中,Milvus 提供了数据来源。我们搭建的聊天机器人是个低成本的问答机器人,因为在召回、评估和开发迭代的过程中不需要调用大语言模型 API。只有在最后一步——生成最终问答结果的时候会调用到 1 次 API。

本文由 mdnice 多平台发布

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

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

相关文章

Backtrader 文档学习-Data Feeds(下)

Backtrader 文档学习-Data Feeds(下) 1. Data Resampling 当数据仅在单个时间范围内可用,需要在不同的时间范围内进行分析时,就需要进行一些重采样。 “重采样”实际上应该称为“上采样”,因为它是从一个源时间区间到…

C++的泛型编程—模板

目录 一.什么是泛型编程? ​编辑 ​编辑 二.函数模板 函数模板的实例化 当不同类型形参传参时的处理 使用多个模板参数 三.模板参数的匹配原则 四.类模板 1.定义对象时要显式实例化 2.类模板不支持声明与定义分离 3.非类型模板参数 4.模板的特化 函数模板…

MySQL的安装及如何连接到Navicat和IntelliJ IDEA

MySQL的安装及如何连接到Navicat和IntelliJ IDEA 文章目录 MySQL的安装及如何连接到Navicat和IntelliJ IDEA1 MySQL安装1.1 下载1.2 安装(解压)1.3 配置1.3.1 添加环境变量1.3.2 新建配置文件1.3.3 初始化MySQL1.3.4 注册MySQL服务1.3.5 启动MySQL服务1.3.6 修改默认账户密码 1…

Windows中安装nvm进行Node版本控制

1.nvm介绍 nvm英文全程也叫node.js version management,是一个node.js的版本管理工具。nvm和npm都是node.js版本管理工具,但是为了解决node各种不同之间版本存在不兼容的问题,因此可以通过nvm安装和切换不同版本的node。 2.nvm下载 可在点…

6个免费设计资源站,设计师们赶紧收藏!

本期给大家分享5个免费的设计资源站,设计师必备的设计设计神奇,绝对能帮助你在工作中事半功倍,赶紧收藏吧~ 1、菜鸟图库 https://www.sucai999.com/?vNTYwNDUx 菜鸟图库是我推荐过很多次的网站,主要是站内素材多,像…

PHPStorm一站式配置

phpstorm安装好之后,先别急着编码。工欲善其事,必先利其器,配置好下面这些之后让编码事半功倍。 主题 Appearance & Behavior -> Appearance -> Theme 选中 [Light with Light Header] 亮色较为护眼 关闭更新 Appearance & …

C#学习笔记 - C#基础知识 - C#从入门到放弃 - C# 方法

C# 入门基础知识 - 方法 第8节 方法8.1 C# 函数/方法简介8.2 方法的声明及调用8.2.1 参数列表方法的声明及调用8.2.2 参数数组方法的声明及调用8.2.3、引用参数与值参数 8.3 静态方法和实例方法8.3.1 静态、实例方法的区别8.2.3 静态、实例方法的声明及其调用 8.4 虚方法8.4.1 …

Linux学习(3)——基本命令-文件

1、cat:查看文件内容--上下合并文件 注意:cat只能查看普通的文本文件 如果文件内容过多会显示不全 选项效果-n显示行号包括空行-b跳过空白行编号;注意,在一行打了空格不算空白行,enter键直接跳过这一行才算-s将所有连续…

【JAVA】CyclicBarrier源码解析以及示例

文章目录 前言CyclicBarrier源码解析以及示例主要成员变量核心方法 应用场景任务分解与合并应用示例 并行计算应用示例 游戏开发应用示例输出结果 数据加载应用示例 并发工具的协同应用示例 CyclicBarrier和CountDownLatch的区别循环性:计数器的变化:用途…

[c]用指针进行四个数排序

#include<stdio.h> void swap(int*p1,int*p2)//定义函数&#xff0c;实现两个数值交换 {int temp;temp*p1;*p1*p2;*p2temp; } void psort( int *pa, int *pb,int *pc,int *pd) {int i1;for(i1;i<3;i)//对四个数排序&#xff0c;至少3次循环&#xff0c;交换过后是升序…

DDPM推导笔记

各位佬看文章之前&#xff0c;可以先去看看这个视频&#xff0c;并给这位up主点赞投币&#xff0c;这位佬讲解的太好了&#xff1a;大白话AI 1.前置知识的学习 1.1 正态分布特性 ​ &#xff08;1&#xff09;正态分布的概率密度函数 f ( x ) 1 2 π σ e − ( x − μ ) …

Android13音频录制适配

Android13音频录制适配 前言&#xff1a; 之前写过一篇音频录制的文章&#xff0c;当时是在Android10以下的手机可以成功录制和播放&#xff0c;但是Android10及以上手机提示创建文件失败&#xff0c;最近做过Android13的适配&#xff0c;索性一起把之前的录音也适配了&#…

Python 时间日期处理库函数

标准库 datetime >>> import datetime >>> date datetime.date(2023, 12, 20) >>> print(date) 2023-12-20 >>> date datetime.datetime(2023, 12, 20) >>> print(date) 2023-12-20 00:00:00 >>> print(date.strfti…

gem5 RubyPort: mem_request_port作用与连接 simple-MI_example.py

简介 回答这个问题&#xff1a;RubyPort的口下&#xff0c;一共定义了六个口&#xff0c;分别是mem_request_port&#xff0c;mem_response_port&#xff0c;pio_request_port&#xff0c;pio_response_port&#xff0c;in_ports, interrupt_out_ports&#xff0c;他们分别有什…

【异常】jdk21升级,asm报错Unsupported class file major version 65 springboot2 升级JDK21

【异常】jdk21升级&#xff0c;asm报错Unsupported class file major version 65 错误信息 Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isnt supported yet…

Java对接腾讯多人音视频房间示例

最近在对接腾讯的多人音视频房间&#xff0c;做一个类似于腾讯会议的工具&#xff0c;至于为什么不直接用腾讯会议&#xff0c;这个我也不知道&#xff0c;当然我也不敢问 首先是腾讯官方的文档地址&#xff1a;https://cloud.tencent.com/document/product/1690 我是后端所以…

CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem:大屏高宽自适应问题

前言 继上篇《CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem》。 发现一个有趣的问题&#xff0c;文件 rem.js 中按照宽度设置自适应&#xff0c;适用于大多数页面&#xff0c;但当遇到大屏就不那么合适了。 问题 使用宽度&#xff0c;注意代码第2 和 4 行&#xff1a;…

JAVA面试题分享一百九十九:RabbitMQ 发布确认高级

目录 一、前言 二、发布确认SpringBoot版本 介绍 实战 添加配置类 消息生产者 消息消费者 消息生产者发布消息后的回调接口 三、回退消息 介绍 四、实战 修改配置文件 修改回调接口 五、备份交换机 介绍 实战 修改高级确认发布 配置类 报警消费者 一、前言 …

基于单片机智能自动浇花系统设计

**单片机设计介绍&#xff0c;基于单片机智能自动浇花系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能自动浇花系统是一种可以自动感知周围环境&#xff0c;并执行相应动作的系统。通过使用传感器检测土…

【Netty】NIO与Netty核心概念

目录 NIO编程NIO介绍NIO和BIO的比较缓冲区(Buffer)基本介绍常用API缓冲区对象创建添加数据读取数据 通道(Channel)基本介绍Channel常用类ServerSocketChannelSocketChannel Selector (选择器)基本介绍常用API介绍示例代码 NIO 三大核心原理 Netty核心概念Netty 介绍原生 NIO 存…