Repository: kestra-io/plugin-langchain4j
Pull request #46: Add support for more language providers #28
Author: login: rkampani
Labels: name: kind/external
description: Pull requests raised by community contributors
Created: 2025-05-12T21:43:56Z | Merged: 2025-05-16T07:52:54Z

<!-- Thanks for submitting a Pull Request to kestra. To help us review your contribution, please follow the guidelines below:

- Make sure that your commits follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification e.g. `feat(ui): add a new navigation menu item` or `fix(core): fix a bug in the core model` or `docs: update the README.md`. This will help us automatically generate the changelog.
- The title should briefly summarize the proposed changes.
- Provide a short overview of the change and the value it adds.
- Share a flow example to help the reviewer understand and QA the change.
- Use "close" to automatically close an issue. For example, `close #1234` will close issue #1234. -->

### What changes are being made and why?
<!-- Please include a brief summary of the changes included in this PR e.g. closes #1234. -->

---

### How the changes have been QAed?

<!-- Include example code that shows how this PR has been QAed. The code should present a complete yet easily reproducible flow.

```yaml
# Your example flow code here
```

Note that this is not a replacement for unit tests but rather a way to demonstrate how the changes work in a real-life scenario, as the end-user would experience them.

Remove this section if this change applies to all flows or to the documentation only. -->

---

### Setup Instructions

<!--If there are any setup requirements like API keys or trial accounts, kindly include brief bullet-points-description outlining the setup process below.

- [External System Documentation](URL)
- Steps to set up the necessary resources

If there are no setup requirements, you can remove this section.

Thank you for your contribution. ❤️  -->



Diff:
diff --git a/build.gradle b/build.gradle
index d3f2e984..17558424 100644
--- a/build.gradle
+++ b/build.gradle
@@ -56,6 +56,8 @@ dependencies {
     implementation "dev.langchain4j:langchain4j-ollama:$langchain4jVersion"
     implementation "dev.langchain4j:langchain4j-google-ai-gemini:$langchain4jVersion"
     implementation "dev.langchain4j:langchain4j-vertex-ai:$langchain4jVersion"
+    implementation "dev.langchain4j:langchain4j-anthropic:$langchain4jVersion"
+    implementation "dev.langchain4j:langchain4j-mistral-ai:$langchain4jVersion"
     implementation "dev.langchain4j:langchain4j-pgvector:$langchain4jVersion"
     implementation "dev.langchain4j:langchain4j-elasticsearch:$langchain4jVersion"
 }
diff --git a/gradle.properties b/gradle.properties
index a4b9b1a6..33ec622e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
 version=0.23.0-SNAPSHOT
 kestraVersion=[0.23,)
