【DataLoom】智能问数 - 自然语言与数据库交互

news2024/10/5 8:32:49

探索DataLoom的智能问数功能:简化数据库查询

在数据驱动的决策制定中,数据库查询是获取洞察的关键步骤。但是,传统的数据库查询方法往往复杂且技术性强,这限制了非技术用户的使用。DataLoom的智能问数功能正是为了解决这一问题而设计的。本文将详细介绍这一功能,并展示其背后的代码实现。

DataLoom简介

DataLoom是一个创新的数据管理平台,旨在通过提供直观的界面和强大的后端处理能力,简化数据查询和分析过程。我们的目标是让数据查询变得简单,让每个人都能轻松地从数据中获取洞察。

智能问数功能

在这里插入图片描述

在这里插入图片描述

智能问数是DataLoom的核心功能之一,它允许用户通过自然语言输入查询请求,系统会自动将其转换为SQL语句并执行。这一功能极大地降低了技术门槛,使得即使是没有数据库背景的用户也能快速获取所需数据。

核心流程图

在这里插入图片描述

核心代码
ChatForSQLRequest类
@Data
public class ChatForSQLRequest {
    /**
     * 模型Id
     */
    private Long chatId;
    /**
     * 询问的数据
     */
    private String question;
}

这是一个简单的Java Bean类,用于封装用户请求智能问数时发送的数据。它包含两个属性:chatId(模型Id)和question(询问的数据)。

userChatForSQL方法
public void userChatForSQL(ChatForSQLRequest chatForSQLRequest, User loginUser) {
    Long chatId = chatForSQLRequest.getChatId();
    String question = chatForSQLRequest.getQuestion();
    // 1. 获取模型ID
    Chat chat = chatService.getById(chatId);
    ThrowUtils.throwIf(chat == null, ErrorCode.PARAMS_ERROR, "不存在该助手");
    // 2. 获取数据源所有的元数据
    Long datasourceId = chat.getDatasourceId();
    List<AskAIWithDataTablesAndFieldsRequest> dataTablesAndFieldsRequests = getAskAIWithDataTablesAndFieldsRequests(loginUser, datasourceId);
    // 3. 构造请求AI的输入
    String input = buildAskAISQLInput(dataTablesAndFieldsRequests, question);
    // 4. 持久化消息
    ChatHistory user_q = new ChatHistory();
    user_q.setChatRole(ChatHistoryRoleEnum.USER.getValue());
    user_q.setChatId(chatId);
    user_q.setModelId(chat.getModelId());
    user_q.setContent(question);
    chatHistoryService.save(user_q);
    // 5. 利用webSocket发送消息通知开始
    AskSQLWebSocketMsgVO askSQLWebSocketMsgVO = new AskSQLWebSocketMsgVO();
    askSQLWebSocketMsgVO.setType("start");
    askSQLWebSocket.sendOneMessage(loginUser.getId(), askSQLWebSocketMsgVO);
    // 6. 询问AI,获取返回的SQL
    String sql = aiManager.doAskSQLWithKimi(input, LIMIT_RECORDS);
    // 7. 执行SQL,并得到返回的结果
    QueryAICustomSQLVO queryAICustomSQLVO = null;
    try {
        queryAICustomSQLVO = buildUserChatForSqlVO(datasourceId, sql);
    } catch (Exception e) { // 防止异常发生,前端还继续等待接收数据
        if (e instanceof SQLException) { // 记录异常
            queryAICustomSQLVO = new QueryAICustomSQLVO();
            queryAICustomSQLVO.setSql(sql);
            ChatHistory chatHistory = ChatHistory.builder()
            .chatRole(ChatHistoryRoleEnum.MODEL.getValue())
            .chatId(chatId)
            .modelId(chat.getModelId())
            .status(ChatHistoryStatusEnum.FAIL.getValue())
            .execMessage("查询异常")
            .content(JSONUtil.toJsonStr(queryAICustomSQLVO))
            .build();
            chatHistoryService.updateById(chatHistory);
        }
        notifyMessageEnd(loginUser.getId());
        return;
    }
    // 8. 将查询的结果存放在数据库中
    ChatHistory chatHistory = new ChatHistory();
    chatHistory.setChatRole(ChatHistoryRoleEnum.MODEL.getValue());
    chatHistory.setChatId(chatId);
    chatHistory.setModelId(chat.getModelId());
    // 9. 存储结果类JSON字符串
    chatHistory.setContent(JSONUtil.toJsonStr(queryAICustomSQLVO));
    try {
        chatHistoryService.save(chatHistory);
    } catch (Exception e) {
        notifyMessageEnd(loginUser.getId());
        return;
    }
    // 10. 利用webSocket发送消息通知
    AskSQLWebSocketMsgVO res = AskSQLWebSocketMsgVO.builder()
    .res(queryAICustomSQLVO.getRes())
    .columns(queryAICustomSQLVO.getColumns())
    .type("running")
    .sql(sql)
    .build();
    askSQLWebSocket.sendOneMessage(loginUser.getId(), res);
    // 11. 通知结束
    notifyMessageEnd(loginUser.getId());
}

这个方法是处理用户SQL查询请求的核心逻辑。它执行以下步骤:

  1. 验证模型ID是否存在。
  2. 获取数据源的所有元数据。
  3. 构造请求AI的输入。
  4. 持久化用户的消息。
  5. 通过WebSocket通知前端开始处理。
  6. 询问AI,获取返回的SQL语句。
  7. 执行SQL并获取结果。
  8. 将查询结果持久化。
  9. 存储结果类为JSON字符串。
  10. 通过WebSocket发送查询结果。
  11. 通知查询结束。
getAskAIWithDataTablesAndFieldsRequests方法
/**
 * 查询对应数据源所有元数据(表信息、表字段)
 * @param loginUser
 * @param datasourceId
 * @return
 */
private List<AskAIWithDataTablesAndFieldsRequest> getAskAIWithDataTablesAndFieldsRequests(User loginUser, Long datasourceId) {
    List<CoreDatasetTable> tables = coreDatasourceService.getTablesByDatasourceId(datasourceId, loginUser);
    ThrowUtils.throwIf(tables.isEmpty(), ErrorCode.PARAMS_ERROR, "数据源暂无数据");
    List<AskAIWithDataTablesAndFieldsRequest> dataTablesAndFieldsRequests = new ArrayList<>();
    tables.forEach(table -> {
        // 查询所有字段
        LambdaQueryWrapper<CoreDatasetTableField> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CoreDatasetTableField::getDatasetTableId, table.getId());
        List<CoreDatasetTableField> tableFields = coreDatasetTableFieldService.list(wrapper);
        AskAIWithDataTablesAndFieldsRequest askAIWithDataTablesAndFieldsRequest = AskAIWithDataTablesAndFieldsRequest.builder()
        .tableId(table.getId())
        .tableComment(table.getName())
        .tableName(table.getTableName())
        .coreDatasetTableFieldList(tableFields)
        .build();
        dataTablesAndFieldsRequests.add(askAIWithDataTablesAndFieldsRequest);
    });
    return dataTablesAndFieldsRequests;
}

这个方法用于查询给定数据源的所有表信息和表字段。它遍历所有表,为每个表查询字段信息,并构建一个包含这些信息的请求列表。

buildAskAISQLInput方法
/**
 * 构造智能问数的问题
 * @param dataTablesAndFieldsRequests 数据源元数据
 * @param question
 * @return
 * 示例:
 * 分析需求:%s,
 * [
 * {表名: %s, 表注释: %s, 字段列表:[{%s}、{%s}]}
 * {表名: %s, 表注释: %s, 字段列表:[{%s}、{%s}]}
 * ]
 */
private String buildAskAISQLInput(List<AskAIWithDataTablesAndFieldsRequest> dataTablesAndFieldsRequests, String question) {
    StringBuilder res = new StringBuilder();
    // 1. 构造需求
    res.append(String.format(ANALYSIS_QUESTION, question));
    res.append(SPLIT);
    // 2. 构造表与字段信息
    StringBuilder tablesAndFields = new StringBuilder();
    dataTablesAndFieldsRequests.forEach(tableAndFields -> {
        // 构造当前表字段列表
        StringBuilder tableFieldsInfo = new StringBuilder();
        List<CoreDatasetTableField> fieldList = tableAndFields.getCoreDatasetTableFieldList();
        fieldList.forEach(field -> {
            tableFieldsInfo.append(String.format(FIELDS_INFO, field.getOriginName(), field.getName(), field.getType()));
            tableFieldsInfo.append(SPLIT);
        });
        // 构造当前表信息
        String tableFieldsInfoList = String.format(LIST_INFO, tableFieldsInfo);
        tablesAndFields.append(String.format(TABLE_INFO, tableAndFields.getTableName(), tableAndFields.getTableComment(), tableFieldsInfoList));
        tableFieldsInfo.append(SPLIT);
    });
    res.append(String.format(TABLES_AND_FIELDS_PART, tablesAndFields));
    return res.toString();
}

