update modules/ai: mod 4 files

This commit is contained in:
jack ning
2025-03-07 17:19:09 +08:00
parent 9988b12575
commit 8c7389c756
4 changed files with 195 additions and 89 deletions

View File

@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-17 11:39:17
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-07 16:40:36
* @LastEditTime: 2025-03-07 16:53:02
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -13,30 +13,42 @@
*/
package com.bytedesk.ai.springai.dashscope;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.bytedesk.core.annotation.UserIp;
import com.bytedesk.core.utils.JsonResult;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* 阿里通义千问接口
*/
@@ -49,33 +61,39 @@ public class SpringAIDashscopeController {
private final SpringAIDashscopeService springAIDashscopeService;
private final DashScopeChatModel bytedeskDashScopeChatModel;
private final Optional<SpringAIDashscopeImageService> imageService;
private final ExecutorService executorService = Executors.newCachedThreadPool();
@Qualifier("bytedeskDashScopeChatClient")
private final ChatClient bytedeskDashScopeChatClient;
/**
* 方式1同步调用
* http://127.0.0.1:9003/springai/dashscope/chat/sync?message=hello
* ChatClient 简单调用
* http://127.0.0.1:9003/springai/dashscope/simple/chat?query=
*/
@GetMapping("/chat/sync")
public ResponseEntity<JsonResult<?>> chatSync(
@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
String response = springAIDashscopeService.processPromptSync(message);
return ResponseEntity.ok(JsonResult.success(response));
@GetMapping("/simple/chat")
public ResponseEntity<?> simpleChat(
@RequestParam(value = "query", defaultValue = "你好,很高兴认识你,能简单介绍一下自己吗?") String query) {
String result = bytedeskDashScopeChatClient.prompt(query).call().content();
return ResponseEntity.ok(JsonResult.success(result));
}
/**
* 方式2异步流式调用
* http://127.0.0.1:9003/springai/dashscope/chat/stream?message=hello
* ChatClient 流式调用
* http://127.0.0.1:9003/springai/dashscope/stream/chat?query=
*/
@GetMapping("/chat/stream")
public Flux<ChatResponse> chatStream(
@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, HttpServletResponse response) {
// 避免返回乱码
@GetMapping("/stream/chat")
public Flux<String> streamChat(@RequestParam(value = "query", defaultValue = "你好,很高兴认识你,能简单介绍一下自己吗?") String query,
HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
// 设置返回类型
Prompt prompt = new Prompt(new UserMessage(message));
return bytedeskDashScopeChatModel.stream(prompt);
return bytedeskDashScopeChatClient.prompt(query).stream().content();
}
/**
* 方式3SSE调用
* http://127.0.0.1:9003/springai/dashscope/chat/sse?message=hello
@@ -135,4 +153,120 @@ public class SpringAIDashscopeController {
executorService.shutdown();
}
}
/**
* ChatClient 使用自定义的 Advisor 实现功能增强.
* eg:
* http://127.0.0.1:9003/springai/dashscope/advisor/chat/123?query=你好,我叫牧生,之后的会话中都带上我的名字
* 你好,牧生!很高兴认识你。在接下来的对话中,我会记得带上你的名字。有什么想聊的吗?
* http://127.0.0.1:9003/springai/dashscope/advisor/chat/123?query=我叫什么名字?
* 你叫牧生呀。有什么事情想要分享或者讨论吗,牧生?
*/
@GetMapping("/advisor/chat/{id}")
public Flux<String> advisorChat(
HttpServletResponse response,
@PathVariable String id,
@RequestParam String query) {
response.setCharacterEncoding("UTF-8");
return this.bytedeskDashScopeChatClient.prompt(query)
.advisors(
a -> a
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, id)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.stream().content();
}
// http://127.0.0.1:9003/springai/image/image2text
@UserIp
@PostMapping("/image2text")
@Operation(summary = "DashScope Image Recognition")
public Flux<JsonResult<?>> image2text(@RequestParam("image") MultipartFile image) {
if (image.isEmpty()) {
return Flux.just(JsonResult.error("No image file provided"));
}
if (imageService.isPresent()) {
return imageService.get().image2Text(image).map(JsonResult::success);
} else {
return Flux.just(JsonResult.error("Image service not enabled"));
}
}
// http://127.0.0.1:9003/springai/image/text2Image?prompt=A beautiful sunset
// over a calm ocean
@UserIp
@GetMapping("/text2Image")
@Operation(summary = "DashScope Image Generation")
public JsonResult<?> text2Image(
@RequestParam(value = "prompt", defaultValue = "A beautiful sunset over a calm ocean") String prompt,
HttpServletResponse response) {
if (prompt == null || prompt.isEmpty()) {
return JsonResult.error("Prompt is required");
}
if (imageService.isPresent()) {
imageService.get().text2Image(prompt, response);
} else {
return JsonResult.error("Image service not enabled");
}
return JsonResult.success();
}
/**
* audio2text
* http://127.0.0.1:9003/springai/audio/audio2text
* 用于将音频转换为文本输出
*/
// @UserIp
// @PostMapping("/audio2text")
// @Operation(summary = "DashScope Audio Transcription")
// public Flux<JsonResult<?>> audioToText(@RequestParam("audio") MultipartFile audio) {
// if (audio.isEmpty()) {
// return Flux.just(JsonResult.error("No audio file provided"));
// }
// if (audioService.isPresent()) {
// return audioService.get().audio2text(audio).map(JsonResult::success);
// } else {
// return Flux.just(JsonResult.error("Audio service not enabled"));
// }
// }
/**
* text2audio
* http://127.0.0.1:9003/springai/audio/text2audio?prompt=Hello, how are you?
* 用于将文本转换为语音输出
*/
// @UserIp
// @GetMapping("/text2audio")
// @Operation(summary = "DashScope Speech Synthesis")
// public JsonResult<?> textToAudio(
// @RequestParam(value = "prompt", defaultValue = "Hello, how are you?") String prompt) {
// if (prompt == null || prompt.isEmpty()) {
// return JsonResult.error("Prompt is required");
// }
// if (audioService.isPresent()) {
// byte[] audioData = audioService.get().text2audio(prompt);
// // 测试验证音频数据是否为空
// try (FileOutputStream fos = new FileOutputStream("audio.wav")) {
// fos.write(audioData);
// } catch (IOException e) {
// return JsonResult.error("Failed to save audio file: " + e.getMessage());
// }
// return JsonResult.success(audioData);
// } else {
// return JsonResult.error("Audio service not enabled");
// }
// }
}

View File

@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-28 17:56:26
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-07 15:37:58
* @LastEditTime: 2025-03-07 17:00:40
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,6 +16,7 @@ package com.bytedesk.ai.springai.dashscope;
import java.util.List;
import java.util.Optional;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
@@ -24,7 +25,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.bytedesk.ai.springai.base.BaseSpringAIService;
import com.bytedesk.ai.springai.spring.SpringAIVectorService;
import com.bytedesk.core.message.IMessageSendService;
@@ -38,55 +38,52 @@ import lombok.extern.slf4j.Slf4j;
@ConditionalOnProperty(name = "spring.ai.dashscope.chat.enabled", havingValue = "true", matchIfMissing = false)
public class SpringAIDashscopeService extends BaseSpringAIService {
private final Optional<DashScopeChatModel> bytedeskDashScopeChatModel;
// private final Optional<DashScopeChatModel> bytedeskDashScopeChatModel;
// private final ChatClient bytedeskDashScopeChatClient;
@Qualifier("bytedeskDashScopeChatClient")
private final ChatClient bytedeskDashScopeChatClient;
public SpringAIDashscopeService(
@Qualifier("bytedeskDashScopeChatModel") Optional<DashScopeChatModel> bytedeskDashScopeChatModel,
@Qualifier("bytedeskDashScopeChatClient") ChatClient bytedeskDashScopeChatClient,
Optional<SpringAIVectorService> springAIVectorService,
IMessageSendService messageSendService) {
super(springAIVectorService, messageSendService);
this.bytedeskDashScopeChatModel = bytedeskDashScopeChatModel;
// this.bytedeskDashScopeChatModel = bytedeskDashScopeChatModel;
this.bytedeskDashScopeChatClient = bytedeskDashScopeChatClient;
}
@Override
protected void processPrompt(Prompt prompt, MessageProtobuf messageProtobuf) {
bytedeskDashScopeChatModel.ifPresent(model -> model.stream(prompt).subscribe(
response -> {
if (response != null) {
log.info("DashScope API response metadata: {}", response.getMetadata());
List<Generation> generations = response.getResults();
for (Generation generation : generations) {
AssistantMessage assistantMessage = generation.getOutput();
String textContent = assistantMessage.getText();
messageProtobuf.setType(MessageTypeEnum.STREAM);
messageProtobuf.setContent(textContent);
messageSendService.sendProtobufMessage(messageProtobuf);
}
}
},
error -> {
log.error("DashScope API error: ", error);
messageProtobuf.setType(MessageTypeEnum.ERROR);
messageProtobuf.setContent("服务暂时不可用,请稍后重试");
messageSendService.sendProtobufMessage(messageProtobuf);
},
() -> log.info("Chat stream completed")
));
bytedeskDashScopeChatClient.prompt(prompt.toString())
.stream()
.content()
.subscribe(
content -> {
messageProtobuf.setType(MessageTypeEnum.STREAM);
messageProtobuf.setContent(content);
messageSendService.sendProtobufMessage(messageProtobuf);
},
error -> {
log.error("DashScope API error: ", error);
messageProtobuf.setType(MessageTypeEnum.ERROR);
messageProtobuf.setContent("服务暂时不可用,请稍后重试");
messageSendService.sendProtobufMessage(messageProtobuf);
},
() -> log.info("Chat stream completed")
);
}
@Override
protected String generateFaqPairs(String prompt) {
return bytedeskDashScopeChatModel.map(model -> model.call(prompt)).orElse("");
return bytedeskDashScopeChatClient.prompt(prompt).call().content();
}
@Override
protected String processPromptSync(String message) {
try {
return bytedeskDashScopeChatModel.map(model -> model.call(message))
.orElse("DashScope service is not available");
return bytedeskDashScopeChatClient.prompt(message).call().content();
} catch (Exception e) {
log.error("DashScope API sync error: ", e);
return "服务暂时不可用,请稍后重试";
@@ -95,25 +92,17 @@ public class SpringAIDashscopeService extends BaseSpringAIService {
@Override
protected void processPromptSSE(String message, SseEmitter emitter) {
bytedeskDashScopeChatModel.ifPresentOrElse(
model -> {
Prompt prompt = new Prompt(message);
model.stream(prompt).subscribe(
response -> {
try {
bytedeskDashScopeChatClient.prompt(message)
.stream()
.content()
.subscribe(
content -> {
try {
if (response != null) {
List<Generation> generations = response.getResults();
for (Generation generation : generations) {
AssistantMessage assistantMessage = generation.getOutput();
String textContent = assistantMessage.getText();
// 发送SSE事件
emitter.send(SseEmitter.event()
.data(textContent)
.id(String.valueOf(System.currentTimeMillis()))
.name("message"));
}
}
emitter.send(SseEmitter.event()
.data(content)
.id(String.valueOf(System.currentTimeMillis()))
.name("message"));
} catch (Exception e) {
log.error("Error sending SSE event", e);
emitter.completeWithError(e);
@@ -138,17 +127,9 @@ public class SpringAIDashscopeService extends BaseSpringAIService {
}
}
);
},
() -> {
try {
emitter.send(SseEmitter.event()
.data("DashScope service is not available")
.name("error"));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
}
);
} catch (Exception e) {
log.error("Error starting SSE stream", e);
emitter.completeWithError(e);
}
}
}

View File

@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2025-02-12 12:09:13
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-07 07:39:32
* @LastEditTime: 2025-03-07 17:17:43
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -34,14 +34,6 @@ public class SpringAIConfig {
private final Optional<OllamaChatModel> ollamaChatModel;
// @Bean("defaultChatClientBuilder")
// ChatClient.Builder defaultChatClientBuilder() {
// if (ollamaChatModel.isPresent()) {
// return ChatClient.builder(ollamaChatModel.get());
// }
// return null;
// }
// https://docs.spring.io/spring-ai/reference/api/chatclient.html
@Primary
@Bean("defaultChatClient")

View File

@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-27 21:27:01
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-03-06 16:52:59
* @LastEditTime: 2025-03-07 17:11:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -42,7 +42,6 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.HashMap;
import com.alibaba.fastjson2.JSON;
import com.bytedesk.ai.springai.spring.event.VectorSplitEvent;
import com.bytedesk.ai.utils.reader.WebDocumentReader;
import com.bytedesk.core.config.BytedeskEventPublisher;