# 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 解析获取数据 -> 大模型结合问题和答案回答 -> 回复用户

流程图如下:

  1. 新建一个 工作流

  2. 添加 知识库 节点,引入上面添加的三个知识库

  3. 添加大模型解析知识库内容生成 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文本内容
    
    
  4. 代码执行器 这里加这个主要是对 sql 进行加密传输,其实也可以不加

  5. 问题分类器 主要是对用户提问的问题进行分类,如果是不相关的问题,则做其他处理,如图:

  6. http 请求节点 这一步主要是传入 sql 调 api 接口进行数据查询

    这里其实可以用 Flask 具体使用如下:

    运行一个 Flask 服务

    #安装
    pip install Flask
    pip install pyodbc

    添加并运行下面的脚本:

    from flask import Flask, request, jsonify
    import pyodbc
    app = 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 应用,监听端口为 5003
      app.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 json
    def 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)}")
  7. json 解析节点 这一步主要是对查询出来的数据进行解析,获取原始的数据

  8. 添加大模型节点 这里的节点是为了对用户的问题,和结果做回答,这里可以用私有的模型,保证数据的隐私安全。

    添加一些提示词让大模型更自然的回答如下:

    通过{{#context#}}的结果内容回答用户提问的{{#sys.query#}}的问题
    
    注意事项:
    #数据呈现、解读和分析要求:
    1.所有数据已符合用户问题中的条件(如地区名称、日期范围)
    2.直接使用提供的数据分析,不质疑数据是否符合条件
    3.不需再次筛选或确认数据类别/时间范围
    4.数据为[]或空时,回复"没有查询到相关数据",不得编造数据
    5.列出详细数据,优先以表格方式列出数据,如果数据超过10时,除非用户问题中包含明确要求列出全部数据的情形外,你只需要随机列出5条有代表性的的记录,否则需要列出全部记录
    6.当有记录被省略时,必须作出说明
    7.对数据进行概览和总结,必须包括原始数据的总记录数
    8.识别趋势、异常,并提供分析和建议
    9.对用户输入的内容进行识别和判断,如果内容涉及政治、时事、社会问题以及违背道德和法律法规的情形,一律输出:”您提出的问题超出我应当回答的范围,请询问与公司业务相关的问题,否则我无法作出回答“
    
    #其他注意事项
    1、严格按照上下文内容回答
    
    
  9. 直接返回 最后直接返回

# 对比图

没改之前的:

有点差强人意

改之后的:

查询出来的数据都是准确的!

# 扩展

# 自定义聊天机器人界面

如果想自定义对话框样式,可以看官网的另一个模板项目 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 = ''