【LangChain】【Python】【NL2SQL】sql解释器简单实现

SQL解释器

前言

大模型对于编写SQL语句的能力是很强的,只需要提供一份数据库表的说明文档,大模型就可以为我们生成对应的SQL语句。现在就使用大模型接口的function_call功能来实现一个带数据库查询的智能体。

单次工具调用整体思路

  1. 编写一份数据库说明文档
  2. 带着说明文档、用户提问、数据库查询工具,调用大模型接口
  3. 判断是否需要调用工具
  4. 如果需要则调用工具,并将响应结果给大模型再次生成总结。
  5. 如果不需要则直接返回大模型的回答。

编写数据库说明文档

markup 复制代码
# 数据库数据字典

本数据字典记录了testdb数据库中LC数据表的基本情况。

## LC数据表

- 基本解释

   	LC数据表记录了拍拍贷互联网金融公司在2015年1月1日到2017年1月30日期间共计30余万条的贷款用户的相关信息。用户的信息维度比较广泛,大致可分为用户的基本信息、认证信息、信用信息和贷款信息等。
   	
   	希望通过该数据表中的数据来分析得出逾期用户的特征,及公司核心业务标的,达到降低业务逾期风险,增加业务收入的目的。

- 数据来源

  	LC数据集由拍拍贷平台进行的采集和记录,并且通过回访确认相关信息,数据集的准确性和可信度都非常高。

- 各字段说明

| Column Name      | Description                                        | Value Range               | Type         |
| ---------------- | -------------------------------------------------- | ------------------------- | ------------ |
| 序号             | 贷款用户的唯一标识                                 |                           | INT          |
| 借款金额         | 借款成交总金额                                     |                           | FLOAT        |
| 初始评级         | 借款成交时的信用评级                               | A,B,C,D,E,F               | VARCHAR(255) |
| 借款类型         | 借款的具体类型                                     | 电商,APP闪电,普通和其他 | VARCHAR(255) |
| 年龄             | 借款人在借款成功时的年龄                           |                           | INT          |
| 性别             | 借款人性别                                         | 男,女                    | VARCHAR(255) |
| 户口认证         | 该借款人户口认证是否成功                           | 未成功认证,成功认证      | VARCHAR(255) |
| 征信认证         | 该借款人征信认证是否成功。成功则表示有人行征信报告 | 未成功认证,成功认证      | VARCHAR(255) |
| 总待还本金       | 借款人在借款成交之前待还本金金额                   |                           | FLOAT        |
| 历史正常还款期数 | 借款人在借款成交之前的按期还款期数                 |                           | INT          |
| 历史逾期还款期数 | 借款人在借款成交之前的逾期还款期数                 |                           | INT          |

编写sql查询工具方法

python 复制代码
import pymysql
import json


def get_sql_result(sql_query):
    """
    查询数据库相关数据的函数
    :param sql_query: 必要参数,字符串类型,用于表示查询数据的sql语句;
    :return:sql_query表示的sql语句查询到的结果;
    """
    print("执行sql:\n" + sql_query)
    # 数据库连接
    connection = pymysql.Connect(
        host='192.168.0.5',
        port=3306,
        user='root',
        password='123456',
        db='pv_db',
        charset='utf8'
    )
    try:
        with connection.cursor() as cursor:
            # SQL查询语句
            sql = sql_query
            cursor.execute(sql)

            # 获取查询结果
            results = cursor.fetchall()
    finally:
        connection.close()

    print("执行结果:")
    if results:
        for row in results:
            print(row)
    else:
        print("无结果")

    # 转换 Decimal 为 float 以便 JSON 序列化
    def convert_decimal(obj):
        from decimal import Decimal
        import datetime
        if isinstance(obj, Decimal):
            return float(obj)  # 或者 str(obj) 如果你想要字符串形式
        elif isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj, datetime.date):
            return obj.isoformat()
        elif isinstance(obj, datetime.time):
            return obj.isoformat()
        return obj

    # 转换结果
    converted_results = []
    for row in results:
        converted_row = tuple(convert_decimal(item) for item in row)
        converted_results.append(converted_row)

    return json.dumps(converted_results)

