编者按:这篇文章是与 Manuel 和 Francisco 合作撰写的,他们来自 Pampa Labs 团队。我们一直很高兴看到新的最佳实践出现,以更彻底地定制/个性化应用程序,而这篇关于通过应用创新的 RAG 技术来扩展标准 SQL 工具包的功能的文章就是一个很棒的例子。
LangChain 库提供了不同的工具来与 SQL 数据库交互,这些工具可以用于构建和运行基于自然语言输入的查询。例如,标准的 SQL 工具包借鉴了标准的最佳实践,这些实践已在本博客文章中广泛介绍。然而,在构建自定义解决方案并将通用工具调整到特定用例方面,仍然有改进的空间。拥有一个即插即用工具包的优势与拥有一个不够灵活的解决方案形成对比,后者无法让用户融入他们关于数据库的领域特定知识。
我们可以使用利用领域特定知识的额外自定义工具来扩展开箱即用的 SQL 工具包。通过这种方式,我们兼得两者之长:任何人都可以通过最少的设置运行标准的 SQL Agent,同时能够融入在推理时向提示添加相关信息的额外工具。在这篇博文中,我们将介绍如何使用一些非常有用的示例额外工具来扩展标准的 SQL 工具包。
问题
使用标准的 SQL 工具包,代理能够构建和运行查询,以提供用户问题的答案。尽管这个工具包足够强大,只需连接到数据库即可构建第一个开箱即用的原型,但任何试图将其用于足够复杂的数据库的人都至少会面临以下问题之一
- 查询生成不正确,导致多次重试直到获得正确的查询。
- 过度使用工具,使得整个思考过程在时间和 token 方面都非常低效。
- 非常广泛的提示,其中包含并非总是与特定用户问题相关的信息。
这些问题背后的根本原因是,我们试图仅使用通用工具来构建自定义解决方案,而没有利用我们确实了解用例细微差别这一事实。因此,我们需要找到一种方法来增强代理的领域特定知识,而无需在提示模板中硬编码任何内容。
扩展 SQL 工具包
已证明,用数据库信息来喂养提示对于构建正确的 SQL 查询至关重要。这就是为什么工具包使代理能够获取关于表名、模式、示例行等信息的原因。然而,所有这些工具所能做的就是检索关于数据库的信息,类似于数据科学家在首次交互时处理新数据集的方式。
但如果这不是第一次交互呢?
任何构建 LLM-SQL 解决方案的人都会为表格带来丰富的领域特定知识。他们知道哪些问题通常难以转化为查询,以及何时以及应该将哪些补充信息纳入提示。这在仅仅使用标准工具包就不足以应付的情况下变得尤为重要。这些见解可以使用检索增强生成动态地纳入提示,检索增强生成涉及在向量数据库中进行语义搜索并检索相关数据。
包含少量示例
用 少量示例 的 问题-查询匹配 来喂养提示 提高了查询生成准确率。 这可以通过简单地在提示中附加标准静态示例来指导代理如何根据问题构建查询来实现。然而,更有效的方法是拥有一个良好的示例数据集,并动态地包含那些与用户问题相关的示例。
为了实现这一点,我们需要一个自定义的检索器工具来处理向量数据库,以便检索与用户问题语义相似的示例。代理甚至可以决定是否需要使用其他工具。
让我们看一个例子!
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”)。
我们如何解决这个问题?
解决这个问题的一种方法是使用数据库中存在的所有不同的专有名词创建一个向量存储。然后,我们可以让代理在每次用户的问题中包含专有名词时查询该向量存储,以找到该单词的正确拼写。通过这种方式,代理可以确保它在构建目标查询之前理解用户指的是哪个实体。
让我们看一个例子!
`
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 按某种顺序使用工具时,我们发现通常在代理的提示中指示比在工具的描述中指示更有效——有关更多信息,请参阅文档中的 SQL 用例。
更进一步
正如这些最佳实践通过利用开发人员的领域特定知识来改进标准 SQL 工具包一样,在准确性和成本方面仍然有改进的空间。
关于增强少量示例方法的一些示例包括
- 应用相似度阈值来决定检索到的示例是否足够相关以包含在提示中(例如,一个与其他问题非常不同的新问题,不应检索任何示例)。
- 类似地,设置一个阈值来决定示例是否过于相关,并且不应使用其他工具,从而节省大量时间和 token(例如,仅调整列过滤器,仅具有相关示例就足够了,不需要其他工具)。
- 优先考虑少量示例的多样性,以便覆盖更广泛的示例领域,正如 Hongjin Su 等人的以下论文中所述。
此外,一些与少量示例没有严格相关性但确实涉及使用 RAG 的示例包括
- 如果用户的问题涉及过滤列(例如,产品名称),则检索相关类别列中的所有值。
- 调整示例行以仅显示与用户问题相关的列。
如果您想帮助实施这些最佳实践中的任何一个,或者有其他您发现有用的最佳实践,请毫不犹豫地加入 Discord 中 #sql 频道 的讨论!