📌 Simple-OpenAI
一个用于以最简单的方式使用OpenAI API的Java库。
目录
💡 描述
Simple-OpenAI是一个用于发送请求和接收OpenAI API响应的Java http客户端库。它在所有服务中都提供了一个一致的接口,就像您在其他语言(如Python或NodeJs)中找到的一样简单。它是一个非官方的库。
Simple-OpenAI使用CleverClient库进行http通信,Jackson进行Json解析,以及Lombok最小化样板代码,等等其他库。
✅ 支持的服务
Simple-OpenAI旨在与OpenAI的最新变化保持同步。目前,它支持截至2024年7月18日的所有现有功能,并将继续随着未来的变化而更新。
完全支持所有的OpenAI服务:
- 音频(语音,转录,翻译)
- 批处理(批次的聊天完成)
- 聊天完成(文本生成,流式,函数调用,视觉)
- 完成(遗留文本生成)
- 嵌入(文本向量化)
- 文件(上传文件)
- 微调(定制模型)
- 图像(生成,编辑,变体)
- 模型(列表)
- 审核(检查有害文本)
- 上传(分阶段上传大文件)新
- 助手Beta v2(助手,线程,消息,运行,步骤,向量存储,流式,函数调用,视觉)
注意:
- 方法的响应为
CompletableFuture<ResponseObject>
,这意味着它们是异步的,但您可以调用join()方法以在完成时返回结果值。 - 上述点的例外情况是名称以后缀
AndPoll()
结尾的方法。这些方法是同步的,直到您提供的谓词函数返回false。
⚙ 安装
您可以通过将以下依赖项添加到您的Maven项目中来安装Simple-OpenAI:
<dependency>
<groupId>io.github.sashirestela</groupId>
<artifactId>simple-openai</artifactId>
<version>[最新版本]</version>
</dependency>
或者使用Gradle:
dependencies {
implementation 'io.github.sashirestela:simple-openai:[最新版本]'
}
📘 使用
创建一个SimpleOpenAI对象
在使用这些服务之前,您需要首先创建一个SimpleOpenAI对象。您必须提供至少您的_OpenAI Api Key_(见这里了解更多详情)。在下例中,我们从名为OPENAI_API_KEY
的环境变量中获取Api Key,将其保存在已创建的变量中:
var openAI = SimpleOpenAI.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
如果您有多个组织,并且希望通过组织来识别使用情况,您还可以选择性地传递您的_OpenAI Organization Id_,或者,如果您希望提供对单个项目的访问,您可以传递您的_OpenAI Project Id_。在下例中,我们使用环境变量获取这些ID:
var openAI = SimpleOpenAI.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.organizationId(System.getenv("OPENAI_ORGANIZATION_ID"))
.projectId(System.getenv("OPENAI_PROJECT_ID"))
.build();
同样,您还可以提供一个自定义Java HttpClient对象,如果您希望对http连接有更多选项,例如执行器、代理、超时、cookies等(见这里了解更多详情)。在下例中,我们提供了一个自定义HttpClient:
var httpClient = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.executor(Executors.newFixedThreadPool(3))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
.build();
var openAI = SimpleOpenAI.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.httpClient(httpClient)
.build();
在创建了一个SimpleOpenAI对象后,您就可以调用其服务与OpenAI API通信了。让我们看一些示例。
音频示例
调用音频服务将文本转换为音频的示例。我们请求以二进制格式(InputStream)接收音频:
var speechRequest = SpeechRequest.builder()
.model("tts-1")
.input("Hello world, welcome to the AI universe!")
.voice(Voice.ALLOY)
.responseFormat(SpeechResponseFormat.MP3)
.speed(1.0)
.build();
var futureSpeech = openAI.audios().speak(speechRequest);
var speechResponse = futureSpeech.join();
try {
var audioFile = new FileOutputStream(speechFileName);
audioFile.write(speechResponse.readAllBytes());
System.out.println(audioFile.getChannel().size() + " bytes");
audioFile.close();
} catch (Exception e) {
e.printStackTrace();
}
调用音频服务将音频转录成文本的示例。我们请求以纯文本格式接收转录(参见方法名):
var audioRequest = TranscriptionRequest.builder()
.file(Paths.get("hello_audio.mp3"))
.model("whisper-1")
.responseFormat(AudioResponseFormat.VERBOSE_JSON)
.temperature(0.2)
.timestampGranularity(TimestampGranularity.WORD)
.timestampGranularity(TimestampGranularity.SEGMENT)
.build();
var futureAudio = openAI.audios().transcribe(audioRequest);
var audioResponse = futureAudio.join();
System.out.println(audioResponse);
图片示例
调用图片服务以响应我们的提示生成两张图片的示例。我们请求接收图片的网址,并将其打印到控制台:
var imageRequest = ImageRequest.builder()
.prompt("A cartoon of a hummingbird that is flying around a flower.")
.n(2)
.size(Size.X256)
.responseFormat(ImageResponseFormat.URL)
.model("dall-e-2")
.build();
var futureImage = openAI.images().create(imageRequest);
var imageResponse = futureImage.join();
imageResponse.stream().forEach(img -> System.out.println("\n" + img.getUrl()));
聊天补全示例
调用聊天补全服务以提问并等待完整答案的示例。我们将其打印到控制台:
var chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.message(SystemMessage.of("You are an expert in AI."))
.message(UserMessage.of("Write a technical article about ChatGPT, no more than 100 words."))
.temperature(0.0)
.maxTokens(300)
.build();
var futureChat = openAI.chatCompletions().create(chatRequest);
var chatResponse = futureChat.join();
System.out.println(chatResponse.firstContent());
流模式下的聊天补全示例
调用聊天补全服务以提问并等待部分消息增量的示例。当每个增量到达时,我们会在控制台中立即打印:
var chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.message(SystemMessage.of("You are an expert in AI."))
.message(UserMessage.of("Write a technical article about ChatGPT, no more than 100 words."))
.temperature(0.0)
.maxTokens(300)
.build();
var futureChat = openAI.chatCompletions().createStream(chatRequest);
var chatResponse = futureChat.join();
chatResponse.filter(chatResp -> chatResp.getChoices().size() > 0 && chatResp.firstContent() != null)
.map(Chat::firstContent)
.forEach(System.out::print);
System.out.println();
带功能的聊天补全示例
此功能使聊天补全服务能够解决我们上下文中的具体问题。在此示例中,我们设置了三个功能,并输入了一个将调用其中一个功能(product
功能)的提示。为了设置功能,我们使用了实现Functional
接口的附加类。那些类通过每个函数参数定义字段,使用注释描述它们,并且每个类必须重写 execute
方法来实现函数的逻辑。请注意,我们使用 FunctionExecutor
实用类来注册函数,并执行由 openai.chatCompletions()
调用选择的函数:
public void demoCallChatWithFunctions() {
var functionExecutor = new FunctionExecutor();
functionExecutor.enrollFunction(
FunctionDef.builder()
.name("get_weather")
.description("Get the current weather of a location")
.functionalClass(Weather.class)
.build());
functionExecutor.enrollFunction(
FunctionDef.builder()
.name("product")
.description("Get the product of two numbers")
.functionalClass(Product.class)
.build());
functionExecutor.enrollFunction(
FunctionDef.builder()
.name("run_alarm")
.description("Run an alarm")
.functionalClass(RunAlarm.class)
.build());
var messages = new ArrayList<ChatMessage>();
messages.add(UserMessage.of("What is the product of 123 and 456?"));
chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
.tools(functionExecutor.getToolFunctions())
.build();
var futureChat = openAI.chatCompletions().create(chatRequest);
var chatResponse = futureChat.join();
var chatMessage = chatResponse.firstMessage();
var chatToolCall = chatMessage.getToolCalls().get(0);
var result = functionExecutor.execute(chatToolCall.getFunction());
messages.add(chatMessage);
messages.add(ToolMessage.of(result.toString(), chatToolCall.getId()));
chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
.tools(functionExecutor.getToolFunctions())
.build();
futureChat = openAI.chatCompletions().create(chatRequest);
chatResponse = futureChat.join();
System.out.println(chatResponse.firstContent());
}
public static class Weather implements Functional {
@JsonPropertyDescription("市和州,例如:León, Guanajuato")
public String location;
@JsonPropertyDescription("温度单位,可以是 'celsius' 或 'fahrenheit'")
@JsonProperty(required = true)
public String unit;
@Override
public Object execute() {
return Math.random() * 45;
}
}
public static class Product implements Functional {
@JsonPropertyDescription("乘积的被乘数部分")
@JsonProperty(required = true)
public double multiplicand;
@JsonPropertyDescription("乘积的乘数部分")
@JsonProperty(required = true)
public double multiplier;
@Override
public Object execute() {
return multiplicand * multiplier;
}
}
public static class RunAlarm implements Functional {
@Override
public Object execute() {
return "DONE";
}
}
带视觉功能的聊天补全示例
调用聊天补全服务,以允许模型获取外部图像并回答有关这些图像的问题的示例:
var chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
"What do you see in the image? Give in details in no more than 100 words."),
ContentPartImageUrl.of(ImageUrl.of(
"https://upload.wikimedia.org/wikipedia/commons/e/eb/Machu_Picchu%2C_Peru.jpg"))))))
.temperature(0.0)
.maxTokens(500)
.build();
var chatResponse = openAI.chatCompletions().createStream(chatRequest).join();
chatResponse.filter(chatResp -> chatResp.getChoices().size() > 0 && chatResp.firstContent() != null)
.map(Chat::firstContent)
.forEach(System.out::print);
System.out.println();
调用聊天补全服务,以允许模型获取本地图像并回答有关这些图像的问题的示例:
var chatRequest = ChatRequest.builder()
.model("gpt-4o-mini")
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
"What do you see in the image? Give in details in no more than 100 words."),
ContentPartImageUrl.of(loadImageAsBase64("src/demo/resources/machupicchu.jpg"))))))
.temperature(0.0)
.maxTokens(500)
.build();
var chatResponse = openAI.chatCompletions().createStream(chatRequest).join();
chatResponse.filter(chatResp -> chatResp.getChoices().size() > 0 && chatResp.firstContent() != null)
.map(Chat::firstContent)
.forEach(System.out::print);
System.out.println();
private static ImageUrl loadImageAsBase64(String imagePath) {
try {
Path path = Paths.get(imagePath);
byte[] imageBytes = Files.readAllBytes(path);
String base64String = Base64.getEncoder().encodeToString(imageBytes);
var extension = imagePath.substring(imagePath.lastIndexOf('.') + 1);
var prefix = "data:image/" + extension + ";base64,";
return ImageUrl.of(prefix + base64String);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
聊天补全会话示例
此示例通过命令控制台模拟会话聊天,并演示了带流和调用功能的ChatCompletion的用法。
您可以查看完整的演示代码以及运行演示代码的结果:
演示代码
package io.github.sashirestela.openai.demo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.github.sashirestela.openai.SimpleOpenAI;
import io.github.sashirestela.openai.common.function.FunctionDef;
import io.github.sashirestela.openai.common.function.FunctionExecutor;
import io.github.sashirestela.openai.common.function.Functional;
import io.github.sashirestela.openai.common.tool.ToolCall;
import io.github.sashirestela.openai.domain.chat.Chat;
import io.github.sashirestela.openai.domain.chat.Chat.Choice;
import io.github.sashirestela.openai.domain.chat.ChatMessage;
import io.github.sashirestela.openai.domain.chat.ChatMessage.AssistantMessage;
import io.github.sashirestela.openai.domain.chat.ChatMessage.ResponseMessage;
import io.github.sashirestela.openai.domain.chat.ChatMessage.ToolMessage;
import io.github.sashirestela.openai.domain.chat.ChatMessage.UserMessage;
import io.github.sashirestela.openai.domain.chat.ChatRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ConversationDemo {
private SimpleOpenAI openAI;
private FunctionExecutor functionExecutor;
private int indexTool;
private StringBuilder content;
private StringBuilder functionArgs;
public ConversationDemo() {
openAI = SimpleOpenAI.builder().apiKey(System.getenv("OPENAI_API_KEY")).build();
}
public void prepareConversation() {
List<FunctionDef> functionList = new ArrayList<>();
functionList.add(FunctionDef.builder()
.name("getCurrentTemperature")
.description("Get the current temperature for a specific location")
.functionalClass(CurrentTemperature.class)
.build());
functionList.add(FunctionDef.builder()
.name("getRainProbability")
.description("Get the probability of rain for a specific location")
.functionalClass(RainProbability.class)
.build());
functionExecutor = new FunctionExecutor(functionList);
}
public void runConversation() {
List<ChatMessage> messages = new ArrayList<>();
var myMessage = System.console().readLine("\nWelcome! Write any message: ");
messages.add(UserMessage.of(myMessage));
while (!myMessage.toLowerCase().equals("exit")) {
var chatStream = openAI.chatCompletions()
.createStream(ChatRequest.builder()
.model("gpt-4o")
.messages(messages)
.tools(functionExecutor.getToolFunctions())
.temperature(0.2)
.stream(true)
.build())
.join();
indexTool = -1;
content = new StringBuilder();
functionArgs = new StringBuilder();
var response = getResponse(chatStream);
if (response.getMessage().getContent() != null) {
messages.add(AssistantMessage.of(response.getMessage().getContent()));
}
if (response.getFinishReason().equals("tool_calls")) {
messages.add(response.getMessage());
var toolCalls = response.getMessage().getToolCalls();
var toolMessages = functionExecutor.executeAll(toolCalls,
(toolCallId, result) -> ToolMessage.of(result, toolCallId));
messages.addAll(toolMessages);
} else {
myMessage = System.console().readLine("\n\nWrite any message (or write 'exit' to finish): ");
messages.add(UserMessage.of(myMessage));
}
}
}
private Choice getResponse(Stream<Chat> chatStream) {
var choice = new Choice();
choice.setIndex(0);
var chatMsgResponse = new ResponseMessage();
List<ToolCall> toolCalls = new ArrayList<>();
chatStream.forEach(responseChunk -> { var choices = responseChunk.getChoices(); if (choices.size() > 0) { var innerChoice = choices.get(0); var delta = innerChoice.getMessage(); if (delta.getRole() != null) { chatMsgResponse.setRole(delta.getRole()); } else if (delta.getContent() != null && !delta.getContent().isEmpty()) { content.append(delta.getContent()); System.out.print(delta.getContent()); } else if (delta.getToolCalls() != null) { var toolCall = delta.getToolCalls().get(0); if (toolCall.getIndex() != indexTool) { if (toolCalls.size() > 0) { toolCalls.get(toolCalls.size() - 1).getFunction().setArguments(functionArgs.toString()); functionArgs = new StringBuilder(); } toolCalls.add(toolCall); indexTool++; } else { functionArgs.append(toolCall.getFunction().getArguments()); } } else { if (content.length() > 0) { chatMsgResponse.setContent(content.toString()); } if (toolCalls.size() > 0) { toolCalls.get(toolCalls.size() - 1).getFunction().setArguments(functionArgs.toString()); chatMsgResponse.setToolCalls(toolCalls); } choice.setMessage(chatMsgResponse); choice.setFinishReason(innerChoice.getFinishReason()); } } }); return choice; }
public static void main(String[] args) {
var demo = new ConversationDemo();
demo.prepareConversation();
demo.runConversation();
}
public static class CurrentTemperature implements Functional {
@JsonPropertyDescription("The city and state, e.g., San Francisco, CA")
@JsonProperty(required = true)
public String location;
@JsonPropertyDescription("The temperature unit to use. Infer this from the user's location.")
@JsonProperty(required = true)
public String unit;
@Override
public Object execute() {
double centigrades = Math.random() * (40.0 - 10.0) + 10.0;
double fahrenheit = centigrades * 9.0 / 5.0 + 32.0;
String shortUnit = unit.substring(0, 1).toUpperCase();
return shortUnit.equals("C") ? centigrades : (shortUnit.equals("F") ? fahrenheit : 0.0);
}
}
public static class RainProbability implements Functional {
@JsonPropertyDescription("The city and state, e.g., San Francisco, CA")
@JsonProperty(required = true)
public String location;
@Override
public Object execute() {
return Math.random() * 100;
}
}
}
System.out.println("文件已删除: " + deletedFile.getDeleted());
System.out.println("向量存储已删除: " + deletedVectorStore.getDeleted());
System.out.println("助手已删除: " + deletedAssistant.getDeleted());
System.out.println("线程已删除: " + deletedThread.getDeleted());
}
public static void main(String[] args) {
var demo = new ConversationV2Demo();
demo.prepareConversation();
demo.runConversation();
demo.cleanConversation();
}
public static class CurrentTemperature implements Functional {
@JsonPropertyDescription("城市和州,例如,旧金山,加利福尼亚州")
@JsonProperty(required = true)
public String location;
@JsonPropertyDescription("使用的温度单位。根据用户的位置推断。")
@JsonProperty(required = true)
public String unit;
@Override
public Object execute() {
double centigrades = Math.random() * (40.0 - 10.0) + 10.0;
double fahrenheit = centigrades * 9.0 / 5.0 + 32.0;
String shortUnit = unit.substring(0, 1).toUpperCase();
return shortUnit.equals("C") ? centigrades : (shortUnit.equals("F") ? fahrenheit : 0.0);
}
}
public static class RainProbability implements Functional {
@JsonPropertyDescription("城市和州,例如,旧金山,加利福尼亚州")
@JsonProperty(required = true)
public String location;
@Override
public Object execute() {
return Math.random() * 100;
}
}
❤ 展现您的支持
感谢您使用 simple-openai。如果您觉得这个项目有价值,可以通过以下几种方式来表达您的支持,最好是全部都做 🙂:
- 告诉您的朋友这个项目 🗣📢。
- 在您的社交网络上写一个简短的评价 ✍🌐。
- 在Github上给我们一个星星 ⭐。