Java 中的 OpenAI API 客户端
1、概览
随着生成式 AI 和 ChatGPT 的广泛应用,许多语言都开始提供与 OpenAI API 交互的库。Java 也不例外。
本文将带你了解 openai-java 库,它是一个开源的 OpenAI API 客户端,可以很方便地与 OpenAI API 通信。
2、依赖
首先,导入项目所需的 依赖,以下这三个模块专门用于交互的不同方面:
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.18.2</version>
</dependency>
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>api</artifactId>
<version>0.18.2</version>
</dependency>
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>client</artifactId>
<version>0.18.2</version>
</dependency>
请注意,依赖名称中明确提到了 GPT3,但它 也适用于 GPT4。
3、Baeldung 辅导员
接下来,我们要构建一个工具,尝试根据我们最喜欢的学习平台上的文章和教程来创建课程表。虽然互联网为我们提供了无限的资源,我们几乎可以在线找到任何东西,但筛选信息却很困难。
4. OpenAI API Token
第一步是将应用连接到 OpenAI API。为此,需要提供一个 OpenAI Token,该 Token 可在 OpenAI 网站 上生成:
注意,要小心保存你的 Token,避免暴露它。为此,openai-java
示例使用了环境变量。这可能不是用于生产的最佳解决方案,但在小型实验中完全可行。
在运行时,不一定需要为机器配置环境变量;大多数 IDE 都支持在运行时为应用单独设置环境变量,例如 IntelliJ IDEA。
我们可以生成两种 Token:个人(Personal)和服务账户(Service Account)。个人 Token 不言自明。服务账户 Token 用于连接到 OpenAI 项目的机器人或应用。虽然两者都可以使用,但对于我们的目的来说,个人 Token 已经足够了。
5、OpenAiService
OpenAI API 的入口是名为 OpenAiService
的类,非常方便。通过该类的实例,我们可以与 API 进行交互,并接收来自 ChatGPT 的响应。要创建该类,需要传递在上一步中生成的 Token:
String token = System.getenv("OPENAI_TOKEN");
OpenAiService service = new OpenAiService(token);
5.1、ChatCompletionRequest
使用 ChatCompletionRequest
创建一个请求。最低限度的 设置 要求我们只提供消息和一个模型:
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model(GPT_3_5_TURBO_0301.getName())
.messages(messages)
.build();
让我们一步步回顾一下这些参数。
5.2、Model
选择适合需求的模型非常重要,而且它也会影响 成本。因此,需要做出明智的选择。例如,在清理文本或根据一些简单格式解析文本时,通常不需要使用最先进的模型。与此同时,更复杂或更重要的任务需要更先进的模型来实现目标。
虽然可以直接传递模型名称,但最好使用 ModelEnum
枚举类:
@Getter
@AllArgsConstructor
public enum ModelEnum {
GPT_3_5_TURBO("gpt-3.5-turbo"),
GPT_3_5_TURBO_0301("gpt-3.5-turbo-0301"),
GPT_4("gpt-4"),
GPT_4_0314("gpt-4-0314"),
GPT_4_32K("gpt-4-32k"),
GPT_4_32K_0314("gpt-4-32k-0314"),
GPT_4_1106_preview("gpt-4-1106-preview");
private String name;
}
它并不包含所有的模型,但一般情况下,这已经足够了。如果想使用不同的模型,可以将其名称作为 String
提供。
5.3、Message
接下来是创建的消息。使用的是 ChatMessage
类。在本例中,只传递 role(角色)和 message(消息)本身:
List<ChatMessage> messages = new ArrayList<>();
ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), PROMPT);
messages.add(systemMessage);
这里发送的是一组消息,尽管在通常的聊天中,我们是一条一条地发送信息,但在这种情况下,它更类似于电子邮件 Threads。
系统在完成后继续追加下一条消息到消息链中。这样,我们可以保持对话的上下文。我们可以将其视为一个无状态的服务。然而,这意味着我们必须传递消息以保持上下文。
同时,我们还可以另辟蹊径,创建一个 assistant(助手)。通过这种方法,我们可以将消息存储在 Threads 中,而且不需要来回发送整个历史记录。
在传递消息时,消息的内容是合理的,但角色的目的并不是明确的。因为我们要一次性发送所有信息,所以需要提供某种方法,根据用户的角色来识别消息和用户之间的关系。
5.4、Role
如前所述,角色(Role)对于 ChatGPT 理解对话背景至关重要。我们可以用它们来识别消息背后的参与者。这样,就能帮助 ChatGPT 正确理解信息。聊天信息支持四种角色:聊天(Chat)、系统(System)、助手(Assistant)和功能(Function):
public enum ChatMessageRole {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
FUNCTION("function");
private final String value;
ChatMessageRole(final String value) {
this.value = value;
}
public String value() {
return value;
}
}
通常,SYSTEM 角色指的是初始上下文或提示。USER 代表 ChatGPT 的用户,而 ASSISTANT 本身就是一个 ChatGPT。这意味着,从技术上讲,我们也可以从 ASSISTANT 的角度编写消息。顾名思义,FUNCTION 角色确定了助手可以使用的功能。
5.5、Token
在模型和消息的上下文中的 Token 和上文所述的 API 的访问 Token 含义不同。可以把 Token 看作是我们可以处理的消息量和我们希望得到的响应量。
我们可以通过限制响应中的 Token 数来限制模型生成大量响应:
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model(MODEL)
.maxTokens(MAX_TOKENS) // 限制 Token
.messages(messages)
.build();
由于每个模型对单词和 Token 的处理略有不同,它们之间没有直接的映射关系。这个参数限制了回答的 Token 数量。使用默认值可能会导致过度回复,并增加使用费用。因此,明确配置这个参数是一个好的实践。
在每个响应后获取已使用 Token 的信息:
long usedTokens = result.getUsage().getTotalTokens();
System.out.println("Total tokens used: " + usedTokens);
5.6、Tokenization
上一个示例中输出了响应中使用的 Token 数量。虽然这一信息很有价值,但我们往往还需要估算请求的大小。为此,可以使用 OpenAI 提供的 Tokenizers。
为了以更自动化的方式完成这项工作,openai-java 为我们提供了 TikTokensUtil
,我们可以将模型名称和消息传递给它,然后得到 Token 的数量。
5.7、配置
还可以使用一个名为 n()
的神秘方法来配置请求。它可以控制每个请求得到多少个响应。简而言之,可以为同一个请求提供两个不同的响应。默认情况下,只有一个。
有时,它对机器人和网站助手也很有用。不过,响应是根据所有选项的 Token 计费的。
5.8、偏差和随机性
我们可以使用一些附加选项来控制 ChatGPT 答案的随机性和偏差。例如,logitBias()
可以提高看到或看不到特定 Token 的概率。注意,这里说的是 Token 而不是特定的单词。但是,这并不意味着这个 Token 不会 100% 出现。
此外,还可以使用 topP()
和 temperature()
来随机化响应。虽然这在某些情况下很有用,但本文用不着。
6、课程
现在,检查一下工具的运行情况。整体代码如下
public static void main(String[] args) {
String token = System.getenv("OPENAI_TOKEN");
OpenAiService service = new OpenAiService(token);
List<ChatMessage> messages = new ArrayList<>();
ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), PROMPT);
messages.add(systemMessage);
System.out.print(GREETING);
Scanner scanner = new Scanner(System.in);
ChatMessage firstMsg = new ChatMessage(ChatMessageRole.USER.value(), scanner.nextLine());
messages.add(firstMsg);
while (true) {
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model(GPT_3_5_TURBO_0301.getName())
.messages(messages)
.build();
ChatCompletionResult result = service.createChatCompletion(chatCompletionRequest);
long usedTokens = result.getUsage().getTotalTokens();
ChatMessage response = result.getChoices().get(0).getMessage();
messages.add(response);
System.out.println(response.getContent());
System.out.println("Total tokens used: " + usedTokens);
System.out.print("Anything else?\n");
String nextLine = scanner.nextLine();
if (nextLine.equalsIgnoreCase("exit")) {
System.exit(0);
}
messages.add(new ChatMessage(ChatMessageRole.USER.value(), nextLine));
}
}
运行后,就可以通过控制台与它进行交互:
Hello!
What do you want to learn?
对此,我们可以写出自己感兴趣的话题:
$ I would like to learn about binary trees.
和预期的一样,该工具会提供一些我们可以用来了解相关主题的文章:
Great! Here's a suggested order for Baeldung's articles on binary trees:
1. Introduction to Binary Trees: https://www.baeldung.com/java-binary-tree-intro
2. Implementing a Binary Tree in Java: https://www.baeldung.com/java-binary-tree
3. Depth First Traversal of Binary Tree: https://www.baeldung.com/java-depth-first-binary-tree-traversal
4. Breadth First Traversal of Binary Tree: https://www.baeldung.com/java-breadth-first-binary-tree-traversal
5. Finding the Maximum Element in a Binary Tree: https://www.baeldung.com/java-binary-tree-maximum-element
6. Binary Search Trees in Java: https://www.baeldung.com/java-binary-search-tree
7. Deleting from a Binary Search Tree: https://www.baeldung.com/java-binary-search-tree-delete
I hope this helps you out!
Total tokens used: 288
Anything else?
这样,我们就通过创建课程和学习新知识来解决问题。然而,并非一切都那么美好,问题在于只有一篇文章是真实的。在大多数情况下,ChatGPT 列出了不存在的文章,并附有相应的链接。虽然这些名称和链接听起来很合理,但它们不会给我们带来任何帮助。
这是任何 AI 工具的一个重要方面。生成模型很难检查信息的有效性。由于生成模型的基础是预测和挑选最合适的下一个词,因此可能很难验证信息的正确性。我们不能百分之百地依赖于生成模型的信息。
7、总结
从处理电子邮件、创建购物清单到优化教育,AI 工具都能帮助我们改进应用和自动化日常琐事。Java 提供了几种与 OpenAI API 交互的方法,如 openai-java 客户端库。
不过,重要的是要记住,生成模型尽管很有说服力,却很难验证信息是否真实。因此,我们有责任重新检查关键信息,或者为模型提供足够的信息,使其能够给出有效的答案。
Ref:https://www.baeldung.com/java-openai-api-client