Langchain4j
This module provides integration between Micronaut and Langchain4j.
|
Note
|
This module is regarded as experimental and subject to change since the underlying technology (AI) is volatile and subject to change.
|
Various modules are provided that allow automatically configuring common Langchain4j types like ChatModel, ImageModel etc. Refer to the sections below for the supported Langchain4j extensions.
Add the following annotation processor dependency:
annotationProcessor("io.micronaut.langchain4j:micronaut-langchain4j-processor")
<annotationProcessorPaths>
<path>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-processor</artifactId>
</path>
</annotationProcessorPaths>
implementation("io.micronaut.langchain4j:micronaut-langchain4j-core")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-core</artifactId>
</dependency>
implementation("io.micronaut.langchain4j:micronaut-langchain4j-ollama")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-ollama</artifactId>
</dependency>
testResourcesService("io.micronaut.langchain4j:micronaut-langchain4j-ollama-testresource")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-ollama-testresource</artifactId>
<scope>testResourcesService</scope>
</dependency>
Add the necessary configuration to configure the model name you want to use:
Configuring the Model Name
langchain4j.ollama.model-name=orca-mini
langchain4j.ollama.model-name: orca-mini
"langchain4j.ollama.model-name" = "orca-mini"
langchain4j.ollama.modelName = 'orca-mini'
{
langchain4j.ollama.model-name = "orca-mini"
}
{
"langchain4j.ollama.model-name": "orca-mini"
}
It is possible to use response streaming.
First, you need to configure a streaming chat model with langchain4j.*.streaming-chat-model.*. For example, with OpenAI:
Example Configuration
langchain4j.open-ai.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.streaming-chat-model.model-name=gpt-4o
langchain4j.open-ai.streaming-chat-model.log-requests=true
langchain4j.open-ai.streaming-chat-model.log-responses=true
langchain4j:
open-ai:
api-key: ${OPENAI_API_KEY}
streaming-chat-model:
model-name: gpt-4o
log-requests: true
log-responses: true
[langchain4j.open-ai]
api-key = "${OPENAI_API_KEY}"
[langchain4j.open-ai.streaming-chat-model]
model-name = "gpt-4o"
log-requests = true
log-responses = true
langchain4j {
openAi {
apiKey = '${OPENAI_API_KEY}'
streamingChatModel {
modelName = 'gpt-4o'
logRequests = true
logResponses = true
}
}
}
{
langchain4j = {
open-ai = {
api-key = "${OPENAI_API_KEY}"
streaming-chat-model = {
model-name = "gpt-4o"
log-requests = true
log-responses = true
}
}
}
}
{
"langchain4j": {
"open-ai": {
"api-key": "${OPENAI_API_KEY}",
"streaming-chat-model": {
"model-name": "gpt-4o",
"log-requests": true,
"log-responses": true
}
}
}
}
Then, you will be able to inject a bean of type dev.langchain4j.model.chat.StreamingChatModel.
Additionally, you can use an AI Service with a method whose return type uses Project Reactor.
For example, an @AIService interface with a method whose return type is Flux<String>.
In order to do this, you will need to add the following dependency:
implementation("dev.langchain4j:langchain4j-reactor")
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
Defining @AiService interfaces
package example.micronaut.aiservice;
import dev.langchain4j.service.SystemMessage;
import io.micronaut.langchain4j.annotation.AiService;
@AiService //
public interface Friend {
@SystemMessage("You are a good friend of mine. Answer using slang.") //
String chat(String userMessage);
}
package example.micronaut.aiservice
import dev.langchain4j.service.SystemMessage
import io.micronaut.langchain4j.annotation.AiService
@AiService //
interface Friend {
@SystemMessage("You are a good friend of mine. Answer using slang.") //
fun chat(userMessage: String): String
}
package example.micronaut.aiservice
import dev.langchain4j.service.SystemMessage
import io.micronaut.langchain4j.annotation.AiService
@AiService //
interface Friend {
@SystemMessage("You are a good friend of mine. Answer using slang.") //
String chat(String userMessage)
}
You can now inject the @AiService definition into any Micronaut component including tests:
Calling @AiService definitions
package example.micronaut.aiservice;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import dev.langchain4j.model.chat.ChatModel;
import io.micronaut.langchain4j.testutils.OllamaTestPropertyProvider;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers(disabledWithoutDocker = true)
@MicronautTest(startApplication = false)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AiServiceTest implements OllamaTestPropertyProvider {
@Test
void testAiService(Friend friend, ChatModel languageModel) {
String result = friend.chat("Hello");
assertNotNull(result);
assertNotNull(languageModel);
}
}
package example.micronaut.aiservice
import dev.langchain4j.model.chat.ChatModel
import io.micronaut.langchain4j.testutils.OllamaTestPropertyProvider
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers(disabledWithoutDocker = true)
@MicronautTest(startApplication = false)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class AiServiceTest : OllamaTestPropertyProvider {
@Test
fun testAiService(friend: Friend, languageModel: ChatModel) {
val result: String = friend.chat("Hello")
Assertions.assertNotNull(result)
Assertions.assertNotNull(languageModel)
}
}
package example.micronaut.aiservice
import dev.langchain4j.model.chat.ChatModel
import io.micronaut.langchain4j.testutils.OllamaTestPropertyProvider
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.testcontainers.junit.jupiter.Testcontainers
import static org.junit.jupiter.api.Assertions.assertNotNull
@Testcontainers(disabledWithoutDocker = true)
@MicronautTest(startApplication = false)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AiServiceTest implements OllamaTestPropertyProvider {
@Test
void testAiService(Friend friend, ChatModel languageModel) {
String result = friend.chat("Hello")
assertNotNull(result)
assertNotNull(languageModel)
}
}
Agentic Service
Micronaut lets you declare LangChain4j Agentic services and have concrete agents generated at runtime.
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-agentic")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-agentic</artifactId>
</dependency>
Annotate an interface with AgenticService and declare methods using LangChain4j Agentic annotations.
The integration interprets your annotations and uses Micronaut DI to wire the underlying LangChain4j builders.
It supports:
-
Typed agents built via AgenticServices.agentBuilder(Class) with @Agent methods.
-
Declarative workflow agents via AgenticServices.createAgenticSystem(…).
-
Micronaut model, memory, RAG, tool, and lifecycle integration for each generated agent builder.
Quick start
package example.micronaut.agentic;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import io.micronaut.langchain4j.agentic.annotation.AgenticService;
/**
* Minimal typed Agentic service used by the test-suite to validate Micronaut integration.
*/
@AgenticService
public interface GreeterAgent {
@UserMessage("Say hello to {{name}}")
@Agent(description = "Greets a person by name")
String greet(@V("name") String name);
}
Usage in tests
package example.micronaut.agentic;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@MicronautTest(startApplication = false, environments = "agentic-test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AgenticServiceTest {
@Test
void testAgenticGreeter(GreeterAgent agent) {
String result = agent.greet("John");
assertNotNull(result);
}
}
Declarative workflows
You can build workflows using LangChain4j’s declarative annotations. The integration routes construction through Micronaut DI so that supported workflow builders are Micronaut-managed beans (allowing listeners and customization).
Supervisor-style and planner-style agents can still be modeled using LangChain4j typed agents or composed workflows, but the Micronaut-managed workflow builder lifecycle currently applies to the workflow types listed above.
Example: an evening planner (Sequence)
package example.micronaut.agentic;
import dev.langchain4j.agentic.declarative.SequenceAgent;
import dev.langchain4j.service.V;
import io.micronaut.langchain4j.agentic.annotation.AgenticService;
/**
* Declarative sequence workflow inspired by the "EveningPlannerAgent" example.
* This agent coordinates sub-agents and produces a final "plan" output.
*/
@AgenticService(outputKey = "plan")
public interface EveningPlannerAgent {
// Declarative sequence workflow definition (no method body needed)
@SequenceAgent(
subAgents = {
TravelRecommenderAgent.class,
RecipeAdvisorAgent.class,
PlanSynthesizerAgent.class
},
outputKey = "plan",
name = "planEvening"
)
String plan(@V("topic") String topic);
}
Parallel workflow example
package example.micronaut.agentic;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.declarative.Output;
import dev.langchain4j.agentic.declarative.ParallelAgent;
import dev.langchain4j.agentic.declarative.ParallelExecutor;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import io.micronaut.langchain4j.agentic.annotation.AgenticService;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* Validates declarative ParallelAgent workflow wiring through @AgenticService.
* Ensures Micronaut DI correctly builds the agentic system and executes the parallel plan.
*/
@MicronautTest(startApplication = false, environments = "agentic-test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ParallelPlanningAgentTest {
@Test
void testDeclarativeParallel(EveningPlanner agent) {
String plan = agent.plan("jazz", "romantic");
System.out.println("parallel plan = " + plan);
assertFalse(plan.isEmpty());
}
@AgenticService
public interface MusicPlanner {
@UserMessage("""
Choose a band which plays music in the {{style}} style.
Answer with the name of the band only: no details, no explanation.
""")
@Agent(outputKey = "band")
String suggestBand(@V("style") String style);
}
@AgenticService
public interface DinnerPlanner {
@UserMessage("""
Choose a menu for dinner for the following mood: {{mood}}
Answer with the menu only: no details, no explanations.
""")
@Agent(outputKey = "menu")
String suggestMenu(@V("mood") String mood);
}
/**
* Demonstrates declarative ParallelAgent orchestration using Micronaut DI.
*/
@AgenticService(outputKey = "plan")
public interface EveningPlanner {
@ParallelAgent(
subAgents = {
MusicPlanner.class,
DinnerPlanner.class
},
outputKey = "plan",
name = "planEvening"
)
String plan(@V("style") String style, @V("mood") String mood);
// Use a shared executor for parallel execution
@ParallelExecutor
static Executor executor() {
return ForkJoinPool.commonPool();
}
// Aggregate the parallel outputs into a single "plan" string
@Output
static String aggregate(@V("band") String band, @V("menu") String menu) {
String c = band == null ? "" : band.trim();
String r = menu == null ? "" : menu.trim();
if (c.isEmpty() && r.isEmpty()) {
return "";
}
if (c.isEmpty()) {
return "Play: " + r;
}
if (r.isEmpty()) {
return "Menu: " + c;
}
return "Play: " + c + " | Menu: " + r;
}
}
}
Loop example with customization via a listener
package example.micronaut.agentic;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.declarative.LoopAgent;
import dev.langchain4j.agentic.workflow.LoopAgentService;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.langchain4j.agentic.annotation.AgenticService;
import jakarta.inject.Singleton;
/**
* Demonstrates declarative LoopAgent orchestration using Micronaut DI.
*/
@AgenticService(outputKey = "translation")
public interface LoopingPlannerAgent {
@LoopAgent(
subAgents = {
TranslatorAgent.class
},
outputKey = "text",
maxIterations = 3
)
String translatesInLoop(@V("text") String text);
interface TranslatorAgent {
@UserMessage("""
You are a translator.
If the text is in English, translate to French.
If the text is in French, translate to German.
If the text is in German, translate to Spanish.
Translate this: "{{text}}". Answer with the translation only, no explanations, no details.
""")
@Agent(outputKey = "text")
String translate(@V("text") String text);
}
@Singleton
@Requires(property = "spec.name", value = "LoopingPlannerAgentTest")
class LoopBuilderListener implements BeanCreatedEventListener<LoopAgentService<?>> {
@Override
public LoopAgentService<?> onCreated(@NonNull BeanCreatedEvent<LoopAgentService<?>> event) {
LoopAgentService<?> builder = event.getBean();
builder.exitCondition((scope, idx) -> idx == 3);
return builder;
}
}
}
Configuration
You can select which ChatModel bean to use per agent via configuration. The agent id is derived from the interface name by:
langchain4j.agentic.agents.<agent-id>.chat-model
langchain4j.agentic.agents.greeter.chat-model=friendly-chat-model
langchain4j.agentic.agents.creative-writer.chat-model=creative-chat-model
langchain4j:
agentic:
agents:
greeter:
chat-model: friendly-chat-model
creative-writer:
chat-model: creative-chat-model
[langchain4j.agentic.agents.greeter]
chat-model = "friendly-chat-model"
[langchain4j.agentic.agents.creative-writer]
chat-model = "creative-chat-model"
langchain4j {
agentic {
agents {
greeter {
chatModel = 'friendly-chat-model'
}
creativeWriter {
chatModel = 'creative-chat-model'
}
}
}
}
{
langchain4j = {
agentic = {
agents = {
greeter = {
chat-model = "friendly-chat-model"
}
creative-writer = {
chat-model = "creative-chat-model"
}
}
}
}
}
{
"langchain4j": {
"agentic": {
"agents": {
"greeter": {
"chat-model": "friendly-chat-model"
},
"creative-writer": {
"chat-model": "creative-chat-model"
}
}
}
}
}
If the property is not set and there is exactly one ChatModel bean in the context, it will be used automatically.
Memory
By default, agentic services reuse the core MessageWindowChatMemory built from the configured ChatMemoryStore. The core module publishes a MessageWindowChatMemory.Builder per available ChatMemoryStore (via @EachBean(ChatMemoryStore)), so agents automatically use the default builder when a single memory store is configured.
You can optionally override memory per agent:
langchain4j.agentic.agents.<agent-id>.memory.store
langchain4j.agentic.agents.<agent-id>.memory.max-messages
Notes:
- If memory.store is not set, the default MessageWindowChatMemory.Builder is used (as resolved by Micronaut DI).
- If multiple memory stores are available, set memory.store to select the store for an agent.
- If memory.max-messages is not set, the global core setting langchain4j.chat-memory-store.message-window.max-messages applies.
langchain4j.agentic.agents.greeter.memory.store=redis
langchain4j.agentic.agents.creative-writer.memory.max-messages=50
langchain4j:
agentic:
agents:
greeter:
memory:
store: redis
creative-writer:
memory:
max-messages: 50
[langchain4j.agentic.agents.greeter.memory]
store = "redis"
[langchain4j.agentic.agents.creative-writer.memory]
max-messages = 50
langchain4j {
agentic {
agents {
greeter {
memory {
store = 'redis'
}
}
creativeWriter {
memory {
maxMessages = 50
}
}
}
}
}
{
langchain4j = {
agentic = {
agents = {
greeter = {
memory = {
store = "redis"
}
}
creative-writer = {
memory = {
max-messages = 50
}
}
}
}
}
}
{
"langchain4j": {
"agentic": {
"agents": {
"greeter": {
"memory": {
"store": "redis"
}
},
"creative-writer": {
"memory": {
"max-messages": 50
}
}
}
}
}
}
You can request specific tool beans to be registered with the agent builder using the tools attribute.
Tool classes should be Micronaut beans containing methods annotated with dev.langchain4j.agent.tool.Tool.
Customization
You can customize builders using Micronaut lifecycle listeners. Because workflow builders are created as Micronaut beans during declarative system construction, your listeners can adjust names, exit conditions, parallelism, etc., before the system executes.
Typed agents can also be customized via BeanCreatedEventListener<AgentBuilder<?, ?>>; the LoopingPlannerAgent snippet above shows the same Micronaut lifecycle technique applied to LoopAgentService.
For example, this listener customizes every generated declarative agent builder before LangChain4j builds the agentic system:
package example.micronaut.agentic;
import dev.langchain4j.agentic.agent.AgentBuilder;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
@Singleton
@Requires(env = "agentic-docs")
final class SupportAgentBuilderListener implements BeanCreatedEventListener<AgentBuilder<?, ?>> {
@Override
public AgentBuilder<?, ?> onCreated(@NonNull BeanCreatedEvent<AgentBuilder<?, ?>> event) {
AgentBuilder<?, ?> builder = event.getBean();
builder.name("customer-support-agent");
builder.outputKey("supportResponse");
return builder;
}
}
package example.micronaut.agentic
import dev.langchain4j.agentic.agent.AgentBuilder
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import jakarta.inject.Singleton
@Singleton
@Requires(env = ["agentic-docs"])
internal class SupportAgentBuilderListener : BeanCreatedEventListener<AgentBuilder<*, *>> {
override fun onCreated(event: BeanCreatedEvent<AgentBuilder<*, *>>): AgentBuilder<*, *> {
val builder = event.bean
builder.name("customer-support-agent")
builder.outputKey("supportResponse")
return builder
}
}
package example.micronaut.agentic
import dev.langchain4j.agentic.agent.AgentBuilder
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import jakarta.inject.Singleton
@Singleton
@Requires(env = "agentic-docs")
class SupportAgentBuilderListener implements BeanCreatedEventListener<AgentBuilder<?, ?>> {
@Override
AgentBuilder<?, ?> onCreated(BeanCreatedEvent<AgentBuilder<?, ?>> event) {
AgentBuilder<?, ?> builder = event.bean
builder.name("customer-support-agent")
builder.outputKey("supportResponse")
builder
}
}
Workflow builders can be customized the same way. This example changes the loop exit condition for all LoopAgentService builders created by the agentic integration:
package example.micronaut.agentic;
import dev.langchain4j.agentic.workflow.LoopAgentService;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
@Singleton
@Requires(env = "agentic-docs")
final class LoopAgentServiceListener implements BeanCreatedEventListener<LoopAgentService<?>> {
@Override
public LoopAgentService<?> onCreated(@NonNull BeanCreatedEvent<LoopAgentService<?>> event) {
LoopAgentService<?> builder = event.getBean();
builder.exitCondition((scope, iteration) -> iteration >= 3);
return builder;
}
}
package example.micronaut.agentic
import dev.langchain4j.agentic.workflow.LoopAgentService
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import jakarta.inject.Singleton
@Singleton
@Requires(env = ["agentic-docs"])
internal class LoopAgentServiceListener : BeanCreatedEventListener<LoopAgentService<*>> {
override fun onCreated(event: BeanCreatedEvent<LoopAgentService<*>>): LoopAgentService<*> {
val builder = event.bean
builder.exitCondition { _, iteration -> iteration >= 3 }
return builder
}
}
package example.micronaut.agentic
import dev.langchain4j.agentic.workflow.LoopAgentService
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import jakarta.inject.Singleton
@Singleton
@Requires(env = "agentic-docs")
class LoopAgentServiceListener implements BeanCreatedEventListener<LoopAgentService<?>> {
@Override
LoopAgentService<?> onCreated(BeanCreatedEvent<LoopAgentService<?>> event) {
LoopAgentService<?> builder = event.bean
builder.exitCondition { scope, iteration -> iteration >= 3 }
builder
}
}
The same approach applies to Micronaut-managed workflow services: SequentialAgentService, ParallelAgentService, ParallelMapperService, ConditionalAgentService, and LoopAgentService.
Micronaut LangChain4j includes a small evaluation API for asserting AI responses in tests.
The EvaluationRequest record captures the original user text, optional grounding context, and generated response.
An Evaluator consumes that request and returns an EvaluationResult.
Built-in evaluators include:
When an AI service returns dev.langchain4j.service.Result<T>, you can reuse retrieved sources as evaluation context:
Defining an @AiService that returns Result<String>
package example.micronaut.aiservice.evaluation;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import io.micronaut.context.annotation.Requires;
import io.micronaut.langchain4j.annotation.AiService;
@Requires(property = "spec.name", value = "AiServiceEvaluationExample")
@AiService
public interface EvaluatingFriend {
@SystemMessage("You are a good friend of mine. Answer using slang.")
Result<String> chat(String userMessage);
}
Evaluating an AI service response
package example.micronaut.aiservice.evaluation;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.Result;
import io.micronaut.context.annotation.Property;
import io.micronaut.langchain4j.evaluation.EvaluationRequest;
import io.micronaut.langchain4j.evaluation.EvaluationResult;
import io.micronaut.langchain4j.evaluation.RelevancyEvaluator;
import io.micronaut.langchain4j.testutils.OllamaTestPropertyProvider;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@Property(name = "spec.name", value = "AiServiceEvaluationExample")
@Testcontainers(disabledWithoutDocker = true)
@MicronautTest(startApplication = false)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AiServiceEvaluationExample implements OllamaTestPropertyProvider {
@Test
void evaluatesAiServiceResponse(EvaluatingFriend friend, ChatModel chatModel) {
String userText = "Reply with exactly: Micronaut is a JVM framework.";
Result<String> response = friend.chat(userText);
RelevancyEvaluator evaluator = new RelevancyEvaluator(chatModel);
EvaluationResult evaluation = evaluator.evaluate(EvaluationRequest.from(userText, response));
assertNotNull(evaluation);
assertFalse(evaluation.feedback().isBlank());
}
}
FactCheckingEvaluator requires non-empty context, which makes it a good fit for RAG-style responses backed by retrieved sources.
Tools allow AI models to request specific actions that extends beyond their built-in capabilities.
package example.micronaut.aiservice.tools;
import dev.langchain4j.agent.tool.Tool;
import jakarta.inject.Singleton;
import java.time.LocalDate;
@Singleton //
public class LegalDocumentTools {
@Tool("Returns the last time the PRIVACY document was updated") //
public LocalDate lastUpdatePrivacy() {
return LocalDate.of(2013, 3, 9); //
}
}
package example.micronaut.aiservice.tools
import dev.langchain4j.agent.tool.Tool
import jakarta.inject.Singleton
import java.time.LocalDate
@Singleton //
class LegalDocumentTools {
@Tool("Returns the last time the PRIVACY document was updated") //
fun lastUpdatePrivacy(): LocalDate = LocalDate.of(2013, 3, 9) //
}
package example.micronaut.aiservice.tools
import dev.langchain4j.agent.tool.Tool
import groovy.transform.CompileStatic
import jakarta.inject.Singleton
import java.time.LocalDate
@Singleton //
@CompileStatic
class LegalDocumentTools {
@Tool("Returns the last time the PRIVACY document was updated") //
LocalDate lastUpdatePrivacy() {
return LocalDate.of(2013, 3, 9) //
}
}
A model would have no way to know the last date when the PRIVACY document was updated. Hence, it is a good candidate for a tool.
<3> The dates are harcoded for the purpose of this example, but they could have been retrieved from a database or external API.
You can supply the tools to use to an @AiService:
package example.micronaut.aiservice.tools;
import io.micronaut.langchain4j.annotation.AiService;
@AiService(tools = LegalDocumentTools.class)
public interface CompanyBot {
String ask(String question);
}
package example.micronaut.aiservice.tools
import io.micronaut.langchain4j.annotation.AiService
@AiService(tools = [LegalDocumentTools::class])
interface CompanyBot {
fun ask(question: String): String
}
package example.micronaut.aiservice.tools
import io.micronaut.langchain4j.annotation.AiService
@AiService(tools = LegalDocumentTools.class)
interface CompanyBot {
String ask(String question)
}
The following modules provide integration with Langchain4j Language Models.
Each module configures one or more ChatLanguageModel beans, making them available for dependency injection based on configuration.
This example, asks a chat model to generate the list of the top 3 albums of a Jazz musician.
package example.micronaut;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import jakarta.inject.Singleton;
import java.util.List;
@Singleton
public class MusicianAssistant {
private static final SystemMessage SYSTEM_MSG = SystemMessage.from("""
You are an expert in Jazz music.
Reply with only the names of the artists, albums, etc.
Be very concise.
If a list is given, separate the items with commas.""");
private final ChatModel model;
public MusicianAssistant(ChatModel model) { //
this.model = model;
}
public Musician generateTopThreeAlbums(String name) {
List<ChatMessage> messages = generateTopThreeAlbumsMessages(name);
ChatResponse albums = model.chat(messages);
String topThreeAlbums = albums.aiMessage().text();
return new Musician(name, topThreeAlbums);
}
private static List<ChatMessage> generateTopThreeAlbumsMessages(String name) {
return List.of(SYSTEM_MSG, UserMessage.from(
String.format("Only list the top 3 albums of %s", name)
));
}
}
package example.micronaut
import dev.langchain4j.data.message.ChatMessage
import dev.langchain4j.data.message.SystemMessage
import dev.langchain4j.data.message.UserMessage
import dev.langchain4j.model.chat.ChatModel
import jakarta.inject.Singleton
@Singleton
class MusicianAssistant(private val model: ChatModel) { //
fun generateTopThreeAlbums(name: String): Musician {
val messages = generateTopThreeAlbumsMessages(name)
val albums = model.chat(messages)
val topThreeAlbums = albums.aiMessage().text()
return Musician(name, topThreeAlbums)
}
private fun generateTopThreeAlbumsMessages(name: String): List<ChatMessage> {
return listOf(
SYSTEM_MSG, UserMessage.from(
String.format("Only list the top 3 albums of %s", name)
)
)
}
companion object {
private val SYSTEM_MSG: SystemMessage = SystemMessage.from(
"""
You are an expert in Jazz music.
Reply with only the names of the artists, albums, etc.
Be very concise.
If a list is given, separate the items with commas.
""".trimIndent()
)
}
}
package example.micronaut
import dev.langchain4j.data.message.ChatMessage
import dev.langchain4j.data.message.SystemMessage
import dev.langchain4j.data.message.UserMessage
import dev.langchain4j.model.chat.ChatModel
import dev.langchain4j.model.chat.response.ChatResponse
import jakarta.inject.Singleton
import groovy.transform.CompileStatic
@CompileStatic
@Singleton
class MusicianAssistant {
private static final SystemMessage SYSTEM_MSG = SystemMessage.from("""
You are an expert in Jazz music.
Reply with only the names of the artists, albums, etc.
Be very concise.
If a list is given, separate the items with commas.""")
private final ChatModel model
MusicianAssistant(ChatModel model) { //
this.model = model
}
Musician generateTopThreeAlbums(String name) {
List<ChatMessage> messages = generateTopThreeAlbumsMessages(name)
ChatResponse albums = model.chat(messages);
String topThreeAlbums = albums.aiMessage().text();
new Musician(name: name, albums: topThreeAlbums);
}
private static List<ChatMessage> generateTopThreeAlbumsMessages(String name) {
[
SYSTEM_MSG,
UserMessage.from(String.format("Only list the top 3 albums of %s", name))
]
}
}
package example.micronaut;
public record Musician(String name, String albums) {
}
package example.micronaut
data class Musician(val name: String, val albums: String)
package example.micronaut
import groovy.transform.CompileStatic
@CompileStatic
class Musician {
String name
String albums
}
You can configure the chat model via configuration.
For example, you may want to configure OpenAI in the main classpath:
src/main/resources/application.properties
micronaut.application.name=micronaut-guide
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
langchain4j.open-ai.chat-model.timeout=60s
langchain4j.open-ai.chat-model.temperature=0.3
langchain4j.open-ai.chat-model.model-name=gpt-4.1
And a local SLM (Small Language Model) such as Ollama in the test classpath:
src/test/resources/application-test.properties
langchain4j.open-ai.enabled=false
langchain4j.ollama.model-name=tinyllama
langchain4j.ollama.chat-model.timeout=5m
langchain4j.ollama.chat-model.log-requests=true
langchain4j.ollama.chat-model.log-responses=true
Moreover, you can also register a bean of type BeanCreatedEventListener to configure the Chat Model builder programmatically if configuration is not enough.
package example.micronaut;
import dev.langchain4j.model.ollama.OllamaChatModel;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import org.jspecify.annotations.NonNull;
import jakarta.inject.Singleton;
@Singleton
class OllamaChatModelBuilderListener
implements BeanCreatedEventListener<OllamaChatModel.OllamaChatModelBuilder> {
@Override
public OllamaChatModel.OllamaChatModelBuilder onCreated(
@NonNull BeanCreatedEvent<OllamaChatModel.OllamaChatModelBuilder> event) {
OllamaChatModel.OllamaChatModelBuilder builder = event.getBean();
builder.temperature(0.0);
return builder;
}
}
package example.micronaut
import dev.langchain4j.model.ollama.OllamaChatModel.OllamaChatModelBuilder
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import org.jspecify.annotations.NonNull
import jakarta.inject.Singleton
@Singleton
class OllamaChatModelBuilderListener : BeanCreatedEventListener<OllamaChatModelBuilder> {
override fun onCreated(event: @NonNull BeanCreatedEvent<OllamaChatModelBuilder>): OllamaChatModelBuilder {
val builder = event.bean
builder.temperature(0.0)
return builder
}
}
package example.micronaut
import dev.langchain4j.model.ollama.OllamaChatModel
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import org.jspecify.annotations.NonNull
import jakarta.inject.Singleton
@Singleton
class OllamaChatModelBuilderListener
implements BeanCreatedEventListener<OllamaChatModel.OllamaChatModelBuilder> {
@Override
OllamaChatModel.OllamaChatModelBuilder onCreated(
@NonNull BeanCreatedEvent<OllamaChatModel.OllamaChatModelBuilder> event) {
OllamaChatModel.OllamaChatModelBuilder builder = event.getBean()
builder.temperature(0.0)
builder
}
}
Models are stateless by design. Chat memory serves as container for previous messages,
helping you maintain context in a conversation, but the model itself is not aware of this memory;
it relies on you to include the relevant messages in each request for coherent and contextually relevant responses.
Langchain4J provides an API ChatMemory to help you manage chat memory. You can provide your own implementation or use one of the provided implementations.
The default implementation of ChatMemory, dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore, stores ChatMessage instances in memory.
To use the Redis implementation dev.langchain4j.community.store.memory.chat.redis.RedisChatMemoryStore, add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-redis")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-redis</artifactId>
</dependency>
To use the Neo4J implementation dev.langchain4j.community.store.memory.chat.neo4j.Neo4jChatMemoryStore, add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-neo4j")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-neo4j</artifactId>
</dependency>
To use the Cassandra implementation dev.langchain4j.store.memory.chat.cassandra.CassandraChatMemoryStore, add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-cassandra")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-cassandra</artifactId>
</dependency>
To use the Oracle implementation dev.langchain4j.store.memory.chat.oracle.OracleChatMemoryStore, add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-oracle")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-oracle</artifactId>
</dependency>
Then configure a JDBC datasource and the chat memory store properties, for example:
datasources.default.dialect=oracle
langchain4j.chat-memory-store.oracle.default.enabled=true
langchain4j.chat-memory-store.oracle.default.table-name=CHAT_MEMORY
langchain4j.chat-memory-store.oracle.default.memory-id-column-name=MEMORY_ID
langchain4j.chat-memory-store.oracle.default.content-column-name=CONTENT
datasources.default.dialect: oracle
langchain4j.chat-memory-store.oracle.default.enabled: true
langchain4j.chat-memory-store.oracle.default.table-name: CHAT_MEMORY
langchain4j.chat-memory-store.oracle.default.memory-id-column-name: MEMORY_ID
langchain4j.chat-memory-store.oracle.default.content-column-name: CONTENT
"datasources.default.dialect" = "oracle"
"langchain4j.chat-memory-store.oracle.default.enabled" = true
"langchain4j.chat-memory-store.oracle.default.table-name" = "CHAT_MEMORY"
"langchain4j.chat-memory-store.oracle.default.memory-id-column-name" = "MEMORY_ID"
"langchain4j.chat-memory-store.oracle.default.content-column-name" = "CONTENT"
datasources.default.dialect = 'oracle'
langchain4j.chatMemoryStore.oracle.default.enabled = true
langchain4j.chatMemoryStore.oracle.default.tableName = 'CHAT_MEMORY'
langchain4j.chatMemoryStore.oracle.default.memoryIdColumnName = 'MEMORY_ID'
langchain4j.chatMemoryStore.oracle.default.contentColumnName = 'CONTENT'
{
datasources.default.dialect = "oracle"
langchain4j.chat-memory-store.oracle.default.enabled = true
langchain4j.chat-memory-store.oracle.default.table-name = "CHAT_MEMORY"
langchain4j.chat-memory-store.oracle.default.memory-id-column-name = "MEMORY_ID"
langchain4j.chat-memory-store.oracle.default.content-column-name = "CONTENT"
}
{
"datasources.default.dialect": "oracle",
"langchain4j.chat-memory-store.oracle.default.enabled": true,
"langchain4j.chat-memory-store.oracle.default.table-name": "CHAT_MEMORY",
"langchain4j.chat-memory-store.oracle.default.memory-id-column-name": "MEMORY_ID",
"langchain4j.chat-memory-store.oracle.default.content-column-name": "CONTENT"
}
The table must already exist. By default, the expected schema is CHAT_MEMORY(MEMORY_ID, CONTENT).
The segment under oracle (for example default) maps to the datasource name.
For multiple datasources, configure multiple entries such as
langchain4j.chat-memory-store.oracle.reporting.*.
The following example shows how to use the ChatMemory:
package example.micronaut;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import jakarta.inject.Singleton;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Singleton
public class AssistantWithMemory {
private final Map<String, ChatMemory> conversations = new ConcurrentHashMap<>();
private final ChatModel model;
private final MessageWindowChatMemory.Builder messageWindowChatMemoryBuilder;
public AssistantWithMemory(MessageWindowChatMemory.Builder messageWindowChatMemoryBuilder,
ChatModel model) {
this.messageWindowChatMemoryBuilder = messageWindowChatMemoryBuilder;
this.model = model;
}
public MemoryIdAndResponse chat(String conversationId, String message) {
ChatMemory chatMemory = conversations.get(conversationId);
if (chatMemory == null) {
throw new IllegalArgumentException("Unknown conversation: " + conversationId);
}
chatMemory.add(UserMessage.from(message));
ChatResponse chatResponse = model.chat(chatMemory.messages());
AiMessage answer = chatResponse.aiMessage();
chatMemory.add(answer);
return new MemoryIdAndResponse(conversationId, answer.text());
}
public MemoryIdAndResponse chat(String message) {
String conversationId = startConversation();
return chat(conversationId, message);
}
private String startConversation() {
String memoryId = generateChatMemoryId();
ChatMemory chatMemory = generateChatMemory(memoryId);
conversations.putIfAbsent(memoryId, chatMemory);
return memoryId;
}
private String generateChatMemoryId() {
return UUID.randomUUID().toString();
}
private ChatMemory generateChatMemory(String memoryId) {
return messageWindowChatMemoryBuilder
.id(memoryId)
.build();
}
}
package example.micronaut
import dev.langchain4j.data.message.UserMessage
import dev.langchain4j.memory.ChatMemory
import dev.langchain4j.memory.chat.MessageWindowChatMemory
import dev.langchain4j.model.chat.ChatModel
import jakarta.inject.Singleton
import java.util.*
import java.util.concurrent.ConcurrentHashMap
@Singleton
class AssistantWithMemory(
val messageWindowChatMemoryBuilder: MessageWindowChatMemory.Builder,
val model: ChatModel) {
private val conversations: MutableMap<String, ChatMemory> = ConcurrentHashMap<String, ChatMemory>()
fun chat(conversationId: String, message: String): MemoryIdAndResponse {
val chatMemory = requireNotNull(this.conversations[conversationId]) {
"Unknown conversation: $conversationId"
}
chatMemory.add(UserMessage.from(message))
val chatResponse = model.chat(chatMemory.messages())
val aiMessage = chatResponse.aiMessage()
chatMemory.add(aiMessage)
return MemoryIdAndResponse(conversationId, aiMessage.text())
}
fun chat(message: String): MemoryIdAndResponse {
val conversationId = startConversation()
return chat(conversationId, message)
}
private fun startConversation(): String {
val memoryId = generateChatMemoryId()
val chatMemory = generateChatMemory(memoryId)
conversations.putIfAbsent(memoryId, chatMemory)
return memoryId
}
private fun generateChatMemoryId(): String {
return UUID.randomUUID().toString()
}
private fun generateChatMemory(memoryId: String): ChatMemory {
return messageWindowChatMemoryBuilder
.id(memoryId)
.build()
}
}
package example.micronaut
import dev.langchain4j.data.message.AiMessage
import dev.langchain4j.data.message.UserMessage
import dev.langchain4j.memory.ChatMemory
import dev.langchain4j.memory.chat.MessageWindowChatMemory
import dev.langchain4j.model.chat.ChatModel
import dev.langchain4j.model.chat.response.ChatResponse
import jakarta.inject.Singleton
import java.util.concurrent.ConcurrentHashMap
@Singleton
class AssistantWithMemory {
private final Map<String, ChatMemory> conversations = new ConcurrentHashMap<>()
private final MessageWindowChatMemory.Builder messageWindowChatMemoryBuilder
private final ChatModel model
AssistantWithMemory(MessageWindowChatMemory.Builder messageWindowChatMemoryBuilder,
ChatModel model) {
this.model = model
this.messageWindowChatMemoryBuilder = messageWindowChatMemoryBuilder
}
private String startConversation() {
String memoryId = generateChatMemoryId()
ChatMemory chatMemory = generateChatMemory(memoryId)
conversations.putIfAbsent(memoryId, chatMemory)
memoryId
}
private String generateChatMemoryId() {
UUID.randomUUID().toString()
}
private ChatMemory generateChatMemory(String memoryId) {
messageWindowChatMemoryBuilder
.id(memoryId)
.build()
}
MemoryIdAndResponse chat(String memoryId, String message) {
ChatMemory chatMemory = conversations.get(memoryId)
if (chatMemory == null) {
throw new IllegalArgumentException("Unknown conversation: " + memoryId)
}
chatMemory.add(UserMessage.from(message))
ChatResponse chatResponse = model.chat(chatMemory.messages())
AiMessage aiMessage = chatResponse.aiMessage()
chatMemory.add(aiMessage)
new MemoryIdAndResponse(memoryId: memoryId, response: aiMessage.text())
}
MemoryIdAndResponse chat(String message) {
String conversationId = startConversation()
chat(conversationId, message)
}
}
You could invoke the previous class as illustrated in the following test:
@Test
void chatWithMemory(AssistantWithMemory assistant) {
MemoryIdAndResponse johnConversation = assistant.chat("Let me introduce myself. My name is John");
String johnConversationId = johnConversation.memoryId();
assertNotNull(johnConversationId);
MemoryIdAndResponse aegonConversation = assistant.chat("Let me introduce myself. My name is Dan");
String aegonConversationId = aegonConversation.memoryId();
assertNotNull(aegonConversationId);
MemoryIdAndResponse answer = assistant.chat(johnConversationId, "What's my name?");
assertTrue(answer.response().toLowerCase().contains("john"), answer.response());
answer = assistant.chat(aegonConversationId, "What's my name?");
assertTrue(answer.response().toLowerCase().contains("dan"), answer.response());
}
@Test
fun chatWithMemory(assistant: AssistantWithMemory) {
val johnConversation = assistant.chat("Let me introduce myself. My name is John")
val johnConversationId = johnConversation.memoryId
assertNotNull(johnConversationId)
val aegonConversation = assistant.chat("Let me introduce myself. My name is Dan")
val aegonConversationId = aegonConversation.memoryId
assertNotNull(aegonConversationId)
var answer = assistant.chat(johnConversationId, "What's my name?")
assertTrue(answer.response.lowercase().contains("john"), answer.response)
answer = assistant.chat(aegonConversationId, "What's my name?")
assertTrue(answer.response.lowercase().contains("dan"), answer.response)
}
@Test
void chatWithMemory(AssistantWithMemory assistant) {
MemoryIdAndResponse johnConversation = assistant.chat("Let me introduce myself. My name is John")
String johnConversationId = johnConversation.memoryId
assertNotNull(johnConversationId)
MemoryIdAndResponse aegonConversation = assistant.chat("Let me introduce myself. My name is Dan")
String aegonConversationId = aegonConversation.memoryId
assertNotNull(aegonConversationId)
MemoryIdAndResponse answer = assistant.chat(johnConversationId, "What's my name?")
assertTrue(answer.response.toLowerCase().contains("john"), answer.response)
answer = assistant.chat(aegonConversationId, "What's my name?")
assertTrue(answer.response.toLowerCase().contains("dan"), answer.response)
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-anthropic")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-anthropic</artifactId>
</dependency>
Example Configuration
langchain4j.anthropic.api-key=YOUR_KEY
langchain4j.anthropic.api-key: YOUR_KEY
"langchain4j.anthropic.api-key" = "YOUR_KEY"
langchain4j.anthropic.apiKey = 'YOUR_KEY'
{
langchain4j.anthropic.api-key = "YOUR_KEY"
}
{
"langchain4j.anthropic.api-key": "YOUR_KEY"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-azure")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-azure</artifactId>
</dependency>
Example Configuration
langchain4j.azure-open-ai.api-key=YOUR_KEY
langchain4j.azure-open-ai.endpoint=YOUR_ENDPOINT
langchain4j.azure-open-ai.api-key: YOUR_KEY
langchain4j.azure-open-ai.endpoint: YOUR_ENDPOINT
"langchain4j.azure-open-ai.api-key" = "YOUR_KEY"
"langchain4j.azure-open-ai.endpoint" = "YOUR_ENDPOINT"
langchain4j.azureOpenAi.apiKey = 'YOUR_KEY'
langchain4j.azureOpenAi.endpoint = 'YOUR_ENDPOINT'
{
langchain4j.azure-open-ai.api-key = "YOUR_KEY"
langchain4j.azure-open-ai.endpoint = "YOUR_ENDPOINT"
}
{
"langchain4j.azure-open-ai.api-key": "YOUR_KEY",
"langchain4j.azure-open-ai.endpoint": "YOUR_ENDPOINT"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-bedrock")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-bedrock</artifactId>
</dependency>
Example Configuration
langchain4j.bedrock-llama.api-key=YOUR_KEY
langchain4j.bedrock-llama.api-key: YOUR_KEY
"langchain4j.bedrock-llama.api-key" = "YOUR_KEY"
langchain4j.bedrockLlama.apiKey = 'YOUR_KEY'
{
langchain4j.bedrock-llama.api-key = "YOUR_KEY"
}
{
"langchain4j.bedrock-llama.api-key": "YOUR_KEY"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-hugging-face")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-hugging-face</artifactId>
</dependency>
Example Configuration
langchain4j.hugging-face.access-token=YOUR_ACCESS_TOKEN
langchain4j.hugging-face.access-token: YOUR_ACCESS_TOKEN
"langchain4j.hugging-face.access-token" = "YOUR_ACCESS_TOKEN"
langchain4j.huggingFace.accessToken = 'YOUR_ACCESS_TOKEN'
{
langchain4j.hugging-face.access-token = "YOUR_ACCESS_TOKEN"
}
{
"langchain4j.hugging-face.access-token": "YOUR_ACCESS_TOKEN"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-mistralai")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-mistralai</artifactId>
</dependency>
Example Configuration
langchain4j.mistral-ai.api-key=YOUR_KEY
langchain4j.mistral-ai.api-key: YOUR_KEY
"langchain4j.mistral-ai.api-key" = "YOUR_KEY"
langchain4j.mistralAi.apiKey = 'YOUR_KEY'
{
langchain4j.mistral-ai.api-key = "YOUR_KEY"
}
{
"langchain4j.mistral-ai.api-key": "YOUR_KEY"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-ollama")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-ollama</artifactId>
</dependency>
Example Configuration
langchain4j.ollama.base-url=YOUR_URL
langchain4j.ollama.base-url: YOUR_URL
"langchain4j.ollama.base-url" = "YOUR_URL"
langchain4j.ollama.baseUrl = 'YOUR_URL'
{
langchain4j.ollama.base-url = "YOUR_URL"
}
{
"langchain4j.ollama.base-url": "YOUR_URL"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-oci-genai")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-oci-genai</artifactId>
</dependency>
Example Configuration
langchain4j.oci-gen-ai.chat-model.model-name=orca-mini
langchain4j.oci-gen-ai.compartment-id=your-compartment
langchain4j.oci-gen-ai.chat-model.model-name: orca-mini
langchain4j.oci-gen-ai.compartment-id: your-compartment
"langchain4j.oci-gen-ai.chat-model.model-name" = "orca-mini"
"langchain4j.oci-gen-ai.compartment-id" = "your-compartment"
langchain4j.ociGenAi.chatModel.modelName = 'orca-mini'
langchain4j.ociGenAi.compartmentId = 'your-compartment'
{
langchain4j.oci-gen-ai.chat-model.model-name = "orca-mini"
langchain4j.oci-gen-ai.compartment-id = "your-compartment"
}
{
"langchain4j.oci-gen-ai.chat-model.model-name": "orca-mini",
"langchain4j.oci-gen-ai.compartment-id": "your-compartment"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-openai")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-openai</artifactId>
</dependency>
Provider modules use Micronaut’s HTTP client API and exclude LangChain4j’s JDK HTTP client.
Add a concrete Micronaut HTTP client implementation to your application, for example the default Netty implementation:
runtimeOnly("io.micronaut:micronaut-http-client")
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>runtime</scope>
</dependency>
For tests that instantiate OpenAI models, add the same implementation to the test runtime classpath:
testRuntimeOnly("io.micronaut:micronaut-http-client")
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>test</scope>
</dependency>
Example Configuration
langchain4j.open-ai.api-key=YOUR_KEY
langchain4j.open-ai.api-key: YOUR_KEY
"langchain4j.open-ai.api-key" = "YOUR_KEY"
langchain4j.openAi.apiKey = 'YOUR_KEY'
{
langchain4j.open-ai.api-key = "YOUR_KEY"
}
{
"langchain4j.open-ai.api-key": "YOUR_KEY"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-googleai-gemini")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-googleai-gemini</artifactId>
</dependency>
Example Configuration
langchain4j.google-ai-gemini.api-key=YOUR_API_KEY
langchain4j.google-ai-gemini.api-key: YOUR_API_KEY
"langchain4j.google-ai-gemini.api-key" = "YOUR_API_KEY"
langchain4j.googleAiGemini.apiKey = 'YOUR_API_KEY'
{
langchain4j.google-ai-gemini.api-key = "YOUR_API_KEY"
}
{
"langchain4j.google-ai-gemini.api-key": "YOUR_API_KEY"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-vertexai")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-vertexai</artifactId>
</dependency>
To provide explicit Google Cloud credentials, register a GoogleCredentials bean. When no such bean is present, the Vertex AI client uses Application Default Credentials.
Example Configuration
langchain4j.vertex-ai.endpoint=YOUR_ENDPOINT
langchain4j.vertex-ai.model-name=YOUR_MODEL
langchain4j.vertex-ai.project=YOUR_PROJECT
langchain4j.vertex-ai.location=YOUR_LOCATION
langchain4j.vertex-ai.publisher=YOUR_PUBLISHER
langchain4j.vertex-ai.endpoint: YOUR_ENDPOINT
langchain4j.vertex-ai.model-name: YOUR_MODEL
langchain4j.vertex-ai.project: YOUR_PROJECT
langchain4j.vertex-ai.location: YOUR_LOCATION
langchain4j.vertex-ai.publisher: YOUR_PUBLISHER
"langchain4j.vertex-ai.endpoint" = "YOUR_ENDPOINT"
"langchain4j.vertex-ai.model-name" = "YOUR_MODEL"
"langchain4j.vertex-ai.project" = "YOUR_PROJECT"
"langchain4j.vertex-ai.location" = "YOUR_LOCATION"
"langchain4j.vertex-ai.publisher" = "YOUR_PUBLISHER"
langchain4j.vertexAi.endpoint = 'YOUR_ENDPOINT'
langchain4j.vertexAi.modelName = 'YOUR_MODEL'
langchain4j.vertexAi.project = 'YOUR_PROJECT'
langchain4j.vertexAi.location = 'YOUR_LOCATION'
langchain4j.vertexAi.publisher = 'YOUR_PUBLISHER'
{
langchain4j.vertex-ai.endpoint = "YOUR_ENDPOINT"
langchain4j.vertex-ai.model-name = "YOUR_MODEL"
langchain4j.vertex-ai.project = "YOUR_PROJECT"
langchain4j.vertex-ai.location = "YOUR_LOCATION"
langchain4j.vertex-ai.publisher = "YOUR_PUBLISHER"
}
{
"langchain4j.vertex-ai.endpoint": "YOUR_ENDPOINT",
"langchain4j.vertex-ai.model-name": "YOUR_MODEL",
"langchain4j.vertex-ai.project": "YOUR_PROJECT",
"langchain4j.vertex-ai.location": "YOUR_LOCATION",
"langchain4j.vertex-ai.publisher": "YOUR_PUBLISHER"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-vertexai-gemini")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-vertexai-gemini</artifactId>
</dependency>
To provide explicit Google Cloud credentials, register a GoogleCredentials bean. When no such bean is present, the Vertex AI Gemini client uses Application Default Credentials.
Example Configuration
langchain4j.vertex-ai-gemini.model-name=YOUR_MODEL
langchain4j.vertex-ai-gemini.project=YOUR_PROJECT
langchain4j.vertex-ai-gemini.location=YOUR_LOCATION
langchain4j.vertex-ai-gemini.model-name: YOUR_MODEL
langchain4j.vertex-ai-gemini.project: YOUR_PROJECT
langchain4j.vertex-ai-gemini.location: YOUR_LOCATION
"langchain4j.vertex-ai-gemini.model-name" = "YOUR_MODEL"
"langchain4j.vertex-ai-gemini.project" = "YOUR_PROJECT"
"langchain4j.vertex-ai-gemini.location" = "YOUR_LOCATION"
langchain4j.vertexAiGemini.modelName = 'YOUR_MODEL'
langchain4j.vertexAiGemini.project = 'YOUR_PROJECT'
langchain4j.vertexAiGemini.location = 'YOUR_LOCATION'
{
langchain4j.vertex-ai-gemini.model-name = "YOUR_MODEL"
langchain4j.vertex-ai-gemini.project = "YOUR_PROJECT"
langchain4j.vertex-ai-gemini.location = "YOUR_LOCATION"
}
{
"langchain4j.vertex-ai-gemini.model-name": "YOUR_MODEL",
"langchain4j.vertex-ai-gemini.project": "YOUR_PROJECT",
"langchain4j.vertex-ai-gemini.location": "YOUR_LOCATION"
}
An in-memory embedding store is enable by default, set the following property
langchain4j.in-memory.embedding-store.enabled with value false to disable it.
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-chroma")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-chroma</artifactId>
</dependency>
Example Configuration
langchain4j.chroma.embedding-store.base-url=http://localhost:8000
langchain4j.chroma.embedding-store.collection-name=documents
langchain4j.chroma.embedding-store.api-version=V2
langchain4j.chroma.embedding-store.base-url: http://localhost:8000
langchain4j.chroma.embedding-store.collection-name: documents
langchain4j.chroma.embedding-store.api-version: V2
"langchain4j.chroma.embedding-store.base-url" = "http://localhost:8000"
"langchain4j.chroma.embedding-store.collection-name" = "documents"
"langchain4j.chroma.embedding-store.api-version" = "V2"
langchain4j.chroma.embeddingStore.baseUrl = 'http://localhost:8000'
langchain4j.chroma.embeddingStore.collectionName = 'documents'
langchain4j.chroma.embeddingStore.apiVersion = 'V2'
{
langchain4j.chroma.embedding-store.base-url = "http://localhost:8000"
langchain4j.chroma.embedding-store.collection-name = "documents"
langchain4j.chroma.embedding-store.api-version = "V2"
}
{
"langchain4j.chroma.embedding-store.base-url": "http://localhost:8000",
"langchain4j.chroma.embedding-store.collection-name": "documents",
"langchain4j.chroma.embedding-store.api-version": "V2"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-elasticsearch")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-elasticsearch</artifactId>
</dependency>
Example Configuration
elasticsearch.httpHosts=http://localhost:9200,http://127.0.0.2:9200
langchain4j.elasticsearch.embedding-stores.default.dimension=384
elasticsearch.httpHosts: "http://localhost:9200,http://127.0.0.2:9200"
langchain4j.elasticsearch.embedding-stores.default.dimension: 384
"elasticsearch.httpHosts" = "http://localhost:9200,http://127.0.0.2:9200"
"langchain4j.elasticsearch.embedding-stores.default.dimension" = 384
elasticsearch.httpHosts = 'http://localhost:9200,http://127.0.0.2:9200'
langchain4j.elasticsearch.embeddingStores.default.dimension = 384
{
elasticsearch.httpHosts = "http://localhost:9200,http://127.0.0.2:9200"
langchain4j.elasticsearch.embedding-stores.default.dimension = 384
}
{
"elasticsearch.httpHosts": "http://localhost:9200,http://127.0.0.2:9200",
"langchain4j.elasticsearch.embedding-stores.default.dimension": 384
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-mongodb-atlas")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-mongodb-atlas</artifactId>
</dependency>
Configuring a MongoDB server
mongodb.servers.default.uri: mongodb://username:password@localhost:27017/databaseName
Example Configuration
langchain4j.mongodb-atlas.embedding-stores.default.database-name=testdb
langchain4j.mongodb-atlas.embedding-stores.default.collection-name=testcol
langchain4j.mongodb-atlas.embedding-stores.default.index-name=testindex
langchain4j.mongodb-atlas.embedding-stores.default.database-name: testdb
langchain4j.mongodb-atlas.embedding-stores.default.collection-name: testcol
langchain4j.mongodb-atlas.embedding-stores.default.index-name: testindex
"langchain4j.mongodb-atlas.embedding-stores.default.database-name" = "testdb"
"langchain4j.mongodb-atlas.embedding-stores.default.collection-name" = "testcol"
"langchain4j.mongodb-atlas.embedding-stores.default.index-name" = "testindex"
langchain4j.mongodbAtlas.embeddingStores.default.databaseName = 'testdb'
langchain4j.mongodbAtlas.embeddingStores.default.collectionName = 'testcol'
langchain4j.mongodbAtlas.embeddingStores.default.indexName = 'testindex'
{
langchain4j.mongodb-atlas.embedding-stores.default.database-name = "testdb"
langchain4j.mongodb-atlas.embedding-stores.default.collection-name = "testcol"
langchain4j.mongodb-atlas.embedding-stores.default.index-name = "testindex"
}
{
"langchain4j.mongodb-atlas.embedding-stores.default.database-name": "testdb",
"langchain4j.mongodb-atlas.embedding-stores.default.collection-name": "testcol",
"langchain4j.mongodb-atlas.embedding-stores.default.index-name": "testindex"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-neo4j")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-neo4j</artifactId>
</dependency>
Example Configuration
neo4j.uri=bolt://localhost
langchain4j.neo4j.embedding-stores.default.dimension=384
neo4j.uri: bolt://localhost
langchain4j.neo4j.embedding-stores.default.dimension: 384
"neo4j.uri" = "bolt://localhost"
"langchain4j.neo4j.embedding-stores.default.dimension" = 384
neo4j.uri = 'bolt://localhost'
langchain4j.neo4j.embeddingStores.default.dimension = 384
{
neo4j.uri = "bolt://localhost"
langchain4j.neo4j.embedding-stores.default.dimension = 384
}
{
"neo4j.uri": "bolt://localhost",
"langchain4j.neo4j.embedding-stores.default.dimension": 384
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-oracle")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-oracle</artifactId>
</dependency>
runtimeOnly("io.micronaut.sql:micronaut-jdbc-hikari")
<dependency>
<groupId>io.micronaut.sql</groupId>
<artifactId>micronaut-jdbc-hikari</artifactId>
<scope>runtime</scope>
</dependency>
Example Configuration
datasources.default.dialect=oracle
langchain4j.oracle.embedding-stores.default.table=test
langchain4j.oracle.embedding-stores.default.table.create-option=create_if_not_exists
datasources.default.dialect: oracle
langchain4j.oracle.embedding-stores.default.table: test
langchain4j.oracle.embedding-stores.default.table.create-option: create_if_not_exists
"datasources.default.dialect" = "oracle"
"langchain4j.oracle.embedding-stores.default.table" = "test"
"langchain4j.oracle.embedding-stores.default.table.create-option" = "create_if_not_exists"
datasources.default.dialect = 'oracle'
langchain4j.oracle.embeddingStores.default.table = 'test'
langchain4j.oracle.embeddingStores.default.table.createOption = 'create_if_not_exists'
{
datasources.default.dialect = "oracle"
langchain4j.oracle.embedding-stores.default.table = "test"
langchain4j.oracle.embedding-stores.default.table.create-option = "create_if_not_exists"
}
{
"datasources.default.dialect": "oracle",
"langchain4j.oracle.embedding-stores.default.table": "test",
"langchain4j.oracle.embedding-stores.default.table.create-option": "create_if_not_exists"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-opensearch")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-opensearch</artifactId>
</dependency>
Example Configuration
micronaut.opensearch.rest-client.http-hosts=http://localhost:9200,http://127.0.0.2:9200
langchain4j.opensearch.embedding-stores.default.dimension=384
micronaut.opensearch.rest-client.http-hosts: "http://localhost:9200,http://127.0.0.2:9200"
langchain4j.opensearch.embedding-stores.default.dimension: 384
"micronaut.opensearch.rest-client.http-hosts" = "http://localhost:9200,http://127.0.0.2:9200"
"langchain4j.opensearch.embedding-stores.default.dimension" = 384
micronaut.opensearch.restClient.httpHosts = 'http://localhost:9200,http://127.0.0.2:9200'
langchain4j.opensearch.embeddingStores.default.dimension = 384
{
micronaut.opensearch.rest-client.http-hosts = "http://localhost:9200,http://127.0.0.2:9200"
langchain4j.opensearch.embedding-stores.default.dimension = 384
}
{
"micronaut.opensearch.rest-client.http-hosts": "http://localhost:9200,http://127.0.0.2:9200",
"langchain4j.opensearch.embedding-stores.default.dimension": 384
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-pgvector")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-pgvector</artifactId>
</dependency>
runtimeOnly("io.micronaut.sql:micronaut-jdbc-hikari")
<dependency>
<groupId>io.micronaut.sql</groupId>
<artifactId>micronaut-jdbc-hikari</artifactId>
<scope>runtime</scope>
</dependency>
Example Configuration
datasources.default.dialect=postgres
langchain4j.pgvector.embedding-stores.default.table=mytable
langchain4j.pgvector.embedding-stores.default.dimension=384
test-resources.containers.postgres.image-name=pgvector/pgvector:pg16
datasources.default.dialect: postgres
langchain4j.pgvector.embedding-stores.default.table: "mytable"
langchain4j.pgvector.embedding-stores.default.dimension: 384
# Add this if you plan to use testresources
test-resources.containers.postgres.image-name: pgvector/pgvector:pg16
"datasources.default.dialect" = "postgres"
"langchain4j.pgvector.embedding-stores.default.table" = "mytable"
"langchain4j.pgvector.embedding-stores.default.dimension" = 384
"test-resources.containers.postgres.image-name" = "pgvector/pgvector:pg16"
datasources.default.dialect = 'postgres'
langchain4j.pgvector.embeddingStores.default.table = 'mytable'
langchain4j.pgvector.embeddingStores.default.dimension = 384
testResources.containers.postgres.imageName = 'pgvector/pgvector:pg16'
{
datasources.default.dialect = "postgres"
langchain4j.pgvector.embedding-stores.default.table = "mytable"
langchain4j.pgvector.embedding-stores.default.dimension = 384
test-resources.containers.postgres.image-name = "pgvector/pgvector:pg16"
}
{
"datasources.default.dialect": "postgres",
"langchain4j.pgvector.embedding-stores.default.table": "mytable",
"langchain4j.pgvector.embedding-stores.default.dimension": 384,
"test-resources.containers.postgres.image-name": "pgvector/pgvector:pg16"
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-redis")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-redis</artifactId>
</dependency>
Example Configuration
langchain4j.redis.embedding-store.host=localhost
langchain4j.redis.embedding-store.port=6379
langchain4j.redis.embedding-stores.default.dimension=384
langchain4j.redis.embedding-store.host: localhost
langchain4j.redis.embedding-store.port: 6379
langchain4j.redis.embedding-stores.default.dimension: 384
"langchain4j.redis.embedding-store.host" = "localhost"
"langchain4j.redis.embedding-store.port" = 6379
"langchain4j.redis.embedding-stores.default.dimension" = 384
langchain4j.redis.embeddingStore.host = 'localhost'
langchain4j.redis.embeddingStore.port = 6379
langchain4j.redis.embeddingStores.default.dimension = 384
{
langchain4j.redis.embedding-store.host = "localhost"
langchain4j.redis.embedding-store.port = 6379
langchain4j.redis.embedding-stores.default.dimension = 384
}
{
"langchain4j.redis.embedding-store.host": "localhost",
"langchain4j.redis.embedding-store.port": 6379,
"langchain4j.redis.embedding-stores.default.dimension": 384
}
Add the following dependency:
implementation("io.micronaut.langchain4j:micronaut-langchain4j-store-qdrant")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-store-qdrant</artifactId>
</dependency>
To use Testcontainers & Test Resources add the following dependency:
testResourcesService("io.micronaut.langchain4j:micronaut-langchain4j-qdrant-testresource")
<dependency>
<groupId>io.micronaut.langchain4j</groupId>
<artifactId>micronaut-langchain4j-qdrant-testresource</artifactId>
<scope>testResourcesService</scope>
</dependency>
Example Configuration
langchain4j.qdrant.embedding-store.host=localhost
langchain4j.qdrant.embedding-store.port=6334
langchain4j.qdrant.embedding-store.collection-name=mycollection
# Omitt the following 2 properties if you use Test resources
langchain4j.qdrant.embedding-store.host: localhost
langchain4j.qdrant.embedding-store.port: 6334
# Minimal configuration required for Test resources
langchain4j.qdrant.embedding-store.collection-name: mycollection
"langchain4j.qdrant.embedding-store.host" = "localhost"
"langchain4j.qdrant.embedding-store.port" = 6334
"langchain4j.qdrant.embedding-store.collection-name" = "mycollection"
langchain4j.qdrant.embeddingStore.host = 'localhost'
langchain4j.qdrant.embeddingStore.port = 6334
langchain4j.qdrant.embeddingStore.collectionName = 'mycollection'
{
langchain4j.qdrant.embedding-store.host = "localhost"
langchain4j.qdrant.embedding-store.port = 6334
langchain4j.qdrant.embedding-store.collection-name = "mycollection"
}
{
"langchain4j.qdrant.embedding-store.host": "localhost",
"langchain4j.qdrant.embedding-store.port": 6334,
"langchain4j.qdrant.embedding-store.collection-name": "mycollection"
}
You can find the source code of this project in this repository:
For this project, you can find a list of releases (with release notes) here: