1.7.0后端

feat(AI):完成AI自动生成报表功能;
fix(AI):完善AI问答。
master
xs 1 month ago
parent 0960d04194
commit 0103c1c901

@ -17,15 +17,22 @@ public interface HwMomAiConstants {
public static final String AI_FORM_SETTING_FIELD_TYPE_DICT = "3";//字典数据 public static final String AI_FORM_SETTING_FIELD_TYPE_DICT = "3";//字典数据
/**
* ai_chat_messagemessagge_typeai_chat_message_detailmessage_detail_type
*/
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION = "1";//AI问答 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION = "1";//AI问答
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_KNOWLEDGE_QUESTION = "2";//AI知识库问答 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_KNOWLEDGE_QUESTION = "2";//AI知识库问答
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_SQL = "3";//AI生成SQL public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_SQL = "3";//AI生成SQL
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_FORM = "4";//AI填报 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_FORM = "4";//AI填报
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION_VECTOR = "5";//提问获取向量 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION_VECTOR = "5";//提问获取向量
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_CONTENT_VECTOR = "6";//上传内容获取向量 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_CONTENT_VECTOR = "6";//上传内容获取向量
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_REPORT_SQL = "7";//AI生成报表SQL
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_REPORT = "8";//AI生成报表SQL
public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_TEST = "9";//AI测试 public static final String AI_CHAT_MESSAGE_DETAIL_TYPE_TEST = "9";//AI测试
public static final String AI_ASR_RESULT_SUCCESS = "1";//语音识别结果:成功
public static final String AI_ASR_RESULT_FAILED = "0";//语音识别结果:失败
} }

@ -53,6 +53,17 @@
<artifactId>ruoyi-common-mybatis</artifactId> <artifactId>ruoyi-common-mybatis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.25.0</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-dubbo</artifactId> <artifactId>ruoyi-common-dubbo</artifactId>
@ -114,18 +125,7 @@
</dependency> </dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hwmom-api-mes</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hwmom-api-pda</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
@ -175,16 +175,16 @@
</dependency> </dependency>
<!-- OpenAI SDK --> <!-- OpenAI SDK -->
<dependency> <!-- <dependency>-->
<groupId>com.theokanning.openai-gpt3-java</groupId> <!-- <groupId>com.theokanning.openai-gpt3-java</groupId>-->
<artifactId>service</artifactId> <!-- <artifactId>service</artifactId>-->
<version>0.18.0</version> <!-- <version>0.18.0</version>-->
</dependency> <!-- </dependency>-->
<dependency> <!-- <dependency>-->
<groupId>com.theokanning.openai-gpt3-java</groupId> <!-- <groupId>com.theokanning.openai-gpt3-java</groupId>-->
<artifactId>client</artifactId> <!-- <artifactId>client</artifactId>-->
<version>0.18.0</version> <!-- <version>0.18.0</version>-->
</dependency> <!-- </dependency>-->
<!--阿里云百炼SDK,通义千问--> <!--阿里云百炼SDK,通义千问-->
<dependency> <dependency>
@ -196,22 +196,22 @@
<!-- 本地模型依赖以sentence-transformers为例 --> <!-- 本地模型依赖以sentence-transformers为例 -->
<dependency> <!-- <dependency>-->
<groupId>org.tensorflow</groupId> <!-- <groupId>org.tensorflow</groupId>-->
<artifactId>tensorflow-core-platform</artifactId> <!-- <artifactId>tensorflow-core-platform</artifactId>-->
<version>0.5.0</version> <!-- <version>0.5.0</version>-->
</dependency> <!-- </dependency>-->
<dependency> <!-- <dependency>-->
<groupId>org.deeplearning4j</groupId> <!-- <groupId>org.deeplearning4j</groupId>-->
<artifactId>deeplearning4j-core</artifactId> <!-- <artifactId>deeplearning4j-core</artifactId>-->
<version>1.0.0-M2.1</version> <!-- <version>1.0.0-M2.1</version>-->
</dependency> <!-- </dependency>-->
<dependency> <!-- <dependency>-->
<groupId>ai.djl</groupId> <!-- <groupId>ai.djl</groupId>-->
<artifactId>api</artifactId> <!-- <artifactId>api</artifactId>-->
<version>0.28.0</version> <!-- <version>0.28.0</version>-->
</dependency> <!-- </dependency>-->
<dependency> <dependency>
<groupId>ai.djl</groupId> <groupId>ai.djl</groupId>
<artifactId>model-zoo</artifactId> <artifactId>model-zoo</artifactId>
@ -294,16 +294,16 @@
<dependency> <!-- <dependency>-->
<groupId>org.apache.poi</groupId> <!-- <groupId>org.apache.poi</groupId>-->
<artifactId>poi-ooxml</artifactId> <!-- <artifactId>poi-ooxml</artifactId>-->
<version>5.2.3</version> <!-- <version>5.2.3</version>-->
</dependency> <!-- </dependency>-->
<dependency> <!-- <dependency>-->
<groupId>commons-io</groupId> <!-- <groupId>commons-io</groupId>-->
<artifactId>commons-io</artifactId> <!-- <artifactId>commons-io</artifactId>-->
<version>2.13.0</version> <!-- <version>2.13.0</version>-->
</dependency> <!-- </dependency>-->
<dependency> <dependency>
<groupId>org.anyline</groupId> <groupId>org.anyline</groupId>

