Commit 685caf9a authored by wangning's avatar wangning

Initial commit

parents
Pipeline #464 failed with stages
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pro.spss.agent</groupId>
<artifactId>ciecc-agent</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>pro.spss.server</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.2</version>
</dependency>
<!-- mybatis-generator -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.2.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
<scope>compile</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.example</groupId>-->
<!-- <artifactId>smart-analyzer</artifactId>-->
<!-- <version>1.0.0</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package pro.spss.server.agent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableFeignClients
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package pro.spss.server.agent.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.domain.response.Result;
import pro.spss.server.agent.service.chatService.BaseChatService;
import pro.spss.server.agent.service.handler.IntentHandlerRegistry;
import pro.spss.server.common.interceptor.Authenticator;
import java.io.IOException;
@RestController
@Slf4j
@RequestMapping("/api/v1/da/agent")
@Tag(name = "智能调用")
public class ChatController {
@Autowired
private BaseChatService baseChat;
@Autowired
private IntentHandlerRegistry intentHandlerRegistry;
@PostMapping("/analyse")
@Operation(summary = "智能分析")
public Result ask(@RequestBody UserChatMessage userChatMessage) throws IOException {
log.info("用户提交:{}", userChatMessage);
String userId = Authenticator.currentUserId();
String token = Authenticator.currentToken();
userChatMessage.setToken(token);
userChatMessage.setUserId(userId);
log.info("用户提交:{}", userChatMessage);
baseChat.baseChat(userChatMessage, false);
return Result.success("提交成功");
}
@PostMapping("/clear")
@Operation(summary = "清除历史会话记录")
public Result clearHistory() {
String userId = String.valueOf(Authenticator.currentUserId());
return baseChat.clearHistory(userId);
}
@GetMapping("/tools")
@Operation(summary = "获取所有工具信息清单(意图处理器)")
public Result listTools() {
return Result.success(intentHandlerRegistry.getAllToolInfos());
}
}
package pro.spss.server.agent.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import pro.spss.server.agent.service.sseService.SseService;
/**
* @author:wn
* @Desc
* @create: 2025-03-25-09:45
**/
@RestController
@RequestMapping("/api/v1/da/agent")
public class SseController {
@Autowired
private SseService sseService;
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter getConnection(){
return sseService.connect();
}
}
package pro.spss.server.agent.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.domain.response.Result;
import pro.spss.server.agent.service.chatService.BaseChatService;
import pro.spss.server.common.interceptor.Authenticator;
import java.io.IOException;
@RestController
@Slf4j
@RequestMapping("/api/v1/da/smart-calls")
@Tag(name = "智能调用")
public class TestContoller {
@Autowired
private BaseChatService baseChat;
@PostMapping("/analyse")
@Operation(summary = "智能分析")
public Result chat(@RequestBody UserChatMessage userChatMessage) throws IOException {
String userId = Authenticator.currentUserId();
String token = Authenticator.currentToken();
userChatMessage.setToken(token);
userChatMessage.setUserId(userId);
log.info("用户提交:{}", userChatMessage);
String userIntent = baseChat.testIntent(userChatMessage);
return Result.success(userIntent);
}
@PostMapping("/clear")
@Operation(summary = "清除历史会话记录")
public Result clearHistory() {
String userId = String.valueOf(Authenticator.currentUserId());
return baseChat.clearHistory(userId);
}
}
package pro.spss.server.agent.domain.DTO;
import com.alibaba.fastjson2.JSONArray;
import lombok.Builder;
import lombok.Data;
import pro.spss.server.agent.domain.enums.CreateWayEnum;
/**
* @author:wn
* @Desc
* @create: 2025-03-13-09:17
**/
@Data
@Builder
public class TaskDTO {
private String algoId;
private String algoName;
private String dataId;
private String dataVersionId;
private CreateWayEnum createWay;
private JSONArray params;
private Integer taskPriority;
}
package pro.spss.server.agent.domain.constant;
import pro.spss.server.agent.utils.TxtFileReader;
/**
* @author:wn
* @Desc
* @create: 2025-03-07-11:43
**/
public class AlgoConfig {
public static final String ALGO_NAME_LIST = TxtFileReader.readFile("promptwords/algo_list.txt");
public static final String SMART_CHAT_PROMPT = TxtFileReader.readFile("promptwords/smart_chat.txt");
// public static final String CHAT_ALGO_PROMPT = TxtFileReader.readFile("promptwords/0_0.txt");
public static final String INTENT_PROMPT = TxtFileReader.readFile("promptwords/0_0_0.txt");
public static final String RECOGNIZE_ALGO_NAME = TxtFileReader.readFile("promptwords/0_1.txt");
public static final String ALGO_1 = TxtFileReader.readFile("promptwords/1.txt");
public static final String ALGO_2 = TxtFileReader.readFile("promptwords/2.txt");
public static final String ALGO_3 = TxtFileReader.readFile("promptwords/3.txt");
public static final String ALGO_4 = TxtFileReader.readFile("promptwords/4.txt");
public static final String ALGO_5 = TxtFileReader.readFile("promptwords/5.txt");
public static final String ALGO_6 = TxtFileReader.readFile("promptwords/6.txt");
public static final String ALGO_7 = TxtFileReader.readFile("promptwords/7.txt");
public static final String ALGO_8 = TxtFileReader.readFile("promptwords/8.txt");
public static final String ALGO_9 = TxtFileReader.readFile("promptwords/9.txt");
public static final String ALGO_10 = TxtFileReader.readFile("promptwords/10.txt");
public static final String ALGO_11 = TxtFileReader.readFile("promptwords/11.txt");
public static final String ALGO_12 = TxtFileReader.readFile("promptwords/12.txt");
public static final String ALGO_13 = TxtFileReader.readFile("promptwords/13.txt");
public static final String ALGO_15 = TxtFileReader.readFile("promptwords/15.txt");
public static final String ALGO_16 = TxtFileReader.readFile("promptwords/16.txt");
public static final String ALGO_17 = TxtFileReader.readFile("promptwords/17.txt");
public static final String ALGO_18 = TxtFileReader.readFile("promptwords/18.txt");
public static final String ALGO_19 = TxtFileReader.readFile("promptwords/19.txt");
public static final String ALGO_21 = TxtFileReader.readFile("promptwords/21.txt");
public static final String ALGO_22 = TxtFileReader.readFile("promptwords/22.txt");
public static final String ALGO_23 = TxtFileReader.readFile("promptwords/23.txt");
public static final String ALGO_24 = TxtFileReader.readFile("promptwords/24.txt");
public static final String ALGO_25 = TxtFileReader.readFile("promptwords/25.txt");
public static final String ALGO_26 = TxtFileReader.readFile("promptwords/26.txt");
public static final String ALGO_27 = TxtFileReader.readFile("promptwords/27.txt");
public static final String ALGO_28 = TxtFileReader.readFile("promptwords/28.txt");
public static final String ALGO_30 = TxtFileReader.readFile("promptwords/30.txt");
public static final String ALGO_32 = TxtFileReader.readFile("promptwords/32.txt");
public static final String ALGO_33 = TxtFileReader.readFile("promptwords/33.txt");
public static final String ALGO_34 = TxtFileReader.readFile("promptwords/34.txt");
public static final String ALGO_35 = TxtFileReader.readFile("promptwords/35.txt");
public static final String ALGO_39 = TxtFileReader.readFile("promptwords/39.txt");
public static final String ALGO_40 = TxtFileReader.readFile("promptwords/40.txt");
public static final String ALGO_52 = TxtFileReader.readFile("promptwords/52.txt");
public static final String ALGO_53 = TxtFileReader.readFile("promptwords/53.txt");
public static final String ALGO_55 = TxtFileReader.readFile("promptwords/55.txt");
public static final String DEFAULT_INPUT = "";
}
\ No newline at end of file
package pro.spss.server.agent.domain.constant;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.enums.IntentStatus;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
* @author:wn
* @Desc
* @create: 2025-06-10-11:44
**/
@Component
public class AlgoNameConfig {
private final Map<String, String> algoNameToId = new HashMap<>();
@PostConstruct
public void load() {
algoNameToId.put("最小二乘回归", "1");
algoNameToId.put("岭回归", "2");
algoNameToId.put("二分类逻辑回归", "3");
algoNameToId.put("套索回归", "4");
algoNameToId.put("多项式回归", "5");
algoNameToId.put("支持向量机回归", "6");
algoNameToId.put("BP神经网络", "7");
algoNameToId.put("判别分析", "8");
algoNameToId.put("决策树", "9");
algoNameToId.put("随机森林", "10");
algoNameToId.put("自适应增强算法", "11");
algoNameToId.put("梯度提升树", "12");
algoNameToId.put("K-均值聚类", "13");
algoNameToId.put("k近邻法", "14");
algoNameToId.put("层次聚类", "15");
algoNameToId.put("因子分析", "16");
algoNameToId.put("主成分分析", "17");
algoNameToId.put("t检验", "18");
algoNameToId.put("正态检验", "19");
algoNameToId.put("关联规则挖掘法", "20");
algoNameToId.put("皮尔逊相关分析", "21");
algoNameToId.put("斯皮尔曼相关分析", "22");
algoNameToId.put("移动平均法", "23");
algoNameToId.put("指数平滑法", "24");
algoNameToId.put("自回归模型(AR)", "25");
algoNameToId.put("滑动平均模型(MA)", "26");
algoNameToId.put("自回归滑动平均模型", "27");
algoNameToId.put("差分自回归移动平均模型", "28");
algoNameToId.put("优劣解距离法", "30");
algoNameToId.put("弹性系数法", "31");
algoNameToId.put("模糊综合评价", "33");
algoNameToId.put("净现值法", "34");
algoNameToId.put("秩和比评价法", "35");
algoNameToId.put("区位熵分析", "36");
algoNameToId.put("李克特量表", "37");
algoNameToId.put("蒙特卡洛模拟", "38");
algoNameToId.put("灰色模型", "39");
algoNameToId.put("柯布-道格拉斯生产函数", "40");
algoNameToId.put("描述性统计", "41");
algoNameToId.put("标准化", "42");
algoNameToId.put("区间缩放", "43");
algoNameToId.put("非线性转换", "44");
algoNameToId.put("归一化", "45");
algoNameToId.put("二值化", "46");
algoNameToId.put("哑编码", "47");
algoNameToId.put("缺失值计算", "48");
algoNameToId.put("多项式转换", "49");
algoNameToId.put("自定义转换", "50");
algoNameToId.put("层次分析法", "51");
algoNameToId.put("数据包络分析(BCC)", "52");
algoNameToId.put("数据包络分析(CCR)", "53");
}
public String getAlgoId(String name) {
return algoNameToId.get(name);
}
public AiChatResponse matchAlgoNameInPrompt(String prompt) {
AiChatResponse result = new AiChatResponse();
for (Map.Entry<String, String> entry : algoNameToId.entrySet()) {
String algoName = entry.getKey();
if (prompt != null && prompt.contains(algoName)) {
result.setHasIntent(IntentStatus.UserHasAglo.getState());
result.setAlgoName(algoName);
return result;
}
}
result.setHasIntent(IntentStatus.NoIntent.getState());
return result;
}
}
package pro.spss.server.agent.domain.constant;
import java.util.Map;
/**
* @author:wn
* @Desc
* @create: 2025-06-12-17:28
**/
public class AlgoPromptConfig {
private static final Map<String, String> algoPromptMap = Map.ofEntries(
Map.entry("1", AlgoConfig.ALGO_1),
Map.entry("2", AlgoConfig.ALGO_2),
Map.entry("3", AlgoConfig.ALGO_3),
Map.entry("4", AlgoConfig.ALGO_4),
Map.entry("5", AlgoConfig.ALGO_5),
Map.entry("6", AlgoConfig.ALGO_6),
Map.entry("7", AlgoConfig.ALGO_7),
Map.entry("8", AlgoConfig.ALGO_8),
Map.entry("9", AlgoConfig.ALGO_9),
Map.entry("10", AlgoConfig.ALGO_10),
Map.entry("11", AlgoConfig.ALGO_11),
Map.entry("12", AlgoConfig.ALGO_12),
Map.entry("13", AlgoConfig.ALGO_13),
Map.entry("15", AlgoConfig.ALGO_15),
Map.entry("16", AlgoConfig.ALGO_16),
Map.entry("17", AlgoConfig.ALGO_17),
Map.entry("18", AlgoConfig.ALGO_18),
Map.entry("19", AlgoConfig.ALGO_19),
Map.entry("21", AlgoConfig.ALGO_21),
Map.entry("22", AlgoConfig.ALGO_22),
Map.entry("23", AlgoConfig.ALGO_23),
Map.entry("24", AlgoConfig.ALGO_24),
Map.entry("25", AlgoConfig.ALGO_25),
Map.entry("26", AlgoConfig.ALGO_26),
Map.entry("27", AlgoConfig.ALGO_27),
Map.entry("28", AlgoConfig.ALGO_28),
Map.entry("30", AlgoConfig.ALGO_30),
Map.entry("33", AlgoConfig.ALGO_33),
Map.entry("34", AlgoConfig.ALGO_34),
Map.entry("35", AlgoConfig.ALGO_35),
Map.entry("39", AlgoConfig.ALGO_39),
Map.entry("40", AlgoConfig.ALGO_40),
Map.entry("52", AlgoConfig.ALGO_52),
Map.entry("53", AlgoConfig.ALGO_53),
Map.entry("55", AlgoConfig.ALGO_55)
);
public static String getPromptById(String algoId) {
return algoPromptMap.getOrDefault(algoId, AlgoConfig.DEFAULT_INPUT);
}
}
package pro.spss.server.agent.domain.constant;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
/**
* @author:wn
* @Desc
* @create: 2024-12-31-14:00
**/
public class ChatConstants {
public static final String USER_ROLE = "user";
public static final String ROLE_SYSTEM = "system";
public static final double TEMPERATURE = 1.0;
public static final String CONTENT = "content";
public static final String RESPONSE = "response";
public static final String PARAMS = "params";
public static final String OPTIONS = "options";
public static final String MESSAGE = "message";
public static final String USER_PROMPT = "\n\n【用户需求】\n";
public static final String DATA_STATUS = "【当前状态】";
public static final String DATA_PROMPT = "【数据内容】";
public static final String WELCOME_MESSAGE = "您好,我是数据分析机器人,根据您上传的数据和需求为您推荐合适的算法和参数配置,请问您想分析什么?";
public static final String MARKDOWN = "(?i)```json\\s*|```\\s*";
public static final JSONArray SYSTEM_MESSAGES = initSystemMessage();
private static JSONArray initSystemMessage() {
return new JSONArray();
}
public static JSONObject createMessage(String role, String content) {
JSONObject message = new JSONObject();
message.put("role", role);
message.put("content", content);
return message;
}
}
package pro.spss.server.agent.domain.constant;
/**
* @author:wn
* @Desc
* @create: 2025-04-07-09:40
**/
public class DeaConstants {
public static final String TIME = "selectTimeNames";
public static final String INDUSTRY = "selectIndustryNames";
public static final String ADDRESS = "selectAddressNames";
}
package pro.spss.server.agent.domain.entity;
import lombok.Data;
@Data
public class AIResponse {
private String responseText;
private String think;
private Integer totalToken;
@Override
public String toString() {
return "AIResponse{" +
"responseText='" + responseText + '\'' +
", think='" + think + '\'' +
", totalToken=" + totalToken +
'}';
}
}
package pro.spss.server.agent.domain.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class AiChatResponse extends RecognizeIntentResult {
// Getter 和 Setter 方法
private String response;
@JsonProperty("recommendedAlgorithms")
private List<String> recommendedAlgorithms;
}
\ No newline at end of file
package pro.spss.server.agent.domain.entity;
import lombok.Data;
import java.util.List;
/**
* @author:wn
* @Desc
* @create: 2024-12-31-13:45
**/
@Data
public class ChatRequest {
private String model;
private List<Message> messages;
private boolean stream;
private float temperature;
@Data
public static class Message {
private String role;
private String content;
}
}
package pro.spss.server.agent.domain.entity;
import lombok.Data;
import java.util.List;
/**
* @author:wn
* @Desc
* @create: 2024-12-31-13:41
**/
@Data
public class ChatResponse {
private List<Choice> choices;
@Data
public static class Choice {
private Message message;
}
@Data
public static class Message {
private String role;
private String content;
}
}
\ No newline at end of file
package pro.spss.server.agent.domain.entity;
import lombok.Data;
/**
* @author:wn
* @Desc
* @create: 2025-04-01-11:28
**/
@Data
public class ComputeResult {
private String algoId;
private String component;
private Object graph;
private Object table;
private Object text;
}
package pro.spss.server.agent.domain.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author:wn
* @Desc
* @create: 2025-06-16-17:53
**/
@Data
public class RecognizeIntentResult {
@JsonProperty("hasIntent")
private Integer hasIntent;
private String algoName;
}
package pro.spss.server.agent.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @author:wn
* @Desc
* @create: 2025-11-11-16:15
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDataBinding {
private String bindId;
private String userId;
private String dataId;
private String dataVersionId;
private Date createTime;
}
\ No newline at end of file
package pro.spss.server.agent.domain.enums;
import lombok.Getter;
import pro.spss.server.agent.domain.response.Result;
/**
* @author:wn
* @Desc
* @create: 2025-04-01-12:56
**/
@Getter
public enum ChatStatusEnum {
REQUESTFAILED (301, "请求失败"),
ALGORUNFAILED (302, "算法执行失败"),
NORESULT (304, "未收到算法执行结果"),
RESPONSEFAILED (303, "大模型无有效回应"),
EXECUTOR_FAILED(305, "算法提交任务失败");
private Integer code;
private String message;
public Integer getCode() {
return code;
}
ChatStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Result toResult() {
return Result.failed(this.code, this.message);
}
}
package pro.spss.server.agent.domain.enums;
/**
* @author:wn
* @Desc
* @create: 2025-06-17-15:59
**/
public enum ConversationStateEnum {
NO_DATA_INTENT_RECOGNITION(0), //未上传数据之前的闲聊以及引导阶段
INTENT_RECOGNITION(1), // 意图识别阶段
PARAM_RECOMMENDATION(2), // 算法参数配置推荐阶段
PARAM_CONFIRMATION(3), // 参数确认阶段
PARAM_MODIFICATION(4), // 参数修改阶段
USER_CONFIREM_RUNTASK(5), // 用户已确认可以执行算法
DEFAULT(99); // 默认状态
private final int code;
// 构造器名必须和枚举名一致
ConversationStateEnum(int code) {
this.code = code;
}
public int getCode() {
return code;
}
// 根据int获取对应枚举
public static ConversationStateEnum fromCode(int code) {
for (ConversationStateEnum state : ConversationStateEnum.values()) {
if (state.getCode() == code) {
return state;
}
}
return DEFAULT;
}
}
package pro.spss.server.agent.domain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @Author wangning
* @createTime 2024/11/13 15:22
* @Description
*/
@AllArgsConstructor
@Getter
public enum CreateWayEnum {
DATA_FIRST(0, "先选数据"),
ALGO_FIRST(1, "先选算法");
private final int code;
private final String info;
}
package pro.spss.server.agent.domain.enums;
public enum IntentStatus {
NoIntent(0), //没有识别到意图
UserHasAglo(1), //用户指定算法
Suggest(2); //大模型推荐算法
private Integer state;
private IntentStatus(Integer state) {
this.state = state;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
}
package pro.spss.server.agent.domain.enums;
public enum MessageType {
INIT("init"),
MESSAGE("msg"),
ASK("ask"),
TASKRESULT("result");
private String type;
MessageType(String type){
this.type = type;
}
public String getType(){
return type;
}
}
package pro.spss.server.agent.domain.enums;
import lombok.Getter;
@Getter
public enum ResponseMessageType {
INIT("init"),
MESSAGE("message"),
RESULT("result"),
CONFIG("config");
private String type;
private ResponseMessageType(String type) {
this.type = type;
}
}
package pro.spss.server.agent.domain.enums;
import lombok.Getter;
/**
* @author:wn
* @Desc
* @create: 2025-03-20-16:07
**/
@Getter
public enum ResultCodeEnum {
SUCCESS(true, 200, "成功"),
BASE_ERROR(false,500, "后端异常"),
ANALYSIS_SUCCESS(true, 201, "创建分析任务成功"),
UNKNOWN_REASON(false, 20001, "未知错误"),
INVALIDREQUEST(false, 20002, "无效请求"),
INVALIDTOKEN(false, 22001, "无效token"),
ILLEGALPASSWARD(false, 22002, "密码错误"),
FORBIDDENACCESS(false, 22003, "无效凭证"),
VALIDATIONERROR(false, 23001, "算法调用异常"),
DOWNLOAD_ERROR(false, 24001, "下载异常"),
SAVE_ChART_FAIL(false, 25001, "保存图片失败");
private boolean success;
private Integer code;
private String message;
private Object data;
private ResultCodeEnum(Boolean success, Integer code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
}
package pro.spss.server.agent.domain.enums;
public enum RoleType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system");
private String type;
RoleType(String type){
this.type = type;
}
public String getType(){
return type;
}
}
package pro.spss.server.agent.domain.enums;
import lombok.Getter;
/**
* @author:wn
* @Desc
* @create: 2024-01-16-20:52
**/
@Getter
public enum TaskStatusEnum {
WAITING(0, "等待中"),
VALIDATION_FAILED(1, "校验失败"),
IN_PROGRESS(2, "执行中"),
SUCCESS(3, "执行成功"),
SUCCESS_WITH_WARNING(4, "执行成功但有警告"),
FAILED(5, "执行失败");
private Integer code;
private String message;
public Integer getCode() {
return code;
}
TaskStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
package pro.spss.server.agent.domain.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class DaAgentMessage {
private Long messageId;
private Long sessionId;
private String role; // 'user' or 'assistant'
private String content;
private String think; // 深度思考内容
private Integer tokens;
private String type; // 消息类型
private String metadata; // JSON as string
private String toolName; // 调用的工具名称
private long startTimestamp; // 调用开始时间戳
private long endTimestamp; // 调用结束时间戳
private Date createdAt;
// 新增反馈相关字段
private Integer feedbackRating; // 1=赞, -1=踩, 0=中立
private String feedbackContent; // 反馈内容
private Boolean isCollected; // 收藏状态:false/true
private Date feedbackAt; // 反馈时间
}
package pro.spss.server.agent.domain.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class DaAgentSession {
private Long sessionId;
private String userId;
private String title;
private Integer status; // 1=活跃, 0=归档, -1=删除
private String metadata; // JSON as string
private Date createdAt;
private Date updatedAt;
private Date lastActiveAt;
}
package pro.spss.server.agent.domain.pojo;
import lombok.Data;
/**
* @author:wn
* @Desc
* @create: 2025-03-24-00:06
**/
@Data
public class TaskMessage {
private String taskId;
private String taskName;
private int taskStatus; //任务状态
private String algoId;
private String resultPath;
private Object taskParams;
private String dataId;
}
package pro.spss.server.agent.domain.request;
import com.alibaba.fastjson2.JSONArray;
import lombok.Data;
import pro.spss.server.agent.domain.enums.ConversationStateEnum;
/**
* @author: wn
* @Desc
* @create: 2025-03-06-10:59
**/
@Data
public class RequestParams {
private String dataId;
private String dataVersionId;
private String algoId;
private String algoName;
private int createWay;
private JSONArray params;
private ConversationStateEnum state = ConversationStateEnum.INTENT_RECOGNITION;
private boolean dataIdUpdated = false;
private boolean dataVersionIdUpdated = false;
private boolean confirmed = false;
// 新增字段
private boolean dataHandled = false;
private String dataSummary; // 数据概要(列类型+示例),供自动参数生成使用
// public void updateDataId(String dataId) {
// if (!this.dataIdUpdated && dataId != null) {
// this.dataId = dataId;
// this.dataIdUpdated = true;
// }
// }
public void updateDataId(String dataId) {
if (dataId != null) {
this.dataId = dataId;
}
}
// public void updateDataVersionId(String dataVersionId) {
// if (!this.dataVersionIdUpdated && dataVersionId != null) {
// this.dataVersionId = dataVersionId;
// this.dataVersionIdUpdated = true;
// }
// }
public void updateDataVersionId(String dataVersionId) {
if (dataVersionId != null) {
this.dataVersionId = dataVersionId;
}
}
public void updateAlgoId(String algoId) {
if (algoId != null) {
this.algoId = algoId;
}
}
public void updateAlgoName(String algoName) {
if (algoName != null) {
this.algoName = algoName;
}
}
public void updateParams(JSONArray params) {
if (params != null) {
this.params = params;
}
}
public int getStateCode() {
return state.getCode();
}
// 新增dataHandled的getter和setter
public boolean isDataHandled() {
return dataHandled;
}
public void setDataHandled(boolean dataHandled) {
this.dataHandled = dataHandled;
}
public String getDataSummary() {
return dataSummary;
}
public void setDataSummary(String dataSummary) {
this.dataSummary = dataSummary;
}
}
package pro.spss.server.agent.domain.request;
import lombok.Data;
/**
* @author:wn
* @Desc
* @create: 2025-06-11-19:11
**/
@Data
public class UserChatMessage {
private String prompt; //用户消息
private String dataId; //数据id
private String dataVersionId; //数据版本id
private Boolean confirm; // 用户是否确认执行任务
private String token; // 用户token
private String userId;
}
package pro.spss.server.agent.domain.response;
public class ErrorResponse extends Result {
public ErrorResponse(Integer code, String message) {
super(code, message, false, null);
}
}
package pro.spss.server.agent.domain.response;
import java.util.ArrayList;
import java.util.List;
public class QueryResponse<T> {
private int pageSize;
private int pageNumber;
private int count;
private List<T> list;
public QueryResponse() {
list = new ArrayList<>();
}
public QueryResponse(int count, int pageSize, int pageNumber) {
this.count = count;
this.pageSize = pageSize;
this.pageNumber = pageNumber;
}
public QueryResponse(int count, int pageSize, int pageNumber, List<T> list) {
this.count = count;
this.pageSize = pageSize;
this.pageNumber = pageNumber;
this.list = list;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageNumber() {
return pageNumber;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
/**
* 找出 分页查询 改页码 应该从第几条记录开始limit
* @return
*/
public int getFirstIndex() {
if(this.pageSize <= 0 ) { // 如果pageSize 小于 0, 将会设置成默认0
this.pageSize = 10;
}
//计算总页数
int totalPage = 0;
if(this.count == 0) {
totalPage = 0;
return 0;
}else {
totalPage = this.count/this.pageSize;
if(this.count%this.pageSize > 0) {
totalPage = totalPage+1;
}
}
// 如果pageNumber 小于0 则设置成第一页
if(this.pageNumber<= 0){
this.pageNumber = 1;
} else if(this.pageNumber > totalPage) { // 如果pageNumber 大于最大页码数 则设置成最大页码
this.pageNumber = totalPage;
}
return (this.pageNumber-1)*this.pageSize; //返回页面数
}
}
package pro.spss.server.agent.domain.response;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import pro.spss.server.agent.domain.enums.ResponseMessageType;
/**
* @author:wn
* @Desc
* @create: 2025-03-24-17:24
**/
@Data
public class ResponseMessage {
private Long messageId;
private String sessionId;
private String response;
private String think; //深度思考内容
private Integer totalToken;
private Object options;
private Integer code;
private String type = ResponseMessageType.MESSAGE.getType(); // 消息类型 "message/result/config"
private String message; // 消息内容
private long startTimestamp; //开始时间
private long endTimestamp; //结束时间
private String userId;
private JSONObject result;
public ResponseMessage() {
startTimestamp = System.currentTimeMillis();
}
public String toJSONString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON序列化失败", e);
}
}
}
package pro.spss.server.agent.domain.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import pro.spss.server.agent.domain.enums.ResultCodeEnum;
//设置统一资源返回结果集
@Data
@Schema(name = "Result全局统一返回结果")
public class Result {
@Schema(description = "返回码")
private Integer code;
@Schema(description = "返回消息")
private String message;
@Schema(description = "是否成功")
private Boolean success;
@Schema(description = "返回数据")
private Object data;
private Result() {}
public Result(Integer newCode, String newMessage, Boolean newSuccess, Object newData) {
this.code = newCode;
this.message = newMessage;
this.success = newSuccess;
this.data = newData;
}
public Result(Object data, String failed, boolean b) {
this.code = code;
this.message = message;
this.success = success;
this.data = data;
}
public Result(ResultCodeEnum resultCodeEnum, Object data) {
this.code = resultCodeEnum.getCode();
this.message = resultCodeEnum.getMessage();
this.success = resultCodeEnum.isSuccess();
this.data = data;
}
//返回成功的结果集
public static Result success(){
Result r = new Result();
r.setSuccess(ResultCodeEnum.SUCCESS.isSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return r;
}
public static Result analysisSuccess(Object data){
Result r = new Result();
r.setSuccess(ResultCodeEnum.ANALYSIS_SUCCESS.isSuccess());
r.setCode(ResultCodeEnum.ANALYSIS_SUCCESS.getCode());
r.setMessage(ResultCodeEnum.ANALYSIS_SUCCESS.getMessage());
r.setData(data);
return r;
}
//返回带参的成功结果集
public static Result success(Object data) {
Result r = new Result();
r.setSuccess(ResultCodeEnum.SUCCESS.isSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
r.setData(data);
return r;
}
//返回失败的结果集
public static Result failed(Result result) {
Result r = new Result();
r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.isSuccess());
r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
return r;
}
public static Result failed(int code, String message) {
Result r = new Result();
r.setSuccess(false);
r.setCode(code);
r.setMessage(message);
return r;
}
public static Result failed(int code, String message, Object data) {
Result r = new Result();
r.setSuccess(false);
r.setCode(code);
r.setMessage(message);
r.setData(data);
return r;
}
/**
* @return
*/
public static Result setResult( ResultCodeEnum resultCodeEnum){
Result r = new Result();
r.setSuccess(resultCodeEnum.isSuccess());
r.setCode(resultCodeEnum.getCode());
r.setMessage(resultCodeEnum.getMessage());
return r;
}
public Result success(Boolean success){
this.setSuccess(success);
return this;
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(Object data) {
this.data = data;
return this;
}
}
package pro.spss.server.agent.domain.response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import pro.spss.server.agent.exception.BaseException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 结果封装:统一异常处理 + 结果包装。
*/
@Slf4j
@RestControllerAdvice
public class ResultWrapper implements ResponseBodyAdvice<Object> {
private boolean isSseRequest(HttpServletRequest request, HttpServletResponse response) {
try {
String accept = request != null ? request.getHeader("Accept") : null;
String contentType = response != null ? response.getContentType() : null;
return (accept != null && accept.contains(MediaType.TEXT_EVENT_STREAM_VALUE))
|| (contentType != null && contentType.contains(MediaType.TEXT_EVENT_STREAM_VALUE));
} catch (Exception ignore) {
return false;
}
}
private ResponseEntity<String> buildSseErrorBody(int code, String message) {
// SSE 文本事件格式:event + data + 空行
String payload = new StringBuilder()
.append("event: error\n")
.append("data: {")
.append("\"code\":").append(code).append(",")
.append("\"message\":\"").append(message == null ? "" : message.replace("\n", " ")).append("\"}")
.append("\n\n").toString();
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(payload);
}
/** 全局异常处理 */
@ExceptionHandler(Exception.class)
Object handleAllException(HttpServletRequest request, HttpServletResponse response, Exception e) {
log.error("出现了异常!", e);
String message = (e == null) ? "未知的错误" : e.getMessage();
// SSE 请求:按 SSE 事件返回,避免 JSON 转换器与 event-stream 冲突
if (isSseRequest(request, response)) {
return buildSseErrorBody(500, "服务异常:" + message);
}
// 常规请求:统一 Result JSON
response.setStatus(200);
return Result.failed(500, "服务异常:" + message);
}
/** 自定义业务异常处理 */
@ExceptionHandler(BaseException.class)
Object handleBaseException(HttpServletRequest request, HttpServletResponse response, Exception e) {
log.error("出现了异常!", e);
BaseException be = (BaseException) e;
if (isSseRequest(request, response)) {
return buildSseErrorBody(be.getCode(), be.getErrorMessage());
}
response.setStatus(200);
return Result.failed(be.getCode(), be.getErrorMessage());
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
/** 结果统一处理:SSE 不做包装,直接透传。 */
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (selectedContentType != null && MediaType.TEXT_EVENT_STREAM.includes(selectedContentType)) {
return body;
}
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
}
package pro.spss.server.agent.domain.response;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class TokenResponse {
private String access_token;
private String token_type;
private int expires_in;
private String refresh_token;
@JsonIgnore
private long grant;
public TokenResponse(String access_token, String token_type, int expires_in, String refresh_token, long grant) {
this.access_token = access_token;
this.token_type = token_type;
this.expires_in = expires_in;
this.refresh_token = refresh_token;
this.grant = grant;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getToken_type() {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public long getGrant() {
return grant;
}
public void setGrant(long grant) {
this.grant = grant;
}
}
package pro.spss.server.agent.exception;
/**
* @ClassName: BaseException
* @Description: 自定义基础Exception
* @Author: wzc
* @Date: 2023/11/24 13:47
*/
public abstract class BaseException extends Exception {
private int code;
private String errorMessage;
public BaseException(int code, String errorMessage) {
super();
this.code = code;
this.errorMessage = errorMessage;
}
public int getCode() {
return code;
}
public String getErrorMessage() {
return errorMessage;
}
public BaseException withErrMessage(String message) {
this.errorMessage = message;
return this;
}
@Override
public String toString() {
return "BaseException{" +
"code=" + code +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}
package pro.spss.server.agent.exception;
import pro.spss.server.agent.domain.enums.ResultCodeEnum;
/**
* @ClassName: InvalidTokenException
* @Description: 自定义token异常
* @Author: wzc
* @Date: 2023/11/24 13:47
*/
public class InvalidTokenException extends BaseException {
public static InvalidTokenException instance = new InvalidTokenException();
public InvalidTokenException() {
super(ResultCodeEnum.INVALIDTOKEN.getCode(), ResultCodeEnum.INVALIDTOKEN.getMessage());
}
}
package pro.spss.server.agent.exception;
/**
* @author wzc
* @version 1.0
* @description: TODO
* @date 2024/5/17 11:38
*/
public class SampleException extends RuntimeException {
// 自定义异常代码
private int code = 500;
public SampleException(int code, String errorMessage) {
super(errorMessage);
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
package pro.spss.server.agent.exception;
/**
* @ClassName: SimpleException
* @Description:
* @Author: wzc
* @Date: 2023/4/22 16:02
*/
public class SimpleException extends BaseException {
public SimpleException(int code, String errorMessage) {
super(code, errorMessage);
}
}
package pro.spss.server.agent.exception;
import pro.spss.server.agent.domain.enums.ResultCodeEnum;
public class ToolExecutionException extends BaseException {
public static InvalidTokenException instance = new InvalidTokenException();
public ToolExecutionException() {
super(ResultCodeEnum.BASE_ERROR.getCode(), ResultCodeEnum.BASE_ERROR.getMessage());
}
public ToolExecutionException(String message) {
super(ResultCodeEnum.BASE_ERROR.getCode(), message);
}
}
\ No newline at end of file
package pro.spss.server.agent.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import pro.spss.server.agent.domain.pojo.DaAgentMessage;
import java.util.List;
@Mapper
public interface DaAgentMessageMapper {
int insert(DaAgentMessage message);
List<DaAgentMessage> listBySession(@Param("sessionId") Long sessionId,
@Param("offset") int offset,
@Param("limit") int limit,
@Param("role") String role);
int countBySession(@Param("sessionId") Long sessionId,
@Param("role") String role);
int updateById(DaAgentMessage message);
DaAgentMessage findLastByRole(@Param("sessionId") Long sessionId, @Param("role") String role);
int deleteLastAssistantBySession(@Param("sessionId") Long sessionId);
}
package pro.spss.server.agent.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import pro.spss.server.agent.domain.pojo.DaAgentSession;
import java.util.Date;
import java.util.List;
@Mapper
public interface DaAgentSessionMapper {
int insert(DaAgentSession session);
int update(DaAgentSession session);
DaAgentSession findById(@Param("sessionId") Long sessionId);
List<DaAgentSession> findByUser(@Param("userId") String userId,
@Param("offset") int offset,
@Param("limit") int limit,
@Param("status") Integer status);
int countByUser(@Param("userId") String userId,
@Param("status") Integer status);
int softDelete(@Param("sessionId") Long sessionId);
DaAgentSession findLatestActiveByUser(@Param("userId") String userId);
int updateTitle(@Param("sessionId") Long sessionId, @Param("title") String title);
int deleteEmptyOlderThan(@Param("userId") String userId, @Param("cutoff") Date cutoff);
}
package pro.spss.server.agent.mapper;
import org.apache.ibatis.annotations.Mapper;
import pro.spss.server.agent.domain.pojo.TaskMessage;
@Mapper
public interface ResultMapper {
TaskMessage getTaskMessage(String taskId);
String getDataQuery(String dataId);
}
package pro.spss.server.agent.service.chatCore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 上层服务传入的模型服务配置,便于底层客户端无状态执行
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatApiConfig {
private String url;
private String key;
private String modelName; // 便于需要时在请求体中使用
}
package pro.spss.server.agent.service.chatCore;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.entity.AIResponse;
import javax.annotation.Resource;
import java.util.Map;
/**
* 大模型聊天助手服务:负责消息裁切、请求体构建、会话更新;底层交互由 ChatModelClient 完成
*/
@Slf4j
@Service
public class ChatHelper {
@Value("${agent.api.model_name}")
private String modelName;
@Value("${agent.api.url}")
private String apiUrl;
@Value("${agent.api.key}")
private String apiKey;
@Resource
private ChatModelClient chatModelClient;
/**
* 构建请求体
*/
public JSONObject buildRequestBody(JSONArray messages) {
JSONObject requestBody = new JSONObject();
requestBody.put("model", modelName);
requestBody.put("messages", messages);
requestBody.put("temperature", ChatConstants.TEMPERATURE);
return requestBody;
}
/**
* 同步发送请求(阻塞) - 由 ChatModelClient 执行
*/
public String sendRequestSync(JSONObject requestBody) {
ChatApiConfig cfg = new ChatApiConfig(apiUrl, apiKey, modelName);
return chatModelClient.sendRequestSync(requestBody, cfg);
}
/**
* 解析响应结果,提取 choices 数组
*/
public JSONArray parseResponse(String responseBody) {
return chatModelClient.parseChoices(responseBody);
}
public AIResponse parseResponseText(String responseBody) {
return chatModelClient.parseResponse(responseBody);
}
/**
* 解析响应并更新会话,返回 contentJson
*/
public JSONObject parseAndUpdateSession(String responseBody, JSONArray messages, String sessionId, Map<String, JSONArray> chatSessions) {
try {
JSONArray choices = parseResponse(responseBody);
if (choices != null && !choices.isEmpty()) {
JSONObject assistantMessage = choices.getJSONObject(0).getJSONObject(ChatConstants.MESSAGE);
assistantMessage.remove("reasoning_content");
messages.add(assistantMessage);
chatSessions.put(sessionId, messages);
String rawContent = assistantMessage.getString(ChatConstants.CONTENT);
if (rawContent == null) {
log.warn("响应中不包含 content 字段");
return null;
}
String content1 = rawContent.replaceAll(ChatConstants.MARKDOWN, "");
int thinkEnd = content1.indexOf("</think>");
String content = thinkEnd >= 0 ? content1.substring(thinkEnd + 8) : content1; // 安全处理
log.info("解析后的内容: {}", content);
try {
return JSONObject.parseObject(content);
} catch (Exception parseEx) {
log.error("解析 content 为 JSON 失败,返回原始字符串包装", parseEx);
JSONObject fallback = new JSONObject();
fallback.put("response", "非常抱歉,我还在不断的学习中,未能给您推荐出合适的算法,请您尝试将您的需求给我描述的再详细一点。");
fallback.put("options", new JSONArray());
fallback.put("params", new JSONArray());
return fallback;
}
}
} catch (Exception e) {
log.error("解析响应并更新会话失败", e);
}
return null;
}
/**
* 纯解析:从原始响应体中提取 content(去除 reasoning_content 与 markdown,截断 </think>),
* 并尝试解析为 JSON;失败则返回带默认字段的 JSONObject(response/options/params)。
* 无副作用,不更新会话缓存。
*/
public JSONObject parseContentJson(String responseBody) {
try {
JSONArray choices = parseResponse(responseBody);
if (choices != null && !choices.isEmpty()) {
JSONObject assistantMessage = choices.getJSONObject(0).getJSONObject(ChatConstants.MESSAGE);
// 去除可能存在的推理字段
assistantMessage.remove("reasoning_content");
String rawContent = assistantMessage.getString(ChatConstants.CONTENT);
if (rawContent == null) {
log.warn("响应中不包含 content 字段");
return null;
}
// 去除 markdown 并截断 think
String content1 = rawContent.replaceAll(ChatConstants.MARKDOWN, "");
int thinkEnd = content1.indexOf("</think>");
String content = thinkEnd >= 0 ? content1.substring(thinkEnd + 8) : content1;
try {
return JSONObject.parseObject(content);
} catch (Exception parseEx) {
log.error("解析 content 为 JSON 失败,返回默认结构", parseEx);
JSONObject fallback = new JSONObject();
fallback.put(ChatConstants.RESPONSE, "非常抱歉,我还在不断的学习中,未能给您推荐出合适的算法,请您尝试将您的需求给我描述的再详细一点。");
fallback.put(ChatConstants.OPTIONS, new JSONArray());
fallback.put(ChatConstants.PARAMS, new JSONArray());
return fallback;
}
}
} catch (Exception e) {
log.error("纯解析 contentJson 失败", e);
}
return null;
}
/**
* 统一重试发送的入口:构造 ChatApiConfig 并委托给 ChatModelClient。
*/
public String sendWithRetry(JSONObject requestBody, int maxAttempts, long backoffMs, long totalTimeoutMs) {
ChatApiConfig cfg = new ChatApiConfig(apiUrl, apiKey, modelName);
return chatModelClient.sendWithRetry(requestBody, cfg, maxAttempts, backoffMs, totalTimeoutMs);
}
}
package pro.spss.server.agent.service.chatCore;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.mchange.v2.resourcepool.TimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.entity.AIResponse;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 底层与大模型交互的客户端,封装 WebClient 请求与错误处理(无状态),由上层传入配置
*/
@Slf4j
@Component
public class ChatModelClient {
@Value("${agent.handler_result}")
private boolean handlerResult;
/**
* 发送同步请求到大模型(由上层传入 url 与 key 配置)
*/
public String sendRequestSync(JSONObject requestBody, ChatApiConfig config) {
WebClient webClient = WebClient.builder()
.baseUrl(config.getUrl())
.defaultHeader("Authorization", "Bearer " + config.getKey())
.defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
log.debug("发送给大模型的信息:");
log.debug(requestBody.toString());
long startTime = System.currentTimeMillis();
try {
String response = webClient.post()
.bodyValue(requestBody.toJSONString())
.retrieve()
.onStatus(this::isErrorStatus, this::handleErrorResponse)
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(300))
.doOnSuccess(result -> log.info("大模型请求成功,耗时: {}ms",
System.currentTimeMillis() - startTime))
.doOnError(error -> log.error("大模型请求失败,耗时: {}ms",
System.currentTimeMillis() - startTime))
.block();
log.debug("大模型返回");
log.debug(response);
return response != null ? response : "服务返回空响应";
} catch (Exception e) {
return formatExceptionMessage(e);
}
}
private boolean isErrorStatus(HttpStatus status) {
return !status.is2xxSuccessful();
}
private Mono<Throwable> handleErrorResponse(ClientResponse response) {
HttpStatus statusCode = response.statusCode();
return response.bodyToMono(String.class)
.defaultIfEmpty("无错误详情")
.map(errorBody -> {
String errorMessage = String.format("大模型服务异常 - 状态码: %d, 错误: %s",
statusCode.value(), truncateErrorMessage(errorBody));
if (statusCode.is4xxClientError()) {
return new IllegalArgumentException(errorMessage);
} else {
return new RuntimeException(errorMessage);
}
})
.flatMap(Mono::error);
}
private String truncateErrorMessage(String errorBody) {
if (errorBody == null) return "无错误信息";
return errorBody.length() > 200 ? errorBody.substring(0, 200) + "..." : errorBody;
}
private String formatExceptionMessage(Exception e) {
log.error("大模型服务请求异常", e);
if (e instanceof TimeoutException) {
return "{\"error\": \"请求超时\", \"message\": \"服务响应超时,请稍后重试\"}";
} else if (e instanceof WebClientResponseException.TooManyRequests) {
return "{\"error\": \"请求过于频繁\", \"message\": \"请降低请求频率\"}";
} else if (e instanceof WebClientResponseException) {
WebClientResponseException wcre = (WebClientResponseException) e;
return String.format("{\"error\": \"服务异常\", \"status\": %d, \"message\": \"%s\"}",
wcre.getStatusCode().value(), "远程服务响应异常");
} else if (e.getCause() != null) {
return String.format("{\"error\": \"服务异常\", \"message\": \"%s\"}",
e.getCause().getMessage());
} else {
return "{\"error\": \"系统异常\", \"message\": \"服务暂时不可用,请稍后重试\"}";
}
}
/**
* 从大模型返回的响应中解析 choices 数组
*/
public JSONArray parseChoices(String responseBody) {
if (responseBody == null) {
return null;
}
JSONObject responseJson = JSONObject.parseObject(responseBody);
return responseJson.getJSONArray("choices");
}
public AIResponse parseResponse(String responseBody) {
if (responseBody == null) {
return null;
}
JSONObject responseJson = JSONObject.parseObject(responseBody);
JSONArray choices = responseJson.getJSONArray("choices");
int totalToken = responseJson.getJSONObject("usage").getInteger("total_tokens");
AIResponse aiResponse = new AIResponse();
aiResponse.setTotalToken(totalToken);
String responseText = "";
String think = null;
//结果处理
if (choices != null && !choices.isEmpty()) {
JSONObject assistantMessage = choices.getJSONObject(0).getJSONObject(ChatConstants.MESSAGE);
String content = assistantMessage.getString(ChatConstants.CONTENT).replaceAll(ChatConstants.MARKDOWN, "");
if(handlerResult) {
responseText = content.substring(content.indexOf("</think>") + 10);
int startIndex = content.indexOf("<think>") + 8;
int endIndex = content.indexOf("</think>");
if (startIndex >= 0 && endIndex > startIndex) {
think = content.substring(startIndex, endIndex);
//think字段需要去除多余的换行,连续多个换行只保留一个
think = think.replaceAll("\\n+", "\n").trim();
}
}
aiResponse.setResponseText(responseText);
aiResponse.setThink(think);
log.debug("大模型回复:");
log.debug(aiResponse.toString());
}
return aiResponse;
}
/**
* 统一的重试发送封装:在最大尝试次数与总超时预算内进行重试,简单退避。
* 返回 null 表示发送失败。
*/
public String sendWithRetry(JSONObject requestBody,
ChatApiConfig config,
int maxAttempts,
long backoffMs,
long totalTimeoutMs) {
long start = System.currentTimeMillis();
int attempt = 0;
while (attempt < Math.max(1, maxAttempts)) {
attempt++;
try {
String resp = sendRequestSync(requestBody, config);
if (resp != null && !resp.isBlank()) {
return resp;
}
log.warn("模型响应为空,尝试 {} / {}", attempt, maxAttempts);
} catch (Exception e) {
log.warn("调用模型失败,尝试 {} / {},错误:{}", attempt, maxAttempts, e.getMessage());
}
// 检查总超时预算
if (System.currentTimeMillis() - start >= totalTimeoutMs) {
log.warn("发送总耗时超过预算 {} ms,终止重试", totalTimeoutMs);
break;
}
try {
Thread.sleep(Math.max(0, backoffMs));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
return null;
}
}
package pro.spss.server.agent.service.chatService;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.domain.response.Result;
public interface BaseChatService {
void baseChat(UserChatMessage userChatMessage, boolean retry);
Result clearHistory(String userId);
String testIntent(UserChatMessage userChatMessage);
}
package pro.spss.server.agent.service.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import pro.spss.server.agent.domain.DTO.TaskDTO;
import pro.spss.server.agent.domain.response.Result;
@FeignClient(name = "taskServiceClient", url = "${url.analysis}")
public interface TaskServiceClient {
@PostMapping(value = "/task/exec")
Result callExec(@RequestBody TaskDTO taskDTO, @RequestHeader(name = "Authorization",required = true) String Token);
}
package pro.spss.server.agent.service.handler;
import com.alibaba.fastjson2.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.handler.tools.IntentStateHandler;
/**
* ConversationHandler:路由控制器,负责把“意图识别结果”交给对应的状态处理器去处理。
*/
@Slf4j
@Component
public class ConversationHandler {
private final IntentHandlerRegistry registry;
public ConversationHandler(IntentHandlerRegistry registry) {
this.registry = registry;
}
public ResponseMessage toolExecutor(RequestParams requestParams,
UserChatMessage userChatMessage,
String selectedTool,
JSONArray messages,
String prompt) {
String algoName = "";
String toolName = selectedTool;
if (selectedTool.contains(":")){
algoName = selectedTool.split(":")[1];
toolName = selectedTool.split(":")[0];
}
IntentStateHandler handler = registry.get(toolName);
if (handler == null) {
// 无对应处理器时回落到无意图
handler = registry.get("default");
}
log.info("准备调用工具:{}",handler.getName());
AiChatResponse intentResult = new AiChatResponse();
intentResult.setAlgoName(algoName);
return handler.handle(intentResult, requestParams, messages, prompt,userChatMessage.getToken());
}
}
package pro.spss.server.agent.service.handler;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.entity.AIResponse;
import pro.spss.server.agent.domain.enums.ChatStatusEnum;
import pro.spss.server.agent.domain.enums.ConversationStateEnum;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.chatCore.ChatHelper;
/**
* 提供 ConversationHandler 可复用的支撑方法,便于被状态处理器调用。
*/
@Slf4j
@Component
public class ConversationSupport {
private final ChatHelper chatHelper;
@Value("${agent.chat.retry.maxAttempts:2}")
private int maxAttempts;
@Value("${agent.chat.retry.backoffMs:500}")
private long backoffMs;
@Value("${agent.chat.timeout.totalMs:10000}")
private long totalTimeoutMs;
public ConversationSupport(ChatHelper chatHelper) {
this.chatHelper = chatHelper;
}
/**
* 将系统提示与用户输入注入历史,构造请求,统一重试与错误处理,返回标准响应。
*/
public ResponseMessage sendRequestWithHistoryMessages(JSONArray historyMessages, String intentPrompt, String prompt) {
// 注入系统与用户消息
if (historyMessages == null) {
historyMessages = new JSONArray();
}
if (historyMessages.isEmpty()) {
JSONArray messages = new JSONArray();
messages.add(ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, intentPrompt));
messages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, prompt));
historyMessages.addAll(messages);
} else {
historyMessages.add(0, ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, intentPrompt));
historyMessages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, prompt));
}
JSONObject requestBody = chatHelper.buildRequestBody(historyMessages);
String response = sendWithRetry(requestBody);
if (response == null) {
return buildFailed(ChatStatusEnum.REQUESTFAILED);
}
AIResponse ai = safeParse(response);
if (ai == null) {
return buildFailed(ChatStatusEnum.RESPONSEFAILED);
}
ResponseMessage msg = new ResponseMessage();
msg.setResponse(ai.getResponseText());
msg.setThink(ai.getThink());
msg.setTotalToken(ai.getTotalToken());
return msg;
}
/**
* 直接使用给定消息数组发起请求,统一重试、解析与响应映射,同时更新必要的 RequestParams 状态。
*/
public ResponseMessage sendRequestWithMessages(JSONArray messages, RequestParams requestParams) {
if (messages == null) messages = new JSONArray();
JSONObject requestBody = chatHelper.buildRequestBody(messages);
String responseBody = sendWithRetry(requestBody);
if (responseBody == null) {
return buildFailed(ChatStatusEnum.REQUESTFAILED);
}
// 使用 ChatHelper 的纯解析,提取 response/params/options
JSONObject contentJson = chatHelper.parseContentJson(responseBody);
if (contentJson == null) {
return buildFailed(ChatStatusEnum.RESPONSEFAILED);
}
String response = contentJson.getString(ChatConstants.RESPONSE);
JSONArray params = contentJson.getJSONArray(ChatConstants.PARAMS);
JSONArray options = contentJson.getJSONArray(ChatConstants.OPTIONS);
if (requestParams != null && params != null && !params.isEmpty()) {
requestParams.updateParams(params);
requestParams.setState(ConversationStateEnum.PARAM_CONFIRMATION);
}
ResponseMessage msg = new ResponseMessage();
msg.setResponse(response);
msg.setOptions(options != null ? options : new JSONArray());
return msg;
}
/**
* 安全解析为 AIResponse;失败时返回 null。
*/
private AIResponse safeParse(String response) {
try {
return chatHelper.parseResponseText(response);
} catch (Exception e) {
log.warn("解析响应到 AIResponse 失败:{}", e.getMessage());
return null;
}
}
/**
* 统一构造失败响应。
*/
private ResponseMessage buildFailed(ChatStatusEnum status) {
ResponseMessage msg = new ResponseMessage();
msg.setCode(status.getCode());
msg.setMessage(status.getMessage());
msg.setResponse(status.getMessage());
msg.setOptions(new JSONArray());
return msg;
}
private String sendWithRetry(JSONObject requestBody) {
return chatHelper.sendWithRetry(requestBody, maxAttempts, backoffMs, totalTimeoutMs);
}
}
package pro.spss.server.agent.service.handler;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.service.handler.tools.IntentStateHandler;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 工具注册中心:按 name 收集与查询
*/
@Component
public class IntentHandlerRegistry {
private final Map<String, IntentStateHandler> handlersByName = new HashMap<>();
public IntentHandlerRegistry(List<IntentStateHandler> handlerList) {
if (handlerList != null) {
for (IntentStateHandler h : handlerList) {
handlersByName.put(h.getName(), h);
}
}
}
public IntentStateHandler get(String name) { return handlersByName.get(name); }
// 对外工具清单(name, title, desc),仅包含暴露的工具,按 name 排序
public List<Map<String, String>> getAllToolInfos() {
return handlersByName.values().stream()
.filter(IntentStateHandler::isExposed)
.map(h -> {
Map<String, String> m = new HashMap<>();
m.put("name", h.getName());
m.put("title", h.getTitle());
m.put("desc", h.getDesc());
m.put("exampleUserPrompt", h.exampleUserPrompt());
m.put("condition", h.getCondition());
return m;
})
.sorted(Comparator.comparing(m -> m.get("name"), String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.toList());
}
}
package pro.spss.server.agent.service.handler;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import pro.spss.server.agent.domain.constant.AlgoConfig;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.service.chatCore.ChatHelper;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
import pro.spss.server.agent.service.sessionService.ChatSessionManager;
/**
* 意图识别
*/
@Service
@Slf4j
public class IntentTool {
@Autowired
private ChatHelper chatHelper;
@Autowired
private IntentHandlerRegistry intentHandlerRegistry;
@Autowired
private ChatSessionManager chatSessionManager;
@Value("${agent.intent_history_limit:6}")
private int intentHistoryLimit;
@Value("${agent.handler_result}")
private boolean handlerResult;
@Autowired
private PromptBuilderService promptBuilderService;
/**
* 意图识别:聚合系统提示词模板、动态工具清单、最近历史与用户本轮输入,
* 调用大模型返回目标工具名称。默认回退为 "default"。
* 模板中若包含 {{TOOLS}} 或 {{ALGO_LIST}},将进行占位符替换。
*
* @param prompt 用户本轮原始提示
* @param requestParams 会话上下文参数
* @param userChatMessage 用户消息体
* @return 工具名称(唯一 name)
*/
public String getToolByIntent(String prompt, RequestParams requestParams, UserChatMessage userChatMessage) {
// 读取系统提示词模板
String intentPromptTemplate = promptBuilderService.build(AlgoConfig.INTENT_PROMPT);
// 使用段落版本(当前生效的格式);如需切换为 Markdown,只需改为 buildToolsMarkdownTable()
String toolsText = buildToolsParagraph();
// 将模板中的静态工具段替换为动态清单。模板中建议预留占位符 {{TOOLS}}
String intentPrompt;
if (intentPromptTemplate.contains("{{TOOLS}}")) {
intentPrompt = intentPromptTemplate.replace("{{TOOLS}}", toolsText).replace("{{ALGO_LIST}}", AlgoConfig.ALGO_NAME_LIST);
} else {
// 向后兼容:如果模板没有占位符,则直接在末尾追加,避免破坏原文案
intentPrompt = intentPromptTemplate + "\n\n" + toolsText;
}
String toolId = "default";
String basePrompt = "是否确认执行:" + (requestParams.isConfirmed() ? "是" : "否") + "。\n";
if (requestParams.getDataSummary() != null && !requestParams.getDataSummary().isBlank()) {
prompt = ChatConstants.DATA_PROMPT + requestParams.getDataSummary() + ChatConstants.USER_PROMPT + prompt;
}
prompt = basePrompt + prompt;
// 仅保留最近N条历史(用户/助手),不包含系统消息,N为配置 agent.intent_history_limit,默认5
JSONArray fullHistory = chatSessionManager.getMessages(userChatMessage.getUserId());
JSONArray history = new JSONArray();
if (fullHistory != null && !fullHistory.isEmpty()) {
int size = fullHistory.size();
int limit = Math.max(0, intentHistoryLimit);
int start = Math.max(0, size - limit);
for (int i = start; i < size; i++) {
history.add(fullHistory.get(i));
}
}
log.info("历史指令条数:{}(已截断为最近{}条或更少)", history.size(), intentHistoryLimit);
JSONArray messages = new JSONArray();
messages.add(ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, intentPrompt));
messages.addAll(history); // 添加最近历史消息(仅用户/助手)
messages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, prompt));
JSONObject requestBody = chatHelper.buildRequestBody(messages);
String response = chatHelper.sendRequestSync(requestBody);
JSONArray choices = chatHelper.parseResponse(response);
if (choices != null && !choices.isEmpty()) {
JSONObject assistantMessage = choices.getJSONObject(0).getJSONObject(ChatConstants.MESSAGE);
String content = assistantMessage.getString(ChatConstants.CONTENT).replaceAll(ChatConstants.MARKDOWN, "");
if (handlerResult) {
content = content.substring(content.indexOf("</think>") + 10);
}
log.info("意图识别返回:{}", content);
toolId = content;
}
return toolId;
}
/**
* 构建工具清单(段落样式):
* 每个工具一段,包含 name/title/desc/condition(如有)/userExamplePrompt(如有),
* 用于在系统提示词中让大模型感知当前已集成的工具。
*
* @return 段落样式的工具清单文本
*/
private String buildToolsParagraph() {
StringBuilder sb = new StringBuilder();
intentHandlerRegistry.getAllToolInfos().forEach(m -> {
String name = String.valueOf(m.getOrDefault("name", ""));
String title = String.valueOf(m.getOrDefault("title", name));
String desc = String.valueOf(m.getOrDefault("desc", ""));
String example = String.valueOf(
m.getOrDefault("exampleUserPrompt",
m.getOrDefault("userExamplePrompt", ""))
);
String condition = String.valueOf(
m.getOrDefault("condition", "")
);
sb.append("name: ").append(name).append('\n')
.append("title: ").append(title).append('\n')
.append("desc: ").append(desc).append('\n')
.append("condition:").append(condition).append('\n');
if (example != null && !example.isEmpty()) {
sb.append("userExamplePrompt: ").append(example).append('\n');
}
sb.append('\n');
});
return sb.toString();
}
/**
* 构建工具清单(Markdown 表格样式):
* 第一行表头,后续每行一个工具,包含 name/title/desc/userExamplePrompt。
* 保留以便未来按配置切换展示样式。
*
* @return Markdown 表格样式的工具清单文本
*/
private String buildToolsMarkdownTable() {
StringBuilder sb = new StringBuilder();
sb.append("name | title | desc | userExamplePrompt\n");
sb.append("---|---|---|---\n");
intentHandlerRegistry.getAllToolInfos().forEach(m -> {
String name = String.valueOf(m.getOrDefault("name", ""));
String title = String.valueOf(m.getOrDefault("title", name));
String desc = String.valueOf(m.getOrDefault("desc", ""));
String example = String.valueOf(
m.getOrDefault("exampleUserPrompt",
m.getOrDefault("userExamplePrompt", ""))
);
sb.append(name).append(" | ")
.append(title).append(" | ")
.append(desc).append(" | ")
.append(example)
.append('\n');
});
return sb.toString();
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.taskExecutor.TaskExecutor;
import java.io.IOException;
/**
* 算法执行处理器
*/
@Component
public class AlgoExecutorHandler implements IntentStateHandler {
private static final Logger logger = LoggerFactory.getLogger(AlgoExecutorHandler.class);
@Autowired
private TaskExecutor taskExecutor;
@Override
public String getName() { return "algo_executor"; }
@Override
public String getTitle() { return "算法执行工具"; }
@Override
public String getDesc() { return "这个是在算法确认以及参数确认后,根据用户上传的数据以及选择的算法,进行数据分析,并将结果返回给用户。当是否确认执行为是的时候,也选择该工具。"; }
@Override
public String exampleUserPrompt() {
return "执行算法;执行;执行吧;可以执行";
}
@Override
public String getCondition() {
return "适用于是否确认执行为是;用户希望执行所选算法进行数据分析的场景。";
}
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray messages, String prompt, String token) {
logger.info("算法执行处理器");
try {
return taskExecutor.executeTask(requestParams, token);
} catch (IOException e) {
ResponseMessage msg = new ResponseMessage();
msg.setCode(500);
msg.setMessage("算法执行失败");
msg.setResponse("算法执行失败");
return msg;
}
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.constant.AlgoConfig;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.chatCore.ChatHelper;
import pro.spss.server.agent.service.handler.ConversationSupport;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
/**
* 默认处理器(通用意图识别)
*/
@Slf4j
@Component
public class DefaultHandler implements IntentStateHandler {
@Autowired
private ChatHelper chatHelper;
@Autowired
private PromptBuilderService promptBuilderService;
@Autowired
private ConversationSupport support;
@Value("${agent.handler_result}")
private boolean handlerResult;
@Override
public String getName() { return "default"; }
@Override
public String getTitle() { return "系统通用意图识别工具"; }
@Override
public String getDesc() { return "如果不属于上述工具,可以使用该工具进行简单的会话沟通。"; }
@Override
public String exampleUserPrompt() {
return "明天天气怎么样?";
}
@Override
public String getCondition() {
return "无";
}
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray historyMessages, String prompt, String token) {
// 调用大模型识别
String intentPrompt = promptBuilderService.build(AlgoConfig.SMART_CHAT_PROMPT);
ResponseMessage msg = support.sendRequestWithHistoryMessages(historyMessages, intentPrompt, prompt);
return msg;
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
/**
* 意图识别后的状态处理器接口(即工具)。
* 仅保留工具元信息:name/title/desc/isExposed,以及执行方法 handle。
* 为兼容历史调用,保留一个废弃的默认 getState()。
*/
public interface IntentStateHandler {
// 工具唯一标识(供大模型输出使用)
String getName();
// 简短标题(如:"算法推荐工具")
String getTitle();
// 工具描述(用途与触发场景)
String getDesc();
String exampleUserPrompt();
String getCondition();
// 是否对外暴露给大模型进行选择,默认暴露
default boolean isExposed() { return true; }
// 兼容历史:默认返回 0,不建议使用
@Deprecated
default int getState() { return 0; }
/**
* 工具处理主入口
*/
ResponseMessage handle(AiChatResponse intentResult,
RequestParams requestParams,
JSONArray messages,
String prompt,
String token);
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import pro.spss.server.agent.domain.constant.AlgoConfig;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.chatCore.ChatHelper;
import pro.spss.server.agent.service.handler.ConversationSupport;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
/**
* 无意图(闲聊/引导)处理器
*/
//@Component
public class SmartChatTool implements IntentStateHandler {
private static final Logger logger = LoggerFactory.getLogger(SmartChatTool.class);
@Override
public String getName() { return "smart_chat"; }
@Override
public String getTitle() { return "闲聊工具"; }
@Override
public String getDesc() { return "能够根据用户的问题进行闲聊回复,回复内容包括打招呼,问候,询问系统功能,概念介绍等。当是否上传数据为否的时候,最好选择这个工具进行引导。"; }
@Override
public String exampleUserPrompt() {
return "您好,能介绍一下你自己吗?请问系统中有哪些回归算法?";
}
@Override
public String getCondition() {
return "适用于用户没有上传数据集,且用户的问题属于闲聊/引导类问题的场景;也适用于用户上传了数据集,但问题属于闲聊/引导类问题的场景。";
}
@Autowired
private ChatHelper chatHelper;
@Autowired
private PromptBuilderService promptBuilderService;
@Autowired
private ConversationSupport support;
@Value("${agent.handler_result}")
private boolean handlerResult;
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray historyMessages, String prompt, String token) {
logger.info("闲聊阶段: 引导/闲聊");
// 通过通用服务构建提示词(默认使用 AlgoConfig.SMART_CHAT_PROMPT)
String intentPrompt = promptBuilderService.build(AlgoConfig.SMART_CHAT_PROMPT);
ResponseMessage msg = support.sendRequestWithHistoryMessages(historyMessages, intentPrompt, prompt);
return msg;
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.constant.AlgoConfig;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.handler.ConversationSupport;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
/**
* 系统推荐算法处理器
*/
@Slf4j
@Component
public class SuggestIntentHandler implements IntentStateHandler {
private static final Logger logger = LoggerFactory.getLogger(SuggestIntentHandler.class);
@Autowired
private PromptBuilderService promptBuilderService;
@Autowired
private ConversationSupport support;
@Override
public String getName() { return "suggest_algorithm"; }
@Override
public String getTitle() { return "算法推荐工具"; }
@Override
public String getDesc() { return "能够根据用户询问的问题以及携带的数据概览进行算法推荐功能。"; }
@Override
public String exampleUserPrompt() {
return "对31个省市的创新效率进行评价";
}
@Override
public String getCondition() {
return "是否上传数据为是";
}
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray historyMessages, String prompt, String token) {
logger.info("系统推荐算法触发");
JSONArray algoOptions = new JSONArray();
if (intentResult.getRecommendedAlgorithms() != null && !intentResult.getRecommendedAlgorithms().isEmpty()) {
algoOptions.addAll(intentResult.getRecommendedAlgorithms());
}
if (intentResult.getAlgoName() != null && !intentResult.getAlgoName().isEmpty()) {
algoOptions.add(intentResult.getAlgoName());
}
if (algoOptions.isEmpty()) {//上一步没有推荐出来算法
String intentPrompt = promptBuilderService.build(AlgoConfig.RECOGNIZE_ALGO_NAME);
if(historyMessages.isEmpty()) {
JSONArray messages = new JSONArray();
messages.add(ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, intentPrompt));
messages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, prompt));
historyMessages.addAll(messages);
} else {
historyMessages.add(0, ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, intentPrompt));
historyMessages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, prompt));
}
//构建请求大模型参数,并请求
ResponseMessage responseMessage = support.sendRequestWithMessages(historyMessages, requestParams);
log.info("推荐算法结果:" + JSONObject.toJSONString(responseMessage));
return responseMessage;
}
ResponseMessage msg = new ResponseMessage();
msg.setResponse(intentResult.getResponse() != null ? intentResult.getResponse() : ChatConstants.WELCOME_MESSAGE);
msg.setOptions(algoOptions);
return msg;
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import pro.spss.server.agent.domain.constant.AlgoConfig;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.chatCore.ChatHelper;
import pro.spss.server.agent.service.handler.ConversationSupport;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
/**
* 无意图(闲聊/引导)处理器
*/
//@Component
public class SystemInfoChatTool implements IntentStateHandler {
private static final Logger logger = LoggerFactory.getLogger(SystemInfoChatTool.class);
@Override
public String getName() { return "system_info_chat"; }
@Override
public String getTitle() { return "算法介绍工具"; }
@Override
public String getDesc() { return "在用户没有上传数据的情况下询问算法情况,可以对算法的概念以及使用进行介绍等。"; }
@Override
public String exampleUserPrompt() {
return "请帮我介绍一下什么是回归算法?";
}
@Override
public String getCondition() {
return "无";
}
@Autowired
private ChatHelper chatHelper;
@Value("${agent.handler_result}")
private boolean handlerResult;
@Autowired
private PromptBuilderService promptBuilderService;
@Autowired
private ConversationSupport support;
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray historyMessages,
String prompt, String token) {
logger.info("闲聊阶段: 引导/闲聊");
// 调用大模型识别
String intentPrompt = promptBuilderService.build(AlgoConfig.SMART_CHAT_PROMPT);
ResponseMessage msg = support.sendRequestWithHistoryMessages(historyMessages, intentPrompt, prompt);
return msg;
}
}
package pro.spss.server.agent.service.handler.tools;
import com.alibaba.fastjson2.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.constant.AlgoNameConfig;
import pro.spss.server.agent.domain.constant.AlgoPromptConfig;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import pro.spss.server.agent.domain.enums.ConversationStateEnum;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.service.handler.ConversationSupport;
import pro.spss.server.agent.service.prompt.PromptBuilderService;
/**
* 用户直接指定算法处理器
*/
@Component
public class UserAlgoIntentHandler implements IntentStateHandler {
private static final Logger logger = LoggerFactory.getLogger(UserAlgoIntentHandler.class);
@Autowired
private PromptBuilderService promptBuilderService;
private final AlgoNameConfig algoNameConfig;
private final ConversationSupport support;
public UserAlgoIntentHandler(AlgoNameConfig algoNameConfig,
ConversationSupport support) {
this.algoNameConfig = algoNameConfig;
this.support = support;
}
@Override
public String getName() { return "user_algo_selection"; }
@Override
public String getTitle() { return "算法参数推荐工具"; }
@Override
public String getDesc() { return "用户已经上传数据,并且确定了使用某个算法进行数据分析,可以使用算法参数推荐工具对算法参数进行推荐配置。" +
"注意只有当用户明确指定了某个算法的时候才使用这个工具,系统具体集成的算法请参考算法清单。"; }
@Override
public String exampleUserPrompt() {
return "请使用随机森林算法对我的数据进行分类分析。";
}
@Override
public String getCondition() {
return "是否上传数据为是";
}
@Override
public ResponseMessage handle(AiChatResponse intentResult, RequestParams requestParams, JSONArray messages, String prompt, String token) {
String algoName = intentResult.getAlgoName();
logger.debug("用户指定算法: {}", algoName);
String algoId = algoNameConfig.getAlgoId(algoName);
if (algoId == null) {
ResponseMessage msg = new ResponseMessage();
msg.setCode(303);
String ms = "未找到或者名称有歧义的算法: " + algoName + ",请确认算法名称是否正确。";
msg.setMessage(ms);
msg.setResponse(ms);
return msg;
}
requestParams.updateAlgoId(algoId);
requestParams.updateAlgoName(algoName);
// 构造增强上下文:数据摘要+用户意图+系统算法参数提示
String enrichedPrompt = ChatConstants.DATA_PROMPT + requestParams.getDataSummary() + ChatConstants.USER_PROMPT + prompt;
messages.add(ChatConstants.createMessage(ChatConstants.USER_ROLE, enrichedPrompt));
String algoParamsPrompt = AlgoPromptConfig.getPromptById(algoId);
algoParamsPrompt = promptBuilderService.build(algoParamsPrompt);
messages.add(ChatConstants.createMessage(ChatConstants.ROLE_SYSTEM, algoParamsPrompt));
requestParams.setState(ConversationStateEnum.PARAM_RECOMMENDATION);
return support.sendRequestWithMessages(messages, requestParams);
}
}
package pro.spss.server.agent.service.messageService;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pro.spss.server.agent.domain.enums.MessageType;
import pro.spss.server.agent.domain.enums.RoleType;
import pro.spss.server.agent.domain.pojo.DaAgentMessage;
import pro.spss.server.agent.domain.request.UserChatMessage;
import pro.spss.server.agent.mapper.DaAgentMessageMapper;
import java.util.Date;
import java.util.List;
@Service
public class MessageService {
@Autowired
private DaAgentMessageMapper messageMapper;
public DaAgentMessage addMessage(Long sessionId, String role, String content, String think, String toolName) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(role);
m.setContent(content);
m.setThink(think);
m.setToolName(toolName);
m.setType(MessageType.MESSAGE.getType());
messageMapper.insert(m);
return m;
}
public DaAgentMessage addMessage(Long sessionId, String role, String content, String think, String toolName, String type) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(role);
m.setContent(content);
m.setThink(think);
m.setToolName(toolName);
m.setType(type);
messageMapper.insert(m);
return m;
}
public DaAgentMessage addUserMessage(Long sessionId, String content, UserChatMessage userChatMessage) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(RoleType.USER.getType());
m.setContent(content);
m.setThink(null);
m.setToolName(null);
m.setType(MessageType.ASK.getType());
m.setMetadata(JSONObject.toJSONString(userChatMessage));
messageMapper.insert(m);
return m;
}
// 新增:带type/起止时间戳的插入
public DaAgentMessage addMessage(Long sessionId,
String role,
String content,
String think,
String toolName,
Integer totalToken,
String type,
long startTimestamp,
long endTimestamp) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(role);
m.setContent(content);
m.setThink(think);
m.setToolName(toolName);
m.setTokens(totalToken);
m.setType(type);
m.setStartTimestamp(startTimestamp);
m.setEndTimestamp(endTimestamp);
messageMapper.insert(m);
return m;
}
public DaAgentMessage addMessage(Long sessionId,
String role,
String content,
String think,
String toolName,
Integer totalToken,
String type,
JSONObject metadata,
long startTimestamp,
long endTimestamp) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(role);
m.setContent(content);
m.setThink(think);
m.setToolName(toolName);
m.setTokens(totalToken);
m.setType(type);
m.setMetadata(metadata.toJSONString());
m.setStartTimestamp(startTimestamp);
m.setEndTimestamp(endTimestamp);
messageMapper.insert(m);
return m;
}
public DaAgentMessage addMessage(Long sessionId,
String role,
String content,
String think,
String toolName,
Integer totalToken,
String type,
String metadata,
long startTimestamp,
long endTimestamp) {
DaAgentMessage m = new DaAgentMessage();
m.setSessionId(sessionId);
m.setRole(role);
m.setContent(content);
m.setThink(think);
m.setToolName(toolName);
m.setTokens(totalToken);
m.setType(type);
m.setMetadata(metadata);
m.setStartTimestamp(startTimestamp);
m.setEndTimestamp(endTimestamp);
messageMapper.insert(m);
return m;
}
// 新增:按ID更新部分字段(例如补充endTimestamp、tokens等)
public int updateMessage(DaAgentMessage partial) {
if (partial.getMessageId() == null) {
throw new IllegalArgumentException("messageId is required for update");
}
return messageMapper.updateById(partial);
}
public List<DaAgentMessage> listBySession(Long sessionId, int page, int pageSize, String role) {
int offset = Math.max(0, (page - 1) * pageSize);
return messageMapper.listBySession(sessionId, offset, pageSize, role);
}
public int countBySession(Long sessionId, String role) {
return messageMapper.countBySession(sessionId, role);
}
// 新增:一次性拉取该会话的所有消息(仅用户与助手角色)
public List<DaAgentMessage> listAllBySession(Long sessionId) {
// 复用现有SQL:传null role且limit给一个大值
return messageMapper.listBySession(sessionId, 0, Integer.MAX_VALUE, null);
}
// 提交反馈(评分、内容、时间)
public int submitFeedback(Long messageId, Integer rating, String content, Date feedbackAt) {
DaAgentMessage partial = new DaAgentMessage();
partial.setMessageId(messageId);
partial.setFeedbackRating(rating);
partial.setFeedbackContent(content);
partial.setFeedbackAt(feedbackAt);
return messageMapper.updateById(partial);
}
// 便捷:毫秒时间戳版本(0或null不更新)
public int submitFeedback(Long messageId, Integer rating, String content, Long feedbackAtMs) {
Date at = (feedbackAtMs != null && feedbackAtMs != 0) ? new Date(feedbackAtMs) : null;
return submitFeedback(messageId, rating, content, at);
}
// 设置收藏状态
public int setCollected(Long messageId, Boolean collected) {
DaAgentMessage partial = new DaAgentMessage();
partial.setMessageId(messageId);
partial.setIsCollected(collected);
return messageMapper.updateById(partial);
}
public DaAgentMessage findLastUserMessage(Long sessionId) {
return messageMapper.findLastByRole(sessionId, "user");
}
public DaAgentMessage findLastAssistantMessage(Long sessionId) {
return messageMapper.findLastByRole(sessionId, "assistant");
}
public int deleteLastAssistantMessage(Long sessionId) {
return messageMapper.deleteLastAssistantBySession(sessionId);
}
}
package pro.spss.server.agent.service.prompt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* 通用提示词构建服务:
* - 可对单个提示词按配置添加 /no_think 前缀
* - TODO 可从提示词数组中按索引选择一个提示词,并按配置添加前缀
*/
@Service
public class PromptBuilderService {
@Value("${agent.api.think:false}")
private boolean agentApiThink;
/**
* 为单个提示词添加必要的前缀(若 agentApiThink=true 则添加 /no_think)。
*/
public String build(String prompt) {
if (prompt == null) return "";
return agentApiThink ? prompt : "/no_think\n" + prompt;
}
}
package pro.spss.server.agent.service.sessionService;
import com.alibaba.fastjson2.JSONArray;
import lombok.Getter;
import org.springframework.stereotype.Component;
import pro.spss.server.agent.domain.constant.ChatConstants;
import pro.spss.server.agent.domain.enums.ConversationStateEnum;
import pro.spss.server.agent.domain.enums.CreateWayEnum;
import pro.spss.server.agent.domain.request.RequestParams;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 会话管理
*/
@Component
public class ChatSessionManager {
@Getter
private final Map<String, JSONArray> chatSessions = new ConcurrentHashMap<>();
private final Map<String, RequestParams> sessionTaskMap = new ConcurrentHashMap<>();
public JSONArray getMessages(String sessionId) {
return chatSessions.get(sessionId);
}
public void initSession(String sessionId, CreateWayEnum createWay) {
JSONArray messages = new JSONArray(ChatConstants.SYSTEM_MESSAGES);
chatSessions.put(sessionId, messages);
RequestParams params = new RequestParams();
params.setCreateWay(createWay.getCode());
params.setState(ConversationStateEnum.INTENT_RECOGNITION);
sessionTaskMap.put(sessionId, params);
}
public RequestParams getRequestParams(String sessionId) {
if (sessionId == null || sessionId.isBlank()) {
return null;
}
// 确保在未显式 initSession 的情况下也能获取到会话上下文,避免 NPE
return sessionTaskMap.computeIfAbsent(sessionId, k -> {
RequestParams params = new RequestParams();
params.setCreateWay(CreateWayEnum.ALGO_FIRST.getCode());
params.setState(ConversationStateEnum.INTENT_RECOGNITION);
return params;
});
}
public void clear(String sessionId) {
chatSessions.remove(sessionId);
sessionTaskMap.remove(sessionId);
}
}
package pro.spss.server.agent.service.sseService;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import pro.spss.server.agent.domain.enums.ChatStatusEnum;
import pro.spss.server.agent.domain.response.ResponseMessage;
public interface SseService {
SseEmitter connect();
String sendResult(String userId, String userToken, JSONObject jsonObject);
String sendMessage(String type, String userId, String userToken, String message);
String sendMessage(String userId, String userToken, ResponseMessage message);
String sendMessage(String userId, String userToken, ChatStatusEnum message);
String sendMessage(String userId, String userToken, String message);
}
package pro.spss.server.agent.service.taskExecutor;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import java.io.IOException;
public interface TaskExecutor {
ResponseMessage executeTask(RequestParams requestParams, String token) throws IOException;
}
package pro.spss.server.agent.service.taskExecutor;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import pro.spss.server.agent.domain.DTO.TaskDTO;
import pro.spss.server.agent.domain.entity.ComputeResult;
import pro.spss.server.agent.domain.enums.ChatStatusEnum;
import pro.spss.server.agent.domain.enums.CreateWayEnum;
import pro.spss.server.agent.domain.enums.ResponseMessageType;
import pro.spss.server.agent.domain.enums.TaskStatusEnum;
import pro.spss.server.agent.domain.pojo.TaskMessage;
import pro.spss.server.agent.domain.request.RequestParams;
import pro.spss.server.agent.domain.response.ResponseMessage;
import pro.spss.server.agent.domain.response.Result;
import pro.spss.server.agent.mapper.ResultMapper;
import pro.spss.server.agent.service.feign.TaskServiceClient;
import pro.spss.server.agent.service.sseService.SseService;
import pro.spss.server.agent.utils.AlgoParamAutoGenerator;
import pro.spss.server.agent.utils.JsonUtil;
import pro.spss.server.agent.utils.TxtFileReader;
import java.io.File;
import java.util.List;
@Slf4j
@Service
public class TaskExecutorImpl implements TaskExecutor {
@Autowired
private SseService sseService;
@Autowired
private TaskServiceClient taskServiceClient;
@Autowired
private ResultMapper resultMapper;
@Value("${path.analysis-result}")
private String rootPath;
@Override
public ResponseMessage executeTask(RequestParams requestParams, String token) {
// 若参数为空,尝试基于数据概要和算法ID自动生成一份默认参数
if ((requestParams.getParams() == null || requestParams.getParams().isEmpty()) && requestParams.getAlgoId() != null) {
JSONArray autoParams = AlgoParamAutoGenerator.generateDefaults(requestParams.getAlgoId(), requestParams.getDataSummary());
if (!autoParams.isEmpty()) {
requestParams.setParams(autoParams);
log.info("Auto generated default params for algoId={} size={}", requestParams.getAlgoId(), autoParams.size());
}
}
TaskDTO taskDTO = TaskDTO.builder()
.algoId(requestParams.getAlgoId())
.algoName(requestParams.getAlgoName())
.dataId(requestParams.getDataId())
.dataVersionId(requestParams.getDataVersionId())
.createWay(CreateWayEnum.DATA_FIRST)
.params(requestParams.getParams())
.build();
taskDTO.setTaskPriority(1);//TODO
log.info(taskDTO.toString());
Result taskResult = taskServiceClient.callExec(taskDTO, token);
log.info("任务提交结果:{}", taskResult);
if (taskResult == null || !taskResult.getSuccess()) {
// sseService.sendMessage(Authenticator.currentUserId(), token, ChatStatusEnum.ALGORUNFAILED);
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(ChatStatusEnum.EXECUTOR_FAILED.getCode());
responseMessage.setMessage(ChatStatusEnum.EXECUTOR_FAILED.getMessage() + (taskResult != null && taskResult.getMessage() != null ? taskResult.getMessage() : ""));
responseMessage.setResponse(ChatStatusEnum.EXECUTOR_FAILED.getMessage());
responseMessage.setType(ResponseMessageType.RESULT.getType());
// SseServer.sendMessage(token, ChatStatusEnum.ALGORUNFAILED.toResult());
return responseMessage;
}
return waitForTaskResult(taskResult, token);
}
private ResponseMessage waitForTaskResult(Result taskResult, String token) {
int maxAttempts = 6;
long interval = 5000;
for (int i = 0; i < maxAttempts; i++) {
try {
TaskMessage taskMessage = resultMapper.getTaskMessage(taskResult.getData().toString());
if (taskMessage == null) {
Thread.sleep(interval);
continue;
}
if (taskMessage.getTaskStatus() == TaskStatusEnum.VALIDATION_FAILED.getCode()
|| taskMessage.getTaskStatus() == TaskStatusEnum.FAILED.getCode()) {
String resultPath = rootPath + taskMessage.getResultPath();
File resultFile = new File(resultPath);
if (resultFile.exists()) {
String errorFile = TxtFileReader.readSystemFile(resultPath);
JSONObject resultJson = new JSONObject();
resultJson.put("taskStatus", taskMessage.getTaskStatus());
resultJson.put("taskId", taskMessage.getTaskId());
resultJson.put("taskName", taskMessage.getTaskName());
resultJson.put("algoId", taskMessage.getAlgoId());
resultJson.put("resultJson", errorFile);
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(ChatStatusEnum.ALGORUNFAILED.getCode());
responseMessage.setType(ResponseMessageType.RESULT.getType());
responseMessage.setResult(resultJson);
responseMessage.setMessage("执行结果");
return responseMessage;
}
}
if (taskMessage.getTaskStatus() == TaskStatusEnum.SUCCESS.getCode()
|| taskMessage.getTaskStatus() == TaskStatusEnum.SUCCESS_WITH_WARNING.getCode()) {
String resultPath = rootPath + taskMessage.getResultPath();
File resultFile = new File(resultPath);
if (resultFile.exists()) {
List<ComputeResult> computeResults = JsonUtil.readJsonFromFile(resultPath);
log.debug(computeResults.toString());
JSONObject resultJson = new JSONObject();
resultJson.put("resultJson", computeResults);
resultJson.put("taskId", taskMessage.getTaskId());
resultJson.put("taskStatus", taskMessage.getTaskStatus());
resultJson.put("taskName", taskMessage.getTaskName());
resultJson.put("algoId", taskMessage.getAlgoId());
log.debug(resultJson.toString());
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(200);
responseMessage.setType(ResponseMessageType.RESULT.getType());
responseMessage.setResult(resultJson);
responseMessage.setMessage("执行结果");
// sseService.sendResult(Authenticator.currentUserId(), token, resultJson);
return responseMessage;
}
}
Thread.sleep(interval*i/2 + interval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(ChatStatusEnum.ALGORUNFAILED.getCode());
responseMessage.setMessage(ChatStatusEnum.ALGORUNFAILED.getMessage());
responseMessage.setResponse(ChatStatusEnum.ALGORUNFAILED.getMessage());
responseMessage.setType(ResponseMessageType.RESULT.getType());
e.printStackTrace();
return responseMessage;
} catch (Exception e) {
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(ChatStatusEnum.ALGORUNFAILED.getCode());
responseMessage.setMessage(ChatStatusEnum.ALGORUNFAILED.getMessage());
responseMessage.setResponse(ChatStatusEnum.ALGORUNFAILED.getMessage());
responseMessage.setType(ResponseMessageType.RESULT.getType());
e.printStackTrace();
return responseMessage;
}
}
// sseService.sendMessage(Authenticator.currentUserId(), token, ChatStatusEnum.NORESULT);
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setCode(ChatStatusEnum.NORESULT.getCode());
responseMessage.setMessage(ChatStatusEnum.NORESULT.getMessage());
responseMessage.setResponse(ChatStatusEnum.NORESULT.getMessage());
responseMessage.setType(ResponseMessageType.RESULT.getType());
return responseMessage;
}
}
package pro.spss.server.agent.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/**
* @author:wn
* @Desc
* @create: 2025-11-12-11:23
**/
@Component
public class AgentFileReader {
@Value("${path.csv-file}")
private String csvFileBasePath;
public String readCsvFileByDataId(String dataId, String userId) {
if (dataId == null || dataId.trim().isEmpty()) {
throw new IllegalArgumentException("dataId不能为空");
}
String filePath = buildFilePath(dataId, userId);
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
int lineCount = 0;
while ((line = reader.readLine()) != null && lineCount < 4) { // 表头 + 前三行数据
if (lineCount > 0) {
result.append("/n"); // 行间分隔符
}
String[] columns = line.split(",");
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
result.append("/t"); // 列间分隔符
}
result.append(columns[i]);
}
lineCount++;
}
} catch (IOException e) {
throw new RuntimeException("读取CSV文件失败:" + e.getMessage(), e);
}
return result.toString();
}
private String buildFilePath(String dataId, String userId) {
String basePath = csvFileBasePath.endsWith("/") ? csvFileBasePath : csvFileBasePath + "/";
return basePath + userId + File.separator + dataId + "/data.csv";
}
}
\ No newline at end of file
package pro.spss.server.agent.utils;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
/**
* 根据算法ID与数据概要自动生成一份基础参数配置(默认值)。
* 注意:这里的参数示例需要后续根据真实算法接口进行补充和调整。
*/
public class AlgoParamAutoGenerator {
public static JSONArray generateDefaults(String algoId, String dataSummary) {
JSONArray arr = new JSONArray();
if (algoId == null) {
return arr; // 无算法ID不生成
}
switch (algoId) {
case "1": // 最小二乘回归 示例参数
arr.add(makeParam("target", guessTargetColumn(dataSummary), "string", "因变量列名"));
arr.add(makeParam("features", guessFeatureColumns(dataSummary), "array", "自变量列集合"));
break;
case "9": // 决策树 示例参数
arr.add(makeParam("target", guessTargetColumn(dataSummary), "string", "分类目标列"));
arr.add(makeParam("maxDepth", "6", "int", "最大树深度"));
arr.add(makeParam("minSamplesSplit", "2", "int", "最小分裂样本数"));
break;
case "10": // 随机森林 示例参数
arr.add(makeParam("target", guessTargetColumn(dataSummary), "string", "分类/回归目标列"));
arr.add(makeParam("nEstimators", "100", "int", "树的数量"));
arr.add(makeParam("maxFeatures", "auto", "string", "最大特征数策略"));
break;
case "13": // K均值聚类 示例参数
arr.add(makeParam("k", "5", "int", "聚类簇数量"));
arr.add(makeParam("init", "k-means++", "string", "初始化方法"));
break;
case "16": // 因子分析 示例参数
arr.add(makeParam("nFactors", "3", "int", "提取因子数"));
arr.add(makeParam("rotation", "varimax", "string", "旋转方法"));
break;
case "17": // 主成分分析 示例参数
arr.add(makeParam("nComponents", "5", "int", "保留主成分数"));
arr.add(makeParam("whiten", "false", "bool", "是否白化"));
break;
default:
// 通用占位参数
arr.add(makeParam("target", guessTargetColumn(dataSummary), "string", "目标列(如适用)"));
arr.add(makeParam("features", guessFeatureColumns(dataSummary), "array", "特征列集合(如适用)"));
}
return arr;
}
private static JSONObject makeParam(String name, Object value, String type, String desc) {
JSONObject obj = new JSONObject();
obj.put("name", name);
obj.put("value", value);
obj.put("type", type);
obj.put("desc", desc);
return obj;
}
// 简单启发式:取概要首列为目标列(若包含"类型"或"分类"等文字可再优化)
private static String guessTargetColumn(String dataSummary) {
if (dataSummary == null) return "";
String[] lines = dataSummary.split("\n");
for (String l : lines) {
if (l.matches("\\d+\\. .+\\(类型:.*")) {
// 行格式: 序号. 列名 (类型:xxx) 示例:...
int dotIdx = l.indexOf('.') + 1;
int typeIdx = l.indexOf("(类型:");
if (dotIdx > 0 && typeIdx > dotIdx) {
return l.substring(dotIdx, typeIdx).trim();
}
}
}
return "";
}
// 简单启发式:返回除目标列外的其余列名列表(逗号分隔字符串)
private static String guessFeatureColumns(String dataSummary) {
if (dataSummary == null) return "";
String target = guessTargetColumn(dataSummary);
StringBuilder sb = new StringBuilder();
String[] lines = dataSummary.split("\n");
for (String l : lines) {
if (l.matches("\\d+\\. .+\\(类型:.*")) {
int dotIdx = l.indexOf('.') + 1;
int typeIdx = l.indexOf("(类型:");
if (dotIdx > 0 && typeIdx > dotIdx) {
String col = l.substring(dotIdx, typeIdx).trim();
if (!col.equals(target)) {
if (sb.length() > 0) sb.append(',');
sb.append(col);
}
}
}
}
return sb.toString();
}
}
package pro.spss.server.agent.utils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* 数据概要/字段类型快速生成工具。
* 输入: 由 AgentFileReader 返回的原始样本内容,行之间用 /n 分隔,列之间用 /t 分隔。
* 只解析前若干行样本生成列类型与示例,供大模型上下文嵌入,避免超长数据直接送入。
*/
public class DataSummaryUtil {
private static final Pattern NUMERIC_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$");
private static final int MAX_SAMPLE_VALUES_PER_COLUMN = 3;
/**
* 将 AgentFileReader 返回的样本原文转换为简明的数据概要文本。
*/
public static String buildSummary(String rawSample) {
if (rawSample == null || rawSample.isBlank()) {
return "(无可用数据样本)";
}
// 分行(注意:AgentFileReader 使用的是字符串“/n”而非换行符)
String[] lines = rawSample.split("/n");
if (lines.length == 0) {
return "(无数据行)";
}
// 表头(同理,使用字符串“/t”分隔)
String[] headers = lines[0].split("/t");
int colCount = headers.length;
// 收集每列样本值 & 类型判断
List<List<String>> columnValues = new ArrayList<>();
for (int i = 0; i < colCount; i++) {
columnValues.add(new ArrayList<>());
}
int dataRowCount = 0;
for (int i = 1; i < lines.length; i++) {
String[] cols = lines[i].split("/t", -1);
for (int c = 0; c < colCount && c < cols.length; c++) {
if (columnValues.get(c).size() < MAX_SAMPLE_VALUES_PER_COLUMN) {
String v = cols[c];
if (v != null && !v.isBlank()) {
columnValues.get(c).add(v);
}
}
}
dataRowCount++;
}
// 生成概要文本
StringBuilder sb = new StringBuilder();
sb.append("【数据概要】\n");
sb.append("列数:").append(colCount).append(" 样本行:").append(dataRowCount).append("\n");
sb.append("列信息:\n");
for (int i = 0; i < colCount; i++) {
List<String> samples = columnValues.get(i);
String type = inferType(samples);
sb.append(i + 1).append(". ").append(headers[i]).append(" (类型:").append(type).append(") 示例:");
if (samples.isEmpty()) {
sb.append("无");
} else {
sb.append(String.join(", ", samples));
}
sb.append("\n");
}
// 粗略算法方向建议
boolean hasNumeric = hasType(columnValues, "numeric");
boolean hasCategorical = hasType(columnValues, "categorical");
sb.append("可能适用的算法方向: ");
if (hasNumeric && hasCategorical) {
sb.append("分类(逻辑回归, 决策树, 随机森林), 回归(线性回归, 梯度提升), 特征工程(标准化, 缺失值处理)\n");
} else if (hasNumeric) {
sb.append("回归(最小二乘, 支持向量回归), 主成分/因子分析, 时间序列(AR/MA/ARMA)\n");
} else if (hasCategorical) {
sb.append("聚类(K均值/层次), 关联规则挖掘, 编码转换(哑编码, 二值化)\n");
} else {
sb.append("文本/灰色模型或需进一步的数据预处理\n");
}
return sb.toString();
}
private static String inferType(List<String> samples) {
if (samples == null || samples.isEmpty()) {
return "unknown";
}
boolean allNumeric = samples.stream().allMatch(v -> NUMERIC_PATTERN.matcher(v).matches());
if (allNumeric) {
return "numeric";
}
// 判断离散: 不重复值数量与样本数量比值
Set<String> distinct = new HashSet<>(samples);
if (distinct.size() <= 5 && distinct.size() <= samples.size()) {
return "categorical";
}
return "text";
}
private static boolean hasType(List<List<String>> columnValues, String targetType) {
for (List<String> samples : columnValues) {
String type = inferType(samples);
if (type.equals(targetType)) return true;
}
return false;
}
}
package pro.spss.server.agent.utils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper;
import pro.spss.server.agent.domain.entity.AiChatResponse;
import java.util.ArrayList;
public class JsonToObjectConverter {
public static AiChatResponse convertJsonToObject(String jsonString) {
AiChatResponse aiChatResponse = new AiChatResponse();
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(jsonString, AiChatResponse.class);
} catch (JsonParseException e) {
System.out.println("JSON转换失败");
aiChatResponse.setResponse(jsonString);
aiChatResponse.setRecommendedAlgorithms(new ArrayList<>());
return aiChatResponse;
} catch (Exception e) {
e.printStackTrace();
aiChatResponse.setResponse("似乎出现了点问题,我的大脑有点出小差了。");
aiChatResponse.setRecommendedAlgorithms(new ArrayList<>());
return aiChatResponse;
}
}
}
package pro.spss.server.agent.utils;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import pro.spss.server.agent.domain.entity.ComputeResult;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
public class JsonUtil {
/**
* 从指定的文件路径读取 JSON 文件并解析为 List<Result>,且 List 中只有一个 Result 对象。
*
* @param filePath 要读取的 JSON 文件路径
* @return 解析后的 List<Result> 对象
*/
public static List<ComputeResult> readJsonFromFile(String filePath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
StringWriter stringWriter = new StringWriter();
String line;
while ((line = reader.readLine()) != null) {
stringWriter.write(line);
}
String jsonContent = stringWriter.toString();
return JSONArray.parseArray(jsonContent, ComputeResult.class);
}
}
public static String readJsonFileToString(String filePath) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
}
return content.toString();
}
private static final ObjectMapper objectMapper = new ObjectMapper();
public static boolean isValidJson(String jsonString) {
// 基础检查:非空且以{或[开头
if (jsonString == null || jsonString.trim().isEmpty()) {
return false;
}
String trimmed = jsonString.trim();
if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
return false;
}
try {
JsonNode jsonNode = objectMapper.readTree(jsonString);
return jsonNode != null; // 解析成功且不为null
} catch (JsonProcessingException e) {
System.err.println("JSON解析错误: " + e.getMessage());
return false;
} catch (Exception e) {
return false; // 其他异常也视为无效JSON
}
}
}
This diff is collapsed.
This diff is collapsed.
server:
port: 6001
spring:
application:
name: smart-ciecc
profiles:
active: wn
agent:
handler_result: true #是否处理agent返回结果,true表示处理,false表示不处理
model_name: "Qwen3-32B"
init_prompt: "您好,我是数据分析机器人,根据您上传的数据和需求为您推荐合适的算法和参数配置,请问您想分析什么?"
intent_history_limit: 5
logging:
level:
# 业务代码包日志级别(可改为 INFO/DEBUG/WARN/ERROR)
pro.spss.agent: DEBUG
# MyBatis 映射与SQL日志(按需开启)
org.mybatis: INFO
org.springframework.web: INFO
# 可选:指定日志文件与模式(若使用 logback-spring.xml,此项由 logback 管理)
# file:
# name: logs/ciecc-agent.log
# pattern:
# console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment