前言
Speckly 是一个基于 检索增强生成 (RAG) 技术的智能问答机器人,它能像一位经验丰富的工程师,理解你的问题,并从 Speckle 文档中精准地找到答案。更厉害的是,它甚至可以帮你生成代码片段! 🚀
本文将详细介绍 Speckly 的完整开发流程,涵盖从创建图管道到搭建服务器,再到设计用户界面的所有环节,最终实现一个可交互的智能问答系统。
您将学习如何:
- 构建处理用户提问和文档信息的核心逻辑(图管道)。
- 搭建本地服务器,模拟 Speckly 的线上运行环境。
- 使用 Streamlit 和 Gradio 设计用户友好的交互界面。
通过学习本项目,您将掌握在部署模型到生产环境之前进行本地测试的方法,并了解如何构建简洁易用的用户界面。
步骤 1:导入 API 密钥
首先,我们从 .env 文件中导入 API 密钥。另外,我们还可以使用 LangSmith 设置跟踪。
import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv()) 重要提示:如果无法加载 API 密钥,请检查此行Getting the api keys from the .env file
os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')
os.environ['LANGCHAIN_API_KEY'] = os.getenv('LANGCHAIN_API_KEY')
Langsmith Tracing
os.environ['LANGCHAIN_TRACING_V2'] = os.getenv('LANGCHAIN_TRACING_V2')
os.environ['LANGCHAIN_ENDPOINT'] = os.getenv('LANGCHAIN_ENDPOINT')
os.environ['LANGCHAIN_PROJECT'] = os.getenv('LANGCHAIN_PROJECT')
Fire Crawl API
os.environ['FIRE_API_KEY']=os.getenv('FIRE_API_KEY')
以下是一个示例 .env 文件。请获取您的 API 密钥(如果您没有),并将它们粘贴在字符串之间。我在第一篇博文中详细描述了这一点。
OPENAI_API_KEY=''
LANGCHAIN_API_KEY=''
LANGCHAIN_TRACING_V2='true'
LANGCHAIN_ENDPOINT='https://api.smith.langchain.com'
LANGCHAIN_PROJECT=''
步骤 2:加载文档
我们将使用 Mendable.ai 创建的名为 FireCrawl 的产品,它可以将网站转换为对大语言模型友好的文档。这正是我们想要的。我们将抓取 Speckle 的开发者文档,并将所有页面和子页面转换为文档列表。您需要一个 API 密钥才能在加载器函数中使用。
我创建了 DocumentLoader 类,它将 API 密钥作为字符串输入,并使用 get_docs 函数,该函数将 URL 作为输入,并提供一个文档列表(包括元数据)作为输出。
from typing import List
from langchain_community.document_loaders import FireCrawlLoader
from document import Document
class DocumentLoader:
def __init__(self, api_key: str):
self.api_key = api_key
def get_docs(self, url: str) -> List[Document]:
"""
使用 FireCrawlLoader 从指定的 URL 检索文档。
Args:
url (str): 要抓取文档的 URL。
Returns:
List[Document]: 包含检索到的内容的 Document 对象列表。
"""
loader = FireCrawlLoader(
api_key=self.api_key, url=url, mode="crawl"
)
raw_docs = loader.load()
docs = [Document(page_content=doc.page_content, metadata=doc.metadata) for doc in raw_docs]
return docs
就我而言,我已经抓取了文档,并将文档保存在本地,这样我就不会重复这个过程并浪费我的积分了。第一次使用时,您可以使用 get_docs 函数;之后,您可以直接加载文档。
import pickle
# 从本地文件加载已抓取并保存的文档
with open("crawled_docs/saved_docs.pkl", "rb") as f:
saved_docs = pickle.load(f)
步骤 3:创建向量存储和检索器
现在我们有了文档,我们希望将它们分成更小的部分,并将嵌入存储在一个开源向量存储中以供检索。我们将依赖 OpenAI 嵌入模型和 FAISS 向量存储。您还可以选择提供一个路径,以便将向量存储保存在本地。
from typing import List, Optional
from langchain_openai import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
def create_vector_store(docs, store_path: Optional[str] = None) -> FAISS:
"""
从文档列表创建 FAISS 向量存储。
Args:
docs (List[Document]): 包含要存储的内容的 Document 对象列表。
store_path (Optional[str]): 用于在本地存储向量存储的路径。如果为 None,则不会存储向量存储。
Returns:
FAISS: 包含文档的 FAISS 向量存储。
"""
# 创建文本拆分器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
)
texts = text_splitter.split_documents(docs)
# 嵌入对象
embedding_model = OpenAIEmbeddings()
# 创建 FAISS 向量存储
store = FAISS.from_documents(texts, embedding_model)
# 如果提供了路径,则将向量存储保存在本地
if store_path:
store.save_local(store_path)
return store
# 创建向量存储
store = create_vector_store(saved_docs)
# 创建检索器
retriever = store.as_retriever()
步骤 4:创建用于响应生成的检索链
现在,我们将创建 create_generate_chain 函数来创建一个响应生成链。该函数首先使用一个 generate_template 来提供有关该过程的详细说明。这个模板有两个占位符:{context} 用于存储相关信息,{input} 用于存储问题。然后,使用 LangChain 中的 PromptTemplate 模块,它接受两个变量:template = generate_template 和 input_variables = ["context", "input"]。最后一步是使用 generate_prompt、大语言模型和 StrOutputParser() 创建 generate_chain。
# generate_chain.py
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
def create_generate_chain(llm):
"""
创建一个用于回答代码相关问题的生成链。
Args:
llm (LLM): 用于生成响应的语言模型。
Returns:
一个可调用函数,它接受上下文和问题作为输入,并返回字符串响应。
"""
generate_template = """
你是一个名为 Speckly 的乐于助人的代码助手。用户向你提供了一个与代码相关的问题,其内容由以下上下文部分表示(由 <context></context> 分隔)。
使用这些来回答最后的问题。
这些文件涉及 Speckle 开发者文档。你可以假设用户是土木工程师、建筑师或软件开发人员。
如果你不知道答案,就说你不知道。不要试图编造答案。
如果问题与上下文无关,请礼貌地回复说你只回答与上下文相关的问题。
提供尽可能详细的答案,并使用 Python(默认)生成代码,除非用户在问题中特别说明。
<context>
{context}
</context>
<question>
{input}
</question>
"""
generate_prompt = PromptTemplate(template=generate_template, input_variables=["context", "input"])
# 创建生成链
generate_chain = generate_prompt | llm | StrOut