这个方法用于构造智能问数的问题。它将用户的问题和数据源的元数据结合起来,形成一个格式化的字符串,该字符串将作为AI的输入。

doAskSQLWithKimi方法
/**
 * 执行智能问数
 * @param message 构造的输入
 * @param limitSize select 结果限制的行数
 * @return
 */
public String doAskSQLWithKimi(String message, int limitSize) {
    String SQLPrompt = "你是一个MySQL数据库专家,专门负责根据查询需求得出SQL查询语句,接下来我会按照以下固定格式给你提供内容: \n" +
    "分析需求:{分析需求或者目标} \n" +
    "所有的数据表元数据:[{数据库表名、表注释、数据库表的字段、注释以及类型}] \n" +
    "请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释),并且只生成Select语句!!!, 请严格按照数据表元数据中存在的数据表和字段,不要查询不存在的表和字段\n" +
    "要求select的结果不超过" + limitSize + "行";
    List<Message> messages = CollUtil.newArrayList(
        new Message(RoleEnum.system.name(), SQLPrompt),
        new Message(RoleEnum.user.name(), message)
    );
    return moonshotAiClient.chat("moonshot-v1-32k",messages);
}

这个方法用于执行智能问数。它构造一个包含分析需求和数据表元数据的消息,然后通过调用AI客户端来获取SQL查询语句。

buildUserChatForSqlVO方法
/**
 * 执行SQL并封装智能问数返回类
 * @param datasourceId 数据源id
 * @param sql 执行sql
 * @return 智能问数返回类
 */
private QueryAICustomSQLVO buildUserChatForSqlVO(Long datasourceId, String sql) throws SQLException {
    return datasourceEngine.execSelectSqlToQueryAICustomSQLVO(datasourceId, sql);
}

这个方法用于执行SQL语句并将结果封装到QueryAICustomSQLVO对象中。它调用execSelectSqlToQueryAICustomSQLVO方法,传入数据源ID和SQL语句,然后返回查询结果。

execSelectSqlToQueryAICustomSQLVO方法
/**
 * 执行SQL语句并将列集合和记录犯规
 * @param datasourceId 数据源id
 * @param sql sql语句
 * @param parameters 参数
 * @return
 */
public QueryAICustomSQLVO execSelectSqlToQueryAICustomSQLVO(Long datasourceId, String sql, Object... parameters) throws SQLException {
    int dsIndex = (int) (datasourceId % (dataSourceMap.size()));
    // 获取对应连接池
    DataSource dataSource = dataSourceMap.get(dsIndex);
    QueryAICustomSQLVO queryAICustomSQLVO = new QueryAICustomSQLVO();
    // 所有列
    List<String> columns = new ArrayList<>();
    // 所有结果
    List<Map<String, Object>> res = new ArrayList<>();
    Connection connection = dataSource.getConnection();
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // Set parameters to prevent SQL injection
    for (int i = 0; i < parameters.length; i++) {
        preparedStatement.setObject(i + 1, parameters[i]);
    }
    ResultSet rs = preparedStatement.executeQuery();
    // Execute the query or update
    // 处理查询结果
    ResultSetMetaData rsmd = rs.getMetaData();
    int columnCount = rsmd.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
        columns.add(rsmd.getColumnName(i));
    }
    while (rs.next()) {
        Map<String, Object> resMap = new HashMap<>();
        for (int i = 1; i <= columnCount; i++) {
            resMap.put(rsmd.getColumnName(i), rs.getString(i));
        }
        res.add(resMap);
    }
    queryAICustomSQLVO.setSql(sql);
    queryAICustomSQLVO.setColumns(columns);
    queryAICustomSQLVO.setRes(res);
    return queryAICustomSQLVO;
}

这个方法用于执行SQL查询并将结果集转换为一个包含列名和记录的QueryAICustomSQLVO对象。它使用PreparedStatement来设置参数,执行查询,并遍历结果集,将每一行的数据存储到一个Map中,然后将这些Map添加到结果列表中。

