AI:完善AI功能,封装阿里通义千问
hwmom-htk
xs 3 months ago
parent 1d77494d9d
commit 26a4ed046c

@ -11,8 +11,17 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.dromara.ai.domain.AiAsrRecord;
import org.dromara.ai.mapper.AiAsrRecordMapper;
import org.dromara.common.constant.HwMomAiConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.api.RemoteConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
@ -20,6 +29,7 @@ import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
@ -30,16 +40,23 @@ public class AliAsrServiceImpl implements AliAsrService {
private static final Logger logger = LoggerFactory.getLogger(AliAsrServiceImpl.class);
@Autowired
private AiAsrRecordMapper aiAsrRecordMapper;
// 从配置文件读取API Key
// @Value("${aliyun.asr.api-key}")
private String apiKey = "sk-ce77518fcbc54b2bbee2a7c793be6492";
@Value("${asr.aliyun.api-key}")
private String apiKey;
// 临时文件存储路径
// @Value("${file.upload.temp-dir}")
// private String tempDir;
// 模型名称
private static final String MODEL_NAME = "sensevoice-v1";
// 从配置文件读取API Key
@Value("${asr.aliyun.model-name}")
private String modelName;
// private static final String MODEL_NAME = "sensevoice-v1";
// 语言提示
private static final String[] LANGUAGE_HINTS = {"zh"};
@ -73,9 +90,13 @@ public class AliAsrServiceImpl implements AliAsrService {
public String recognizeSpeechByUrl(String audioUrl) {
try {
// 构建识别参数
if(StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)){
throw new RuntimeException("请配置apiKey和modelName");
}
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(apiKey)
.model(MODEL_NAME)
.apiKey(EncryptUtils.decryptByBase64(apiKey))
.model(modelName)
.fileUrls(Arrays.asList(audioUrl))
.parameter("language_hints", LANGUAGE_HINTS)
.build();
@ -88,6 +109,15 @@ public class AliAsrServiceImpl implements AliAsrService {
logger.info("语音识别任务提交成功TaskId: {}", taskId);
// 循环获取任务执行结果,直到任务结束
//保存语音识别记录
AiAsrRecord aiAsrRecord = new AiAsrRecord();
aiAsrRecord.setModelName(modelName);
aiAsrRecord.setAsrFileUrl(audioUrl);
aiAsrRecord.setTenantId(LoginHelper.getTenantId());
aiAsrRecord.setCreateBy(LoginHelper.getUserId());
aiAsrRecord.setCreateDept(LoginHelper.getDeptId());
aiAsrRecord.setCreateTime(new Date());
while (true) {
result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId));
@ -95,6 +125,10 @@ public class AliAsrServiceImpl implements AliAsrService {
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());
}
@ -104,7 +138,13 @@ public class AliAsrServiceImpl implements AliAsrService {
}
// 解析识别结果
return parseRecognitionResult(result);
String recognitionResult = parseRecognitionResult(result);
aiAsrRecord.setAsrContent(recognitionResult);
aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_SUCCESS);
aiAsrRecordMapper.insert(aiAsrRecord);
return recognitionResult;
} catch (Exception e) {
logger.error("语音识别过程发生错误", e);

@ -0,0 +1,117 @@
package org.dromara.ai.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.ai.domain.vo.AiAsrRecordVo;
import org.dromara.ai.domain.bo.AiAsrRecordBo;
import org.dromara.ai.service.IAiAsrRecordService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* AI
* 访:/ai/asrRecord
*
* @author xins
* @date 2025-10-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/asrRecord")
public class AiAsrRecordController extends BaseController {
private final IAiAsrRecordService aiAsrRecordService;
/**
* AI
*/
@SaCheckPermission("ai:asrRecord:list")
@GetMapping("/list")
public TableDataInfo<AiAsrRecordVo> list(AiAsrRecordBo bo, PageQuery pageQuery) {
return aiAsrRecordService.queryPageList(bo, pageQuery);
}
/**
* AI
*/
@SaCheckPermission("ai:asrRecord:export")
@Log(title = "AI语音识别记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(AiAsrRecordBo bo, HttpServletResponse response) {
List<AiAsrRecordVo> list = aiAsrRecordService.queryList(bo);
ExcelUtil.exportExcel(list, "AI语音识别记录", AiAsrRecordVo.class, response);
}
/**
* AI
*
* @param asrRecordId
*/
@SaCheckPermission("ai:asrRecord:query")
@GetMapping("/{asrRecordId}")
public R<AiAsrRecordVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long asrRecordId) {
return R.ok(aiAsrRecordService.queryById(asrRecordId));
}
/**
* AI
*/
@SaCheckPermission("ai:asrRecord:add")
@Log(title = "AI语音识别记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody AiAsrRecordBo bo) {
return toAjax(aiAsrRecordService.insertByBo(bo));
}
/**
* AI
*/
@SaCheckPermission("ai:asrRecord:edit")
@Log(title = "AI语音识别记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody AiAsrRecordBo bo) {
return toAjax(aiAsrRecordService.updateByBo(bo));
}
/**
* AI
*
* @param asrRecordIds
*/
@SaCheckPermission("ai:asrRecord:remove")
@Log(title = "AI语音识别记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{asrRecordIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] asrRecordIds) {
return toAjax(aiAsrRecordService.deleteWithValidByIds(List.of(asrRecordIds), true));
}
/**
* AI
*/
@GetMapping("/getAiAsrRecordList")
public R<List<AiAsrRecordVo>> getAiAsrRecordList(AiAsrRecordBo bo) {
List<AiAsrRecordVo> list = aiAsrRecordService.queryList(bo);
return R.ok(list);
}
}

