编者按:本文与来自 Pampa Labs 团队的 Manuel 和 Francisco 合作撰写。我们一直很高兴看到涌现出更多用于深入定制/个性化应用的最佳实践,而这篇关于通过应用创新的 RAG 技术来扩展标准 SQL 工具包能力的文章是一个绝佳的范例。
LangChain 库提供了与 SQL 数据库交互的不同工具,可用于根据自然语言输入构建和运行查询。例如,标准 SQL 工具包借鉴了本文博客文章中广泛介绍的标准最佳实践。然而,在构建自定义解决方案和调整通用工具以适应特定用例方面,仍有改进的空间。拥有一个“即插即用”的工具包的优势与一个不够灵活、用户无法纳入其关于数据库的领域特定知识的解决方案形成了鲜明对比。
我们可以通过额外的自定义工具来扩展开箱即用的 SQL 工具包,这些工具利用领域特定知识。这样,我们就能兼顾两全其美:任何人都可以用最少的设置运行标准 SQL Agent,同时又能集成额外的工具,在推理时向提示中添加相关信息。在这篇博客文章中,我们将介绍如何用一些非常有用的示例性附加工具来扩展标准 SQL 工具包。
问题
使用标准 SQL 工具包,Agent 能够构建和运行查询,为用户的问题提供答案。虽然这个工具包足够健壮,只需连接到数据库即可构建第一个开箱即用的原型,但试图将其用于足够复杂的数据库的人至少会遇到以下问题之一:
- 查询未正确生成,导致需要重试多次才能获得正确的查询。
- 过度使用工具,导致整个思考过程在时间和 token 方面效率低下。
- 提示内容非常冗长,信息并不总是与特定用户问题相关。
这些问题背后的根本原因在于,我们试图仅使用通用工具构建自定义解决方案,而没有利用我们确实知道用例细微差别的这一事实。因此,我们需要找到一种方法来增强 Agent 的领域特定知识,而无需在提示模板中硬编码任何内容。
扩展 SQL 工具包
已有研究证明,向提示中输入数据库信息对于构建正确的 SQL 查询至关重要。这就是为什么该工具包允许 Agent 获取有关表名、架构、示例行等信息的原因。然而,所有这些工具只能检索数据库信息,类似于数据科学家在与新数据集的初步交互中一样。
但如果这不是第一次互动呢?
任何构建 LLM-SQL 解决方案的人都会带来丰富的领域特定知识。他们知道哪些问题通常难以转化为查询,以及何时以及应将哪些补充信息纳入提示。在仅使用标准工具包不足的场景中,这一点尤其重要。这些见解可以通过检索增强生成 (Retrieval Augmented Generation) 动态地包含到提示中,这涉及在向量数据库中进行语义搜索并检索相关数据。
包含少量样本示例
向提示中输入问题-查询匹配的少量样本 (few-shot) 示例可以提高查询生成准确性。这可以通过简单地将标准的静态示例附加到提示中来实现,以指导 Agent 如何根据问题构建查询。然而,一种更强大的方法是拥有一个强大的良好示例数据集,并动态地包含与用户问题相关的示例。
为了实现这一点,我们需要一个自定义的检索器工具来处理向量数据库,以便检索与用户问题语义相似的示例。Agent 甚至可以决定是否需要使用其他工具。
让我们来看一个例子!
agent.run("How many employees do we have?")
> Entering new AgentExecutor chain...
Invoking: `sql_get_similar_examples` with `How many employees do we have?`
[Document(page_content='How many employees are there', metadata={'sql_query': 'SELECT COUNT(*) FROM "employee"'}), Document(page_content='Which employee has sold the most?', metadata={'sql_query': "SELECT e.FirstName || ' ' || e.LastName AS EmployeeName, SUM(i.Total) AS TotalSales\n FROM Employee e\n JOIN Customer c ON e.EmployeeId = c.SupportRepId\n JOIN Invoice i ON c.CustomerId = i.CustomerId\n GROUP BY e.EmployeeId\n ORDER BY TotalSales DESC\n LIMIT 1;"})]
Invoking: `sql_db_query` with `SELECT COUNT(*) FROM employee`
responded: {content}
[(8,)]We have 8 employees.
> Finished chain.
查找专有名词中的拼写错误
在 LLM-SQL 解决方案中应用 RAG 的另一个很好的用例是使系统能够容忍拼写错误。在查询专有名词(如姓名或国家)时,用户可能会无意中写错专有名词,系统将无法在数据库中找到它(例如,“Franc Sinatra”)。
如何解决这个问题?
一种解决这个问题的方法是创建一个向量存储,其中包含数据库中所有独立的专有名词。然后,每次用户在问题中包含专有名词时,Agent 都可以查询该向量存储,以查找该词的正确拼写。这样,Agent 就可以在构建目标查询之前确保它理解用户指的是哪个实体。
让我们来看一个例子!
`
sql_agent("What is 'Francis Trembling's email address?")
Invoking: `name_search` with `Francis Trembling`
[Document(page_content='François Tremblay', metadata={}), Document(page_content='Edward Francis', metadata={}), Document(page_content='Frank Ralston', metadata={}), Document(page_content='Frank Harris', metadata={}), Document(page_content='N. Frances Street', metadata={})]
Invoking: `sql_db_query_checker` with `SELECT Email FROM Customer WHERE FirstName = 'François' AND LastName = 'Tremblay' LIMIT 1`
responded: {content}
SELECT Email FROM Customer WHERE FirstName = 'François' AND LastName = 'Tremblay' LIMIT 1
Invoking: `sql_db_query` with `SELECT Email FROM Customer WHERE FirstName = 'François' AND LastName = 'Tremblay' LIMIT 1`
[('ftremblay@gmail.com',)]The email address of 'François Tremblay' is 'ftremblay@gmail.com'.
> Finished chain.
{'input': "What is 'Francis Trembling' email address?",
'output': "The email address of 'François Tremblay' is 'ftremblay@gmail.com'."}
实施说明:在指示 LLM 以一种或另一种顺序使用工具时,我们发现通常更有效的方法是在 Agent 的提示中进行指示,而不是在工具的描述中进行指示——有关更多信息,请参阅文档中的 SQL 用例。
更进一步
除了这些通过利用开发者的领域特定知识来改进标准 SQL 工具包的最佳实践外,在准确性和成本方面仍有改进的空间。
一些增强少量样本方法的示例包括:
- 应用**相似性阈值**来决定检索到的示例是否足够相关以包含在提示中(例如,一个与先前问题非常不同的新问题,不应检索任何示例)。
- 类似地,设置一个阈值来决定**示例是否过于相关**,并且不应使用其他工具,从而节省大量时间和 token(例如,仅仅调整列过滤器,有一个相关的示例就足够了,不需要其他工具)。
- 优先考虑少量样本的**多样性**,以覆盖更广泛的示例区域,正如 Hongjin Su 等人的这篇论文中所涵盖的那样。
此外,一些示例虽然不严格与少量样本示例相关,但确实涉及使用 RAG,包括:
- 如果用户的问题涉及过滤列(例如,产品名称),则检索相关分类列中的所有值。
- 调整示例行,仅显示与用户问题相关的列。
如果您想帮助实施其中任何一项,或者有其他您认为有用的最佳实践,请随时加入 Discord 中的 #sql 频道进行讨论!