SQL解释器
前言
大模型对于编写SQL语句的能力是很强的,只需要提供一份数据库表的说明文档,大模型就可以为我们生成对应的SQL语句。现在就使用大模型接口的function_call功能来实现一个带数据库查询的智能体。
单次工具调用整体思路
- 编写一份数据库说明文档
- 带着说明文档、用户提问、数据库查询工具,调用大模型接口
- 判断是否需要调用工具
- 如果需要则调用工具,并将响应结果给大模型再次生成总结。
- 如果不需要则直接返回大模型的回答。
编写数据库说明文档
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}")