基于检索增强生成 (RAG) 的多工具编排旨在创建智能工作流,该工作流使用大型语言模型 (LLM) 和工具(包括网络搜索引擎或向量数据库)来响应查询。通过这种方式,LLM 将自动动态地选择用于每个查询的工具。例如,网络搜索工具将打开当前更新信息的域,而向量数据库(如 Pinecone)将打开上下文特定信息。
在实践中,RAG 通常需要定义函数调用工具(例如网络搜索或数据库查找),并通过 API(例如 Responses API 或 OpenAI)来编排这些工具。这种用法会为每个用户查询启动一系列检索和生成步骤。因此,模型能力的各个方面与当前信息交织在一起。
什么是RAG?
RAG 是指语言模型使用检索到的相关外部信息并将其整合到输出中的过程。因此,RAG 模型并非完全依赖内部训练数据的“闭卷”模型,而是执行明确的检索步骤。它会浏览一组文档(例如向量数据库或搜索索引),并使用这些检索到的文档来增强 LLM 的提示。
目的是提取 LLM 所需的知识,以便对查询提供准确的响应。这样,我们可以将这个过程视为实时的“增强”生成。LLM 能够利用生成功能和通过检索增强的信息,为查询提供与上下文相关的准确答案。通过这样做,LLM 能够使用在训练时不具备的准确、最新、领域特定或专有的知识来回答问题。
RAG 的主要优势:
- 最新且领域特定的知识:RAG 允许模型访问新的非静态训练数据(例如,当前新闻、内部文档)来回答查询。
- 降低幻读率:由于模型是基于实际检索到的事实进行回答,因此 RAG 可以最大限度地减少幻读。
- 可验证性:答案可以引用或显示检索到内容的来源,从而提高答案的透明度和可信度。
RAG 允许 LLM 将生成能力与知识检索功能相结合。在 RAG 方法中,模型在给出答案之前会从外部语料库中检索相关的信息片段,然后利用这些上下文生成更准确、更合理的答案。
为什么在RAG中使用工具?
网页搜索和向量索引查询等工具对 RAG 至关重要,因为它们提供了 LLM 自身无法提供的检索功能。添加这些工具后,RAG 可以消除仅依赖 LLM 服务所带来的问题。例如,LLM 具有知识截止值,可以自信地生成不正确或过时的信息。搜索工具允许系统自动获取最新的按需事实。同样,像 Pinecone 这样的向量数据库存储了特定领域和专有数据,例如医生记录、公司政策等,而这些数据模型原本无法获取。
每种工具都有其优势,而使用多种工具的组合就是多工具协同工作。例如,通用网页搜索工具可以回答高级问题。像 PineconeSearchDocuments 这样的工具可以在包含专有信息集知识的内部向量存储库中找到正确的相关条目。它们共同确保无论模型的答案是什么,都可以在源文件或任何质量最佳的地方找到它。一般性问题可以通过功能齐全的内置工具(例如网页搜索)来处理。“非常具体”的问题或医学问题,需要利用系统内部的知识,则可以通过从向量数据库中检索上下文来解决。总而言之,在 RAG 流程中使用多工具可以提高效度、待校正数据以及准确性和同期上下文。
使用RAG创建多工具编排
现在,我们将通过一个实际示例,使用医学问答数据集创建一个多工具 RAG 系统。具体过程是,我们将一个问答数据集嵌入到 Pinecone 中并建立一个系统。该模型包含一个网页搜索工具和一个基于 Pinecone 的搜索工具。以下是此过程的一些步骤和代码示例。
加载依赖项和数据集
首先,我们将安装、导入必要的库,最后下载数据集。这需要您对数据处理、嵌入和 Pinecone SDK 有基本的了解。例如:
import os, time, random, string import pandas as pd from tqdm.auto import tqdm from sentence_transformers import SentenceTransformer from pinecone import Pinecone, ServerlessSpec import openai from openai import OpenAI import kagglehub
接下来,我们将下载并加载一个包含医学问答关系的数据集。在代码中,我们使用 Kagglehub 实用程序访问了一个以医学为重点的 QA 数据集:
path = kagglehub.dataset_download("thedevastator/comprehensive-medical-q-a-dataset") DATASET_PATH = path # local path to downloaded files df = pd.read_csv(f"{DATASET_PATH}/train.csv")
对于此示例版本,我们可以取一个子集,即前 2500 行。接下来,我们将在列前添加“Question:”和“Answer:”作为前缀,并将它们合并为一个文本字符串。这将是我们要嵌入的上下文。我们将使用文本进行嵌入。例如:
df = df[:2500] df['Question'] = 'Question: ' + df['Question'] df['Answer'] = ' Answer: ' + df['Answer'] df['merged_text'] = df['Question'] + df['Answer']
合并后的文本行如下:“问题:[医学问题] 答案:[答案]”
问题:谁有患淋巴细胞性脉络丛脑膜炎 (LCM) 的风险?
答案:接触受感染啮齿动物的新鲜尿液、粪便、唾液或筑巢材料后,可能会发生淋巴细胞性脉络丛脑膜炎 (LCMV) 感染。当这些物质直接进入破损的皮肤、鼻子、眼睛或口腔,或者可能通过受感染啮齿动物的咬伤而发生传播。目前尚无人际传播的报道,除了从受感染的母亲到胎儿的垂直传播,以及罕见的通过器官移植传播的情况。
基于数据集创建Pinecone索引
现在数据集已加载,我们将为每个合并的问答字符串生成向量嵌入。我们将使用句子转换器模型“BAAI/bge-small-en”对文本进行编码:
MODEL = SentenceTransformer("BAAI/bge-small-en") embeddings = MODEL.encode(df['merged_text'].tolist(), show_progress_bar=True) df['embedding'] = list(embeddings)
我们将从单个样本 len(embeddings[0])
中获取嵌入维数。在我们的例子中,它是 384。然后,我们将创建一个新的 Pinecone 索引并赋予维数。这是使用 Pinecone Python 客户端完成的:
def upsert_to_pinecone(df, embed_dim, model, api_key, region="us-east-1", batch_size=32): # Initialize Pinecone and create the index if it doesn't exist pinecone = Pinecone(api_key=api_key) spec = ServerlessSpec(cloud="aws", region=region) index_name = 'pinecone-index-' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=10)) if index_name not in pinecone.list_indexes().names(): pinecone.create_index( index_name=index_name, dimension=embed_dim, metric='dotproduct', spec=spec ) # Connect to index index = pinecone.Index(index_name) time.sleep(2) print("Index stats:", index.describe_index_stats()) # Upsert in batches for i in tqdm(range(0, len(df), batch_size), desc="Upserting to Pinecone"): i_end = min(i + batch_size, len(df)) # Prepare input and metadata lines_batch = df['merged_text'].iloc[i:i_end].tolist() ids_batch = [str(n) for n in range(i, i_end)] embeds = model.encode(lines_batch, show_progress_bar=False, convert_to_numpy=True) meta = [ { "Question": record.get("Question", ""), "Answer": record.get("Response", "") } for record in df.iloc[i:i_end].to_dict("records") ] # Upsert to index vectors = list(zip(ids_batch, embeds, meta)) index.upsert(vectors=vectors) print(f"Upsert complete. Index name: {index_name}") return index_name
这就是将我们的数据导入 Pinecone 的过程;用 RAG 的术语来说,这相当于将外部权威知识加载到向量存储中。索引创建完成后,我们会将所有嵌入连同元数据(原始问答文本)一起批量更新插入,以供检索:
index_name = upsert_to_pinecone( df=df, embed_dim=384, model=MODEL, api_key="your-pinecone-api-key" )
这里,每个向量都与其文本和元数据一起存储。Pinecone 索引现在已填充我们特定领域的数据集。
查询Pinecone索引
为了使用索引,我们定义了一个函数,可以通过传入新问题来调用该函数。该函数嵌入查询文本,并调用 index.query
返回最相似的前 k 个文档:
def query_pinecone_index(index, model, query_text): query_embedding = model.encode(query_text, convert_to_numpy=True).tolist() res = index.query(vector=query_embedding, top_k=5, include_metadata=True) print("--- Query Results ---") for match in res['matches']: question = match['metadata'].get("Question", 'N/A') answer = match['metadata'].get("Answer", "N/A") print(f"{match['score']:.2f}: {question} - {answer}") return res
例如,如果我们调用 query_pinecone_index(index, MODEL, "What is the most common treatment for diabetes?")
,我们将看到从数据集中打印出最匹配的问答对。这是流程的检索部分:用户查询被嵌入,查找索引,并返回最接近的文档(以及它们的元数据)。检索到这些上下文后,我们就可以使用它们来帮助构建最终答案。
协调多工具调用
接下来,我们定义模型可以使用的工具。在此流水线中,我们定义了两个工具。一个是网页搜索预览,它是一个通用的网页搜索工具,用于从开放互联网中搜索事实。另一个是 PineconeSearchDocuments
,用于对我们的 Pinecone 索引执行语义搜索。每个工具都定义为一个 JSON 对象,其中包含名称、描述和预期参数。以下是示例:
步骤 1:定义网页搜索工具
该工具使代理能够通过输入自然语言请求来执行网页搜索。有可选的位置元数据,可以增强用户相关性的具体性(例如,特定于该地区的新闻、服务)。
web_search_tool = { "type": "function", "name": "web_search_preview", "function": { "name": "web_search_preview", "description": "Perform a web search for general queries.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query string" }, "user_location": { "type": "object", "properties": { "country": {"type": "string", "default": "IN"}, "region": {"type": "string", "default": "Delhi"}, "city": {"type": "string", "default": "New Delhi"} }}}, "required": ["query"] }} }
因此,当代理需要当前信息或训练数据中未包含的信息时,可以使用它。
步骤 2:定义Pinecone搜索工具
此工具使代理能够在向量数据库(例如 Pinecone)上进行语义搜索,从而使 RAG 系统能够依赖向量之间的点积和角度等语义。
该工具接受查询,并基于向量嵌入返回最相似的文档。
pinecone_tool = { "type": "function", "name": "PineconeSearchDocuments", "function": { "name": "PineconeSearchDocuments", "description": "Search for relevant documents based on the user’s question in the vector database.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "The question to search in the vector database." }, "top_k": { "type": "integer", "description": "Number of top results to return.", "default": 3 } }, "required": ["query"], "additionalProperties": False } } }
当代理需要从包含嵌入上下文的文档中检索特定上下文时,就会使用这种方法。
步骤 3:合并工具
现在我们将两个工具合并成一个列表,并将其传递给代理。
tools = [web_search_tool, pinecone_tool]
因此,每个工具都包含一个定义,用于定义我们的模型在调用时应该传入哪些参数。例如,Pinecone 搜索工具需要自然语言查询字符串,该工具会在内部返回索引中排名前 K 的匹配文档。
除了该工具之外,我们还将包含一组需要处理的用户查询。对于每个查询,模型将确定是调用工具还是直接响应。例如:
queries = [ {"query": "Who won the cricket world cup in 1983?"}, {"query": "What is the most common cause of death in India?"}, {"query": "A 7-year-old boy with sickle cell disease has knee and hip pain... What is the next step in management according to our internal knowledge base?"} ]
流程中的多工具编排
最后,我们执行对话流程,让模型代表它们控制工具。我们为模型提供一个系统提示,指导它按特定顺序使用这些工具。在此示例中,我们的提示告诉模型:“当出现问题时,首先调用网络搜索工具,然后调用 PineconeSearchDocuments
”:
system_prompt = ( "Every time it's prompted with a question, first call the web search tool for results, " "then call `PineconeSearchDocuments` to find relevant examples in the internal knowledge base." )
我们收集消息并使用针对用户的每个查询启用的工具调用响应 API:
for item in queries: input_messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": item["query"]} ] response = openai.responses.create( model="gpt-4o-mini", input=input_messages, tools=tools, parallel_tool_calls=True )
输出:
API 返回一条辅助消息,其中可能包含也可能不包含工具调用。我们会检查 response.output
以查看模型是否调用了任何工具。如果调用了,我们会执行这些调用并将结果包含在对话中。例如,如果模型调用了 PineconeSearchDocuments
,我们的代码会在内部运行 query_pinecone_index(index, MODEL, query)
,获取文档答案,并返回包含此信息的工具响应消息。最后,我们将刷新后的对话发送回模型以获取最终响应。
上述流程展示了多工具编排的工作原理;模型会动态选择与查询相关的工具。正如示例所示,对于像“什么是哮喘?”这样的一般性问题,它可以使用网络搜索工具,但对于与“哮喘”更具体的联系的问题,可能需要使用 Pinecone 上下文,并在此基础上进行构建。
我们在代码循环中进行多次工具调用,所有调用完成后,我们调用 API,让模型根据收到的提示构建“最终”答案。总的来说,我们希望收到一个答案,该答案将来自网络知识的外部事实与来自内部知识文档的上下文结合起来,并根据我们的指示进行确认。
您可以在此处参考完整代码。
小结
使用 RAG 的多工具编排功能,可以创建一个功能强大且选项丰富的问答系统。将模型生成与检索工具结合使用,使我们能够充分利用模型的自然语言理解能力和外部数据集的事实准确性。在我们的用例中,我们对一个医学问答的 Pinecone 向量索引进行了地面实况测试,并能够将网络搜索或该索引作为选项调用。通过这种方式,我们的模型能够更加基于实际数据,并能够回答原本无法回答的问题。
在实践中,这种 RAG 流程能够提供更高的答案准确性和相关性,因为该模型可以引用最新的来源,涵盖细分领域知识,并最大限度地减少幻觉。未来的迭代可能会包含更高级的检索模式或生态系统中的其他工具,例如使用知识图谱或 API,但核心部分无需进行任何更改。
常见问题
问题 1:RAG 与传统 LLM 相比最大的优势是什么?
答:RAG 允许 LLM 访问外部数据源(例如矢量数据库或 Web),从而生成更准确、更及时、更针对特定领域的响应,而这是传统的“闭卷”模型无法实现的。
问题 2:RAG 流程中最常用的工具有哪些?
答:通常,常用工具包括:– 用于语义检索的矢量数据库(例如 Pinecone、FAISS 或 Weaviate)。– 使用 API 进行实时 Web 信息搜索。– 提供知识图谱、SQL 数据库或文档存储查询功能的自定义 API 或函数。
问题 3:RAG 可以用于聊天机器人等实时应用程序吗?
答:可以。RAG 非常适合需要动态、基于事实的答案的应用程序,例如客服机器人、医疗或财务助理。因为这些答案基于可检索的文档或事实。
评论留言