本地大模型编程实战(22)用langchain实现基于SQL数据构建问答系统(1)

使 LLM(大语言模型) 系统能够查询结构化数据与非结构化文本数据在性质上可能不同。后者通常生成可在向量数据库中搜索的文本,而结构化数据的方法通常是让 LLM 编写和执行 DSL(例如 SQL)中的查询。
我们将演练在使用基于 langchain 链 ,在结构化数据库 SQlite 中的数据上创建问答系统的基本方法,该系统建立以后,我们用自然语言询问有关数据库中数据的问题并返回自然语言答案。
后面我们将基于 智能体(Agent) 实现类似功能,两者之间的主要区别在于:智能体可以根据需要多次循环查询数据库以回答问题

实现上述功能需要以下步骤:

  • 将问题转换为 DSL 查询:模型将用户输入转换为 SQL 查询;
  • 执行 SQL 查询;
  • 回答问题:模型使用查询结果响应用户输入。
    SQL智能体

使用 qwen2.5deepseek 以及 llama3.1 做实验。

准备

在正式开始撸代码之前,需要准备一下编程环境。

  1. 计算机
    本文涉及的所有代码可以在没有显存的环境中执行。 我使用的机器配置为:

    • CPU: Intel i5-8400 2.80GHz
    • 内存: 16GB
  2. Visual Studio Code 和 venv
    这是很受欢迎的开发工具,相关文章的代码可以在 Visual Studio Code 中开发和调试。 我们用 pythonvenv 创建虚拟环境, 详见:
    在Visual Studio Code中配置venv。

  3. Ollama
    Ollama 平台上部署本地大模型非常方便,基于此平台,我们可以让 langchain 使用 llama3.1qwen2.5deepseek 等各种本地大模型。详见:
    在langchian中使用本地部署的llama3.1大模型 。

准备 SQLite 数据库

SQLite 是一个轻量级、嵌入式的关系型数据库管理系统,不需要独立的服务器进程,所有数据存储在单一文件中。它支持大部分 SQL 标准,适用于移动应用、本地存储和小型项目。

我们将使用 Chinook 数据库做练习,数据库文件放在本文最后提及的代码仓库中的 assert 文件夹,名为:Chinook.db 。
下图是该数据库的结构:
SQLite数据结构

点击 sqlitestudio 可以下载 SQLite 的可视化管理工具,Sample Databases for SQLite 详细介绍了该数据库的情况。

  • 创建数据库实例
from langchain_community.utilities import SQLDatabase
db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}")
  • 测试数据库
print(db.dialect)
print(db.get_usable_table_names())
print(db.run("SELECT * FROM Artist LIMIT 1;"))
sqlite
['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']
[(1, 'AC/DC')]

输出上述内容说明 SQLite 可以正常工作。

将问题转换为 SQL

langchain 中,可以使用 create_sql_query_chain 轻松的将问题转化为 SQL ,并且通过 db.run 方法执行SQL,基于这两个方法,我们创建了下面的方法用于将问题转化为SQL并执行:

def execute_query(llm_model_name,question: str):"""把问题转换为SQL语句并执行"""llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)chain = create_sql_query_chain(llm, db)#print(chain.get_prompts()[0].pretty_print())# 转化问题为SQLresponse = chain.invoke({"question": question})print(f'response SQL is:\n{response}')# 执行SQLresult = db.run(response)print(f'result is:\n{result}')

我们问几个问题,把使用三个大模型做一下简单测试,看看效果。

问题1:“How many Employees are there?”

  • llama3.1
response SQL is:
SELECT COUNT(*) FROM Employee;
result is:
[(8,)]

llama3.1 生成了正确的SQL并返回了正确的结果。
完美!

  • qwen2.5
To find out how many employees there are, you can use the following SQL query:\n\n
```sql\nSELECT COUNT(*) AS EmployeeCount\nFROM Employee;\n```
...

