用例(User Case)
SQL(Sql)
查询检查(QueryChecking)

查询验证

也许任何SQL链路或代理的最容易出错的部分就是编写有效和安全的SQL查询。在本指南中,我们将介绍一些验证查询和处理无效查询的策略。

设置

首先,获取所需的软件包并设置环境变量:

%pip install --upgrade --quiet  langchain langchain-community langchain-openai

在本指南中,默认使用OpenAI模型,但您可以根据自己的选择将其替换为所需的模型提供程序。

import getpass
import os
 
os.environ["OPENAI_API_KEY"] = getpass.getpass()
 
# 如果使用LangSmith,请取消下面的注释。不是必需的。
# os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
# os.environ["LANGCHAIN_TRACING_V2"] = "true"

以下示例将使用带有Chinook数据库的SQLite连接。按照这些安装步骤 (opens in a new tab)在与此笔记本相同的目录中创建Chinook.db

  • 此文件 (opens in a new tab)保存为Chinook_Sqlite.sql
  • 运行sqlite3 Chinook.db
  • 运行.read Chinook_Sqlite.sql
  • 测试SELECT * FROM Artist LIMIT 10;

现在,Chinook.db位于我们的目录中,我们可以使用基于SQLAlchemy的SQLDatabase类与其交互:

from langchain_community.utilities import SQLDatabase
 
db = SQLDatabase.from_uri("sqlite:///Chinook.db")
print(db.dialect)
print(db.get_usable_table_names())
db.run("SELECT * FROM Artist LIMIT 10;")
sqlite
['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']





"[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]"

查询检查器

也许最简单的策略是要求模型本身检查原始查询中常见的错误。假设我们有以下SQL查询链:

from langchain.chains import create_sql_query_chain
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
chain = create_sql_query_chain(llm, db)

我们想要验证其输出。我们可以通过在链中添加第二个提示和模型调用来实现:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
 
system = """为了检查用户的{dialect}查询中是否存在常见错误,请使用以下方法:
- 当NULL值是使用NOT IN时
- UNION ALL应该替代使用UNION
- 对于排除性范围,请使用BETWEEN
- 在谓词中数据类型不匹配
- 正确引用标识符
- 对于函数,使用正确数量的参数
- 将类型转换为正确的数据类型
- 在联接中使用正确的列名
 
如果存在上述任何错误,请重新编写查询。如果没有错误,请只复制原始查询。
 
请仅输出最终的SQL查询。"""
prompt = ChatPromptTemplate.from_messages(
    [("system", system), ("human", "{query}")]
).partial(dialect=db.dialect)
validation_chain = prompt | llm | StrOutputParser()
 
full_chain = {"query": chain} | validation_chain
query = full_chain.invoke(
    {
        "question": "美国顾客中从2003年到2010年之间,因传真缺失计算最平均发票额是多少"
    }
)
query
"SELECT AVG(Invoice.Total) AS AverageInvoice\nFROM Invoice\nJOIN Customer ON Invoice.CustomerId = Customer.CustomerId\nWHERE Customer.Country = 'USA'\nAND Customer.Fax IS NULL\nAND Invoice.InvoiceDate >= '2003-01-01'\nAND Invoice.InvoiceDate < '2010-01-01'"
db.run(query)
'[(6.632999999999998,)]'

这种方法的明显缺点是我们需要进行两次模型调用,而不是一次生成查询。为了解决这个问题,我们可以尝试在单个模型调用中执行查询生成和查询检查:

system = """你是一个{dialect}专家。给定一个问题,创建一个在语法上正确的{dialect}查询来运行。
除非用户在问题中指定了要获取的示例数量,否则最多使用LIMIT子句从{dialect}查询中获取{top_k}个结果。您可以对结果进行排序,以返回数据库中最有信息的数据。
不要从表格中查询所有列。您必须仅查询回答问题所需的列。将每个列名用双引号(")括起来,以将其表示为分隔符标识符。
请注意,仅使用您在下面的表格中看到的列名。小心不要查询不存在的列。还要注意哪个列在哪个表中。
请注意,如果问题涉及“today”,请使用date('now')函数获取当前日期。
 
只使用以下表格:
{table_info}
 
编写查询的初始草稿。然后,双重检查{dialect}查询以查找常见错误,包括:
- 当NULL值是使用NOT IN时
- UNION ALL应该替代使用UNION
- 对于排除性范围,请使用BETWEEN
- 在谓词中数据类型不匹配
- 正确引用标识符
- 对于函数,使用正确数量的参数
- 将类型转换为正确的数据类型
- 在联接中使用正确的列名
 
使用格式:
 
初始草稿:<<FIRST_DRAFT_QUERY>>
最终答案:<<FINAL_ANSWER_QUERY>>
"""
prompt = ChatPromptTemplate.from_messages(
    [("system", system), ("human", "{input}")]
).partial(dialect=db.dialect)
 
 
def parse_final_answer(output: str) -> str:
    return output.split("最终答案:")[1]
 
 
chain = create_sql_query_chain(llm, db, prompt=prompt) | parse_final_answer
prompt.pretty_print()
================================ System Message ================================

你是一个{dialect}专家。给定一个问题,创建一个在语法上正确的{dialect}查询来运行。
除非用户在问题中指定了要获取的示例数量,否则最多使用LIMIT子句从{dialect}查询中获取{top_k}个结果。您可以对结果进行排序,以返回数据库中最有信息的数据。
不要从表格中查询所有列。您必须仅查询回答问题所需的列。将每个列名用双引号(")括起来,以将其表示为分隔符标识符。
请注意,仅使用您在下面的表格中看到的列名。小心不要查询不存在的列。还要注意哪个列在哪个表中。
请注意,如果问题涉及“today”,请使用date('now')函数获取当前日期。

只使用以下表格:
{table_info}

编写查询的初始草稿。然后,双重检查{dialect}查询以查找常见错误,包括:
- 当NULL值是使用NOT IN时
- UNION ALL应该替代使用UNION
- 对于排除性范围,请使用BETWEEN
- 在谓词中数据类型不匹配
- 正确引用标识符
- 对于函数,使用正确数量的参数
- 将类型转换为正确的数据类型
- 在联接中使用正确的列名

使用格式:

初始草稿:<<FIRST_DRAFT_QUERY>>
最终答案:<<FINAL_ANSWER_QUERY>>


================================ Human Message =================================

{input}
query = chain.invoke(
    {
        "question": "美国顾客中从2003年到2010年之间,因传真缺失计算最平均发票额是多少"
    }
)
query
"\nSELECT AVG(i.Total) AS AverageInvoice\nFROM Invoice i\nJOIN Customer c ON i.CustomerId = c.CustomerId\nWHERE c.Country = 'USA' AND c.Fax IS NULL AND i.InvoiceDate >= date('2003-01-01') AND i.InvoiceDate < date('2010-01-01')"
db.run(query)
'[(6.632999999999998,)]'

人在循环中

在某些情况下,我们的数据非常敏感,我们不希望在没有人的批准下执行SQL查询。前往工具使用:人在循环中页面了解如何将人员引入到任何工具、链或代理中。

错误处理

在某些时候,模型会出错并生成一个无效的SQL查询,或者我们的数据库出现问题,或者模型API停止运行。我们希望为我们的链条和代理添加一些错误处理行为,以便在这些情况下优雅地失败,甚至可能自动恢复。要了解有关工具错误处理的信息,请前往工具使用:错误处理页面。