这些代码片段共同构成了DataLoom智能问数功能的核心实现。每个片段都扮演着处理用户请求、与数据库交互、以及与AI服务通信的重要角色。

未来展望

我们对DataLoom的未来充满期待。我们计划引入更多的智能功能,这些功能将在下面的几篇文章中介绍,例如智能仪表盘智能图表分析报告

项目快速启动
  • 见GitHub:DataLoom 仓库
  • 见Gitee地址:Gitee 仓库
如何贡献

如果你对 DataLoom 感兴趣并想做出贡献,欢迎提交 issue 或 Pull Request。我们非常欢迎开发者一起加入,共同改进这个项目。

  • GitHub 地址:DataLoom 仓库
  • Gitee地址:Gitee 仓库
  • 你可以通过创建 Issue 来报告问题,或通过提交 PR 来贡献代码。

希望这篇文章能够激发你对 DataLoom 项目的兴趣!如果你喜欢这个项目,请给我们一个 Star ⭐️,这对我们来说意义重大!

请持续关注,后续文章也会发一些有关项目功能设计亮点介绍

项目地址:DataLoom GitHub 仓库

项目问题通过下面的联系方式进行沟通
邮箱:hardork@163.com
WX号: _hardork

如果需要项目文档📄,可以联系WX号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【WebGis开发 - Cesium】如何确保Cesium场景加载完毕

目录 引言一、监听场景加载进度1. 基础代码2. 加工代码 二、进一步封装代码1. 已知存在的弊端2. 封装hooks函数 三、使用hooks方法1. 先看下效果2. 如何使用该hooks方法 三、总结 引言 本篇为Cesium开发的一些小技巧。 判断Cesium场景是否加载完毕这件事是非常有意义的。 加载…

Spring Boot中线程池使用

说明&#xff1a;在一些场景&#xff0c;如导入数据&#xff0c;批量插入数据库&#xff0c;使用常规方法&#xff0c;需要等待较长时间&#xff0c;而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先&#xff0c;创建一个Spr…

自动驾驶系列—颠覆未来驾驶:深入解析自动驾驶线控转向系统技术

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【树莓派系列】交叉编译工具、交叉编译链的安装使用

交叉编译工具、交叉编译链的安装使用 文章目录 交叉编译工具、交叉编译链的安装使用一、交叉编译1.1什么是交叉编译1.2为什么要交叉编译1.3宿主机和目标机 二、搭建交叉编译工作环境2.1安装工具链2.2配置环境变量● 配置临时环境变量● 配置永久环境变量 三、交叉编译宿主机和目…

NASA:Seasat-A 散射计(SASS)得出的风速和风向矢量数据集

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 SEASAT SCATTEROMETER DEALIASED OCEAN WIND VECTORS (Atlas) 简介 SEASAT散射计反回波强度&#xff08;scattering&#xff09;提供了对海面风速和风向的估计。SEASAT散射计被用来获取海面风场的信…

LabVIEW提高开发效率技巧----调度器设计模式

在LabVIEW开发中&#xff0c;针对多任务并行的需求&#xff0c;使用调度器设计模式&#xff08;Scheduler Pattern&#xff09;可以有效地管理多个任务&#xff0c;确保它们根据优先级或时间间隔合理执行。这种模式在需要多任务并发执行时特别有用&#xff0c;尤其是在实时系统…

【算法】---归并排序(递归非递归实现)

参考 左程云算法 算法导论 前言 本篇介绍 归并排序分治法 前置知识 了解递归&#xff0c; 了解数组。 引入 归并排序 归并排序最早是由公认的现代计算机之父John von Neumann发明的&#xff0c; 这是一种典型的分治思想应用。 我们先介绍分治思想 分治思想 分治思想的…

java:pdfbox 3.0 去除扫描版PDF中文本水印

官网下载 https://pdfbox.apache.org/download.html下载 pdfbox-app-3.0.3.jar cd D:\pdfbox 运行 java -jar pdfbox-app-3.0.3.jar java -jar pdfbox-app-3.0.3.jar Usage: pdfbox [COMMAND] [OPTIONS] Commands:debug Analyzes and inspects the internal structu…

(C语言贪吃蛇)7.显示贪吃蛇完整身体改进