@ -0,0 +1,107 @@
package org.dromara.ai.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* AI ai_asr_record
*
* @author xins
* @date 2025-10-13
*/
@Data
@TableName("ai_asr_record")
public class AiAsrRecord implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId(value = "asr_record_id", type = IdType.AUTO)
private Long asrRecordId;
/**
* AIIDai_platform
*/
private Long platformId;
/**
*
*/
private String modelName;
/**
*
*/
private String asrFileUrl;
/**
* ID
*/
private String taskId;
/**
*
*/
private String asrContent;
/**
* (10)
*/
private String asrResult;
/**
*
*/
private String asrFailedReason;
/**
*
*/
private String tenantId;
/**
*
*/
@JsonIgnore
@TableField(exist = false)
private String searchValue;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private Long createDept;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
*
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private Map<String, Object> params = new HashMap<>();
}

@ -0,0 +1,68 @@
package org.dromara.ai.domain.bo;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.ai.domain.AiAsrRecord;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* AI ai_asr_record
*
* @author xins
* @date 2025-10-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AiAsrRecord.class, reverseConvertGenerate = false)
public class AiAsrRecordBo extends BaseEntity {
/**
* ID
*/
private Long asrRecordId;
/**
* AIIDai_platform
*/
private Long platformId;
/**
*
*/
@ExcelProperty(value = "模型名称")
@NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String modelName;
/**
*
*/
private String asrFileUrl;
/**
* ID
*/
private String taskId;
/**
*
*/
private String asrContent;
/**
* (10)
*/
@NotBlank(message = "识别结果(1成功0失败)不能为空", groups = { AddGroup.class, EditGroup.class })
private String asrResult;
/**
*
*/
private String asrFailedReason;
}

@ -0,0 +1,80 @@
package org.dromara.ai.domain.vo;
import org.dromara.ai.domain.AiAsrRecord;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* AI ai_asr_record
*
* @author xins
* @date 2025-10-13
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AiAsrRecord.class)
public class AiAsrRecordVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ExcelProperty(value = "记录ID主键")
private Long asrRecordId;
/**
* AIIDai_platform
*/
@ExcelProperty(value = "AI平台ID")
private Long platformId;
/**
*
*/
@ExcelProperty(value = "模型名称")
private String modelName;
/**
*
*/
@ExcelProperty(value = "语音识别文件地址")
private String asrFileUrl;
/**
* ID
*/
@ExcelProperty(value = "任务ID")
private String taskId;
/**
*
*/
@ExcelProperty(value = "语音识别内容")
private String asrContent;
/**
* (10)
*/
@ExcelProperty(value = "识别结果(1成功0失败)")
private String asrResult;
/**
*
*/
@ExcelProperty(value = "识别失败原因")
private String asrFailedReason;
}

@ -0,0 +1,15 @@
package org.dromara.ai.mapper;
import org.dromara.ai.domain.AiAsrRecord;
import org.dromara.ai.domain.vo.AiAsrRecordVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* AIMapper
*
* @author xins
* @date 2025-10-13
*/
public interface AiAsrRecordMapper extends BaseMapperPlus<AiAsrRecord, AiAsrRecordVo> {
}

