07 工具的使用 检索增强生成 连接数据库 |豆包MarsCode AI刷题

135 阅读14分钟

工具

LangChain通过提供一个统一的框架来集成功能的具体实现。在这个框架中,每个功能都被封装成一个工具。每个工具都有自己的输入和输出,以及处理这些输入和生成输出的方法。

当代理接收到一个任务时,它会根据任务的类型和需求,通过大模型的推理,来选择合适的工具处理这个任务。将工具生成的输出经过大模型的推理,用作其他工具的输入,或者是最终的结果返回给用户。

LangChain支持的工具

支持的工具可以参考该网址

python.langchain.com.cn/docs/module…

image.png

ArXiv开发科研助理

以arXiv工具举例

from langchain.chat_models import ChatOpenAI
from langchain.agents import load_tools, initialize_agent, AgentType

# 初始化模型和工具
llm = ChatOpenAI(temperature=0.0)
tools = load_tools(
    ["arxiv"],
)
# 初始化链
agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)
# 运行链
agent_chain.run("介绍一下2005.14165这篇论文的创新点?")

ZERO_SHOT_REACT_DESCRIPTION这个Agent的提示

"prompts": [

"Answer the following questions as best you can. You have access to the following tools:\n\n

告诉模型,要尽力回答问题,但是可以访问下面的工具。

arxiv: A wrapper around Arxiv.org Useful for when you need to answer questions about Physics, Mathematics, Computer Science, Quantitative Biology, Quantitative Finance, Statistics, Electrical Engineering, and Economics from scientific articles on arxiv.org. Input should be a search query.\n\n

Use the following format:\n\n

思维过程

Question: the input question you must answer\n (问题:需要回答的问题)

Thought: you should always think about what to do\n (思考:应该总是思考下一步做什么)

Action: the action to take, should be one of [arxiv]\n (行动:从具体工具列表中选择行动——这里只有arxiv一个工具)

Action Input: the input to the action\n (行动的输入:输入工具的内容)

Observation: the result of the action\n... (观察:工具返回的结果)

(this Thought/Action/Action Input/Observation can repeat N times)\n (上面 Thought/Action/Action Input/Observation 的过程将重复N次)

Thought: I now know the final answer\n (现在我知道最终答案了)

Final Answer: the final answer to the original input question\n\n (原始问题的最终答案)

image.png

RAG检索增强生成

Retrieval-Augmented Generation,即检索增强生成,他结合了检索和生成的能力。他可以将传统的语言生成模型与外部知识库相结合,使得模型在生成响应时从知识库中检索相关信息,从而增强模型的生成能力,使其能够产生更准确且有根据的内容。

RAG 的工作原理可以概括为几个步骤。

  1. 检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如ChromaDB、Faiss这样的向量数据库。
  2. 上下文编码:找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
  3. 生成:大模型使用编码的上下文信息,模型生成输出(答案)。

文档加载

LangChain 提供了多种类型的文档加载器,并与该领域的其他主要提供商如 Airbyte 和 Unstructured.IO 进行了集成。

image.png

文本转换

文档加载后,需要对文本进行处理,最常见的文本转换就是把长文档分割成更小的块(或者是片,或者是节点),以适合模型的上下文窗口。

文本分割器

文本分割的一大痛点是要将语义相关的文本片段保留,尽可能不因为分割改变原本的意思。

LangChain中,文本分割器的工作原理如下:

  1. 将文本分成小的、具有语义意义的块(通常是句子)。
  2. 开始将这些小块组合成一个更大的块,直到达到一定的大小。
  3. 一旦达到该大小,一个块就形成了,可以开始创建新文本块。这个新文本块和刚刚生成的块要有一些重叠,以保持块之间的上下文。

所以按照文本的分割方式,分割块的大小以及块之间重叠的文本长度,分为以下几种分割器

image.png 那么怎么选择合适的分割器呢,应该考虑这些因素

首先是LLM的具体限制,比如,GPT-3.5-turbo支持的上下文窗口为4096个令牌。为了保证不超过这个限制,我们可以预留约2000个令牌作为输入提示,留下约2000个令牌作为返回的消息。这样,如果你提取出了五个相关信息块,那么每个片的大小不应超过400个令牌。

若需完成拼写检查、语法检查和文本分析可能需要识别文本中的单个单词或字符或者垃圾邮件识别、查找剽窃和情感分析类任务,以及搜索引擎优化、主题建模中常用的关键字提取任务,则需要较小的分块。

若需要了解文本的任务,比如机器翻译、文本摘要和问答任务需要理解文本的整体含义。而自然语言推理、问答和机器翻译需要识别文本中不同部分之间的关系。还有创意写作,则使用较大的分块。

最后,你也要考虑所分割的文本的性质。例如,如果文本结构很强,如代码或HTML,可能需要较大的块,如果文本结构较弱,如小说或新闻文章,可能需要较小的块。

其他形式的文本转换

LangChain在处理文档中还有一些常用的功能:

  1. 过滤冗余的文档:使用 EmbeddingsRedundantFilter 工具可以识别相似的文档并过滤掉冗余信息,节省存储空间并提高检索效率。
  2. 翻译文档:与工具 doctran 进行集成,可以将文档从一种语言翻译成另一种语言。
  3. 提取元数据:与工具 doctran 进行集成,可以从文档内容中提取关键信息(如日期、作者、关键字等),并将其存储为元数据。
  4. 转换对话格式:与工具 doctran 进行集成,可以将对话式的文档内容转化为问答(Q/A)格式,从而更容易地提取和查询特定的信息或回答。在处理如访谈、对话或其他交互式内容时非常有用。

文本嵌入

形成文本块以后使用LLM做嵌入,将文本准换成数值表示。

Embeddings 创建的向量表示,让我们可以在向量空间中思考文本,并执行语义搜索之类的操作,在向量空间中查找最相似的文本片段。

image.png

LangChain中的Embeddings 类是设计用于与文本嵌入模型交互的类。这个类为所有这些提供者提供标准接口。

Embedding类有embed_documents 方法和embed_query 方法

embed_documents 方法,为文档创建嵌入。这个方法接收多个文本作为输入。

embed_query 方法,为查询创建嵌入。这个方法只接收一个文本作为输入,通常是用户的搜索查询。

# 初始化Embedding类
from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()

embeddings = embeddings_model.embed_documents(
    [
        "您好,有什么需要帮忙的吗?",
        "哦,你好!昨天我订的花几天送达",
        "请您提供一些订单号?",
        "12345678",
    ]
)

embedded_query = embeddings_model.embed_query("刚才对话中的订单号是多少?")

存储嵌入

加速计算嵌入的过程,可以将计算出的嵌入存储或临时缓存,这样在下次需要它们时,就可以直接读取,无需重新计算。

缓存存储

CacheBackedEmbeddings是一个支持缓存的嵌入式包装器,它可以将嵌入缓存在键值存储中。具体操作是:对文本进行哈希处理,并将此哈希值用作缓存的键。

# 导入内存存储库,该库允许我们在RAM中临时存储数据
from langchain.storage import InMemoryStore

# 创建一个InMemoryStore的实例
store = InMemoryStore()

#OpenAIEmbeddings用于生成嵌入,CacheBackedEmbeddings允许缓存这些嵌入
from langchain.embeddings import OpenAIEmbeddings, CacheBackedEmbeddings

# 创建一个OpenAIEmbeddings的实例,这将用于实际计算文档的嵌入
underlying_embeddings = OpenAIEmbeddings()

# 创建一个CacheBackedEmbeddings的实例。
# 这将为underlying_embeddings提供缓存功能,嵌入会被存储在上面创建的InMemoryStore中。
# 我们还为缓存指定了一个命名空间,以确保不同的嵌入模型之间不会出现冲突。
embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings,  # 实际生成嵌入的工具
    store,  # 嵌入的缓存位置
    namespace=underlying_embeddings.model  # 嵌入缓存的命名空间
)

