对接openai image

This commit is contained in:
cherishsince 2024-03-24 20:09:06 +08:00
parent a14aebd5ee
commit 5999b80471
6 changed files with 114 additions and 22 deletions

View File

@ -19,7 +19,6 @@ package cn.iocoder.yudao.framework.ai.image;
import cn.iocoder.yudao.framework.ai.model.ModelClient; import cn.iocoder.yudao.framework.ai.model.ModelClient;
@FunctionalInterface
public interface ImageClient extends ModelClient<ImagePrompt, ImageResponse> { public interface ImageClient extends ModelClient<ImagePrompt, ImageResponse> {
/** /**

View File

@ -1,14 +1,25 @@
package cn.iocoder.yudao.framework.ai.imageopenai; package cn.iocoder.yudao.framework.ai.imageopenai;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
import cn.iocoder.yudao.framework.ai.util.JacksonUtil; import cn.iocoder.yudao.framework.ai.util.JacksonUtil;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClient;
import java.io.IOException;
import java.net.URI;
import java.time.Duration; import java.time.Duration;
/** /**
@ -17,6 +28,7 @@ import java.time.Duration;
* author: fansili * author: fansili
* time: 2024/3/17 09:53 * time: 2024/3/17 09:53
*/ */
@Slf4j
public class OpenAiImageApi { public class OpenAiImageApi {
private static final String DEFAULT_BASE_URL = "https://api.openai.com"; private static final String DEFAULT_BASE_URL = "https://api.openai.com";
@ -24,6 +36,8 @@ public class OpenAiImageApi {
// 发送请求 webClient // 发送请求 webClient
private final WebClient webClient; private final WebClient webClient;
private CloseableHttpClient httpclient = HttpClients.createDefault();
public OpenAiImageApi(String apiKey) { public OpenAiImageApi(String apiKey) {
this.apiKey = apiKey; this.apiKey = apiKey;
// 创建一个HttpClient实例并设置超时 // 创建一个HttpClient实例并设置超时
@ -37,18 +51,40 @@ public class OpenAiImageApi {
} }
public OpenAiImageResponse createImage(OpenAiImageRequest request) { public OpenAiImageResponse createImage(OpenAiImageRequest request) {
String res = webClient.post() HttpPost httpPost = new HttpPost();
.uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build()) httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations")));
.header("Content-Type", "application/json") httpPost.setHeader("Content-Type", "application/json");
.header("Authorization", "Bearer " + apiKey) httpPost.setHeader("Authorization", "Bearer " + apiKey);
// 设置请求体这里假设jsonStr是一个JSON格式的字符串 httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), "UTF-8"));
.body(BodyInserters.fromValue(JacksonUtil.toJson(request)))
// 发送请求并获取响应体 CloseableHttpResponse response= null;
.retrieve() try {
// 转换响应体为String类型 response = httpclient.execute(httpPost);
.bodyToMono(String.class) HttpEntity entity = response.getEntity();
.block(); String resultJson = EntityUtils.toString(entity);
// TODO: 2024/3/17 这里发送请求会失败 log.info("openai 图片生成结果: {}", resultJson);
return null; return JSONUtil.toBean(resultJson, OpenAiImageResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// String res = webClient.post()
// .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build())
// .header("Content-Type", "application/json")
// .header("Authorization", "Bearer " + apiKey)
// // 设置请求体这里假设jsonStr是一个JSON格式的字符串
// .body(BodyInserters.fromValue(JacksonUtil.toJson(request)))
// // 发送请求并获取响应体
// .retrieve()
// // 转换响应体为String类型
// .bodyToMono(String.class)
// .block();
} }
} }

View File

@ -1,15 +1,13 @@
package cn.iocoder.yudao.framework.ai.imageopenai; package cn.iocoder.yudao.framework.ai.imageopenai;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.ai.chat.ChatException; import cn.iocoder.yudao.framework.ai.chat.ChatException;
import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException;
import cn.iocoder.yudao.framework.ai.image.ImageClient; import cn.iocoder.yudao.framework.ai.image.*;
import cn.iocoder.yudao.framework.ai.image.ImageOptions;
import cn.iocoder.yudao.framework.ai.image.ImagePrompt;
import cn.iocoder.yudao.framework.ai.image.ImageResponse;
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
import jdk.jfr.Frequency;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext; import org.springframework.retry.RetryContext;
@ -74,9 +72,15 @@ public class OpenAiImageClient implements ImageClient {
// 创建请求 // 创建请求
OpenAiImageRequest request = new OpenAiImageRequest(); OpenAiImageRequest request = new OpenAiImageRequest();
BeanUtil.copyProperties(openAiImageOptions, request); BeanUtil.copyProperties(openAiImageOptions, request);
request.setPrompt(imagePrompt.getInstructions().get(0).getText());
// 发送请求 // 发送请求
OpenAiImageResponse response = openAiImageApi.createImage(request); OpenAiImageResponse response = openAiImageApi.createImage(request);
return null; return new ImageResponse(response.getData().stream().map(res -> {
byte[] bytes = HttpUtil.downloadBytes(res.getUrl());
String base64 = Base64.encode(bytes);
return new ImageGeneration(new Image(res.getUrl(), base64));
}).toList());
}); });
} }
} }

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.ai.imageopenai;
import cn.iocoder.yudao.framework.ai.image.ImageOptions; import cn.iocoder.yudao.framework.ai.image.ImageOptions;
import lombok.Data; import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
/** /**
@ -47,6 +48,21 @@ public class OpenAiImageOptions implements ImageOptions {
// 代表您的终端用户的唯一标识符有助于OpenAI监控并检测滥用行为了解更多信息请参考官方文档 // 代表您的终端用户的唯一标识符有助于OpenAI监控并检测滥用行为了解更多信息请参考官方文档
private String endUserId; private String endUserId;
@Getter
public enum ResponseFormatEnum {
URL("url"),
BASE64("b64_json"),
;
ResponseFormatEnum(String value) {
this.value = value;
}
private String value;
}
// //
// 适配 spring ai // 适配 spring ai

View File

@ -23,6 +23,7 @@ public class OpenAiImageResponse {
public static class Item { public static class Item {
private String url; private String url;
private String b64_json;
} }
} }

View File

@ -6,6 +6,14 @@ import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Scanner;
/** /**
* author: fansili * author: fansili
* time: 2024/3/17 10:40 * time: 2024/3/17 10:40
@ -20,12 +28,40 @@ public class OpenAiImageClientTests {
// 初始化 openAiImageClient // 初始化 openAiImageClient
this.openAiImageClient = new OpenAiImageClient( this.openAiImageClient = new OpenAiImageClient(
new OpenAiImageApi(""), new OpenAiImageApi(""),
new OpenAiImageOptions() new OpenAiImageOptions().setResponseFormat(OpenAiImageOptions.ResponseFormatEnum.URL.getValue())
); );
} }
@Test @Test
public void callTest() { public void callTest() {
openAiImageClient.call(new ImagePrompt("我和我的小狗,一起在北极和企鹅玩排球。")); ImageResponse call = openAiImageClient.call(new ImagePrompt("我和我的小狗,一起在北极和企鹅玩排球。"));
System.err.println("url: " + call.getResult().getOutput().getUrl());
System.err.println("base64: " + call.getResult().getOutput().getB64Json());
String base64String = call.getResult().getOutput().getB64Json();
ImageIcon imageIcon = new ImageIcon(decodeBase64ToImage(base64String));
JLabel label = new JLabel(imageIcon);
JFrame frame = new JFrame("Base64 Image Display");
frame.getContentPane().add(label);
frame.pack();
frame.setVisible(true);
// 阻止退出
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
}
// 将Base64解码为BufferedImage
private static BufferedImage decodeBase64ToImage(String base64String) {
try {
byte[] decodedBytes = Base64.getDecoder().decode(base64String);
ByteArrayInputStream bis = new ByteArrayInputStream(decodedBytes);
return ImageIO.read(bis);
} catch (IOException e) {
System.out.println("Error decoding the base64 image: " + e.getMessage());
return null;
}
} }
} }