Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions ELEMENT_TEMPLATE_GENERATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Element Template Generation Instructions

## Issue
The Mistral AI provider configuration was added to the code, but the element templates were not regenerated to include it in the UI dropdown.

## Solution

The element templates are automatically generated from Java annotations using the `element-template-generator-maven-plugin`. To regenerate them after adding a new provider:

### Step 1: Generate Element Templates

Run the following Maven command from the root of the repository:

```bash
mvn clean compile io.camunda.connector:element-template-generator-maven-plugin:generate-templates -pl connectors/agentic-ai
```

Or run a full build which will trigger the generator:

```bash
mvn clean install -DskipTests
```

### Step 2: Verify the Changes

After generation, check that the element templates in `connectors/agentic-ai/element-templates/` include Mistral AI:

1. **agenticai-aiagent-outbound-connector.json** - Should include "Mistral AI" in the provider dropdown
2. **agenticai-aiagent-job-worker.json** - Should be updated by the Groovy transform script
3. **hybrid/agenticai-aiagent-outbound-connector-hybrid.json** - Should include Mistral AI

### Step 3: Verify Provider Configuration

The generated template should include:
- Mistral AI in the provider dropdown choices
- Conditional properties for Mistral configuration:
- `provider.mistral.endpoint` (optional base URL)
- `provider.mistral.authentication.apiKey` (required API key)
- `provider.mistral.model.model` (required model name)
- `provider.mistral.model.parameters.maxTokens` (optional)
- `provider.mistral.model.parameters.temperature` (optional)
- `provider.mistral.model.parameters.topP` (optional)
- `provider.mistral.model.parameters.safePrompt` (optional)
- `provider.mistral.model.parameters.randomSeed` (optional)

### What Was Added

The following files were modified to support Mistral AI:

1. **MistralProviderConfiguration.java** - New provider configuration with `@TemplateProperty` annotations
2. **ProviderConfiguration.java** - Added Mistral to the sealed interface and `@JsonSubTypes`
3. **ChatModelFactoryImpl.java** - Added `createMistralChatModelBuilder()` method
4. **pom.xml** - Added `langchain4j-mistral-ai` dependency

All the necessary annotations are in place (`@TemplateSubType`, `@TemplateProperty`, etc.), so the plugin should automatically generate the correct element template entries.

## Why This is Needed

The element template generator reads the Java annotations on the provider configuration classes and generates JSON element templates that:
1. Add provider options to dropdowns in the Camunda Modeler
2. Define conditional properties that appear when a provider is selected
3. Set up proper data bindings for the BPMN process variables

