Skip to content

Commit 79e2a6b

Browse files
authored
Merge pull request #166 from redhat-developer-demos/chains-memory
update chains and memory
2 parents dc51ffb + e8fbc00 commit 79e2a6b

1 file changed

Lines changed: 66 additions & 176 deletions

File tree

documentation/modules/ROOT/pages/18_chains_memory.adoc

Lines changed: 66 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
:project-ai-name: quarkus-langchain4j-app
44

5-
So far we explored how to use prompts with LLMs, however to really leverage the power of LLMs it is essential that you
6-
can build a conversation by referring to previous questions and answers and manage concurrent interactions.
5+
So far we explored how to use prompts with LLMs, however to really leverage the power of LLMs it is essential that you can build a conversation by referring to previous questions and answers and manage concurrent interactions.
76

87
In this section, we'll cover how we can achieve this with the LangChain4j extension in Quarkus.
98

@@ -22,61 +21,17 @@ import dev.langchain4j.service.MemoryId;
2221
import dev.langchain4j.service.UserMessage;
2322
import io.quarkiverse.langchain4j.RegisterAiService;
2423
25-
@RegisterAiService(/*chatMemoryProviderSupplier = RegisterAiService.BeanChatMemoryProviderSupplier.class*/)
24+
@RegisterAiService()
2625
public interface AssistantWithMemory {
2726
2827
String chat(@MemoryId Integer id, @UserMessage String msg);
2928
3029
}
3130
----
3231

33-
== Implement the ChatMemoryProvider
34-
35-
LangChain4j provides the interface `ChatMemoryProvider` to help us manage the memory of our conversations with the LLM.
36-
37-
Create a new `ChatMemoryBean` Java class in `src/main/java` in the `com.redhat.developers` package with the following contents:
38-
39-
[.console-input]
40-
[source,java]
41-
----
42-
package com.redhat.developers;
43-
44-
import java.util.Map;
45-
import java.util.concurrent.ConcurrentHashMap;
46-
47-
import jakarta.annotation.PreDestroy;
48-
import jakarta.enterprise.context.ApplicationScoped;
49-
50-
import dev.langchain4j.memory.ChatMemory;
51-
import dev.langchain4j.memory.chat.ChatMemoryProvider;
52-
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
53-
54-
@ApplicationScoped
55-
public class ChatMemoryBean implements ChatMemoryProvider {
56-
57-
private final Map<Object, ChatMemory> memories = new ConcurrentHashMap<>();
58-
59-
@Override
60-
public ChatMemory get(Object memoryId) {
61-
return memories.computeIfAbsent(memoryId, id -> MessageWindowChatMemory.builder() //<1>
62-
.maxMessages(20) //<2>
63-
.id(memoryId)
64-
.build());
65-
}
66-
67-
@PreDestroy
68-
public void close() {
69-
memories.clear();
70-
}
71-
}
72-
----
73-
<1> If no chat memory exists yet, create a new instance
74-
<2> Retain a maximum of 20 messages
75-
76-
7732
== Create a Developer resource
7833

79-
Now let's create a resource to help us write some code.
34+
Now let's create a resource to help us write some code, and then ask the model to create a test for the code as well in a second request. Thanks to the memory feature, the model will remember what code we're referring to from the first request.
8035

8136
Create a new `DeveloperResource` Java class in `src/main/java` in the `com.redhat.developers` package with the following contents:
8237

