mirror of
https://gitee.com/270580156/weiyu.git
synced 2026-05-16 20:27:50 +00:00
update modules/ai: mod 4 files
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 方式3:SSE调用
|
||||
* 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");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user