Without regenerating the templates, users won't see "Mistral AI" as an option in the provider dropdown, even though the backend code fully supports it.
4 changes: 4 additions & 0 deletions connectors/agentic-ai/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mistral-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-vertex-ai-gemini</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import dev.langchain4j.model.bedrock.BedrockChatModel;
import dev.langchain4j.model.bedrock.BedrockChatRequestParameters;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.mistralai.MistralAiChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiChatRequestParameters;
import dev.langchain4j.model.vertexai.gemini.VertexAiGeminiChatModel;
Expand All @@ -24,6 +25,7 @@
import io.camunda.connector.agenticai.aiagent.model.request.provider.BedrockProviderConfiguration;
import io.camunda.connector.agenticai.aiagent.model.request.provider.GoogleVertexAiProviderConfiguration;
import io.camunda.connector.agenticai.aiagent.model.request.provider.GoogleVertexAiProviderConfiguration.GoogleVertexAiAuthentication.ServiceAccountCredentialsAuthentication;
import io.camunda.connector.agenticai.aiagent.model.request.provider.MistralProviderConfiguration;
import io.camunda.connector.agenticai.aiagent.model.request.provider.OpenAiCompatibleProviderConfiguration;
import io.camunda.connector.agenticai.aiagent.model.request.provider.OpenAiCompatibleProviderConfiguration.OpenAiCompatibleAuthentication;
import io.camunda.connector.agenticai.aiagent.model.request.provider.OpenAiProviderConfiguration;
Expand Down Expand Up @@ -69,6 +71,7 @@ public ChatModel createChatModel(ProviderConfiguration providerConfiguration) {
case BedrockProviderConfiguration bedrock -> createBedrockChatModelBuilder(bedrock).build();
case GoogleVertexAiProviderConfiguration vertexAi ->
createGoogleVertexAiChatModelBuilder(vertexAi).build();
case MistralProviderConfiguration mistral -> createMistralChatModelBuilder(mistral).build();
case OpenAiProviderConfiguration openai -> createOpenaiChatModelBuilder(openai).build();
case OpenAiCompatibleProviderConfiguration openaiCompatible ->
createOpenaiCompatibleChatModelBuilder(openaiCompatible).build();
Expand Down Expand Up @@ -260,6 +263,31 @@ protected OpenAiChatModel.OpenAiChatModelBuilder createOpenaiChatModelBuilder(
return builder;
}

protected MistralAiChatModel.MistralAiChatModelBuilder createMistralChatModelBuilder(
MistralProviderConfiguration configuration) {
final var connection = configuration.mistral();

final var builder =
MistralAiChatModel.builder()
.apiKey(connection.authentication().apiKey())
.modelName(connection.model().model())
.timeout(deriveTimeoutSetting(connection.timeouts()))
.httpClientBuilder(proxySupport.createJdkHttpClientBuilder());

Optional.ofNullable(connection.endpoint()).ifPresent(builder::baseUrl);

final var modelParameters = connection.model().parameters();
if (modelParameters != null) {
Optional.ofNullable(modelParameters.maxTokens()).ifPresent(builder::maxTokens);
Optional.ofNullable(modelParameters.temperature()).ifPresent(builder::temperature);
Optional.ofNullable(modelParameters.topP()).ifPresent(builder::topP);
Optional.ofNullable(modelParameters.safePrompt()).ifPresent(builder::safePrompt);
Optional.ofNullable(modelParameters.randomSeed()).ifPresent(builder::randomSeed);
}

return builder;
}

protected OpenAiChatModel.OpenAiChatModelBuilder createOpenaiCompatibleChatModelBuilder(
OpenAiCompatibleProviderConfiguration configuration) {
final var connection = configuration.openaiCompatible();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/
package io.camunda.connector.agenticai.aiagent.model.request.provider;

import static io.camunda.connector.agenticai.aiagent.model.request.provider.MistralProviderConfiguration.MISTRAL_ID;

import io.camunda.connector.agenticai.aiagent.model.request.provider.shared.HttpUrl;
import io.camunda.connector.agenticai.aiagent.model.request.provider.shared.TimeoutConfiguration;
import io.camunda.connector.generator.java.annotation.FeelMode;
import io.camunda.connector.generator.java.annotation.TemplateProperty;
import io.camunda.connector.generator.java.annotation.TemplateSubType;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

@TemplateSubType(id = MISTRAL_ID, label = "Mistral AI")
public record MistralProviderConfiguration(@Valid @NotNull MistralConnection mistral)
implements ProviderConfiguration {

@TemplateProperty(ignore = true)
public static final String MISTRAL_ID = "mistral";

public record MistralConnection(
@HttpUrl
@TemplateProperty(
group = "provider",
label = "Base URL",
description =
"The Mistral API base URL. Default: https://api.mistral.ai/v1",
type = TemplateProperty.PropertyType.String,
feel = FeelMode.optional,
defaultValue = "https://api.mistral.ai/v1",
defaultValueType = TemplateProperty.DefaultValueType.String,
optional = true)
String endpoint,
@Valid @NotNull MistralAuthentication authentication,
@Valid TimeoutConfiguration timeouts,
@Valid @NotNull MistralModel model) {}

public record MistralAuthentication(
@NotBlank
@TemplateProperty(
group = "provider",
label = "Mistral API key",
type = TemplateProperty.PropertyType.String,
feel = FeelMode.optional,
constraints = @TemplateProperty.PropertyConstraints(notEmpty = true))
String apiKey) {

@Override
public String toString() {
return "MistralAuthentication{apiKey=[REDACTED]}";
}
}

public record MistralModel(
@NotBlank
@TemplateProperty(
group = "model",
label = "Model",
description =
"Specify the model ID. Popular models: mistral-large-latest, mistral-small-latest, mistral-medium-latest, codestral-latest, open-mistral-nemo, pixtral-large-latest. Details in the <a href=\"https://docs.mistral.ai/getting-started/models/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.String,
feel = FeelMode.optional,
defaultValue = "mistral-large-latest",
defaultValueType = TemplateProperty.DefaultValueType.String,
constraints = @TemplateProperty.PropertyConstraints(notEmpty = true))
String model,
@Valid MistralModel.MistralModelParameters parameters) {

public record MistralModelParameters(
@Min(0)
@TemplateProperty(
group = "model",
label = "Maximum tokens",
tooltip =
"The maximum number of tokens to generate in the completion. <br><br>Details in the <a href=\"https://docs.mistral.ai/api/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.Number,
feel = FeelMode.required,
optional = true)
Integer maxTokens,
@Min(0)
@TemplateProperty(
group = "model",
label = "Temperature",
tooltip =
"What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. <br><br>Details in the <a href=\"https://docs.mistral.ai/api/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.Number,
feel = FeelMode.required,
optional = true)
Double temperature,
@Min(0)
@TemplateProperty(
group = "model",
label = "top P",
tooltip =
"Nucleus sampling probability. Recommended for advanced use cases only (you usually only need to use temperature). <br><br>Details in the <a href=\"https://docs.mistral.ai/api/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.Number,
feel = FeelMode.required,
optional = true)
Double topP,
@TemplateProperty(
group = "model",
label = "Safe prompt",
tooltip =
"Whether to inject a safety prompt before all conversations. <br><br>Details in the <a href=\"https://docs.mistral.ai/capabilities/guardrailing/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.Boolean,
feel = FeelMode.optional,
optional = true)
Boolean safePrompt,
@Min(0)
@TemplateProperty(
group = "model",
label = "Random seed",
tooltip =
"The seed to use for random sampling. If set, different calls will generate deterministic results. <br><br>Details in the <a href=\"https://docs.mistral.ai/api/\" target=\"_blank\">documentation</a>.",
type = TemplateProperty.PropertyType.Number,
feel = FeelMode.required,
optional = true)
Integer randomSeed) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static io.camunda.connector.agenticai.aiagent.model.request.provider.AzureOpenAiProviderConfiguration.AZURE_OPENAI_ID;
import static io.camunda.connector.agenticai.aiagent.model.request.provider.BedrockProviderConfiguration.BEDROCK_ID;
import static io.camunda.connector.agenticai.aiagent.model.request.provider.GoogleVertexAiProviderConfiguration.GOOGLE_VERTEX_AI_ID;
import static io.camunda.connector.agenticai.aiagent.model.request.provider.MistralProviderConfiguration.MISTRAL_ID;
import static io.camunda.connector.agenticai.aiagent.model.request.provider.OpenAiCompatibleProviderConfiguration.OPENAI_COMPATIBLE_ID;
import static io.camunda.connector.agenticai.aiagent.model.request.provider.OpenAiProviderConfiguration.OPENAI_ID;

Expand All @@ -23,6 +24,7 @@
@JsonSubTypes.Type(value = BedrockProviderConfiguration.class, name = BEDROCK_ID),
@JsonSubTypes.Type(value = AzureOpenAiProviderConfiguration.class, name = AZURE_OPENAI_ID),
@JsonSubTypes.Type(value = GoogleVertexAiProviderConfiguration.class, name = GOOGLE_VERTEX_AI_ID),
@JsonSubTypes.Type(value = MistralProviderConfiguration.class, name = MISTRAL_ID),
@JsonSubTypes.Type(value = OpenAiProviderConfiguration.class, name = OPENAI_ID),
@JsonSubTypes.Type(
value = OpenAiCompatibleProviderConfiguration.class,
Expand All @@ -39,5 +41,6 @@ public sealed interface ProviderConfiguration
BedrockProviderConfiguration,
AzureOpenAiProviderConfiguration,
GoogleVertexAiProviderConfiguration,
MistralProviderConfiguration,
OpenAiProviderConfiguration,
OpenAiCompatibleProviderConfiguration {}
Loading
Loading