# 使用embedder为两段文本生成嵌入。
# 结果,即嵌入向量,将被存储在上面定义的内存存储中。
embeddings = embedder.embed_documents(["你好", "智能鲜花客服"])

这里使用的是InMemoryStore在内存中缓存嵌入主要用于单元测试或原型设计,如果需要长期存储嵌入,最好不要使用这个。除此之外,还有LocalFileStore和RedisStore。

向量存储

这是一种更常见的存储方式。angChain支持非常多种向量数据库,其中有很多是开源的,也有很多是商用的。比如Elasticsearch、Faiss、Chroma和Qdrant等等。

应该根据具体需求进行选择。

数据检索

在LangChain中,Retriever,也就是检索器,是数据检索模块的核心入口,它通过非结构化查询返回相关的文档。

向量存储检索器

使用VectorstoreIndexCreator来创建索引实现数据检索功能,并在索引的query方法中,通过vectorstore类的as_retriever方法,把向量数据库直接作为检索器完成检索任务

# 导入文档加载器模块,并使用TextLoader来加载文本文件
from langchain.document_loaders import TextLoader
loader = TextLoader('LangChainSamples/OneFlower/易速鲜花花语大全.txt', encoding='utf8')

# 使用VectorstoreIndexCreator来从加载器创建索引
from langchain.indexes import VectorstoreIndexCreator
index = VectorstoreIndexCreator().from_loaders([loader])

