refactor(ai): 重构AI命令服务实现类

- 重命名变量名 log 为 commandLog 以提高代码可读性
This commit is contained in:
Ray.Hao
2025-12-10 11:57:25 +08:00
parent 1f650fb469
commit 1b46b60b3f
2 changed files with 269 additions and 269 deletions

View File

@@ -32,17 +32,17 @@ public class AiCommandLogServiceImpl extends ServiceImpl<AiCommandLogMapper, AiC
@Override
public void rollbackCommand(String logId) {
AiCommandLog log = this.getById(logId);
if (log == null) {
AiCommandLog commandLog = this.getById(logId);
if (commandLog == null) {
throw new RuntimeException("命令记录不存在");
}
if (log.getExecuteStatus() == null || log.getExecuteStatus() != 1) {
if (commandLog.getExecuteStatus() == null || commandLog.getExecuteStatus() != 1) {
throw new RuntimeException("只能撤销成功执行的命令");
}
// TODO: 实现具体的回滚逻辑
log.info("撤销命令执行: logId={}, function={}", logId, log.getFunctionName());
log.info("撤销命令执行: logId={}, function={}", logId, commandLog.getFunctionName());
throw new UnsupportedOperationException("回滚功能尚未实现");
}
}

View File

@@ -37,288 +37,288 @@ import java.util.Optional;
@RequiredArgsConstructor
public class AiCommandServiceImpl implements AiCommandService {
private static final String SYSTEM_PROMPT = """
你是一个智能的企业操作助手,需要将用户的自然语言命令解析成标准的函数调用。
请返回严格的 JSON 格式,包含字段:
- success: boolean
- explanation: string
- confidence: number (0-1)
- error: string
- provider: string
- model: string
- functionCalls: 数组,每个元素包含 name、description、arguments(对象)
当无法识别命令时success=false并给出 error。
""";
private static final String SYSTEM_PROMPT = """
你是一个智能的企业操作助手,需要将用户的自然语言命令解析成标准的函数调用。
请返回严格的 JSON 格式,包含字段:
- success: boolean
- explanation: string
- confidence: number (0-1)
- error: string
- provider: string
- model: string
- functionCalls: 数组,每个元素包含 name、description、arguments(对象)
当无法识别命令时success=false并给出 error。
""";
private final AiCommandLogService logService;
private final UserTools userTools;
private final ChatClient chatClient;
private final AiCommandLogService logService;
private final UserTools userTools;
private final ChatClient chatClient;
@Override
public AiParseResponseDTO parseCommand(AiParseRequestDTO request, HttpServletRequest httpRequest) {
long startTime = System.currentTimeMillis();
String command = Optional.ofNullable(request.getCommand()).orElse("").trim();
@Override
public AiParseResponseDTO parseCommand(AiParseRequestDTO request, HttpServletRequest httpRequest) {
long startTime = System.currentTimeMillis();
String command = Optional.ofNullable(request.getCommand()).orElse("").trim();
if (StrUtil.isBlank(command)) {
return AiParseResponseDTO.builder()
.success(false)
.error("命令不能为空")
.functionCalls(Collections.emptyList())
.build();
if (StrUtil.isBlank(command)) {
return AiParseResponseDTO.builder()
.success(false)
.error("命令不能为空")
.functionCalls(Collections.emptyList())
.build();
}
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
String ipAddress = JakartaServletUtil.getClientIP(httpRequest);
AiCommandLog commandLog = new AiCommandLog();
commandLog.setUserId(userId);
commandLog.setUsername(username);
commandLog.setOriginalCommand(command);
commandLog.setIpAddress(ipAddress);
commandLog.setAiProvider("spring-ai");
commandLog.setAiModel("auto");
String systemPrompt = buildSystemPrompt();
String userPrompt = buildUserPrompt(request);
try {
log.info("📤 发送命令至 AI 模型: {}", command);
ChatResponse chatResponse = chatClient.prompt()
.system(systemPrompt)
.user(userPrompt)
.call().chatResponse();
String rawContent = Optional.ofNullable(chatResponse.getResult())
.map(result -> result.getOutput().getText())
.orElse("");
ParseResult parseResult = parseAiResponse(rawContent);
commandLog.setAiProvider(StrUtil.emptyToDefault(parseResult.provider(), "spring-ai"));
commandLog.setAiModel(StrUtil.emptyToDefault(parseResult.model(), "auto"));
commandLog.setParseStatus(parseResult.success() ? 1 : 0);
commandLog.setExplanation(parseResult.explanation());
commandLog.setFunctionCalls(JSONUtil.toJsonStr(parseResult.functionCalls()));
commandLog.setConfidence(parseResult.confidence() != null ? BigDecimal.valueOf(parseResult.confidence()) : null);
commandLog.setParseErrorMessage(parseResult.success() ? null : StrUtil.emptyToDefault(parseResult.error(), "解析失败"));
long duration = System.currentTimeMillis() - startTime;
commandLog.setParseDurationMs((int) duration);
logService.save(commandLog);
AiParseResponseDTO response = AiParseResponseDTO.builder()
.parseLogId(commandLog.getId())
.success(parseResult.success())
.functionCalls(parseResult.functionCalls())
.explanation(parseResult.explanation())
.confidence(parseResult.confidence())
.error(parseResult.error())
.rawResponse(rawContent)
.build();
if (!parseResult.success()) {
log.warn("❗️ AI 未能解析命令: {}", parseResult.error());
} else {
log.info("✅ 解析成功审计记录ID: {}", commandLog.getId());
}
return response;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
commandLog.setParseStatus(0);
commandLog.setFunctionCalls(JSONUtil.toJsonStr(Collections.emptyList()));
commandLog.setParseErrorMessage(e.getMessage());
commandLog.setParseDurationMs((int) duration);
logService.save(commandLog);
log.error("❌ 解析命令失败: {}", e.getMessage(), e);
throw new RuntimeException("解析命令失败: " + e.getMessage(), e);
}
}
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
String ipAddress = JakartaServletUtil.getClientIP(httpRequest);
AiCommandLog log = new AiCommandLog();
log.setUserId(userId);
log.setUsername(username);
log.setOriginalCommand(command);
log.setIpAddress(ipAddress);
log.setAiProvider("spring-ai");
log.setAiModel("auto");
String systemPrompt = buildSystemPrompt();
String userPrompt = buildUserPrompt(request);
try {
log.info("📤 发送命令至 AI 模型: {}", command);
ChatResponse chatResponse = chatClient.prompt()
.system(systemPrompt)
.user(userPrompt)
.call().chatResponse();
String rawContent = Optional.ofNullable(chatResponse.getResult())
.map(result -> result.getOutput().getText())
.orElse("");
ParseResult parseResult = parseAiResponse(rawContent);
log.setAiProvider(StrUtil.emptyToDefault(parseResult.provider(), "spring-ai"));
log.setAiModel(StrUtil.emptyToDefault(parseResult.model(), "auto"));
log.setParseStatus(parseResult.success() ? 1 : 0);
log.setExplanation(parseResult.explanation());
log.setFunctionCalls(JSONUtil.toJsonStr(parseResult.functionCalls()));
log.setConfidence(parseResult.confidence() != null ? BigDecimal.valueOf(parseResult.confidence()) : null);
log.setParseErrorMessage(parseResult.success() ? null : StrUtil.emptyToDefault(parseResult.error(), "解析失败"));
long duration = System.currentTimeMillis() - startTime;
log.setParseDurationMs((int) duration);
logService.save(log);
AiParseResponseDTO response = AiParseResponseDTO.builder()
.parseLogId(log.getId())
.success(parseResult.success())
.functionCalls(parseResult.functionCalls())
.explanation(parseResult.explanation())
.confidence(parseResult.confidence())
.error(parseResult.error())
.rawResponse(rawContent)
.build();
if (!parseResult.success()) {
log.warn("❗️ AI 未能解析命令: {}", parseResult.error());
} else {
log.info("✅ 解析成功审计记录ID: {}", log.getId());
}
return response;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
log.setParseStatus(0);
log.setFunctionCalls(JSONUtil.toJsonStr(Collections.emptyList()));
log.setParseErrorMessage(e.getMessage());
log.setParseDurationMs((int) duration);
logService.save(log);
log.error("❌ 解析命令失败: {}", e.getMessage(), e);
throw new RuntimeException("解析命令失败: " + e.getMessage(), e);
}
}
private String buildSystemPrompt() {
return SYSTEM_PROMPT;
}
private String buildUserPrompt(AiParseRequestDTO request) {
JSONObject payload = JSONUtil.createObj()
.set("command", request.getCommand())
.set("currentRoute", request.getCurrentRoute())
.set("currentComponent", request.getCurrentComponent())
.set("context", Optional.ofNullable(request.getContext()).orElse(Collections.emptyMap()))
.set("availableFunctions", availableFunctions());
return StrUtil.format("""
请根据以下上下文识别用户意图,并输出符合系统提示要求的 JSON
{}
""", JSONUtil.toJsonPrettyStr(payload));
}
private List<Map<String, Object>> availableFunctions() {
return List.of(
Map.of(
"name", "updateUserNickname",
"description", "根据用户名更新用户昵称",
"requiredParameters", List.of("username", "nickname")
)
);
}
private ParseResult parseAiResponse(String rawContent) {
if (StrUtil.isBlank(rawContent)) {
throw new IllegalStateException("AI 返回内容为空");
private String buildSystemPrompt() {
return SYSTEM_PROMPT;
}
try {
JSONObject jsonObject = JSONUtil.parseObj(rawContent);
boolean success = jsonObject.getBool("success", false);
String explanation = jsonObject.getStr("explanation");
Double confidence = jsonObject.containsKey("confidence") ? jsonObject.getDouble("confidence") : null;
String error = jsonObject.getStr("error");
String provider = jsonObject.getStr("provider");
String model = jsonObject.getStr("model");
private String buildUserPrompt(AiParseRequestDTO request) {
JSONObject payload = JSONUtil.createObj()
.set("command", request.getCommand())
.set("currentRoute", request.getCurrentRoute())
.set("currentComponent", request.getCurrentComponent())
.set("context", Optional.ofNullable(request.getContext()).orElse(Collections.emptyMap()))
.set("availableFunctions", availableFunctions());
List<AiFunctionCallDTO> functionCalls = toFunctionCallList(jsonObject.getJSONArray("functionCalls"));
return new ParseResult(success, explanation, confidence, error, provider, model, functionCalls);
} catch (Exception ex) {
throw new IllegalStateException("无法解析 AI 响应: " + ex.getMessage(), ex);
}
}
private List<AiFunctionCallDTO> toFunctionCallList(JSONArray array) {
if (array == null || array.isEmpty()) {
return Collections.emptyList();
return StrUtil.format("""
请根据以下上下文识别用户意图,并输出符合系统提示要求的 JSON
{}
""", JSONUtil.toJsonPrettyStr(payload));
}
List<AiFunctionCallDTO> result = new ArrayList<>();
for (Object element : array) {
JSONObject functionJson = JSONUtil.parseObj(element);
Map<String, Object> arguments = Optional.ofNullable(functionJson.getJSONObject("arguments"))
.map(obj -> obj.toBean(new TypeReference<Map<String, Object>>() {
}))
.orElse(Collections.emptyMap());
result.add(AiFunctionCallDTO.builder()
.name(functionJson.getStr("name"))
.description(functionJson.getStr("description"))
.arguments(arguments)
.build());
}
return result;
}
private record ParseResult(
boolean success,
String explanation,
Double confidence,
String error,
String provider,
String model,
List<AiFunctionCallDTO> functionCalls
) {
}
@Override
public Object executeCommand(AiExecuteRequestDTO request, HttpServletRequest httpRequest) throws Exception {
long startTime = System.currentTimeMillis();
// 获取用户信息
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
String ipAddress = JakartaServletUtil.getClientIP(httpRequest);
AiFunctionCallDTO functionCall = request.getFunctionCall();
// 根据解析日志ID获取审计记录如果不存在则创建新记录
AiCommandLog log;
if (StrUtil.isNotBlank(request.getParseLogId())) {
// 更新已存在的审计记录(解析阶段已创建)
log = logService.getById(request.getParseLogId());
if (log == null) {
throw new IllegalStateException("未找到对应的解析记录ID: " + request.getParseLogId());
}
} else {
// 如果没有解析日志ID创建新记录兼容直接执行的情况
log = new AiCommandLog();
log.setUserId(userId);
log.setUsername(username);
log.setOriginalCommand(request.getOriginalCommand());
log.setIpAddress(ipAddress);
logService.save(log);
private List<Map<String, Object>> availableFunctions() {
return List.of(
Map.of(
"name", "updateUserNickname",
"description", "根据用户名更新用户昵称",
"requiredParameters", List.of("username", "nickname")
)
);
}
// 更新执行相关字段
log.setFunctionName(functionCall.getName());
log.setFunctionArguments(JSONUtil.toJsonStr(functionCall.getArguments()));
log.setExecuteStatus(0); // 0-待执行
private ParseResult parseAiResponse(String rawContent) {
if (StrUtil.isBlank(rawContent)) {
throw new IllegalStateException("AI 返回内容为空");
}
try {
// 🎯 执行具体的函数调用
Object result = executeFunctionCall(functionCall);
try {
JSONObject jsonObject = JSONUtil.parseObj(rawContent);
boolean success = jsonObject.getBool("success", false);
String explanation = jsonObject.getStr("explanation");
Double confidence = jsonObject.containsKey("confidence") ? jsonObject.getDouble("confidence") : null;
String error = jsonObject.getStr("error");
String provider = jsonObject.getStr("provider");
String model = jsonObject.getStr("model");
// 更新执行成功
log.setExecuteStatus(1); // 1-成功
log.setExecuteErrorMessage(null);
List<AiFunctionCallDTO> functionCalls = toFunctionCallList(jsonObject.getJSONArray("functionCalls"));
// 更新审计记录
logService.updateById(log);
log.info("✅ 命令执行成功审计记录ID: {}", log.getId());
return result;
} catch (Exception e) {
// 更新执行失败
log.setExecuteStatus(-1); // -1-失败
log.setExecuteErrorMessage(e.getMessage());
// 更新审计记录
logService.updateById(log);
log.error("❌ 命令执行失败审计记录ID: {}", log.getId(), e);
// 抛出异常,由 Controller 统一处理
throw e;
}
}
/**
* 执行具体的函数调用
*/
private Object executeFunctionCall(AiFunctionCallDTO functionCall) {
String functionName = functionCall.getName();
Map<String, Object> arguments = functionCall.getArguments();
log.info("🎯 执行函数: {}, 参数: {}", functionName, arguments);
// 根据函数名称路由到不同的处理器
switch (functionName) {
case "updateUserNickname":
return executeUpdateUserNickname(arguments);
default:
throw new UnsupportedOperationException("不支持的函数: " + functionName);
}
}
/**
* 使用 Tool: 根据用户名更新用户昵称
*/
private Object executeUpdateUserNickname(Map<String, Object> arguments) {
String username = (String) arguments.get("username");
String nickname = (String) arguments.get("nickname");
log.info("🔧 [Tool] 更新用户昵称: username={}, nickname={}", username, nickname);
String resultMsg = userTools.updateUserNickname(username, nickname);
boolean success = resultMsg != null && resultMsg.contains("成功");
if (!success) {
throw new RuntimeException(resultMsg != null ? resultMsg : "更新用户昵称失败");
return new ParseResult(success, explanation, confidence, error, provider, model, functionCalls);
} catch (Exception ex) {
throw new IllegalStateException("无法解析 AI 响应: " + ex.getMessage(), ex);
}
}
return Map.of("username", username, "nickname", nickname, "message", resultMsg);
}
private List<AiFunctionCallDTO> toFunctionCallList(JSONArray array) {
if (array == null || array.isEmpty()) {
return Collections.emptyList();
}
List<AiFunctionCallDTO> result = new ArrayList<>();
for (Object element : array) {
JSONObject functionJson = JSONUtil.parseObj(element);
Map<String, Object> arguments = Optional.ofNullable(functionJson.getJSONObject("arguments"))
.map(obj -> obj.toBean(new TypeReference<Map<String, Object>>() {
}))
.orElse(Collections.emptyMap());
result.add(AiFunctionCallDTO.builder()
.name(functionJson.getStr("name"))
.description(functionJson.getStr("description"))
.arguments(arguments)
.build());
}
return result;
}
private record ParseResult(
boolean success,
String explanation,
Double confidence,
String error,
String provider,
String model,
List<AiFunctionCallDTO> functionCalls
) {
}
@Override
public Object executeCommand(AiExecuteRequestDTO request, HttpServletRequest httpRequest) throws Exception {
long startTime = System.currentTimeMillis();
// 获取用户信息
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
String ipAddress = JakartaServletUtil.getClientIP(httpRequest);
AiFunctionCallDTO functionCall = request.getFunctionCall();
// 根据解析日志ID获取审计记录如果不存在则创建新记录
AiCommandLog commandLog;
if (StrUtil.isNotBlank(request.getParseLogId())) {
// 更新已存在的审计记录(解析阶段已创建)
commandLog = logService.getById(request.getParseLogId());
if (commandLog == null) {
throw new IllegalStateException("未找到对应的解析记录ID: " + request.getParseLogId());
}
} else {
// 如果没有解析日志ID创建新记录兼容直接执行的情况
commandLog = new AiCommandLog();
commandLog.setUserId(userId);
commandLog.setUsername(username);
commandLog.setOriginalCommand(request.getOriginalCommand());
commandLog.setIpAddress(ipAddress);
logService.save(commandLog);
}
// 更新执行相关字段
commandLog.setFunctionName(functionCall.getName());
commandLog.setFunctionArguments(JSONUtil.toJsonStr(functionCall.getArguments()));
commandLog.setExecuteStatus(0); // 0-待执行
try {
// 🎯 执行具体的函数调用
Object result = executeFunctionCall(functionCall);
// 更新执行成功
commandLog.setExecuteStatus(1); // 1-成功
commandLog.setExecuteErrorMessage(null);
// 更新审计记录
logService.updateById(commandLog);
log.info("✅ 命令执行成功审计记录ID: {}", commandLog.getId());
return result;
} catch (Exception e) {
// 更新执行失败
commandLog.setExecuteStatus(-1); // -1-失败
commandLog.setExecuteErrorMessage(e.getMessage());
// 更新审计记录
logService.updateById(commandLog);
log.error("❌ 命令执行失败审计记录ID: {}", commandLog.getId(), e);
// 抛出异常,由 Controller 统一处理
throw e;
}
}
/**
* 执行具体的函数调用
*/
private Object executeFunctionCall(AiFunctionCallDTO functionCall) {
String functionName = functionCall.getName();
Map<String, Object> arguments = functionCall.getArguments();
log.info("🎯 执行函数: {}, 参数: {}", functionName, arguments);
// 根据函数名称路由到不同的处理器
switch (functionName) {
case "updateUserNickname":
return executeUpdateUserNickname(arguments);
default:
throw new UnsupportedOperationException("不支持的函数: " + functionName);
}
}
/**
* 使用 Tool: 根据用户名更新用户昵称
*/
private Object executeUpdateUserNickname(Map<String, Object> arguments) {
String username = (String) arguments.get("username");
String nickname = (String) arguments.get("nickname");
log.info("🔧 [Tool] 更新用户昵称: username={}, nickname={}", username, nickname);
String resultMsg = userTools.updateUserNickname(username, nickname);
boolean success = resultMsg != null && resultMsg.contains("成功");
if (!success) {
throw new RuntimeException(resultMsg != null ? resultMsg : "更新用户昵称失败");
}
return Map.of("username", username, "nickname", nickname, "message", resultMsg);
}
}