步骤 01 - Quarkus LangChain4j 简介
要开始,请确保您使用 step-01
目录。
此步骤是工作坊的起点。它是一个简单的 Quarkus 应用程序,使用 Quarkus LangChain4j 扩展与 OpenAI 的 GPT-4o 模型进行交互。它是一个简单的聊天机器人,我们将在后续步骤中对其进行扩展。
运行应用程序
使用以下命令运行应用程序
mvnw 权限问题
如果您遇到有关 mvnw
Maven 包装器的错误,您可以通过导航到项目文件夹并执行 chmod +x mvnw
来为该文件授予执行权限。
无法展开 OPENAI_API_KEY 值
如果您遇到一个指示 java.util.NoSuchElementException: SRCFG00011: Could not expand value OPENAI_API_KEY in property quarkus.langchain4j.openai.api-key
的错误,请确保您已将环境变量 OPENAI_API_KEY
设置为您的 OpenAI API 密钥。
这将启动位于 https://:8080 的页面。打开它,然后点击右下角的红色机器人图标开始与聊天机器人聊天。
与聊天机器人聊天
聊天机器人通过后端调用 GPT-4o(来自 OpenAI)。您可以进行测试并观察它具有记忆。示例
User: My name is Clement.
AI: Hi Clement, nice to meet you.
User: What is my name?
AI: Your name is Clement.
这就是为 LLM 构建记忆的方式。 在终端中,您可以观察到后台对 OpenAI 所做的调用。注意角色 ‘user’ (UserMessage
) 和 ‘assistant’ (AiMessage
)。
# The request -> Sending a message to the LLM
INFO [io.qua.lan.ope.OpenAiRestApi$OpenAiClientLogger] (vert.x-eventloop-thread-0) Request:
- method: POST
- url: https://api.openai.com/v1/chat/completions
- headers: [Accept: application/json], [Authorization: Be...ex], [Content-Type: application/json], [User-Agent: langchain4j-openai], [content-length: 378]
- body: {
"model" : "gpt-4o",
# The conversation so far, including the latest messages
"messages" : [ {
"role" : "user", # The role of the message (user or assistant)
"content" : "My name is Clement."
}, {
"role" : "assistant", # Assistant means LLM
"content" : "Hello, Clement! How can I assist you today?"
}, {
"role" : "user", # User means the user (you)
"content" : "What is my name?"
} ],
"temperature" : 1.0,
"top_p" : 1.0,
"presence_penalty" : 0.0,
"frequency_penalty" : 0.0
}
# The response from the LLM
INFO [io.qua.lan.ope.OpenAiRestApi$OpenAiClientLogger] (vert.x-eventloop-thread-0) Response:
- status code: 200
- headers: [Content-Type: application/json], [Transfer-Encoding: chunked], [Connection: keep-alive], [access-control-expose-headers: X-Request-ID], [openai-organization: user-vyycjqq0phctctikkw1zawlm], [openai-processing-ms: 213], [openai-version: 2020-10-01], [strict-transport-security: max-age=15552000; includeSubDomains; preload], [x-ratelimit-limit-requests: 500], [x-ratelimit-limit-tokens: 30000], [x-ratelimit-remaining-requests: 499], [x-ratelimit-remaining-tokens: 29958], [x-ratelimit-reset-requests: 120ms], [x-ratelimit-reset-tokens: 84ms], [x-request-id: req_2ea6d71590bc8d857260b25d9f414c0c], [CF-Cache-Status: DYNAMIC], [Set-Cookie: __...ne], [X-Content-Type-Options: nosniff], [Set-Cookie: _c...ne], [Server: cloudflare], [CF-RAY: 8c3ed3291afc27b2-LYS], [alt-svc: h3=":443"; ma=86400]
- body: {
"id": "chatcmpl-A7zaWTn1uMzq7Stw50Ug2Pg9TkBpV",
"object": "chat.completion",
"created": 1726468404,
"model": "gpt-4o-2024-05-13",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Your name is Clement. How can I help you today?",
"refusal": null
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 44,
"completion_tokens": 12,
"total_tokens": 56,
"completion_tokens_details": {
"reasoning_tokens": 0
}
},
"system_fingerprint": "fp_25624ae3a5"
}
与 LLM 交互的一个非常重要的方面是它们的无状态性。要构建对话,您需要重发迄今为止交换过的所有消息。该列表包括用户消息和助手消息。这就是记忆的构建方式,以及 LLM 如何提供上下文相关的响应。我们将在后续步骤中了解如何管理这一点。
应用程序的结构
在继续之前,让我们看一下代码。
如果您打开 pom.xml
文件,您会发现该项目是一个 Quarkus 应用程序,并带有 quarkus-langchain4j-openai
扩展。
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-openai</artifactId>
</dependency>
Quarkus LangChain4j OpenAI 是一个 Quarkus 扩展,它提供了一种与语言模型 (LLM) 交互的简单方法,例如 OpenAI 的 GPT-4o。它实际上可以与任何提供 OpenAI API 的模型(如 vLLM 或 Podman AI Lab)进行交互。Quarkus LangChain4j 抽象了调用模型的复杂性,并提供了一个简单的 API 来与之交互。
在我们的例子中,该应用程序是一个简单的聊天机器人。它使用WebSocket,这就是为什么您也可以在 pom.xml
文件中看到以下依赖项
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
如果您现在打开 src/main/java/dev/langchain4j/quarkus/workshop/CustomerSupportAgentWebSocket.java
文件,您可以看到 Web 套接字是如何实现的
package dev.langchain4j.quarkus.workshop;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@WebSocket(path = "/customer-support-agent")
public class CustomerSupportAgentWebSocket {
private final CustomerSupportAgent customerSupportAgent;
public CustomerSupportAgentWebSocket(CustomerSupportAgent customerSupportAgent) {
this.customerSupportAgent = customerSupportAgent;
}
@OnOpen
public String onOpen() {
return "Welcome to Miles of Smiles! How can I help you today?";
}
@OnTextMessage
public String onTextMessage(String message) {
return customerSupportAgent.chat(message);
}
}
基本上,它
- 连接打开时欢迎用户
- 收到消息时调用
CustomerSupportAgent
类的chat
方法,并将结果(通过 Web 套接字)发送回用户。
现在让我们看看应用程序的基石,CustomerSupportAgent
接口。
package dev.langchain4j.quarkus.workshop;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.SessionScoped;
@SessionScoped
@RegisterAiService
public interface CustomerSupportAgent {
String chat(String userMessage);
}
此接口用 @RegisterAiService
注释,以表明它是一个 AI 服务。 AI 服务 是由 Quarkus LangChain4j 扩展管理的。它模拟了与 AI 模型的交互。如您所见,它是一个接口,而不是一个具体的类,所以您不需要实现任何东西(感谢 Quarkus!)。Quarkus LangChain4j 将在构建时为您提供实现。因此,您的应用程序只与接口中定义的方法进行交互。
此接口中有一个方法 chat
,但您可以根据需要命名该方法。它将用户消息作为输入(由于它是唯一的参数,我们认为它是用户消息)并返回 AI 模型响应。 Quarkus LangChain4j 抽象了这一切。
SessionScoped
?
细心的读者可能已经注意到了 @SessionScoped
注释。这是一个 CDI 注释,它将对象的作用域限定为会话。在我们的例子中,会话就是 Web 套接字。会话在用户连接到 Web 套接字时开始,在用户断开连接时结束。此注释表明 CustomerSupportAgent
对象在会话开始时创建,在会话结束时销毁。它影响我们聊天机器人的记忆,因为它会记住在此会话中到目前为止的对话。
到目前为止一切顺利!让我们继续下一步。