# 定义查询字符串, 使用创建的索引执行查询
query = "玫瑰花的花语是什么?"
result = index.query(query)
print(result) # 打印查询结果

同样的,可以用下面的代码,来显式地指定索引创建器的vectorstore、embedding以及text_splitter,并把它们替换成所需要的工具,比如另外一种向量数据库或者别的Embedding模型。

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
index_creator = VectorstoreIndexCreator(
    vectorstore_cls=Chroma,
    embedding=OpenAIEmbeddings(),
    text_splitter=CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
)

各种类型的检索器

除了向量存储检索器,LangChain中还提供很多种其他的检索工具。

image.png

连接数据库

数据库的使用非常具有工程意义,如何使用自然语言而不是SQL语句查询数据库。下图给了一个以LLM为驱动引擎,从自然语言的(模糊)询问,到自然语言的查询结果输出的流程。

image.png

  • 提出问题:用自然语言提出一个问题,比如,“去年的总销售额是多少”
  • LLM理解并转译:LLM解析问题,理解其背后的意图和所需的信息。然后模型会根据解析的内容,生成相应的SQL查询语句,例如 “SELECT SUM(sales) FROM sales_data WHERE year = 'last_year';”
  • 执行SQL查询:生成的SQL查询语句会被发送到相应的数据库进行执行。数据库处理这个查询,并返回所需的数据结果
  • LLM接收并解释结果:LLM接收数据库返回查询结果,然后将其转化为更容易被人类理解的答案格式
  • 提供答案:最后,LLM将结果转化为自然语言答案,并返回给用户。例如“去年的总销售额为1,000,000元”

下面,使用一个案例来解释,鲜花只能系统中的所有业务数据都存储在数据库中,而目标则是通过自然语言来为销售的每一种鲜花数据创建各种查询。这样,无论是员工还是顾客,当他们想了解某种鲜花的价格时,都可以快速地生成适当的查询语句。

创建数据库表

本例使用SQLite作为我们的示例数据库。

它提供了轻量级的磁盘文件数据库,并不需要单独的服务器进程或系统,应用程序可以直接与数据库文件交互。同时,它也不需要配置、安装或管理,非常适合桌面应用、嵌入式应用或初创企业的简单需求。它也特别适用于那些不需要大型数据库系统带来的全部功能,但仍然需要数据持久性的应用程序,如移动应用或小型Web应用。sqlite3库,则是Python内置的轻量级SQLite数据库。通过sqlite3库,Python为开发者提供了一个简单、直接的方式来创建、查询和管理SQLite数据库。

# 导入sqlite3库
import sqlite3

# 连接到数据库
conn = sqlite3.connect('FlowerShop.db')
cursor = conn.cursor()

# 执行SQL命令来创建Flowers表
cursor.execute('''
        CREATE TABLE Flowers (
            ID INTEGER PRIMARY KEY, 
            Name TEXT NOT NULL, 
            Type TEXT NOT NULL, 
            Source TEXT NOT NULL, 
            PurchasePrice REAL, 
            SalePrice REAL,
            StockQuantity INTEGER, 
            SoldQuantity INTEGER, 
            ExpiryDate DATE,  
            Description TEXT, 
            EntryDate DATE DEFAULT CURRENT_DATE 
        );
    ''')