@@ -85,78 +40,54 @@ Create a new `DeveloperResource` Java class in `src/main/java` in the `com.redha
8540
----
8641
package com.redhat.developers;
8742
88-
import static dev.langchain4j.data.message.UserMessage.userMessage;
89-
import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
90-
9143
import jakarta.inject.Inject;
9244
import jakarta.ws.rs.GET;
9345
import jakarta.ws.rs.Path;
9446
import jakarta.ws.rs.Produces;
9547
import jakarta.ws.rs.core.MediaType;
9648
97-
import dev.langchain4j.chain.ConversationalChain;
98-
import dev.langchain4j.data.message.AiMessage;
99-
import dev.langchain4j.data.message.UserMessage;
100-
import dev.langchain4j.memory.ChatMemory;
101-
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
102-
import dev.langchain4j.model.Tokenizer;
103-
import dev.langchain4j.model.chat.ChatLanguageModel;
104-
import dev.langchain4j.model.openai.OpenAiTokenizer;
105-
import dev.langchain4j.model.output.Response;
106-
107-
@Path("/code")
49+
@Path("/")
10850
public class DeveloperResource {
10951
11052
@Inject
111-
private ChatLanguageModel model;
53+
private AssistantWithMemory ai;
11254
11355
@GET
114-
@Path("/rest")
56+
@Path("/memory")
11557
@Produces(MediaType.TEXT_PLAIN)
116-
public void createRestEndpoint() {
117-
118-
Tokenizer tokenizer = new OpenAiTokenizer();
119-
ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(1000, tokenizer);
120-
121-
UserMessage userMessage1 = userMessage(
122-
"How do I write a REST endpoint in Java using Quarkus? ");
123-
chatMemory.add(userMessage1);
58+
public String memory() {
59+
String msg1 = "How do I write a REST endpoint in Java using Quarkus?";
12460
125-
System.out.println("[User]: " + userMessage1.contents() + System.lineSeparator());
61+
String response = "[User]: " + msg1 + "\n\n" +
62+
"[LLM]: "+ ai.chat(1, msg1) + "\n\n\n" +
63+
"------------------------------------------\n\n\n";
12664
127-
final Response<AiMessage> response1 = model.generate(chatMemory.messages());
128-
chatMemory.add(response1.content());
65+
String msg2 = "Create a test of the first step. " +
66+
"Be short, 15 lines of code maximum.";
67+
68+
response += "[User]: " + msg2 + "\n\n"+
69+
"[LLM]: "+ ai.chat(1, msg2);
12970
130-
System.out.println("[LLM]: " + response1.content().text() + System.lineSeparator());
131-
132-
UserMessage userMessage2 = userMessage(
133-
"Create a test of the first point. " +
134-
"Be short, 15 lines of code maximum.");
135-
chatMemory.add(userMessage2);
136-
137-
System.out.println("[User]: " + userMessage2.contents() + System.lineSeparator());
138-
139-
final Response<AiMessage> response2 = model.generate(chatMemory.messages());
140-
141-
System.out.println("[LLM]: " + response2.content().text() + System.lineSeparator());
71+
return response;
14272
14373
}
74+
14475
}
14576
----
14677

14778
== Invoke the endpoint
14879

149-
You can check your prompt implementation by pointing your browser to http://localhost:8080/code/rest[window=_blank]
80+
You can check your prompt implementation by pointing your browser to http://localhost:8080/memory[window=_blank]
15081

15182
You can also run the following command in your terminal:
15283

15384
[.console-input]
15485
[source,bash]
15586
----
156-
curl localhost:8080/code/rest
87+
curl localhost:8080/memory
15788
----
15889

159-
The result will be in the logs of your Quarkus application (ie. the terminal where you're running the `quarkus dev` command). An example of output (it can vary on each prompt execution):
90+
An example of output (can vary on each prompt execution):
16091

16192
[.console-output]
16293
[source,text]
@@ -200,7 +131,7 @@ public class HelloResource {
200131
This class defines two REST endpoints: `/hello` for saying hello to the world, and `/hello/{name}` for saying hello to a specific name. You can access these endpoints at `http://localhost:8080/hello` and `http://localhost:8080/hello/{name}` respectively.
201132
202133
203-
[User]: Create a test of the first point. Be short, 15 lines of code maximum.
134+
[User]: Create a test of the first step. Be short, 15 lines of code maximum.
204135
205136
[LLM]: Here's an example of a simple test for the `sayHello` endpoint in Quarkus using JUnit:
206137
@@ -231,138 +162,97 @@ In this test, we are using the QuarkusTest annotation to run the test in the Qua
231162
232163
----
233164