qwen2.5 推理出了正确的 SQL,可惜该SQL在一段文字中,所以在后面执行sql会有“SQL语法错误”。

  • deepseek-r1

返回一大段推理过程,但是未推理出SQL语句。

问题2:“Which country’s customers spent the most?”

  • llama3.1
response SQL is:
SELECT T2.Country FROM Invoice AS T1 INNER JOIN Customer AS T2 ON T1.CustomerId = T2.CustomerId GROUP BY T2.Country ORDER BY SUM(T1.Total) DESC LIMIT 1;
result is:
[('USA',)]

完美!

问题3:“Describe the PlaylistTrack table.”

response SQL is:
SELECT * FROM `PlaylistTrack`
result is:
[(1, 3402), (1, 3389), (1, 3390), ...

不理想。

为了进一步探索 create_sql_query_chain 都做了什么,我们可以在 此语句后面执行:

print(chain.get_prompts()[0].pretty_print())

打印出的提示词为:

You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.
Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (") to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.
Pay attention to use date('now') function to get the current date, if the question involves "today".Use the following format:Question: Question here
SQLQuery: SQL Query to run
SQLResult: Result of the SQLQuery
Answer: Final answer hereOnly use the following tables:
{table_info}Question: {input}

可见,创建这个链实际上是执行了上述的提示词,可能我们修改一下提示词或者自定义一个链才能让 qwen2.5deepseek-r1 正常工作。

我们也可以用下面更优雅的代码实现将问题转换为SQL:

def execute_query_2(llm_model_name,question: str):"""把问题转换为SQL语句并执行"""llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)execute_query = QuerySQLDataBaseTool(db=db)write_query = create_sql_query_chain(llm, db)chain = write_query | execute_queryresponse = chain.invoke({"question": question})print(f'response SQL is:\n{response}')

回答问题

现在,我们已经有了自动生成和执行查询的方法,我们只需要将原始问题和 SQL 查询结果结合起来即可生成最终答案。我们可以通过再次将问题和结果传递给 LLM 来实现这一点:

def ask(llm_model_name,question: str):answer_prompt = PromptTemplate.from_template("""Given the following user question, corresponding SQL query, and SQL result, answer the user question.Question: {question}SQL Query: {query}SQL Result: {result}Answer: """)llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)execute_query = QuerySQLDataBaseTool(db=db)write_query = create_sql_query_chain(llm, db)chain = (RunnablePassthrough.assign(query=write_query).assign(result=itemgetter("query") | execute_query)| answer_prompt| llm| StrOutputParser())response = chain.invoke({"question": question})print(f'Answer is:\n{response}')

我们看看上述 LCEL(LangChain Expression Language) 中发生的事情:

LangChain 表达式语言 (LCEL) 采用声明式方法从现有 Runnable 构建新的 Runnable,最常用的表达式是 | ,它可以把前后两个链或者其它 Runnable 组件串联起来:前面组件的输出可以提供给后面的组件作为输入。
更多内容参见:LangChain Expression Language (LCEL) 。

  • 在第一个 RunnablePassthrough.assign 之后,会生成一个包含两个元素的 runnable
    {"question": question, "query": write_query.invoke(question)}
    其中 write_query 将生成一个 SQL 查询来回答问题。
  • 在第二个 RunnablePassthrough.assign 之后,我们添加了第三个元素 result ,它的内容由 execute_query.invoke(query) 生成, query 是在上一步中计算的。
  • 这三个元素输入被格式化为提示并传递到 LLM
  • StrOutputParser() 提取输出消息的字符串内容。
    请注意:我们正在将 LLM、工具、提示和其他链组合在一起,但由于它们都实现了 Runnable 接口,因此它们的输入和输出可以绑定在一起:前面的输出可以作为后面的输入。

下面我们使用 llama3.1 ,用三个问题看看 ask 方法的输出内容。

  • 问题1:“How many Employees are there?”
