Compare commits

..

No commits in common. "4e5c17d59f37f6b27232789860c397d0c96e3922" and "5be7f81924dcadae6cf85c3d59e04131464fd44c" have entirely different histories.

25 changed files with 110 additions and 312 deletions

View File

@ -1,6 +1,5 @@
package com.cmvr.web.controller.test; package com.cmvr.web.controller.test;
import com.cmvr.common.annotation.Anonymous;
import com.cmvr.common.core.controller.BaseController; import com.cmvr.common.core.controller.BaseController;
import com.cmvr.common.core.domain.AjaxResult; import com.cmvr.common.core.domain.AjaxResult;
import com.cmvr.test.flow.context.TaskContextManager; import com.cmvr.test.flow.context.TaskContextManager;
@ -27,8 +26,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@Api(tags = "测试--流程服务") @Api(tags = "测试--流程服务")
@RestController @RestController
@ -43,34 +40,15 @@ public class TeFlowController extends BaseController {
private final FlowControlService flowControlService; private final FlowControlService flowControlService;
private final FlowActionExecutorService flowActionExecutorService; private final FlowActionExecutorService flowActionExecutorService;
private final TiTouchOperateService tiTouchOperateService; private final TiTouchOperateService tiTouchOperateService;
private final List<String> taskInstIdList = new ArrayList<>();
@ApiOperation("流程发布") @ApiOperation("流程发布")
@PostMapping("/publish") @PostMapping("/publish")
public AjaxResult publish(@RequestBody TeDetectionItem detectionItem) { public AjaxResult publish(@RequestBody TeDetectionItem detectionItem) {
return toAjax(teDetectionItemService.publish(detectionItem.getId())); return toAjax(teDetectionItemService.publish(detectionItem.getId()));
} }
@ApiOperation("任务执行入口")
@PostMapping("/demo")
@Anonymous
public AjaxResult demo(@Valid @RequestBody TeTaskExecuteNormalVO taskExecuteNormalVO) {
if (taskInstIdList.size() > 0) {
try {
taskInstIdList.forEach(flowControlService::stop);
} catch (Exception e) {
// 忽略
}
taskInstIdList.clear();
}
String insId = flowTaskRuntimeService.executeTask(taskExecuteNormalVO);
taskInstIdList.add(insId);
return AjaxResult.success();
}
@ApiOperation("任务执行入口") @ApiOperation("任务执行入口")
@PostMapping("/execute") @PostMapping("/execute")
@Anonymous
public AjaxResult execute(@Valid @RequestBody TeTaskExecuteNormalVO taskExecuteNormalVO) { public AjaxResult execute(@Valid @RequestBody TeTaskExecuteNormalVO taskExecuteNormalVO) {
return AjaxResult.success(flowTaskRuntimeService.executeTask(taskExecuteNormalVO)); return AjaxResult.success(flowTaskRuntimeService.executeTask(taskExecuteNormalVO));
} }

View File

@ -92,7 +92,7 @@ spring:
# Minio配置 # Minio配置
minio: minio:
url: http://minio:9000 url: http://mini:9000
accessKey: AKICMVR accessKey: AKICMVR
secretKey: wJalrXUtnFEMI secretKey: wJalrXUtnFEMI
bucketName: cmvr-iot bucketName: cmvr-iot
@ -118,8 +118,8 @@ api:
# ActionEnum 的枚举作为 key # ActionEnum 的枚举作为 key
agents: agents:
INTENT_RECOGNITION: # 意图识别 INTENT_RECOGNITION: # 意图识别
app-id: d5dn5ibp9adhq1b34lig app-id: d3c98sp0gdon0fcf6l00
app-key: d6j5bcellh49on5tasvg app-key: d3c98vp0gdon0fcf6n20
TI_TOUCH_COORDINATES: # 获取触控二维坐标 TI_TOUCH_COORDINATES: # 获取触控二维坐标
app-id: d1ebtabnjkflk4gmhikg app-id: d1ebtabnjkflk4gmhikg
app-key: d5thge2cktmipk78h82g app-key: d5thge2cktmipk78h82g

View File

@ -3,7 +3,6 @@ package com.cmvr.edge.client.service.impl;
import cmvr.api.HlcCommand; import cmvr.api.HlcCommand;
import cmvr.api.HlcServiceGrpc; import cmvr.api.HlcServiceGrpc;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.edge.client.manage.GrpcServiceManager; import com.cmvr.edge.client.manage.GrpcServiceManager;
import com.cmvr.edge.client.model.hlc.EdgeTouchVO; import com.cmvr.edge.client.model.hlc.EdgeTouchVO;
import com.cmvr.edge.client.service.EdgeHlcService; import com.cmvr.edge.client.service.EdgeHlcService;
@ -86,9 +85,6 @@ public class EdgeHlcServiceImpl implements EdgeHlcService {
@Override @Override
public String touch(EdgeTouchVO edgeTouchVO) { public String touch(EdgeTouchVO edgeTouchVO) {
if (edgeTouchVO.getX() == edgeTouchVO.getY() && edgeTouchVO.getX() == 0) {
throw new GlobalException("触控坐标不能为0");
}
HlcServiceGrpc.HlcServiceBlockingStub stub = grpcServiceManager.getGrpcClient(edgeTouchVO.getTerminalId(), HlcServiceGrpc.HlcServiceBlockingStub.class); HlcServiceGrpc.HlcServiceBlockingStub stub = grpcServiceManager.getGrpcClient(edgeTouchVO.getTerminalId(), HlcServiceGrpc.HlcServiceBlockingStub.class);
HlcCommand.Touch.Request request = HlcCommand.Touch.Request.newBuilder() HlcCommand.Touch.Request request = HlcCommand.Touch.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeTouchVO.getDeviceId())) .setHeader(EdgeCommonUtil.buildRequest(edgeTouchVO.getDeviceId()))

View File

@ -3,5 +3,5 @@ package com.cmvr.llm.service;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
public interface LLMAiAgentPlatformService { public interface LLMAiAgentPlatformService {
JSONObject query(String action, String text, String apiKey, Boolean invokeTts, JSONObject config); JSONObject query(String action, String text, String apiKey);
} }

View File

@ -90,9 +90,7 @@ public class WorkflowInvokeServiceCopy {
appKey, // apiKey (header用) appKey, // apiKey (header用)
appId, // apiId (body的AppKey) appId, // apiId (body的AppKey)
"user_123", // userId "user_123", // userId
body, body
false,
null
); );
System.out.println( result); System.out.println( result);
System.out.println("总耗时:" + (System.currentTimeMillis() - startTime)); System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));