@ -90,21 +90,27 @@ public class AliAsrServiceImpl implements AliAsrService {
public String recognizeSpeechByUrl(String audioUrl) { public String recognizeSpeechByUrl(String audioUrl) {
try { try {
// 构建识别参数 // 构建识别参数
if(StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)){ if (StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)) {
throw new RuntimeException("请配置apiKey和modelName"); throw new RuntimeException("请配置apiKey和modelName");
} }
TranscriptionParam param = TranscriptionParam.builder() TranscriptionParam param = TranscriptionParam.builder()
.apiKey(EncryptUtils.decryptByBase64(apiKey)) .apiKey(EncryptUtils.decryptByBase64(apiKey))
.model(modelName) .model(modelName)
// .model("paraformer-v2")
.fileUrls(Arrays.asList(audioUrl)) .fileUrls(Arrays.asList(audioUrl))
.parameter("language_hints", LANGUAGE_HINTS) .parameter("language_hints", LANGUAGE_HINTS)
.build(); .build();
Transcription transcription = new Transcription(); Transcription transcription = new Transcription();
// 提交转写请求
TranscriptionResult result = transcription.asyncCall(param);
System.out.println("RequestId: " + result.getRequestId());
// 阻塞等待任务完成并获取结果
result = transcription.wait(
TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
// 提交语音识别任务 // 提交语音识别任务
TranscriptionResult result = transcription.asyncCall(param);
String taskId = result.getTaskId(); String taskId = result.getTaskId();
logger.info("语音识别任务提交成功TaskId: {}", taskId); logger.info("语音识别任务提交成功TaskId: {}", taskId);
@ -118,24 +124,24 @@ public class AliAsrServiceImpl implements AliAsrService {
aiAsrRecord.setCreateDept(LoginHelper.getDeptId()); aiAsrRecord.setCreateDept(LoginHelper.getDeptId());
aiAsrRecord.setCreateTime(new Date()); aiAsrRecord.setCreateTime(new Date());
while (true) { // while (true) {
result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId)); // result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId));
//
if (result.getTaskStatus() == TaskStatus.SUCCEEDED) { // if (result.getTaskStatus() == TaskStatus.SUCCEEDED) {
logger.info("语音识别任务完成TaskId: {}", taskId); // logger.info("语音识别任务完成TaskId: {}", taskId);
break; // break;
} else if (result.getTaskStatus() == TaskStatus.FAILED) { // } else if (result.getTaskStatus() == TaskStatus.FAILED) {
aiAsrRecord.setAsrFailedReason(result.getResults().toString()); // aiAsrRecord.setAsrFailedReason(result.getResults().toString());
aiAsrRecord.setTaskId(taskId); // aiAsrRecord.setTaskId(taskId);
aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_FAILED); // aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_FAILED);
aiAsrRecordMapper.insert(aiAsrRecord); // aiAsrRecordMapper.insert(aiAsrRecord);
logger.error("语音识别任务失败TaskId: {}, 错误信息: {}", taskId, result.getResults().toString()); // logger.error("语音识别任务失败TaskId: {}, 错误信息: {}", taskId, result.getResults().toString());
throw new RuntimeException("语音识别任务失败: " + result.getResults().toString()); // throw new RuntimeException("语音识别任务失败: " + result.getResults().toString());
} // }
//
// 等待1秒后再次查询 // // 等待1秒后再次查询
Thread.sleep(1000); // Thread.sleep(1000);
} // }
// 解析识别结果 // 解析识别结果
String recognitionResult = parseRecognitionResult(result); String recognitionResult = parseRecognitionResult(result);
@ -161,20 +167,23 @@ public class AliAsrServiceImpl implements AliAsrService {
throw new RuntimeException("未获取到识别结果"); throw new RuntimeException("未获取到识别结果");
} }
TranscriptionTaskResult taskResult = taskResultList.get(0);
String transcriptionUrl = taskResult.getTranscriptionUrl();
// 获取识别结果详情 if (taskResultList != null && taskResultList.size() > 0) {
HttpURLConnection connection = (HttpURLConnection) new URL(transcriptionUrl).openConnection(); HttpURLConnection connection = null;
connection.setRequestMethod("GET"); try {
connection.connect(); TranscriptionTaskResult taskResult = taskResultList.get(0);
String transcriptionUrl = taskResult.getTranscriptionUrl();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { connection =
Gson gson = new GsonBuilder().setPrettyPrinting().create(); (HttpURLConnection) new URL(transcriptionUrl).openConnection();
JsonElement jsonElement = gson.fromJson(reader, JsonElement.class); connection.setRequestMethod("GET");
connection.connect();
if (jsonElement.isJsonObject()) { BufferedReader reader =
JsonObject jsonObject = jsonElement.getAsJsonObject(); new BufferedReader(new InputStreamReader(connection.getInputStream()));
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonElement jsonResult = gson.fromJson(reader, JsonObject.class);
System.out.println(gson.toJson(jsonResult));
if (jsonResult.isJsonObject()) {
JsonObject jsonObject = jsonResult.getAsJsonObject();
JsonArray transcriptsArr = jsonObject.getAsJsonArray("transcripts"); JsonArray transcriptsArr = jsonObject.getAsJsonArray("transcripts");
if (transcriptsArr != null && !transcriptsArr.isEmpty()) { if (transcriptsArr != null && !transcriptsArr.isEmpty()) {
@ -183,11 +192,44 @@ public class AliAsrServiceImpl implements AliAsrService {
return extractTextFromTags(text); return extractTextFromTags(text);
} }
} }
} catch (Exception e) {
throw new RuntimeException("解析识别结果失败"); throw new RuntimeException("语音识别失败:" + e.getMessage());
} finally { } finally {
connection.disconnect(); if (connection != null) {
connection.disconnect();
}
}
} }
// TranscriptionTaskResult taskResult = taskResultList.get(0);
// String transcriptionUrl = taskResult.getTranscriptionUrl();
//
// // 获取识别结果详情
// HttpURLConnection connection = (HttpURLConnection) new URL(transcriptionUrl).openConnection();
// connection.setRequestMethod("GET");
// connection.connect();
//
// try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
// Gson gson = new GsonBuilder().setPrettyPrinting().create();
// JsonElement jsonElement = gson.fromJson(reader, JsonElement.class);
//
// if (jsonElement.isJsonObject()) {
// JsonObject jsonObject = jsonElement.getAsJsonObject();
// JsonArray transcriptsArr = jsonObject.getAsJsonArray("transcripts");
//
// if (transcriptsArr != null && !transcriptsArr.isEmpty()) {
// JsonObject jsonObject1 = transcriptsArr.get(0).getAsJsonObject();
// String text = jsonObject1.get("text").getAsString();
// return extractTextFromTags(text);
// }
// }
//
// throw new RuntimeException("解析识别结果失败");
// } finally {
// connection.disconnect();
// }
return null;
} }
/** /**

@ -25,6 +25,7 @@ import org.dromara.ai.process.provider.processor.AIProviderProcessorFactory;
import org.dromara.ai.process.provider.processor.IUnifiedAIProviderProcessor; import org.dromara.ai.process.provider.processor.IUnifiedAIProviderProcessor;
import org.dromara.ai.service.*; import org.dromara.ai.service.*;
import org.dromara.ai.vectordb.service.IVectorDBService; import org.dromara.ai.vectordb.service.IVectorDBService;
import org.dromara.common.constant.HwMomAiConstants;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.EditGroup; import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.idempotent.annotation.RepeatSubmit;
@ -68,11 +69,11 @@ public class AiAssistantController extends BaseController {
} }
/** /**
* AI * AI(AI)
*/ */
@GetMapping("/getAiChatMessageList") @GetMapping("/getAiChatMessageList")
public R<List<AiChatMessageVo>> getAiChatMessageList(AiChatMessageBo bo) { public R<List<AiChatMessageVo>> getAiChatMessageList(AiChatMessageBo bo) {
List<AiChatMessageVo> list = aiChatMessageService.queryList(bo); List<AiChatMessageVo> list = aiChatMessageService.queryQaList(bo);
return R.ok(list); return R.ok(list);
} }
@ -125,6 +126,7 @@ public class AiAssistantController extends BaseController {
* *
*/ */
@PostMapping("/chat") @PostMapping("/chat")
@SaCheckPermission("ai:aiChatMessage:list")
public Mono<AIResponse> chat( public Mono<AIResponse> chat(
@RequestParam("provider") String provider, @RequestBody AIRequest request) { @RequestParam("provider") String provider, @RequestBody AIRequest request) {
@ -139,8 +141,10 @@ public class AiAssistantController extends BaseController {
* @param request * @param request
* @return * @return
*/ */
@SaCheckPermission("ai:aiAssistant:generateSql")
@PostMapping(value = "/generateSql") @PostMapping(value = "/generateSql")
public String generateSql(AIRequest request) { public String generateSql(AIRequest request) {
request.setMessageDetailType(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_SQL);
return aiAssistantService.generateSQL(request); return aiAssistantService.generateSQL(request);
} }

@ -0,0 +1,160 @@
package org.dromara.ai.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.bo.AiAsrRecordBo;
import org.dromara.ai.domain.bo.AiChatMessageDetailBo;
import org.dromara.ai.domain.bo.AiTokenUsageBo;
import org.dromara.ai.domain.dto.AIReportRequest;
import org.dromara.ai.domain.dto.AIReportResponse;
import org.dromara.ai.domain.vo.AiAsrRecordVo;
import org.dromara.ai.domain.vo.AiChatMessageDetailVo;
import org.dromara.ai.domain.vo.AiChatMessageVo;
import org.dromara.ai.domain.vo.AiTokenUsageVo;
import org.dromara.ai.process.dto.AIRequest;
import org.dromara.ai.service.IAIAssistantService;
import org.dromara.ai.service.IAIReportService;
import org.dromara.common.constant.HwMomAiConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* AI
* 访:/ai/aiReport
*
* @author xins
* @date 2025-08-07
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/aiReport")
public class AiReportController extends BaseController {
private final Logger logger = LoggerFactory.getLogger(AiReportController.class);
private final IAIReportService aiReportService;
private final IAIAssistantService aiAssistantService;
/**
* sqlAIModelID, AIPLATFORMID
*
* @param request
* @return
*/
@PostMapping(value = "/generateReportSql")
@SaCheckPermission("ai:aiReport:generate")
public R<String> generateReportSql(AIRequest request) {
request.setMessageDetailType(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_REPORT_SQL);
return R.ok(aiAssistantService.generateSQL(request));
}
@PostMapping("/generateReport")
@SaCheckPermission("ai:aiReport:generate")
@ResponseBody
public R<AIReportResponse> generateReport(@RequestBody AIRequest request) {
String provider = "deepseek"; // 或从请求中获取
request.setMessageDetailType(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_REPORT);
Mono<AIReportResponse> aiReportResponseMono = aiReportService.generateReport(provider, request);
// System.out.println(aiReportResponseMono.block().getChartHtml());
return R.ok(aiReportResponseMono.block());
}
/**
* AI
*/
@GetMapping("/getRecentReports")
@SaCheckPermission("ai:aiReport:generate")
@ResponseBody
public TableDataInfo<AiChatMessage> getRecentReports() {
return aiReportService.getRecentReports();
}
/**
* AI
*/
@GetMapping("/getRecentReportInfo")
@SaCheckPermission("ai:aiReport:generate")
public R<AiChatMessageDetailVo> getRecentReportInfo(AiChatMessageDetailBo bo) {
AiChatMessageDetailVo aiChatMessageDetailVo = aiReportService.getRecentReportInfo(bo);
return R.ok(aiChatMessageDetailVo);
}
/**
* token使
*/
// @SaCheckPermission("ai:tokenUsage:export")
@Log(title = "AI生成报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportReportData")
@SaCheckPermission("ai:aiReport:export")
public void exportReportData(AIRequest aiRequest, HttpServletResponse response) {
aiReportService.exportReportData(aiRequest,response);
}
@PostMapping(value = "/streamGenerate", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@SaCheckPermission("ai:aiReport:generate")
public Flux<String> streamGenerateReport(
@RequestParam("provider") String provider, @RequestBody AIRequest request) {
// LoginUser loginUser = LoginHelper.getLoginUser();
// IUnifiedAIProviderProcessor processor = aiProviderProcessorFactory.getProcessor(provider);
return aiReportService.streamGenerateReport(provider, request);
}
/**
* AI
*/
@PostMapping("/generateTestReport")
@SaCheckPermission("ai:aiReport:generate")
@ResponseBody
public R<AIReportResponse> generateTestReport(@RequestBody AIReportRequest request) {
try {
logger.info("收到AI报表生成请求: {}", request.getPrompt());
// 参数验证
if (request.getPrompt() == null || request.getPrompt().trim().isEmpty()) {
return R.fail("请输入分析需求描述");
}
// 生成报表
AIReportResponse response = aiReportService.generateTestReport(request);
logger.info("AI报表生成成功SQL: {}", response.getSql());
return R.ok(response);
} catch (Exception e) {
logger.error("AI报表生成失败", e);
return R.fail("报表生成失败: " + e.getMessage());
}
}
}

@ -0,0 +1,34 @@
package org.dromara.ai.domain.dto;
/**
* @Author xins
* @Date 2026/1/5 14:38
* @Description:AI
*/
import lombok.Data;
import java.util.Map;
@Data
public class AIReportRequest {
/**
*
*/
private String prompt;
/**
* ID
*/
private String dataSourceId;
/**
*
*/
private String chartType;
/**
*
*/
private Map<String, Object> parameters;
}

@ -0,0 +1,63 @@
package org.dromara.ai.domain.dto;
/**
* @Author xins
* @Date 2026/1/5 14:39
* @Description:AI
*/
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class AIReportResponse {
/**
* SQL
*/
private String sql;
/**
*
*/
private List<Map<String, Object>> data;
/**
*
*/
private List<FieldInfo> fields;
/**
* HTML
*/
private String chartHtml;
/**
* HTML
*/
private String tableHtml;
/**
*
*/
private String analysis;
/**
*
*/
private Long executionTime;
/**
*
*/
@Data
public static class FieldInfo {
private String name;
private String type;
private String comment;
private boolean isPrimary;
private boolean isNumeric;
}
}

@ -0,0 +1,21 @@
package org.dromara.ai.domain.dto;
/**
* @Author xins
* @Date 2026/1/5 14:39
* @Description:AI
*/
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class AIReportResult {
private Boolean success;
private String code;
private String message;
private AIReportResponse aiReportResponse;
}

@ -0,0 +1,17 @@
package org.dromara.ai.domain.dto;
import lombok.Data;
import org.dromara.ai.process.dto.TokenUsage;
/**
* @Author xins
* @Date 2026/1/7 10:17
* @Description:
*/
@Data
public class StreamResult {
private String type; // "chunk" 或 "complete"
private String content;
private TokenUsage tokenUsage;
private Boolean isComplete = false;
}

@ -64,4 +64,11 @@ public interface SQLServerDatabaseMetaMapper {
@Param("orderBy") String orderBy, @Param("orderBy") String orderBy,
@Param("groupBy") String groupBy); @Param("groupBy") String groupBy);
/**
* SELECT
* @param sql
*/
List<Map<String, Object>> dynamicSelectSql(@Param("sql") String sql);
} }

@ -125,4 +125,10 @@ public class AIRequest {
* *
*/ */
private Object customParams; private Object customParams;
private String messageDetailType;
private String sql;
private Long chatMessageId;
} }

@ -2,6 +2,7 @@ package org.dromara.ai.process.provider.processor;
import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse; import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.process.dto.AIRequest; import org.dromara.ai.process.dto.AIRequest;
import org.dromara.ai.process.dto.AIResponse; import org.dromara.ai.process.dto.AIResponse;
import org.dromara.ai.process.dto.TokenUsage; import org.dromara.ai.process.dto.TokenUsage;
@ -103,4 +104,7 @@ public interface IUnifiedAIProviderProcessor {
Long modelId, Long knowledgeBaseId, Long knowledgeContentId, Long modelId, Long knowledgeBaseId, Long knowledgeContentId,
Long chatMessageId,String sessionId,String takeFlag,String completeFlag, Long chatMessageId,String sessionId,String takeFlag,String completeFlag,
Long userId,String tenantId,Long deptId); Long userId,String tenantId,Long deptId);
public Flux<StreamResult> chatStreamComplete(AIRequest request, LoginUser loginUser);
} }

