arXiv 论文聊天机器人
在这个教程中,我将演示如何使用检索增强生成 (RAG) 构建语义研究论文引擎。此工具可以促进学术研究,使查找相关论文变得更容易,用户还可以通过询问有关推荐论文的问题直接与内容交互。
我将利用 LangChain 作为构建语义引擎的主要框架,以及 OpenAI 的语言模型和 Chroma DB 的向量数据库。为了构建 Copilot 嵌入式 Web 应用程序,我将使用 Chainlit 的 Copilot 功能并结合 Literal AI 的可观察性功能。最后,我们将在应用程序中集成可观察性功能以跟踪和调试对 LLM 的调用。
以下是本教程中将介绍的所有内容的概述:
- 使用 OpenAI、LangChain 和 Chroma DB 开发 RAG 管道,以处理和检索来自 arXiv API 的最相关 PDF 文档。
- 使用 Copilot 开发 Chainlit 应用程序,用于在线论文检索。
- 使用 Literal AI 的 LLM 可观察性功能增强应用程序。
本教程的代码可在此 GitHub 存储库中找到。
1、环境设置
创建一个新的 conda 环境:
conda create -n semantic_research_engine python=3.10
激活环境:
conda activate semantic_research_engine
通过运行以下命令在激活的环境中安装所有必需的依赖项:
pip install -r requirements.txt
2、RAG 管道创建
检索增强生成 (RAG) 是一种流行的技术,可让你使用自己的数据构建自定义对话式 AI 应用程序。
RAG 的原理相当简单:我们将文本数据转换为向量嵌入并将其插入向量数据库。然后,将此数据库链接到大型语言模型 (LLM)。我们正在限制我们的 LLM 从我们自己的数据库中获取信息,而不是依赖先验知识来回答用户查询。
在接下来的几个步骤中,我将详细介绍如何为我们的语义研究论文引擎执行此操作。我们将创建一个名为 rag_test.py 的测试脚本,以了解和构建 RAG 管道的组件。在构建我们的 Copilot 集成 Chainlit 应用程序时,这些组件将被重复使用。
步骤 1
通过注册帐户来保护 OpenAI API 密钥。完成后,在你的项目目录中创建一个 .env 文件并添加你的 OpenAI API 密钥,如下所示:
OPENAI_API_KEY="your_openai_api_key"
此 .env 将包含我们项目的所有 API 密钥。
步骤 2:提取
在此步骤中,我们将创建一个数据库来存储给定用户查询的研究论文。
为此,我们首先需要从 arXiv API 中检索查询的相关论文列表。我们将使用 LangChain 中的 ArxivLoader()
包,因为它抽象了 API 交互,并检索论文以进行进一步处理。
我们可以将这些论文分成更小的块,以确保以后高效处理和相关信息检索。为此,我们将使用 LangChain 中的 RecursiveTextSplitter()
,因为它可以在拆分文档时确保信息的语义保留。
接下来,我们将使用 HuggingFace 中的 sentence transformers
嵌入为这些块创建嵌入。最后,我们将把这些拆分的文档嵌入导入 Chroma DB 数据库以供进一步查询:
# rag_test.py
from langchain_community.document_loaders import ArxivLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
query = "lightweight transformer for language tasks"
arxiv_docs = ArxivLoader(query=query, load_max_docs=3).load()
pdf_data = []
for doc in arxiv_docs:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100)
texts = text_splitter.create_documents([doc.page_content])
pdf_data.append(texts)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-l6-v2")
db = Chroma.from_documents(pdf_data[0], embeddings)
步骤 3:检索和生成
创建特定主题的数据库后,我们可以使用该数据库作为检索器,根据提供的上下文回答用户问题。LangChain 提供了几种不同的检索链,最简单的是 RetrievalQA
链,我们将在本教程中使用。我们将使用 from_chain_type()
方法进行设置,指定模型和检索器。为了将文档集成到 LLM,我们将使用 stuff 链类型,因为它将所有文档填充到单个提示中。
# rag_test.py
from langchain.chains import RetrievalQA
from langchain_openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
qa = RetrievalQA.from_chain_type(llm=llm,
chain_type="stuff",
retriever=db.as_retriever())
question = "how many and which benchmark datasets and tasks were
compared for light weight transformer?"
result = qa({"query": question})
现在我们已经介绍了从 arXiv API 进行在线检索以及 RAG 管道的提取和检索步骤,我们已准备好为语义研究引擎开发 Web 应用程序。
4、Literal AI简介
Literal AI 是一个用于构建生产级 LLM 应用程序的可观察性、评估和分析平台。 Literal AI 提供的一些主要功能包括:
- 可观察性:支持监控 LLM 应用程序,包括对话、中间步骤、提示等。
- 数据集:允许创建混合生产数据和手写示例的数据集。
- 在线评估:支持使用不同的评估器评估线程并在生产中执行。
- 提示游乐场:允许迭代、版本控制和部署提示。
我们将使用可观察性和提示迭代功能来评估和调试使用我们的语义研究论文应用程序进行的调用。
5、使用 Literal AI 的提示游乐场
在创建对话式 AI 应用程序时,开发人员需要迭代多个版本的提示才能找到产生最佳结果的提示。
提示工程在大多数 LLM 任务中起着至关重要的作用,因为微小的修改可能会显著改变语言模型的响应。
Literal AI 的提示游乐场可用于简化此过程。选择模型提供者后,你可以输入初始提示模板,添加任何其他信息,并迭代优化提示以找到最合适的提示。
在接下来的几个步骤中,我们将使用这个操场来为我们的应用程序找到最佳提示。
步骤 1
通过导航到 Literal AI 仪表板创建 API 密钥。注册一个帐户,导航到项目页面,然后创建一个新项目。每个项目都有其唯一的 API 密钥。在“设置”选项卡上,你将在 API 密钥部分找到你的 API 密钥。将其添加到你的 .env 文件中:
LITERAL_API_KEY="your_literal_api_key"
步骤 2
在左侧边栏中,单击“提示”,然后导航到“新提示”。这应该会打开一个新的提示创建会话。
进入游乐场后,在左侧边栏的“模板”部分中添加一条新的系统消息。括号中的所有内容都将添加到变量中,并被视为提示中的输入:
You are a helpful assistant. Use provided {{context}} to answer user
{{question}}. Do not use prior knowledge.
Answer:
在右侧边栏中,可以提供 OpenAI API 密钥。选择模型、温度和最大长度等参数来完成提示。
对提示版本满意后,单击保存。系统将提示你输入提示的名称和可选描述。我们可以将此版本添加到我们的代码中。在名为 search_engine.py
的新脚本中,添加以下代码:
#search_engine.py
from literalai import LiteralClient
from dotenv import load_dotenv
load_dotenv()
client = LiteralClient()
# This will fetch the champion version, you can also pass a specific version
prompt = client.api.get_prompt(name="test_prompt")
prompt = prompt.to_langchain_chat_prompt_template()
prompt.input_variables = ["context", "question"]
Literal AI 允许你使用版本功能保存提示的不同运行。你还可以查看每个版本与前一个版本有何不同。默认情况下,会提取冠军版本。如果你想将某个版本更改为冠军版本,可以在操场中选择它,然后单击“Promote”。
添加上述代码后,我们将能够在 Literal AI 仪表板中查看特定提示的生成(稍后会详细介绍)。
6、Chainlit Copilot
Chainlit 是一个开源 Python 包,旨在构建可用于生产的对话式 AI 应用程序。它为多个事件(聊天开始、用户消息、会话恢复、会话停止等)提供装饰器。你可以查看这篇文章以获得更详细的解释。
具体来说,在本教程中,我们将重点介绍如何使用 Chainlit 为我们的 RAG 应用程序构建软件 Copilot。Chainlit Copilot 在应用程序中提供上下文指导和自动化用户操作。
7、构建Copilot
出于多种原因,在你的应用程序网站中嵌入副驾驶很有用。我们将为我们的语义研究论文引擎构建一个简单的 Web 界面,并在其中集成一个Copilot。这个Copilot将具有一些不同的功能,但以下是最突出的功能:
- 它将嵌入我们网站的 HTML 文件中。
- Copilot将能够代表用户采取行动。假设用户要求提供有关特定主题的在线研究论文。这些可以显示在模态中,我们可以配置我们的副驾驶以自动执行此操作,而无需用户输入。
在接下来的几个步骤中,我将详细介绍如何使用 Chainlit 为我们的语义研究引擎创建软件副驾驶。
步骤 1
第一步涉及为我们的 chainlit 应用程序编写逻辑。我们将在我们的用例中使用两个 chainlit 装饰器函数: @cl.on_chat_start
和 @cl.on_message
。我们将把在线搜索和 RAG 管道中的逻辑添加到这些函数中。需要记住以下几点:
@cl.on_chat_start
包含在新用户会话开始时需要执行的所有代码。@cl.on_message
包含用户发送新消息时需要执行的所有代码。
我们将在 @cl.on_chat_start
装饰器中封装从接收研究主题到创建数据库和提取文档的整个过程。在 search_engine.py
脚本中,导入所有必要的模块和库:
# search_engine.py
import chainlit as cl
from langchain_community.document_loaders import ArxivLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
现在让我们添加 @cl.on_chat_start
装饰器的代码。我们将使此函数异步,以确保多个任务可以同时运行:
# search_engine.py
# contd.
@cl.on_chat_start
async def retrieve_docs():
# QUERY PORTION
arxiv_query = None
# Wait for the user to send in a topic
while arxiv_query is None:
arxiv_query = await cl.AskUserMessage(
content="Please enter a topic to begin!", timeout=15).send()
query = arxiv_query['output']
# ARXIV DOCS PORTION
arxiv_docs = ArxivLoader(query=arxiv_query, load_max_docs=3).load()
# Prepare arXiv results for display
arxiv_papers = [f"Published: {doc.metadata['Published']} \n "
f"Title: {doc.metadata['Title']} \n "
f"Authors: {doc.metadata['Authors']} \n "
f"Summary: {doc.metadata['Summary'][:50]}... \n---\n"
for doc in arxiv_docs]
await cl.Message(content=f"{arxiv_papers}").send()
await cl.Message(content=f"Downloading and chunking articles for {query} "
f"This operation can take a while!").send()
# DB PORTION
pdf_data = []
for doc in arxiv_docs:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=100)
texts = text_splitter.create_documents([doc.page_content])
pdf_data.append(texts)
llm = ChatOpenAI(model='gpt-3.5-turbo',
temperature=0)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-l6-v2")
db = Chroma.from_documents(pdf_data[0], embeddings)
# CHAIN PORTION
chain = RetrievalQA.from_chain_type(llm=llm,
chain_type="stuff",
retriever=db.as_retriever(),
chain_type_kwargs={
"verbose": True,
"prompt": prompt
}
)
# Let the user know that the pipeline is ready
await cl.Message(content=f"Database creation for `{query}` complete. "
f"You can now ask questions!").send()
cl.user_session.set("chain", chain)
cl.user_session.set("db", db)
让我们来看看我们在这个函数中包装的代码:
- 提示用户查询:我们首先让用户发送一个研究主题。在用户提交主题之前,此功能不会继续。
- 在线搜索:我们使用 LangChain 的 arXiv 搜索包装器检索相关论文,并以可读格式显示每个条目的相关字段。
- 提取:接下来,我们对文章进行分块并创建嵌入以供进一步处理。分块确保有效处理大型论文。之后,从处理后的文档块和嵌入中创建 Chroma 数据库。
- 检索:最后,我们建立了一个
RetrievalQA
链,将 LLM 和新创建的数据库集成为检索器。我们还提供了之前在 Literal AI 游乐场中创建的提示。 - 存储变量:我们使用
cl.user_session.set
功能将链和数据库存储在变量中,以便以后重复使用。 - 用户消息:我们在整个函数中使用 Chainlit 的
cl.Message
功能与用户交互。
现在让我们定义我们的 @cl.on_message
函数,并添加我们的 RAG 管道的生成部分。用户应该能够从提取的论文中提出问题,应用程序应该提供相关的答案:
@cl.on_message
async def retrieve_docs(message: cl.Message):
question = message.content
chain = cl.user_session.get("chain")
db = cl.user_session.get("db")
# Create a new instance of the callback handler for each invocation
cb = client.langchain_callback()
variables = {"context": db.as_retriever(search_kwargs={"k": 1}),
"query": question}
database_results = await chain.acall(variables,
callbacks=[cb])
results = [f"Question: {question} "
f"\n Answer: {database_results['result']}"]
await cl.Message(results).send()
以下是上述函数中代码的分解说明:
- 链和数据库检索:我们首先从用户会话中检索先前存储的链和数据库。
- LangChain 回调集成:为了确保我们可以跟踪我们的提示和使用特定提示版本的所有生成,我们需要在调用链时从 Literal AI 添加 LangChain 回调处理程序。我们使用 LiteralClient 实例中的
langchain_callback()
方法创建回调处理程序。此回调将自动将所有 LangChain 交互记录到 Literal AI。 - 生成:我们定义变量:数据库作为检索的上下文,用户的问题作为查询,还指定检索顶部结果 (k: 1)。最后,我们使用提供的变量和回调调用链。
步骤2
第2步涉及将副驾驶嵌入到我们的应用程序网站中。我们将创建一个简单的网站进行演示。创建一个 index.html 文件并向其中添加以下代码:
<!DOCTYPE html>
<html>
<head>
<title>Semantic Search Engine</title>
</head>
<body>
<!-- ... -->
<script src="http://localhost:8000/copilot/index.js"></script>
<script>
window.mountChainlitWidget({
chainlitServer: "http://localhost:8000",
});
</script>
</body>
在上面的代码中,我们通过指向托管我们应用程序的 Chainlit 服务器的位置,将 copilot 嵌入到我们的网站中。 window.mountChainlitWidget
会在你网站的右下角添加一个浮动按钮。单击它将打开 Copilot。为了确保我们的 Copilot 正常工作,我们需要先运行我们的 Chainlit 应用程序。进入项目目录并运行:
chainlit run search_engine.py -w
上面的代码在 https://localhost:8000
上运行应用程序。接下来,我们需要托管我们的应用程序网站。
在浏览器中打开 index.html 脚本不起作用。相反,我们需要创建一个 HTTPS 测试服务器。你可以通过不同的方式执行此操作,但一种简单的方法是使用 npx。npx 包含在 npm(Node 包管理器)中,npm 随 Node.js 一起提供。要获取 npx,你只需在系统上安装 Node.js。进入目录并运行:
npx http-server
运行上述命令将在 https://localhost:8080
上为我们的网站提供服务。导航到该地址,你将能够看到一个嵌入了 copilot 的简单 Web 界面。
由于我们将使用 @cl.on_chat_start
包装函数来欢迎用户,因此我们可以在 Chainlit 配置中将 show_readme_as_default
设置为 false
以避免闪烁。可以在项目目录中的 .chainlit/config.toml
找到你的配置文件。
步骤 3
要仅在 Copilot 内执行代码,我们可以添加以下内容:
@cl.on_message
async def retrieve_docs(message: cl.Message):
if cl.context.session.client_type == "copilot":
# code to be executed only inside the Copilot
此块内的任何代码仅在你从 Copilot 内与应用程序交互时才会执行。例如,如果你在托管于 https://localhost:8000
的 Chainlit 应用程序界面上运行查询,则上述 if 块内的代码将不会被执行,因为它期望客户端类型是 Copilot。
这是一个很有用的功能,你可以使用它来区分直接在 Chainlit 应用程序中执行的操作和通过 Copilot 界面发起的操作。通过这样做,你可以根据请求的上下文定制应用程序的行为,从而实现更动态、响应更快的用户体验。
步骤4
Copilot 可以调用你网站上的函数。这对于代表用户执行操作非常有用,例如打开模式、创建新文档等。我们将修改我们的 Chainlit 装饰器函数包含两个新的 Copilot 函数。
我们需要在 index.html 文件中指定当我们的 Chainlit 后端应用程序中的 Copilot 函数被激活时前端应该如何响应。具体反应将根据应用程序而有所不同。对于我们的语义研究论文引擎,每当需要显示相关论文或数据库答案以响应用户查询时,我们都会在前端生成弹出通知。
我们将在我们的应用程序中创建两个 Copilot 函数:
showArxivResults
:此函数将负责显示 arxiv API 根据用户查询提取的在线结果。showDatabaseResults
:此函数将负责显示根据用户问题从我们的提取数据库中提取的结果。
首先,让我们在 search_engine.py
脚本中设置后端逻辑并修改 @cl.on_chat_start
函数:
@cl.on_chat_start
async def retrieve_docs():
if cl.context.session.client_type == "copilot":
# same code as before
# Trigger popup for arXiv results
fn_arxiv = cl.CopilotFunction(name="showArxivResults",
args={"results": "\n".join(arxiv_papers)})
await fn_arxiv.acall()
# same code as before
在上面的代码中,定义了一个名为 showArxivResults
的 Copilot 函数,并异步调用。此函数旨在直接在 Copilot 界面中显示格式化的 arXiv 论文列表。函数签名非常简单:我们指定函数的名称以及它将返回的参数。我们将在 index.html 文件中使用此信息来创建弹出窗口。
接下来,我们需要使用第二个 Copilot 函数修改我们的 @cl.on_message
函数,该函数将在用户根据所摄取的论文提出问题时执行:
@cl.on_message
async def retrieve_docs(message: cl.Message):
if cl.context.session.client_type == "copilot":
# same code as before
# Trigger popup for database results
fn_db = cl.CopilotFunction(name="showDatabaseResults",
args={"results": "\n".join(results)})
await fn_db.acall()
# same code as before
在上面的代码中,我们定义了第二个名为 showDatabaseResults
的 Copilot 函数以异步调用。此函数的任务是在 Copilot 界面中显示从数据库检索到的结果。函数签名指定了函数的名称以及它将返回的参数。
步骤 5
我们现在将编辑 index.html 文件以包含以下更改:
- 添加两个 Copilot 函数。
- 指定当触发两个 Copilot 函数中的任何一个时,我们的网站上会发生什么。我们将创建一个弹出窗口来显示来自应用程序后端的结果。
- 为弹出窗口添加简单的样式。
首先,我们需要为我们的 Copilot 函数添加事件监听器。在 index.html 文件的 <script>
部分,添加如下代码:
<script>
// previous code
window.addEventListener("chainlit-call-fn", (e) => {
const { name, args, callback } = e.detail;
if (name === "showArxivResults") {
document.getElementById("arxiv-result-text").innerHTML =
args.results.replace(/\n/g, "<br>");
document.getElementById("popup").style.display = "flex";
if (callback) callback();
} else if (name === "showDatabaseResults") {
document.getElementById("database-results-text").innerHTML =
args.results.replace(/\n/g, "<br>");
document.getElementById("popup").style.display = "flex";
if (callback) callback();
}
});
</script>
以下是上述代码的分解说明:
- 包括显示 (
showPopup()
) 和隐藏 (hidePopup()
) 弹出模式的函数。 - 为
chainlit-call-fn
事件注册了一个事件监听器,当调用 Copilot 函数 (showArxivResults
或showDatabaseResults
) 时会触发该事件。 - 检测到事件后,监听器会检查调用的 Copilot 函数的名称。根据函数名称,它会使用该函数提供的结果更新弹出窗口中相关部分的内容。它会将换行符 (
\n
) 替换为 HTML 换行符 (<br>
),以正确格式化文本以进行 HTML 显示。 - 更新内容后,会显示弹出模式 (
display: "flex"
),让用户看到结果。可以使用关闭按钮隐藏模式,该按钮调用hidePopup()
函数。
接下来,我们需要定义上面指定的弹出模式。我们可以通过将以下代码添加到 index.html 脚本的 <script>
标记来实现此目的:
<div id="popup" class="popup">
<span class="close-btn" onclick="hidePopup()">×</span>
<div class="arxiv-results-wrapper">
<h1>Arxiv Results</h1>
<p id="arxiv-result-text">Online results will be displayed here.</p>
</div>
<div class="database-results-wrapper">
<h1>Database Results</h1>
<p id="database-results-text">Database results will be displayed here.</p>
</div>
</div>
我们还要为弹出窗口添加一些样式。编辑 index.html 文件的 标签:
<style>
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
.close-btn {
position: absolute;
top: 10px;
right: 20px;
font-size: 24px;
cursor: pointer;
}
.popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
width: 40%;
flex-direction: column;
gap: 50px;
}
p {
color: #00000099;
}
</style>
8、启动应用程序
现在我们已经将 Copilot 逻辑添加到 Chainlit 应用程序中,我们可以运行应用程序和网站。要使 Copilot 正常工作,我们的应用程序必须已在运行。在项目目录中打开一个终端,然后运行以下命令启动 Chainlit 服务器:
chainlit run search_engine.py -h
在新终端中,使用以下命令启动网站:
npx http-server
9、使用 Literal AI 实现LLM 可观察性
通常需要将可观察性功能集成到生产级应用程序中,例如由 Copilot 运行的语义研究引擎,以确保应用程序在生产环境中的可靠性。我们将在 Literal AI 框架中使用它。
对于任何 Chainlit 应用程序,Literal AI 都会自动开始监控应用程序并将数据发送到 Literal AI 平台。我们在 search_engine.py 脚本中创建提示时已经启动了 Literal AI 客户端。现在,每次用户与我们的应用程序交互时,我们都会在 Literal AI 仪表板中看到日志。
9.1 仪表板
导航到 Literal AI 仪表板,从左侧面板中选择项目,然后单击可观察性。你将看到以下功能的日志。
9.2 线索
线索代表助手和用户之间的对话会话。你应该能够看到用户在应用程序中进行的所有对话。
展开特定对话将提供关键详细信息,例如每个步骤所花费的时间、用户消息的详细信息以及详细说明所有步骤的基于树的视图。你还可以将对话添加到数据集。
9.3 运行
运行是代理或链采取的一系列步骤。这提供了每次执行链或代理时所采取的所有步骤的详细信息。使用此选项卡,我们可以获得每个用户查询的输入和输出。
你可以展开运行,这将提供更多详细信息。同样,可以将此信息添加到数据集中。
9.4 生成
生成包含发送到 LLM 的输入及其完成。这提供了关键详细信息,包括用于完成的模型、令牌计数以及请求完成的用户(如果您配置了多个用户会话)。
10、Literal AI 中的提示评估
自从我们添加了 LangChain 集成以来,我们可以跟踪应用程序代码中创建和使用的每个提示的生成和线索。因此,每次为用户查询调用链时,都会在 Literal AI 仪表板中添加针对它的日志。这有助于查看哪些提示负责特定的生成,并比较不同版本的性能。
11、结束语
在本教程中,我演示了如何使用 LangChain、OpenAI 和 ChromaDB 的 RAG 功能创建语义研究论文引擎。
此外,我还展示了如何为该引擎开发 Web 应用程序,集成 Copilot 和 Literal AI 的可观察性功能。通常需要结合评估和可观察性来确保在现实世界的语言模型应用程序中获得最佳性能。
此外,Copilot 可以成为不同软件应用程序的极其有用的功能,本教程可以作为了解如何为您的应用程序设置它的一个很好的起点。
你可以在我的 GitHub 上找到本教程的代码。
原文链接:Building an Observable arXiv RAG Chatbot with LangChain, Chainlit, and Literal AI
汇智网翻译整理,转载请标明出处