mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-20 18:15:56 +08:00
update snail-ai 0.0.5
This commit is contained in:
parent
5a74eb85c3
commit
f508ed5c57
16
pom.xml
16
pom.xml
@ -33,7 +33,7 @@
|
|||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<dynamic-ds.version>4.5.0</dynamic-ds.version>
|
<dynamic-ds.version>4.5.0</dynamic-ds.version>
|
||||||
<snailjob.version>2.0.0</snailjob.version>
|
<snailjob.version>2.0.0</snailjob.version>
|
||||||
<snailai.version>0.0.4</snailai.version>
|
<snailai.version>0.0.5</snailai.version>
|
||||||
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
|
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
|
||||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||||
<lombok.version>1.18.42</lombok.version>
|
<lombok.version>1.18.42</lombok.version>
|
||||||
@ -55,8 +55,7 @@
|
|||||||
<mica-mqtt.version>2.6.5</mica-mqtt.version>
|
<mica-mqtt.version>2.6.5</mica-mqtt.version>
|
||||||
<easy-es.version>3.0.2</easy-es.version>
|
<easy-es.version>3.0.2</easy-es.version>
|
||||||
<elasticsearch-client.version>7.17.28</elasticsearch-client.version>
|
<elasticsearch-client.version>7.17.28</elasticsearch-client.version>
|
||||||
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
|
<spring-ai.version>2.0.0</spring-ai.version>
|
||||||
<spring-ai.version>2.0.0-M8</spring-ai.version>
|
|
||||||
|
|
||||||
<!-- 插件版本 -->
|
<!-- 插件版本 -->
|
||||||
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
|
||||||
@ -310,10 +309,17 @@
|
|||||||
<version>${snailjob.version}</version>
|
<version>${snailjob.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Snail AI Agent 启动器 -->
|
<!-- Snail AI Agent 聊天室 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aizuda</groupId>
|
<groupId>com.aizuda</groupId>
|
||||||
<artifactId>snail-ai-agent-starter</artifactId>
|
<artifactId>snail-ai-agent-chat-starter</artifactId>
|
||||||
|
<version>${snailai.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Snail AI Agent 执行器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-agent-executor-starter</artifactId>
|
||||||
<version>${snailai.version}</version>
|
<version>${snailai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@ -32,8 +32,6 @@ snail-job:
|
|||||||
snail-ai:
|
snail-ai:
|
||||||
# 启用客户端模式
|
# 启用客户端模式
|
||||||
enabled: false
|
enabled: false
|
||||||
# 聊天发送模式: stream(流式) / sync(同步)
|
|
||||||
chat-mode: stream
|
|
||||||
# ==================== Server 连接 ====================
|
# ==================== Server 连接 ====================
|
||||||
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
||||||
server:
|
server:
|
||||||
@ -44,7 +42,7 @@ snail-ai:
|
|||||||
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
||||||
app-id: 1
|
app-id: 1
|
||||||
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
||||||
token: SAI_7c557fc05a304b0482a65ca67afdb0b8
|
token: SAI_566a6bfbc26e4998b4841cc927d50c5d
|
||||||
port: 18889
|
port: 18889
|
||||||
# Skill 文件临时目录
|
# Skill 文件临时目录
|
||||||
skill-temp-dir: /tmp/snail-ai-agent/skills
|
skill-temp-dir: /tmp/snail-ai-agent/skills
|
||||||
|
|||||||
@ -35,8 +35,6 @@ snail-job:
|
|||||||
snail-ai:
|
snail-ai:
|
||||||
# 启用客户端模式
|
# 启用客户端模式
|
||||||
enabled: false
|
enabled: false
|
||||||
# 聊天发送模式: stream(流式) / sync(同步)
|
|
||||||
chat-mode: stream
|
|
||||||
# ==================== Server 连接 ====================
|
# ==================== Server 连接 ====================
|
||||||
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
||||||
server:
|
server:
|
||||||
@ -47,7 +45,7 @@ snail-ai:
|
|||||||
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
||||||
app-id: 1
|
app-id: 1
|
||||||
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
||||||
token: SAI_7c557fc05a304b0482a65ca67afdb0b8
|
token: SAI_566a6bfbc26e4998b4841cc927d50c5d
|
||||||
port: 18889
|
port: 18889
|
||||||
# Skill 文件临时目录
|
# Skill 文件临时目录
|
||||||
skill-temp-dir: /tmp/snail-ai-agent/skills
|
skill-temp-dir: /tmp/snail-ai-agent/skills
|
||||||
|
|||||||
@ -109,6 +109,8 @@ security:
|
|||||||
- /*/api-docs
|
- /*/api-docs
|
||||||
- /*/api-docs/**
|
- /*/api-docs/**
|
||||||
- /warm-flow-ui/config
|
- /warm-flow-ui/config
|
||||||
|
- /snail-chat/**
|
||||||
|
- /api/snail/chat/**
|
||||||
|
|
||||||
# MyBatisPlus配置
|
# MyBatisPlus配置
|
||||||
# https://baomidou.com/config/
|
# https://baomidou.com/config/
|
||||||
|
|||||||
@ -23,10 +23,16 @@
|
|||||||
<artifactId>ruoyi-common-core</artifactId>
|
<artifactId>ruoyi-common-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Snail AI Agent 启动器 -->
|
<!-- Snail AI Agent 聊天室 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aizuda</groupId>
|
<groupId>com.aizuda</groupId>
|
||||||
<artifactId>snail-ai-agent-starter</artifactId>
|
<artifactId>snail-ai-agent-chat-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Snail AI Agent 执行器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-agent-executor-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Snail AI OpenAPI 启动器 -->
|
<!-- Snail AI OpenAPI 启动器 -->
|
||||||
|
|||||||
@ -0,0 +1,124 @@
|
|||||||
|
package org.dromara.common.ai.handler;
|
||||||
|
|
||||||
|
import com.aizuda.snail.ai.agent.chat.starter.SnailAiChatGatewayController;
|
||||||
|
import com.aizuda.snail.ai.common.execption.BaseSnailAiException;
|
||||||
|
import com.aizuda.snail.ai.common.execption.SnailAiAuthenticationException;
|
||||||
|
import com.aizuda.snail.ai.common.model.ModelCallException;
|
||||||
|
import com.aizuda.snail.ai.common.model.Result;
|
||||||
|
import jakarta.validation.ConstraintViolation;
|
||||||
|
import jakarta.validation.ConstraintViolationException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.MessageSourceResolvable;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snail AI Chat 网关异常处理器。
|
||||||
|
* <p>
|
||||||
|
* /api/snail/chat/** 接口由 Snail AI SDK 前端消费,响应结构需要保持 SDK Result(status/message/data),
|
||||||
|
* 不能落到 RuoYi 通用 R(code/msg/data) 格式。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@RestControllerAdvice(assignableTypes = SnailAiChatGatewayController.class)
|
||||||
|
public class SnailAiChatExceptionHandler {
|
||||||
|
|
||||||
|
private static final int AUTHENTICATION_ERROR_STATUS = 5001;
|
||||||
|
|
||||||
|
@ExceptionHandler(SnailAiAuthenticationException.class)
|
||||||
|
public Result<Void> handleAuthenticationException(SnailAiAuthenticationException e) {
|
||||||
|
log.warn("Snail AI Chat authentication failed: {}", e.getMessage());
|
||||||
|
return Result.fail(AUTHENTICATION_ERROR_STATUS, defaultMessage(e, "认证失败,请重新登录"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(BaseSnailAiException.class)
|
||||||
|
public Result<Void> handleSnailAiException(BaseSnailAiException e) {
|
||||||
|
log.warn("Snail AI Chat request failed: {}", e.getMessage());
|
||||||
|
return Result.fail(defaultMessage(e, "AI 服务请求失败,请稍后再试"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ModelCallException.class)
|
||||||
|
public Result<Void> handleModelCallException(ModelCallException e) {
|
||||||
|
log.warn("Snail AI Chat model call failed: {}", e.getMessage(), e);
|
||||||
|
return Result.fail(defaultMessage(e, "模型调用失败,请稍后再试"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({
|
||||||
|
BindException.class,
|
||||||
|
MethodArgumentNotValidException.class,
|
||||||
|
HandlerMethodValidationException.class
|
||||||
|
})
|
||||||
|
public Result<Void> handleValidationException(Exception e) {
|
||||||
|
log.warn("Snail AI Chat validation failed: {}", e.getMessage());
|
||||||
|
return Result.fail(validationMessage(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
|
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
|
||||||
|
log.warn("Snail AI Chat constraint validation failed: {}", e.getMessage());
|
||||||
|
String message = e.getConstraintViolations().stream()
|
||||||
|
.map(ConstraintViolation::getMessage)
|
||||||
|
.filter(this::hasText)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
return Result.fail(hasText(message) ? message : "请求参数校验失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
|
public Result<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||||
|
log.warn("Snail AI Chat request body parse failed: {}", e.getMessage());
|
||||||
|
return Result.fail("请求参数格式错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
|
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||||
|
log.warn("Snail AI Chat illegal argument: {}", e.getMessage());
|
||||||
|
return Result.fail(defaultMessage(e, "请求参数不合法"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IllegalStateException.class)
|
||||||
|
public Result<Void> handleIllegalStateException(IllegalStateException e) {
|
||||||
|
log.warn("Snail AI Chat illegal state: {}", e.getMessage());
|
||||||
|
return Result.fail(defaultMessage(e, "AI 会话状态异常,请刷新后重试"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public Result<Void> handleException(Exception e) {
|
||||||
|
log.error("Snail AI Chat unexpected exception", e);
|
||||||
|
return Result.fail("AI 服务异常,请稍后再试");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String validationMessage(Exception e) {
|
||||||
|
Collection<? extends MessageSourceResolvable> errors;
|
||||||
|
if (e instanceof BindException bindException) {
|
||||||
|
errors = bindException.getAllErrors();
|
||||||
|
} else if (e instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
|
||||||
|
errors = methodArgumentNotValidException.getBindingResult().getAllErrors();
|
||||||
|
} else if (e instanceof HandlerMethodValidationException handlerMethodValidationException) {
|
||||||
|
errors = handlerMethodValidationException.getAllErrors();
|
||||||
|
} else {
|
||||||
|
return "请求参数校验失败";
|
||||||
|
}
|
||||||
|
String message = errors.stream()
|
||||||
|
.map(MessageSourceResolvable::getDefaultMessage)
|
||||||
|
.filter(this::hasText)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
return hasText(message) ? message : "请求参数校验失败";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultMessage(Throwable e, String fallback) {
|
||||||
|
return hasText(e.getMessage()) ? e.getMessage() : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasText(String value) {
|
||||||
|
return value != null && !value.isBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,33 +2,19 @@ package org.dromara.ai.controller;
|
|||||||
|
|
||||||
import com.aizuda.snail.ai.common.execption.SnailAiException;
|
import com.aizuda.snail.ai.common.execption.SnailAiException;
|
||||||
import com.aizuda.snail.ai.common.model.Result;
|
import com.aizuda.snail.ai.common.model.Result;
|
||||||
import com.aizuda.snail.ai.common.openapi.dto.*;
|
import com.aizuda.snail.ai.common.openapi.dto.OpenApiUserRegisterRequest;
|
||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiAgentClient;
|
import com.aizuda.snail.ai.common.openapi.dto.OpenApiUserVO;
|
||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiChatClient;
|
|
||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiConversationClient;
|
|
||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
|
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
|
||||||
import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.validation.constraints.Min;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.web.core.BaseController;
|
import org.dromara.common.web.core.BaseController;
|
||||||
import org.dromara.system.api.model.LoginUser;
|
import org.dromara.system.api.model.LoginUser;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snail AI OpenAPI 控制器
|
* Snail AI OpenAPI 控制器
|
||||||
@ -47,20 +33,7 @@ public class SnailAiController extends BaseController {
|
|||||||
* Snail AI 成功状态码。
|
* Snail AI 成功状态码。
|
||||||
*/
|
*/
|
||||||
private static final int SNAIL_AI_SUCCESS = 1;
|
private static final int SNAIL_AI_SUCCESS = 1;
|
||||||
/**
|
|
||||||
* SSE 超时时间。
|
|
||||||
*/
|
|
||||||
private static final long SSE_TIMEOUT = 300000L;
|
|
||||||
|
|
||||||
private final OpenApiAgentClient agentClient;
|
|
||||||
private final OpenApiChatClient chatClient;
|
|
||||||
private final OpenApiConversationClient conversationClient;
|
|
||||||
private final OpenApiUserClient userClient;
|
private final OpenApiUserClient userClient;
|
||||||
/**
|
|
||||||
* 聊天模式。
|
|
||||||
*/
|
|
||||||
@Value("${snail-ai.chat-mode:stream}")
|
|
||||||
private String chatMode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册当前登录用户并返回 OpenAPI 用户信息。
|
* 注册当前登录用户并返回 OpenAPI 用户信息。
|
||||||
@ -70,272 +43,6 @@ public class SnailAiController extends BaseController {
|
|||||||
return R.ok(ensureOpenApiUser());
|
return R.ok(ensureOpenApiUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询当前登录用户对应的 OpenAPI 用户信息。
|
|
||||||
*/
|
|
||||||
@GetMapping("/user")
|
|
||||||
public R<OpenApiUserVO> getUser() {
|
|
||||||
OpenApiUserQueryRequest request = new OpenApiUserQueryRequest();
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
return toR(userClient.getUser(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询当前用户可访问的智能体列表。
|
|
||||||
*/
|
|
||||||
@GetMapping("/agents")
|
|
||||||
public R<List<OpenApiAgentVO>> listAgents() {
|
|
||||||
return toR(agentClient.listAgents());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据智能体 ID 查询智能体详情。
|
|
||||||
*/
|
|
||||||
@GetMapping("/agent/{agentId}")
|
|
||||||
public R<OpenApiAgentVO> getAgent(@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId) {
|
|
||||||
OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest();
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
return toR(agentClient.getAgent(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为指定智能体创建新会话。
|
|
||||||
*/
|
|
||||||
@PostMapping("/agent/{agentId}/conversation")
|
|
||||||
public R<OpenApiConversationVO> createConversation(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@RequestBody OpenApiCreateConversationRequest request) {
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
return toR(conversationClient.createConversation(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询指定智能体下的会话列表。
|
|
||||||
*/
|
|
||||||
@GetMapping("/agent/{agentId}/conversations")
|
|
||||||
public R<PageResult<OpenApiConversationVO>> listConversations(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@Min(value = 1, message = "页码不能小于1") @RequestParam(defaultValue = "1") int page,
|
|
||||||
@Min(value = 1, message = "每页条数不能小于1") @RequestParam(defaultValue = "10") int size) {
|
|
||||||
OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest();
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
request.setPage(page);
|
|
||||||
request.setSize(size);
|
|
||||||
return toPageR(conversationClient.listConversations(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询指定会话的消息历史。
|
|
||||||
*/
|
|
||||||
@GetMapping("/agent/{agentId}/conversation/{conversationId}/messages")
|
|
||||||
public R<List<OpenApiMessageVO>> getMessages(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@NotBlank(message = "会话ID不能为空") @PathVariable String conversationId) {
|
|
||||||
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setConversationId(conversationId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
return toR(conversationClient.getMessages(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除指定会话。
|
|
||||||
*/
|
|
||||||
@DeleteMapping("/agent/{agentId}/conversation/{conversationId}")
|
|
||||||
public R<Void> deleteConversation(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@NotBlank(message = "会话ID不能为空") @PathVariable String conversationId) {
|
|
||||||
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setConversationId(conversationId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
return toR(conversationClient.deleteConversation(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前聊天发送模式。
|
|
||||||
*/
|
|
||||||
@GetMapping("/chat/mode")
|
|
||||||
public R<Map<String, String>> getChatMode() {
|
|
||||||
String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream";
|
|
||||||
return R.ok(Map.of("mode", mode));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步对话接口。
|
|
||||||
*/
|
|
||||||
@PostMapping("/agent/{agentId}/chat/sync")
|
|
||||||
public R<OpenApiChatSyncResponse> chatSync(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@RequestBody OpenApiChatRequest request) {
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
log.info("Sync chat request: agentId={}", agentId);
|
|
||||||
return toR(chatClient.chatSync(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 流式对话接口,按 SSE 事件返回消息分片。
|
|
||||||
*/
|
|
||||||
@PostMapping(value = "/agent/{agentId}/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
||||||
public SseEmitter chatStream(
|
|
||||||
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
|
||||||
@RequestBody OpenApiChatRequest request,
|
|
||||||
HttpServletResponse response) {
|
|
||||||
prepareSseResponse(response);
|
|
||||||
SseEmitter emitter = new SseEmitter(SSE_TIMEOUT);
|
|
||||||
AtomicBoolean closed = new AtomicBoolean(false);
|
|
||||||
emitter.onTimeout(() -> {
|
|
||||||
if (!closed.get()) {
|
|
||||||
safeSend(emitter, closed, "error", "SSE stream timeout");
|
|
||||||
safeSend(emitter, closed, "done", "");
|
|
||||||
if (closed.compareAndSet(false, true)) {
|
|
||||||
emitter.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
emitter.onCompletion(() -> closed.set(true));
|
|
||||||
emitter.onError(error -> {
|
|
||||||
closed.set(true);
|
|
||||||
log.warn("SSE emitter error: {}", error.getMessage());
|
|
||||||
});
|
|
||||||
|
|
||||||
request.setAgentId(agentId);
|
|
||||||
request.setOpenId(ensureOpenId());
|
|
||||||
|
|
||||||
log.info("Stream chat request: agentId={}", agentId);
|
|
||||||
try {
|
|
||||||
chatClient.chatStream(request, new SseEventListener() {
|
|
||||||
/**
|
|
||||||
* 推送普通文本片段。
|
|
||||||
*
|
|
||||||
* @param text 文本片段
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onText(String text) {
|
|
||||||
safeSend(emitter, closed, "text", text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 推送思考内容片段。
|
|
||||||
*
|
|
||||||
* @param thinking 思考内容
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onThinking(String thinking) {
|
|
||||||
safeSend(emitter, closed, "thinking", thinking);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理流式对话完成事件。
|
|
||||||
*
|
|
||||||
* @param data 完成数据
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onComplete(String data) {
|
|
||||||
if (!closed.get()) {
|
|
||||||
safeSend(emitter, closed, "done", data);
|
|
||||||
log.info("Stream chat completed");
|
|
||||||
if (closed.compareAndSet(false, true)) {
|
|
||||||
emitter.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理流式对话异常事件。
|
|
||||||
*
|
|
||||||
* @param errorMessage 错误信息
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onError(String errorMessage) {
|
|
||||||
log.error("Stream chat error: {}", errorMessage);
|
|
||||||
if (!closed.get()) {
|
|
||||||
safeSend(emitter, closed, "error", errorMessage);
|
|
||||||
safeSend(emitter, closed, "done", "");
|
|
||||||
if (closed.compareAndSet(false, true)) {
|
|
||||||
emitter.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Stream chat exception", e);
|
|
||||||
if (!closed.get()) {
|
|
||||||
safeSend(emitter, closed, "error", "stream exception: " + e.getMessage());
|
|
||||||
safeSend(emitter, closed, "done", "");
|
|
||||||
if (closed.compareAndSet(false, true)) {
|
|
||||||
emitter.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return emitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换 Snail AI 普通响应为项目统一响应。
|
|
||||||
*/
|
|
||||||
private <T> R<T> toR(Result<T> result) {
|
|
||||||
if (result == null) {
|
|
||||||
return R.fail("Snail AI 服务返回为空");
|
|
||||||
}
|
|
||||||
if (result.getStatus() != SNAIL_AI_SUCCESS) {
|
|
||||||
return R.fail(result.getMessage());
|
|
||||||
}
|
|
||||||
return R.ok(result.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换 Snail AI 分页响应为项目统一分页响应。
|
|
||||||
*/
|
|
||||||
private <T> R<PageResult<T>> toPageR(com.aizuda.snail.ai.common.model.PageResult<List<T>> result) {
|
|
||||||
R<List<T>> response = toR(result);
|
|
||||||
if (R.isError(response)) {
|
|
||||||
return R.fail(response.getMsg());
|
|
||||||
}
|
|
||||||
return R.ok(PageResult.build(response.getData(), result.getTotal()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 输出一条 SSE 事件。
|
|
||||||
*/
|
|
||||||
private boolean safeSend(SseEmitter emitter, AtomicBoolean closed, String event, String data) {
|
|
||||||
if (closed.get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
emitter.send(SseEmitter.event().name(event).data(data == null ? "" : data));
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
closed.set(true);
|
|
||||||
log.warn("SSE send failed, event={}", event, e);
|
|
||||||
emitter.completeWithError(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 SSE 响应头,覆盖统一鉴权成功路径中的默认 JSON 响应类型。
|
|
||||||
*
|
|
||||||
* @param response 当前响应
|
|
||||||
*/
|
|
||||||
private void prepareSseResponse(HttpServletResponse response) {
|
|
||||||
response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.setHeader("Cache-Control", "no-cache");
|
|
||||||
response.setHeader("X-Accel-Buffering", "no");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户对应的 openId,不存在时会自动注册。
|
|
||||||
*/
|
|
||||||
private String ensureOpenId() {
|
|
||||||
return ensureOpenApiUser().getOpenId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保当前登录用户已注册为 OpenAPI 用户。
|
* 确保当前登录用户已注册为 OpenAPI 用户。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -49,6 +49,10 @@ http {
|
|||||||
server 127.0.0.1:8800;
|
server 127.0.0.1:8800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream snailai-server {
|
||||||
|
server 127.0.0.1:8900;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
@ -147,6 +151,28 @@ http {
|
|||||||
proxy_pass http://snailjob-server/snail-job/;
|
proxy_pass http://snailjob-server/snail-job/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /snail-ai/ {
|
||||||
|
# 设置客户端请求头中的 Host 信息(保持原始 Host)
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
# 获取客户端真实 IP
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# 自定义头 REMOTE-HOST,记录客户端 IP
|
||||||
|
proxy_set_header REMOTE-HOST $remote_addr;
|
||||||
|
# 获取完整的客户端 IP 链(经过多级代理时)
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# 设置后端响应超时时间(这里是 24 小时,适合长连接/SSE)
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
# SSE (Server-Sent Events) 与 WebSocket 支持参数
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
# 禁用代理缓冲,直接传输给客户端
|
||||||
|
proxy_buffering off;
|
||||||
|
# 禁用代理缓存
|
||||||
|
proxy_cache off;
|
||||||
|
proxy_pass http://snailai-server/snail-ai/;
|
||||||
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
location = /50x.html {
|
location = /50x.html {
|
||||||
root html;
|
root html;
|
||||||
|
|||||||
@ -375,6 +375,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
|
|||||||
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '文件管理菜单');
|
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '文件管理菜单');
|
||||||
-- snail-job server控制台
|
-- snail-job server控制台
|
||||||
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 5, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'snailjob控制台菜单');
|
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 5, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'snailjob控制台菜单');
|
||||||
|
-- snail-ai server控制台
|
||||||
|
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'AI控制台菜单');
|
||||||
|
|
||||||
-- 三级菜单
|
-- 三级菜单
|
||||||
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '操作日志菜单');
|
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '操作日志菜单');
|
||||||
|
|||||||
@ -372,6 +372,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
|
|||||||
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '文件管理菜单');
|
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '文件管理菜单');
|
||||||
-- snail-job server控制台
|
-- snail-job server控制台
|
||||||
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'SnailJob控制台菜单');
|
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'SnailJob控制台菜单');
|
||||||
|
-- snail-ai server控制台
|
||||||
|
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'AI控制台菜单');
|
||||||
|
|
||||||
-- 三级菜单
|
-- 三级菜单
|
||||||
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '操作日志菜单');
|
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '操作日志菜单');
|
||||||
|
|||||||
@ -97,11 +97,6 @@ CREATE TABLE sai_user
|
|||||||
) ENGINE = InnoDB
|
) ENGINE = InnoDB
|
||||||
DEFAULT CHARSET = utf8mb4;
|
DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
-- 默认管理员:admin / admin123
|
|
||||||
INSERT INTO sai_user (id, role, username, email, password, create_dt, update_dt)
|
|
||||||
VALUES (1, 2, 'admin', '', 'pbkdf2$120000$c25haWwtYWktYWRtaW4tMQ==$kakglT/wYKOgv/77Ah1stie58d/JbY2nGgq5DwgUBw4=',
|
|
||||||
'2026-02-11 13:56:48.210429', '2026-02-11 13:56:48.210429');
|
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
-- 1. AI 模型提供商表
|
-- 1. AI 模型提供商表
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@ -134,6 +129,7 @@ CREATE TABLE IF NOT EXISTS sai_model_config
|
|||||||
model_name VARCHAR(255) NOT NULL COMMENT '模型名称',
|
model_name VARCHAR(255) NOT NULL COMMENT '模型名称',
|
||||||
model_key VARCHAR(100) NOT NULL COMMENT '模型标识符',
|
model_key VARCHAR(100) NOT NULL COMMENT '模型标识符',
|
||||||
model_type VARCHAR(50) NOT NULL COMMENT '模型类型(CHAT/EMBEDDING/RERANKER/IMAGE/SPEECH)',
|
model_type VARCHAR(50) NOT NULL COMMENT '模型类型(CHAT/EMBEDDING/RERANKER/IMAGE/SPEECH)',
|
||||||
|
adapter_key VARCHAR(100) COMMENT '底层协议适配器标识(openai-compatible/http等)',
|
||||||
description VARCHAR(1000) COMMENT '模型描述',
|
description VARCHAR(1000) COMMENT '模型描述',
|
||||||
api_key VARCHAR(1000) COMMENT 'API密钥(加密存储)',
|
api_key VARCHAR(1000) COMMENT 'API密钥(加密存储)',
|
||||||
api_endpoint VARCHAR(500) COMMENT 'API端点URL',
|
api_endpoint VARCHAR(500) COMMENT 'API端点URL',
|
||||||
@ -156,9 +152,6 @@ CREATE INDEX idx_is_default ON sai_model_config (is_default);
|
|||||||
CREATE INDEX idx_scope ON sai_model_config (scope);
|
CREATE INDEX idx_scope ON sai_model_config (scope);
|
||||||
CREATE INDEX idx_model_key ON sai_model_config (model_key);
|
CREATE INDEX idx_model_key ON sai_model_config (model_key);
|
||||||
|
|
||||||
INSERT INTO sai_model_config VALUES (1, 5, 'glm-5.1', 'glm-5.1', 'CHAT', '', 'xxx', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{\"frequencyPenalty\":0.0,\"maxTokens\":20000,\"presencePenalty\":0.0,\"stopSequences\":[],\"stream\":true,\"temperature\":0.7,\"timeoutMs\":300000,\"topK\":1,\"topP\":1.0}', NULL, 'GLOBAL', 1, 1, '2026-06-05 09:34:01', '2026-06-05 09:34:02');
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
-- 3. 模型使用统计表
|
-- 3. 模型使用统计表
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@ -186,19 +179,6 @@ CREATE INDEX idx_model_id ON sai_model_usage_stat (model_id);
|
|||||||
CREATE INDEX idx_user_id ON sai_model_usage_stat (user_id);
|
CREATE INDEX idx_user_id ON sai_model_usage_stat (user_id);
|
||||||
CREATE INDEX idx_last_used_dt ON sai_model_usage_stat (last_used_dt);
|
CREATE INDEX idx_last_used_dt ON sai_model_usage_stat (last_used_dt);
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- 初始化数据 (可选)
|
|
||||||
-- ============================================
|
|
||||||
-- 插入常见的AI提供商(重复 provider_key 则忽略)
|
|
||||||
INSERT IGNORE INTO sai_model_provider (provider_name, provider_key, description, is_enabled)
|
|
||||||
VALUES ('OpenAI', 'openai', 'OpenAI官方模型 (GPT-4, GPT-3.5等)', 1),
|
|
||||||
('Claude', 'claude', 'Anthropic Claude模型', 1),
|
|
||||||
('Ollama', 'ollama', '本地开源模型 (Llama, Mistral等)', 1),
|
|
||||||
('Google Gemini', 'gemini', 'Google Gemini模型', 1),
|
|
||||||
('阿里云百炼', 'qwen', '阿里云百炼 OpenAI 兼容模型 (Qwen等)', 1),
|
|
||||||
('DeepSeek', 'deepseek', 'DeepSeek OpenAI 兼容模型', 1),
|
|
||||||
('智谱AI', 'zhipu', '智谱AI OpenAI 兼容模型 (GLM等)', 1);
|
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
-- 智能体相关表
|
-- 智能体相关表
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@ -237,9 +217,6 @@ CREATE TABLE IF NOT EXISTS sai_agent
|
|||||||
CREATE INDEX idx_agent_creator ON sai_agent (creator_id);
|
CREATE INDEX idx_agent_creator ON sai_agent (creator_id);
|
||||||
CREATE INDEX idx_agent_featured ON sai_agent (is_featured);
|
CREATE INDEX idx_agent_featured ON sai_agent (is_featured);
|
||||||
|
|
||||||
INSERT INTO sai_agent VALUES (1, '智测先锋专家', '智测先锋专家是一款专注于软件测试与质量保障领域的智能助手。它能够高效生成覆盖全面的测试用例,深度分析Bug根因并提供修复建议,支持编写自动化测试脚本,以及解读复杂的测试报告。适用于软件开发周期的各个QA阶段,包括单元测试、接口测试、UI自动化及回归测试规划。其核心特点是逻辑严密、注重边界与异常场景,帮助团队大幅提升测试效率与软件质量。', NULL, '你是一位资深的软件测试与质量保障(QA)专家,名为“智测先锋专家”。\\n\\n【角色定位】你是开发团队的最后一道防线,致力于保障软件产品的卓越质量。\\n\\n【专业领域】精通黑盒与白盒测试、自动化测试框架(如Selenium、Pytest)、接口与性能测试、安全测试及CI/CD持续集成流程。\\n\\n【回答风格】逻辑严密、条理清晰、客观专业。善于使用结构化排版(如Markdown列表、代码块、表格)呈现测试用例和步骤,语言精炼,直击痛点。\\n\\n【行为指南】\\n1. 生成测试用例:必须覆盖正常流、异常流、边界值和兼容性等方面,确保测试的全面性与无遗漏。\\n2. 分析Bug根因:从代码逻辑、数据状态、环境配置等多维度推导,不仅给出修复建议,更要提供预防性的测试策略。\\n3. 编写自动化脚本:确保代码规范、包含必要注释与断言(Assert),并明确说明运行依赖与环境配置。\\n4. 需求澄清:若用户提问模糊,主动追问业务背景、技术栈等关键细节,拒绝给出宽泛且无实操价值的答案。\\n5. 风险预警:始终秉持质量第一理念,在解答中适时提示潜在的测试盲区与质量风险。', '你好!我是智测先锋专家,你的专属软件测试与质量保障顾问。无论是编写用例还是排查Bug,我都能为你提供专业支持!', '[\"如何为一个用户登录接口设计全面的测试用例?\",\"帮我分析这个空指针异常Bug的可能根因及修复建议。\",\"请提供一段Python的Pytest接口自动化测试脚本示例。\",\"怎样制定一个高效的回归测试策略?\"]', 2, 0, 0, 0, 0, 0, NULL, 1, 20, 1, 0, 1, 1, NULL, '1', '2026-06-05 09:34:50', '2026-06-05 09:34:51');
|
|
||||||
|
|
||||||
|
|
||||||
-- 智能体对话表
|
-- 智能体对话表
|
||||||
CREATE TABLE IF NOT EXISTS sai_agent_conversation
|
CREATE TABLE IF NOT EXISTS sai_agent_conversation
|
||||||
(
|
(
|
||||||
@ -436,11 +413,6 @@ CREATE TABLE IF NOT EXISTS sai_store_instance
|
|||||||
CREATE INDEX idx_store_instance_category ON sai_store_instance (category);
|
CREATE INDEX idx_store_instance_category ON sai_store_instance (category);
|
||||||
CREATE INDEX idx_store_instance_type ON sai_store_instance (type);
|
CREATE INDEX idx_store_instance_type ON sai_store_instance (type);
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- 记忆系统(配置 / 主表 / 历史 / 摘要 / 统计 / 提取进度)
|
|
||||||
-- 依赖:sai_store_instance(sai_memory_conversation 外键)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 客户端应用
|
-- 客户端应用
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
CREATE TABLE IF NOT EXISTS sai_app
|
CREATE TABLE IF NOT EXISTS sai_app
|
||||||
@ -459,9 +431,6 @@ CREATE TABLE IF NOT EXISTS sai_app
|
|||||||
DEFAULT CHARSET = utf8mb4
|
DEFAULT CHARSET = utf8mb4
|
||||||
COLLATE = utf8mb4_unicode_ci COMMENT = '客户端应用';
|
COLLATE = utf8mb4_unicode_ci COMMENT = '客户端应用';
|
||||||
|
|
||||||
INSERT INTO sai_app VALUES (1, '1', '测试', '', 'SAI_566a6bfbc26e4998b4841cc927d50c5d', 'LEAST_LOAD', 1, '2026-06-05 09:32:50', '2026-06-05 09:32:50');
|
|
||||||
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- AI客户端实例节点
|
-- AI客户端实例节点
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -503,6 +472,7 @@ CREATE TABLE sai_openapi_user
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
COMMENT='OpenAPI 外部用户映射表';
|
COMMENT='OpenAPI 外部用户映射表';
|
||||||
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- 通用资源存储
|
-- 通用资源存储
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -526,3 +496,30 @@ CREATE TABLE IF NOT EXISTS sai_resource
|
|||||||
) ENGINE = InnoDB
|
) ENGINE = InnoDB
|
||||||
DEFAULT CHARSET = utf8mb4
|
DEFAULT CHARSET = utf8mb4
|
||||||
COLLATE = utf8mb4_unicode_ci COMMENT = '通用资源存储';
|
COLLATE = utf8mb4_unicode_ci COMMENT = '通用资源存储';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 初始化数据
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 默认管理员:admin / admin123
|
||||||
|
INSERT INTO sai_user (id, role, username, email, password, create_dt, update_dt)
|
||||||
|
VALUES (1, 2, 'admin', '', 'pbkdf2$120000$c25haWwtYWktYWRtaW4tMQ==$kakglT/wYKOgv/77Ah1stie58d/JbY2nGgq5DwgUBw4=',
|
||||||
|
'2026-02-11 13:56:48.210429', '2026-02-11 13:56:48.210429');
|
||||||
|
|
||||||
|
-- 插入常见的AI提供商
|
||||||
|
INSERT INTO sai_model_provider (provider_name, provider_key, description, is_enabled)
|
||||||
|
VALUES ('OpenAI', 'openai', 'OpenAI官方模型 (GPT-4, GPT-3.5等)', 1),
|
||||||
|
('Claude', 'claude', 'Anthropic Claude模型', 1),
|
||||||
|
('Ollama', 'ollama', '本地开源模型 (Llama, Mistral等)', 1),
|
||||||
|
('Google Gemini', 'gemini', 'Google Gemini模型', 1),
|
||||||
|
('阿里云百炼', 'qwen', '阿里云百炼 OpenAI 兼容模型 (Qwen等)', 1),
|
||||||
|
('DeepSeek', 'deepseek', 'DeepSeek OpenAI 兼容模型', 1),
|
||||||
|
('智谱AI', 'zhipu', '智谱AI OpenAI 兼容模型 (GLM等)', 1);
|
||||||
|
|
||||||
|
INSERT INTO sai_model_config VALUES (1, 5, 'glm-5.1', 'glm-5.1', 'CHAT', 'openai-compatible', '', 'xxx', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{\"frequencyPenalty\":0.0,\"maxTokens\":20000,\"presencePenalty\":0.0,\"stopSequences\":[],\"stream\":true,\"temperature\":0.7,\"timeoutMs\":300000,\"topK\":1,\"topP\":1.0}', NULL, 'GLOBAL', 1, 1, '2026-06-05 09:34:01', '2026-06-05 09:34:02');
|
||||||
|
|
||||||
|
INSERT INTO sai_agent VALUES (1, '智测先锋专家', '智测先锋专家是一款专注于软件测试与质量保障领域的智能助手。它能够高效生成覆盖全面的测试用例,深度分析Bug根因并提供修复建议,支持编写自动化测试脚本,以及解读复杂的测试报告。适用于软件开发周期的各个QA阶段,包括单元测试、接口测试、UI自动化及回归测试规划。其核心特点是逻辑严密、注重边界与异常场景,帮助团队大幅提升测试效率与软件质量。', NULL, '你是一位资深的软件测试与质量保障(QA)专家,名为“智测先锋专家”。\\n\\n【角色定位】你是开发团队的最后一道防线,致力于保障软件产品的卓越质量。\\n\\n【专业领域】精通黑盒与白盒测试、自动化测试框架(如Selenium、Pytest)、接口与性能测试、安全测试及CI/CD持续集成流程。\\n\\n【回答风格】逻辑严密、条理清晰、客观专业。善于使用结构化排版(如Markdown列表、代码块、表格)呈现测试用例和步骤,语言精炼,直击痛点。\\n\\n【行为指南】\\n1. 生成测试用例:必须覆盖正常流、异常流、边界值和兼容性等方面,确保测试的全面性与无遗漏。\\n2. 分析Bug根因:从代码逻辑、数据状态、环境配置等多维度推导,不仅给出修复建议,更要提供预防性的测试策略。\\n3. 编写自动化脚本:确保代码规范、包含必要注释与断言(Assert),并明确说明运行依赖与环境配置。\\n4. 需求澄清:若用户提问模糊,主动追问业务背景、技术栈等关键细节,拒绝给出宽泛且无实操价值的答案。\\n5. 风险预警:始终秉持质量第一理念,在解答中适时提示潜在的测试盲区与质量风险。', '你好!我是智测先锋专家,你的专属软件测试与质量保障顾问。无论是编写用例还是排查Bug,我都能为你提供专业支持!', '[\"如何为一个用户登录接口设计全面的测试用例?\",\"帮我分析这个空指针异常Bug的可能根因及修复建议。\",\"请提供一段Python的Pytest接口自动化测试脚本示例。\",\"怎样制定一个高效的回归测试策略?\"]', 2, 0, 0, 0, 0, 0, NULL, 1, 20, 1, 0, 1, 1, NULL, '1', '2026-06-05 09:34:50', '2026-06-05 09:34:51');
|
||||||
|
|
||||||
|
INSERT INTO sai_app VALUES (1, '1', '测试', '', 'SAI_566a6bfbc26e4998b4841cc927d50c5d', 'LEAST_LOAD', 1, '2026-06-05 09:32:50', '2026-06-05 09:32:50');
|
||||||
|
|
||||||
|
INSERT INTO sai_openapi_user VALUES (1, '1', '46ed53c6a20044c7bbd870848e80f92f', 1, '1', 'test', NOW(), NOW());
|
||||||
|
|||||||
@ -240,6 +240,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
|
|||||||
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '文件管理菜单');
|
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '文件管理菜单');
|
||||||
-- snail-job server控制台
|
-- snail-job server控制台
|
||||||
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'SnailJob控制台菜单');
|
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'SnailJob控制台菜单');
|
||||||
|
-- snail-ai server控制台
|
||||||
|
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'AI控制台菜单');
|
||||||
|
|
||||||
-- 三级菜单
|
-- 三级菜单
|
||||||
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '操作日志菜单');
|
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '操作日志菜单');
|
||||||
|
|||||||
@ -1416,6 +1416,8 @@ insert into sys_menu values(1761400000000000118, N'文件管理', 17614000000000
|
|||||||
GO
|
GO
|
||||||
insert into sys_menu values(1761400000000000120, N'任务调度中心', 1761400000000000002, 5, N'snailjob', N'monitor/snailjob/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:snailjob:list', N'job', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'SnailJob控制台菜单');
|
insert into sys_menu values(1761400000000000120, N'任务调度中心', 1761400000000000002, 5, N'snailjob', N'monitor/snailjob/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:snailjob:list', N'job', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'SnailJob控制台菜单');
|
||||||
GO
|
GO
|
||||||
|
insert into sys_menu values(1761400000000000121, N'AI控制台', 1761400000000000002, 7, N'snailai', N'monitor/snailai/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:snailai:list', N'checkbox', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'AI控制台菜单');
|
||||||
|
GO
|
||||||
insert into sys_menu values(1761400000000000500, N'操作日志', 1761400000000000108, 1, N'operlog', N'monitor/operlog/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:operlog:list', N'form', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'操作日志菜单');
|
insert into sys_menu values(1761400000000000500, N'操作日志', 1761400000000000108, 1, N'operlog', N'monitor/operlog/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:operlog:list', N'form', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'操作日志菜单');
|
||||||
GO
|
GO
|
||||||
insert into sys_menu values(1761400000000000501, N'登录日志', 1761400000000000108, 2, N'logininfo', N'monitor/logininfo/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:logininfo:list', N'logininfo', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'登录日志菜单');
|
insert into sys_menu values(1761400000000000501, N'登录日志', 1761400000000000108, 2, N'logininfo', N'monitor/logininfo/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:logininfo:list', N'logininfo', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'登录日志菜单');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user