feat(oa): 添加 AI 对话管理功能
- 新增 AI配置、对话历史、对话消息等相关的实体类、Mapper、Service 和 Controller - 实现 AI 对话管理的基础功能,包括创建对话、发送消息、结束对话等- 集成 DeepSeek AI 服务,实现与 AI 模型的交互 - 添加 token消耗和费用计算相关逻辑
This commit is contained in:
125
ruoyi-oa/src/main/java/com/ruoyi/oa/utils/AiServiceUtil.java
Normal file
125
ruoyi-oa/src/main/java/com/ruoyi/oa/utils/AiServiceUtil.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package com.ruoyi.oa.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.oa.domain.vo.SysOaAiConfigVo;
|
||||
import com.ruoyi.oa.domain.vo.SysOaAiMessageVo;
|
||||
import com.ruoyi.oa.service.ISysOaAiConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* AI服务工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-12-19
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AiServiceUtil {
|
||||
|
||||
private final ISysOaAiConfigService configService;
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 调用DeepSeek AI服务(单次对话)
|
||||
*
|
||||
* @param message 用户消息
|
||||
* @return AI回复
|
||||
*/
|
||||
public String callDeepSeek(String message) {
|
||||
return callDeepSeekWithHistory(message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用DeepSeek AI服务(连续对话)
|
||||
*
|
||||
* @param message 用户消息
|
||||
* @param conversationHistory 对话历史
|
||||
* @return AI回复
|
||||
*/
|
||||
public String callDeepSeekWithHistory(String message, List<SysOaAiMessageVo> conversationHistory) {
|
||||
try {
|
||||
// 获取AI配置
|
||||
SysOaAiConfigVo config = configService.getActiveConfig();
|
||||
if (config == null) {
|
||||
throw new RuntimeException("未找到可用的AI配置");
|
||||
}
|
||||
|
||||
// 构建请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + config.getApiKey());
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", config.getModelName());
|
||||
requestBody.put("temperature", config.getTemperature());
|
||||
requestBody.put("max_tokens", config.getMaxTokens());
|
||||
|
||||
List<Map<String, String>> messages = new ArrayList<>();
|
||||
|
||||
// 添加对话历史
|
||||
if (conversationHistory != null && !conversationHistory.isEmpty()) {
|
||||
for (SysOaAiMessageVo historyMessage : conversationHistory) {
|
||||
Map<String, String> historyMsg = new HashMap<>();
|
||||
historyMsg.put("role", historyMessage.getRole());
|
||||
historyMsg.put("content", historyMessage.getContent());
|
||||
messages.add(historyMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加当前用户消息
|
||||
Map<String, String> userMessage = new HashMap<>();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", message);
|
||||
messages.add(userMessage);
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
String url = config.getBaseUrl() + "/chat/completions";
|
||||
|
||||
log.info("调用DeepSeek API: {}, 消息数量: {}", url, messages.size());
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
JsonNode jsonNode = objectMapper.readTree(response.getBody());
|
||||
String aiResponse = jsonNode.path("choices").path(0).path("message").path("content").asText();
|
||||
|
||||
// 记录token使用情况
|
||||
int totalTokens = jsonNode.path("usage").path("total_tokens").asInt();
|
||||
log.info("AI回复成功,消耗token: {}", totalTokens);
|
||||
|
||||
return aiResponse;
|
||||
} else {
|
||||
log.error("DeepSeek API调用失败: {}", response.getStatusCode());
|
||||
throw new RuntimeException("AI服务调用失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("调用DeepSeek服务异常", e);
|
||||
throw new RuntimeException("AI服务调用异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算费用(示例:每1000个token 0.002美元)
|
||||
*
|
||||
* @param tokens token数量
|
||||
* @return 费用
|
||||
*/
|
||||
public BigDecimal calculateCost(int tokens) {
|
||||
// 这里可以根据实际的定价策略计算费用
|
||||
BigDecimal costPerToken = new BigDecimal("0.000002"); // 每token 0.000002美元
|
||||
return costPerToken.multiply(new BigDecimal(tokens));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user