-langchain4jVersion=1.0.0-beta4
+langchain4jVersion=1.0.0-beta3
diff --git a/src/main/java/io/kestra/plugin/langchain4j/ChatCompletion.java b/src/main/java/io/kestra/plugin/langchain4j/ChatCompletion.java
index 02dffacc..edf16b24 100644
--- a/src/main/java/io/kestra/plugin/langchain4j/ChatCompletion.java
+++ b/src/main/java/io/kestra/plugin/langchain4j/ChatCompletion.java
@@ -27,8 +27,8 @@
 @Getter
 @NoArgsConstructor
 @Schema(
-    title = "Complete chats with AI models.", 
-    description = "Handles chat interactions using AI models (OpenAI, Ollama, Gemini).")
+    title = "Complete chats with AI models.",
+    description = "Handles chat interactions using AI models (OpenAI, Ollama, Gemini, Anthropic, MistralAI, Deepseek).")
 @Plugin(
     examples = {
         @Example(
@@ -74,6 +74,68 @@
                         content: Hello, I need information about your services
                 """
             }
+        ),
+        @Example(
+            title = "Chat Completion with Anthropic AI",
+            full = true,
+            code = {
+                """
+                id: anthropic_chat_completion
+                namespace: company.team
+                 task:
+                    id: chat_completion
+                    type: io.kestra.core.plugin.langchain4j.ChatCompletion
+                    provider:
+                        type: io.kestra.plugin.langchain4j.provider.AnthropicAI
+                        apiKey: your_anthropic_api_key
+                        modelName: claude-3-haiku-20240307
+                    messages:
+                      - type: USER
+                        content: Hello, my name is John
+                """
+            }
+        ),
+        @Example(
+            title = "Chat Completion with Deepseek",
+            full = true,
+            code = {
+                """
+                id: deepseek_chat_completion
+                namespace: company.team
+                 task:
+                    id: chat_completion
+                    type: io.kestra.core.plugin.langchain4j.ChatCompletion
+                    provider:
+                        type: io.kestra.plugin.langchain4j.provider.DeepseekAI
+                        apiKey: your_deepseek_api_key
+                        modelName: deepseek-chat
+                        baseUrl: https://api.deepseek.com
+                    messages:
+                      - type: USER
+                        content: Hello, my name is John
+                """
+            }
+        ),
+        @Example(
+            title = "Chat Completion with MistralAI",
+            full = true,
+            code = {
+                """
+                id: mistral_chat_completion
+                namespace: company.team
+                 task:
+                    id: chat_completion
+                    type: io.kestra.core.plugin.langchain4j.ChatCompletion
+                    provider:
+                        type: io.kestra.plugin.langchain4j.provider.MistralAI
+                        apiKey: your_mistral_api_key
+                        modelName: mistral:7b
+                        baseUrl: https://api.mistral.ai/v1
+                    messages:
+                      - type: USER
+                        content: Hello, my name is John
+                """
+            }
         )
     },
     beta = true
diff --git a/src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java b/src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java
new file mode 100644
index 00000000..3be94e87
--- /dev/null
+++ b/src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java
@@ -0,0 +1,56 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Anthropic AI Model Provider"
+)
+public class AnthropicAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return AnthropicChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topK(runContext.render(configuration.getTopK()).as(Integer.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .build();
+    }
+
+    @Override
+    public ImageModel imageModel(RunContext runContext) {
+        throw new UnsupportedOperationException("Anthropic didn't support image model");
+    }
+
+    @Override
+    public EmbeddingModel embeddingModel(RunContext runContext) throws IllegalVariableEvaluationException {
+        throw new UnsupportedOperationException("Anthropic doesn’t support embeddings");
+    }
+
+}
diff --git a/src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java b/src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
new file mode 100644
index 00000000..10840fb6
--- /dev/null
+++ b/src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
@@ -0,0 +1,72 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Deepseek Model Provider"
+)
+public class DeepseekAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        if (configuration.getTopK() != null) {
+            throw new IllegalArgumentException("OpenAI models didn't support topK");
+        }
+        return OpenAiChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .baseUrl(runContext.render(baseUrl).as(String.class).orElse(null))
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .build();
+    }
+
+    @Override
+    public ImageModel imageModel(RunContext runContext) throws IllegalVariableEvaluationException {
+        return OpenAiImageModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .baseUrl(runContext.render(baseUrl).as(String.class).orElse(null))
+            .build();
+    }
+
+    @Override
+    public EmbeddingModel embeddingModel(RunContext runContext) throws IllegalVariableEvaluationException {
+        return OpenAiEmbeddingModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .baseUrl(runContext.render(baseUrl).as(String.class).orElse(null))
+            .build();
+    }
+
+}
diff --git a/src/main/java/io/kestra/plugin/langchain4j/provider/MistralAI.java b/src/main/java/io/kestra/plugin/langchain4j/provider/MistralAI.java
new file mode 100644
index 00000000..74c7e7c3
--- /dev/null
+++ b/src/main/java/io/kestra/plugin/langchain4j/provider/MistralAI.java
@@ -0,0 +1,69 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.mistralai.MistralAiChatModel;
+import dev.langchain4j.model.mistralai.MistralAiEmbeddingModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Mistral AI Model Provider"
+)
+public class MistralAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        if (configuration.getTopK() != null) {
+            throw new IllegalArgumentException("Mistral models didn't support topK");
+        }
+        return MistralAiChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .baseUrl(runContext.render(this.baseUrl).as(String.class).orElse(null))
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .build();
+    }
+
+    @Override
+    public ImageModel imageModel(RunContext runContext) {
+        throw new UnsupportedOperationException("MistralAI didn't support image model");
+    }
+
+    @Override
+    public EmbeddingModel embeddingModel(RunContext runContext) throws IllegalVariableEvaluationException {
+        return MistralAiEmbeddingModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .baseUrl(runContext.render(this.baseUrl).as(String.class).orElse(null))
+            .build();
+
+    }
+
+}
diff --git a/src/test/java/io/kestra/plugin/langchain4j/ChatCompletionTest.java b/src/test/java/io/kestra/plugin/langchain4j/ChatCompletionTest.java
index 5305aa5f..da337c06 100644
--- a/src/test/java/io/kestra/plugin/langchain4j/ChatCompletionTest.java
+++ b/src/test/java/io/kestra/plugin/langchain4j/ChatCompletionTest.java
@@ -1,13 +1,21 @@
 package io.kestra.plugin.langchain4j;
 
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.anthropic.AnthropicChatModelName;
+import dev.langchain4j.model.mistralai.MistralAiModels;
 import io.kestra.core.junit.annotations.KestraTest;
 import io.kestra.core.models.property.Property;
 import io.kestra.core.runners.RunContext;
 import io.kestra.core.runners.RunContextFactory;
+import io.kestra.plugin.langchain4j.provider.AnthropicAI;
+import io.kestra.plugin.langchain4j.provider.DeepseekAI;
 import io.kestra.plugin.langchain4j.provider.GoogleGemini;
+import io.kestra.plugin.langchain4j.provider.MistralAI;
 import io.kestra.plugin.langchain4j.provider.Ollama;
 import io.kestra.plugin.langchain4j.provider.OpenAI;
+import io.micronaut.runtime.Micronaut;
 import jakarta.inject.Inject;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
@@ -17,10 +25,14 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @KestraTest
 class ChatCompletionTest extends ContainerTest {
     private final String GEMINI_APIKEY = System.getenv("GEMINI_APIKEY");
+    private final String ANTHROPIC_APIKEY = System.getenv("ANTHROPIC_API_KEY");
+    private final String MISTRAL_APIKEY = System.getenv("MISTRAL_API_KEY");
+    private final String DEEPSEEK_APIKEY = System.getenv("DEEPSEEK_API_KEY");
 
     @Inject
     private RunContextFactory runContextFactory;
@@ -204,4 +216,303 @@ void testChatCompletionOllama() throws Exception {
         assertThat(secondOutput.getAiResponse(), containsString("John"));
         assertThat(secondOutput.getOutputMessages().size(), is(2));
     }
+
+
+    @EnabledIfEnvironmentVariable(named = "ANTHROPIC_APIKEY", matches = ".*")
+    @Test
+    void testChatCompletionAnthropicAI() throws Exception {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "modelName", AnthropicChatModelName.CLAUDE_3_HAIKU_20240307,
+            "apiKey", ANTHROPIC_APIKEY,
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(AnthropicAI.builder()
+                .type(AnthropicAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .build()
+            )
+            .build();
+
+        ChatCompletion.Output output = task.run(runContext);
+
+        assertThat(output.getAiResponse(), notNullValue());
+        List<ChatCompletion.ChatMessage> updatedMessages = output.getOutputMessages();
+
+        // GIVEN: Second prompt using the updated messages
+        updatedMessages.add(ChatCompletion.ChatMessage.builder()
+            .type(ChatCompletion.ChatMessageType.USER)
+            .content("What's my name?")
+            .build());
+
+        runContext = runContextFactory.of(Map.of(
+            "modelName", AnthropicChatModelName.CLAUDE_3_HAIKU_20240307,
+            "apiKey", ANTHROPIC_APIKEY,
+            "messages", updatedMessages
+        ));
+
+        ChatCompletion secondTask = ChatCompletion.builder()
+            .provider(Ollama.builder()
+                .type(AnthropicAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .build()
+            )
+            .messages(new Property<>("{{ messages }}"))
+            .build();
+
+        // WHEN: Run the second task
+        ChatCompletion.Output secondOutput = secondTask.run(runContext);
+
+        // THEN: Validate the second response
+        assertThat(secondOutput.getAiResponse(), containsString("John"));
+        assertThat(secondOutput.getOutputMessages().size(), is(2));
+    }
+
+    @Test
+    void testChatCompletionAnthropicAI_givenInvalidApiKey_shouldThrow4xxUnAuthorizedException() {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "modelName", AnthropicChatModelName.CLAUDE_3_HAIKU_20240307,
+            "apiKey", "ANTHROPIC_APIKEY",
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(AnthropicAI.builder()
+                .type(AnthropicAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .build()
+            )
+            .build();
+
+        // Assert RuntimeException and error message
+        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+            ChatCompletion.Output output = task.run(runContext);
+        }, "status code: 401");
+
+        // Verify error message contains 404 details
+        assertThat(exception.getMessage(), containsString("authentication_error"));
+    }
+
+
+    @EnabledIfEnvironmentVariable(named = "MISTRAL_APIKEY", matches = ".*")
+    @Test
+    void testChatCompletionMistralAI() throws Exception {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "modelName", "mistral:7b",
+            "apiKey", "MISTRAL_APIKEY",
+             "baseUrl", "https://api.mistral.ai/v1",
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(MistralAI.builder()
+                .type(MistralAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .build();
+
+        ChatCompletion.Output output = task.run(runContext);
+
+        assertThat(output.getAiResponse(), notNullValue());
+        List<ChatCompletion.ChatMessage> updatedMessages = output.getOutputMessages();
+
+        // GIVEN: Second prompt using the updated messages
+        updatedMessages.add(ChatCompletion.ChatMessage.builder()
+            .type(ChatCompletion.ChatMessageType.USER)
+            .content("What's my name?")
+            .build());
+
+        runContext = runContextFactory.of(Map.of(
+            "modelName", "mistral:7b",
+            "apiKey", MISTRAL_APIKEY,
+            "baseUrl", ollamaEndpoint,
+            "messages", updatedMessages
+        ));
+
+        ChatCompletion secondTask = ChatCompletion.builder()
+            .provider(MistralAI.builder()
+                .type(MistralAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .messages(new Property<>("{{ messages }}"))
+            .build();
+
+        // WHEN: Run the second task
+        ChatCompletion.Output secondOutput = secondTask.run(runContext);
+
+        // THEN: Validate the second response
+        assertThat(secondOutput.getAiResponse(), containsString("John"));
+        assertThat(secondOutput.getOutputMessages().size(), is(2));
+    }
+
+    @Test
+    void testChatCompletionMistralAI_givenInvalidApiKey_shouldThrow4xxUnAuthorizedException() {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "modelName", "mistral:7b",
+            "apiKey", "MISTRAL_APIKEY",
+            "baseUrl", "https://api.mistral.ai/v1",
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(MistralAI.builder()
+                .type(MistralAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .build();
+
+        // Assert RuntimeException and error message
+        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+            ChatCompletion.Output output = task.run(runContext);
+        }, "status code: 401");
+
+        // Verify error message contains 404 details
+        assertThat(exception.getMessage(), containsString("Unauthorized"));
+    }
+
+    @Test
+    void testChatCompletionMistralAI_givenInvalidBaseUrlMistralAI_shouldThrow4xx() {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "modelName", "mistral:7b",
+            "apiKey", "MISTRAL_APIKEY",
+            "baseUrl", ollamaEndpoint,
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(MistralAI.builder()
+                .type(MistralAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .build();
+
+        // Assert RuntimeException and error message
+        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+            ChatCompletion.Output output = task.run(runContext);
+        }, "status code: 404; body: 404 page not found");
+
+        // Verify error message contains 404 details
+        assertThat(exception.getMessage(), containsString("404"));
+    }
+
+    @Test
+    @EnabledIfEnvironmentVariable(named = "DEEPSEEK_APIKEY", matches = ".*")
+    void testChatCompletionDeepseek() throws Exception {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "apiKey", DEEPSEEK_APIKEY,
+            "modelName", "deepseek-chat",
+            "baseUrl", "https://api.deepseek.com/v1",
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(DeepseekAI.builder()
+                .type(DeepseekAI.class.getName())
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .modelName(new Property<>("{{ modelName }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .build();
+
+        ChatCompletion.Output output = task.run(runContext);
+
+        assertThat(output.getAiResponse(), notNullValue());
+        List<ChatCompletion.ChatMessage> updatedMessages = output.getOutputMessages();
+
+        // GIVEN: Second prompt using the updated messages
+        updatedMessages.add(ChatCompletion.ChatMessage.builder()
+            .type(ChatCompletion.ChatMessageType.USER)
+            .content("What's my name?")
+            .build());
+
+        runContext = runContextFactory.of(Map.of(
+            "apiKey", DEEPSEEK_APIKEY,
+            "modelName", "deepseek-chat",
+            "baseUrl", "https://api.deepseek.com/v1",
+            "messages", updatedMessages
+        ));
+
+        ChatCompletion secondTask = ChatCompletion.builder()
+            .provider(DeepseekAI.builder()
+                .type(DeepseekAI.class.getName())
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .modelName(new Property<>("{{ modelName }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .messages(new Property<>("{{ messages }}"))
+            .build();
+
+        // WHEN: Run the second task
+        ChatCompletion.Output secondOutput = secondTask.run(runContext);
+
+        // THEN: Validate the second response
+        assertThat(secondOutput.getAiResponse(), containsString("John"));
+        assertThat(secondOutput.getOutputMessages().size(), is(2));
+    }
+
+    @Test
+    void testChatCompletionDeepseek_givenInvalidApiKey_shouldThrow4xxUnAuthorizedException() {
+        RunContext runContext = runContextFactory.of(Map.of(
+            "apiKey", "DEEPSEEK_APIKEY",
+            "modelName", "deepseek-chat",
+            "baseUrl", "https://api.deepseek.com/v1",
+            "messages", List.of(
+                ChatCompletion.ChatMessage.builder().type(ChatCompletion.ChatMessageType.USER).content("Hello, my name is John").build()
+            )
+        ));
+
+        ChatCompletion task = ChatCompletion.builder()
+            .messages(new Property<>("{{ messages }}"))
+            .provider(DeepseekAI.builder()
+                .type(DeepseekAI.class.getName())
+                .modelName(new Property<>("{{ modelName }}"))
+                .apiKey(new Property<>("{{ apiKey }}"))
+                .baseUrl(new Property<>("{{ baseUrl }}"))
+                .build()
+            )
+            .build();
+
+        // Assert RuntimeException and error message
+        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+            ChatCompletion.Output output = task.run(runContext);
+        }, "status code: 401");
+
+        // Verify error message contains 404 details
+        assertThat(exception.getMessage(), containsString("Authentication Fails, Your api key: ****IKEY is invalid"));
+    }
 }


Reviews:
user: login: loicmathieu
body: Hi, thanks a lot for your contribution.

Just for us to know, did you test them or not? No problem if you didn't test them, we know that having API Key for all AI provider is not easy ;)

Ideally, you should also add example inside the tasks but I'll ask our tech writer to take care of it later.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T16:21:50Z
user: login: rkampani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T16:28:22Z
user: login: loicmathieu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T16:35:30Z
user: login: rkampani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T16:57:15Z
user: login: loicmathieu
body: LGTM thanks a lot for your contribution.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T07:29:29Z

Review comments:
diff_hunk: @@ -0,0 +1,59 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Anthropic AI Model Provider"
+)
+public class AnthropicAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return AnthropicChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topK(runContext.render(configuration.getTopK()).as(Integer.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .logRequests(true)
+            .logResponses(true)
+            .cacheSystemMessages(true)
path: src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java
user: login: loicmathieu
body: I don't think we enable those properties in any other ChatModel, is there a reason why you did that?
For logRequest/logResponse, we usually avoid too much logging so better to turn them off (should be the default).
created_at: 2025-05-15T16:13:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,69 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Deepseek Model Provider"
+)
+public class DeepseekAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
path: src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
user: login: loicmathieu
body: Is it really needed? Is there multiple possible baseUrl for DeepSeek?
At least, set a default to the official DeepSeek API base URL
created_at: 2025-05-15T16:14:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,69 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Deepseek Model Provider"
+)
+public class DeepseekAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
path: src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
user: login: loicmathieu
body: as here it's not mandatory and the default would be to the official OpenAI API URL as it uses the OpenAiChatModel under the cover
created_at: 2025-05-15T16:16:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,69 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Deepseek Model Provider"
+)
+public class DeepseekAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return OpenAiChatModel.builder()
path: src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
user: login: loicmathieu
body: Can you add, as in the OpenAI provider a check that users didn't set topK as it's not supported by OpenAI?

```
if (configuration.getTopK() != null) {
            throw new IllegalArgumentException("OpenAI models didn't support topK");
        }
```
created_at: 2025-05-15T16:17:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,66 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.mistralai.MistralAiChatModel;
+import dev.langchain4j.model.mistralai.MistralAiEmbeddingModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Mistral AI Model Provider"
+)
+public class MistralAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return MistralAiChatModel.builder()
path: src/main/java/io/kestra/plugin/langchain4j/provider/MistralAI.java
user: login: loicmathieu
body: If topK is not supported, can you add a check as for OpenAI and DeepSeek?
created_at: 2025-05-15T16:18:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Anthropic AI Model Provider"
+)
+public class AnthropicAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return AnthropicChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topK(runContext.render(configuration.getTopK()).as(Integer.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .logRequests(true)
+            .logResponses(true)
+            .cacheSystemMessages(true)
path: src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java
user: login: rkampani
body: I had enabled logging for my testing while wiring up the test cases — sorry, I completely forgot to remove it. I also added caching, since even Anthropic recommends it to help reprocess requests more efficiently.-so i added, will definitely remove it since we're not doing in any other models
https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching 
created_at: 2025-05-15T16:28:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Anthropic AI Model Provider"
+)
+public class AnthropicAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Override
+    public ChatLanguageModel chatLanguageModel(RunContext runContext, ChatConfiguration configuration) throws IllegalVariableEvaluationException {
+        return AnthropicChatModel.builder()
+            .modelName(runContext.render(this.getModelName()).as(String.class).orElseThrow())
+            .apiKey(runContext.render(this.apiKey).as(String.class).orElseThrow())
+            .temperature(runContext.render(configuration.getTemperature()).as(Double.class).orElse(null))
+            .topK(runContext.render(configuration.getTopK()).as(Integer.class).orElse(null))
+            .topP(runContext.render(configuration.getTopP()).as(Double.class).orElse(null))
+            .logRequests(true)
+            .logResponses(true)
+            .cacheSystemMessages(true)
path: src/main/java/io/kestra/plugin/langchain4j/provider/AnthropicAI.java
user: login: loicmathieu
body: Anyway, as we create a new model for each task execution I'm not sure caching will work.
created_at: 2025-05-15T16:35:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,69 @@
+package io.kestra.plugin.langchain4j.provider;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.image.ImageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiImageModel;
+import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.property.Property;
+import io.kestra.core.runners.RunContext;
+import io.kestra.plugin.langchain4j.domain.ChatConfiguration;
+import io.kestra.plugin.langchain4j.domain.ModelProvider;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Plugin(beta = true)
+@JsonDeserialize
+@Schema(
+    title = "Deepseek Model Provider"
+)
+public class DeepseekAI extends ModelProvider {
+    @Schema(title = "API Key")
+    @NotNull
+    private Property<String> apiKey;
+
+    @Schema(title = "API base URL")
+    private Property<String> baseUrl;
path: src/main/java/io/kestra/plugin/langchain4j/provider/DeepseekAI.java
user: login: rkampani
body: i believe OpenAI SDK defaults to OpenAI’s API URL (https://api.openai.com/v1) if no base_url is specified. This is because the SDK is primarily built for OpenAI’s services. It explicitly recommends setting the base_url to https://api.deepseek.com or https://api.deepseek.com/v1 when using the OpenAI SDK. [deepseek-documentation](https://api-docs.deepseek.com/). and 

Here is code L4j snippet

- [OpnAiChatModel](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-open-ai/src/main/java/dev/langchain4j/model/openai/OpenAiChatModel.java#L61) 
- [Default Urll](https://github.com/langchain4j/langchain4j/blob/04ef681053a38ee046f2ffb52d490d3acf7aed94/langchain4j-open-ai/src/main/java/dev/langchain4j/model/openai/internal/OpenAiUtils.java#L77)
created_at: 2025-05-15T16:57:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: rkampani
created_at: 2025-05-15T16:32:30Z
body: > Hi, thanks a lot for your contribution.
> 
> Just for us to know, did you test them or not? No problem if you didn't test them, we know that having API Key for all AI provider is not easy ;)

[RK] I don’t have all the API keys needed to validate with every provider, so it would be great if someone from the team could help with that 

> Ideally, you should also add example inside the tasks but I'll ask our tech writer to take care of it later.

[RK] Oh, thank you! I’m new to this repo, so I’m not yet familiar with everything that’s needed to complete the full workflow. Really appreciate it!
author_association: CONTRIBUTOR
user: login: loicmathieu
created_at: 2025-05-15T16:37:38Z
body: @rkampani you can find here and example of a task inline documentation example: https://github.com/kestra-io/plugin-langchain4j/blob/master/src/main/java/io/kestra/plugin/langchain4j/ChatCompletion.java#L34

This will be automatically added to the documentation on both our website and the Kestra application UI.
But as I said, I can ask someone from our team to write those, if you're not familiar with Kestra it can be complex for you.

Your contribution is already of great value for us ;)
author_association: MEMBER
user: login: rkampani
created_at: 2025-05-15T17:40:32Z
body: Thank you for sharing the inline doc example. Most of the changes have been completed as suggested. Please re-review and let me know your thoughts on the baseUrl — DeepSeekAI ..I believe it recommended overwriting the base URL.
author_association: CONTRIBUTOR