# 插入5种鲜花的数据
flowers = [
    ('Rose', 'Flower', 'France', 1.2, 2.5, 100, 10, '2023-12-31', 'A beautiful red rose'),
    ('Tulip', 'Flower', 'Netherlands', 0.8, 2.0, 150, 25, '2023-12-31', 'A colorful tulip'),
    ('Lily', 'Flower', 'China', 1.5, 3.0, 80, 5, '2023-12-31', 'An elegant white lily'),
    ('Daisy', 'Flower', 'USA', 0.7, 1.8, 120, 15, '2023-12-31', 'A cheerful daisy flower'),
    ('Orchid', 'Flower', 'Brazil', 2.0, 4.0, 50, 2, '2023-12-31', 'A delicate purple orchid')
]

for flower in flowers:
    cursor.execute('''
        INSERT INTO Flowers (Name, Type, Source, PurchasePrice, SalePrice, StockQuantity, SoldQuantity, ExpiryDate, Description) 
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
    ''', flower)

# 提交更改
conn.commit()

# 关闭数据库连接
conn.close()

运行后连接到FlowerShop.db数据库,并创建了一个名为FLowers的新表。并使用一个flowers列表存储五种鲜花的相关数据,并将他们插到Flowers表中。

用Chain查询数据库

通过SQLDatabaseChain来查询数据库。

# 导入langchain的实用工具和相关的模块
from langchain.utilities import SQLDatabase
from langchain.llms import OpenAI
from langchain_experimental.sql import SQLDatabaseChain

# 连接到FlowerShop数据库(之前我们使用的是Chinook.db)
db = SQLDatabase.from_uri("sqlite:///FlowerShop.db")

# 创建OpenAI的低级语言模型(LLM)实例,这里我们设置temperature为0模型输出会更加确定性
llm = OpenAI(temperature=0, verbose=True)

# 创建SQL数据库链实例,它允许我们使用LLM来查询SQL数据库
db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)

# 运行与鲜花运营相关的问题
response = db_chain.run("有多少种不同的鲜花?")
print(response)

response = db_chain.run("哪种鲜花的存货数量最少?")
print(response)

response = db_chain.run("平均销售价格是多少?")
print(response)

response = db_chain.run("从法国进口的鲜花有多少种?")
print(response)

response = db_chain.run("哪种鲜花的销售量最高?")
print(response)

运行结果

image.png 输入的问题被传唤为SQL语句,并且查询数据库表后,得到查询结果之后,又通过LLM把这个结果转换为自然语言。

用Agent查询数据库

相比SQLDatabaseChain,使用 SQL 代理有一些优点

  • 它可以根据数据库的架构以及数据库的内容回答问题(例如它会检索特定表的描述)。
  • 它具有纠错能力,当执行生成的查询遇到错误时,它能够捕获该错误,然后正确地重新生成并执行新的查询。

LangChain使用create_sql_agent函数来初始化代理,通过这个函数创建的SQL代理包含SQLDatabaseToolkit,这个工具箱中包含以下工具:

  • 创建并执行查询
  • 检查查询语法
  • 检索数据表的描述
from langchain.utilities import SQLDatabase
from langchain.llms import OpenAI
from langchain.agents import create_sql_agent
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
from langchain.agents.agent_types import AgentType

# 连接到FlowerShop数据库
db = SQLDatabase.from_uri("sqlite:///FlowerShop.db")
llm = OpenAI(temperature=0, verbose=True)

# 创建SQL Agent
agent_executor = create_sql_agent(
    llm=llm,
    toolkit=SQLDatabaseToolkit(db=db, llm=llm),
    verbose=True,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
)

# 使用Agent执行SQL查询

questions = [
    "哪种鲜花的存货数量最少?",
    "平均销售价格是多少?",
]

for question in questions:
    response = agent_executor.run(question)
    print(response)

问题一的运行结果

image.png

问题二的运行结果

image.png 使用Agemt相比于Chain更具有思考的能力,首先他输入问题后,首先确定第一个行动是使用工具sql_db_list_tables,然后观察该工具所返回的表格,思考后再确定下一个 action是sql_db_schema,然后的行为是sql_db_query也就是创建SQL语句,最后得到结果后,用自然语言进行回答。