trace/src/main/java/com/l/tracecd/service/VoiceService.java
Lee c5dee7e444
Some checks failed
Java Maven 3.9.9 & JDK 26 CI/CD Pipeline / build-and-deploy (push) Failing after 9m3s
初始化项目
2026-06-08 15:04:18 +08:00

222 lines
9.0 KiB
Java

package com.l.tracecd.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.l.tracecd.constant.Constants;
import com.l.tracecd.dto.DeepSeekRequest;
import com.l.tracecd.dto.DeepSeekResponse;
import com.l.tracecd.dto.VoiceResponse;
import com.l.tracecd.entity.DailyRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 语音处理编排服务
* 协调 ASR → LLM → SQL 校验/执行 → 结果返回的完整流程
*/
@Service
public class VoiceService {
private static final Logger log = LoggerFactory.getLogger(VoiceService.class);
private final AsrService asrService;
private final LlmService llmService;
private final SqlValidationService sqlValidationService;
private final RecordService recordService;
private final DistinctValueService distinctValueService;
private final ObjectMapper objectMapper;
private final javax.sql.DataSource dataSource;
public VoiceService(AsrService asrService, LlmService llmService,
SqlValidationService sqlValidationService, RecordService recordService,
DistinctValueService distinctValueService, ObjectMapper objectMapper,
DataSource dataSource) {
this.asrService = asrService;
this.llmService = llmService;
this.sqlValidationService = sqlValidationService;
this.recordService = recordService;
this.distinctValueService = distinctValueService;
this.objectMapper = objectMapper;
this.dataSource = dataSource;
}
/**
* 处理语音请求的完整流程
*
* @param audioBytes 音频字节数组
* @param mimeType 音频 MIME 类型
* @return 处理结果
*/
public VoiceResponse processVoice(byte[] audioBytes, String mimeType) {
try {
// 1. ASR 识别
log.info("开始语音处理流程,音频大小: {} bytes", audioBytes.length);
String recognizedText = asrService.recognize(audioBytes, mimeType);
if (recognizedText == null || recognizedText.isBlank()) {
return VoiceResponse.error("未能识别到语音内容,请重试");
}
// 2. 发送给 LLM 分析
DeepSeekResponse.ResponseMessage llmResponse = llmService.analyze(recognizedText);
// 3. 根据 LLM 响应判断意图并处理
return handleLlmResponse(llmResponse, recognizedText);
} catch (Exception e) {
log.error("语音处理失败", e);
return VoiceResponse.error("处理失败: " + e.getMessage());
}
}
/**
* 根据 LLM 响应处理不同意图
*/
private VoiceResponse handleLlmResponse(DeepSeekResponse.ResponseMessage response,
String originalText) throws Exception {
// 情况1: 有 tool_calls → 查询意图
if (response.getToolCalls() != null && !response.getToolCalls().isEmpty()) {
log.info("检测到查询意图,处理 function call");
return handleQueryIntent(response, originalText);
}
// 情况2: 内容包含 INSERT → 录入意图
String content = response.getContent();
if (content != null && content.toUpperCase().contains("INSERT INTO")) {
log.info("检测到录入意图");
return handleRecordIntent(content, originalText);
}
// 情况3: 普通聊天回复
log.info("检测到聊天意图");
String reply = content != null ? content : "抱歉,我没有理解您的意思";
return VoiceResponse.chatReply(reply);
}
/**
* 处理录入意图:校验 SQL → 重试 → 入库
*/
private VoiceResponse handleRecordIntent(String sql, String originalText) throws Exception {
String category = null;
for (int i = 0; i < Constants.SQL_MAX_RETRIES; i++) {
try {
String validatedSql = sqlValidationService.validateInsert(sql);
DailyRecord record = recordService.insertBySql(validatedSql);
distinctValueService.updateDistinctValues(record);
category = record.getCategory();
log.info("录入成功: {}", category);
return VoiceResponse.recordSuccess(category);
} catch (SqlValidationService.SqlValidationException e) {
log.warn("SQL 校验失败 (第{}次): {}", i + 1, e.getMessage());
if (i < Constants.SQL_MAX_RETRIES - 1) {
DeepSeekResponse.ResponseMessage retryResponse =
llmService.retryInsert(originalText, e.getMessage());
String newContent = retryResponse.getContent();
if (newContent != null) {
sql = newContent;
}
}
}
}
return VoiceResponse.error("SQL 生成失败,已重试 " + Constants.SQL_MAX_RETRIES + "");
}
/**
* 处理查询意图:执行 function call → 返回结果给 LLM → 获取格式化回复
*/
private VoiceResponse handleQueryIntent(DeepSeekResponse.ResponseMessage response,
String originalText) throws Exception {
DeepSeekRequest.ToolCall toolCall = response.getToolCalls().getFirst();
String functionName = toolCall.getFunction().getName();
if (!Constants.FUNCTION_QUERY_RECORDS.equals(functionName)) {
log.warn("未知的 function call: {}", functionName);
return VoiceResponse.chatReply(response.getContent() != null ? response.getContent() : "抱歉,无法处理该请求");
}
// 解析 SQL 参数
String argumentsJson = toolCall.getFunction().getArguments();
Map<String, Object> args = objectMapper.readValue(argumentsJson,
new TypeReference<Map<String, Object>>() {});
String sql = (String) args.get("sql");
// 校验并执行 SQL
String validatedSql = sqlValidationService.validateSelect(sql);
String queryResultJson = executeSelectSql(validatedSql);
// 构建对话历史发送回 LLM
List<DeepSeekRequest.Message> messages = buildToolResultMessages(originalText, response, toolCall, queryResultJson);
DeepSeekResponse.ResponseMessage finalResponse = llmService.continueWithToolResult(messages);
String replyContent = finalResponse.getContent();
if (replyContent == null || replyContent.isBlank()) {
replyContent = "查询完成,但未能生成回复";
}
return VoiceResponse.queryResult(replyContent);
}
/**
* 执行 SELECT SQL 并返回 JSON 字符串
*/
private String executeSelectSql(String sql) throws Exception {
log.debug("执行函数查询 SQL: {}", sql);
try (var conn = dataSource.getConnection();
var stmt = conn.createStatement();
var rs = stmt.executeQuery(sql)) {
var meta = rs.getMetaData();
int colCount = meta.getColumnCount();
List<Map<String, Object>> rows = new ArrayList<>();
while (rs.next()) {
java.util.LinkedHashMap<String, Object> row = new java.util.LinkedHashMap<>();
for (int i = 1; i <= colCount; i++) {
row.put(meta.getColumnLabel(i), rs.getObject(i));
}
rows.add(row);
}
String json = objectMapper.writeValueAsString(rows);
log.info("查询返回 {} 条记录", rows.size());
return json;
}
}
/**
* 构建 function call 的完整对话历史
*/
private List<DeepSeekRequest.Message> buildToolResultMessages(
String originalText, DeepSeekResponse.ResponseMessage assistantMsg,
DeepSeekRequest.ToolCall toolCall, String queryResultJson) {
List<DeepSeekRequest.Message> messages = new ArrayList<>();
// system
messages.add(new DeepSeekRequest.Message("system",
"你是一个日常记账助手。请根据查询结果友好地回答用户的问题。多条数据请用 markdown 表格展示,表格上方展示金额汇总。"));
// user original
messages.add(new DeepSeekRequest.Message("user", originalText));
// assistant with tool_call
DeepSeekRequest.Message assistantMessage = new DeepSeekRequest.Message("assistant", null);
assistantMessage.setToolCalls(List.of(toolCall));
messages.add(assistantMessage);
// tool result
DeepSeekRequest.Message toolMessage = new DeepSeekRequest.Message("tool", queryResultJson);
toolMessage.setToolCallId(toolCall.getId());
toolMessage.setName(Constants.FUNCTION_QUERY_RECORDS);
messages.add(toolMessage);
return messages;
}
}