mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-18 01:25:28 +08:00
snail-ai测试版本
This commit is contained in:
parent
bb94a6b9a9
commit
4eacdc54ca
@ -32,6 +32,8 @@ snail-job:
|
||||
snail-ai:
|
||||
# 启用客户端模式
|
||||
enabled: true
|
||||
# 聊天发送模式: stream(流式) / sync(同步)
|
||||
chat-mode: stream
|
||||
# ==================== Server 连接 ====================
|
||||
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
||||
server:
|
||||
|
||||
@ -31,6 +31,41 @@ snail-job:
|
||||
# 客户端ip指定
|
||||
host:
|
||||
|
||||
--- # snail-ai 配置
|
||||
snail-ai:
|
||||
# 启用客户端模式
|
||||
enabled: true
|
||||
# 聊天发送模式: stream(流式) / sync(同步)
|
||||
chat-mode: stream
|
||||
# ==================== Server 连接 ====================
|
||||
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 18888
|
||||
# ==================== 客户端配置 ====================
|
||||
# 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求)
|
||||
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
||||
app-id: 1
|
||||
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
||||
token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b
|
||||
port: 18889
|
||||
# Skill 文件临时目录
|
||||
skill-temp-dir: /tmp/snail-ai-agent/skills
|
||||
# ==================== OpenAPI Client 配置 ====================
|
||||
open-api:
|
||||
# 启用 OpenAPI Client
|
||||
enabled: true
|
||||
# Server HTTP 端口
|
||||
web-port: 18080
|
||||
# 是否使用 HTTPS
|
||||
https: false
|
||||
# API 路径前缀
|
||||
prefix: snail-ai
|
||||
# 超时配置(毫秒)
|
||||
connect-timeout-ms: 5000
|
||||
read-timeout-ms: 60000
|
||||
chat-timeout-ms: 300000
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
datasource:
|
||||
|
||||
@ -12,14 +12,20 @@ import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* OpenAPI 使用示例 Controller
|
||||
@ -40,6 +46,8 @@ public class OpenApiDemoController {
|
||||
private final OpenApiChatClient chatClient;
|
||||
private final OpenApiConversationClient conversationClient;
|
||||
private final OpenApiUserClient userClient;
|
||||
@Value("${snail-ai.chat-mode:stream}")
|
||||
private String chatMode;
|
||||
|
||||
// ==================== User 相关接口 ====================
|
||||
|
||||
@ -138,6 +146,13 @@ public class OpenApiDemoController {
|
||||
|
||||
// ==================== Chat 相关接口 ====================
|
||||
|
||||
@GetMapping("/chat/mode")
|
||||
@Operation(summary = "获取聊天发送模式", description = "返回 stream(流式) 或 sync(同步)")
|
||||
public Result<Map<String, String>> getChatMode() {
|
||||
String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream";
|
||||
return Result.ok(Map.of("mode", mode));
|
||||
}
|
||||
|
||||
@PostMapping("/agent/{agentId}/chat/sync")
|
||||
@Operation(summary = "同步对话", description = "发送消息并等待 AI 回复(非流式)")
|
||||
public Result<OpenApiChatSyncResponse> chatSync(
|
||||
@ -153,15 +168,19 @@ public class OpenApiDemoController {
|
||||
|
||||
@GetMapping("/agent/{agentId}/chat/stream")
|
||||
@Operation(summary = "流式对话", description = "发送消息并以 SSE 流式接收 AI 回复")
|
||||
public SseEmitter chatStream(
|
||||
public void chatStream(
|
||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
||||
@PathVariable Long agentId,
|
||||
@Parameter(description = "用户消息", required = true, example = "你好")
|
||||
@RequestParam String content,
|
||||
@Parameter(description = "会话 ID(可选)", example = "conv-123")
|
||||
@RequestParam(required = false) String conversationId) {
|
||||
|
||||
SseEmitter emitter = new SseEmitter(300000L); // 5 分钟超时
|
||||
@RequestParam(required = false) String conversationId,
|
||||
HttpServletResponse response) {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Connection", "keep-alive");
|
||||
|
||||
OpenApiChatRequest request = new OpenApiChatRequest();
|
||||
request.setAgentId(agentId);
|
||||
@ -170,26 +189,26 @@ public class OpenApiDemoController {
|
||||
request.setConversationId(conversationId);
|
||||
|
||||
log.info("Stream chat request: agentId={}, content={}", agentId, content);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
final boolean[] completed = {false};
|
||||
try {
|
||||
PrintWriter writer = response.getWriter();
|
||||
chatClient.chatStream(request, new SseEventListener() {
|
||||
@Override
|
||||
public void onText(String text) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("text")
|
||||
.data(text));
|
||||
writeSseEvent(writer, "text", text);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to send SSE text", e);
|
||||
emitter.completeWithError(e);
|
||||
completed[0] = true;
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(String thinking) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("thinking")
|
||||
.data(thinking));
|
||||
writeSseEvent(writer, "thinking", thinking);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to send SSE thinking", e);
|
||||
}
|
||||
@ -198,14 +217,13 @@ public class OpenApiDemoController {
|
||||
@Override
|
||||
public void onComplete(String data) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("done")
|
||||
.data(data));
|
||||
emitter.complete();
|
||||
writeSseEvent(writer, "done", data);
|
||||
log.info("Stream chat completed");
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to send SSE completion", e);
|
||||
emitter.completeWithError(e);
|
||||
} finally {
|
||||
completed[0] = true;
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,21 +231,45 @@ public class OpenApiDemoController {
|
||||
public void onError(String errorMessage) {
|
||||
log.error("Stream chat error: {}", errorMessage);
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("error")
|
||||
.data(errorMessage));
|
||||
writeSseEvent(writer, "error", errorMessage);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to send SSE error", e);
|
||||
} finally {
|
||||
completed[0] = true;
|
||||
latch.countDown();
|
||||
}
|
||||
emitter.completeWithError(new SnailAiException(errorMessage));
|
||||
}
|
||||
});
|
||||
latch.await(5, TimeUnit.MINUTES);
|
||||
if (!completed[0]) {
|
||||
writeSseEvent(writer, "error", "SSE stream timeout");
|
||||
writeSseEvent(writer, "done", "");
|
||||
}
|
||||
writer.flush();
|
||||
} catch (Exception e) {
|
||||
log.error("Stream chat exception", e);
|
||||
emitter.completeWithError(e);
|
||||
try {
|
||||
PrintWriter writer = response.getWriter();
|
||||
writeSseEvent(writer, "error", "stream exception: " + e.getMessage());
|
||||
writeSseEvent(writer, "done", "");
|
||||
writer.flush();
|
||||
} catch (IOException ex) {
|
||||
log.error("Failed to write stream exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emitter;
|
||||
private void writeSseEvent(PrintWriter writer, String event, String data) throws IOException {
|
||||
synchronized (writer) {
|
||||
writer.write("event: " + event + "\n");
|
||||
String payload = data == null ? "" : data;
|
||||
String[] lines = payload.split("\\R", -1);
|
||||
for (String line : lines) {
|
||||
writer.write("data: " + line + "\n");
|
||||
}
|
||||
writer.write("\n");
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private String ensureOpenId() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user