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 114 <artifactId>broadscope-bailian-sdk-java</artifactId>
115 115 <version>1.3.0</version>
116 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 123 </dependencies>
118 124  
119 125 </project>
120 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 19  
20 20 import cn.iocoder.yudao.framework.ai.model.ModelClient;
21 21  
22   -@FunctionalInterface
23 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 1 package cn.iocoder.yudao.framework.ai.imageopenai;
2 2  
  3 +import cn.hutool.json.JSONUtil;
3 4 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
4 5 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
5 6 import cn.iocoder.yudao.framework.ai.util.JacksonUtil;
6 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 16 import org.springframework.http.client.reactive.ReactorClientHttpConnector;
8 17 import org.springframework.web.reactive.function.BodyInserters;
9 18 import org.springframework.web.reactive.function.client.WebClient;
10 19 import reactor.netty.http.client.HttpClient;
11 20  
  21 +import java.io.IOException;
  22 +import java.net.URI;
12 23 import java.time.Duration;
13 24  
14 25 /**
... ... @@ -17,6 +28,7 @@ import java.time.Duration;
17 28 * author: fansili
18 29 * time: 2024/3/17 09:53
19 30 */
  31 +@Slf4j
20 32 public class OpenAiImageApi {
21 33  
22 34 private static final String DEFAULT_BASE_URL = "https://api.openai.com";
... ... @@ -24,6 +36,8 @@ public class OpenAiImageApi {
24 36 // 发送请求 webClient
25 37 private final WebClient webClient;
26 38  
  39 + private CloseableHttpClient httpclient = HttpClients.createDefault();
  40 +
27 41 public OpenAiImageApi(String apiKey) {
28 42 this.apiKey = apiKey;
29 43 // 创建一个HttpClient实例并设置超时
... ... @@ -37,18 +51,40 @@ public class OpenAiImageApi {
37 51 }
38 52  
39 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 1 package cn.iocoder.yudao.framework.ai.imageopenai;
2 2  
3 3 import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.codec.Base64;
  5 +import cn.hutool.http.HttpUtil;
4 6 import cn.iocoder.yudao.framework.ai.chat.ChatException;
5 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 9 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
11 10 import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
12   -import jdk.jfr.Frequency;
13 11 import lombok.extern.slf4j.Slf4j;
14 12 import org.springframework.retry.RetryCallback;
15 13 import org.springframework.retry.RetryContext;
... ... @@ -74,9 +72,15 @@ public class OpenAiImageClient implements ImageClient {
74 72 // 创建请求
75 73 OpenAiImageRequest request = new OpenAiImageRequest();
76 74 BeanUtil.copyProperties(openAiImageOptions, request);
  75 + request.setPrompt(imagePrompt.getInstructions().get(0).getText());
77 76 // 发送请求
78 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 2  
3 3 import cn.iocoder.yudao.framework.ai.image.ImageOptions;
4 4 import lombok.Data;
  5 +import lombok.Getter;
5 6 import lombok.experimental.Accessors;
6 7  
7 8 /**
... ... @@ -47,6 +48,21 @@ public class OpenAiImageOptions implements ImageOptions {
47 48 // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。
48 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 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 23 public static class Item {
24 24  
25 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 import org.junit.Before;
7 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 18 * author: fansili
11 19 * time: 2024/3/17 10:40
... ... @@ -20,12 +28,40 @@ public class OpenAiImageClientTests {
20 28 // 初始化 openAiImageClient
21 29 this.openAiImageClient = new OpenAiImageClient(
22 30 new OpenAiImageApi(""),
23   - new OpenAiImageOptions()
  31 + new OpenAiImageOptions().setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue())
24 32 );
25 33 }
26 34  
27 35 @Test
28 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 }
... ...