View File

@ -14,7 +14,7 @@ import java.util.Map;
public class LLMAiAgentPlatformServiceImpl implements LLMAiAgentPlatformService { public class LLMAiAgentPlatformServiceImpl implements LLMAiAgentPlatformService {
private final LlmChatService llmChatService; private final LlmChatService llmChatService;
@Override @Override
public JSONObject query(String action, String text, String apiKey, Boolean invokeTts, JSONObject config) { public JSONObject query(String action, String text, String apiKey) {
Map<String, Object> body = new HashMap<>(); Map<String, Object> body = new HashMap<>();
body.put("Query", text); body.put("Query", text);
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@ -24,9 +24,7 @@ public class LLMAiAgentPlatformServiceImpl implements LLMAiAgentPlatformService
apiKey, // apiKey (header用) apiKey, // apiKey (header用)
apiKey, // apiId (body的AppKey) apiKey, // apiId (body的AppKey)
"user_123", // userId "user_123", // userId
body, body
invokeTts,
config
); );
System.out.println( result); System.out.println( result);
System.out.println("总耗时:" + (System.currentTimeMillis() - startTime)); System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));

View File

@ -36,9 +36,7 @@ public class LLMIntentRecognitionServiceImpl implements LLMIntentRecognitionServ
acg.getAppKey(), // apiKey (header用) acg.getAppKey(), // apiKey (header用)
acg.getAppId(), // apiId (body的AppKey) acg.getAppId(), // apiId (body的AppKey)
"user_123", // userId "user_123", // userId
body, body
false,
null
); );
System.out.println( result); System.out.println( result);
System.out.println("总耗时:" + (System.currentTimeMillis() - startTime)); System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));

View File