# 工具描述
get_sql_result_function_desc = {
    "type": "function",
    "function": {
        "name": "get_sql_result",
        "description": "查询数据库相关数据的函数,可以执行数据库查询语句。不可执行SHOW TABLES",
        "parameters": {
            "type": "object",
            "properties": {
                "sql_query": {
                    "type": "string",
                    "description": "必要参数,字符串类型,用于表示查询数据的sql语句",
                }
            },
            "required": ["sql_query"]
        },
    }
}
# 工具名字映射
func_map = {
    "name": "get_sql_result",
    "function": get_sql_result,
    "function_desc": get_sql_result_function_desc
}

编写sql解释器

python 复制代码
from openai import OpenAI
import json

# 获取数据库说明文档
with open('./data/LC数据字典.md', 'r', encoding='utf-8') as f:
    md_content = f.read()

from sql_tools import func_map


def auto_run_conversation(messages, func_map_list=None):
    api_key = "xxxx"
    client = OpenAI(api_key=api_key,
                    base_url="https://api.deepseek.com")
    if func_map_list is None:
        print('模型原生能力解决该提问.........')
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages
        )
        final_response = response.choices[0].message.content
    else:
        tools = []
        func_name_map = {}
        # 循环func_map_list
        for func_map in func_map_list:
            tools.append(func_map['function_desc'])
            func_name_map[func_map['name']] = func_map['function']
        # print(tools)
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,
            tools=tools,
        )
        response_message = response.choices[0].message
        # 判断返回结果是否存在tool_calls,即判断是否需要调用外部函数来回答问题
        tool_calls = response_message.tool_calls
        if tool_calls:
            tool_call = tool_calls[0]
            print('function_calling解决该提问.........')
            function_args_str = tool_call.function.arguments
            print("方法名:" + tool_call.function.name)
            # print('生成的sql为::', function_args_str)
            function_to_call = func_name_map[tool_call.function.name]
            function_args = json.loads(function_args_str)
            function_response = function_to_call(**function_args)
            # 追加消息
            messages.append(response_message.model_dump())
            messages.append({
                "role": "tool",
                "content": function_response,
                "tool_call_id": response_message.tool_calls[0].id
            })
            # 再次调用大模型
            second_response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages)
            final_response = second_response.choices[0].message.content
        else:
            final_response = response_message.content

    return final_response

# 问题
q = """
"""

messages = [
    {"role": "system", "content": md_content},
    {"role": "user", "content": q},
]
func_map_list = [func_map]
res = auto_run_conversation(messages, func_map_list)
print(res)

多轮对话,sql解释器生成报告

对于简单的需求只需要查询一次表即可。但对于比较复杂的需求可能需要多次查表,才能查询出完整的信息进行汇总生成报告。因此这里编写了多次查询,并且带有sql报错自动纠错功能。

python 复制代码
from openai import OpenAI
import json

# 获取数据库说明文档
with open('./data/pv_db.md', 'r', encoding='utf-8') as f:
    md_content = f.read()

from sql_tools import func_map


