# ChatBI **Repository Path**: yfwork/chat-bi ## Basic Information - **Project Name**: ChatBI - **Description**: 一个chatBI应用的简单实现,前端用html简单演示,后端使用java+springboot实现。使用者只需修改数据库及openai便能快速接入你的项目中!! - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2025-07-11 - **Last Updated**: 2025-07-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # chatBI总结 # 目录说明 chatBIdemo:后端工程文件 images:md文件中的图片 chatBI调研历程:一步一步的干活 README:说明文档 前端.html:前端文件 --- ## 效果展示图 点击文件夹上的html文件,然后询问关于自己数据库上的问题 ![image-20250303142906513](images/image-20250303142906513.png) # 一,前言 ## 为什么做 公司是tob的业务,让我做个chatBI调研,然后就写了这个小项目,大体是实现了,但是还是有很多优化空间。 ## chatBI是什么 传统商业智能(BI)工具长期面临两大挑战:**技术门槛过高**导致业务人员难以自主分析数据,**交互体验割裂**使得从数据查询到可视化呈现需要跨越多个专业系统。 ChatBI 项目应运而生,通过将自然语言处理(NLP)与 BI 系统深度融合,构建了一套"对话即分析"的智能解决方案。本系统突破性地实现了三大创新: 1. **零代码交互**:用户可通过自然语言直接查询数据,如输入"显示最近三个月各区域销售额趋势",系统自动生成可视化图表; 2. **动态元数据感知**:通过自动化元数据采集技术,实时感知数据结构变化,避免人工维护数据字典的繁琐; 3. **分析链路闭环**:集成数据查询、智能解读、可视化呈现于一体,5 秒内完成从问题提出到分析报告生成的全流程。 --- # 二,测试环境 1,语言:采用的java语言,springboot框架 2,ai接口:采用火山引擎 --- # 三,实现方案 ## 展望流程图: ![image-20250303114235425](images/image-20250303114235425.png) ![image-20250303114242164](images/image-20250303114242164.png) 只实现了主要的功能。 --- ## 开始构建项目 ### 打开idea ![image-20250303114633270](images/image-20250303114633270.png) ![image-20250303114732645](images/image-20250303114732645.png) ### 目录结构: ![image-20250303152920822](images/image-20250303152920822.png) ### 添加依赖: ```xml com.mysql mysql-connector-j runtime com.alibaba fastjson 1.2.76 com.volcengine volcengine-java-sdk-ark-runtime LATEST ``` ### application.yml **需要修改成你的配置** ``` server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/redonline?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true username: root password: 1234 ai: volc: api-key: "你的火山秘钥" base-url: "你的url" model-id: "你的model-id" system-prompt: "你是一个高级数据分析专家(ChatBI),请根据用户问题、数据库元数据和查询结果进行分析。" # 可选自定义 ``` ### 自动收集数据库信息代码 #### MetadataService : 执行数据库的搜集逻辑 ``` package com.llj.chatbidemo.ai; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.sql.*; import java.util.ArrayList; import java.util.List; //新版数据库元数据收集:只需有查询权限就可手机数据库信息 @Service public class MetadataService { @Autowired private JdbcTemplate jdbcTemplate; String dataBaseName = "redonline"; // 主入口方法 public JSONObject generateMetadata() throws SQLException { JSONObject metadata = new JSONObject(); metadata.put("version", "1.0.0"); JSONArray tables = new JSONArray(); // 获取所有表名 List tableNames = getTableNames(); for (String tableName : tableNames) { JSONObject tableInfo = new JSONObject(); tableInfo.put("physical_name", tableName); tableInfo.put("technical_comment", getTableComment(tableName)); tableInfo.put("first_row_sample", getFirstRowSample(tableName)); tables.add(tableInfo); } metadata.put("tables", tables); return metadata; } // 获取所有表名 private List getTableNames() throws SQLException { List tables = new ArrayList<>(); try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { DatabaseMetaData metaData = conn.getMetaData(); ResultSet rs = metaData.getTables(dataBaseName, null, "%", new String[]{"TABLE"}); while (rs.next()) { tables.add(rs.getString("TABLE_NAME")); } } return tables; } // 获取表注释 private String getTableComment(String tableName) throws SQLException { try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { DatabaseMetaData metaData = conn.getMetaData(); ResultSet rs = metaData.getTables(dataBaseName, null, tableName, new String[]{"TABLE"}); return rs.next() ? rs.getString("REMARKS") : ""; } } // 获取首行数据样本 private JSONObject getFirstRowSample(String tableName) { String sql = "SELECT * FROM " + tableName + " LIMIT 1"; try { return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { ResultSetMetaData meta = rs.getMetaData(); JSONObject row = new JSONObject(); for (int i = 1; i <= meta.getColumnCount(); i++) { row.put(meta.getColumnName(i), rs.getObject(i)); } return row; }); } catch (EmptyResultDataAccessException e) { return new JSONObject(); // 空表返回空对象 } } } ``` #### MetadataHolder:调用数据库收集流程,并对外提供数据库信息 ``` package com.llj.chatbidemo.config; import com.alibaba.fastjson.JSONObject; import com.llj.chatbidemo.ai.MetadataService; import org.springframework.stereotype.Component; import java.sql.SQLException; @Component public class MetadataHolder { private JSONObject metadata; // Fastjson类型 private final MetadataService service; // 获取最新元数据(线程安全) public JSONObject getMetadata() { return metadata; } public MetadataHolder(MetadataService collector) throws SQLException { this.service = collector; refresh(); // 启动时立即加载 } // 可选的定时刷新(例如每天凌晨更新) //@Scheduled(cron = "0 0 0 * * ?") public void refresh() throws SQLException { JSONObject jsonNode = service.generateMetadata(); this.metadata = jsonNode; } } ``` ### chatBI应用 #### ChatAITool: 封装好的ai工具类 ``` package com.llj.chatbidemo.config; import com.volcengine.ark.runtime.model.completion.chat.*; import com.volcengine.ark.runtime.service.ArkService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; @Component public class ChatAITool { @Value("${ai.volc.api-key}") private String apiKey; @Value("${ai.volc.base-url}") private String baseUrl; @Value("${ai.volc.model-id}") private String modelId; @Value("${ai.volc.system-prompt}") private String systemPrompt; private ArkService arkService; @PostConstruct private void init() { System.out.println(apiKey); this.arkService = ArkService.builder() .apiKey(apiKey) .baseUrl(baseUrl) .build(); } /** * 标准问答(自动包含系统提示) */ public String askQuestion(String userQuestion) { List messages = buildMessages(userQuestion); return executeChatCompletion(messages); } /** * 自定义消息列表问答 */ public String askWithMessages(List customMessages) { return executeChatCompletion(customMessages); } private List buildMessages(String userQuestion) { List messages = new ArrayList<>(); messages.add(ChatMessage.builder() .role(ChatMessageRole.SYSTEM) .content(systemPrompt) .build()); messages.add(ChatMessage.builder() .role(ChatMessageRole.USER) .content(userQuestion) .build()); return messages; } private String executeChatCompletion(List messages) { try { ChatCompletionRequest request = ChatCompletionRequest.builder() .model(modelId) .messages(messages) .build(); return (String) arkService.createChatCompletion(request) .getChoices() .stream() .findFirst() .map(choice -> choice.getMessage().getContent()) .orElseThrow(() -> new RuntimeException("AI响应为空")); } catch (Exception e) { throw new ChatException("AI服务调用失败", e); } } public void shutdown() { if (arkService != null) { arkService.shutdownExecutor(); } } // 自定义异常类 public static class ChatException extends RuntimeException { public ChatException(String message, Throwable cause) { super(message, cause); } } } ``` #### Result:返回结果封装 ``` package com.llj.chatbidemo.config; import java.io.Serializable; /** * 后端统一返回结果 * @param */ public class Result implements Serializable { private Integer code; //编码:1成功,0和其它数字为失败 private String msg; //错误信息 private T data; //数据 public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static Result success() { Result result = new Result(); result.code = 1; return result; } public static Result success(T object) { Result result = new Result(); result.data = object; result.code = 1; return result; } public static Result error(int exCode,String msg) { Result result = new Result(); result.msg = msg; result.code = exCode; return result; } } ``` #### ChatBIServiceImpl: chatBI的逻辑 ``` package com.llj.chatbidemo.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.llj.chatbidemo.config.ChatAITool; import com.llj.chatbidemo.config.MetadataHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class ChatBIServiceImpl { @Autowired private MetadataHolder metadataHolder; @Autowired private ChatAITool chatAITool; // 应能正常注入 @Autowired private JdbcTemplate jdbcTemplate; // 核心提示词模板(可根据业务扩展) private static final String ANALYSIS_PROMPT = """ 你是一个高级数据分析专家(ChatBI),请根据用户问题、数据库元数据和查询结果进行分析。 ### 数据库元数据(JSON格式): {metadata} ### 用户问题: {question} ### 最新查询结果(JSON): {result} 请按以下步骤思考: 1. 用1句话总结数据趋势 2. 指出2个关键数据点 3. 给出1个业务建议 4. 推荐1种可视化图表类型(柱状图、折线图等) 严格按照该JSON格式返回,不要输入其他多余的字符: { "summary": "...", "key_points": ["...", "..."], "recommendation": "...", "chart_type": "..." } """; // 自然语言转SQL public String naturalLanguageToSQL(String question) { String prompt = """ 根据数据库结构和用户问题生成规范的MySQL查询,严格遵循以下协议: ### 数据库元数据:%s ### 用户问题: %s ### 生成协议(必须满足): 1. 【语句结构】 ✓ 必须包含完整子句:SELECT → FROM → [JOIN] → WHERE → ORDER BY → LIMIT ✓ SELECT必须显式列出字段(禁用SELECT *) ✓ FROM必须指定主表 2. 【表关联】 ✓ 多表查询必须使用显式JOIN(INNER/LEFT) ✓ JOIN必须带ON条件 ✓ 必须为所有表定义别名 3. 【字段规则】 ✓ 至少包含1个业务描述字段 ✓ 时间相关查询必须选择时间字段 ✓ 统计类问题必须用聚合函数 4. 【条件处理】 ✓ 用户问题中的过滤值必须转为WHERE条件 5. 【结果规范】 ✓ 按业务逻辑排序(时间正序/倒序,数值大小等) ✓ LIMIT 10必须作为最后子句 ✓ 字符串值用单引号包裹 ### 输出要求: - 只用标准SQL,不要用方言 - 包含LIMIT 10 - 表名用反引号包裹 - 输出必须为纯SQL语句,不要包含任何其他字符(如```sql等代码块符号) - 输出只有SQL,不要解释 """.formatted(metadataHolder.getMetadata(), question); try { // 调用火山引擎AI return chatAITool.askQuestion(prompt); } catch (Exception e) { throw new RuntimeException("SQL生成失败: " + e.getMessage()); } } //根据用户问题与数据结果进行数据分析 public String analyzeData(String question, String sql) { // 执行SQL获取结果 String result = executeSQL(sql); // 构造分析提示 String fullPrompt = ANALYSIS_PROMPT .replace("{metadata}", metadataHolder.getMetadata().toJSONString()) .replace("{question}", question) .replace("{result}", result); try { // 调用OpenAI API return chatAITool.askQuestion(fullPrompt); } catch (Exception e) { throw new RuntimeException("Failed to call OpenAI API" + e.getMessage()); } } public String explainMetric(String metricName) { String prompt = """ 作为数据分析助手,请解释以下指标的业务含义: 数据库元数据:%s 指标名称:%s 要求: 1. 用非技术人员能理解的语言 2. 包含计算方式(如果有) 3. 说明正常值范围 4. 给出1个应用示例 """.formatted(metadataHolder.getMetadata(), metricName); return chatAITool.askQuestion(prompt); } // 在原有 ChatBIService 中添加方法 public String generateEChartsConfig(String sql, String question) { String echartsExample = """ { "title": { "text": "月度销售趋势 (示例)", "left": "center" }, "tooltip": { "trigger": "axis" }, "xAxis": { "type": "category", "data": ["1月", "2月", "3月", "4月", "5月", "6月"] }, "yAxis": { "type": "value" }, "series": [{ "name": "销售额", "type": "bar", "data": [6500, 8200, 7200, 9500, 7300, 8800], "itemStyle": { "color": "#409eff", "borderRadius": [4, 4, 0, 0] }, "emphasis": { "itemStyle": { "shadowBlur": 10, "shadowColor": "rgba(64, 158, 255, 0.5)" } } }] }"""; //String dataStr = (data instanceof String) ? (String) data : data.toString(); String EC_Prompt = """ 根据用户提供的数据集和问题描述,生成符合ECharts规范的配置选项(option)。请严格遵循以下步骤: ### 数据集(JSON格式): %s ### 用户问题: %s ### 图表类型选择规则: 根据数据特征自动选择图表类型: 1. 趋势分析(时间序列) → 折线图 - 示例:每月预约量变化 2. 类别对比(离散数据) → 柱状图/条形图 - 示例:不同剧本的评分对比 3. 比例分布 → 饼图/环形图 - 示例:各状态场次占比 4. 相关性分析 → 散点图/热力图 - 示例:玩家数量与评分的关系 5. 地理数据 → 地图 - 示例:用户地域分布 ### 生成规则: 1. 自动识别最佳图表类型(柱状图/折线图/饼图等) 2. 包含完整的配置结构(title/xAxis/yAxis/series等) 3. 数据预处理(排序/过滤/聚合) 4. 智能颜色编码(使用企业级配色方案) 5. 响应式布局支持(resizeable: true) 示例参考: %s 要求: - 输出纯净JSON,无额外文本 - 禁止在返回内容中出现js函数 - 严格遵循ECharts官方规范 - 对无法可视化的数据返回错误结构: { "error": true, "message": "原因说明" } 请直接输出配置JSON: """.formatted(executeSQL(sql), question, echartsExample); try { String json = chatAITool.askQuestion(EC_Prompt); return json; } catch (Exception e) { return "{ \"error\": true, \"message\": \"配置生成失败: " + e.getMessage() + "\" }"; } } //需要获取数据库元数据时调用 public JSONObject getMetadata() { JSONObject meta = metadataHolder.getMetadata(); String tableName = meta.getJSONArray("tables") .getJSONObject(0) .getString("physical_name"); return meta; } //执行sql语句 public String executeSQL(String sql) { // 执行SQL获取结果 String result = JSON.toJSONString(jdbcTemplate.queryForList(sql)); return result; } } ``` #### ChatBIController:接受前端的请求 ``` package com.llj.chatbidemo.controller; import com.alibaba.fastjson.JSON; import com.llj.chatbidemo.config.ChatAITool; import com.llj.chatbidemo.config.Result; import com.llj.chatbidemo.service.ChatBIServiceImpl; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/chatbi") @Tag(name = "ChatBI接口", description = "用于数据分析接口") public class ChatBIController { @Autowired private ChatBIServiceImpl chatBIService; @Autowired private ChatAITool chatAITool; // 应能正常注入 @PostMapping("/ask") public String ask(@RequestBody String question) { return chatAITool.askQuestion(question); } // 自然语言转SQL @PostMapping("/ask1") @CrossOrigin public Result askQuestion(@RequestParam("question") String question) { //对用户问题进行NLP预处理,目前感觉没太必要 //String prompt = nlpUtils.preprocessInput(question); //得到sql语句 String sql = chatBIService.naturalLanguageToSQL(question); String analysis; try { //得到分析 analysis = chatBIService.analyzeData(question, sql); } catch (BadSqlGrammarException e) { System.out.println("SQL语法错误: " + e.getMessage()); return Result.error(2, "ai生成的SQL语法错误 sql:" + sql); } String echartsConfig; try { //得到echarts配置 echartsConfig = chatBIService.generateEChartsConfig(sql, question); } catch (BadSqlGrammarException e) { return Result.error(2, "ai生成图表失败,失败原因" +e.getMessage()); } System.out.println("SQL: " + sql); System.out.println(" Analysis: "+analysis); System.out.println("EChartsConfig: " + echartsConfig); return Result.success(Map.of( "success", true, "sql", sql, "analysis", JSON.parse(analysis) // 按需解析 , "option", JSON.parse(echartsConfig) )); } // 指标解释服务 @GetMapping("/explain/{metric}") public String explainMetric(@PathVariable String metric) { return chatBIService.explainMetric(metric); } } ``` # 四,优缺点分析(基于实现方案的分析) **优点:** 1. 轻量化,适合小型项目 2. 能够快速构建 3. 可替换成自己部署的大模型 **缺点:** 1. 图表生成不稳定,因为有些问题是没法图表展示的 2. 不适配大型项目:数据库中表,仅限280张以下,超过的话调用ai会返回token过长错误 --- # 五:展望(未来优化方向) ## 1,接入大型数据库 **数据集格式变更:**是否可以将数据库信息放到向量数据库中 **优化数据集:**在保证数据集能成功的让ai知道数据库的信息。简化数据库信息结构来减少token ## 2,提示词优化 **”分析“:**分析能否更具体一点。 **”建议“:**可以根据用户身份生成对应的建议 ### 3,接入权限 **安全:** 防止普通用户获取重要数据库信息