From a691cf132cd9a8d2e216b57582b2b3e362d9eb80 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Apr 2024 09:43:03 +0300 Subject: [PATCH 1/2] Introduce the use of quarkus-wiremock Relates to: #446 --- .../ROOT/pages/includes/attributes.adoc | 2 +- hugging-face/deployment/pom.xml | 6 +- .../QuarkusHuggingFaceClientTest.java | 41 ++++-------- mistral/deployment/pom.xml | 6 +- .../MistralAiChatLanguageModelSmokeTest.java | 39 +++--------- pom.xml | 2 + testing-internal/pom.xml | 38 +++++++++++ .../testing/internal/WiremockAware.java | 63 +++++++++++++++++++ 8 files changed, 131 insertions(+), 66 deletions(-) create mode 100644 testing-internal/pom.xml create mode 100644 testing-internal/src/main/java/io/quarkiverse/langchain4j/testing/internal/WiremockAware.java diff --git a/docs/modules/ROOT/pages/includes/attributes.adoc b/docs/modules/ROOT/pages/includes/attributes.adoc index 110a360f7..ce4896e27 100644 --- a/docs/modules/ROOT/pages/includes/attributes.adoc +++ b/docs/modules/ROOT/pages/includes/attributes.adoc @@ -1,3 +1,3 @@ :project-version: 0.10.3 :langchain4j-version: 0.29.1 -:examples-dir: ./../examples/ +:examples-dir: ./../examples/ \ No newline at end of file diff --git a/hugging-face/deployment/pom.xml b/hugging-face/deployment/pom.xml index 33f61aa34..4c300e8ac 100644 --- a/hugging-face/deployment/pom.xml +++ b/hugging-face/deployment/pom.xml @@ -39,9 +39,9 @@ test - org.wiremock - wiremock-standalone - ${wiremock.version} + io.quarkiverse.langchain4j + quarkus-langchain4j-testing-internal + ${project.version} test diff --git a/hugging-face/deployment/src/test/java/io/quarkiverse/langchain4j/huggingface/deployment/QuarkusHuggingFaceClientTest.java b/hugging-face/deployment/src/test/java/io/quarkiverse/langchain4j/huggingface/deployment/QuarkusHuggingFaceClientTest.java index dadd855f9..3a6ee37e4 100644 --- a/hugging-face/deployment/src/test/java/io/quarkiverse/langchain4j/huggingface/deployment/QuarkusHuggingFaceClientTest.java +++ b/hugging-face/deployment/src/test/java/io/quarkiverse/langchain4j/huggingface/deployment/QuarkusHuggingFaceClientTest.java @@ -4,22 +4,18 @@ import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.MalformedURLException; +import java.net.URL; import java.util.List; +import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import com.github.tomakehurst.wiremock.WireMockServer; - import dev.langchain4j.model.huggingface.HuggingFaceModelName; import dev.langchain4j.model.huggingface.client.EmbeddingRequest; import dev.langchain4j.model.huggingface.client.Options; @@ -28,35 +24,22 @@ import dev.langchain4j.model.huggingface.client.TextGenerationResponse; import io.quarkiverse.langchain4j.huggingface.HuggingFaceRestApi; import io.quarkiverse.langchain4j.huggingface.QuarkusHuggingFaceClientFactory; +import io.quarkiverse.langchain4j.testing.internal.WiremockAware; import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; -public class QuarkusHuggingFaceClientTest { +public class QuarkusHuggingFaceClientTest extends WiremockAware { @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); - private static final int WIREMOCK_PORT = 8089; private static final String CHAT_MODEL_ID = HuggingFaceModelName.TII_UAE_FALCON_7B_INSTRUCT; private static final String EMBED_MODEL_ID = HuggingFaceModelName.SENTENCE_TRANSFORMERS_ALL_MINI_LM_L6_V2; private static final String API_KEY = "key"; - static WireMockServer wireMockServer; - - @BeforeAll - static void beforeAll() { - wireMockServer = new WireMockServer(options().port(WIREMOCK_PORT)); - wireMockServer.start(); - } - - @AfterAll - static void afterAll() { - wireMockServer.stop(); - } - @Test void chat() { - wireMockServer.stubFor( + wiremock().register( post(urlEqualTo("/models/" + sanitizeModelForUrl(CHAT_MODEL_ID))) .withHeader("Authorization", equalTo("Bearer " + API_KEY)) .willReturn(aResponse() @@ -87,7 +70,7 @@ void chat() { @Test void embed() { - wireMockServer.stubFor( + wiremock().register( post(urlEqualTo("/models/" + sanitizeModelForUrl(EMBED_MODEL_ID))) .withHeader("Authorization", equalTo("Bearer " + API_KEY)) .willReturn(aResponse() @@ -108,21 +91,21 @@ private String sanitizeModelForUrl(String modelId) { private QuarkusHuggingFaceClientFactory.QuarkusHuggingFaceClient createClient() { try { HuggingFaceRestApi restApi = QuarkusRestClientBuilder.newBuilder() - .baseUri(new URI("http://localhost:" + WIREMOCK_PORT + "/models/" + sanitizeModelForUrl(EMBED_MODEL_ID))) + .baseUrl(new URL(resolvedWiremockUrl("/models/" + sanitizeModelForUrl(EMBED_MODEL_ID)))) .build(HuggingFaceRestApi.class); return new QuarkusHuggingFaceClientFactory.QuarkusHuggingFaceClient(restApi, API_KEY); - } catch (URISyntaxException e) { + } catch (MalformedURLException e) { throw new RuntimeException(e); } } private QuarkusHuggingFaceClientFactory.QuarkusHuggingFaceClient createClientForChat() { try { - HuggingFaceRestApi restApi = QuarkusRestClientBuilder.newBuilder() - .baseUri(new URI("http://localhost:" + WIREMOCK_PORT + "/models/" + sanitizeModelForUrl(CHAT_MODEL_ID))) + HuggingFaceRestApi restApi = RestClientBuilder.newBuilder() + .baseUrl(new URL(resolvedWiremockUrl("/models/" + sanitizeModelForUrl(CHAT_MODEL_ID)))) .build(HuggingFaceRestApi.class); return new QuarkusHuggingFaceClientFactory.QuarkusHuggingFaceClient(restApi, API_KEY); - } catch (URISyntaxException e) { + } catch (MalformedURLException e) { throw new RuntimeException(e); } } diff --git a/mistral/deployment/pom.xml b/mistral/deployment/pom.xml index c1ee348ee..411f07b84 100644 --- a/mistral/deployment/pom.xml +++ b/mistral/deployment/pom.xml @@ -35,9 +35,9 @@ test - org.wiremock - wiremock-standalone - ${wiremock.version} + io.quarkiverse.langchain4j + quarkus-langchain4j-testing-internal + ${project.version} test diff --git a/mistral/deployment/src/test/java/io/quarkiverse/langchain4j/mistralai/deployment/MistralAiChatLanguageModelSmokeTest.java b/mistral/deployment/src/test/java/io/quarkiverse/langchain4j/mistralai/deployment/MistralAiChatLanguageModelSmokeTest.java index 810c0e64e..5aa58e1bf 100644 --- a/mistral/deployment/src/test/java/io/quarkiverse/langchain4j/mistralai/deployment/MistralAiChatLanguageModelSmokeTest.java +++ b/mistral/deployment/src/test/java/io/quarkiverse/langchain4j/mistralai/deployment/MistralAiChatLanguageModelSmokeTest.java @@ -4,57 +4,36 @@ import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import jakarta.inject.Inject; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.mistralai.MistralAiChatModel; +import io.quarkiverse.langchain4j.testing.internal.WiremockAware; import io.quarkus.arc.ClientProxy; import io.quarkus.test.QuarkusUnitTest; -class MistralAiChatLanguageModelSmokeTest { - private static final int WIREMOCK_PORT = 8089; - private static final String CHAT_MODEL_ID = "mistral-tiny"; +class MistralAiChatLanguageModelSmokeTest extends WiremockAware { + private static final String API_KEY = "somekey"; + private static final String CHAT_MODEL_ID = "mistral-tiny"; @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)) .overrideRuntimeConfigKey("quarkus.langchain4j.mistralai.api-key", API_KEY) .overrideRuntimeConfigKey("quarkus.langchain4j.mistralai.log-requests", "true") - .overrideRuntimeConfigKey("quarkus.langchain4j.mistralai.base-url", "http://localhost:" + WIREMOCK_PORT + "/v1"); - - static WireMockServer wireMockServer; - - @BeforeAll - static void beforeAll() { - wireMockServer = new WireMockServer(options().port(WIREMOCK_PORT)); - wireMockServer.start(); - } - - @AfterAll - static void afterAll() { - wireMockServer.stop(); - } - - @BeforeEach - void setup() { - wireMockServer.resetAll(); - } + .overrideRuntimeConfigKey("quarkus.langchain4j.mistralai.base-url", + WiremockAware.wiremockUrlForConfig("/v1")); @Inject ChatLanguageModel chatLanguageModel; @@ -63,7 +42,7 @@ void setup() { void test() { assertThat(ClientProxy.unwrap(chatLanguageModel)).isInstanceOf(MistralAiChatModel.class); - wireMockServer.stubFor( + wiremock().register( post(urlEqualTo("/v1/chat/completions")) .withHeader("Authorization", equalTo("Bearer " + API_KEY)) .willReturn(aResponse() @@ -95,8 +74,8 @@ void test() { String response = chatLanguageModel.generate("hello"); assertThat(response).isEqualTo("Nice to meet you"); - assertThat(wireMockServer.getAllServeEvents()).hasSize(1); - ServeEvent serveEvent = wireMockServer.getAllServeEvents().get(0); // this works because we reset requests for Wiremock before each test + assertThat(wiremock().getServeEvents()).hasSize(1); + ServeEvent serveEvent = wiremock().getServeEvents().get(0); // this works because we reset requests for Wiremock before each test LoggedRequest loggedRequest = serveEvent.getRequest(); assertThat(loggedRequest.getHeader("User-Agent")).isEqualTo("Resteasy Reactive Client"); String requestBody = new String(loggedRequest.getBody()); diff --git a/pom.xml b/pom.xml index 683c75a46..4be25e2fe 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ cohere core docs + testing-internal easy-rag hugging-face infinispan @@ -48,6 +49,7 @@ 3.5.2 0.1.4 0.103.1 + 1.3.0 diff --git a/testing-internal/pom.xml b/testing-internal/pom.xml new file mode 100644 index 000000000..26f9629e3 --- /dev/null +++ b/testing-internal/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-langchain4j-testing-internal + Quarkus LangChain4j - Internal Testing + A module that provides testing utilities used by other modules + + + + io.quarkiverse.wiremock + quarkus-wiremock-test + ${quarkus-wiremock.version} + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + diff --git a/testing-internal/src/main/java/io/quarkiverse/langchain4j/testing/internal/WiremockAware.java b/testing-internal/src/main/java/io/quarkiverse/langchain4j/testing/internal/WiremockAware.java new file mode 100644 index 000000000..808b89f30 --- /dev/null +++ b/testing-internal/src/main/java/io/quarkiverse/langchain4j/testing/internal/WiremockAware.java @@ -0,0 +1,63 @@ +package io.quarkiverse.langchain4j.testing.internal; + +import org.eclipse.microprofile.config.ConfigProvider; + +import com.github.tomakehurst.wiremock.client.WireMock; + +/** + * This class is used instead of {@link io.quarkiverse.wiremock.devservice.ConnectWireMock} because the latter does not + * work well with {@code QuarkusUnitTest} + */ +public abstract class WiremockAware { + + private WireMock wireMock; + + public static String wiremockUrlForConfig() { + return "http://localhost:${quarkus.wiremock.devservices.port}"; + } + + public static String wiremockUrlForConfig(String path) { + if (path.isEmpty()) { + return wiremockUrlForConfig(); + } + if (!path.startsWith("/")) { + path = "/" + path; + } + return wiremockUrlForConfig() + path; + + } + + /** + * This is meant to be called by test methods or pre- and post-test methods + */ + public String resolvedWiremockUrl() { + return String.format("http://localhost:%d", getResolvedWiremockPort()); + } + + /** + * This is meant to be called by test methods or pre- and post-test methods + */ + public String resolvedWiremockUrl(String path) { + if (path.isEmpty()) { + return resolvedWiremockUrl(); + } + if (!path.startsWith("/")) { + path = "/" + path; + } + return resolvedWiremockUrl() + path; + } + + /** + * This is meant to be called by test methods or pre- and post-test methods + */ + public WireMock wiremock() { + if (wireMock == null) { + wireMock = new WireMock(getResolvedWiremockPort()); + } + return wireMock; + } + + private Integer getResolvedWiremockPort() { + return ConfigProvider.getConfig().getValue("quarkus.wiremock.devservices.port", Integer.class); + } +} From 21aeed19585a80762ba76c277f2fb19ed0a16885 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Apr 2024 11:19:01 +0300 Subject: [PATCH 2/2] Remove unused wiremock dependency --- cohere/deployment/pom.xml | 6 ------ easy-rag/deployment/pom.xml | 6 ------ infinispan/deployment/pom.xml | 6 ------ ollama/deployment/pom.xml | 6 ------ pgvector/deployment/pom.xml | 6 ------ pinecone/deployment/pom.xml | 6 ------ redis/deployment/pom.xml | 6 ------ 7 files changed, 42 deletions(-) diff --git a/cohere/deployment/pom.xml b/cohere/deployment/pom.xml index 37fbb05eb..115185893 100644 --- a/cohere/deployment/pom.xml +++ b/cohere/deployment/pom.xml @@ -34,12 +34,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - diff --git a/easy-rag/deployment/pom.xml b/easy-rag/deployment/pom.xml index eed25c775..87408c993 100644 --- a/easy-rag/deployment/pom.xml +++ b/easy-rag/deployment/pom.xml @@ -33,12 +33,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2-q diff --git a/infinispan/deployment/pom.xml b/infinispan/deployment/pom.xml index 5a17428b1..c4937d47b 100644 --- a/infinispan/deployment/pom.xml +++ b/infinispan/deployment/pom.xml @@ -46,12 +46,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2-q diff --git a/ollama/deployment/pom.xml b/ollama/deployment/pom.xml index 448cd731a..eb75964ed 100644 --- a/ollama/deployment/pom.xml +++ b/ollama/deployment/pom.xml @@ -42,12 +42,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - diff --git a/pgvector/deployment/pom.xml b/pgvector/deployment/pom.xml index 4244b6265..dc62bb9d6 100644 --- a/pgvector/deployment/pom.xml +++ b/pgvector/deployment/pom.xml @@ -54,12 +54,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2-q diff --git a/pinecone/deployment/pom.xml b/pinecone/deployment/pom.xml index 23a3988d4..ff1429f33 100644 --- a/pinecone/deployment/pom.xml +++ b/pinecone/deployment/pom.xml @@ -46,12 +46,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2-q diff --git a/redis/deployment/pom.xml b/redis/deployment/pom.xml index 19b178b42..cd15bb5b6 100644 --- a/redis/deployment/pom.xml +++ b/redis/deployment/pom.xml @@ -50,12 +50,6 @@ ${assertj.version} test - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2-q