前言
由于 deepseek 等大语言模型数据时效性问题,无法跟上现实世界信息的动态变化,企业内部信息更是无法理解,为了将 deepseek 应用到企业内部,之前有写过通过联网搜索、上传文件、搭建知识等检索增强生成(RAG)方式,现在继续探讨如何通过结合数据库来实现问答。
一、准备环境
- ollama,用于在本地运行、部署和管理大型语言模型(LLMs)。
- deepseek 模型,本文用的 deepseek-r1:14b。
- langchain,大语言模型应用程序的开发框架,主要 python 实现。
- Mysql,这里建议单独部署一个专门用来数据库问答,避免影响生产环境。
二、开发思路
本文打算通过三张表,用户基本信息表,公司/部门信息表,一个用户与公司/部门关联表,再结合 deepseek 根据用户问答使用自然语言流畅问答,打造一个企业智能通讯录。
- 使用 deepseek 生成可执行的 sql 语句,langchain 自带了一些模块,比如:
- create_sql_query_chain:基于用户自然语言问题构建SQL查询
- SQLDatabaseChain:使用链进行查询、创建和执行来查询SQL数据库
- create_sql_agent:使用代理进行健壮和灵活的与SQL数据库交互
发现还是不太理想,决定还是让 deepseek 根据”表结构 + 提示词 + 使用示例“来生成 sql 语句。
- 对生成的 sql 语句进行清洗,比如推理思考过程、markdown标签、其他无关信息等,提取出可直接执行的纯 sql 。
- 执行 sql 获取结果,在让 deepseek 根据”表结构 + 提示词 + 执行的SQL + 执行结果",来回答。
三、代码解读
- 准备好用户、组织架构方面的表,注意以下几点:
- 去掉用不着的字段,防止干扰 deepseek 思考
- 去掉敏感字段,比如密码、身份证
- 每个字段用自然语言添加注释
- 有关联关系的字段,注释清楚
- 每张表用自然语言添加注释
- 由于表结构后续还会用到,建议单独放一个文件
新建一个文件 “table_schema”,写入表结构信息,这是我准备的表:
- 用户信息表
CREATE TABLE `uc_user` (`ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '主键,用户ID',`USER_NAME` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '名称',`ACCOUNT` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '账号',`GENDER` varchar(96) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '性别 男:F-男 女:F-女',`EMAIL` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '邮箱',`PHONE` varchar(48) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '手机号码',`ADDRSS` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '地址',`CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间',`MODIFY_DATE` datetime DEFAULT NULL COMMENT '修改时间',`STATUS` varchar(6) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '状态 1:正常 0:禁用',`REMARK` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '备注',`CREATE_ID` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '创建人id,用户表主键',`into_date` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '入职日期',PRIMARY KEY (`ID`) USING BTREE,KEY `idx_user_name` (`USER_NAME`) USING BTREE,KEY `idx_account` (`ACCOUNT`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='用户信息表';- 公司/部门信息表
CREATE TABLE `uc_organization` (`ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '主键',`ORG_CODE` varchar(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '部门编码',`ORG_NAME` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '部门或公司名称',`PARENT_ORG_ID` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '上级部门/公司ID,公司/部门信息表主键',`ORG_PATH_NAME` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '组织架构全路径',`SORT` int DEFAULT NULL COMMENT '排序号',`ORG_TYPE` varchar(150) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '组织类型:0.根组 1.分公司 2.子公司 3.部门',`CREATE_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '创建人ID,用户表的主键',`CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间',`MODIFY_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '修改人ID,用户表的主键',`MODIFY_DATE` datetime DEFAULT NULL COMMENT '修改时间',`STATE` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '状态 1:正常 0:禁止',`ADDR` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '详细地址',PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='公司/部门信息表';- 用户和部门/公司关联表
CREATE TABLE `uc_user_app_relation` (`USER_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '用户ID,用户表的主键',`STATUS` varchar(6) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '状态 1:正常 0:禁用',`COMPANY_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '公司ID,组织和公司表的主键',`ORG_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '部门ID,组织和公司表的主键',`CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间',`MODIFY_DATE` datetime DEFAULT NULL COMMENT '修改时间',`CREATE_ID` varchar(192) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '创建人ID',KEY `idx_user_id` (`USER_ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='用户和部门/公司关联表';
这里的表稍微有点复杂,主要在 uc_organization 表,即有公司又有部门,还有上下级关系,所以接下来写系统提示词。
- 假如不知道怎么写系统提示词的,可以去 deepseek 官网看示例,然后慢慢调整。
官网:https://api-docs.deepseek.com/zh-cn/prompt-library/
提示词:
### 你是一个的 MYSQL 专家给定输入问题,根据提供的表结构信息,将自然语言查询需求转换为准确且可执行的SQL语句。**表结构信息**{table_schema}## 查询需求
1. 用清晰的自然语言描述查询需求,包括:
- 需要获取的数据字段
- 筛选条件
- 排序要求
- 聚合需求(如求和/计数/平均值等)
- 多表关联需求
2. 生成智能模糊查询,包括:
- 输入参数:`[用户输入的简称片段]`
- 匹配字段:`[目标字段名]`
- 预期效果:智能匹配包含输入词素及其变体的全称
- 特殊要求:`[分词处理/同义词扩展/优先级排序]`## 约束条件
- 严格基于提供的表结构生成
- 优先使用标准SQL语法
- 不准捏造字段
- 不要列出系统字段,如主键、创建日期、修改日期等
- 每张表查询都要带上正常状态!!!不管单表还是多表查询,特别多表查询主表也要加上正常状态!!!
- 使用规范的SQL格式:关键字大写、适当缩进、使用表别名时保持一致性、避免使用*选择所有列
- 必须直接输出SQL语句,不要其他内容,不要分步骤列出
- 只能生成SELECT查询语句### 不准捏造表和字段!!!
### 主表一定要加上状态查询!!!
这里踩的坑有点多,比如:
- 每张表都有状态字段,都需要带上这个查询条件,类似的很多系统也有软删除 is_deleted 字段,要着重强调
- 我们习惯性的会问公司简称,需要加上模糊搜索
- 有时候 deepseek 会捏造字段,也要特别注意
到这里 deepseek 对于单表生成 sql 还可以,但是多表查询,特别表结构又复杂,需要引导 deepseek 如何生成,这就体现了参考示例的作用,写几个稍微复杂的 sql 查询示例让 deepseek 理解,如下:
- 查询某个用户信息
SELECT u.USER_NAME AS 姓名,u.ACCOUNT AS 账号,u.GENDER AS 性别,u.EMAIL AS 邮箱,u.PHONE AS 手机号码,u.ADDRSS AS 地址,u.into_date AS 入职日期,o.ORG_NAME AS 公司名称,d.ORG_NAME AS 部门名称
FROM uc_user u
LEFT JOIN uc_user_app_relation r ON u.ID = r.USER_ID AND r.STATUS = '1'
LEFT JOIN uc_organization o ON r.COMPANY_ID = o.ID
LEFT JOIN uc_organization d ON r.ORG_ID = d.ID
WHERE u.USER_NAME = '某某某';
GROUP BY u.ID;
- 查询某个公司下面人员清单
SELECT u.USER_NAME AS 姓名,u.ACCOUNT AS 账号,u.GENDER AS 性别,u.EMAIL AS 邮箱,u.PHONE AS 手机号码,u.ADDRSS AS 地址,u.into_date AS 入职日期,o.ORG_NAME AS 部门名称,c.ORG_NAME AS 公司名称
FROM uc_user u
LEFT JOIN uc_user_app_relation r ON u.ID = r.USER_ID
LEFT JOIN uc_organization o ON r.ORG_ID = o.ID
LEFT JOIN uc_organization c ON r.COMPANY_ID = c.ID
WHERE u.STATUS = '1' AND r.STATUS = '1' AND o.STATE = '1' AND c.STATE = '1' AND c.ORG_NAME LIKE '%某某公司%'
- 查询某个公司某个部门下面的人员清单
SELECT u.USER_NAME AS 姓名,u.ACCOUNT AS 账号,u.GENDER AS 性别,u.EMAIL AS 邮箱,u.PHONE AS 手机号码,u.ADDRSS AS 地址,u.into_date AS 入职日期,o.ORG_NAME AS 部门名称,c.ORG_NAME AS 公司名称
FROM uc_user u
LEFT JOIN uc_user_app_relation r ON u.ID = r.USER_ID
LEFT JOIN uc_organization o ON r.ORG_ID = o.ID
LEFT JOIN uc_organization c ON r.COMPANY_ID = c.ID
WHERE u.STATUS = '1' AND r.STATUS = '1' AND o.STATE = '1' AND c.STATE = '1' AND c.ORG_NAME LIKE '%某某公司%' AND o.ORG_NAME LIKE '%某某部门%'
- 某某公司的子公司清单
SELECT c.ORG_NAME AS 公司名称,p.ORG_NAME AS 上级公司名称,c.ORG_PATH_NAME AS 组织架构全路径
FROM uc_organization c
LEFT JOIN uc_organization p ON c.PARENT_ORG_ID = p.ID
WHERE c.STATE = '1' AND p.STATE = '1' AND c.ORG_TYPE < 3 AND p.ORG_NAME LIKE '%某某公司%'
ORDER BY c.ORG_PATH_NAME;
- 某某公司的部门清单
SELECT c.ORG_NAME AS 部门名称,p.ORG_NAME AS 上级部门名称,c.ORG_PATH_NAME AS 组织架构全路径
FROM uc_organization c
LEFT JOIN uc_organization p ON c.PARENT_ORG_ID = p.ID
WHERE c.STATE = '1' AND p.STATE = '1' AND c.ORG_TYPE = 3 AND p.ORG_NAME LIKE '%某某公司%'
ORDER BY c.ORG_PATH_NAME;
直接将参考示例放到提示词下面就好了(代码有 ```sql 标签贴出格式就乱了,大家自己搞一下)
- deepseek 生成的内容带了推理过程和标签,直接用正则过滤一下。
代码:
def extract_sql_blocks(text):# 使用正则表达式匹配非贪婪模式,并包含换行符pattern = r'```sql(.*?)```'matches = re.findall(pattern, text, re.DOTALL)# 去除每个匹配项两端的空白字符return [match.strip() for match in matches]def remove_think_tags(text):"""移除文本中<think>标签及其内容"""pattern = re.compile(r'<think>.*?</think>', re.DOTALL) # 非贪婪匹配 + 跨行匹配return pattern.sub('', text)
过滤之前:
过滤之后:
4. 执行 sql 获取查询结果
mysql_uri = "mysql+mysqlconnector://test:123456@127.0.0.1:3306/test"
db = SQLDatabase.from_uri(mysql_uri)
query_result = db.run(query_sql)
- 将表结构、提示词、执行sql、返回结果全都给 deepseek 回答,提示词模板如下:
**指令**
你是一个数据库专家,根据表结构、查询sql、查询结果,回答用户问题,如果查询结果为空,直接回复”暂未找到您要的信息“。**数据库表结构**
{query_tables}**查询sql**
{query_sql}**查询结果**
{query_result}## 要求:
1. 用自然语言总结查询结果,回答用户的问题,不允许在回答中添加编造成分,回答请使用中文。
2. 字段请用中文描述,没有中文注释的不返回。
代码:
def start_chat_mysql(query, model):# 根据自定义提示词写出SQL语句response = create_query_sql(query)# 过滤推理和无关信息,只保留可执行的SQLsqls = extract_sql_blocks(remove_think_tags(response))query_sql = sqls[0]# print(query_sql)# 初始化数据库连接db = SQLDatabase.from_uri(mysql_uri)# 执行查询query_result = db.run(query_sql)# 初始化LLM和链llm = get_chat_llm(model)prompt = get_answer_prompt()chain = prompt | llmwith open("table_schema", "r", encoding="utf-8") as file:table_schema = file.read()response = chain.invoke({"query_tables": table_schema,"query_sql": query_sql,"query_result": query_result,"input": query})return response
没有找到的时候,deepseek 回答:
找到的时候, deepseek 回答:
完整代码私信要吧~ 这里贴出来格式冲突不太好看