Skip to content

Commit 34170ae

Browse files
authored
Merge pull request #1797 from fedinskiy/chatbot-tests
Add tests for the `chatbot` sample and allow them to run with platform BOMs
2 parents 8fa3f30 + b1fc04c commit 34170ae

File tree

3 files changed

+207
-52
lines changed

3 files changed

+207
-52
lines changed

samples/chatbot/pom.xml

Lines changed: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
34
<modelVersion>4.0.0</modelVersion>
45

56
<groupId>io.quarkiverse.langchain4j</groupId>
67
<artifactId>quarkus-langchain4j-sample-chatbot</artifactId>
7-
<name>Quarkus LangChain4j - Sample - Chatbot &amp; RAG</name>
8+
<name>Quarkus LangChain4j - Sample - Chatbot &amp; RAG</name>
89
<version>1.0-SNAPSHOT</version>
910

1011
<properties>
@@ -15,24 +16,12 @@
1516
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
1617
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
1718
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
18-
<quarkus.platform.version>3.20.0</quarkus.platform.version>
19+
<quarkus.platform.version>3.26.4</quarkus.platform.version>
1920
<skipITs>true</skipITs>
21+
<skipTests>true</skipTests>
2022
<surefire-plugin.version>3.2.5</surefire-plugin.version>
21-
<quarkus-langchain4j.version>1.2.0.CR3</quarkus-langchain4j.version>
2223
</properties>
2324

24-
<dependencyManagement>
25-
<dependencies>
26-
<dependency>
27-
<groupId>${quarkus.platform.group-id}</groupId>
28-
<artifactId>${quarkus.platform.artifact-id}</artifactId>
29-
<version>${quarkus.platform.version}</version>
30-
<type>pom</type>
31-
<scope>import</scope>
32-
</dependency>
33-
</dependencies>
34-
</dependencyManagement>
35-
3625
<dependencies>
3726
<dependency>
3827
<groupId>io.quarkus</groupId>
@@ -43,14 +32,14 @@
4332
<artifactId>quarkus-websockets-next</artifactId>
4433
</dependency>
4534
<dependency>
46-
<groupId>io.quarkiverse.langchain4j</groupId>
47-
<artifactId>quarkus-langchain4j-openai</artifactId>
48-
<version>${quarkus-langchain4j.version}</version>
35+
<groupId>io.quarkus</groupId>
36+
<artifactId>quarkus-junit5</artifactId>
37+
<scope>test</scope>
4938
</dependency>
5039
<dependency>
51-
<groupId>io.quarkiverse.langchain4j</groupId>
52-
<artifactId>quarkus-langchain4j-redis</artifactId>
53-
<version>${quarkus-langchain4j.version}</version>
40+
<groupId>org.awaitility</groupId>
41+
<artifactId>awaitility</artifactId>
42+
<scope>test</scope>
5443
</dependency>
5544

5645
<!-- UI -->
@@ -71,34 +60,6 @@
7160
<version>0.2.1</version>
7261
<scope>runtime</scope>
7362
</dependency>
74-
75-
<!-- Minimal dependencies to constrain the build -->
76-
<dependency>
77-
<groupId>io.quarkiverse.langchain4j</groupId>
78-
<artifactId>quarkus-langchain4j-openai-deployment</artifactId>
79-
<version>${quarkus-langchain4j.version}</version>
80-
<scope>test</scope>
81-
<type>pom</type>
82-
<exclusions>
83-
<exclusion>
84-
<groupId>*</groupId>
85-
<artifactId>*</artifactId>
86-
</exclusion>
87-
</exclusions>
88-
</dependency>
89-
<dependency>
90-
<groupId>io.quarkiverse.langchain4j</groupId>
91-
<artifactId>quarkus-langchain4j-redis-deployment</artifactId>
92-
<version>${quarkus-langchain4j.version}</version>
93-
<scope>test</scope>
94-
<type>pom</type>
95-
<exclusions>
96-
<exclusion>
97-
<groupId>*</groupId>
98-
<artifactId>*</artifactId>
99-
</exclusion>
100-
</exclusions>
101-
</dependency>
10263
</dependencies>
10364
<build>
10465
<plugins>
@@ -132,6 +93,76 @@
13293
</build>
13394