前言 上节显示了贪吃蛇身子的三个节点&#xff0c;但是吃了食物后蛇身变长应该如何操作&#xff0c;本节给出答案。 一、贪吃蛇身体是什么&#xff1f; 使用链表这个数据结构来动态的显示贪吃蛇的身体。 二、对贪吃蛇身体进行改进 1.贪吃蛇身子显示 代码如下&#xff1a; …

信息学奥赛使用的编程IDE:Dev-C++ 安装指南

信息学奥赛&#xff08;NOI&#xff09;作为全国性的编程竞赛&#xff0c;要求参赛学生具备扎实的编程能力&#xff0c;而熟练使用适合的编程工具则是学习与竞赛的基础。在众多编程环境中&#xff0c;Dev-C IDE 因其简洁、轻量、支持C编程等特点&#xff0c;成为许多参赛者的常…

最新版的dubbo服务调用(用nacos做注册中心用)

一、介绍 1.1、什么是 nacos Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&a…

Java 每日一刊(第21期):反射机制

文章目录 前言动态插件系统面临的问题如何在运行时动态加载和调用类与方法设计模式的尝试引入反射 Java 反射的核心概念Class 类Constructor 类Method 类Field 类 Java 反射的应用场景框架开发插件系统序列化与反序列化动态代理测试工具 反射的优缺点反射实战动态加载类并调用方…

【hot100-java】【将有序数组转换为二叉搜索树】

二叉树篇 BST树 递归直接实现。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNo…

【C++差分数组】2381. 字母移位 II|1793

本文涉及知识点 C差分数组 LeetCode2381. 字母移位 II 给你一个小写英文字母组成的字符串 s 和一个二维整数数组 shifts &#xff0c;其中 shifts[i] [starti, endi, directioni] 。对于每个 i &#xff0c;将 s 中从下标 starti 到下标 endi &#xff08;两者都包含&#…

STM32的串行外设接口SPI

一、SPI简介 1.SPI总线特点 &#xff08;1&#xff09;四条通信线 SPI需要SCK、MISO、MOSI、NSS四条通信线来完成数据传输 &#xff0c;每增加一个从机&#xff0c;多一条NSS通信线。 &#xff08;2&#xff09;多主多从 SPI总线允许有多个主机和多个从机。 &#xff08;3&…

再见 ESNI,你好 ECH!—— ECH的前世今生

译者注&#xff1a;2024 年 9 月 25 日&#xff0c;Cloudflare 宣布再次推出 ECH 功能。借此契机&#xff0c;本人翻译了 Cloudflare 介绍 ECH 的博文 Good-bye ESNI, hello ECH! &#xff0c;以便科普ECH的发展历程。 现代互联网上的大多数通信都经过加密&#xff0c;以确保其…

Flink源码剖析

写在前面 最近一段时间都没有更新博客了&#xff0c;原因有点离谱&#xff0c;在实现flink的两阶段提交的时候&#xff0c;每次执行自定义的notifyCheckpointComplete时候&#xff0c;好像就会停止消费数据&#xff0c;完成notifyComplete后再消费数据&#xff1b;基于上述原因…

在Stable Diffusion WebUI中安装SadTalker插件时几种错误提示的处理方法

SD中的插件一般安装比较简单&#xff0c;但也有一些插件安装会比较难。比如我在安装SadTalker时&#xff0c;就遇到很多问题&#xff0c;一度放弃了&#xff0c;后来查了一些网上攻略&#xff0c;自己也反复查看日志&#xff0c;终于解决&#xff0c;不吐不快。 一、在Stable …

ElasticSearch高级功能详解与读写性能调优

目录 1. ES数据预处理 1.1 Ingest Node Ingest Node VS Logstash 1.2 Ingest Pipeline Pipeline & Processor 创建pipeline 使用pipeline更新数据 借助update_by_query更新已存在的文档 1.3 Painless Script Painless的用途&#xff1a; 通过Painless脚本访问字…

(17)MATLAB使用伽马(gamma)分布生成Nakagami-m分布的方法1

文章目录 前言一、使用伽马分布生成Nakagami分布随机变量的方法一二、MATLAB仿真代码后续 前言 MATLAB在R2013a版本中引入Nakagami分布对象&#xff0c;可以用来生成Nakagami随机变量。但是在更早的MATLAB版本中&#xff0c;并没有可以直接生成 Nakagami分布的随机变量的内置的…