1.7.0后端

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

@ -17,15 +17,22 @@ public interface HwMomAiConstants {
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_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_FORM = "4";//AI填报
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_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_ASR_RESULT_SUCCESS = "1";//语音识别结果:成功
public static final String AI_ASR_RESULT_FAILED = "0";//语音识别结果:失败
}

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

@ -90,21 +90,27 @@ public class AliAsrServiceImpl implements AliAsrService {
public String recognizeSpeechByUrl(String audioUrl) {
try {
// 构建识别参数
if(StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)){
if (StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)) {
throw new RuntimeException("请配置apiKey和modelName");
}
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(EncryptUtils.decryptByBase64(apiKey))
.model(modelName)
// .model("paraformer-v2")
.fileUrls(Arrays.asList(audioUrl))
.parameter("language_hints", LANGUAGE_HINTS)
.build();
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();
logger.info("语音识别任务提交成功TaskId: {}", taskId);
@ -118,24 +124,24 @@ public class AliAsrServiceImpl implements AliAsrService {
aiAsrRecord.setCreateDept(LoginHelper.getDeptId());
aiAsrRecord.setCreateTime(new Date());
while (true) {
result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId));
if (result.getTaskStatus() == TaskStatus.SUCCEEDED) {
logger.info("语音识别任务完成TaskId: {}", taskId);
break;
} else if (result.getTaskStatus() == TaskStatus.FAILED) {
aiAsrRecord.setAsrFailedReason(result.getResults().toString());
aiAsrRecord.setTaskId(taskId);
aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_FAILED);
aiAsrRecordMapper.insert(aiAsrRecord);
logger.error("语音识别任务失败TaskId: {}, 错误信息: {}", taskId, result.getResults().toString());
throw new RuntimeException("语音识别任务失败: " + result.getResults().toString());
}
// 等待1秒后再次查询
Thread.sleep(1000);
}
// while (true) {
// result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId));
//
// if (result.getTaskStatus() == TaskStatus.SUCCEEDED) {
// logger.info("语音识别任务完成TaskId: {}", taskId);
// break;
// } else if (result.getTaskStatus() == TaskStatus.FAILED) {
// aiAsrRecord.setAsrFailedReason(result.getResults().toString());
// aiAsrRecord.setTaskId(taskId);
// aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_FAILED);
// aiAsrRecordMapper.insert(aiAsrRecord);
// logger.error("语音识别任务失败TaskId: {}, 错误信息: {}", taskId, result.getResults().toString());
// throw new RuntimeException("语音识别任务失败: " + result.getResults().toString());
// }
//
// // 等待1秒后再次查询
// Thread.sleep(1000);
// }
// 解析识别结果
String recognitionResult = parseRecognitionResult(result);
@ -161,20 +167,23 @@ public class AliAsrServiceImpl implements AliAsrService {
throw new RuntimeException("未获取到识别结果");
}
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();
if (taskResultList != null && taskResultList.size() > 0) {
HttpURLConnection connection = null;
try {
TranscriptionTaskResult taskResult = taskResultList.get(0);
String transcriptionUrl = taskResult.getTranscriptionUrl();
connection =
(HttpURLConnection) new URL(transcriptionUrl).openConnection();
connection.setRequestMethod("GET");
connection.connect();
BufferedReader reader =
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");
if (transcriptsArr != null && !transcriptsArr.isEmpty()) {
@ -183,11 +192,44 @@ public class AliAsrServiceImpl implements AliAsrService {
return extractTextFromTags(text);
}
}
throw new RuntimeException("解析识别结果失败");
} finally {
connection.disconnect();
} catch (Exception e) {
throw new RuntimeException("语音识别失败:" + e.getMessage());
} finally {
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.service.*;
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.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
@ -68,11 +69,11 @@ public class AiAssistantController extends BaseController {
}
/**
* AI
* AI(AI)
*/
@GetMapping("/getAiChatMessageList")
public R<List<AiChatMessageVo>> getAiChatMessageList(AiChatMessageBo bo) {
List<AiChatMessageVo> list = aiChatMessageService.queryList(bo);
List<AiChatMessageVo> list = aiChatMessageService.queryQaList(bo);
return R.ok(list);
}
@ -125,6 +126,7 @@ public class AiAssistantController extends BaseController {
*
*/
@PostMapping("/chat")
@SaCheckPermission("ai:aiChatMessage:list")
public Mono<AIResponse> chat(
@RequestParam("provider") String provider, @RequestBody AIRequest request) {
@ -139,8 +141,10 @@ public class AiAssistantController extends BaseController {
* @param request
* @return
*/
@SaCheckPermission("ai:aiAssistant:generateSql")
@PostMapping(value = "/generateSql")
public String generateSql(AIRequest request) {
request.setMessageDetailType(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_SQL);
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("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 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 org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.process.dto.AIRequest;
import org.dromara.ai.process.dto.AIResponse;
import org.dromara.ai.process.dto.TokenUsage;
@ -103,4 +104,7 @@ public interface IUnifiedAIProviderProcessor {
Long modelId, Long knowledgeBaseId, Long knowledgeContentId,
Long chatMessageId,String sessionId,String takeFlag,String completeFlag,
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.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.seata.common.util.StringUtils;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.AiTokenUsage;
@ -88,7 +89,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
protected void configureApiKey(AIRequest request) {
if(request.getModelId()!=null){
if (request.getModelId() != null) {
AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId());
if (aiModelVo != null) {
if (aiModelVo.getApiKey() == null) {
@ -101,12 +102,13 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
/**
* http()
*
* @param url
* @param requestBody
* @param apiKey
* @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
*/
@ -216,6 +210,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
protected void saveChatMessage(AIRequest request, String fullResponse, TokenUsage tokenUsage, LoginUser loginUser) {
try {
String messageType = StringUtils.isNotBlank(request.getMessageDetailType()) ? request.getMessageDetailType() : HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION;
String sessionId = request.getSessionId();
AiChatMessage aiChatMessage = aiChatMessageMapper
.selectOne(new LambdaQueryWrapper<AiChatMessage>()
@ -225,7 +220,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
aiChatMessage = new AiChatMessage();
aiChatMessage.setSessionId(request.getSessionId());
aiChatMessage.setMessageTopic(objectMapper.writeValueAsString(request.getMessageTopic()));
aiChatMessage.setMessageType(AIChatMessageTypeEnum.AI_CHAT.getCode());
aiChatMessage.setMessageType(messageType);
aiChatMessage.setModelId(request.getModelId());
aiChatMessage.setKnowledgeBaseId(request.getKnowledgeBaseId());
// 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,
aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1",
loginUser.getUserId(),loginUser.getTenantId(), loginUser.getDeptId());
aiChatMessage.getChatMessageId(), request.getSessionId(), request.getCarryHistoryFlag(), "1",
loginUser.getUserId(), loginUser.getTenantId(), loginUser.getDeptId());
// AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail();
@ -275,6 +270,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
/**
* token使
*
* @param messageDetailType
* @param questionContent
* @param answerContent
@ -292,12 +288,12 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
*/
@Override
public void saveTokenUsage(String messageDetailType, String questionContent, String answerContent, TokenUsage tokenUsage,
Long modelId, Long knowledgeBaseId, Long knowledgeContentId,
Long chatMessageId,String sessionId,String takeFlag,String completeFlag,
Long userId,String tenantId,Long deptId) {
Long promptToken = tokenUsage!=null ? tokenUsage.getPromptToken():null;
Long completionToken = tokenUsage!=null ? tokenUsage.getCompletionToken():null;
Long totalToken = tokenUsage!=null ? tokenUsage.getTotalToken():null;
Long modelId, Long knowledgeBaseId, Long knowledgeContentId,
Long chatMessageId, String sessionId, String takeFlag, String completeFlag,
Long userId, String tenantId, Long deptId) {
Long promptToken = tokenUsage != null ? tokenUsage.getPromptToken() : null;
Long completionToken = tokenUsage != null ? tokenUsage.getCompletionToken() : null;
Long totalToken = tokenUsage != null ? tokenUsage.getTotalToken() : null;
AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail();
aiChatMessageDetail.setMessageDetailType(messageDetailType);
@ -350,7 +346,6 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
/**
* chunk
*/
@ -364,8 +359,13 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
// getters
public String getContent() { return content; }
public TokenUsage getTokenUsage() { return tokenUsage; }
public String getContent() {
return content;
}
public TokenUsage getTokenUsage() {
return tokenUsage;
}
public boolean hasContent() {
return content != null && !content.isEmpty();
@ -377,8 +377,6 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
// /**
// * 流式回复
// * @param request

@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.domain.vo.AiModelVo;
import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.ai.mapper.AiChatMessageMapper;
@ -53,16 +54,16 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
private final String deepSeekChatModel = "deepseek-chat";
/**
* http()
*
* @param url
* @param requestBody
* @param apiKey
* @return AIResponse
*/
@Override
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey) {
public Mono<AIResponse> standardRequest(String url, String requestBody, String apiKey) {
return webClient.post()
.uri(url)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
@ -146,7 +147,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
})
.doOnComplete(() -> {
// 流完成后保存到数据库
saveChatMessage(request, fullResponseBuilder.toString(), null,loginUser);
saveChatMessage(request, fullResponseBuilder.toString(), null, loginUser);
})
.doOnError(error -> {
// 错误处理
@ -308,7 +309,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
// 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("total_tokens").asLong());
@ -331,7 +332,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
}
@Override
public List<Double> getEmbedding(GetEmbeddingResponse embeddingResponse){
public List<Double> getEmbedding(GetEmbeddingResponse embeddingResponse) {
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) {
// 简单的正则提取,作为最后手段
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.Usage;
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.test.vectorization.config.EmbeddingConfig;
import org.dromara.ai.process.dto.AIRequest;
@ -226,4 +227,9 @@ public class TencentLkeProcessor extends BaseAIProviderProcessor {
protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception {
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.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.dto.StreamResult;
import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.ai.mapper.AiChatMessageMapper;
import org.dromara.ai.process.dto.AIMessage;
@ -431,4 +432,9 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
protected StreamChunkResult extractContentAndTokensFromStreamJson(String jsonChunk) throws Exception {
return null;
}
@Override
public Flux<StreamResult> chatStreamComplete(AIRequest request, LoginUser loginUser) {
return null;
}
}

@ -1,6 +1,7 @@
package org.dromara.ai.service;
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.AIRequest;
import org.dromara.ai.process.dto.AIResponse;
@ -33,4 +34,5 @@ public interface IAIAssistantService {
*/
public JSONObject aiFillForm(AIFillFormRequest aiFillFormRequest);
// 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)
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.bo.AiChatMessageTopicBo;
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.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -182,4 +183,18 @@ public class AiChatMessageServiceImpl implements IAiChatMessageService {
.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 id="dynamicSelectSql" resultType="java.util.Map" statementType="STATEMENT">
${sql}
</select>
</mapper>

Loading…
Cancel
Save