def auto_run_conversation(messages, func_map_list=None):
    api_key = "xxxx"
    client = OpenAI(api_key=api_key,
                    base_url="https://api.deepseek.com")

    if func_map_list is None:
        print('模型原生能力解决该提问.........')
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages
        )
        return response.choices[0].message.content

    # 准备工具列表和函数映射
    tools = []
    func_name_map = {}
    for func in func_map_list:
        tools.append(func['function_desc'])
        func_name_map[func['name']] = func['function']

    # 循环执行,直到没有工具调用为止
    max_iterations = 10  # 防止无限循环
    iteration = 0

    while iteration < max_iterations:
        iteration += 1
        print(f"\n{'=' * 50}")
        print(f"第 {iteration} 次模型调用...")

        # 调用大模型
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,
            tools=tools,
        )

        response_message = response.choices[0].message
        tool_calls = response_message.tool_calls

        # 判断是否需要调用工具
        if tool_calls:
            print('检测到工具调用,执行函数...')

            # 添加到消息历史中
            messages.append(response_message.model_dump())

            # 处理所有工具调用
            for tool_call in tool_calls:
                function_name = tool_call.function.name
                function_args_str = tool_call.function.arguments

                print(f"\n执行函数: {function_name}")
                # print(f"函数参数: {function_args_str}")

                # 获取并执行函数
                if function_name in func_name_map:
                    function_to_call = func_name_map[function_name]
                    try:
                        function_args = json.loads(function_args_str)
                        function_response = function_to_call(**function_args)
                        print(f"函数执行结果: {function_response[:200]}...")  # 只显示前200字符
                    except json.JSONDecodeError as e:
                        function_response = f"参数解析错误: {str(e)}"
                    except Exception as e:
                        function_response = f"函数执行错误: {str(e)}"
                else:
                    function_response = f"未知函数: {function_name}"

                # 添加工具响应到消息
                messages.append({
                    "role": "tool",
                    "content": function_response,
                    "tool_call_id": tool_call.id,
                    "name": function_name
                })
        else:
            print('没有检测到工具调用,生成最终回答...')
            final_response = response_message.content
            # 将模型的最终回答也添加到消息历史中
            if response_message.content:
                messages.append({
                    "role": "assistant",
                    "content": response_message.content
                })
            return final_response

    # 如果达到最大迭代次数
    print(f"达到最大迭代次数 ({max_iterations}),返回最后一次响应")
    return response_message.content if response_message.content else "处理完成"


# 使用示例
if __name__ == "__main__":
    q = """
  		问题
    """
    messages = [
        {"role": "system", "content": md_content},
        {"role": "user", "content": q},
    ]

    func_map_list = [func_map]
    res = auto_run_conversation(messages, func_map_list)

    print("\n" + "=" * 50)
    print("最终回答:")
    print("=" * 50)
    print(res)

    # 可选:查看完整的对话历史
    print("\n" + "=" * 50)
    print("完整的对话历史:")
    print("=" * 50)
    for i, msg in enumerate(messages):
        print(f"\n[{i}] {msg['role']}:")
        if msg['role'] == 'tool':
            print(f"  函数调用ID: {msg.get('tool_call_id')}")
            print(f"  函数名: {msg.get('name')}")
            print(f"  结果: {msg['content'][:100]}...")
        else:
            content = msg.get('content', '')
            if content:
                print(f"  {content[:100]}..." if len(content) > 100 else f"  {content}")
相关推荐
dagouaofei2 小时前
手术室护理年终PPT怎么做?
前端·python·html·powerpoint
挨踢诗人2 小时前
畅捷通T+按一定比例删除零售单和会员数据
sql·零售
优秘UMI2 小时前
大语言模型 (LLM):理解与生成内容的核心技术引擎
python·科技·其他·ai
sherlock_ye42 小时前
‘jupyter‘ 不是内部或外部命令,也不是可运行的程序或批处理文件,最终解决方案!
ide·python·jupyter·conda
ttthe_MOon2 小时前
MySQL 高阶查询语句:子查询、连接查询与多表关联
数据库·sql
Salt_07282 小时前
DAY27 pipeline管道
python·机器学习
萧鼎2 小时前
Python PyWavelets(pywt)库完整技术指南:从小波理论到工程实践
开发语言·python
code_weic2 小时前
Java AI开发框架 - LangChain4J学习笔记
java·人工智能·ai·langchain
MediaTea3 小时前
Python 装饰器:@property_name.deleter
开发语言·python