基于 openEuler+MySQL8.0 + 大模型的 InnoAI SQL 智能助手项目
前言
本文项目基于国产 openEuler 系统,整合 MySQL8.0、Python3.11、腾讯云 TokenHub 大模型网关,实现自然语言转 SQL 查询、AI 自动报表解读、SQL 执行计划智能调优,支持命令行 + Streamlit 网页双端使用。
下文所有部署步骤零省略,每条命令、配置、排错方案完整保留,跟着操作 3 个工作日即可完整落地,附带配套图文插入位置标注。

项目整体技术架构图
一、前期资源准备
1. 硬件资源
虚拟机 / 云 ECS 服务器:CPU≥2 核、内存≥4GB、磁盘≥20GB,可通外网(443 端口放行)
2. 软件安装包提前下载
1.Python3.11.9 源码包:https://www.python.org/downloads/release/python-3119/
2.MySQL8.0.45 Linux 通用压缩包:https://downloads.mysql.com/archives/community/
系统:
3.openEuler 2203_SP4
3. 线上平台资源准备
1. 腾讯云账号,完成实名认证
2.进入 TokenHub 大模型平台,创建 API 密钥,选择模型:deepseek-v4-pro / Qwen3.5-Plus


二、第一阶段:openEuler 系统初始化
2.1 系统基础安全关闭与优化
登录服务器 root 终端,逐条执行:
# 永久关闭SELinux
sed -i '7s/enforcing/disabled/' /etc/selinux/config
# 临时+永久关闭防火墙
systemctl disable --now firewalld
systemctl status firewalld
# 修改主机名
hostnamectl set-hostname server
bash
# 配置阿里云时间同步
vim /etc/chrony.conf
将文件内全部内容替换为:
server ntp.aliyun.com iburst
stratumweight 0
driftfile /var/lib/chrony/drift
rtcsync
makestep 10 3
bindcmdaddress 127.0.0.1
bindcmdaddress ::1
keyfile /etc/chrony.keys
commandkey 1
generatecommandkey
logchange 0.5
保存退出后执行时间同步生效命令:
systemctl restart chronyd
chronyc sources
2.2 安装系统全套编译依赖(缺一不可)
dnf install -y gcc gcc-c++ make cmake zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel libffi-devel tk-devel wget tar vim tr
三、第二阶段:源码编译 Python3.11.9(完整编译流程)
3.1 上传源码包并解压
使用 XFTP 将Python-3.11.9.tgz上传到服务器/usr/local/src目录
cd /usr/local/src
# 解压源码包
tar -zxvf Python-3.11.9.tgz
cd Python-3.11.9
3.2 编译配置、编译安装
# 编译配置,开启动态库支持(大模型SSL接口必备)
./configure --prefix=/usr/local/python3.11 --enable-shared
# 多核编译安装
make -j$(nproc) && make install
3.3 配置动态链接库,解决 libpython 缺失报错
echo "/usr/local/python3.11/lib" > /etc/ld.so.conf.d/python311.conf
ldconfig
3.4 创建全局软链接(不覆盖系统自带 Python)
ln -s /usr/local/python3.11/bin/python3.11 /usr/local/bin/python3
ln -s /usr/local/python3.11/bin/pip3.11 /usr/local/bin/pip3
bash
3.5 校验 Python 安装完整性
# 验证版本
python3 -V
pip3 -V
# 校验SSL模块(调用大模型API必须,缺失会直接接口报错)
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
3.6 配置阿里国内 pip 镜像源,加速依赖下载
cd ~
mkdir .pip
vim ~/.pip/pip.conf
写入配置:
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host=mirrors.aliyun.com
3.7 安装项目 Python 基础依赖
pip3 install --upgrade pip
pip3 install pymysql python-dotenv tabulate langchain langchain-openai
四、第三阶段:MySQL 8.0.45 完整部署初始化
4.1 上传 MySQL 压缩包,解压迁移目录
将mysql-8.0.45-linux-glibc2.28-x86_64.tar.xz上传服务器根目录
cd /
tar -xvf mysql-8.0.45-linux-glibc2.28-x86_64.tar.xz
# 移动到/usr/local标准软件目录
mv mysql-8.0.45-linux-glibc2.28-x86_64 /usr/local/mysql
cd /usr/local/mysql
4.2 创建 mysql 系统用户、数据目录、权限配置
# 创建mysql用户组
groupadd mysql
# 创建不可登录系统mysql用户
useradd -r -g mysql -s /sbin/nologin mysql
# 创建数据存放目录
mkdir data
chmod -R 750 data
# 递归修改目录归属mysql用户
chown -R mysql:mysql /usr/local/mysql
4.3 初始化 MySQL 数据库(务必记录打印的初始 root 密码)
bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
4.4 后台启动 MySQL 服务
bin/mysqld_safe --user=mysql &
# 新开终端查看进程是否启动成功
ps -ef | grep mysql
4.5 登录 MySQL 并修改 root 密码
若登录报动态库缺失,先执行兼容软链接:
ln -s /usr/lib64/libncurses.so.6.3 /usr/lib64/libncurses.so.5
ln -s /usr/lib64/libtinfo.so.6.3 /usr/lib64/libtinfo.so.5
登录数据库:
bin/mysql -u root -p
# 粘贴初始化生成的临时密码进入mysql命令行
执行 SQL 修改密码、刷新权限:
alter user 'root'@'localhost' identified with mysql_native_password by '123456';
flush privileges;
use mysql;
select user, host, plugin from mysql.user;
exit;
4.6 编写 MySQL 全局配置文件 my.cnf
vim /etc/my.cnf
完整配置内容:
[client]
port = 3306
socket = /tmp/mysql.sock
[mysqld]
port = 3306
basedir = /usr/local/mysql
datadir = /usr/local/mysql/data
tmpdir = /tmp
socket = /tmp/mysql.sock
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
default-storage-engine=INNODB
log_error = error.log
4.7 配置 systemd 开机自启服务
vim /usr/lib/systemd/system/mysqld.service
完整服务内容:
[Unit]
Description=MySQL Server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=notify
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE=65535
LimitNPROC=65535
Restart=on-failure
RestartPreventExitStatus=1
TimeoutSec=0
[Install]
WantedBy=multi-user.target
重载服务并设置开机启动:
systemctl daemon-reload
systemctl enable --now mysqld.service
systemctl is-active mysqld
4.8 配置 MySQL 全局环境变量,全局调用 mysql 命令
vim ~/.bash_profile
添加一行:
export PATH=$PATH:/usr/local/mysql/bin
生效并验证:
source ~/.bash_profile
mysql -V
4.9 创建业务库、订单表、导入 20 条测试数据
登录 MySQL:
mysql -uroot -p123456
逐条执行建库、建表、插入测试数据 SQL:
-- 创建项目数据库
create database testdb;
use testdb;
-- 创建电商订单业务表
create table order_info(
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
user_id INT COMMENT '用户ID',
order_name VARCHAR(200) COMMENT '商品名称',
pay_amount DECIMAL(10,2) COMMENT '支付金额',
create_time DATETIME COMMENT '下单时间'
) ENGINE=InnoDB COMMENT='电商订单业务表';
-- 插入20条完整测试订单数据
INSERT INTO order_info(user_id,order_name,pay_amount,create_time)
VALUES
(1001,'智能手机',2999.00,'2026-05-01 10:20:00'),
(1001,'有线入耳耳机',199.00,'2026-05-02 14:10:00'),
(1002,'14英寸轻薄笔记本电脑',5499.00,'2026-05-03 09:30:00'),
(1002,'无线蓝牙鼠标',89.00,'2026-05-03 09:35:00'),
(1003,'平板学习机',1799.00,'2026-05-04 11:05:00'),
(1003,'平板专用保护壳',49.00,'2026-05-04 11:08:00'),
(1004,'机械游戏键盘',349.00,'2026-05-05 16:42:00'),
(1004,'电竞头戴耳机',459.00,'2026-05-05 16:48:00'),
(1005,'大屏智能电视',3299.00,'2026-05-06 08:15:00'),
(1005,'电视壁挂支架',129.00,'2026-05-06 08:20:00'),
(1006,'无线快充充电器',129.00,'2026-05-03 13:22:00'),
(1006,'降噪蓝牙耳机',399.00,'2026-05-03 13:25:00'),
(1007,'电竞显示器',1899.00,'2026-05-07 10:10:00'),
(1007,'显示器增高支架',79.00,'2026-05-07 10:15:00'),
(1008,'折叠平板支架',39.00,'2026-05-04 15:30:00'),
(1008,'便携充电宝',159.00,'2026-05-04 15:33:00'),
(1009,'台式游戏主机',6999.00,'2026-05-08 09:05:00'),
(1009,'电竞防滑鼠标垫',59.00,'2026-05-08 09:08:00'),
(1010,'手机钢化膜',29.00,'2026-05-05 17:12:00'),
(1010,'桌面收纳支架',45.00,'2026-05-05 17:16:00');
-- 验证数据是否导入成功
select * from order_info;
exit;
五、第四阶段:腾讯云 TokenHub 平台密钥准备
1.访问腾讯云官网,手机号注册、完成实名认证
2.侧边栏进入 TokenHub → API Key 管理 → 创建 API 密钥,复制sk-xxxx密钥
3.模型广场选择:deepseek-v4-pro / Qwen3.5-Plus(禁止带思考链的 Flash 类模型)
4.记录固定接口地址:https://tokenhub.tencentmaas.com/v1
六、第五阶段:项目源码完整创建与配置(无删减全文件)
6.1 创建项目目录,初始化全部源码文件
mkdir -p /opt/mysql_ai_tools
cd /opt/mysql_ai_tools
# 一次性创建全部需要的文件
touch main.py mysql_client.py prompts.py web_main.py .env
# 查看目录结构
tree -La 1
6.2 编写.env 配置文件(核心安全配置)
vim /opt/mysql_ai_tools/.env
完整配置内容,替换为自己的数据库密码、LLM 密钥:
# ===================== MySQL 数据库连接配置
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=123456
MYSQL_DB=testdb
# 腾讯云TokenHub大模型配置
LLM_API_KEY=sk-你的TokenHub密钥
LLM_BASE_URL=https://tokenhub.tencentmaas.com/v1
LLM_MODEL_NAME=deepseek-v4-pro
LLM_TEMPERATURE=0
安全加固关键命令(必须执行)
chmod 600 /opt/mysql_ai_tools/.env
6.3 完整 mysql_client.py 数据库封装脚本
vim /opt/mysql_ai_tools/mysql_client.py
完整源码:
# -*- coding: utf-8 -*-
# MySQL8.0统一封装类:连接、查询、执行计划、SQL安全拦截
import pymysql
import os
import re
from dotenv import load_dotenv
load_dotenv()
class Mysql80Client:
def __init__(self):
self.host = os.getenv("MYSQL_HOST", "127.0.0.1")
self.port = int(os.getenv("MYSQL_PORT", "3306"))
self.user = os.getenv("MYSQL_USER", "root")
self.password = os.getenv("MYSQL_PASSWORD", "")
self.database = os.getenv("MYSQL_DB", "testdb")
self.conn = None
self.connect()
def connect(self):
try:
self.conn = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
database=self.database,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
except pymysql.MySQLError as e:
raise Exception(f"数据库连接失败,请检查地址/账号/密码:{e.args[1]}")
except Exception as e:
raise Exception(f"数据库连接异常:{str(e)}")
@staticmethod
def _check_sql_safety(sql: str) -> None:
sql_trim = sql.strip().upper()
danger_keywords = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE", "REPLACE"]
for kw in danger_keywords:
if re.search(r'\b' + re.escape(kw) + r'\b', sql_trim):
raise Exception(f"安全拦截:禁止执行 {kw} 类型语句,仅支持 SELECT 查询")
def execute_query(self, sql: str):
self._check_sql_safety(sql)
try:
with self.conn.cursor() as cursor:
cursor.execute(sql)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
return columns, rows
except pymysql.MySQLError as e:
raise Exception(f"SQL执行失败(错误码 {e.args[0]}):{e.args[1]}")
except Exception as e:
raise Exception(f"查询异常:{str(e)}")
def get_explain_plan(self, sql: str):
self._check_sql_safety(sql)
explain_sql = f"EXPLAIN {sql}"
try:
with self.conn.cursor() as cursor:
cursor.execute(explain_sql)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
return columns, rows
except pymysql.MySQLError as e:
raise Exception(f"获取执行计划失败:{e.args[1]}")
except Exception as e:
raise Exception(f"执行计划异常:{str(e)}")
def close(self):
if self.conn and not self.conn._closed:
self.conn.close()
6.4 完整 prompts.py 提示词工程脚本
vim /opt/mysql_ai_tools/prompts.py
完整源码:
# -*- coding: utf-8 -*-
# 统一管理NL2SQL、SQL调优提示词,内置SQL提取工具
import re
class UnifiedPrompt:
TABLE_SCHEMA = """
表名: order_info (订单信息表)
字段说明:
- id: 订单ID (主键,BIGINT)
- user_id: 用户ID (INT)
- order_name: 商品名称 (VARCHAR)
- pay_amount: 支付金额 (DECIMAL(10,2))
- create_time: 下单时间 (DATETIME)
"""
NL_TO_SQL_PROMPT = f"""
你是严谨的 MySQL 8.0 数据库开发工程师。
【任务目标】根据用户自然语言生成可直接执行的标准MySQL SELECT语句。
【表结构参考】
{TABLE_SCHEMA}
【强制输出规则】
1. 仅生成SELECT,禁止任何增删改、建表、删表语句;
2. 仅使用表内5个字段,禁止编造字段;
3. 字段可加中文别名,别名内部无空格;
4. SQL关键字全部大写;
5. SQL包裹在```sql ```代码块中;
6. 不输出任何解释文字,只返回SQL代码块。
【用户需求】
{{user_input}}
"""
SQL_TUNE_PROMPT = f"""
你是资深 MySQL DBA 性能优化专家。
【表结构参考】
{TABLE_SCHEMA}
【待分析SQL】
{{sql_input}}
【执行计划数据】
{{explain_data}}
输出要求:
1. 分点说明核心性能问题(全表扫描、无索引、filesort、临时表等);
2. 输出完整可直接执行的建索引SQL;
3. 给出优化改写后的完整SQL;
4. 内容简洁清晰,无多余话术。
"""
@staticmethod
def extract_sql(response_text: str) -> str:
if not response_text:
return ""
# 优先级1:标准markdown sql代码块
match = re.search(r"```sql\s*(.*?)\s*```", response_text, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()
# 优先级2:自定义标签兼容
match = re.search(r"<sql>\s*(.*?)\s*</sql>", response_text, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()
# 兜底:匹配SELECT开头语句
match = re.search(r"(SELECT\s+.*?;)", response_text, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()
return ""
prompt_helper = UnifiedPrompt()
6.5 完整 main.py 项目核心调度脚本(命令行入口)
vim /opt/mysql_ai_tools/main.py
完整源码:
# -*- coding: utf-8 -*-
# 项目核心业务逻辑,命令行与网页共用底层函数
import os
import re
import logging
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from mysql_client import Mysql80Client
from tabulate import tabulate
from prompts import UnifiedPrompt, prompt_helper
load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
def check_config() -> None:
required_llm = ["LLM_API_KEY", "LLM_BASE_URL", "LLM_MODEL_NAME"]
missing = [k for k in required_llm if not os.getenv(k)]
if missing:
raise ValueError(f"配置缺失:请在 .env 文件中填写 {', '.join(missing)}")
required_db = ["MYSQL_HOST", "MYSQL_USER", "MYSQL_DB"]
missing_db = [k for k in required_db if not os.getenv(k)]
if missing_db:
raise ValueError(f"数据库配置缺失:请检查 {', '.join(missing_db)}")
def get_llm() -> ChatOpenAI:
api_key = os.getenv("LLM_API_KEY")
base_url = os.getenv("LLM_BASE_URL")
model_name = os.getenv("LLM_MODEL_NAME")
temperature = float(os.getenv("LLM_TEMPERATURE", 0.1))
return ChatOpenAI(
api_key=api_key,
base_url=base_url,
model=model_name,
temperature=temperature
)
def clean_sql_spacing(sql: str) -> str:
if not sql:
return ""
special_spaces = ['\xa0', '\u200b', '\u200c', '\u200d', '\u200e', '\u200f', '\u3000', '\t', '\n', '\r']
for sp in special_spaces:
sql = sql.replace(sp, ' ')
sql = re.sub(r'[\x00-\x1f\x7f]', '', sql)
sql = sql.replace(',', ',').replace(';', ';').replace('(', '(').replace(')', ')')
sql = re.sub(r'\s+', ' ', sql).strip()
def _clean_alias_space(match):
prefix = match.group(1)
alias = match.group(2)
alias_clean = re.sub(r'\s+', '', alias)
return f"{prefix} {alias_clean}"
sql = re.sub(
r'\b(AS)\s+(.+?)(?=\s*,\s*|\s+FROM\b|\s+WHERE\b|\s+ORDER\b|\s+GROUP\b|\s+LIMIT\b|\s*;)',
_clean_alias_space,
sql,
flags=re.IGNORECASE
)
keywords_upper = [
"SELECT", "FROM", "WHERE", "ORDER BY", "GROUP BY",
"AND", "OR", "LIMIT", "DESC", "ASC", "AS",
"INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "ON",
"INSERT INTO", "UPDATE", "SET", "DELETE FROM",
"VALUES", "LIKE", "IN", "BETWEEN", "IS NULL",
"COUNT", "SUM", "AVG", "MAX", "MIN", "OVER"
]
for kw in keywords_upper:
pattern = r'([a-z_])(' + re.escape(kw) + r')'
sql = re.sub(pattern, r'\1 \2', sql)
pattern_cn = r'([\u4e00-\u9fa5])(' + re.escape(kw) + r')'
sql = re.sub(pattern_cn, r'\1 \2', sql)
keywords_lower = [kw.lower() for kw in keywords_upper]
for kw in keywords_lower:
sql = re.sub(r'\b' + re.escape(kw) + r'\b', kw.upper(), sql, flags=re.IGNORECASE)
sql = re.sub(r'\s+', ' ', sql).strip()
return sql
def nl2sql_query(user_input: str) -> dict:
llm = get_llm()
db = Mysql80Client()
try:
logger.info("正在生成SQL语句...")
prompt = UnifiedPrompt.NL_TO_SQL_PROMPT.format(user_input=user_input)
response = llm.invoke(prompt)
raw_content = response.content.strip()
extracted_sql = prompt_helper.extract_sql(raw_content)
if not extracted_sql:
raise Exception("大模型未返回有效SQL,请重新描述需求")
clean_sql = clean_sql_spacing(extracted_sql)
logger.info(f"生成SQL:{clean_sql}")
columns, rows = db.execute_query(clean_sql)
summary = ""
if rows:
logger.info("正在生成数据总结...")
summary_prompt = f"""
以下是真实的SQL查询结果,请作为电商数据分析师给出简练的业务总结。
语句:{clean_sql}
查询数据:{str(rows)}
重点说明数据反映的业务含义,如有异常值请指出。
"""
summary_resp = llm.invoke(summary_prompt)
summary = summary_resp.content.strip()
return {
"success": True,
"sql": clean_sql,
"columns": columns,
"rows": rows,
"summary": summary,
"raw_llm": raw_content
}
except Exception as e:
logger.error(f"查询处理失败:{str(e)}")
return {
"success": False,
"error": str(e),
"raw_llm": raw_content if 'raw_content' in locals() else ""
}
finally:
db.close()
def sql_tune_analyze(raw_sql: str) -> dict:
llm = get_llm()
db = Mysql80Client()
try:
clean_sql = clean_sql_spacing(raw_sql)
logger.info("正在获取执行计划...")
columns, plan_rows = db.get_explain_plan(clean_sql)
logger.info("正在分析性能瓶颈...")
prompt = UnifiedPrompt.SQL_TUNE_PROMPT.format(
sql_input=clean_sql,
explain_data=str(plan_rows)
)
response = llm.invoke(prompt)
return {
"success": True,
"sql": clean_sql,
"plan_columns": columns,
"plan_rows": plan_rows,
"suggestion": response.content.strip()
}
except Exception as e:
logger.error(f"调优分析失败:{str(e)}")
return {
"success": False,
"error": str(e)
}
finally:
db.close()
def main_cli():
try:
check_config()
except ValueError as e:
print(f"❌ {e}")
return
while True:
print("\n=============== InnoAI SQL 助手 ===============")
print("1. 自然语言生成SQL,自动查询并AI总结数据")
print("2. 输入SQL语句,AI分析执行计划并给出调优方案")
print("0. 退出程序")
choice = input("请输入功能序号: ").strip()
if choice == '1':
query = input("请输入你的数据查询需求: ").strip()
if not query:
print("⚠️ 请输入有效需求")
continue
result = nl2sql_query(query)
if not result["success"]:
print(f"\n❌ 处理失败:{result['error']}")
if result.get("raw_llm"):
print(f"大模型原始回复:\n{result['raw_llm']}")
continue
print(f"\n✅ 生成SQL:")
print(result["sql"])
if result["rows"]:
print(f"\n📊 查询结果(共 {len(result['rows'])} 条):")
print(tabulate(result["rows"], headers="keys", tablefmt="pretty"))
if result["summary"]:
print(f"\n💡 业务总结:\n{result['summary']}")
else:
print("\n⚠️ 未查询到匹配数据")
elif choice == '2':
sql_input = input("\n请输入需要分析的 SQL 语句: ").strip()
if not sql_input:
print("⚠️ 请输入有效SQL")
continue
result = sql_tune_analyze(sql_input)
if not result["success"]:
print(f"\n❌ 分析失败:{result['error']}")
continue
print(f"\n📊 执行计划详情:")
print(tabulate(result["plan_rows"], headers="keys", tablefmt="pretty"))
print(f"\n📈 调优建议:\n{result['suggestion']}")
elif choice == '0':
print("程序已安全退出。")
break
else:
print("无效输入,请重试。")
print("\n" + "-" * 40)
if __name__ == "__main__":
main_cli()
七、第六阶段:命令行 CLI 功能完整测试
7.1 启动命令行程序
python3 /opt/mysql_ai_tools/main.py
菜单界面:
7.2 测试用例 1:自然语言查询
选择 1,输入需求:查询1001、1002、1003每个用户的订单总消费金额与订单笔数,按总消费从高到低排序
系统自动:生成 SQL → 执行查询 → 打印表格 → AI 输出业务总结
7.3 测试用例 2:SQL 性能调优
选择 2,粘贴上面生成的 SQL,系统输出 EXPLAIN 执行计划 + 索引优化方案
八、第七阶段:Streamlit 网页可视化完整部署
8.1 安装网页依赖包(指定 python3.11 的 pip)
/usr/local/python3.11/bin/pip3 install langchain-openai streamlit pymysql python-dotenv sqlparse tabulate
# 校验依赖是否正常导入
python3 -c "import streamlit;print('Streamlit依赖安装完成')"
8.2 完整 web_main.py 网页界面脚本
vim /opt/mysql_ai_tools/web_main.py
完整源码:
# -*- coding: utf-8 -*-
# Streamlit可视化Web界面,复用main底层逻辑
import streamlit as st
import os
import re
from main import check_config, nl2sql_query, sql_tune_analyze
st.set_page_config(page_title="InnoAI SQL 助手", layout="wide")
# 全局页面样式美化
st.markdown("""
<style>
html, body, [class*="css"] {
font-size: 15px;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
.block-container {
padding-top: 2.5rem;
padding-bottom: 3rem;
max-width: 1100px;
margin: 0 auto;
}
h1 {
font-size: 2rem !important;
font-weight: 600;
margin-bottom: 1.8rem;
letter-spacing: 0.5px;
}
h2, h3 {
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 1rem;
}
h3 {
font-size: 1.25rem !important;
}
.stTextArea label p {
font-size: 15px;
font-weight: 500;
color: #1f2937;
margin-bottom: 0.5rem;
}
textarea {
font-size: 15px !important;
line-height: 1.6 !important;
border-radius: 8px !important;
padding: 12px 14px !important;
}
.stButton > button {
font-size: 15px !important;
font-weight: 500;
padding: 0.65rem 2.2rem !important;
border-radius: 8px !important;
min-width: 160px;
}
section[data-testid="stSidebar"] {
font-size: 14.5px;
}
section[data-testid="stSidebar"] h1 {
font-size: 1.5rem !important;
white-space: nowrap;
margin-bottom: 1.2rem;
}
section[data-testid="stSidebar"] .stRadio > div {
gap: 0.8rem;
}
.stAlert {
font-size: 14.5px;
border-radius: 8px !important;
}
.stCodeBlock {
position: relative !important;
border-radius: 8px !important;
}
.stCodeBlock button {
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
z-index: 999 !important;
width: 36px;
height: 36px;
top: 10px;
right: 10px;
border-radius: 6px;
background: #f3f4f6 !important;
color: #374151 !important;
border: 1px solid #e5e7eb !important;
}
.stCodeBlock button:hover {
background: #e5e7eb !important;
}
.stCodeBlock code, .stCodeBlock pre {
font-size: 14.5px !important;
line-height: 1.7 !important;
font-family: "JetBrains Mono", Consolas, Monaco, monospace;
}
.stDataFrame {
font-size: 14.5px;
border-radius: 8px;
overflow: hidden;
}
.stDataFrame [data-testid="table"] td {
padding: 10px 12px;
}
</style>
""", unsafe_allow_html=True)
# 全局配置校验,会话缓存只执行一次
if "config_checked" not in st.session_state:
try:
check_config()
st.session_state.config_checked = True
except ValueError as e:
st.error(f"配置错误:{e}")
st.stop()
# 侧边栏导航
with st.sidebar:
st.title("🚀 InnoAI SQL 助手")
st.info(f"当前模型:{os.getenv('LLM_MODEL_NAME', '未知')}")
page = st.radio("功能导航", ["🔍 数据查询与总结", "⚙️ SQL 性能调优"])
# 页面1:自然语言转SQL查询
if page == "🔍 数据查询与总结":
st.header("💬 自然语言转 SQL 查询")
user_input = st.text_area("请输入你的业务查询需求:", height=150)
if st.button("🚀 生成并执行", type="primary"):
input_trim = user_input.strip()
if not input_trim:
st.warning("请输入有效的业务查询需求后再提交")
elif re.match(r'(?i)^\s*SELECT\s+', input_trim):
st.warning("此处请输入自然语言描述的查询需求,请勿直接粘贴 SQL 语句。")
else:
with st.spinner("AI 正在生成 SQL 并查询数据..."):
result = nl2sql_query(user_input)
if not result["success"]:
st.error(f"处理失败:{result['error']}")
if result.get("raw_llm"):
with st.expander("查看大模型原始回复"):
st.code(result["raw_llm"])
else:
st.success("SQL 生成并执行成功")
st.code(result["sql"], language="sql")
if result["rows"]:
st.dataframe(result["rows"], use_container_width=True)
if result["summary"]:
st.markdown("### 💡 业务总结")
st.info(result["summary"])
else:
st.info("未查询到匹配的数据")
# 页面2:SQL性能调优
else:
st.header("🔧 SQL 性能调优分析")
raw_sql = st.text_area("请输入待分析的 SQL 语句:", height=200)
if st.button("📊 开始分析", type="primary"):
input_trim = raw_sql.strip()
if not input_trim:
st.warning("请输入有效的 SQL 语句后再提交")
elif not re.match(r'(?i)^\s*SELECT\s+', input_trim):
st.warning("此处请输入待分析的 SELECT SQL 语句,如需自然语言转SQL请切换页面。")
else:
with st.spinner("正在获取执行计划并分析..."):
result = sql_tune_analyze(raw_sql)
if not result["success"]:
st.error(f"分析失败:{result['error']}")
else:
st.success("执行计划获取成功")
st.dataframe(result["plan_rows"], use_container_width=True)
st.markdown("### 💡 调优建议")
st.markdown(result["suggestion"])
8.3 创建 systemd 后台常驻服务(网页开机自启)
vim /etc/systemd/system/mysql-ai-web.service
完整服务配置:
[Unit]
Description=InnoAI SQL Streamlit Web Service
After=network.target mysqld.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/mysql_ai_tools
ExecStart=/usr/local/python3.11/bin/python3 -m streamlit run web_main.py --server.address 0.0.0.0 --server.port 8501 --server.headless true
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
重载服务并启动:
systemctl daemon-reload
systemctl enable --now mysql-ai-web
# 查看网页服务运行状态
systemctl status mysql-ai-web
8.4 浏览器访问测试
1.获取服务器内网 / 公网 IP
hostname -I
2.浏览器地址栏输入:http://服务器IP:8501

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)