mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-02-08 14:44:57 +08:00
对接 openai dall-e-3,发送请求解析地址存在问题,链接不上
This commit is contained in:
parent
633b112603
commit
a14aebd5ee
@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.framework.ai.imageopenai;
|
||||
|
||||
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest;
|
||||
import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse;
|
||||
import cn.iocoder.yudao.framework.ai.util.JacksonUtil;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* open ai image
|
||||
* <p>
|
||||
* author: fansili
|
||||
* time: 2024/3/17 09:53
|
||||
*/
|
||||
public class OpenAiImageApi {
|
||||
|
||||
private static final String DEFAULT_BASE_URL = "https://api.openai.com";
|
||||
private String apiKey = "your-api-key";
|
||||
// 发送请求 webClient
|
||||
private final WebClient webClient;
|
||||
|
||||
public OpenAiImageApi(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
// 创建一个HttpClient实例并设置超时
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofSeconds(300)) // 设置响应超时时间为30秒
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 * 100); // 设置连接超时为5秒
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(DEFAULT_BASE_URL)
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
}
|
||||
|
||||
public OpenAiImageResponse createImage(OpenAiImageRequest request) {
|
||||
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();
|
||||
// TODO: 2024/3/17 这里发送请求会失败!
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.framework.ai.imageopenai;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.ai.chat.ChatException;
|
||||
import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException;
|
||||
import cn.iocoder.yudao.framework.ai.image.ImageClient;
|
||||
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.OpenAiImageResponse;
|
||||
import jdk.jfr.Frequency;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* open ai 生成 image
|
||||
*
|
||||
* author: fansili
|
||||
* time: 2024/3/17 09:51
|
||||
*/
|
||||
@Slf4j
|
||||
public class OpenAiImageClient implements ImageClient {
|
||||
|
||||
/**
|
||||
* open image ai
|
||||
*/
|
||||
private OpenAiImageApi openAiImageApi;
|
||||
/**
|
||||
* 默认使用的 ImageOptions
|
||||
*/
|
||||
private OpenAiImageOptions defaultImageOptions;
|
||||
|
||||
|
||||
public final RetryTemplate retryTemplate = RetryTemplate.builder()
|
||||
// 最大重试次数 10
|
||||
.maxAttempts(10)
|
||||
.retryOn(YiYanApiException.class)
|
||||
// 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms
|
||||
.exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
|
||||
.withListener(new RetryListener() {
|
||||
@Override
|
||||
public <T extends Object, E extends Throwable> void onError(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
log.warn("重试异常:" + context.getRetryCount(), throwable);
|
||||
};
|
||||
})
|
||||
.build();
|
||||
|
||||
public OpenAiImageClient(OpenAiImageApi openAiImageApi, OpenAiImageOptions defaultImageOptions) {
|
||||
this.openAiImageApi = openAiImageApi;
|
||||
this.defaultImageOptions = defaultImageOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageResponse call(ImagePrompt imagePrompt) {
|
||||
return this.retryTemplate.execute(ctx -> {
|
||||
// 检查是否配置了 OpenAiImageOptions
|
||||
if (defaultImageOptions == null && imagePrompt.getOptions() == null) {
|
||||
throw new ChatException("OpenAiImageOptions 未配置参数!");
|
||||
}
|
||||
// 优先使用 request 中的 ImageOptions
|
||||
ImageOptions useImageOptions = imagePrompt.getOptions() == null ? defaultImageOptions : imagePrompt.getOptions();
|
||||
if (!(useImageOptions instanceof OpenAiImageOptions)) {
|
||||
throw new ChatException("配置信息不正确,传入的必须是 OpenAiImageOptions!");
|
||||
}
|
||||
// 转换 OpenAiImageOptions
|
||||
OpenAiImageOptions openAiImageOptions = (OpenAiImageOptions) useImageOptions;
|
||||
// 创建请求
|
||||
OpenAiImageRequest request = new OpenAiImageRequest();
|
||||
BeanUtil.copyProperties(openAiImageOptions, request);
|
||||
// 发送请求
|
||||
OpenAiImageResponse response = openAiImageApi.createImage(request);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.framework.ai.imageopenai;
|
||||
|
||||
import cn.iocoder.yudao.framework.ai.image.ImageOptions;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* open ai 配置文件
|
||||
*
|
||||
* 文档地址:https://platform.openai.com/docs/api-reference/images/create
|
||||
*
|
||||
* author: fansili
|
||||
* time: 2024/3/17 09:53
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OpenAiImageOptions implements ImageOptions {
|
||||
|
||||
// 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。
|
||||
private String prompt;
|
||||
|
||||
// 可选字段,默认为dall-e-2
|
||||
// 指定用于生成图像的模型名称。
|
||||
private String model = "dall-e-2";
|
||||
|
||||
// 可选字段,默认为1
|
||||
// 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。
|
||||
private Integer n = 1;
|
||||
|
||||
// 可选字段,默认为standard
|
||||
// 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。
|
||||
private String quality = "standard";
|
||||
|
||||
// 可选字段,默认为url
|
||||
// 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。
|
||||
private String responseFormat = "url";
|
||||
|
||||
// 可选字段,默认为1024x1024
|
||||
// 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。
|
||||
private String imageSize = "1024x1024";
|
||||
|
||||
// 可选字段,默认为vivid
|
||||
// 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。
|
||||
private String style = "vivid";
|
||||
|
||||
// 可选字段
|
||||
// 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。
|
||||
private String endUserId;
|
||||
|
||||
//
|
||||
// 适配 spring ai
|
||||
|
||||
@Override
|
||||
public Integer getN() {
|
||||
return this.n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getWidth() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getHeight() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseFormat() {
|
||||
return this.responseFormat;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.framework.ai.imageopenai.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* open ai 配置文件
|
||||
*
|
||||
* 文档地址:https://platform.openai.com/docs/api-reference/images/create
|
||||
*
|
||||
* author: fansili
|
||||
* time: 2024/3/17 09:53
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OpenAiImageRequest {
|
||||
|
||||
// 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。
|
||||
@JsonProperty("prompt")
|
||||
private String prompt;
|
||||
|
||||
// 可选字段,默认为dall-e-2、dall-e-3
|
||||
// 指定用于生成图像的模型名称。
|
||||
@JsonProperty("model")
|
||||
private String model = "dall-e-2";
|
||||
|
||||
// 可选字段,默认为1
|
||||
// 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。
|
||||
@JsonProperty("n")
|
||||
private Integer n = 1;
|
||||
|
||||
// 可选字段,默认为standard
|
||||
// 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。
|
||||
@JsonProperty("quality")
|
||||
private String quality = "standard";
|
||||
|
||||
// 可选字段,默认为url
|
||||
// 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。
|
||||
@JsonProperty("response_format")
|
||||
private String responseFormat = "url";
|
||||
|
||||
// 可选字段,默认为1024x1024
|
||||
// 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。
|
||||
@JsonProperty("size")
|
||||
private String imageSize = "1024x1024";
|
||||
|
||||
// 可选字段,默认为vivid
|
||||
// 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。
|
||||
@JsonProperty("style")
|
||||
private String style = "vivid";
|
||||
|
||||
// 可选字段
|
||||
// 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。
|
||||
@JsonProperty("user")
|
||||
private String endUserId;
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.framework.ai.imageopenai.api;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* image 返回
|
||||
*
|
||||
* author: fansili
|
||||
* time: 2024/3/17 10:27
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OpenAiImageResponse {
|
||||
|
||||
private long created;
|
||||
private List<Item> data;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Item {
|
||||
|
||||
private String url;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package cn.iocoder.yudao.framework.ai.util;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Jackson工具类
|
||||
*
|
||||
* author: fansili
|
||||
* time: 2024/3/17 10:13
|
||||
*/
|
||||
public class JacksonUtil {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 初始化 ObjectMapper 以美化输出(即格式化JSON内容)
|
||||
*/
|
||||
static {
|
||||
// 美化输出(缩进)
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
// 忽略值为 null 的属性
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
// 配置一个模块来将 Long 类型转换为 String 类型
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
objectMapper.registerModule(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为 JSON 字符串
|
||||
*
|
||||
* @param obj 需要序列化的Java对象
|
||||
* @return 序列化后的 JSON 字符串
|
||||
* @throws JsonProcessingException 当 JSON 序列化过程中出现错误时抛出异常
|
||||
*/
|
||||
public static String toJson(Object obj) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(obj);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 字符串反序列化为指定类型的对象
|
||||
*
|
||||
* @param json JSON 字符串
|
||||
* @param clazz 目标类型 Class 对象
|
||||
* @param <T> 泛型类型参数
|
||||
* @return 反序列化后的 Java 对象
|
||||
* @throws IOException 当 JSON 解析过程中出现错误时抛出异常
|
||||
*/
|
||||
public static <T> T fromJson(String json, Class<T> clazz) {
|
||||
try {
|
||||
return objectMapper.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为格式化的 JSON 字符串(已启用 INDENT_OUTPUT 功能,所以所有方法都会返回格式化后的 JSON)
|
||||
*
|
||||
* @param obj 需要序列化的Java对象
|
||||
* @return 格式化后的 JSON 字符串
|
||||
* @throws JsonProcessingException 当 JSON 序列化过程中出现错误时抛出异常
|
||||
*/
|
||||
public static String toFormattedJson(Object obj) {
|
||||
// 已在类初始化时设置了 SerializationFeature.INDENT_OUTPUT,此处无需额外操作
|
||||
return toJson(obj);
|
||||
}
|
||||
}
|
@ -23,9 +23,9 @@ public class QianWenChatClientTests {
|
||||
@Before
|
||||
public void setup() {
|
||||
QianWenApi qianWenApi = new QianWenApi(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"LTAI5tNTVhXW4fLKUjMrr98z",
|
||||
"ZJ0JQeyjzxxm5CfeTV6k1wNE9UsvZP",
|
||||
"f0c1088824594f589c8f10567ccd929f_p_efm",
|
||||
null
|
||||
);
|
||||
qianWenChatClient = new QianWenChatClient(
|
||||
|
@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.framework.ai.image;
|
||||
|
||||
import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageApi;
|
||||
import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient;
|
||||
import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* author: fansili
|
||||
* time: 2024/3/17 10:40
|
||||
*/
|
||||
public class OpenAiImageClientTests {
|
||||
|
||||
|
||||
private OpenAiImageClient openAiImageClient;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// 初始化 openAiImageClient
|
||||
this.openAiImageClient = new OpenAiImageClient(
|
||||
new OpenAiImageApi(""),
|
||||
new OpenAiImageOptions()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callTest() {
|
||||
openAiImageClient.call(new ImagePrompt("我和我的小狗,一起在北极和企鹅玩排球。"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user