Commit 9cd97a77e06a2956b20d47ccce97761265c4d75f

Authored by cherishsince
2 parents 73631fb3 6e261de0

Merge remote-tracking branch 'origin/master-jdk21-ai' into master-jdk21-ai

yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -114,6 +114,12 @@ @@ -114,6 +114,12 @@
114 <artifactId>broadscope-bailian-sdk-java</artifactId> 114 <artifactId>broadscope-bailian-sdk-java</artifactId>
115 <version>1.3.0</version> 115 <version>1.3.0</version>
116 </dependency> 116 </dependency>
  117 + <dependency>
  118 + <groupId>org.apache.httpcomponents</groupId>
  119 + <artifactId>httpclient</artifactId>
  120 + <version>4.5.14</version>
  121 + <scope>compile</scope>
  122 + </dependency>
117 </dependencies> 123 </dependencies>
118 124
119 </project> 125 </project>
120 \ No newline at end of file 126 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java
@@ -19,7 +19,6 @@ package cn.iocoder.yudao.framework.ai.image; @@ -19,7 +19,6 @@ package cn.iocoder.yudao.framework.ai.image;
19 19
20 import cn.iocoder.yudao.framework.ai.model.ModelClient; 20 import cn.iocoder.yudao.framework.ai.model.ModelClient;
21 21
22 -@FunctionalInterface  
23 public interface ImageClient extends ModelClient<ImagePrompt, ImageResponse> { 22 public interface ImageClient extends ModelClient<ImagePrompt, ImageResponse> {
24 23
25 /** 24 /**
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java
1 package cn.iocoder.yudao.framework.ai.imageopenai; 1 package cn.iocoder.yudao.framework.ai.imageopenai;
2 2
  3 +import cn.hutool.json.JSONUtil;
3 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; 4 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
4 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; 5 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
5 import cn.iocoder.yudao.framework.ai.util.JacksonUtil; 6 import cn.iocoder.yudao.framework.ai.util.JacksonUtil;
6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.ChannelOption;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.apache.http.HttpEntity;
  10 +import org.apache.http.client.methods.CloseableHttpResponse;
  11 +import org.apache.http.client.methods.HttpPost;
  12 +import org.apache.http.entity.StringEntity;
  13 +import org.apache.http.impl.client.CloseableHttpClient;
  14 +import org.apache.http.impl.client.HttpClients;
  15 +import org.apache.http.util.EntityUtils;
7 import org.springframework.http.client.reactive.ReactorClientHttpConnector; 16 import org.springframework.http.client.reactive.ReactorClientHttpConnector;
8 import org.springframework.web.reactive.function.BodyInserters; 17 import org.springframework.web.reactive.function.BodyInserters;
9 import org.springframework.web.reactive.function.client.WebClient; 18 import org.springframework.web.reactive.function.client.WebClient;
10 import reactor.netty.http.client.HttpClient; 19 import reactor.netty.http.client.HttpClient;
11 20
  21 +import java.io.IOException;
  22 +import java.net.URI;
12 import java.time.Duration; 23 import java.time.Duration;
13 24
14 /** 25 /**
@@ -17,6 +28,7 @@ import java.time.Duration; @@ -17,6 +28,7 @@ import java.time.Duration;
17 * author: fansili 28 * author: fansili
18 * time: 2024/3/17 09:53 29 * time: 2024/3/17 09:53
19 */ 30 */
  31 +@Slf4j
20 public class OpenAiImageApi { 32 public class OpenAiImageApi {
21 33
22 private static final String DEFAULT_BASE_URL = "https://api.openai.com"; 34 private static final String DEFAULT_BASE_URL = "https://api.openai.com";
@@ -24,6 +36,8 @@ public class OpenAiImageApi { @@ -24,6 +36,8 @@ public class OpenAiImageApi {
24 // 发送请求 webClient 36 // 发送请求 webClient
25 private final WebClient webClient; 37 private final WebClient webClient;
26 38
  39 + private CloseableHttpClient httpclient = HttpClients.createDefault();
  40 +
27 public OpenAiImageApi(String apiKey) { 41 public OpenAiImageApi(String apiKey) {
28 this.apiKey = apiKey; 42 this.apiKey = apiKey;
29 // 创建一个HttpClient实例并设置超时 43 // 创建一个HttpClient实例并设置超时
@@ -37,18 +51,40 @@ public class OpenAiImageApi { @@ -37,18 +51,40 @@ public class OpenAiImageApi {
37 } 51 }
38 52
39 public OpenAiImageResponse createImage(OpenAiImageRequest request) { 53 public OpenAiImageResponse createImage(OpenAiImageRequest request) {
40 - String res = webClient.post()  
41 - .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build())  
42 - .header("Content-Type", "application/json")  
43 - .header("Authorization", "Bearer " + apiKey)  
44 - // 设置请求体(这里假设jsonStr是一个JSON格式的字符串)  
45 - .body(BodyInserters.fromValue(JacksonUtil.toJson(request)))  
46 - // 发送请求并获取响应体  
47 - .retrieve()  
48 - // 转换响应体为String类型  
49 - .bodyToMono(String.class)  
50 - .block();  
51 - // TODO: 2024/3/17 这里发送请求会失败!  
52 - return null; 54 + HttpPost httpPost = new HttpPost();
  55 + httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations")));
  56 + httpPost.setHeader("Content-Type", "application/json");
  57 + httpPost.setHeader("Authorization", "Bearer " + apiKey);
  58 + httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), "UTF-8"));
  59 +
  60 + CloseableHttpResponse response= null;
  61 + try {
  62 + response = httpclient.execute(httpPost);
  63 + HttpEntity entity = response.getEntity();
  64 + String resultJson = EntityUtils.toString(entity);
  65 + log.info("openai 图片生成结果: {}", resultJson);
  66 + return JSONUtil.toBean(resultJson, OpenAiImageResponse.class);
  67 + } catch (IOException e) {
  68 + throw new RuntimeException(e);
  69 + } finally {
  70 + if (response != null) {
  71 + try {
  72 + response.close();
  73 + } catch (IOException e) {
  74 + throw new RuntimeException(e);
  75 + }
  76 + }
  77 + }
  78 +// String res = webClient.post()
  79 +// .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build())
  80 +// .header("Content-Type", "application/json")
  81 +// .header("Authorization", "Bearer " + apiKey)
  82 +// // 设置请求体(这里假设jsonStr是一个JSON格式的字符串)
  83 +// .body(BodyInserters.fromValue(JacksonUtil.toJson(request)))
  84 +// // 发送请求并获取响应体
  85 +// .retrieve()
  86 +// // 转换响应体为String类型
  87 +// .bodyToMono(String.class)
  88 +// .block();
53 } 89 }
54 } 90 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java
1 package cn.iocoder.yudao.framework.ai.imageopenai; 1 package cn.iocoder.yudao.framework.ai.imageopenai;
2 2
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.codec.Base64;
  5 +import cn.hutool.http.HttpUtil;
4 import cn.iocoder.yudao.framework.ai.chat.ChatException; 6 import cn.iocoder.yudao.framework.ai.chat.ChatException;
5 import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; 7 import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException;
6 -import cn.iocoder.yudao.framework.ai.image.ImageClient;  
7 -import cn.iocoder.yudao.framework.ai.image.ImageOptions;  
8 -import cn.iocoder.yudao.framework.ai.image.ImagePrompt;  
9 -import cn.iocoder.yudao.framework.ai.image.ImageResponse; 8 +import cn.iocoder.yudao.framework.ai.image.*;
10 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; 9 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
11 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; 10 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
12 -import jdk.jfr.Frequency;  
13 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
14 import org.springframework.retry.RetryCallback; 12 import org.springframework.retry.RetryCallback;
15 import org.springframework.retry.RetryContext; 13 import org.springframework.retry.RetryContext;
@@ -74,9 +72,15 @@ public class OpenAiImageClient implements ImageClient { @@ -74,9 +72,15 @@ public class OpenAiImageClient implements ImageClient {
74 // 创建请求 72 // 创建请求
75 OpenAiImageRequest request = new OpenAiImageRequest(); 73 OpenAiImageRequest request = new OpenAiImageRequest();
76 BeanUtil.copyProperties(openAiImageOptions, request); 74 BeanUtil.copyProperties(openAiImageOptions, request);
  75 + request.setPrompt(imagePrompt.getInstructions().get(0).getText());
77 // 发送请求 76 // 发送请求
78 OpenAiImageResponse response = openAiImageApi.createImage(request); 77 OpenAiImageResponse response = openAiImageApi.createImage(request);
79 - return null; 78 + return new ImageResponse(response.getData().stream().map(res -> {
  79 + byte[] bytes = HttpUtil.downloadBytes(res.getUrl());
  80 + String base64 = Base64.encode(bytes);
  81 + return new ImageGeneration(new Image(res.getUrl(), base64));
  82 + }).toList());
80 }); 83 });
81 } 84 }
  85 +