@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.yulichang.toolkit.JoinWrappers; import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.seata.common.util.StringUtils;
import org.dromara.ai.domain.AiChatMessage; import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.AiTokenUsage; import org.dromara.ai.domain.AiTokenUsage;
@ -88,7 +89,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
protected void configureApiKey(AIRequest request) { protected void configureApiKey(AIRequest request) {
if(request.getModelId()!=null){ if (request.getModelId() != null) {
AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId()); AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId());
if (aiModelVo != null) { if (aiModelVo != null) {
if (aiModelVo.getApiKey() == null) { if (aiModelVo.getApiKey() == null) {
@ -101,12 +102,13 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
/** /**
* http() * http()
*
* @param url * @param url
* @param requestBody * @param requestBody
* @param apiKey * @param apiKey
* @return AIResponse * @return AIResponse
*/ */
public abstract Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey); public abstract Mono<AIResponse> standardRequest(String url, String requestBody, String apiKey);
/** /**
@ -165,14 +167,6 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
/** /**
* JSONtoken * JSONtoken
*/ */
@ -216,6 +210,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
protected void saveChatMessage(AIRequest request, String fullResponse, TokenUsage tokenUsage, LoginUser loginUser) { protected void saveChatMessage(AIRequest request, String fullResponse, TokenUsage tokenUsage, LoginUser loginUser) {
try { try {
String messageType = StringUtils.isNotBlank(request.getMessageDetailType()) ? request.getMessageDetailType() : HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION;
String sessionId = request.getSessionId(); String sessionId = request.getSessionId();
AiChatMessage aiChatMessage = aiChatMessageMapper AiChatMessage aiChatMessage = aiChatMessageMapper
.selectOne(new LambdaQueryWrapper<AiChatMessage>() .selectOne(new LambdaQueryWrapper<AiChatMessage>()
@ -225,7 +220,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
aiChatMessage = new AiChatMessage(); aiChatMessage = new AiChatMessage();
aiChatMessage.setSessionId(request.getSessionId()); aiChatMessage.setSessionId(request.getSessionId());
aiChatMessage.setMessageTopic(objectMapper.writeValueAsString(request.getMessageTopic())); aiChatMessage.setMessageTopic(objectMapper.writeValueAsString(request.getMessageTopic()));
aiChatMessage.setMessageType(AIChatMessageTypeEnum.AI_CHAT.getCode()); aiChatMessage.setMessageType(messageType);
aiChatMessage.setModelId(request.getModelId()); aiChatMessage.setModelId(request.getModelId());
aiChatMessage.setKnowledgeBaseId(request.getKnowledgeBaseId()); aiChatMessage.setKnowledgeBaseId(request.getKnowledgeBaseId());
// aiChatMessage.setTotalToken(); // aiChatMessage.setTotalToken();
@ -239,10 +234,10 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION, objectMapper.writeValueAsString(request.getQuestionContent()), saveTokenUsage(messageType, objectMapper.writeValueAsString(request.getQuestionContent()),
objectMapper.writeValueAsString(fullResponse), tokenUsage, request.getModelId(), request.getKnowledgeBaseId(), null, objectMapper.writeValueAsString(fullResponse), tokenUsage, request.getModelId(), request.getKnowledgeBaseId(), null,
aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1", aiChatMessage.getChatMessageId(), request.getSessionId(), request.getCarryHistoryFlag(), "1",
loginUser.getUserId(),loginUser.getTenantId(), loginUser.getDeptId()); loginUser.getUserId(), loginUser.getTenantId(), loginUser.getDeptId());
// AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail(); // AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail();
@ -275,6 +270,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
/** /**
* token使 * token使
*
* @param messageDetailType * @param messageDetailType
* @param questionContent * @param questionContent
* @param answerContent * @param answerContent
@ -292,12 +288,12 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
*/ */
@Override @Override
public void saveTokenUsage(String messageDetailType, String questionContent, String answerContent, TokenUsage tokenUsage, public void saveTokenUsage(String messageDetailType, String questionContent, String answerContent, TokenUsage tokenUsage,
Long modelId, Long knowledgeBaseId, Long knowledgeContentId, Long modelId, Long knowledgeBaseId, Long knowledgeContentId,
Long chatMessageId,String sessionId,String takeFlag,String completeFlag, Long chatMessageId, String sessionId, String takeFlag, String completeFlag,
Long userId,String tenantId,Long deptId) { Long userId, String tenantId, Long deptId) {
Long promptToken = tokenUsage!=null ? tokenUsage.getPromptToken():null; Long promptToken = tokenUsage != null ? tokenUsage.getPromptToken() : null;
Long completionToken = tokenUsage!=null ? tokenUsage.getCompletionToken():null; Long completionToken = tokenUsage != null ? tokenUsage.getCompletionToken() : null;
Long totalToken = tokenUsage!=null ? tokenUsage.getTotalToken():null; Long totalToken = tokenUsage != null ? tokenUsage.getTotalToken() : null;
AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail(); AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail();
aiChatMessageDetail.setMessageDetailType(messageDetailType); aiChatMessageDetail.setMessageDetailType(messageDetailType);
@ -350,7 +346,6 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
/** /**
* chunk * chunk
*/ */
@ -364,8 +359,13 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
// getters // getters
public String getContent() { return content; } public String getContent() {
public TokenUsage getTokenUsage() { return tokenUsage; } return content;
}
public TokenUsage getTokenUsage() {
return tokenUsage;
}
public boolean hasContent() { public boolean hasContent() {
return content != null && !content.isEmpty(); return content != null && !content.isEmpty();
@ -377,8 +377,6 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
} }
// /** // /**
// * 流式回复 // * 流式回复
// * @param request // * @param request

@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse; import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
import org.dromara.ai.domain.AiChatMessage; import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.domain.vo.AiModelVo; import org.dromara.ai.domain.vo.AiModelVo;
import org.dromara.ai.mapper.AiChatMessageDetailMapper; import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.ai.mapper.AiChatMessageMapper; import org.dromara.ai.mapper.AiChatMessageMapper;
@ -53,16 +54,16 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
private final String deepSeekChatModel = "deepseek-chat"; private final String deepSeekChatModel = "deepseek-chat";
/** /**
* http() * http()
*
* @param url * @param url
* @param requestBody * @param requestBody
* @param apiKey * @param apiKey
* @return AIResponse * @return AIResponse
*/ */
@Override @Override
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey) { public Mono<AIResponse> standardRequest(String url, String requestBody, String apiKey) {
return webClient.post() return webClient.post()
.uri(url) .uri(url)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey) .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
@ -146,7 +147,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
}) })
.doOnComplete(() -> { .doOnComplete(() -> {
// 流完成后保存到数据库 // 流完成后保存到数据库
saveChatMessage(request, fullResponseBuilder.toString(), null,loginUser); saveChatMessage(request, fullResponseBuilder.toString(), null, loginUser);
}) })
.doOnError(error -> { .doOnError(error -> {
// 错误处理 // 错误处理
@ -308,7 +309,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
// usageNode.path("total_tokens").asInt() // usageNode.path("total_tokens").asInt()
// ); // );
TokenUsage tokenUsage = new TokenUsage( usageNode.path("prompt_tokens").asLong(), TokenUsage tokenUsage = new TokenUsage(usageNode.path("prompt_tokens").asLong(),
usageNode.path("completion_tokens").asLong(), usageNode.path("completion_tokens").asLong(),
usageNode.path("total_tokens").asLong()); usageNode.path("total_tokens").asLong());
@ -331,7 +332,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
} }
@Override @Override
public List<Double> getEmbedding(GetEmbeddingResponse embeddingResponse){ public List<Double> getEmbedding(GetEmbeddingResponse embeddingResponse) {
return List.of(); return List.of();
} }
@ -346,6 +347,63 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
} }
@Override
public Flux<StreamResult> chatStreamComplete(AIRequest request, LoginUser loginUser) {
try {
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("model", deepSeekChatModel);
rootNode.set("messages", objectMapper.valueToTree(request.getMessages()));
rootNode.put("stream", true);
if (request.getTemperature() != null) {
rootNode.put("temperature", request.getTemperature());
}
String requestBody = objectMapper.writeValueAsString(rootNode);
configureApiKey(request);
// 用于收集完整响应和token信息
StringBuilder fullResponseBuilder = new StringBuilder();
TokenUsage finalTokenUsage = new TokenUsage(0L, 0L, 0L);
// 返回包装后的结果流
return executeStreamRequest(API_URL, requestBody, request.getApiKey())
.map(chunkResult -> {
// 收集内容
if (chunkResult.hasContent()) {
fullResponseBuilder.append(chunkResult.getContent());
}
// 更新token使用信息
if (chunkResult.hasTokenUsage()) {
TokenUsage usage = chunkResult.getTokenUsage();
finalTokenUsage.setPromptToken(usage.getPromptToken());
finalTokenUsage.setCompletionToken(usage.getCompletionToken());
finalTokenUsage.setTotalToken(usage.getTotalToken());
}
// 创建流式结果
StreamResult result = new StreamResult();
result.setType("chunk");
result.setContent(chunkResult.hasContent() ? chunkResult.getContent() : "");
return result;
})
.doOnComplete(() -> {
// 流完成后保存到数据库,但不发送额外内容
System.out.println(fullResponseBuilder.toString());
saveChatMessage(request, fullResponseBuilder.toString(), finalTokenUsage, loginUser);
})
.doOnError(error -> {
// 即使出错也尝试保存已收集的内容
saveChatMessage(request, fullResponseBuilder.toString(), finalTokenUsage, loginUser);
});
} catch (IOException e) {
return Flux.error(new RuntimeException("构建请求失败: " + e.getMessage()));
}
}
private String extractContentRaw(String jsonStr) { private String extractContentRaw(String jsonStr) {
// 简单的正则提取,作为最后手段 // 简单的正则提取,作为最后手段
Pattern pattern = Pattern.compile("\"content\":\"(.*?)(?<!\\\\)\""); Pattern pattern = Pattern.compile("\"content\":\"(.*?)(?<!\\\\)\"");
@ -359,9 +417,4 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
} }
} }

@ -10,6 +10,7 @@ import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingRequest;
import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse; import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
import com.tencentcloudapi.lkeap.v20240522.models.Usage; import com.tencentcloudapi.lkeap.v20240522.models.Usage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.process.provider.processor.utils.ProcessorUtils; import org.dromara.ai.process.provider.processor.utils.ProcessorUtils;
import org.dromara.ai.test.vectorization.config.EmbeddingConfig; import org.dromara.ai.test.vectorization.config.EmbeddingConfig;
import org.dromara.ai.process.dto.AIRequest; import org.dromara.ai.process.dto.AIRequest;
@ -226,4 +227,9 @@ public class TencentLkeProcessor extends BaseAIProviderProcessor {
protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception { protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception {
return null; return null;
} }
@Override
public Flux<StreamResult> chatStreamComplete(AIRequest request, LoginUser loginUser) {
return null;
}
} }

@ -12,6 +12,7 @@ import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
import org.checkerframework.checker.units.qual.A; import org.checkerframework.checker.units.qual.A;
import org.dromara.ai.domain.AiChatMessage; import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.mapper.AiChatMessageDetailMapper; import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.ai.mapper.AiChatMessageMapper; import org.dromara.ai.mapper.AiChatMessageMapper;
import org.dromara.ai.process.dto.AIMessage; import org.dromara.ai.process.dto.AIMessage;
@ -431,4 +432,9 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception { protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception {
return null; return null;
} }
@Override
public Flux<StreamResult> chatStreamComplete(AIRequest request, LoginUser loginUser) {
return null;
}
} }

@ -1,6 +1,7 @@
package org.dromara.ai.service; package org.dromara.ai.service;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import org.dromara.ai.domain.dto.AIReportResponse;
import org.dromara.ai.process.dto.AIFillFormRequest; import org.dromara.ai.process.dto.AIFillFormRequest;
import org.dromara.ai.process.dto.AIRequest; import org.dromara.ai.process.dto.AIRequest;
import org.dromara.ai.process.dto.AIResponse; import org.dromara.ai.process.dto.AIResponse;
@ -33,4 +34,5 @@ public interface IAIAssistantService {
*/ */
public JSONObject aiFillForm(AIFillFormRequest aiFillFormRequest); public JSONObject aiFillForm(AIFillFormRequest aiFillFormRequest);
// public boolean testAIModel(String provider, AIRequest aiRequest); // public boolean testAIModel(String provider, AIRequest aiRequest);
} }

@ -0,0 +1,74 @@
package org.dromara.ai.service;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.bo.AiChatMessageDetailBo;
import org.dromara.ai.domain.dto.AIReportRequest;
import org.dromara.ai.domain.dto.AIReportResponse;
import org.dromara.ai.domain.vo.AiChatMessageDetailVo;
import org.dromara.ai.domain.vo.AiChatMessageVo;
import org.dromara.ai.process.dto.AIRequest;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* AI
*/
public interface IAIReportService {
/**
* ,AI
*
* @param provider
* @param aiRequest
* @return Flux<String>
*/
public Mono<AIReportResponse> generateReport(String provider, AIRequest aiRequest);
/**
* 5AI
* @return List<AiChatMessageVo>
*/
public TableDataInfo<AiChatMessage> getRecentReports();
/**
* sessionidmessagedetailtypeAI
* @param aiChatMessageDetailBo
* @return AiChatMessageDetailVo
*/
public AiChatMessageDetailVo getRecentReportInfo(AiChatMessageDetailBo aiChatMessageDetailBo);
/**
* AIexcel
* @param aiRequest
* @param response
*/
public void exportReportData(AIRequest aiRequest, HttpServletResponse response);
public Flux<String> streamGenerateReport(String provider, AIRequest aiRequest);
/**
*
* @param request
* @return
*/
AIReportResponse generateTestReport(AIReportRequest request);
/**
* AISQL
* @param prompt
* @return SQL
*/
String generateSQL(String prompt);
}

@ -88,4 +88,12 @@ public interface IAiChatMessageService {
*/ */
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidBySessionId(String sessionId, Boolean isValid); public Boolean deleteWithValidBySessionId(String sessionId, Boolean isValid);
/**
* ()
*
* @param bo
* @return
*/
public List<AiChatMessageVo> queryQaList(AiChatMessageBo bo);
} }

@ -5,6 +5,7 @@ import com.github.yulichang.interfaces.MPJBaseJoin;
import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.bo.AiChatMessageTopicBo; import org.dromara.ai.domain.bo.AiChatMessageTopicBo;
import org.dromara.ai.mapper.AiChatMessageDetailMapper; import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.common.constant.HwMomAiConstants;
import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -182,4 +183,18 @@ public class AiChatMessageServiceImpl implements IAiChatMessageService {
.eq(AiChatMessage::getSessionId, sessionId)) > 0; .eq(AiChatMessage::getSessionId, sessionId)) > 0;
} }
/**
* ()
*
* @param bo
* @return
*/
@Override
public List<AiChatMessageVo> queryQaList(AiChatMessageBo bo) {
MPJLambdaWrapper<AiChatMessage> lqw = buildQueryWrapper(bo);
lqw.ne(AiChatMessage::getMessageType, HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_REPORT);
return baseMapper.selectVoList(lqw);
}
} }

@ -145,4 +145,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="dynamicSelectSql" resultType="java.util.Map" statementType="STATEMENT">
${sql}
</select>
</mapper> </mapper>

Loading…
Cancel
Save