From 03036c32eac6548e8cbecdcce599a542404dd7b8 Mon Sep 17 00:00:00 2001 From: Thanaphoom Babparn Date: Sat, 18 Jul 2026 22:25:51 +0700 Subject: [PATCH] feat(langchain4j): make modelName optional when ChatModel or StreamingChatModel is provided --- .../adk/models/langchain4j/LangChain4j.java | 30 ++++++- .../models/langchain4j/LangChain4jTest.java | 87 +++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/contrib/langchain4j/src/main/java/com/google/adk/models/langchain4j/LangChain4j.java b/contrib/langchain4j/src/main/java/com/google/adk/models/langchain4j/LangChain4j.java index b35c68f89..ae2544e6e 100644 --- a/contrib/langchain4j/src/main/java/com/google/adk/models/langchain4j/LangChain4j.java +++ b/contrib/langchain4j/src/main/java/com/google/adk/models/langchain4j/LangChain4j.java @@ -57,6 +57,7 @@ import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.ChatRequestParameters; import dev.langchain4j.model.chat.request.ToolChoice; import dev.langchain4j.model.chat.request.json.JsonArraySchema; import dev.langchain4j.model.chat.request.json.JsonBooleanSchema; @@ -100,6 +101,7 @@ public abstract class LangChain4j extends BaseLlm { public abstract ObjectMapper objectMapper(); + @Nullable public abstract String modelName(); @Nullable @@ -126,7 +128,33 @@ public abstract static class Builder { public abstract Builder modelName(String modelName); - public abstract LangChain4j build(); + abstract @Nullable ChatModel chatModel(); + + abstract @Nullable StreamingChatModel streamingChatModel(); + + abstract @Nullable String modelName(); + + abstract LangChain4j autoBuild(); + + public LangChain4j build() { + if (modelName() == null) { + String name = chatModel() == null ? null : nameOf(chatModel().defaultRequestParameters()); + if (name == null && streamingChatModel() != null) { + name = nameOf(streamingChatModel().defaultRequestParameters()); + } + if (name == null) { + throw new IllegalStateException( + "Set modelName(), or use a ChatModel or StreamingChatModel whose" + + " defaultRequestParameters() declare a model name"); + } + modelName(name); + } + return autoBuild(); + } + + private static @Nullable String nameOf(@Nullable ChatRequestParameters parameters) { + return parameters == null ? null : parameters.modelName(); + } } public LangChain4j(ChatModel chatModel) { diff --git a/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/LangChain4jTest.java b/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/LangChain4jTest.java index affacd7c3..9c56a0743 100644 --- a/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/LangChain4jTest.java +++ b/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/LangChain4jTest.java @@ -16,6 +16,7 @@ package com.google.adk.models.langchain4j; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -31,6 +32,7 @@ import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.ChatRequestParameters; import dev.langchain4j.model.chat.request.json.JsonObjectSchema; import dev.langchain4j.model.chat.request.json.JsonStringSchema; import dev.langchain4j.model.chat.response.ChatResponse; @@ -1145,4 +1147,89 @@ void testGenerateContentWithMalformedCharsetFallback() { assertThat(textContent.text()).isEqualTo(textPayload); } + + @Test + @DisplayName("Should auto-detect model name from ChatModel when not explicitly set") + void testBuilderAutoDetectsModelNameFromChatModel() { + // Given + final ChatRequestParameters params = mock(ChatRequestParameters.class); + when(params.modelName()).thenReturn("auto-detected-model"); + when(chatModel.defaultRequestParameters()).thenReturn(params); + + // When + final LangChain4j lc4j = LangChain4j.builder().chatModel(chatModel).build(); + + // Then + assertThat(lc4j.modelName()).isEqualTo("auto-detected-model"); + assertThat(lc4j.model()).isEqualTo("auto-detected-model"); + } + + @Test + @DisplayName("Should auto-detect model name from StreamingChatModel when not explicitly set") + void testBuilderAutoDetectsModelNameFromStreamingChatModel() { + // Given + final ChatRequestParameters params = mock(ChatRequestParameters.class); + when(params.modelName()).thenReturn("auto-detected-streaming-model"); + when(streamingChatModel.defaultRequestParameters()).thenReturn(params); + + // When + final LangChain4j lc4j = LangChain4j.builder().streamingChatModel(streamingChatModel).build(); + + // Then + assertThat(lc4j.modelName()).isEqualTo("auto-detected-streaming-model"); + assertThat(lc4j.model()).isEqualTo("auto-detected-streaming-model"); + } + + @Test + @DisplayName("Should prefer explicit model name over auto-detected one") + void testBuilderPrefersExplicitModelName() { + // Given + final ChatRequestParameters params = mock(ChatRequestParameters.class); + when(params.modelName()).thenReturn("auto-detected-model"); + when(chatModel.defaultRequestParameters()).thenReturn(params); + + // When + final LangChain4j lc4j = + LangChain4j.builder().chatModel(chatModel).modelName("explicit-model").build(); + + // Then + assertThat(lc4j.modelName()).isEqualTo("explicit-model"); + } + + @Test + @DisplayName("Should fall back to StreamingChatModel name when ChatModel has none") + void testBuilderFallsBackToStreamingModelNameWhenChatModelHasNone() { + // Given + final ChatRequestParameters emptyParams = mock(ChatRequestParameters.class); + when(chatModel.defaultRequestParameters()).thenReturn(emptyParams); + final ChatRequestParameters streamingParams = mock(ChatRequestParameters.class); + when(streamingParams.modelName()).thenReturn("streaming-model"); + when(streamingChatModel.defaultRequestParameters()).thenReturn(streamingParams); + + // When + final LangChain4j lc4j = + LangChain4j.builder().chatModel(chatModel).streamingChatModel(streamingChatModel).build(); + + // Then + assertThat(lc4j.modelName()).isEqualTo("streaming-model"); + } + + @Test + @DisplayName("Should fail when no source declares a model name") + void testBuilderFailsWhenNoModelNameAvailable() { + // Given + final ChatRequestParameters emptyParams = mock(ChatRequestParameters.class); + when(chatModel.defaultRequestParameters()).thenReturn(emptyParams); + when(streamingChatModel.defaultRequestParameters()).thenReturn(emptyParams); + + // When / Then + assertThrows( + IllegalStateException.class, + () -> + LangChain4j.builder() + .chatModel(chatModel) + .streamingChatModel(streamingChatModel) + .build()); + assertThrows(IllegalStateException.class, () -> LangChain4j.builder().build()); + } }