# Dify 搭建数据查询 AI 应用 - 优化检索
# 简介
这里不再累赘可以看 Dify 搭建数据查询 AI 应用 这篇文章
# 开始优化
在 Dify 搭建数据查询 AI 应用 这篇文章中我们使用的知识库来生成 sql,生成的质量并不理想,所以重新优化一下
# 知识库
重新对我们的业务 表结构 、 详细的字段说明 还有一些 sql的示例 做详细的介绍和关联如图:

三资 Description 内容(分段标识为:“#”):
### 地区表(dist) | |
地区表用于存储地区的信息。以下是该表中每个字段的含义: | |
- `id`: 这是一个整数类型的自增字段。 | |
- `distno`: 字符串类型,用于存储地区的编号。地区编号,1位长度表示省级,2位长度表示市级,4位长度表示区县级,6位长度表示镇街级,9位长度表示村级。 | |
- `distname`: 字符串类型,用于存储地区的名称。 | |
- `parentDistNo`: 字符串类型,用于存储上级地区编号。可以和本身关联,关联上一级的地区。 | |
- `parentDistName`: 字符串类型,用于存储上级地区名称。 | |
### 账套表(zt) | |
账套表用于存储地区下的账套单位信息。以下是该表中每个字段的含义: | |
- `id`: 这是一个整数类型的自增字段。 | |
- `distno`: 字符串类型,用于存储地区编号,这个字段可以和地区表关联,这里的地区编号只有到9位长度的,表示是哪个村下的账套数据。 | |
- `distname`: 字符串类型,用于存储地区的名称。 | |
- `ztId`: 字符串类型,用于存储账套单位编号。长度大于9,前9位是村级的地区编号。 | |
- `ztName`: 字符串类型,用于存储账套单位名称。 | |
- `ztTypeId`: 整数类型,用于存储账套单位的类型,可能的值有“1” 表示“经联社”, “2”表示“经济社”, “3”表示“公司”。 | |
- `sqdwdbr`: 字符串类型,用于存储账套单位的单位代表人。 | |
- `sqdwdz`: 字符串类型,用于存储账套单位的单位地址。 | |
- `zucode`: 字符串类型,用于存储账套单位的统一社会信用代码。 | |
### 资产表(ht_land) | |
资产表用于存储土地资产的信息。以下是该表中每个字段的含义: | |
- `id`: 这是一个整数类型的自增字段。 | |
- `title`: 字符串类型,用于存储资产的标题。 | |
- `distNo`: 字符串类型,用于存储地区编号,这个字段可以和地区表关联,以实现是哪个地区的资产。 | |
- `distName`: 字符串类型,用于存储地区的名称。 | |
- `landNo`: 字符串类型,用于存储资产的编号。 | |
- `landName`: 字符串类型,用于存储资产的名称。 | |
- `zcdl`: 字符串类型,用于存储资产的类型,可能的值有,实物资产类型,物业资产类型,资源性资产类型,其他资产类型。 | |
- `area`: 小数类型,用于存储资产所占面积。 | |
- `unit`: 字符串类型,用于存储资产的面积单位,可能的值有:亩、平方米等。 | |
### 收益及收益分配表(Rep_01) | |
收益及收益分配表用于存储各项指标收益情况。以下是该表中每个字段的含义: | |
- `serial`: 这是一个整数类型的自增字段。 | |
- `Zth`: 字符串类型,用于存储账套单位编号,和账套表的`ztId`关联,表示是哪个账套单位下的”收益及收益分配“ 情况。 | |
- `YEARS`: 整数类型,用于存储“收益及收益分配”发生的年份。 | |
- `MONTHS`: 整数类型,用于存储“收益及收益分配”发生的月份。 | |
- `C1`: 字符串类型,用于存储“收益及收益分配”的指标名称,比如有“一、经营收入”, “二、经营收益”,“补助收入” 等项目。 | |
- `C2`: 整数类型,用于存储“收益及收益分配”的表格显示的排序行次。 | |
- `C3`: 小数类型,用于存储“收益及收益分配”指标对应的本月发生金额,如果“C1”的指标是“补助收入”,并且“MONTHS”是“2”,就表示“2”月的“补助收入”的金额。 | |
- `C4`: 小数类型,用于存储“收益及收益分配”指标对应的本年发生金额,如果“C1”的指标是“补助收入”,并且“YEARS”是“2022”,就表示“2022”年的“补助收入”的金额。 | |
### 交易表(proj) | |
交易表用于存储资产交易的情况。以下是该表中每个字段的含义: | |
- `id`: 这是一个整数类型的自增字段。 | |
- `title`: 字符串类型,用于存储交易的项目名称。 | |
- `prj_no`: 字符串类型,用于存储交易的编号。 | |
- `distno`: 字符串类型,用于存储地区编号,这个字段可以和地区表关联,以实现是哪个地区的资产。 | |
- `distname`: 字符串类型,用于存储地区的名称。 | |
- `landno`: 字符串类型,用于存储资产的编号,该字段是一个外键,关联资产表的资产编号,指向资产表中的相应记录。 | |
- `landname`: 字符串类型,用于存储资产的名称。 | |
- `jyjffs`: 字符串类型,用于存储交易方式,列如:电子暗投,电子竞投,公开暗投,公开明投,协商谈判,续约,综合评审。 | |
- `totalAmount`: 小数类型,交易金额,单位是元。 |
三资 DDL(分段标识为:“;”):
CREATE TABLE `dist` ( | |
`id` INT NOT NULL AUTO_INCREMENT, | |
`distno` VARCHAR(32) NOT NULL, | |
`distname` VARCHAR(255) NOT NULL, | |
`parentDistNo` VARCHAR(32), | |
`parentDistName` VARCHAR(255) | |
PRIMARY KEY (`id`), | |
UNIQUE KEY `uk_distno` (`distno`) | |
); | |
CREATE TABLE `ht_land` ( | |
`id` INT NOT NULL AUTO_INCREMENT, | |
`title` VARCHAR(32), | |
`distNo` VARCHAR(32) NOT NULL, | |
`distName` VARCHAR(32), | |
`landNo` VARCHAR(255) NOT NULL, | |
`landName` VARCHAR(255), | |
`area` decimal(18,4), | |
`unit` VARCHAR(255), | |
PRIMARY KEY (`id`), | |
UNIQUE KEY `uk_landNo` (`landNo`), | |
FOREIGN KEY (`distno`) REFERENCES `dist` (`distno`) | |
); | |
CREATE TABLE `zt` ( | |
`id` INT NOT NULL AUTO_INCREMENT, | |
`distno` VARCHAR(32) NOT NULL, | |
`distname` VARCHAR(32), | |
`ztId` VARCHAR(32) NOT NULL, | |
`ztName` VARCHAR(255), | |
`ztTypeId` INT, | |
`sqdwdbr` VARCHAR(255), | |
`sqdwdz` VARCHAR(255), | |
`zucode` VARCHAR(255), | |
PRIMARY KEY (`id`), | |
UNIQUE KEY `uk_ztId` (`ztId`), | |
FOREIGN KEY (`distno`) REFERENCES `dist` (`distno`) | |
); | |
CREATE TABLE `Rep_01` ( | |
`SERIAL` INT NOT NULL AUTO_INCREMENT, | |
`ZTH` VARCHAR(32) NOT NULL, | |
`YEARS` INT, | |
`MONTHS` INT, | |
`C1` VARCHAR(255), | |
`C2` VARCHAR(255), | |
`C3` decimal(18,4), | |
`C4` decimal(18,4), | |
PRIMARY KEY (`id`), | |
FOREIGN KEY (`ZTH`) REFERENCES `zt` (`ztId`) | |
); | |
CREATE TABLE `proj` ( | |
`id` INT NOT NULL AUTO_INCREMENT, | |
`title` VARCHAR(32) NOT NULL, | |
`prj_no` VARCHAR(255), | |
`distno` VARCHAR(32), | |
`distname` VARCHAR(255), | |
`landno` VARCHAR(255), | |
`landname` VARCHAR(255), | |
`jyjffs` VARCHAR(64), | |
`totalAmount` decimal(18,4), | |
PRIMARY KEY (`id`), | |
UNIQUE KEY `uk_prj_no` (`prj_no`), | |
FOREIGN KEY (`distno`) REFERENCES `dist` (`distno`) | |
); |
sql 示列(分段标识为:“#”):
查询某个地区下的所有账套单位?, select * from zt where distno like (select distno + '%' from dist where distname = '某个地区'); |
测试检索效果如图,可以看的出来正确的检索出来了:

# 工作流
流程还是之前的流程,只是 知识库引用 还有 大模型解析返回sql 需要调整提示词
大致流程:开始 -> 知识检索 -> 大模型解析返回 sql -> 问题分类(对业务不相关问题的处理) -> 数据库查询 -> json 解析获取数据 -> 大模型结合问题和答案回答 -> 回复用户
流程图如下:

新建一个
工作流添加
知识库节点,引入上面添加的三个知识库添加大模型解析知识库内容生成 sql,为了更好的生成对应业务的 sql,需要添加提示词(可以根据自己的要求更改)
这里有改动:# 角色 你是一个SQL专家或者数据库分析师, 根据检索到{{#context#}}以及用户输入的{{#sys.query#}}内容,生成对应的查询sql,sql语句必须经过严格的校验: # 硬性要求 1.严格使用检索到的表字段 2.确保SQLSERVER语法兼容 3.仅输出最终结果sql语句的txt文本,不要加任何信息 4.禁止中间过程输出 5.返回纯SQL文本内容, 不要代码块 6.表名称要去掉特殊符号 #编写SQL时的注意事项: 1.务必根据上下文提供的数据表结构描述来编写SQL语句,确保仅使用数据表结构描述中提到的表名和字段名,并参考对字段的解释 2.确保SQL兼容SQL Server 2014 3.只用简体中文 4.只输出一个完整SQL语句,无注释,确保可直接执行并获得预期的结果 5.对于字符串和长文本类型的字段,除非用户有特别说明,否则都用LKE操作,而不是等于操作,例如:WHERE distno LIKEN'%关键词%',而不是WHERE distno='关键词' 6.除法处理:参考以下模板以避免错误: CASE WHEN 「除数] = 0 THEN 0 ELSE CAST([被除数] AS FLOAT)/[除数] END AS[结果列名] 8.适用场景: 生成查询SQL时需遵循上述规则 9.输出规范: 纯SQL文本无附加说明 10.技术限制: 不支持图像处理类的请求 11.返回纯SQL文本内容代码执行器这里加这个主要是对 sql 进行加密传输,其实也可以不加问题分类器主要是对用户提问的问题进行分类,如果是不相关的问题,则做其他处理,如图:![]()
http 请求节点这一步主要是传入sql调 api 接口进行数据查询这里其实可以用
Flask具体使用如下:运行一个
Flask服务#安装
pip install Flask
pip install pyodbc添加并运行下面的脚本:
from flask import Flask, request, jsonify
import pyodbcapp = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # 设置 JSON_AS_ASCII 为 False
# 数据库连接配置# 不同的数据库连接字符串格式可能不同,这里以 SQL Server 为例DB_CONNECTION_STRING = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=192.168.0.150;DATABASE=zcjy_hpq;UID=sa;PWD=Ghj38259201"
@app.route('/execute_query', methods=['POST'])
# 定义一个函数,用于执行 SQL 查询def execute_query():
try:
data = request.get_json()
sql_query = data.get('sql_query')
if not sql_query:
return jsonify({"error": "Missing sql_query parameter"}), 400
# 创建数据库连接conn = pyodbc.connect(DB_CONNECTION_STRING, timeout=60) # 增加超时时间
cursor = conn.cursor()
# 执行 SQL 查询cursor.execute(sql_query)
# 获取结果columns = [column[0] for column in cursor.description]
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
cursor.close()
conn.close()
# 直接返回 JSON 结果return jsonify(results), 200 # 返回 200 状态码
except Exception as e:
app.logger.error(f"Error occurred: {e}") # 记录错误
return jsonify({"error": str(e)}), 500 # 返回 500 状态码
if __name__ == '__main__':
# 启动 Flask 应用,监听端口为 5003app.run(host='0.0.0.0', port=5003, debug=True)
运行:
D:\大数据>python sqlserver.py
* Serving Flask app 'sqlserver'* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5003
* Running on http://192.168.0.61:5003
Press CTRL+C to quit
* Restarting with stat* Debugger is active!* Debugger PIN: 696-647-281在节点添加一下内容:
import urllib.request
import jsondef main(sql_query: str) -> dict:
# Flask 服务端点,这个服务运行在后台,用途是接收一个 sql 语句,提交给数据库查询并获得结果api_url = "http://192.168.0.61:5003/execute_query"
# 请求体payload = {
"sql_query": sql_query
}# 将请求体转换为 JSON 字符串data = json.dumps(payload).encode('utf-8')
# 创建请求对象req = urllib.request.Request(api_url, data=data, headers={"Content-Type": "application/json"})
try:
# 发送请求并获取响应with urllib.request.urlopen(req) as response:
# 读取并解码响应result_data = json.loads(response.read().decode('utf-8'))
# 将结果转换为字符串格式result_str = json.dumps(result_data, ensure_ascii=False) # 确保中文字符正常显示
return {'result': result_str} # 返回字符串类型的结果
except Exception as e:
raise Exception(f"Error: {str(e)}")
json 解析节点这一步主要是对查询出来的数据进行解析,获取原始的数据添加大模型节点这里的节点是为了对用户的问题,和结果做回答,这里可以用私有的模型,保证数据的隐私安全。添加一些提示词让大模型更自然的回答如下:
通过{{#context#}}的结果内容回答用户提问的{{#sys.query#}}的问题 注意事项: #数据呈现、解读和分析要求: 1.所有数据已符合用户问题中的条件(如地区名称、日期范围) 2.直接使用提供的数据分析,不质疑数据是否符合条件 3.不需再次筛选或确认数据类别/时间范围 4.数据为[]或空时,回复"没有查询到相关数据",不得编造数据 5.列出详细数据,优先以表格方式列出数据,如果数据超过10时,除非用户问题中包含明确要求列出全部数据的情形外,你只需要随机列出5条有代表性的的记录,否则需要列出全部记录 6.当有记录被省略时,必须作出说明 7.对数据进行概览和总结,必须包括原始数据的总记录数 8.识别趋势、异常,并提供分析和建议 9.对用户输入的内容进行识别和判断,如果内容涉及政治、时事、社会问题以及违背道德和法律法规的情形,一律输出:”您提出的问题超出我应当回答的范围,请询问与公司业务相关的问题,否则我无法作出回答“ #其他注意事项 1、严格按照上下文内容回答直接返回最后直接返回
# 对比图
没改之前的:

有点差强人意
改之后的:

查询出来的数据都是准确的!
# 扩展
# 自定义聊天机器人界面
如果想自定义对话框样式,可以看官网的另一个模板项目 webapp-conversation MIT 协议,可以自由修改
拉取代码到本地后:
在当前目录下创建一个名为 .env.local 的文件,并从 .env.example 复制内容。设置以下配置项:
APP ID: 这是您应用的唯一标识符。您可以在应用详情页的 URL 中找到它。
例如,在 URL https://cloud.dify.ai/app/xxx/workflow 中,xxx 就是您的 APP ID。
NEXT_PUBLIC_APP_ID=
APP API 密钥: 这是用于验证应用 API 请求的密钥。
您可以在应用的「API 访问」页面,点击右上角的「API 密钥」按钮生成。
NEXT_PUBLIC_APP_KEY=
APP URL: 这是 API 的基础 URL。如果您使用 Dify 云服务,请设置为:https://api.dify.ai/v1
NEXT_PUBLIC_API_URL=
更多配置 config/index.ts :
export const APP_INFO: AppInfo = {
title: 'Chat APP',
description: '',
copyright: '',
privacy_policy: '',
default_language: 'zh-Hans'
}
export const isShowPrompt = true
export const promptTemplate = ''