@ -25,5 +25,10 @@ public class AIMessage {
*
*/
private Long timestamp;
/**
* AIID
*/
private Long modelId;
}

@ -96,8 +96,11 @@ public interface IUnifiedAIProviderProcessor {
* @param takeFlag
* @param completeFlag
* @param userId
* @param tenantId
* @param deptId
*/
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);
Long chatMessageId,String sessionId,String takeFlag,String completeFlag,
Long userId,String tenantId,Long deptId);
}

@ -9,8 +9,10 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiChatMessageDetail;
import org.dromara.ai.domain.AiTokenUsage;
import org.dromara.ai.domain.vo.AiModelVo;
import org.dromara.ai.mapper.AiChatMessageDetailMapper;
import org.dromara.ai.mapper.AiChatMessageMapper;
import org.dromara.ai.mapper.AiModelMapper;
import org.dromara.ai.mapper.AiTokenUsageMapper;
import org.dromara.ai.process.dto.AIMessage;
import org.dromara.ai.process.dto.AIRequest;
@ -20,6 +22,7 @@ import org.dromara.ai.process.enums.AIChatMessageTypeEnum;
import org.dromara.ai.process.provider.processor.IUnifiedAIProviderProcessor;
import org.dromara.ai.test.ChatRequest;
import org.dromara.common.constant.HwMomAiConstants;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
@ -50,6 +53,8 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
private AiChatMessageDetailMapper aiChatMessageDetailMapper;
@Autowired
private AiTokenUsageMapper aiTokenUsageMapper;
@Autowired
private AiModelMapper aiModelMapper;
// 用于解析流式JSON块的模式
private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}");
@ -82,34 +87,18 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
}
/**
* JSON
*/
protected abstract String extractContentFromStreamJson(String jsonChunk) throws Exception;
/**
* JSON
*/
protected abstract AIResponse extractAIResponse(String json) throws Exception;
/**
* SSEJSON
*/
protected Flux<String> extractJsonChunks(String rawChunk) {
// 简单实现假设每个事件都是完整的JSON
// return Flux.just(data);
System.out.println("RawChunk: " + rawChunk);
// 从原始块中提取完整的JSON对象
Matcher matcher = JSON_PATTERN.matcher(rawChunk);
Flux<String> chunks = Flux.empty();
while (matcher.find()) {
chunks = chunks.concatWithValues(matcher.group());
protected void configureApiKey(AIRequest request) {
if(request.getModelId()!=null){
AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId());
if (aiModelVo != null) {
if (aiModelVo.getApiKey() == null) {
throw new RuntimeException("请设置AI模型的API Key");
}
request.setApiKey(EncryptUtils.decryptByBase64(aiModelVo.getApiKey()));
}
}
return chunks;
}
/**
* http()
* @param url
@ -117,24 +106,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
* @param apiKey
* @return AIResponse
*/
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey) {
return webClient.post()
.uri(url)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.flatMap(response -> {
try {
return Mono.just(extractAIResponse(response));
} catch (Exception e) {
return buildErrorResponse("解析响应失败: " + e.getMessage());
}
})
.onErrorResume(e -> buildErrorResponse("API调用失败: " + e.getMessage()));
// .timeout(Duration.ofSeconds(30));
}
public abstract Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey);
/**
@ -166,6 +138,37 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
}
/**
* JSON
*/
protected abstract String extractContentFromStreamJson(String jsonChunk) throws Exception;
/**
* JSON
*/
protected abstract AIResponse extractAIResponse(String json) throws Exception;
/**
* SSEJSON
*/
protected Flux<String> extractJsonChunks(String rawChunk) {
// 简单实现假设每个事件都是完整的JSON
// return Flux.just(data);
System.out.println("RawChunk: " + rawChunk);
// 从原始块中提取完整的JSON对象
Matcher matcher = JSON_PATTERN.matcher(rawChunk);
Flux<String> chunks = Flux.empty();
while (matcher.find()) {
chunks = chunks.concatWithValues(matcher.group());
}
return chunks;
}
@ -238,7 +241,8 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION, objectMapper.writeValueAsString(request.getQuestionContent()),
objectMapper.writeValueAsString(fullResponse), tokenUsage, request.getModelId(), request.getKnowledgeBaseId(), null,
aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1", loginUser.getUserId());
aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1",
loginUser.getUserId(),loginUser.getTenantId(), loginUser.getDeptId());
// AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail();
@ -283,11 +287,14 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
* @param takeFlag
* @param completeFlag
* @param userId
* @param tenantId
* @param deptId
*/
@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) {
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;
@ -306,6 +313,9 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce
aiChatMessageDetail.setCompleteFlag(completeFlag);
aiChatMessageDetail.setChatMessageId(chatMessageId);
aiChatMessageDetail.setSessionId(sessionId);
aiChatMessageDetail.setCreateBy(userId);
aiChatMessageDetail.setTenantId(tenantId);
aiChatMessageDetail.setCreateDept(deptId);
aiChatMessageDetailMapper.insert(aiChatMessageDetail);
MPJLambdaWrapper<AiTokenUsage> lqw = JoinWrappers.lambda(AiTokenUsage.class)

@ -25,6 +25,8 @@ import org.dromara.ai.service.IAiChatMessageService;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.dromara.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -47,16 +49,38 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
// 用于解析流式JSON块的模式
private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}");
// private final String apiKey = "sk-e1df7a607644479e8ebad3be233ddafa";
private final String apiUrl = "https://api.deepseek.com/v1/";
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
private final String deepSeekChatModel = "deepseek-chat";
@Autowired
private AiModelMapper aiModelMapper;
/**
* http()
* @param url
* @param requestBody
* @param apiKey
* @return AIResponse
*/
@Override
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey) {
return webClient.post()
.uri(url)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.flatMap(response -> {
try {
return Mono.just(extractAIResponse(response));
} catch (Exception e) {
return buildErrorResponse("解析响应失败: " + e.getMessage());
}
})
.onErrorResume(e -> buildErrorResponse("API调用失败: " + e.getMessage()));
// .timeout(Duration.ofSeconds(30));
}
public Mono<AIResponse> chatTest(AIRequest request) {
AIMessage aiMessage = new AIMessage();
aiMessage.setRole("user");
@ -334,16 +358,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor {
return "";
}
private void configureApiKey(AIRequest request) {
AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId());
if (aiModelVo != null) {
if (aiModelVo.getApiKey() == null) {
throw new RuntimeException("请设置AI模型的API Key");
}
request.setApiKey(EncryptUtils.decryptByBase64(aiModelVo.getApiKey()));
}
}

