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 生成一个基于事实的聊天答案。

RAG 的具体流程:
-
准备可信的自定义数据和一个 Embeding 模型。
-
用 Encoder 对数据进行分块并生成 Embedding 向量,将数据和元数据保存在向量数据库中。
-
用户提出一个问题。使用第 1 步中相同的 Encoder 将问题转化为 Embedding 向量。
-
用向量数据库进行语义搜索来检索问题的答案。
-
将搜索答案文本块作为“上下文”和用户问题结果,形成 Prompt。将 Prompt 发送给 LLM。
-
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)

本段文本块都有文档作为支撑。此外,标题和文本块也保存在一起,标题可以后续使用。
03.生成 Embedding 向量
最新的 MTEB 性能测试结果显示,开源 Embedding/召回模型和 OpenAI Embeddings (ada-002)效果相似。下图中分数最高的小模型是bge-large-en-v1.5
,本文将选择这个模型。

上图为 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 中创建索引并插入数据
我们将原始文本块以 vector
、text
、source
、h1
、h2
的形式存储在向量数据库中。

启动并连接 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)

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")

下面是检索结果,我们把这些文本放入 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,}

上图显示,检索出了 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']}")

答案不是很准确,我们用召回的文本提出同样的问题试试看:
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)

答案准确多了!
接下来,我们用 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)

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

5 分钟内搭建一个免费问答机器人:Milvus + LangChain
搭建一个好用、便宜又准确的问答机器人需要多长时间? 答案是 5 分钟。只需借助开源的 RAG 技术栈、LangChain 以及好用的向量数据库 Milvus。必须要强调的是,该问答机器人的成本很低,因为我们在召回、评估和开发迭代的过程中不需要调用大语言…...
WPF Border
在 WPF 中,Border 是一种常用的控件,用于给其他控件提供边框和背景效果。 要使用 Border 控件,您可以在 XAML 代码中添加以下代码: <Border BorderBrush"Black" BorderThickness"2" Background"Lig…...