82 } 86 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.ai.imageopenai; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.ai.imageopenai;
2 2
3 import cn.iocoder.yudao.framework.ai.image.ImageOptions; 3 import cn.iocoder.yudao.framework.ai.image.ImageOptions;
4 import lombok.Data; 4 import lombok.Data;
  5 +import lombok.Getter;
5 import lombok.experimental.Accessors; 6 import lombok.experimental.Accessors;
6 7
7 /** 8 /**
@@ -47,6 +48,21 @@ public class OpenAiImageOptions implements ImageOptions { @@ -47,6 +48,21 @@ public class OpenAiImageOptions implements ImageOptions {
47 // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 48 // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。
48 private String endUserId; 49 private String endUserId;
49 50
  51 + @Getter
  52 + public enum ResponseFormatEnum {
  53 +
  54 + URL("url"),
  55 + BASE64("b64_json"),
  56 +
  57 + ;
  58 +
  59 + ResponseFormatEnum(String value) {
  60 + this.value = value;
  61 + }
  62 +
  63 + private String value;
  64 + }
  65 +
50 // 66 //
51 // 适配 spring ai 67 // 适配 spring ai
52 68
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java
@@ -23,6 +23,7 @@ public class OpenAiImageResponse { @@ -23,6 +23,7 @@ public class OpenAiImageResponse {
23 public static class Item { 23 public static class Item {
24 24
25 private String url; 25 private String url;
  26 + private String b64_json;
26 27
27 } 28 }
28 } 29 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java
@@ -6,6 +6,14 @@ import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; @@ -6,6 +6,14 @@ import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions;
6 import org.junit.Before; 6 import org.junit.Before;
7 import org.junit.Test; 7 import org.junit.Test;
8 8
  9 +import javax.imageio.ImageIO;
  10 +import javax.swing.*;
  11 +import java.awt.image.BufferedImage;
  12 +import java.io.ByteArrayInputStream;
  13 +import java.io.IOException;
  14 +import java.util.Base64;
  15 +import java.util.Scanner;
  16 +
9 /** 17 /**
10 * author: fansili 18 * author: fansili
11 * time: 2024/3/17 10:40 19 * time: 2024/3/17 10:40
@@ -20,12 +28,40 @@ public class OpenAiImageClientTests { @@ -20,12 +28,40 @@ public class OpenAiImageClientTests {
20 // 初始化 openAiImageClient 28 // 初始化 openAiImageClient
21 this.openAiImageClient = new OpenAiImageClient( 29 this.openAiImageClient = new OpenAiImageClient(
22 new OpenAiImageApi(""), 30 new OpenAiImageApi(""),
23 - new OpenAiImageOptions() 31 + new OpenAiImageOptions().setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue())
24 ); 32 );
25 } 33 }
26 34
27 @Test 35 @Test
28 public void callTest() { 36 public void callTest() {
29 - openAiImageClient.call(new ImagePrompt("我和我的小狗,一起在北极和企鹅玩排球。")); 37 + ImageResponse call = openAiImageClient.call(new ImagePrompt("我和我的小狗,一起在北极和企鹅玩排球。"));
  38 + System.err.println("url: " + call.getResult().getOutput().getUrl());
  39 + System.err.println("base64: " + call.getResult().getOutput().getB64Json());
  40 +
  41 + String base64String = call.getResult().getOutput().getB64Json();
  42 + ImageIcon imageIcon = new ImageIcon(decodeBase64ToImage(base64String));
  43 + JLabel label = new JLabel(imageIcon);
  44 +
  45 + JFrame frame = new JFrame("Base64 Image Display");
  46 + frame.getContentPane().add(label);
  47 + frame.pack();
  48 + frame.setVisible(true);
  49 +
  50 + // 阻止退出
  51 + Scanner scanner = new Scanner(System.in);
  52 + scanner.nextLine();
  53 + }
  54 +
  55 +
  56 + // 将Base64解码为BufferedImage
  57 + private static BufferedImage decodeBase64ToImage(String base64String) {
  58 + try {
  59 + byte[] decodedBytes = Base64.getDecoder().decode(base64String);
  60 + ByteArrayInputStream bis = new ByteArrayInputStream(decodedBytes);
  61 + return ImageIO.read(bis);
  62 + } catch (IOException e) {
  63 + System.out.println("Error decoding the base64 image: " + e.getMessage());
  64 + return null;
  65 + }
30 } 66 }
31 } 67 }