234-
Let's now get some help to learn a little bit about Kubernetes.
235165

236-
Add a new `generateKubernetes()` method to the `DeveloperResource` class:
166+
167+
== How to index a conversation
168+
169+
We can use the LangChain4j extension to index a conversation so we can reuse it, and keep multiple, parallel conversations separated.
170+
171+
Let's add a new `guessWho()` method to our `DeveloperResource`:
237172

238173
[.console-input]
239174
[source,java]
240175
----
241176
@GET
242-
@Path("/k8s")
177+
@Path("/guess")
243178
@Produces(MediaType.TEXT_PLAIN)
244-
public void generateKubernetes() {
179+
public String guess() {
180+
String msg1FromUser1 = "Hello, my name is Klaus and I'm a doctor";
181+
182+
String response = "[User1]: " + msg1FromUser1 + "\n\n" +
183+
"[LLM]: " + ai.chat(1, msg1FromUser1) + "\n\n\n" +
184+
"------------------------------------------\n\n\n";
245185
246-
ConversationalChain chain = ConversationalChain.builder()
247-
.chatLanguageModel(model)
248-
.build();
186+
String msg1FromUser2 = "Hi, I'm Francine and I'm a lawyer";
249187
250-
String userMessage1 = "Can you give a brief explanation of Kubernetes, 3 lines max?";
251-
System.out.println("[User]: " + userMessage1 + System.lineSeparator());
188+
response += "[User2]: " + msg1FromUser2 + "\n\n" +
189+
"[LLM]: " + ai.chat(2, msg1FromUser2) + "\n\n\n" +
190+
"------------------------------------------\n\n\n";
252191
253-
String answer1 = chain.execute(userMessage1);
254-
System.out.println("[LLM]: " + answer1 + System.lineSeparator());
192+
String msg2FromUser2 = "What is my name?";
255193
256-
String userMessage2 = "Can you give me a YAML example to deploy an application for that?";
257-
System.out.println("[User]: " + userMessage2 + System.lineSeparator());
194+
response += "[User2]: " + msg2FromUser2 + "\n\n" +
195+
"[LLM]: " + ai.chat(2, msg2FromUser2) + "\n\n\n" +
196+
"------------------------------------------\n\n\n";
258197
259-
String answer2 = chain.execute(userMessage2);
260-
System.out.println("[LLM]: " + answer2);
198+
String msg2FromUser1 = "What is my profession?";
261199
200+
response += "[User1]: " + msg2FromUser1 + "\n\n" +
201+
"[LLM]: " + ai.chat(1, msg2FromUser1) + "\n\n\n" +
202+
"------------------------------------------\n\n\n";
203+
204+
return response;
262205
}
206+
263207
----
264208

265209
== Invoke the endpoint
266210

267-
You can check your prompt implementation by pointing your browser to http://localhost:8080/code/k8s[window=_blank]
211+
You can check your implementation by pointing your browser to http://localhost:8080/guess[window=_blank]
268212

269213
You can also run the following command:
270214

271215
[.console-input]
272216
[source,bash]
273217
----
274-
curl localhost:8080/code/k8s
218+
curl localhost:8080/guess
275219
----
276220

277-
The result will be once again in your Quarkus application logs. An example of output (it can vary on each prompt execution):
221+
The result will be at your Quarkus terminal. An example of output (it can vary on each prompt execution):
278222

279223
[.console-output]
280224
[source,text]
281225
----
282-
[User]: Can you give a brief explanation of Kubernetes, 3 lines max?
283-
284-
[LLM]: Kubernetes is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. It simplifies the process of managing and coordinating large numbers of containers across multiple clusters. Kubernetes provides a scalable and efficient way to deploy and manage containerized applications in a production-ready environment.
285-
286-
287-
[User]: Can you give me a YAML example to deploy an application for that?
288-
289-
[LLM]: Sure! Here is an example of a simple YAML file that deploys a sample application using Kubernetes:
290-
291-
```yaml
292-
apiVersion: apps/v1
293-
kind: Deployment
294-
metadata:
295-
name: sample-app
296-
spec:
297-
replicas: 3
298-
selector:
299-
matchLabels:
300-
app: sample-app
301-
template:
302-
metadata:
303-
labels:
304-
app: sample-app
305-
spec:
306-
containers:
307-
- name: sample-app
308-
image: nginx:latest
309-
ports:
310-
- containerPort: 80
311-
```
226+
[User1]: Hello, my name is Klaus and I'm a doctor
312227
313-
Save this YAML file as `sample-app-deployment.yaml` and apply it using the `kubectl apply -f sample-app-deployment.yaml` command to deploy the sample application with 3 replicas running NGINX.
314-
----
228+
[LLM]: Nice to meet you, Klaus! What field of medicine do you specialize in?
315229
316-
== How to index a conversation
317230
318-
We can use the LangChain4j extension to index a conversation so we can reuse it.
231+
------------------------------------------
319232

320-
Let's inject an instance of the `AssistantWithMemory` class and add a new `guessWho()` method to our `DeveloperResource`:
321233

322-
[.console-input]
323-
[source,java]
324-
----
325-
@Inject
326-
AssistantWithMemory assistant;
234+
[User2]: Hi, I'm Francine and I'm a lawyer
327235

328-
@GET
329-
@Path("/guess")
330-
@Produces(MediaType.TEXT_PLAIN)
331-
public void guessWho() {
236+
[LLM]: Hello Francine, nice to meet you. How can I assist you today?
332237

333-
System.out.println(assistant.chat(1, "Hello, my name is Klaus, and I'm a Doctor"));
334238

335-
System.out.println(assistant.chat(2, "Hello, my name is Francine, and I'm a Lawyer"));
239+
------------------------------------------
336240
337-
System.out.println(assistant.chat(1, "What is my name?"));
338241
339-
System.out.println(assistant.chat(2, "What is my profession?"));
242+
[User2]: What is my name?
340243
341-
}
244+
[LLM]: Your name is Francine, and you mentioned earlier that you are a lawyer. How can I assist you today, Francine?
342245
343-
----
344246
345-
== Invoke the endpoint
247+
------------------------------------------
346248

347-
You can check your implementation by pointing your browser to http://localhost:8080/code/guess[window=_blank]
348249

349-
You can also run the following command:
250+
[User1]: What is my profession?
350251

351-
[.console-input]
352-
[source,bash]
353-
----
354-
curl localhost:8080/code/guess
355-
----
252+
[LLM]: Your profession is being a doctor, Klaus. How can I assist you today?
356253

357-
The result will be at your Quarkus terminal. An example of output (it can vary on each prompt execution):
358254

359-
[.console-output]
360-
[source,text]
361-
----
362-
Hello Klaus, it's nice to meet you. What type of doctor are you?
363-
Hello Francine, nice to meet you! How can I assist you today?
364-
Your name is Klaus.
365-
Your profession is a Lawyer. You are legally trained and licensed to represent clients in legal matters.
255+
------------------------------------------
366256
----
367257

368-
NOTE: You might be confused by the responses (ie. Klaus is not a lawyer but a doctor). Take a close look at the IDs of our calls to the assistant. Do you notice that the last question was in fact directed to Francine with ID=2? We were indeed able to maintain 2 separate and concurrent conversations with the LLM!
258+
NOTE: Take a close look at the IDs of our calls to the assistant. Do you notice that the last question was in fact directed to Klaus with ID=1? We were indeed able to maintain 2 separate and concurrent conversations with the LLM!

0 commit comments

Comments
 (0)