@ -60,6 +60,17 @@ public class TencentLkeProcessor extends BaseAIProviderProcessor {
properties.getTencentLke().getModel());
}
/**
* http()
* @param url
* @param requestBody
* @param apiKey
* @return AIResponse
*/
@Override
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey){
return null;
}
@Override
public List<Double> getEmbeddingTest(AIRequest aiRequest) {

@ -2,12 +2,14 @@ package org.dromara.ai.process.provider.processor.impl;
//import com.example.deepseek.dto.EmbeddingResponse;
import com.alibaba.dashscope.aigc.generation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.mapper.AiChatMessageDetailMapper;
@ -22,14 +24,28 @@ import org.dromara.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.lang.System;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
/**
* @Author xins
* @Date 2025/8/15 11:00
@ -42,11 +58,7 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
// 用于解析流式JSON块的模式
private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}");
private final String apiKey = "sk-e1df7a607644479e8ebad3be233ddafa";
private final String apiUrl = "https://api.t.com/v1/";
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
private final String deepSeekChatModel = "deepseek-chat";
private static final String ALIYUN_MODEL_NAME = "qwen-plus";
@Autowired
@ -54,14 +66,57 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
@Autowired
private AiChatMessageDetailMapper aiChatMessageDetailMapper;
/**
* http()
* @param url
* @param requestBody
* @param apiKey
* @return AIResponse
*/
@Override
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey){
return null;
}
/**
* http()
* @param aiRequest
* @return AIResponse
*/
private Mono<AIResponse> aliStandardRequest(AIRequest aiRequest){
Generation gen = new Generation();
Message systemMsg = Message.builder()
.role(Role.SYSTEM.getValue())
.content("You are a helpful assistant.")
.build();
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content(aiRequest.getText())
.build();
GenerationParam param = GenerationParam.builder()
// 若没有配置环境变量请用阿里云百炼API Key将下行替换为.apiKey("sk-xxx")
.apiKey(aiRequest.getApiKey())
// 模型列表https://help.aliyun.com/zh/model-studio/getting-started/models
.model(ALIYUN_MODEL_NAME)
.messages(Arrays.asList(systemMsg, userMsg))
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
try {
GenerationResult generationResult = gen.call(param);
return Mono.just(extractAliAIResponse(generationResult));
} catch (NoApiKeyException e) {
throw new RuntimeException(e);
} catch (InputRequiredException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Mono<AIResponse> chatTest(AIRequest request) {
AIMessage aiMessage = new AIMessage();
aiMessage.setRole("user");
aiMessage.setContent("这是一个测试请求,请回复'测试成功'");
request.setMessages(Collections.singletonList(
aiMessage
));
request.setText("这是一个测试请求,请回复'测试成功'");
String apiEncryptFlag = request.getApiKeyEncryptFlag();
if(apiEncryptFlag.equals("1")){
request.setApiKey(EncryptUtils.decryptByBase64(request.getApiKey()));
@ -74,56 +129,89 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
@Override
public Mono<AIResponse> chat(AIRequest request) {
try {
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("model", deepSeekChatModel);
rootNode.set("messages", objectMapper.valueToTree(request.getMessages()));
if (request.getTemperature() != null) {
rootNode.put("temperature", request.getTemperature());
}
if (request.getMaxTokens() != null) {
rootNode.put("max_tokens", request.getMaxTokens());
}
String requestBody = objectMapper.writeValueAsString(rootNode);
return standardRequest(API_URL, requestBody, request.getApiKey());
} catch (IOException e) {
configureApiKey(request);
return aliStandardRequest(request);
} catch (Exception e) {
return buildErrorResponse("构建请求失败: " + e.getMessage());
}
}
/**
*
*/
private Flowable<GenerationResult> createStreamFlowable(AIRequest aiRequest)
throws ApiException, NoApiKeyException, InputRequiredException {
Generation gen = new Generation();
List<AIMessage> aiMessages = aiRequest.getMessages();
List<Message> messages = new ArrayList<>();
Message systemMsg = Message.builder()
.role(Role.SYSTEM.getValue())
.content("You are a helpful assistant.")
.build();
messages.add(systemMsg);
for(AIMessage aiMessage:aiMessages){
Message inputMessage = Message.builder()
.role(aiMessage.getRole())
.content(aiMessage.getContent())
.build();
messages.add(inputMessage);
}
GenerationParam param = GenerationParam.builder()
.apiKey(aiRequest.getApiKey())
.model(ALIYUN_MODEL_NAME)
.messages(messages)
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
return gen.streamCall(param);
}
@Override
public Flux<String> chatStreamContent(AIRequest request, LoginUser loginUser) {
try {
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("model", deepSeekChatModel);
rootNode.set("messages", objectMapper.valueToTree(request.getMessages()));
rootNode.put("stream", true);
return null;
}
if (request.getTemperature() != null) {
rootNode.put("temperature", request.getTemperature());
}
// request.setApiKey();
// 执行流式请求并收集完整响应
StringBuilder fullResponseBuilder = new StringBuilder();
String requestBody = objectMapper.writeValueAsString(rootNode);
/**
* GenerationResult
*/
private static String extractContent(GenerationResult result) {
if (result == null || result.getOutput() == null) {
return null;
// return executeStreamRequest(API_URL, requestBody, apiKey).doOnNext(chunk -> {
// // 收集每个chunk
// fullResponseBuilder.append(chunk);
// })
// .doOnComplete(() -> {
// // 流完成后保存到数据库
// saveChatMessage(request, fullResponseBuilder.toString());
// })
// .doOnError(error -> {
// // 错误处理
//// log.error("流式请求出错", error);
// });
} catch (IOException e) {
return Flux.error(new RuntimeException("构建请求失败: " + e.getMessage()));
}
try {
return result.getOutput()
.getChoices()
.get(0)
.getMessage()
.getContent();
} catch (Exception e) {
return null;
}
}
/**
*
*/
private static boolean isFinished(GenerationResult result) {
if (result == null || result.getOutput() == null) {
return false;
}
try {
String finishReason = result.getOutput()
.getChoices()
.get(0)
.getFinishReason();
return "stop".equals(finishReason);
} catch (Exception e) {
return false;
}
}
@ -162,6 +250,18 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
}
}
protected AIResponse extractAliAIResponse(GenerationResult generationResult) throws Exception {
List<GenerationOutput.Choice> choices = generationResult.getOutput().getChoices();
String content = choices.get(0).getMessage().getContent();
GenerationUsage generationUsage = generationResult.getUsage();
TokenUsage tokenUsage = new TokenUsage(
generationUsage.getInputTokens().longValue(),generationUsage.getOutputTokens().longValue(),
generationUsage.getTotalTokens().longValue()
);
return new AIResponse(content, tokenUsage);
}
@Override
protected AIResponse extractAIResponse(String json) throws Exception {
JsonNode node = objectMapper.readTree(json);
@ -264,7 +364,67 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
@Override
public Flux<String> chatStream(AIRequest request, LoginUser loginUser) {
return null;
configureApiKey(request);
return Flux.create(sink -> {
try {
Flowable<GenerationResult> flowable = createStreamFlowable(request);
AtomicBoolean isCompleted = new AtomicBoolean(false);
// 用于收集完整响应和token信息
TokenUsage finalTokenUsage = new TokenUsage(0L, 0L, 0L);
flowable.subscribe(
// onNext
generationResult -> {
if (sink.isCancelled()) {
return;
}
try {
String content = extractContent(generationResult);
if (content != null && !content.isEmpty()) {
sink.next(content);
}
// 检查是否结束
if (isFinished(generationResult)) {
if (!isCompleted.getAndSet(true)) {
sink.complete();
}
GenerationUsage usage = generationResult.getUsage();
finalTokenUsage.setPromptToken(usage.getInputTokens().longValue());
finalTokenUsage.setCompletionToken(usage.getOutputTokens().longValue());
finalTokenUsage.setTotalToken(usage.getTotalTokens().longValue());
saveChatMessage(request, content, finalTokenUsage, loginUser);
}
} catch (Exception e) {
sink.error(e);
}
},
// onError
error -> {
if (!isCompleted.getAndSet(true)) {
sink.error(error);
}
},
// onComplete
() -> {
if (!isCompleted.getAndSet(true)) {
sink.complete();
}
}
);
// 设置取消回调
sink.onCancel(() -> {
isCompleted.set(true);
// 可以在这里添加资源清理逻辑
});
} catch (Exception e) {
sink.error(e);
}
}, FluxSink.OverflowStrategy.BUFFER);
}
@Override

@ -0,0 +1,69 @@
package org.dromara.ai.service;
import org.dromara.ai.domain.AiAsrRecord;
import org.dromara.ai.domain.vo.AiAsrRecordVo;
import org.dromara.ai.domain.bo.AiAsrRecordBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* AIService
*
* @author xins
* @date 2025-10-13
*/
public interface IAiAsrRecordService {
/**
* AI
*
* @param asrRecordId
* @return AI
*/
AiAsrRecordVo queryById(Long asrRecordId);
/**
* AI
*
* @param bo
* @param pageQuery
* @return AI
*/
TableDataInfo<AiAsrRecordVo> queryPageList(AiAsrRecordBo bo, PageQuery pageQuery);
/**
* AI
*
* @param bo
* @return AI
*/
List<AiAsrRecordVo> queryList(AiAsrRecordBo bo);
/**
* AI
*
* @param bo AI
* @return
*/
Boolean insertByBo(AiAsrRecordBo bo);
/**
* AI
*
* @param bo AI
* @return
*/
Boolean updateByBo(AiAsrRecordBo bo);
/**
* AI
*
* @param ids
* @param isValid
* @return
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

@ -150,7 +150,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
tencentLkeProcessor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION_VECTOR,"AI问答获取向量", null, tokenUsage,
embeddingModelId, aiRequest.getKnowledgeBaseId(), null,
null, null, "0", "1",LoginHelper.getUserId());
null, null, "0", "1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId());
List<Double> queryEmbedding = tencentLkeProcessor.getEmbedding(embeddingResponses);
@ -284,7 +284,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
//```
processor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_SQL,prompt, content,response.block().getTokenUsage(),
aiRequest.getModelId(), null,null,
null,null,"0","1",LoginHelper.getUserId());
null,null,"0","1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId());
return extractSqlFromContent(content);
} else {
@ -434,6 +434,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
Long modelId = aiFillFormRequest.getModelId();
AIRequest aiRequest = new AIRequest();
aiRequest.setMessages(Collections.singletonList(aiMessage));
aiRequest.setText(sb.toString());
aiRequest.setModelId(modelId);
Mono<AIResponse> response = processor.chat(aiRequest);
@ -443,7 +444,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
parseRelateTable(aiFormSettingDetailList, contentJson);
processor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_FORM,sb.toString(), content, response.block().getTokenUsage(),
modelId, null, null,
null, null, "0", "1",LoginHelper.getUserId());
null, null, "0", "1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId());
System.out.println(contentJson.toJSONString());
return contentJson;

@ -0,0 +1,136 @@
package org.dromara.ai.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.dromara.ai.domain.bo.AiAsrRecordBo;
import org.dromara.ai.domain.vo.AiAsrRecordVo;
import org.dromara.ai.domain.AiAsrRecord;
import org.dromara.ai.mapper.AiAsrRecordMapper;
import org.dromara.ai.service.IAiAsrRecordService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* AIService
*
* @author xins
* @date 2025-10-13
*/
@RequiredArgsConstructor
@Service
public class AiAsrRecordServiceImpl implements IAiAsrRecordService {
private final AiAsrRecordMapper baseMapper;
/**
* AI
*
* @param asrRecordId
* @return AI
*/
@Override
public AiAsrRecordVo queryById(Long asrRecordId){
return baseMapper.selectVoById(asrRecordId);
}
/**
* AI
*
* @param bo
* @param pageQuery
* @return AI
*/
@Override
public TableDataInfo<AiAsrRecordVo> queryPageList(AiAsrRecordBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<AiAsrRecord> lqw = buildQueryWrapper(bo);
Page<AiAsrRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* AI
*
* @param bo
* @return AI
*/
@Override
public List<AiAsrRecordVo> queryList(AiAsrRecordBo bo) {
MPJLambdaWrapper<AiAsrRecord> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private MPJLambdaWrapper<AiAsrRecord> buildQueryWrapper(AiAsrRecordBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<AiAsrRecord> lqw = JoinWrappers.lambda(AiAsrRecord.class)
.selectAll(AiAsrRecord.class)
.eq(bo.getAsrRecordId() != null, AiAsrRecord::getAsrRecordId, bo.getAsrRecordId())
.eq(StringUtils.isNotBlank(bo.getAsrFileUrl()), AiAsrRecord::getAsrFileUrl, bo.getAsrFileUrl())
.eq(StringUtils.isNotBlank(bo.getAsrContent()), AiAsrRecord::getAsrContent, bo.getAsrContent())
.eq(StringUtils.isNotBlank(bo.getAsrResult()), AiAsrRecord::getAsrResult, bo.getAsrResult())
.eq(StringUtils.isNotBlank(bo.getAsrFailedReason()), AiAsrRecord::getAsrFailedReason, bo.getAsrFailedReason())
.orderByDesc(AiAsrRecord::getCreateTime);
return lqw;
}
/**
* AI
*
* @param bo AI
* @return
*/
@Override
public Boolean insertByBo(AiAsrRecordBo bo) {
AiAsrRecord add = MapstructUtils.convert(bo, AiAsrRecord.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setAsrRecordId(add.getAsrRecordId());
}
return flag;
}
/**
* AI
*
* @param bo AI
* @return
*/
@Override
public Boolean updateByBo(AiAsrRecordBo bo) {
AiAsrRecord update = MapstructUtils.convert(bo, AiAsrRecord.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
*
*/
private void validEntityBeforeSave(AiAsrRecord entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* AI
*
* @param ids
* @param isValid
* @return
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

@ -172,6 +172,7 @@ public class AiChatMessageDetailServiceImpl implements IAiChatMessageDetailServi
aiAssistantMessage.setRole("assistant");
aiAssistantMessage.setContent(aiChatMessageDetail.getAnswerContent());
aiAssistantMessage.setTimestamp(aiChatMessageDetail.getCreateTime().getTime());
aiAssistantMessage.setModelId(aiChatMessageDetail.getModelId());
aiMessages.add(aiAssistantMessage);
}

@ -357,7 +357,7 @@ public class AiKnowledgeBaseServiceImpl implements IAiKnowledgeBaseService {
TokenUsage tokenUsage = new TokenUsage(null,null,usage.getTotalTokens());
tencentLkeProcessor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_CONTENT_VECTOR,"AI上传知识库内容获取向量", null, tokenUsage,
modelId, aiRequest.getKnowledgeBaseId(), aiKnowledgeEmbedding.getKnowledgeContentId(),
null, null, "0", "1", LoginHelper.getUserId());
null, null, "0", "1", LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId());
// 转换结果格式

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.ai.mapper.AiAsrRecordMapper">
</mapper>
Loading…
Cancel
Save