13495
<profiles>
96+
<profile>
97+
<id>default-project-deps</id>
98+
<activation>
99+
<property>
100+
<name>!platform-deps</name>
101+
</property>
102+
</activation>
103+
<properties>
104+
<quarkus-langchain4j.version>1.2.0.CR3</quarkus-langchain4j.version>
105+
</properties>
106+
<dependencyManagement>
107+
<dependencies>
108+
<dependency>
109+
<groupId>${quarkus.platform.group-id}</groupId>
110+
<artifactId>${quarkus.platform.artifact-id}</artifactId>
111+
<version>${quarkus.platform.version}</version>
112+
<type>pom</type>
113+
<scope>import</scope>
114+
</dependency>
115+
</dependencies>
116+
</dependencyManagement>
117+
<dependencies>
118+
<dependency>
119+
<groupId>io.quarkiverse.langchain4j</groupId>
120+
<artifactId>quarkus-langchain4j-openai</artifactId>
121+
<version>${quarkus-langchain4j.version}</version>
122+
</dependency>
123+
<dependency>
124+
<groupId>io.quarkiverse.langchain4j</groupId>
125+
<artifactId>quarkus-langchain4j-redis</artifactId>
126+
<version>${quarkus-langchain4j.version}</version>
127+
</dependency>
128+
</dependencies>
129+
</profile>
130+
<profile>
131+
<id>platform-deps</id>
132+
<activation>
133+
<property>
134+
<name>platform-deps</name>
135+
</property>
136+
</activation>
137+
<dependencyManagement>
138+
<dependencies>
139+
<dependency>
140+
<groupId>${quarkus.platform.group-id}</groupId>
141+
<artifactId>${quarkus.platform.artifact-id}</artifactId>
142+
<version>${quarkus.platform.version}</version>
143+
<type>pom</type>
144+
<scope>import</scope>
145+
</dependency>
146+
<dependency>
147+
<groupId>${quarkus.platform.group-id}</groupId>
148+
<artifactId>quarkus-langchain4j-bom</artifactId>
149+
<version>${quarkus.platform.version}</version>
150+
<type>pom</type>
151+
<scope>import</scope>
152+
</dependency>
153+
</dependencies>
154+
</dependencyManagement>
155+
<dependencies>
156+
<dependency>
157+
<groupId>io.quarkiverse.langchain4j</groupId>
158+
<artifactId>quarkus-langchain4j-openai</artifactId>
159+
</dependency>
160+
<dependency>
161+
<groupId>io.quarkiverse.langchain4j</groupId>
162+
<artifactId>quarkus-langchain4j-redis</artifactId>
163+
</dependency>
164+
</dependencies>
165+
</profile>
135166
<profile>
136167
<id>native</id>
137168
<activation>
@@ -152,8 +183,11 @@
152183
</goals>
153184
<configuration>
154185
<systemPropertyVariables>
155-
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
156-
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
186+
<native.image.path>
187+
${project.build.directory}/${project.build.finalName}-runner
188+
</native.image.path>
189+
<java.util.logging.manager>org.jboss.logmanager.LogManager
190+
</java.util.logging.manager>
157191
<maven.home>${maven.home}</maven.home>
158192
</systemPropertyVariables>
159193
</configuration>
@@ -163,6 +197,7 @@
163197
</plugins>
164198
</build>
165199
<properties>
200+
<skipITs>false</skipITs>
166201
<quarkus.package.type>native</quarkus.package.type>
167202
</properties>
168203
</profile>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.quarkiverse.langchain4j.tests.rag;
2+
3+
import io.quarkus.test.junit.QuarkusIntegrationTest;
4+
5+
@QuarkusIntegrationTest
6+
public class RAGIT extends RAGTest {
7+
8+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package io.quarkiverse.langchain4j.tests.rag;
2+
3+
import io.quarkus.test.common.http.TestHTTPResource;
4+
import io.quarkus.test.junit.QuarkusTest;
5+
import org.awaitility.Awaitility;
6+
import org.jboss.logging.Logger;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.Assertions;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.net.URI;
13+
import java.net.URISyntaxException;
14+
import java.net.URL;
15+
import java.net.http.HttpClient;
16+
import java.net.http.WebSocket;
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.concurrent.CompletionStage;
21+
import java.util.concurrent.ExecutionException;
22+
import java.util.concurrent.TimeUnit;
23+
24+
@QuarkusTest
25+
public class RAGTest {
26+
private static final Logger LOG = Logger.getLogger(RAGTest.class);
27+
28+
private List<String> answers;
29+
private WebSocket webSocket;
30+
31+
@TestHTTPResource
32+
URL url;
33+
34+
@BeforeEach
35+
void setUp() throws URISyntaxException, ExecutionException, InterruptedException {
36+
String socket = "/chatbot";
37+
URI uri = new URI("ws", null,
38+
url.getHost(),
39+
url.getPort(),
40+
socket, null, null);
41+
LOG.info("Connecting to: " + socket + " at " + uri);
42+
answers = Collections.synchronizedList(new ArrayList<>());
43+
webSocket = HttpClient.newHttpClient().newWebSocketBuilder()
44+
.buildAsync(uri, new WebSocketListener(answers)).get();
45+
}
46+
47+
@AfterEach
48+
void tearDown() throws ExecutionException, InterruptedException {
49+
answers = null;
50+
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "").get();
51+
}
52+
53+
@Test
54+
public void smoke() {
55+
Awaitility.await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
56+
Assertions.assertFalse(answers.isEmpty());
57+
});
58+
Assertions.assertEquals("Hello, I'm Bob, how can I help you?", answers.get(0));
59+
}
60+
61+
@Test
62+
public void documentBasedAnswer() throws InterruptedException {
63+
// Input tokens are usually much cheaper, than output tokens, so let's stop the LLM from talking too much
64+
String prompt = "What is the opening deposit (in USD) for a standard savings account? Answer with number only";
65+
webSocket.sendText(prompt, true);
66+
67+
// The answer is split to many messages due to Multi<String> in the Bot.
68+
// We need to wait for the end of the answer, detected as no new messages during a second.
69+
// The prompt should protect against this, but it is not guaranteed.
70+
int repeats = 0;
71+
int lastSize=0;
72+
while (answers.size() <= 1 || answers.size()!=lastSize) {
73+
if (repeats++ > 10) {
74+
LOG.warn("We have waited for: " + repeats + " seconds and it is too much!");
75+
break;
76+
}
77+
lastSize=answers.size();
78+
Thread.sleep(1000);
79+
}
80+
81+
String response = String.join("", answers);
82+
Assertions.assertTrue(response.contains("25"));
83+
}
84+
85+
class WebSocketListener implements WebSocket.Listener {
86+
private final List<String> answers;
87+
private StringBuilder current;
88+
89+
WebSocketListener(List<String> answers) {
90+
this.answers = answers;
91+
}
92+
93+
@Override
94+
public void onOpen(WebSocket webSocket) {
95+
WebSocket.Listener.super.onOpen(webSocket);
96+
}
97+
98+
@Override
99+
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
100+
LOG.info("Message: " + data);
101+
if (current == null) {
102+
current = new StringBuilder();
103+
}
104+
current.append(data);
105+
if (last) {
106+
answers.add(current.toString());
107+
current = null;
108+
}
109+
return WebSocket.Listener.super.onText(webSocket, data, last);
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)