There are 8 employees.
  • 问题2:“Which country’s customers spent the most?”
The country whose customers spent the most is the USA.

总结

从这次演练的效果看,在基于 langchain 框架,使用 LLM(大语言模型) 可以生成 SQL 语句,这使得我们可以说一句“人话”,计算机就可以自动查询 SQLite,并且像人一样告诉我们结果。
可惜 qwen2.5deepseek-r1 在该领域与 langchain 的集成不太理想。


代码

本文涉及的所有代码以及相关资源都已经共享,参见:

  • github
  • gitee

为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。

参考

  • Build a Question/Answering system over SQL data

🪐感谢您观看,祝好运🪐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/70755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用机器学习实现实时交易欺诈检测

以下是一个基于Python的银行反欺诈AI应用示例代码,演示如何利用机器学习实现实时交易欺诈检测。该示例使用LightGBM算法训练模型,并通过Flask框架构建实时检测API: python import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preproc…

最好的Git实践指南(Windows/Linux双系统详解)

Git最佳实践指南:从入门到熟练(Windows/Linux双系统详解) 一、环境搭建与基础配置(适用Windows/Linux) 1.1 Git安装与验证 # Windows系统安装(推荐Chocolatey包管理) # 直接下载git二进制文件…

Python零基础学习第二天(条件语句,循环语句)

Python零基础学习第二天:流程控制与模块导入 一、流程控制结构 1. 条件语句(if, elif, else) 条件语句用于根据不同的条件执行不同的代码块。基本形式如下: if 条件1: # 当条件1为True时执行这里的代码 elif 条件2: # 当前面的条…

从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(三) 实现注册 登录接口

1.划分文件夹 在src目录下创建controllers middleware models routes controllers 放具体的方法 signup login middleware 里面是中间件 请求的验证 models 放对象实体 routes 处理访问路径像/signup /login 等等 2. 接口开发 系统的主要 有用户认证 和 消息 2种类型…

使用Socket编写超牛的http服务器和客户端(一)

实现一个高性能的基于 IOCP(I/O Completion Ports)的 HTTP 服务器,支持多线程、动态线程池调整和路由处理。 主要功能和特性 IOCP 模型: 使用多个 IOCP 句柄(IOCP_COUNT),将客户端连接均匀分配到不同的 IOCP 上,减少线程竞争。 工作线程使用 GetQueuedCompletionStatu…

podman加速器配置,harbor镜像仓库部署

Docker加速器 registries加速器 [rootlocalhost ~]# cat /etc/redhat-release CentOS Stream release 8 [rootlocalhost ~]# cd /etc/containers/ [rootlocalhost containers]# ls certs.d policy.json registries.conf.d storage.conf oci registries.conf re…

MOE结构解读和deepseek的MoE结构

不管dense还是MoE(Mixture of Experts)都是基于transformer的。 下面回顾下解码器块的主要架构: 注意力机制-层归一化&残差连接-FFN前馈神经网络-层归一化&残差连接。 dense模型是沿用了这个一架构,将post-norm换为pre-no…

C#与AI的交互(以DeepSeek为例)

C#与ai的交互 与AI的交互使用的Http请求的方式,通过发送请求,服务器响应ai生成的文本 下面是完整的代码,我这里使用的是Ollama本地部署的deepseek,在联网调用api时,则url会有不同 public class OllamaRequester {[Se…

第九节: Vue 3 中的 provide 与 inject:优雅的跨组件通信

文章目录 前言什么是 provide 和 inject?provide 的基本使用inject 的基本使用provide 提供响应式数据数据provide 提供修改数据的方法provide 提供只读响应数据provide 使用symbol作为注入名inject 默认值总结 前言 在 Vue 3 中,provide 和 inject 是一…

微信小程序-二维码绘制

wxml <view bindlongtap"saveQrcode"><!-- 二维码 --><view style"position: absolute;background-color: #FFFAEC;width: 100%;height: 100vh;"><canvas canvas-id"myQrcode" style"width: 200px; height: 200px;ba…

