使用 LangChain 和 Llama 3.1 查询你的 PostgreSQL 数据库:探索 LLMs — 2


7 个月前

在我们之前的博客文章中,我们讨论了如何通过输入自然语言文本来生成和执行数据库查询。在今天的博客文章中,我们将尝试将查询的输出再次传递给大型语言模型(LLM),以生成同样是自然语言的结果。

如果你是这个系列的新读者,可以先查看 这篇文章 以获取更多背景信息。

让我们开始编码吧!

第0步 — 之前的代码

我们将在之前博客文章的基础上构建这个功能,因此以下部分将保持相对相似。

from langchain_community.llms import Ollama
llm = Ollama(model = "llama3.1")
from langchain_community.utilities import SQLDatabase
from langchain.chains import create_sql_query_chain
from langchain_core.prompts import PromptTemplate
db = SQLDatabase.from_uri("postgresql://abouzuhayr:@localhost:5432/postgres")
write_query = create_sql_query_chain(llm=llm, db=db)

第1步:定义答案提示模板

我们将定义一个提示模板,用于生成最终的用户答案。

from langchain_core.prompts import PromptTemplate
answer_prompt = PromptTemplate.from_template(
    """根据以下用户问题、相应的SQL查询和SQL结果,回答用户问题。

    问题: {question}
    SQL 查询: {query}
    SQL 结果: {result}
    答案: """
)

PromptTemplate 是一种结构化输入的方式,我们将提供给 LLM。它允许我们定义一个带有占位符(如 {question}{query}{result})的模板,这些占位符将在执行时用实际值填充。这有助于 LLM 理解上下文并生成准确的响应。

第2步:为 write_query_with_question 创建可运行组件

接下来,我们需要创建一个组件,从用户的问题生成 SQL 查询,并将问题传递给后续使用。我们将定义一个函数并用 RunnableLambda 包装它:

from langchain.schema.runnable import RunnableLambda

# 创建一个可运行的组件,包装 write_query 并传递问题
def write_query_with_question(inputs):
    response = write_query.invoke(inputs)
    return {'response': response, 'question': inputs['question']}

write_query_runnable = RunnableLambda(write_query_with_question)

理解 RunnableLambda

RunnableLambda 允许我们包装一个简单的 Python 函数,以便它可以作为我们 LangChain 链中的一个组件使用。这使得它与其他链元素兼容,并确保数据在它们之间顺畅流动。

在这个例子中,write_query_with_question

  • 调用 write_query 链(从问题生成 SQL 查询)。
  • 返回一个字典,包含 write_query 的响应和原始问题。

第3步:提取并执行 SQL 查询

现在,我们将定义一个函数,从 LLM 的响应中提取 SQL 查询并在我们的数据库中执行它:

import re

# 定义提取和执行 SQL 查询的函数
def extract_and_execute_sql(inputs):
    # 提取响应文本和问题
    response = inputs.get('response', '')
    question = inputs.get('question', '')

    # 定义正则表达式模式以匹配 SQL 查询
    pattern = re.compile(r'SQLQuery:s*(.*)')

    # 在响应中搜索模式
    match = pattern.search(response)

    if match:
        # 提取匹配的 SQL 查询
        sql_query = match.group(1).strip()

        # 使用自定义逻辑执行查询
        result = db.run(sql_query)

        # 返回链中下一步所需的信息
        return {
            "question": question,
            "query": sql_query,
            "result": result
        }
    else:
        return {
            "question": question,
            "query": None,
            "result": "在响应中未找到 SQL 查询。"
        }

这个函数:

  • 使用正则表达式从 LLM 的响应中提取 SQL 查询。
  • 在数据库中执行 SQL 查询。
  • 返回问题、提取的 SQL 查询和查询结果。

RunnableLambda 包装 extract_and_execute_sql

我们用 RunnableLambda 包装 extract_and_execute_sql 函数,以便它可以包含在我们的链中:

# 用 RunnableLambda 包装你的函数
extract_and_execute = RunnableLambda(extract_and_execute_sql)

同样,RunnableLambda 使我们的自定义函数与链兼容,将其视为可运行的组件。

构建链

现在,我们将组件组装成一个链:

from langchain_core.output_parsers import StrOutputParser

# 创建链
chain = (
    write_query_runnable
    | extract_and_execute
    | answer_prompt
    | llm
    | StrOutputParser()
)

链是如何工作的?

这个链由几个组件组成,使用 | 操作符连接在一起,表示数据从一个组件流向下一个组件。

  1. write_query_runnable:从用户的问题生成 SQL 查询并传递问题。
  2. extract_and_execute:从响应中提取 SQL 查询并在数据库中执行。
  3. answer_prompt:将输入(问题、SQL 查询和结果)格式化为 LLM 的提示。
  4. llm:语言模型根据提示生成自然语言答案。
  5. StrOutputParser():将 LLM 的输出解析为字符串以供最终展示。

可视化数据流

输入:{"question": "总共有多少员工?"}

write_query_runnable 之后:

  • 生成 SQL 查询。
  • 输出:{'response': '...SQLQuery: SELECT COUNT(*) FROM employees;', 'question': '总共有多少员工?'}

extract_and_execute 之后:

  • 提取并执行 SQL 查询。
  • 输出:{'question': '总共有多少员工?', 'query': 'SELECT COUNT(*) FROM employees;', 'result': '42'}

answer_prompt 之后:

  • 格式化 LLM 的提示。
  • 输出:填充占位符的格式化字符串。

llm 之后:

  • 生成自然语言答案。
  • 输出:'总共有 42 名员工。'

StrOutputParser() 之后:

  • 将输出解析为字符串。
  • 最终输出:'总共有 42 名员工。'

调用链

我们现在可以使用链来处理问题:

# 用你的问题调用链
response = chain.invoke({"question": "总共有多少员工?"})

# 打印最终答案
print(response)

这将输出:

总共有 5 名员工。

结论

通过利用 LangChain 的链式能力,我们可以构建一个模块化和可扩展的应用程序,处理自然语言问题,生成和执行 SQL 查询,并以用户友好的格式返回结果。使用 PromptTemplateRunnableLambda 使我们能够创建可重用的组件,轻松集成到链中。

关键要点

  • PromptTemplate:通过定义带有占位符的模板,帮助结构化 LLM 的输入。
  • RunnableLambda:包装自定义函数,使其与 LangChain 的链兼容,将其视为可运行的组件。
  • 链式处理:通过将组件连接在一起,我们可以创建逐步处理数据的复杂工作流。

在我们下一个博客文章中,我们将探讨如何为我们的聊天机器人添加记忆,使其在回答问题时能够保留上下文,从而使使用体验更加有趣!

在此之前,祝你编码愉快!

等等,你就这样离开而不分享你的智慧吗?🤔

等等!在你再次潜入代码深渊或开始将更多函数串联在一起之前,让我们聊聊吧!如果这篇文章让你发笑、思考或质疑我的理智(都是有效的反应),为什么不点个赞呢?👍

记住,每次你留下评论:

  • 一只虫子就会长出翅膀 🐛➡️🦋
  • 程序员的咖啡变成代码 ☕➡️💻
  • 无限循环找到尽头 🔄➡️🏁

而且,老实说,我的代码需要所有的帮助。😅

FluxAI 中文

© 2025. All Rights Reserved