diff --git a/core/runtime/src/main/java/io/quarkiverse/langchain4j/QuarkusPromptTemplateFactory.java b/core/runtime/src/main/java/io/quarkiverse/langchain4j/QuarkusPromptTemplateFactory.java index c4d7709ba..f5312cdb1 100644 --- a/core/runtime/src/main/java/io/quarkiverse/langchain4j/QuarkusPromptTemplateFactory.java +++ b/core/runtime/src/main/java/io/quarkiverse/langchain4j/QuarkusPromptTemplateFactory.java @@ -1,6 +1,7 @@ package io.quarkiverse.langchain4j; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; @@ -14,21 +15,28 @@ public class QuarkusPromptTemplateFactory implements PromptTemplateFactory { - private final LazyValue engineLazyValue; + private static final AtomicReference> engineLazyValue = new AtomicReference<>(); public QuarkusPromptTemplateFactory() { - engineLazyValue = new LazyValue<>(new Supplier() { + engineLazyValue.set(new LazyValue<>(new Supplier() { @Override public Engine get() { return Arc.container().instance(Engine.class).get().newBuilder() .addParserHook(new MustacheTemplateVariableStyleParserHook()).build(); } - }); + })); + } + + public static void clear() { + LazyValue lazyValue = engineLazyValue.get(); + if (lazyValue != null) { + lazyValue.clear(); + } } @Override public Template create(Input input) { - return new QuteTemplate(engineLazyValue.get().parse(input.getTemplate())); + return new QuteTemplate(engineLazyValue.get().get().parse(input.getTemplate())); } public static class MustacheTemplateVariableStyleParserHook implements ParserHook { diff --git a/core/runtime/src/main/java/io/quarkiverse/langchain4j/runtime/LangChain4jRecorder.java b/core/runtime/src/main/java/io/quarkiverse/langchain4j/runtime/LangChain4jRecorder.java index 4438f2717..5619ab225 100644 --- a/core/runtime/src/main/java/io/quarkiverse/langchain4j/runtime/LangChain4jRecorder.java +++ b/core/runtime/src/main/java/io/quarkiverse/langchain4j/runtime/LangChain4jRecorder.java @@ -1,5 +1,6 @@ package io.quarkiverse.langchain4j.runtime; +import io.quarkiverse.langchain4j.QuarkusPromptTemplateFactory; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; @@ -11,6 +12,7 @@ public void cleanUp(ShutdownContext shutdown) { @Override public void run() { StructuredPromptsRecorder.clearTemplates(); + QuarkusPromptTemplateFactory.clear(); AiServicesRecorder.clearMetadata(); ToolsRecorder.clearMetadata(); } diff --git a/openai/openai-vanilla/devmode-tests/pom.xml b/openai/openai-vanilla/devmode-tests/pom.xml new file mode 100644 index 000000000..ac3c4aade --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai-parent + 999-SNAPSHOT + + quarkus-langchain4j-openai-devmode-tests + Quarkus LangChain4j - OpenAI - DevMode tests + + + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai-deployment + ${project.version} + + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + diff --git a/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Configuration.java b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Configuration.java new file mode 100644 index 000000000..d2a0cc407 --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Configuration.java @@ -0,0 +1,21 @@ +package io.quarkiverse.langchain4j.openai.test; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Configuration extends PanacheEntity { + + @Enumerated(EnumType.STRING) + public ConfigurationKey key; + + public String value; + + public static boolean displayNewSpeakers() { + Configuration config = Configuration.find("key", ConfigurationKey.DISPLAY_NEW_SPEAKERS).firstResult(); + return config != null && config.value.equals("true"); + } +} diff --git a/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/ConfigurationKey.java b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/ConfigurationKey.java new file mode 100644 index 000000000..772a72432 --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/ConfigurationKey.java @@ -0,0 +1,5 @@ +package io.quarkiverse.langchain4j.openai.test; + +public enum ConfigurationKey { + DISPLAY_NEW_SPEAKERS, +} diff --git a/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/PromptDevModeTest.java b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/PromptDevModeTest.java new file mode 100644 index 000000000..2c8ff55fa --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/PromptDevModeTest.java @@ -0,0 +1,45 @@ +package io.quarkiverse.langchain4j.openai.test; + +import static io.restassured.RestAssured.get; + +import java.util.function.Function; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +public class PromptDevModeTest { + + @RegisterExtension + static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Service.class, Resource.class, Configuration.class, ConfigurationKey.class, + Resource.ApplicationGlobals.class) + .addAsResource( + new StringAsset( + "quarkus.langchain4j.openai.api-key=whatever\nquarkus.langchain4j.openai.base-url= https://mockgpt.wiremockapi.cloud/v1"), + "application.properties")); + + @Test + public void test() { + get("test") + .then() + .statusCode(200); + + devModeTest.modifySourceFile(Resource.class, new Function() { + @Override + public String apply(String s) { + return s.replace("java", "kotlin"); + } + }); + + get("test") + .then() + .statusCode(200); + } + +} diff --git a/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Resource.java b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Resource.java new file mode 100644 index 000000000..1b3b05baf --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Resource.java @@ -0,0 +1,29 @@ +package io.quarkiverse.langchain4j.openai.test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.quarkus.qute.TemplateGlobal; + +@Path("/test") +public class Resource { + + private final Service service; + + public Resource(Service service) { + this.service = service; + } + + @GET + public String hello() { + return service.findTalks("java"); + } + + @TemplateGlobal + public static class ApplicationGlobals { + + public static boolean displayNewSpeakers() { + return Configuration.displayNewSpeakers(); + } + } +} diff --git a/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Service.java b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Service.java new file mode 100644 index 000000000..161acc67b --- /dev/null +++ b/openai/openai-vanilla/devmode-tests/src/test/java/io/quarkiverse/langchain4j/openai/test/Service.java @@ -0,0 +1,15 @@ +package io.quarkiverse.langchain4j.openai.test; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; + +@RegisterAiService +public interface Service { + + @SystemMessage("You are a computer science conference organiser") + @UserMessage(""" + Help me select talks that match my favorite topics: {topics}. Give me the list of talks. + """) + String findTalks(String topics); +} diff --git a/openai/openai-vanilla/pom.xml b/openai/openai-vanilla/pom.xml index 25f4c4815..90734727d 100644 --- a/openai/openai-vanilla/pom.xml +++ b/openai/openai-vanilla/pom.xml @@ -13,6 +13,7 @@ deployment + devmode-tests runtime