Linux系统中ssh远程登录协议

目录 一、SSH协议概述 二、SSH协议工作原理 三、ssh服务与配置文件 3.1、openssh 3.2、ssh命令 3.3、服务端配置 四、基于密钥验证的免交互登录 4.1、客户端生成密钥 4.2、将公钥拷贝至服务器 4.3、验证免密登录 一、SSH协议概述 SSH&#xff08;Secure Shell&#x…

vue2 ruoyi websocket轮询

文章目录 前言一、websocket和心跳是什么&#xff1f;二、使用步骤1.2.监听变化3.关闭 总结 前言 websocket&#xff0c;实现与后端通讯&#xff0c;使用心跳机制&#xff0c;断联自动恢复。 一、websocket和心跳是什么&#xff1f; WebSocket WebSocket 是一种网络通信协议&a…

LangChain大模型应用开发:LangGraph快速构建Agent工作流应用

介绍 大家好&#xff0c;博主又来给大家分享知识了。今天给大家分享的内容是使用LangChain进行大规模应用开发中的LangGraph快速构建Agent工作流应用。 通过对前几次对LangChain的技术分享。我们知道LangChain作为一个强大的工具集&#xff0c;为开发者们提供了丰富的资源和便…

学习FreeRTOS推荐几篇质量高的文章

学习FreeRTOS是一个非常好的选择&#xff0c;因为它是一个广泛使用的实时操作系统&#xff08;RTOS&#xff09;&#xff0c;特别适合嵌入式系统开发。以下是一些高质量的文章和视频资源&#xff0c;帮助你入门和深入学习FreeRTOS&#xff1a; 文章推荐 FreeRTOS官方文档 链接…

深入理解Redis:数据类型、事务机制及其应用场景

在当今快速发展的技术领域中&#xff0c;Redis作为一种高性能的内存数据库&#xff0c;已经被广泛应用于各种场景&#xff0c;从简单的缓存实现到复杂的数据处理任务。其灵活性和高效性主要来源于对多种数据结构的支持以及强大的功能特性&#xff0c;如事务处理、持久化选项、高…

k8s集群3主5从高可用架构(kubeadm方式安装k8s)

关键步骤说明 环境准备阶段 系统更新&#xff1a;所有节点执行yum/apt update确保软件包最新时间同步&#xff1a;通过ntpdate time.windows.com或部署NTP服务器网络规划&#xff1a;明确划分Service网段&#xff08;默认10.96.0.0/12&#xff09;和Pod网段&#xff08;如Flann…

Dify部署无法拉取镜像

Dify部署无法摘取镜像 sudo docker compose up -d [] Running 10/10✘ nginx Error Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiti... 15.2s✘ we…

科技快讯 | L3自动驾驶之风吹向全球 2025年或成商业化关键;DeepSeek商标遭恶意抢注

消息称AMD拟以40亿美元出售数据中心工厂&#xff0c;部分台企成潜在买家 2月24日&#xff0c;彭博社报道&#xff0c;AMD正与包括台湾广达电子、英业达、和硕联合以及纬创资通在内的亚洲企业谈判&#xff0c;出售其去年收购的数据中心制造工厂&#xff0c;总价值可能在30-40亿美…

06C语言——指针

一、指针入门 (1)、准备知识 0、图解&#xff1a; 1、内存地址 字节&#xff1a;字节是内存的容量单位&#xff0c;英文称为 byte&#xff0c;一个字节有8位&#xff0c;即 1byte(0000 0000 --- 1111 1111) 8bits(0 --- 1) 地址&#xff1a;系统为了便于区分每一个字节而对…

Python爬虫selenium验证-中文识别点选+图片验证码案例

1.获取图片 import re import time import ddddocr import requests from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from selenium.webdriver.support.wait import WebDriverWait from …