@ -1,11 +1,7 @@
package com.cmvr.llm.util; package com.cmvr.llm.util;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.llm.service.LLMAiTtsService;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*; import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -14,18 +10,17 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
* LLM聊天服务 - 严格保持原接口参数 * LLM聊天服务 - 严格保持原接口参数
*/ */
@Slf4j
@Service @Service
public class LlmChatService { public class LlmChatService {
@Autowired
private LLMAiTtsService llmAiTtsService;
private static final String DEFAULT_BASE_URL = "https://aiagentplatform.cmft.com/api/proxy/api/v1"; private static final String DEFAULT_BASE_URL = "https://aiagentplatform.cmft.com/api/proxy/api/v1";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@ -53,7 +48,7 @@ public class LlmChatService {
* @param body 请求体必须包含Query字段可选Name等 * @param body 请求体必须包含Query字段可选Name等
* @return 完整AI回复字符串 * @return 完整AI回复字符串
*/ */
public String chat(String apiKey, String apiId, String userId, Map<String, Object> body, boolean playTts, JSONObject ttsConfig) throws IOException { public String chat(String apiKey, String apiId, String userId, Map<String, Object> body) throws IOException {
String cacheKey = apiKey + "|" + apiId + "|" + userId; String cacheKey = apiKey + "|" + apiId + "|" + userId;
// 获取或创建会话严格保持原createConversation逻辑 // 获取或创建会话严格保持原createConversation逻辑
@ -81,7 +76,7 @@ public class LlmChatService {
} }
// 执行流式请求并同步返回完整结果 // 执行流式请求并同步返回完整结果
return executeSseRequestSync(apiKey, requestBody, playTts, ttsConfig); return executeSseRequestSync(apiKey, requestBody);
} }
/** /**
@ -127,13 +122,10 @@ public class LlmChatService {
} }
} }
// 句子结束符号
static final String SENTENCE_END = "?!。?!\n";
/** /**
* 执行SSE请求 - 完全保持原executeSseRequest逻辑改为同步返回 * 执行SSE请求 - 完全保持原executeSseRequest逻辑改为同步返回
*/ */
private String executeSseRequestSync(String apiKey, Map<String, Object> requestBody, boolean playTts, JSONObject ttsConfig) throws IOException { private String executeSseRequestSync(String apiKey, Map<String, Object> requestBody) throws IOException {
String jsonBody = objectMapper.writeValueAsString(requestBody); String jsonBody = objectMapper.writeValueAsString(requestBody);
Request request = new Request.Builder() Request request = new Request.Builder()
@ -145,49 +137,18 @@ public class LlmChatService {
.build(); .build();
StringBuilder fullContent = new StringBuilder(); StringBuilder fullContent = new StringBuilder();
CountDownLatch mainLatch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Throwable> errorRef = new AtomicReference<>(); AtomicReference<Throwable> errorRef = new AtomicReference<>();
AtomicReference<String> finalMessageIdRef = new AtomicReference<>(""); AtomicReference<String> finalMessageIdRef = new AtomicReference<>("");
// ====================== 核心控制 ======================
// 接收缓存永远不阻塞
StringBuilder receiveBuffer = new StringBuilder();
// 单线程串行执行保证上一句处理完才处理下一句
ExecutorService sentenceExecutor = Executors.newSingleThreadExecutor();
// 标记流是否已经结束
AtomicBoolean streamFinished = new AtomicBoolean(false);
final AtomicBoolean interrupted = new AtomicBoolean(false); // 中断标记
Call call = httpClient.newCall(request); Call call = httpClient.newCall(request);
Runnable interruptTask = () -> {
interrupted.set(true);
call.cancel(); // 真正关闭SSE连接 关键
streamFinished.set(true);
sentenceExecutor.shutdownNow();
mainLatch.countDown();
};
// 使用异步调用但阻塞等待结果 // 使用异步调用但阻塞等待结果
call.enqueue(new Callback() { call.enqueue(new Callback() {
/**
* 等待所有句子处理完再结束
*/
private void shutdownAndWait1() {
try {
sentenceExecutor.shutdown();
sentenceExecutor.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException ignored) {}
mainLatch.countDown();
}
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
errorRef.set(e); errorRef.set(e);
streamFinished.set(true); latch.countDown();
shutdownAndWait1();
} }
@Override @Override
@ -195,8 +156,7 @@ public class LlmChatService {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息"; String errorBody = response.body() != null ? response.body().string() : "无错误信息";
errorRef.set(new IOException("SSE请求失败: " + response.code() + " - " + errorBody)); errorRef.set(new IOException("SSE请求失败: " + response.code() + " - " + errorBody));
streamFinished.set(true); latch.countDown();
shutdownAndWait1();
return; return;
} }
@ -208,7 +168,7 @@ public class LlmChatService {
new InputStreamReader(response.body().byteStream(), StandardCharsets.UTF_8))) { new InputStreamReader(response.body().byteStream(), StandardCharsets.UTF_8))) {
String line; String line;
while (!interrupted.get() && (line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (line.isEmpty()) { if (line.isEmpty()) {
currentEvent = ""; currentEvent = "";
continue; continue;
@ -230,9 +190,8 @@ public class LlmChatService {
if ("[DONE]".equals(data)) { if ("[DONE]".equals(data)) {
fullContent.append(localContent); fullContent.append(localContent);
finalMessageIdRef.set(finalMessageId); finalMessageIdRef.set(finalMessageId);
streamFinished.set(true); latch.countDown();
processBufferIfNeed(); return;
break;
} }
try { try {
@ -249,18 +208,15 @@ public class LlmChatService {
String answer = extractString(jsonData, "answer", "content", "chunk", "text"); String answer = extractString(jsonData, "answer", "content", "chunk", "text");
if (!answer.isEmpty()) { if (!answer.isEmpty()) {
localContent.append(answer); localContent.append(answer);
receiveBuffer.append(answer);
processBufferIfNeed();
} }
} else if ("message_start".equals(event)) { } else if ("message_start".equals(event)) {
log.info("消息开始ID: " + finalMessageId); System.out.println("消息开始ID: " + finalMessageId);
} else if ("message_output_start".equals(event)) { } else if ("message_output_start".equals(event)) {
log.info("输出开始"); System.out.println("输出开始");
} else if ("message_end".equals(event) || "end".equals(event)) { } else if ("message_end".equals(event) || "end".equals(event)) {
fullContent.append(localContent); fullContent.append(localContent);
finalMessageIdRef.set(finalMessageId); finalMessageIdRef.set(finalMessageId);
streamFinished.set(true); latch.countDown();
processBufferIfNeed();
return; return;
} }
@ -273,77 +229,22 @@ public class LlmChatService {
// 流正常结束 // 流正常结束
fullContent.append(localContent); fullContent.append(localContent);
finalMessageIdRef.set(finalMessageId); finalMessageIdRef.set(finalMessageId);
latch.countDown();
} catch (IOException e) { } catch (IOException e) {
errorRef.set(e); errorRef.set(e);
streamFinished.set(true); latch.countDown();
} finally {
processBufferIfNeed();
shutdownAndWait1();
} }
} }
/**
* 核心提取完整句子 + 异步串行处理
*/
private void processBufferIfNeed() {
if (interrupted.get()) return;
if (!true) return;
if (!playTts) return;
sentenceExecutor.submit(() -> {
while (true) {
String buffer = receiveBuffer.toString();
// 寻找最后一个句子结束符
int lastSplit = -1;
for (int i = 0; i < buffer.length(); i++) {
if (SENTENCE_END.contains(String.valueOf(buffer.charAt(i)))) {
lastSplit = i;
}
}
// 有完整句子 流已结束
if (lastSplit >= 0 || streamFinished.get()) {
String sentence;
if (lastSplit >= 0) {
// 截取完整句子
sentence = buffer.substring(0, lastSplit + 1).trim();
// 保留剩余内容
String remain = buffer.substring(lastSplit + 1).trim();
receiveBuffer.setLength(0);
receiveBuffer.append(remain);
} else {
// 流结束直接处理剩余
sentence = buffer.trim();
receiveBuffer.setLength(0);
}
if (!sentence.isEmpty()) {
if (interrupted.get()) return;
yourAsyncMethod(sentence, ttsConfig);
}
// 处理完继续循环看是否还有新句子
if (streamFinished.get() && receiveBuffer.length() == 0) {
break;
}
} else {
// 无完整句子退出
break;
}
}
});
}
}); });
// 等待流式传输完成 // 等待流式传输完成
try { try {
boolean completed = mainLatch.await(120, TimeUnit.SECONDS); boolean completed = latch.await(120, TimeUnit.SECONDS);
if (!completed) { if (!completed) {
throw new IOException("请求超时"); throw new IOException("请求超时");
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
interruptTask.run(); // 被中断时立即停止SSE
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new IOException("请求被中断", e); throw new IOException("请求被中断", e);
} }
@ -359,15 +260,6 @@ public class LlmChatService {
return fullContent.toString(); return fullContent.toString();
} }
private void yourAsyncMethod(String sentence, JSONObject ttsConfig) {
try {
log.info("正在处理句子: " + sentence);
llmAiTtsService.play(ttsConfig.getString("url"), sentence, ttsConfig.getString("voice"), ttsConfig.getString("speed"), ttsConfig.getString("volume"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** /**
* 完全保持原extractString方法 * 完全保持原extractString方法
*/ */

View File

@ -115,7 +115,7 @@ public class SecurityConfig
// 静态资源可匿名访问 // 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/system/file/upload","/evaluation/callback","/flow/**","/flowise/**","/kws/**","/ws/**","/api/grpc/**","/show/**", .antMatchers("/system/file/upload","/evaluation/callback","/flow/**","/flowise/**","/kws/**","/ws/**","/api/grpc/**","/show/**",
"/node-red/**","/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/ti/**", "/flow/execute").permitAll() "/node-red/**","/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/ti/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证 // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated(); .anyRequest().authenticated();
}) })

View File

@ -24,17 +24,10 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
*/ */
@Override @Override
public void insertFill(MetaObject metaObject) { public void insertFill(MetaObject metaObject) {
// 获取用户名异常时给默认值 anonymous
String username;
try {
username = SecurityUtils.getUsername();
} catch (Exception e) {
username = "anonymous"; // 匿名接口默认值
}
// 起始版本 3.3.0(推荐使用) // 起始版本 3.3.0(推荐使用)
this.setFieldValByName(CREATE_BY, username, metaObject); this.setFieldValByName(CREATE_BY, SecurityUtils.getUsername(), metaObject);
this.setFieldValByName(CREATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject); this.setFieldValByName(CREATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
this.setFieldValByName(UPDATE_BY, username, metaObject); this.setFieldValByName(UPDATE_BY, SecurityUtils.getUsername(), metaObject);
this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject); this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
this.setFieldValByName(DELETED, "0", metaObject); this.setFieldValByName(DELETED, "0", metaObject);
// this.setFieldValByName(STATUS, "1", metaObject); // this.setFieldValByName(STATUS, "1", metaObject);
@ -48,14 +41,7 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
*/ */
@Override @Override
public void updateFill(MetaObject metaObject) { public void updateFill(MetaObject metaObject) {
// 获取用户名异常时给默认值 anonymous this.setFieldValByName(UPDATE_BY, SecurityUtils.getUsername(), metaObject);
String username;
try {
username = SecurityUtils.getUsername();
} catch (Exception e) {
username = "anonymous"; // 匿名接口默认值
}
this.setFieldValByName(UPDATE_BY, username, metaObject);
this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject); this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
} }

View File

@ -54,10 +54,7 @@ public class FlowControlService {
if (ctx.isStopped()) { if (ctx.isStopped()) {
throw new GlobalException("任务已终止,无需重复操作"); throw new GlobalException("任务已终止,无需重复操作");
} }
// 异步终止
executor.execute(() -> {
edgeSystemService.stopAll(ctx.getTerminalId()); edgeSystemService.stopAll(ctx.getTerminalId());
});
// 统一记录日志 + 设置上下文状态 + 数据库状态 // 统一记录日志 + 设置上下文状态 + 数据库状态
taskInstHolder.syncStatus(instId, TaskStatusEnum.STOPPED); taskInstHolder.syncStatus(instId, TaskStatusEnum.STOPPED);
@ -81,10 +78,7 @@ public class FlowControlService {
if (ctx.isPaused()) { if (ctx.isPaused()) {
throw new GlobalException("任务已处于暂停状态"); throw new GlobalException("任务已处于暂停状态");
} }
// 异步停止终端
executor.execute(() -> {
edgeSystemService.stopAll(ctx.getTerminalId()); edgeSystemService.stopAll(ctx.getTerminalId());
});
ctx.setPaused(true); ctx.setPaused(true);
ctx.setTerminalStatus(TerminalStatusEnum.RUNNING); ctx.setTerminalStatus(TerminalStatusEnum.RUNNING);
ctx.setStatus(TaskStatusEnum.PAUSED); ctx.setStatus(TaskStatusEnum.PAUSED);
@ -150,7 +144,6 @@ public class FlowControlService {
TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage(); TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage();
BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage); BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage);
resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留中断时的循环次数 resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留中断时的循环次数
resumeMessage.setLoopArray(pendingNode.getLoopArray()); // 保留中断时的循环次数
resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留中断时的路径 resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留中断时的路径
flowItemExecutor.executeNode( flowItemExecutor.executeNode(
@ -176,7 +169,6 @@ public class FlowControlService {
TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage(); TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage();
BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage); BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage);
resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留循环次数 resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留循环次数
resumeMessage.setLoopArray(pendingNode.getLoopArray()); // 保留循环次数
resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留路径 resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留路径
flowItemExecutor.executeNode( flowItemExecutor.executeNode(

View File

@ -47,7 +47,7 @@ public class FlowEndNodeHandler implements FlowNodeTypeHandler {
Thread.currentThread().setName("evaluation-thread-" + Thread.currentThread().getId()); Thread.currentThread().setName("evaluation-thread-" + Thread.currentThread().getId());
try { try {
log.info("异步评估任务开始执行threadName={}, instId={}, itemId={}", Thread.currentThread().getName(), instId, itemId); log.info("异步评估任务开始执行threadName={}, instId={}, itemId={}", Thread.currentThread().getName(), instId, itemId);
// exAeEvaluationService.executeEvaluation(jsonObject); exAeEvaluationService.executeEvaluation(jsonObject);
log.info("异步评估任务执行结束threadName={}, instId={}, itemId={}", Thread.currentThread().getName(), instId, itemId); log.info("异步评估任务执行结束threadName={}, instId={}, itemId={}", Thread.currentThread().getName(), instId, itemId);
} catch (Exception e) { } catch (Exception e) {
log.error("评估任务执行异常threadName={}, instId={}, itemId={}, 错误={}", Thread.currentThread().getName(), instId, itemId, e.getMessage(), e); log.error("评估任务执行异常threadName={}, instId={}, itemId={}, 错误={}", Thread.currentThread().getName(), instId, itemId, e.getMessage(), e);

View File

@ -8,7 +8,6 @@ import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List; import java.util.List;
@Slf4j @Slf4j
@ -19,17 +18,17 @@ public class FlowGetCurrentObjNodeHandler implements FlowNodeTypeHandler {
@Override @Override
public TaskNodeExecuteResult handle(TaskNodeExecuteMessage message) { public TaskNodeExecuteResult handle(TaskNodeExecuteMessage message) {
try { try {
// JSONObject inputParams = message.getInputParams(); JSONObject inputParams = message.getInputParams();
// JSONArray jsonArray = inputParams.getJSONArray("array"); JSONArray jsonArray = inputParams.getJSONArray("array");
//
// List<Integer> iterations = message.getIterations(); List<Integer> iterations = message.getIterations();
// int last = CollUtil.getLast(iterations) - 1; int last = CollUtil.getLast(iterations) - 1;
int index = message.getLoopNum() - 1;
JSONObject output = new JSONObject(); JSONObject output = new JSONObject();
output.put("index", index); output.put("index", last);
JSONArray objects = (JSONArray)message.getLoopArray();
if (CollUtil.isNotEmpty(objects) && index >= 0) { if (CollUtil.isNotEmpty(jsonArray) && last >= 0) {
output.put("object", objects.get(index)); output.put("object", jsonArray.get(last));
} }
return TaskNodeExecuteResult.success(output); return TaskNodeExecuteResult.success(output);

View File

@ -20,30 +20,22 @@ public class FlowHttpNodeHandler implements FlowNodeTypeHandler {
public TaskNodeExecuteResult handle(TaskNodeExecuteMessage message) { public TaskNodeExecuteResult handle(TaskNodeExecuteMessage message) {
try { try {
JSONObject inputParams = message.getInputParams(); JSONObject inputParams = message.getInputParams();
JSONObject config = inputParams.getJSONObject("config");
JSONObject body = inputParams.getJSONObject("body");
JSONObject headers = inputParams.getJSONObject("headers");
// 1. 必填参数校验 // 1. 必填参数校验
String url = config.getString("url"); String url = inputParams.getString("url");
if (url == null || url.isEmpty()) { if (url == null || url.isEmpty()) {
return TaskNodeExecuteResult.failure("HTTP url is required"); return TaskNodeExecuteResult.failure("HTTP url is required");
} }
String method = config.getString("method"); String method = inputParams.getString("method");
if (method == null) { if (method == null) {
method = "POST"; method = "POST";
} }
int timeout = config.getIntValue("timeout", 100000); int timeout = inputParams.getIntValue("timeout", 100000);
JSONObject headers = inputParams.getJSONObject("headers");
Object bodyCfg = inputParams.get("body");
Object bodyCfg = null;
if (!"json".equals(body.getString("bodyType"))) {
JSONObject formData = body.getJSONObject("formData");
bodyCfg = formData;
} else {
bodyCfg = body.get("json");
}
// 2. 构造 HTTP 请求 // 2. 构造 HTTP 请求
HttpRequest request = HttpRequest.of(url) HttpRequest request = HttpRequest.of(url)
.method(Method.valueOf(method.toUpperCase())) .method(Method.valueOf(method.toUpperCase()))

View File

@ -37,7 +37,6 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler {
String nodeId = message.getNodeId(); String nodeId = message.getNodeId();
FlowNodeWrapper nodeWrapper = graph.getNode(nodeId); FlowNodeWrapper nodeWrapper = graph.getNode(nodeId);
int loopCount = inputParams.getIntValue("loopNum"); // 获取循环次数 int loopCount = inputParams.getIntValue("loopNum"); // 获取循环次数
Object loopArray = inputParams.get("loopArray"); // 获取循环次数
// 如果没有设置循环次数或者循环次数为 0则跳过处理 // 如果没有设置循环次数或者循环次数为 0则跳过处理
if (loopCount <= 0) { if (loopCount <= 0) {
@ -66,9 +65,8 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler {
// 克隆 message并明确设置 loopNum // 克隆 message并明确设置 loopNum
TaskNodeExecuteMessage subMessage = new TaskNodeExecuteMessage(); TaskNodeExecuteMessage subMessage = new TaskNodeExecuteMessage();
BeanUtil.copyProperties(message, subMessage); BeanUtil.copyProperties(message, subMessage);
subMessage.setLoopNum(i); // 当前 loop i // subMessage.setLoopNum(i); // 当前 loop i
subMessage.setIterations(newIterations); // 完整路径 subMessage.setIterations(newIterations); // 完整路径
subMessage.setLoopArray(loopArray);
flowItemExecutor.executeSubGraph(subGraph, subMessage, latch::countDown, newIterations); flowItemExecutor.executeSubGraph(subGraph, subMessage, latch::countDown, newIterations);

View File

@ -50,7 +50,7 @@ public class FlowItemExecutor {
FlowGraph graph = FlowModelBuilder.buildExecutableGraph(item.getFlowData()); FlowGraph graph = FlowModelBuilder.buildExecutableGraph(item.getFlowData());
String startNodeId = graph.findStartNodeId(); String startNodeId = graph.findStartNodeId();
List<FlowParamDef> startInputDefs = graph.getStartInputParamsDefs(); List<FlowParamDef> startInputDefs = graph.getStartInputParamsDefs();
startInputDefs.clear();
// 执行 start 节点同步执行 // 执行 start 节点同步执行
NodeExecutor executor = node -> NodeExecutor executor = node ->
executeNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, new ArrayList<>()); executeNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, new ArrayList<>());
@ -73,7 +73,7 @@ public class FlowItemExecutor {
List<FlowParamDef> startInputDefs = subGraph.getStartInputParamsDefs(); List<FlowParamDef> startInputDefs = subGraph.getStartInputParamsDefs();
// 子图的 start 节点 // 子图的 start 节点
FlowNodeWrapper subStartNode = subGraph.getInDegreeZeroNode(); FlowNodeWrapper subStartNode = subGraph.findSubStartNodeId();
String subStartNodeId = subStartNode.getNodeId(); String subStartNodeId = subStartNode.getNodeId();
NodeExecutor executor = node -> executeNode( NodeExecutor executor = node -> executeNode(
@ -84,7 +84,7 @@ public class FlowItemExecutor {
// 执行子图起始节点 // 执行子图起始节点
executeNode(subGraph, subStartNode, subStartNodeId, startInputDefs, executeNode(subGraph, subStartNode, subStartNodeId, startInputDefs,
rootMessage, onFinished, iterations); rootMessage, onFinished, iterations);
log.info("当前执行-子图");
// 启动子图调度 // 启动子图调度
flowTaskScheduler.subStart(subGraph, taskInstHolder.getContext(rootMessage.getInstId()), flowTaskScheduler.subStart(subGraph, taskInstHolder.getContext(rootMessage.getInstId()),
executor, onFinished, iterations); executor, onFinished, iterations);
@ -131,7 +131,7 @@ public class FlowItemExecutor {
} }
// END 节点 or 子图结束 // END 节点 or 子图结束
if (node.getNodeType() == NodeTypeEnum.END || node.getNodeType() == NodeTypeEnum.SUB_END || graph.isSubGraphEndNode(nodeId)) { if (node.getNodeType() == NodeTypeEnum.END || node.getNodeType() == NodeTypeEnum.SUB_END) {
try { try {
log.info("{} 节点执行,释放线程", node.getNodeType()); log.info("{} 节点执行,释放线程", node.getNodeType());
onFinished.run(); onFinished.run();
@ -172,8 +172,6 @@ public class FlowItemExecutor {
// loopNum 不在这里计算而是由 FlowLoopNodeHandler / resume 显式写入 // loopNum 不在这里计算而是由 FlowLoopNodeHandler / resume 显式写入
// 如果 rootMessage 里已经带了 loopNum就沿用它 // 如果 rootMessage 里已经带了 loopNum就沿用它
message.setLoopNum(rootMessage.getLoopNum()); message.setLoopNum(rootMessage.getLoopNum());
message.setLoopArray(rootMessage.getLoopArray());
return message; return message;
} }

View File

@ -42,8 +42,6 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService {
private final TaskInstHolder taskInstHolder; private final TaskInstHolder taskInstHolder;
private final FlowTaskAsyncDispatcher flowTaskAsyncDispatcher; private final FlowTaskAsyncDispatcher flowTaskAsyncDispatcher;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO) { public String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO) {
return executeTaskInternal( return executeTaskInternal(

View File

@ -18,7 +18,6 @@ public interface FlowTaskRuntimeService {
*/ */
String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO); String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO);
/** /**
* 执行试运行任务TRIAL 模式 * 执行试运行任务TRIAL 模式
* *

View File

@ -58,7 +58,7 @@ public class FlowTaskScheduler {
Runnable onFinished, Runnable onFinished,
List<Integer> iterations) { List<Integer> iterations) {
String instId = context.getInstId(); String instId = context.getInstId();
FlowNodeWrapper startNode = graph.getInDegreeZeroNode(); FlowNodeWrapper startNode = graph.findSubStartNodeId();
List<String> nextNodes = graph.getNextNodes(startNode.getNodeId()); List<String> nextNodes = graph.getNextNodes(startNode.getNodeId());
if (nextNodes.isEmpty()) { if (nextNodes.isEmpty()) {

View File

@ -1,7 +1,6 @@
package com.cmvr.test.flow.runtime.engine.support; package com.cmvr.test.flow.runtime.engine.support;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
@ -31,65 +30,11 @@ public class FlowNodeParamPreparer {
FlowNodeWrapper node, FlowNodeWrapper node,
TaskNodeExecuteMessage rootMessage) { TaskNodeExecuteMessage rootMessage) {
TaskContext ctx = taskInstHolder.getContext(rootMessage.getInstId());
JSONObject input = getInputParams(graph, node.getNodeParams(), rootMessage);
// 3 START 节点 runParams 覆盖默认参数
if (node.getNodeType() == NodeTypeEnum.START) {
JSONObject merged = new JSONObject(input);
JSONObject runParams = ctx.getRunParams();
for (String key : runParams.keySet()) {
// runParams 里有值就覆盖掉定义里的值
merged.put(key, runParams.get(key));
}
return merged;
}
// 4 LOOP 节点动态计算 loopCount
if (node.getNodeType() == NodeTypeEnum.LOOP) {
Object loopNumVal = input.get("loopNum");
input.put("loopArray", loopNumVal);
int loopCount = 0;
// 根据迭代路径找到当前层的集合对象
// Object target = resolveLoopTarget(loopNumVal, rootMessage.getIterations(), 0);
Object target = loopNumVal;
if (target instanceof JSONArray) {
loopCount = ((JSONArray) target).size();
} else if (target instanceof Collection) {
loopCount = ((Collection<?>) target).size();
} else if (target != null && target.getClass().isArray()) {
loopCount = Array.getLength(target);
} else if (target instanceof Number) {
loopCount = ((Number) target).intValue();
} else if (target != null) {
try {
String string = target.toString();
if (string.startsWith("[")) {
JSONArray array = JSON.parseArray(string);
loopCount = array.size();
}else {
loopCount = Integer.parseInt(string);
}
} catch (NumberFormatException e) {
throw new GlobalException("loopNum 参数格式错误: " + loopNumVal);
}
}
// 写回 inputParams LoopHandler 使用
input.put("loopNum", loopCount);
}
return input;
}
private JSONObject getInputParams(FlowGraph graph, List<FlowParamDef> paramDefList, TaskNodeExecuteMessage rootMessage) {
JSONObject input = new JSONObject(); JSONObject input = new JSONObject();
TaskContext ctx = taskInstHolder.getContext(rootMessage.getInstId()); TaskContext ctx = taskInstHolder.getContext(rootMessage.getInstId());
// 遍历当前节点定义的参数 // 遍历当前节点定义的参数
for (FlowParamDef param : paramDefList) { for (FlowParamDef param : node.getNodeParams()) {
if (param.getScope() != ParamScope.NODE) continue; if (param.getScope() != ParamScope.NODE) continue;
// 1 静态 input 参数 // 1 静态 input 参数
@ -123,11 +68,53 @@ public class FlowNodeParamPreparer {
Object val = findNestedValue(source, path); Object val = findNestedValue(source, path);
input.put(param.getName(), val); input.put(param.getName(), val);
} }
if (!CollectionUtil.isEmpty(param.getChildren())) { }
// 添加子参数
input.put(param.getName(), getInputParams(graph, param.getChildren(), rootMessage)); // 3 START 节点 runParams 覆盖默认参数
if (node.getNodeType() == NodeTypeEnum.START) {
JSONObject merged = new JSONObject(input);
JSONObject runParams = ctx.getRunParams();
for (String key : runParams.keySet()) {
// runParams 里有值就覆盖掉定义里的值
merged.put(key, runParams.get(key));
}
return merged;
}
// 4 LOOP 节点动态计算 loopCount
if (node.getNodeType() == NodeTypeEnum.LOOP) {
Object loopNumVal = input.get("loopNum");
int loopCount = 0;
// 根据迭代路径找到当前层的集合对象
Object target = resolveLoopTarget(loopNumVal, rootMessage.getIterations(), 0);
if (target instanceof JSONArray) {
loopCount = ((JSONArray) target).size();
} else if (target instanceof Collection) {
loopCount = ((Collection<?>) target).size();
} else if (target != null && target.getClass().isArray()) {
loopCount = Array.getLength(target);
} else if (target instanceof Number) {
loopCount = ((Number) target).intValue();
} else if (target != null) {
try {
String string = target.toString();
if (string.startsWith("[")) {
JSONArray array = JSON.parseArray(string);
loopCount = array.size();
}else {
loopCount = Integer.parseInt(string);
}
} catch (NumberFormatException e) {
throw new GlobalException("loopNum 参数格式错误: " + loopNumVal);
} }
} }
// 写回 inputParams LoopHandler 使用
input.put("loopNum", loopCount);
}
return input; return input;
} }

View File

@ -22,6 +22,5 @@ public class TaskNodeExecuteContext {
private TaskNodeExecuteMessage rootMessage; private TaskNodeExecuteMessage rootMessage;
private Runnable onFinished; private Runnable onFinished;
private int loopIteration; private int loopIteration;
private Object loopArray;
private List<Integer> iterations = new ArrayList<>(); private List<Integer> iterations = new ArrayList<>();
} }

View File

@ -55,8 +55,6 @@ public class TaskNodeExecuteMessage {
*/ */
private List<Integer> iterations = new ArrayList<>(); private List<Integer> iterations = new ArrayList<>();
private Object loopArray;
/** /**
* 节点名称 * 节点名称
*/ */

View File

@ -12,7 +12,6 @@ import org.springframework.stereotype.Service;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class LLMAiAgentPlatformOperateService implements LLMOperateService { public class LLMAiAgentPlatformOperateService implements LLMOperateService {
@ -27,11 +26,10 @@ public class LLMAiAgentPlatformOperateService implements LLMOperateService {
public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) { public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) {
ActionEnum action = message.getAction(); ActionEnum action = message.getAction();
JSONObject inputParams = message.getInputParams(); JSONObject inputParams = message.getInputParams();
JSONObject config = inputParams.getJSONObject("config"); String text = inputParams.getString("text");
String text = config.getString("text"); String apiKey = inputParams.getString("apiKey");
String apiKey = config.getString("apiKey");
Boolean invokeTts = inputParams.getBoolean("invokeTts"); JSONObject output = llmAiAgentPlatformService.query(action.name(), text, apiKey);
JSONObject output =llmAiAgentPlatformService.query(action.name(), text, apiKey, invokeTts, inputParams.getJSONObject("tts"));
return TaskNodeExecuteResult.success(output); return TaskNodeExecuteResult.success(output);
} }

View File

@ -12,8 +12,6 @@ import com.cmvr.edge.client.service.EdgeCameraService;
import com.cmvr.edge.client.service.EdgeHlcService; import com.cmvr.edge.client.service.EdgeHlcService;
import com.cmvr.edge.client.service.EdgeMicrophoneService; import com.cmvr.edge.client.service.EdgeMicrophoneService;
import com.cmvr.edge.client.service.EdgeSpeakerService; import com.cmvr.edge.client.service.EdgeSpeakerService;
import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.llm.service.LLMAiTtsService;
import com.cmvr.test.enums.ActionEnum; import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.model.vo.FlowActionRequestVO; import com.cmvr.test.model.vo.FlowActionRequestVO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -29,8 +27,6 @@ public class FlowActionExecutorService {
private final EdgeMicrophoneService edgeMicrophoneService; private final EdgeMicrophoneService edgeMicrophoneService;
private final EdgeSpeakerService edgeSpeakerService; private final EdgeSpeakerService edgeSpeakerService;
private final EdgeHlcService edgeHlcService; private final EdgeHlcService edgeHlcService;
private final LLMAiTtsService llmAiTtsService;
private final LLMAiAgentPlatformService llmAiAgentPlatformService;
public String actionExecute(FlowActionRequestVO req) { public String actionExecute(FlowActionRequestVO req) {
@ -119,8 +115,6 @@ public class FlowActionExecutorService {
return "LLM 意图识别 OK"; return "LLM 意图识别 OK";
case GENERATE_ADVANCED_AUDIO: case GENERATE_ADVANCED_AUDIO:
return "LLM 高级音频生成 OK"; return "LLM 高级音频生成 OK";
case AI_AGENT_PLATFORM:
return llmAiAgentPlatformService.query(action.getAction(), req.getPayload().getJSONObject("config").getString("text"), req.getPayload().getJSONObject("config").getString("apiKey"), req.getPayload().getBoolean("invokeTts"), req.getPayload().getJSONObject("tts")).toString();
default: default:
throw new UnsupportedOperationException("未实现的 LLM Action: " + action); throw new UnsupportedOperationException("未实现的 LLM Action: " + action);
} }

View File

@ -40,7 +40,7 @@ public class TiVehicleFunctionServiceImpl extends ServiceImpl<TiVehicleFunctionM
@Override @Override
public JSONObject selectTiVehicleFunctionDetailById(Long id) { public JSONObject selectTiVehicleFunctionDetailById(Long id) {
JSONObject jsonObject = JSONObject.from(this.getById(id)); JSONObject jsonObject = JSONObject.from(this.getById(id));
jsonObject.put("funcName", sysDictDataService.selectDictLabel("ti_function_config", jsonObject.getString("funcKey"))); jsonObject.put("funcName", id== 26 ? "首页" : "设置");
return jsonObject; return jsonObject;
} }