Skip to content
Merged
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
1 change: 1 addition & 0 deletions server/ee/apps/runtime-job-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
implementation(project(":server:libs:modules:components:ai:llm:nvidia"))
implementation(project(":server:libs:modules:components:ai:llm:ollama"))
implementation(project(":server:libs:modules:components:ai:llm:open-ai"))
implementation(project(":server:libs:modules:components:ai:llm:open-router"))
implementation(project(":server:libs:modules:components:ai:llm:perplexity"))
implementation(project(":server:libs:modules:components:ai:llm:stability"))
implementation(project(":server:libs:modules:components:ai:llm:vertex:gemini"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version="1.0"

dependencies {
implementation("org.springframework.ai:spring-ai-openai")
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,33 @@
* limitations under the License.
*/

package com.bytechef.component.ai.llm.hugging.face;
package com.bytechef.component.ai.llm.open.router;

import static com.bytechef.component.definition.ComponentDsl.component;

import com.bytechef.component.ComponentHandler;
import com.bytechef.component.ai.llm.hugging.face.action.HuggingFaceChatAction;
import com.bytechef.component.ai.llm.hugging.face.cluster.HuggingFaceChatModel;
import com.bytechef.component.ai.llm.hugging.face.connection.HuggingFaceConnection;
import com.bytechef.component.ai.llm.open.router.action.OpenRouterChatAction;
import com.bytechef.component.ai.llm.open.router.cluster.OpenRouterChatModel;
import com.bytechef.component.ai.llm.open.router.connection.OpenRouterConnection;
import com.bytechef.component.definition.ComponentCategory;
import com.bytechef.component.definition.ComponentDefinition;
import com.google.auto.service.AutoService;

/**
* @author Monika Domiter
* @author Marko Kriskovic
*/
@AutoService(ComponentHandler.class)
public class HuggingFaceComponentHandler implements ComponentHandler {
public class OpenRouterComponentHandler implements ComponentHandler {

private static final ComponentDefinition COMPONENT_DEFINITION = component("huggingFace")
.title("Hugging Face")
private static final ComponentDefinition COMPONENT_DEFINITION = component("openRouter")
.title("Open Router")
.description(
"Hugging Face is on a journey to advance and democratize artificial intelligence through open source " +
"and open science.")
.icon("path:assets/hugging-face.svg")
"OpenRouter provides a unified API that gives you access to hundreds of AI models through a single endpoint, while automatically handling fallbacks and selecting the most cost-effective options.")
.icon("path:assets/open-router.svg")
.categories(ComponentCategory.ARTIFICIAL_INTELLIGENCE)
.connection(HuggingFaceConnection.CONNECTION_DEFINITION)
.actions(HuggingFaceChatAction.ACTION_DEFINITION)
.clusterElements(HuggingFaceChatModel.CLUSTER_ELEMENT_DEFINITION);
.connection(OpenRouterConnection.CONNECTION_DEFINITION)
.actions(OpenRouterChatAction.ACTION_DEFINITION)
.clusterElements(OpenRouterChatModel.CLUSTER_ELEMENT_DEFINITION);

@Override
public ComponentDefinition getDefinition() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.ai.llm.open.router.action;

import static com.bytechef.component.ai.llm.ChatModel.ResponseFormat.TEXT;
import static com.bytechef.component.ai.llm.constant.LLMConstants.ASK;
import static com.bytechef.component.ai.llm.constant.LLMConstants.ATTACHMENTS_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.FORMAT_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.FREQUENCY_PENALTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.FREQUENCY_PENALTY_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.LOGIT_BIAS;
import static com.bytechef.component.ai.llm.constant.LLMConstants.LOGIT_BIAS_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.MAX_TOKENS;
import static com.bytechef.component.ai.llm.constant.LLMConstants.MAX_TOKENS_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.MESSAGES_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.MODEL;
import static com.bytechef.component.ai.llm.constant.LLMConstants.PRESENCE_PENALTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.PRESENCE_PENALTY_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.PROMPT_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.REASONING;
import static com.bytechef.component.ai.llm.constant.LLMConstants.REASONING_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.RESPONSE;
import static com.bytechef.component.ai.llm.constant.LLMConstants.RESPONSE_FORMAT;
import static com.bytechef.component.ai.llm.constant.LLMConstants.RESPONSE_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.SEED;
import static com.bytechef.component.ai.llm.constant.LLMConstants.SEED_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.STOP;
import static com.bytechef.component.ai.llm.constant.LLMConstants.STOP_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.SYSTEM_PROMPT_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TEMPERATURE;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TEMPERATURE_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_K;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_K_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_P;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_P_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.USER;
import static com.bytechef.component.ai.llm.constant.LLMConstants.USER_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.VERBOSITY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.VERBOSITY_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.CHAT_MODEL_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.LOGPROBS;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.LOGPROBS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.MAX_COMPLETION_TOKENS;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.MAX_COMPLETION_TOKENS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.SUPPORTED_PARAMETERS;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.SUPPORTED_PARAMETERS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.TOP_LOGPROBS;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.TOP_LOGPROBS_PROPERTY;
import static com.bytechef.component.definition.Authorization.TOKEN;
import static com.bytechef.component.definition.ComponentDsl.action;

import com.bytechef.component.ai.llm.ChatModel;
import com.bytechef.component.ai.llm.open.router.model.OpenRouterChatModel;
import com.bytechef.component.ai.llm.util.ModelUtils;
import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.TypeReference;

/**
* @author Marko Kriskovic
*/
public class OpenRouterChatAction {

public static final ModifiableActionDefinition ACTION_DEFINITION = action(ASK)
.title("Ask")
.description("Ask anything you want.")
.properties(
SUPPORTED_PARAMETERS_PROPERTY,
CHAT_MODEL_PROPERTY,
PROMPT_PROPERTY,
FORMAT_PROPERTY,
SYSTEM_PROMPT_PROPERTY,
ATTACHMENTS_PROPERTY,
MESSAGES_PROPERTY,
RESPONSE_PROPERTY,
FREQUENCY_PENALTY_PROPERTY
.displayCondition("contains(%s, 'frequency_penalty')".formatted(SUPPORTED_PARAMETERS)),
LOGIT_BIAS_PROPERTY
.displayCondition("contains(%s, 'logit_bias')".formatted(SUPPORTED_PARAMETERS)),
LOGPROBS_PROPERTY
.displayCondition("contains(%s, 'logprobs')".formatted(SUPPORTED_PARAMETERS)),
MAX_COMPLETION_TOKENS_PROPERTY
.displayCondition("contains(%s, 'max_completion_tokens')".formatted(SUPPORTED_PARAMETERS)),
MAX_TOKENS_PROPERTY
.displayCondition("contains(%s, 'max_tokens')".formatted(SUPPORTED_PARAMETERS)),
PRESENCE_PENALTY_PROPERTY
.displayCondition("contains(%s, 'presence_penalty')".formatted(SUPPORTED_PARAMETERS)),
REASONING_PROPERTY
.displayCondition("contains(%s, 'reasoning')".formatted(SUPPORTED_PARAMETERS)),
SEED_PROPERTY
.displayCondition("contains(%s, 'seed')".formatted(SUPPORTED_PARAMETERS)),
STOP_PROPERTY
.displayCondition("contains(%s, 'stop')".formatted(SUPPORTED_PARAMETERS)),
TEMPERATURE_PROPERTY
.displayCondition("contains(%s, 'temperature')".formatted(SUPPORTED_PARAMETERS)),
TOP_LOGPROBS_PROPERTY
.displayCondition("contains(%s, 'top_logprobs')".formatted(SUPPORTED_PARAMETERS)),
TOP_K_PROPERTY
.displayCondition("contains(%s, 'top_k')".formatted(SUPPORTED_PARAMETERS)),
TOP_P_PROPERTY
.displayCondition("contains(%s, 'top_p')".formatted(SUPPORTED_PARAMETERS)),
VERBOSITY_PROPERTY
.displayCondition("contains(%s, 'verbosity')".formatted(SUPPORTED_PARAMETERS)),
USER_PROPERTY)
.output(ModelUtils::output)
.perform(OpenRouterChatAction::perform);

public static final ChatModel CHAT_MODEL = (inputParameters, connectionParameters, responseFormatRequired) -> {
boolean jsonFormat = false;

if (responseFormatRequired) {
ChatModel.ResponseFormat responseFormat = inputParameters.getRequiredFromPath(
RESPONSE + "." + RESPONSE_FORMAT, ChatModel.ResponseFormat.class);

jsonFormat = !responseFormat.equals(TEXT);
}

return OpenRouterChatModel.builder()
.apiKey(connectionParameters.getString(TOKEN))
.model(inputParameters.getRequiredString(MODEL))
.frequencyPenalty(inputParameters.getDouble(FREQUENCY_PENALTY))
.logitBias(inputParameters.getMap(LOGIT_BIAS, new TypeReference<>() {}))
.logprobs(inputParameters.getBoolean(LOGPROBS))
.maxCompletionTokens(inputParameters.getInteger(MAX_COMPLETION_TOKENS))
.maxTokens(inputParameters.getInteger(MAX_TOKENS))
.presencePenalty(inputParameters.getDouble(PRESENCE_PENALTY))
.reasoning(inputParameters.getString(REASONING))
.jsonResponseFormat(jsonFormat)
.seed(inputParameters.getInteger(SEED))
.stop(inputParameters.getList(STOP, new TypeReference<>() {}))
.temperature(inputParameters.getDouble(TEMPERATURE))
.topK(inputParameters.getDouble(TOP_K))
.topLogprobs(inputParameters.getInteger(TOP_LOGPROBS))
.topP(inputParameters.getDouble(TOP_P))
.verbosity(inputParameters.getString(VERBOSITY))
.user(inputParameters.getString(USER))
.build();
};

private OpenRouterChatAction() {
}

public static Object perform(Parameters inputParameters, Parameters connectionParameters, ActionContext context) {
return CHAT_MODEL.getResponse(inputParameters, connectionParameters, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.ai.llm.open.router.cluster;

import static com.bytechef.component.ai.llm.constant.LLMConstants.FREQUENCY_PENALTY_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.LOGIT_BIAS_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.MAX_TOKENS_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.PRESENCE_PENALTY_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.REASONING_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.SEED_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.STOP_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TEMPERATURE_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_K_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.TOP_P_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.USER_PROPERTY;
import static com.bytechef.component.ai.llm.constant.LLMConstants.VERBOSITY_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.action.OpenRouterChatAction.CHAT_MODEL;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.CHAT_MODEL_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.LOGPROBS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.MAX_COMPLETION_TOKENS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.SUPPORTED_PARAMETERS;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.SUPPORTED_PARAMETERS_PROPERTY;
import static com.bytechef.component.ai.llm.open.router.constant.OpenRouterConstants.TOP_LOGPROBS_PROPERTY;

import com.bytechef.component.definition.ClusterElementDefinition;
import com.bytechef.component.definition.ComponentDsl;
import com.bytechef.component.definition.Parameters;
import com.bytechef.platform.component.definition.ai.agent.ModelFunction;
import org.springframework.ai.chat.model.ChatModel;

/**
* @author Marko Kriskovic
*/
public class OpenRouterChatModel {

public static final ClusterElementDefinition<ModelFunction> CLUSTER_ELEMENT_DEFINITION =
ComponentDsl.<ModelFunction>clusterElement("model")
.title("Open Router Model")
.description("Open Router model.")
.type(ModelFunction.MODEL)
.object(() -> OpenRouterChatModel::apply)
.properties(
SUPPORTED_PARAMETERS_PROPERTY,
CHAT_MODEL_PROPERTY,
FREQUENCY_PENALTY_PROPERTY
.displayCondition("contains(%s, 'frequency_penalty')".formatted(SUPPORTED_PARAMETERS)),
LOGIT_BIAS_PROPERTY
.displayCondition("contains(%s, 'logit_bias')".formatted(SUPPORTED_PARAMETERS)),
LOGPROBS_PROPERTY
.displayCondition("contains(%s, 'logprobs')".formatted(SUPPORTED_PARAMETERS)),
MAX_COMPLETION_TOKENS_PROPERTY
.displayCondition("contains(%s, 'max_completion_tokens')".formatted(SUPPORTED_PARAMETERS)),
MAX_TOKENS_PROPERTY
.displayCondition("contains(%s, 'max_tokens')".formatted(SUPPORTED_PARAMETERS)),
PRESENCE_PENALTY_PROPERTY
.displayCondition("contains(%s, 'presence_penalty')".formatted(SUPPORTED_PARAMETERS)),
REASONING_PROPERTY
.displayCondition("contains(%s, 'reasoning')".formatted(SUPPORTED_PARAMETERS)),
SEED_PROPERTY
.displayCondition("contains(%s, 'seed')".formatted(SUPPORTED_PARAMETERS)),
STOP_PROPERTY
.displayCondition("contains(%s, 'stop')".formatted(SUPPORTED_PARAMETERS)),
TEMPERATURE_PROPERTY
.displayCondition("contains(%s, 'temperature')".formatted(SUPPORTED_PARAMETERS)),
TOP_LOGPROBS_PROPERTY
.displayCondition("contains(%s, 'top_logprobs')".formatted(SUPPORTED_PARAMETERS)),
TOP_K_PROPERTY
.displayCondition("contains(%s, 'top_k')".formatted(SUPPORTED_PARAMETERS)),
TOP_P_PROPERTY
.displayCondition("contains(%s, 'top_p')".formatted(SUPPORTED_PARAMETERS)),
VERBOSITY_PROPERTY
.displayCondition("contains(%s, 'verbosity')".formatted(SUPPORTED_PARAMETERS)),
USER_PROPERTY);

protected static ChatModel apply(
Parameters inputParameters, Parameters connectionParameters, boolean responseFormatRequired) {

return CHAT_MODEL.createChatModel(inputParameters, connectionParameters, responseFormatRequired);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.bytechef.component.ai.llm.hugging.face.connection;
package com.bytechef.component.ai.llm.open.router.connection;

import static com.bytechef.component.definition.Authorization.AuthorizationType.BEARER_TOKEN;
import static com.bytechef.component.definition.Authorization.TOKEN;
Expand All @@ -25,13 +25,12 @@
import com.bytechef.component.definition.ComponentDsl.ModifiableConnectionDefinition;

/**
* @author Monika Domiter
* @author Marko Kriskovic
*/
public final class HuggingFaceConnection {
public final class OpenRouterConnection {

public static final ModifiableConnectionDefinition CONNECTION_DEFINITION = connection()
.baseUri((connectionParameters, context) -> "https://api-inference.huggingface.co")
.baseUri((connectionParameters, context) -> "https://openrouter.ai/api/v1")
.authorizations(
authorization(BEARER_TOKEN)
.title("Bearer Token")
Expand All @@ -40,6 +39,6 @@ public final class HuggingFaceConnection {
.label("Token")
.required(true)));

private HuggingFaceConnection() {
private OpenRouterConnection() {
}
}
Loading
Loading