基于博弈树的开源五子棋AI教程[4 静态棋盘评估]
引子 静态棋盘的评估是棋力的一个很重要的体现,一个优秀的基于博弈树搜索的AI往往有上千行工作量,本文没有做深入讨论,仅仅写了个引子用来抛砖引玉。 评估一般从两个角度入手,一个是子力,另一个是局势。 1 评估维度 …...
STL--排序与检索
题目 现有N个大理石,每个大理石上写了一个非负整数。首先把各数从小到大排序,然后回答Q个问题。每个问题是否有一个大理石写着某个整数x,如果是,还要回答哪个大理石写着x。排序后的大理石从左到右编写为1-N。(样例中,…...

大数据处理与分析-Spark
导论 (基于Hadoop的MapReduce的优缺点) MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架 MapReduce是一种用于处理大规模数据集的编程模型和计算框架。它将数据处理过程分为两个主要阶段:Map阶…...

虚拟机的下载、安装(模拟出服务器)
下载 vmware workstation(收费的虚拟机) 下载vbox 网址:Oracle VM VirtualBox(免费的虚拟机) 以下选择一个下载即可,建议下载vbox,因为是免费的。安装的时候默认下一步即可(路径最好…...
K8S Pod Terminating/Unknown故障排查
一、pod异常出现现象 优雅终止周期(Graceful termination period): 当pod被删除时,会进入"Terminating"状态,等待容器优雅关闭。如果容器关闭所需时间超过默认期限(默认30秒),则pod将保持在"Terminating"状态。 Finalize…...

labelme标注的json文件数据转成coco数据集格式(可处理目标框和实例分割)
这里主要是搬运一下能找到的 labelme标注的json文件数据转成coco数据集格式(可处理目标框和实例分割)的代码,以供需要时参考和提供相关帮助。 1、官方labelme实现 如下是labelme官方网址,提供了源代码,以及相关使用方…...

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法
我在插入表数据时遇到了1366报错,报错内容:1366 - Incorrect integer value: Cindy for column name at row 1,下面我演示解决方法。 根据上图,原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…...

MySQL中MVCC的流程
参考文章一 参考文章二 当谈到数据库的并发控制时,多版本并发控制(MVCC)是一个重要的概念。MVCC 是一种用于实现数据库事务隔离性的技术,常见于像 PostgreSQL 和 Oracle 这样的数据库系统中。 MVCC 的核心思想是为每个数据行维护…...
朴素贝叶斯法_naive_Bayes
朴素贝叶斯法(naive Bayes)是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布;然后基于此模型,对给定的输入 x x x,利用贝叶斯定理…...

Windows下安装MongoDB实践总结
本文记录Windows环境下的MongoDB安装与使用总结。 【1】官网下载 官网下载地址:Download MongoDB Community Server | MongoDB 这里可以选择下载zip或者msi,zip是解压后自己配置,msi是傻瓜式一键安装。这里我们分别对比进行实践。 【2】ZI…...

华为云Stack 8.X 流量模型分析(二)
二、流量模型分析相关知识 1.vNIC 虚拟网络接口卡(vNIC)是基于主机物理 NIC 的虚拟网络接口。每个主机可以有多个 NIC,每个 NIC 可以是多个 vNIC 的基础。 将 vNIC 附加到虚拟机时,Red Hat Virtualization Manager 会在虚拟机之间创建多个关联的…...

rk3588 之启动
目录 uboot版本配置修改编译 linux版本配置修改编译 启动sd卡启动制作spi 烧录 参考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用这两个配置即可: orangepi-5-plus-rk3588_defconfig r…...

ARM GIC (五)gicv3架构-LPI
在gicv3中,引入了一种新的中断类型。message based interrupts,消息中断。 一、消息中断 外设,不在通过专用中断线,向gic发送中断,而是写gic的寄存器,来发送中断。 这样的一个好处是,可以减少中断线的个数。 为了支持消息中断,gicv3,增加了LPI,来支持消息中断。并且…...

sql-labs服务器结构
双层服务器结构 一个是tomcat的jsp服务器,一个是apache的php服务器,提供服务的是php服务器,只是tomcat向php服务器请求数据,php服务器返回数据给tomcat。 此处的29-32关都是这个结构,不是用docker拉取的镜像要搭建一下…...

【小沐学写作】Docsify制作在线电子书、技术文档(Docsify + Markdown + node)
文章目录 1、简介2、安装2.1 node2.2 docsify-cli 3、配置3.1 初始化3.2 预览效果3.3 加载对话框3.4 更多页面3.5 侧 栏3.6 自定义导航栏 结语 1、简介 https://docsify.js.org/#/?iddocsify 一个神奇的文档网站生成器。 简单轻巧没有静态构建的 html 文件多个主题 Docsify…...

电脑完全重装教程——原版系统镜像安装
注意事项 本教程会清除所有个人文件 请谨慎操作 请谨慎操作 请谨慎操作 前言 本教程是以系统安装U盘为介质进行系统重装操作,照着流程操作会清除整个硬盘里的文件,请考虑清楚哦~ 有些小伙伴可能随便在百度上找个WinPE作为启动盘就直接…...

【智慧办公】如何让智能会议室的电子标签实现远程、批量更新信息?东胜物联网硬件网关让解决方案更具竞争力
近年来,为了减少办公耗能、节能环保、降本增效,越来越多的企业开始从传统的办公模式转向智慧办公。 以智能会议室为例,会议是企业业务中不可或缺的一部分,但在传统办公模式下,一来会议前行政人员需要提前准备会议材料…...
面向对象设计与分析40讲(16)静态工厂方法模式
前面我们介绍了简单工厂模式,在创建对象前,我们需要先创建工厂,然后再通过工厂去创建产品。 如果将工厂的创建方法static化,那么无需创建工厂即可通过静态方法直接调用的方式创建产品: // 工厂类,定义了静…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...