This is an automated email from the ASF dual-hosted git repository.
tsato pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 6c76d151b99 CAMEL-21508: Create a KServe component
6c76d151b99 is described below
commit 6c76d151b99f85818733c3f681834f623a95c5bd
Author: Tadayoshi Sato <[email protected]>
AuthorDate: Thu Jan 16 18:06:38 2025 +0900
CAMEL-21508: Create a KServe component
---
bom/camel-bom/pom.xml | 5 +
catalog/camel-allcomponents/pom.xml | 5 +
components/camel-ai/camel-kserve/pom.xml | 168 ++++++++++
.../kserve/KServeComponentConfigurer.java | 97 ++++++
.../kserve/KServeConfigurationConfigurer.java | 63 ++++
.../component/kserve/KServeConverterLoader.java | 64 ++++
.../component/kserve/KServeEndpointConfigurer.java | 69 +++++
.../component/kserve/KServeEndpointUriFactory.java | 75 +++++
.../org/apache/camel/component/kserve/kserve.json | 49 +++
.../services/org/apache/camel/TypeConverterLoader | 2 +
.../services/org/apache/camel/component.properties | 7 +
.../services/org/apache/camel/component/kserve | 2 +
.../org/apache/camel/configurer/kserve-component | 2 +
.../org/apache/camel/configurer/kserve-endpoint | 2 +
...ache.camel.component.kserve.KServeConfiguration | 2 +
.../org/apache/camel/urifactory/kserve-endpoint | 2 +
.../src/main/docs/kserve-component.adoc | 243 +++++++++++++++
.../camel/component/kserve/KServeComponent.java | 59 ++++
.../component/kserve/KServeConfiguration.java | 93 ++++++
.../camel/component/kserve/KServeConstants.java | 32 ++
.../camel/component/kserve/KServeConverter.java | 63 ++++
.../camel/component/kserve/KServeEndpoint.java | 105 +++++++
.../camel/component/kserve/KServeProducer.java | 129 ++++++++
.../src/main/proto/grpc_predict_v2.proto | 342 +++++++++++++++++++++
.../component/kserve/it/KServeEndpointIT.java | 312 +++++++++++++++++++
.../camel/component/kserve/it/KServeITSupport.java | 39 +++
.../src/test/resources/log4j2.properties | 28 ++
components/camel-ai/pom.xml | 1 +
.../modules/ROOT/examples/json/kserve.json | 1 +
docs/components/modules/ROOT/nav.adoc | 1 +
.../modules/ROOT/pages/kserve-component.adoc | 1 +
parent/pom.xml | 5 +
pom.xml | 2 +
test-infra/camel-test-infra-all/pom.xml | 7 +-
.../camel-test-infra-triton}/pom.xml | 55 ++--
.../test/infra/triton/common/TritonProperties.java | 27 ++
.../infra/triton/services/TritonInfraService.java | 28 ++
.../services/TritonLocalContainerInfraService.java | 99 ++++++
.../triton/services/TritonRemoteInfraService.java | 55 ++++
.../src/main/resources/META-INF/MANIFEST.MF | 0
.../main/resources/models/simple/1/model.graphdef | 21 ++
.../src/main/resources/models/simple/config.pbtxt | 27 ++
.../infra/triton/services/container.properties | 17 +
.../test/infra/triton/services/TritonService.java | 26 ++
.../triton/services/TritonServiceFactory.java | 42 +++
test-infra/pom.xml | 1 +
46 files changed, 2446 insertions(+), 29 deletions(-)
diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index 92fd81e4287..51d41c5314f 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -1287,6 +1287,11 @@
<artifactId>camel-knative-http</artifactId>
<version>4.10.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-kserve</artifactId>
+ <version>4.10.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-kubernetes</artifactId>
diff --git a/catalog/camel-allcomponents/pom.xml
b/catalog/camel-allcomponents/pom.xml
index 3d614f91871..cdc3f1a434c 100644
--- a/catalog/camel-allcomponents/pom.xml
+++ b/catalog/camel-allcomponents/pom.xml
@@ -1116,6 +1116,11 @@
<artifactId>camel-knative-http</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-kserve</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-kubernetes</artifactId>
diff --git a/components/camel-ai/camel-kserve/pom.xml
b/components/camel-ai/camel-kserve/pom.xml
new file mode 100644
index 00000000000..9733c21a54c
--- /dev/null
+++ b/components/camel-ai/camel-kserve/pom.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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
+
+ http://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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>camel-ai-parent</artifactId>
+ <groupId>org.apache.camel</groupId>
+ <version>4.10.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-kserve</artifactId>
+ <packaging>jar</packaging>
+ <name>Camel :: AI :: KServe</name>
+ <description>Provide access to AI model servers with the KServe standard
to run inference with remote models
+ </description>
+
+ <properties>
+ <!-- Triton Inference Server container is not available on these
platforms -->
+ <skipITs.ppc64le>true</skipITs.ppc64le>
+ <skipITs.s390x>true</skipITs.s390x>
+ </properties>
+
+ <dependencies>
+ <!-- camel -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-support</artifactId>
+ </dependency>
+
+ <!-- Dependencies required by the generated client -->
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>${protobuf-version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-netty-shaded</artifactId>
+ <version>${grpc-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-protobuf</artifactId>
+ <version>${grpc-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-stub</artifactId>
+ <version>${grpc-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
+ <version>${jakarta-annotation-api-version}</version>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-junit5</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java-util</artifactId>
+ <version>${protobuf-version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- test infra -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-triton</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>io.github.ascopes</groupId>
+ <artifactId>protobuf-maven-plugin</artifactId>
+ <version>${protobuf-maven-plugin-version}</version>
+ <configuration>
+ <protocVersion>${protobuf-version}</protocVersion>
+ <sourceDirectories>
+ <sourceDirectory>src/main/proto</sourceDirectory>
+ </sourceDirectories>
+ <binaryMavenPlugins>
+ <binaryMavenPlugin>
+ <groupId>io.grpc</groupId>
+ <artifactId>protoc-gen-grpc-java</artifactId>
+ <version>${grpc-version}</version>
+ </binaryMavenPlugin>
+ </binaryMavenPlugins>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+
<source>target/generated-sources/protobuf</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!-- Remove it once
https://github.com/grpc/grpc-java/issues/9179 is resolved -->
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>process-sources</phase>
+ <configuration>
+ <target>
+ <replace token="@javax.annotation.Generated"
+ value="@jakarta.annotation.Generated"
+ dir="target/generated-sources/protobuf">
+ <include name="**/*.java" />
+ </replace>
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git
a/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeComponentConfigurer.java
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeComponentConfigurer.java
new file mode 100644
index 00000000000..032b117ee96
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeComponentConfigurer.java
@@ -0,0 +1,97 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.kserve;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.support.component.PropertyConfigurerSupport;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo")
+@SuppressWarnings("unchecked")
+public class KServeComponentConfigurer extends PropertyConfigurerSupport
implements GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+ private org.apache.camel.component.kserve.KServeConfiguration
getOrCreateConfiguration(KServeComponent target) {
+ if (target.getConfiguration() == null) {
+ target.setConfiguration(new
org.apache.camel.component.kserve.KServeConfiguration());
+ }
+ return target.getConfiguration();
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
+ KServeComponent target = (KServeComponent) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "autowiredenabled":
+ case "autowiredEnabled":
target.setAutowiredEnabled(property(camelContext, boolean.class, value));
return true;
+ case "configuration": target.setConfiguration(property(camelContext,
org.apache.camel.component.kserve.KServeConfiguration.class, value)); return
true;
+ case "credentials":
getOrCreateConfiguration(target).setCredentials(property(camelContext,
io.grpc.ChannelCredentials.class, value)); return true;
+ case "healthcheckconsumerenabled":
+ case "healthCheckConsumerEnabled":
target.setHealthCheckConsumerEnabled(property(camelContext, boolean.class,
value)); return true;
+ case "healthcheckproducerenabled":
+ case "healthCheckProducerEnabled":
target.setHealthCheckProducerEnabled(property(camelContext, boolean.class,
value)); return true;
+ case "lazystartproducer":
+ case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
+ case "modelname":
+ case "modelName":
getOrCreateConfiguration(target).setModelName(property(camelContext,
java.lang.String.class, value)); return true;
+ case "modelversion":
+ case "modelVersion":
getOrCreateConfiguration(target).setModelVersion(property(camelContext,
java.lang.String.class, value)); return true;
+ case "target":
getOrCreateConfiguration(target).setTarget(property(camelContext,
java.lang.String.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "autowiredenabled":
+ case "autowiredEnabled": return boolean.class;
+ case "configuration": return
org.apache.camel.component.kserve.KServeConfiguration.class;
+ case "credentials": return io.grpc.ChannelCredentials.class;
+ case "healthcheckconsumerenabled":
+ case "healthCheckConsumerEnabled": return boolean.class;
+ case "healthcheckproducerenabled":
+ case "healthCheckProducerEnabled": return boolean.class;
+ case "lazystartproducer":
+ case "lazyStartProducer": return boolean.class;
+ case "modelname":
+ case "modelName": return java.lang.String.class;
+ case "modelversion":
+ case "modelVersion": return java.lang.String.class;
+ case "target": return java.lang.String.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ KServeComponent target = (KServeComponent) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "autowiredenabled":
+ case "autowiredEnabled": return target.isAutowiredEnabled();
+ case "configuration": return target.getConfiguration();
+ case "credentials": return
getOrCreateConfiguration(target).getCredentials();
+ case "healthcheckconsumerenabled":
+ case "healthCheckConsumerEnabled": return
target.isHealthCheckConsumerEnabled();
+ case "healthcheckproducerenabled":
+ case "healthCheckProducerEnabled": return
target.isHealthCheckProducerEnabled();
+ case "lazystartproducer":
+ case "lazyStartProducer": return target.isLazyStartProducer();
+ case "modelname":
+ case "modelName": return
getOrCreateConfiguration(target).getModelName();
+ case "modelversion":
+ case "modelVersion": return
getOrCreateConfiguration(target).getModelVersion();
+ case "target": return getOrCreateConfiguration(target).getTarget();
+ default: return null;
+ }
+ }
+}
+
diff --git
a/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConfigurationConfigurer.java
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConfigurationConfigurer.java
new file mode 100644
index 00000000000..1afd4f2eeb1
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConfigurationConfigurer.java
@@ -0,0 +1,63 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.kserve;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.kserve.KServeConfiguration;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class KServeConfigurationConfigurer extends
org.apache.camel.support.component.PropertyConfigurerSupport implements
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
+ org.apache.camel.component.kserve.KServeConfiguration target =
(org.apache.camel.component.kserve.KServeConfiguration) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials": target.setCredentials(property(camelContext,
io.grpc.ChannelCredentials.class, value)); return true;
+ case "modelname":
+ case "modelName": target.setModelName(property(camelContext,
java.lang.String.class, value)); return true;
+ case "modelversion":
+ case "modelVersion": target.setModelVersion(property(camelContext,
java.lang.String.class, value)); return true;
+ case "target": target.setTarget(property(camelContext,
java.lang.String.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials": return io.grpc.ChannelCredentials.class;
+ case "modelname":
+ case "modelName": return java.lang.String.class;
+ case "modelversion":
+ case "modelVersion": return java.lang.String.class;
+ case "target": return java.lang.String.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ org.apache.camel.component.kserve.KServeConfiguration target =
(org.apache.camel.component.kserve.KServeConfiguration) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials": return target.getCredentials();
+ case "modelname":
+ case "modelName": return target.getModelName();
+ case "modelversion":
+ case "modelVersion": return target.getModelVersion();
+ case "target": return target.getTarget();
+ default: return null;
+ }
+ }
+}
+
diff --git
a/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConverterLoader.java
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConverterLoader.java
new file mode 100644
index 00000000000..d5b7664e26c
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeConverterLoader.java
@@ -0,0 +1,64 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.kserve;
+
+import javax.annotation.processing.Generated;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.DeferredContextBinding;
+import org.apache.camel.Exchange;
+import org.apache.camel.TypeConversionException;
+import org.apache.camel.TypeConverterLoaderException;
+import org.apache.camel.spi.TypeConverterLoader;
+import org.apache.camel.spi.TypeConverterRegistry;
+import org.apache.camel.support.SimpleTypeConverter;
+import org.apache.camel.support.TypeConverterSupport;
+import org.apache.camel.util.DoubleMap;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.TypeConverterLoaderGeneratorMojo")
+@SuppressWarnings("unchecked")
+@DeferredContextBinding
+public final class KServeConverterLoader implements TypeConverterLoader,
CamelContextAware {
+
+ private CamelContext camelContext;
+
+ public KServeConverterLoader() {
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void load(TypeConverterRegistry registry) throws
TypeConverterLoaderException {
+ registerConverters(registry);
+ }
+
+ private void registerConverters(TypeConverterRegistry registry) {
+ addTypeConverter(registry,
inference.GrpcPredictV2.ModelInferRequest.class,
inference.GrpcPredictV2.ModelInferRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toModelInferRequest((inference.GrpcPredictV2.ModelInferRequest.Builder)
value));
+ addTypeConverter(registry,
inference.GrpcPredictV2.ModelMetadataRequest.class,
inference.GrpcPredictV2.ModelMetadataRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toModelMetadataRequest((inference.GrpcPredictV2.ModelMetadataRequest.Builder)
value));
+ addTypeConverter(registry,
inference.GrpcPredictV2.ModelReadyRequest.class,
inference.GrpcPredictV2.ModelReadyRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toModelReadyRequest((inference.GrpcPredictV2.ModelReadyRequest.Builder)
value));
+ addTypeConverter(registry,
inference.GrpcPredictV2.ServerLiveRequest.class,
inference.GrpcPredictV2.ServerLiveRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toServerLiveRequest((inference.GrpcPredictV2.ServerLiveRequest.Builder)
value));
+ addTypeConverter(registry,
inference.GrpcPredictV2.ServerMetadataRequest.class,
inference.GrpcPredictV2.ServerMetadataRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toServerMetadataRequest((inference.GrpcPredictV2.ServerMetadataRequest.Builder)
value));
+ addTypeConverter(registry,
inference.GrpcPredictV2.ServerReadyRequest.class,
inference.GrpcPredictV2.ServerReadyRequest.Builder.class, false,
+ (type, exchange, value) ->
org.apache.camel.component.kserve.KServeConverter.toServerReadyRequest((inference.GrpcPredictV2.ServerReadyRequest.Builder)
value));
+ }
+
+ private static void addTypeConverter(TypeConverterRegistry registry,
Class<?> toType, Class<?> fromType, boolean allowNull,
SimpleTypeConverter.ConversionMethod method) {
+ registry.addTypeConverter(toType, fromType, new
SimpleTypeConverter(allowNull, method));
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointConfigurer.java
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointConfigurer.java
new file mode 100644
index 00000000000..4772486a476
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointConfigurer.java
@@ -0,0 +1,69 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.kserve;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.support.component.PropertyConfigurerSupport;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo")
+@SuppressWarnings("unchecked")
+public class KServeEndpointConfigurer extends PropertyConfigurerSupport
implements GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
+ KServeEndpoint target = (KServeEndpoint) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials":
target.getConfiguration().setCredentials(property(camelContext,
io.grpc.ChannelCredentials.class, value)); return true;
+ case "lazystartproducer":
+ case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
+ case "modelname":
+ case "modelName":
target.getConfiguration().setModelName(property(camelContext,
java.lang.String.class, value)); return true;
+ case "modelversion":
+ case "modelVersion":
target.getConfiguration().setModelVersion(property(camelContext,
java.lang.String.class, value)); return true;
+ case "target":
target.getConfiguration().setTarget(property(camelContext,
java.lang.String.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials": return io.grpc.ChannelCredentials.class;
+ case "lazystartproducer":
+ case "lazyStartProducer": return boolean.class;
+ case "modelname":
+ case "modelName": return java.lang.String.class;
+ case "modelversion":
+ case "modelVersion": return java.lang.String.class;
+ case "target": return java.lang.String.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ KServeEndpoint target = (KServeEndpoint) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "credentials": return target.getConfiguration().getCredentials();
+ case "lazystartproducer":
+ case "lazyStartProducer": return target.isLazyStartProducer();
+ case "modelname":
+ case "modelName": return target.getConfiguration().getModelName();
+ case "modelversion":
+ case "modelVersion": return
target.getConfiguration().getModelVersion();
+ case "target": return target.getConfiguration().getTarget();
+ default: return null;
+ }
+ }
+}
+
diff --git
a/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointUriFactory.java
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointUriFactory.java
new file mode 100644
index 00000000000..555657e9d78
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/java/org/apache/camel/component/kserve/KServeEndpointUriFactory.java
@@ -0,0 +1,75 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.kserve;
+
+import javax.annotation.processing.Generated;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.spi.EndpointUriFactory;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateEndpointUriFactoryMojo")
+public class KServeEndpointUriFactory extends
org.apache.camel.support.component.EndpointUriFactorySupport implements
EndpointUriFactory {
+
+ private static final String BASE = ":api";
+
+ private static final Set<String> PROPERTY_NAMES;
+ private static final Set<String> SECRET_PROPERTY_NAMES;
+ private static final Set<String> MULTI_VALUE_PREFIXES;
+ static {
+ Set<String> props = new HashSet<>(6);
+ props.add("api");
+ props.add("credentials");
+ props.add("lazyStartProducer");
+ props.add("modelName");
+ props.add("modelVersion");
+ props.add("target");
+ PROPERTY_NAMES = Collections.unmodifiableSet(props);
+ SECRET_PROPERTY_NAMES = Collections.emptySet();
+ MULTI_VALUE_PREFIXES = Collections.emptySet();
+ }
+
+ @Override
+ public boolean isEnabled(String scheme) {
+ return "kserve".equals(scheme);
+ }
+
+ @Override
+ public String buildUri(String scheme, Map<String, Object> properties,
boolean encode) throws URISyntaxException {
+ String syntax = scheme + BASE;
+ String uri = syntax;
+
+ Map<String, Object> copy = new HashMap<>(properties);
+
+ uri = buildPathParameter(syntax, uri, "api", null, true, copy);
+ uri = buildQueryParameters(uri, copy, encode);
+ return uri;
+ }
+
+ @Override
+ public Set<String> propertyNames() {
+ return PROPERTY_NAMES;
+ }
+
+ @Override
+ public Set<String> secretPropertyNames() {
+ return SECRET_PROPERTY_NAMES;
+ }
+
+ @Override
+ public Set<String> multiValuePrefixes() {
+ return MULTI_VALUE_PREFIXES;
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ return false;
+ }
+}
+
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/org/apache/camel/component/kserve/kserve.json
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/org/apache/camel/component/kserve/kserve.json
new file mode 100644
index 00000000000..cda7067ee14
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/org/apache/camel/component/kserve/kserve.json
@@ -0,0 +1,49 @@
+{
+ "component": {
+ "kind": "component",
+ "name": "kserve",
+ "title": "KServe",
+ "description": "Provide access to AI model servers with the KServe
standard to run inference with remote models",
+ "deprecated": false,
+ "firstVersion": "4.10.0",
+ "label": "ai",
+ "javaType": "org.apache.camel.component.kserve.KServeComponent",
+ "supportLevel": "Preview",
+ "groupId": "org.apache.camel",
+ "artifactId": "camel-kserve",
+ "version": "4.10.0-SNAPSHOT",
+ "scheme": "kserve",
+ "extendsScheme": "",
+ "syntax": "kserve:api",
+ "async": false,
+ "api": false,
+ "consumerOnly": false,
+ "producerOnly": true,
+ "lenientProperties": false,
+ "browsable": false,
+ "remote": true
+ },
+ "componentProperties": {
+ "configuration": { "index": 0, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType": "org.apache.camel.component.kserve.KServeConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
configuration." },
+ "modelName": { "index": 1, "kind": "property", "displayName": "Model
Name", "group": "common", "label": "common", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The name of the model used for inference." },
+ "modelVersion": { "index": 2, "kind": "property", "displayName": "Model
Version", "group": "common", "label": "common", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The version of the model used for inference."
},
+ "target": { "index": 3, "kind": "property", "displayName": "Target",
"group": "common", "label": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "localhost:8001", "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The target URI of the client. See:
https:\/\/grpc.github.io\/grpc-java\/javadoc\/io\/g [...]
+ "lazyStartProducer": { "index": 4, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
+ "autowiredEnabled": { "index": 5, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
+ "healthCheckConsumerEnabled": { "index": 6, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
+ "healthCheckProducerEnabled": { "index": 7, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on producer [...]
+ "credentials": { "index": 8, "kind": "property", "displayName":
"Credentials", "group": "security", "label": "security", "required": false,
"type": "object", "javaType": "io.grpc.ChannelCredentials", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The credentials of the client." }
+ },
+ "headers": {
+ "CamelKServeModelName": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the model used for inference.",
"constantName": "org.apache.camel.component.kserve.KServeConstants#MODEL_NAME"
},
+ "CamelKServeModelVersion": { "index": 1, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The version of the model used for inference.",
"constantName":
"org.apache.camel.component.kserve.KServeConstants#MODEL_VERSION" }
+ },
+ "properties": {
+ "api": { "index": 0, "kind": "path", "displayName": "Api", "group":
"producer", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "enum": [ "infer", "model\/ready", "model\/metadata",
"server\/ready", "server\/live", "server\/metadata" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
KServe API" },
+ "modelName": { "index": 1, "kind": "parameter", "displayName": "Model
Name", "group": "common", "label": "common", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The name of the model used for inference." },
+ "modelVersion": { "index": 2, "kind": "parameter", "displayName": "Model
Version", "group": "common", "label": "common", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The version of the model used for inference."
},
+ "target": { "index": 3, "kind": "parameter", "displayName": "Target",
"group": "common", "label": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "localhost:8001", "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The target URI of the client. See:
https:\/\/grpc.github.io\/grpc-java\/javadoc\/io\/ [...]
+ "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "credentials": { "index": 5, "kind": "parameter", "displayName":
"Credentials", "group": "security", "label": "security", "required": false,
"type": "object", "javaType": "io.grpc.ChannelCredentials", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kserve.KServeConfiguration", "configurationField":
"configuration", "description": "The credentials of the client." }
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
new file mode 100644
index 00000000000..3cb278bf2aa
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+org.apache.camel.component.kserve.KServeConverterLoader
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component.properties
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component.properties
new file mode 100644
index 00000000000..fd082757d6b
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+components=kserve
+groupId=org.apache.camel
+artifactId=camel-kserve
+version=4.10.0-SNAPSHOT
+projectName=Camel :: AI :: KServe
+projectDescription=Provide access to AI model servers with the KServe standard
to run inference with remote models
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component/kserve
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component/kserve
new file mode 100644
index 00000000000..62255ddd309
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/component/kserve
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.kserve.KServeComponent
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-component
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-component
new file mode 100644
index 00000000000..8de8ef1ca0e
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-component
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.kserve.KServeComponentConfigurer
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-endpoint
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-endpoint
new file mode 100644
index 00000000000..66f0b3890c9
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/kserve-endpoint
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.kserve.KServeEndpointConfigurer
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.kserve.KServeConfiguration
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.kserve.KServeConfiguration
new file mode 100644
index 00000000000..f141901179a
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.kserve.KServeConfiguration
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.kserve.KServeConfigurationConfigurer
diff --git
a/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/urifactory/kserve-endpoint
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/urifactory/kserve-endpoint
new file mode 100644
index 00000000000..d0e4d52c7ee
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/generated/resources/META-INF/services/org/apache/camel/urifactory/kserve-endpoint
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.kserve.KServeEndpointUriFactory
diff --git
a/components/camel-ai/camel-kserve/src/main/docs/kserve-component.adoc
b/components/camel-ai/camel-kserve/src/main/docs/kserve-component.adoc
new file mode 100644
index 00000000000..e73d97b39ae
--- /dev/null
+++ b/components/camel-ai/camel-kserve/src/main/docs/kserve-component.adoc
@@ -0,0 +1,243 @@
+= KServe Component
+:doctitle: KServe
+:shortname: kserve
+:artifactid: camel-kserve
+:description: Provide access to AI model servers with the KServe standard to
run inference with remote models
+:since: 4.10
+:supportlevel: Preview
+:tabs-sync-option:
+:component-header: Only producer is supported
+//Manually maintained attributes
+:group: AI
+:camel-spring-boot-name: kserve
+
+*Since Camel {since}*
+
+*{component-header}*
+
+The KServe component provides the ability to access various AI model servers
using the
https://kserve.github.io/website/latest/modelserving/data_plane/v2_protocol/[KServe
Open Inference Protocl V2]. This allows Camel to remotely perform inference
with AI models on various model servers that support the KServe V2 protocol.
+
+NOTE: Currently, this component only supports
https://kserve.github.io/website/latest/reference/swagger-ui/#grpc[GRPC API].
+
+To use the KServe component, Maven users will need to add the following
dependency to their `pom.xml`:
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-kserve</artifactId>
+ <version>x.x.x</version>
+ <!-- use the same version as your Camel core version -->
+</dependency>
+----
+
+== URI format
+
+----
+kserve:api[?options]
+----
+
+Where `api` represents one of the
https://kserve.github.io/website/latest/reference/swagger-ui/#grpc[KServe Open
Inference Protocol GRPC API].
+
+// component-configure options: START
+
+// component-configure options: END
+
+// component options: START
+include::partial$component-configure-options.adoc[]
+include::partial$component-endpoint-options.adoc[]
+// component options: END
+
+// endpoint options: START
+
+// endpoint options: END
+
+// component headers: START
+include::partial$component-endpoint-headers.adoc[]
+// component headers: END
+
+== Usage
+
+The component supports the following APIs.
+
+----
+kserve:<api>[?options]
+----
+
+[width="100%",cols="2,5,1,2,2",options="header"]
+|===
+| API | Description | Options | Input (Message Body) | Result (Message Body)
+
+| `infer`
+| Performs inference using the specified model.
+| `modelName` +
+ `modelVersion`
+| `ModelInferRequest` footnote:[`inference.GrpcPredictV2.ModelInferRequest`]
+| `ModelInferResponse` footnote:[`inference.GrpcPredictV2.ModelInferResponse`]
+
+| `model/ready`
+| Indicates if a specific model is ready for inferencing.
+| `modelName` +
+ `modelVersion`
+| `ModelReadyRequest` footnote:[`inference.GrpcPredictV2.ModelReadyRequest`] +
+ (optional)
+| `ModelReadyResponse` footnote:[`inference.GrpcPredictV2.ModelReadyResponse`]
+
+| `model/metadata`
+| Provides information about a model.
+| `modelName` +
+ `modelVersion`
+| `ModelMetadataRequest`
footnote:[`inference.GrpcPredictV2.ModelMetadataRequest`] +
+ (optional)
+| `ModelMetadataResponse`
footnote:[`inference.GrpcPredictV2.ModelMetadataResponse`]
+
+| `server/ready`
+| Indicates if the server is ready for inferencing.
+|
+|
+| `ServerReadyResponse`
footnote:[`inference.GrpcPredictV2.ServerReadyResponse`]
+
+| `server/live`
+| Indicates if the inference server is able to receive and respond to metadata
and inference requests.
+|
+|
+| `ServerLiveResponse` footnote:[`inference.GrpcPredictV2.ServerLiveResponse`]
+
+| `server/metadata`
+| Provides information about the server.
+|
+|
+| `ServerMetadataResponse`
footnote:[`inference.GrpcPredictV2.ServerMetadataResponse`]
+|===
+
+== Examples
+
+=== Infer (ModelInfer) API
+
+.Perform inference
+[source,java]
+----
+from("direct:infer")
+ .setBody(constant(createRequest()))
+ .to("kserve:infer?modelName=simple&modelVersion=1")
+ .process(this::postprocess)
+ .log("Result: ${body}");
+
+// Helper methods
+
+ModelInferRequest createRequest() {
+ // How to create a request differs depending on the input types of the
model.
+ var ints0 = IntStream.range(1, 17).boxed().collect(Collectors.toList());
+ var content0 = InferTensorContents.newBuilder().addAllIntContents(ints0);
+ var input0 = ModelInferRequest.InferInputTensor.newBuilder()
+ .setName("INPUT0").setDatatype("INT32").addShape(1).addShape(16)
+ .setContents(content0);
+ var ints1 = IntStream.range(0, 16).boxed().collect(Collectors.toList());
+ var content1 = InferTensorContents.newBuilder().addAllIntContents(ints1);
+ var input1 = ModelInferRequest.InferInputTensor.newBuilder()
+ .setName("INPUT1").setDatatype("INT32").addShape(1).addShape(16)
+ .setContents(content1);
+ return ModelInferRequest.newBuilder()
+ .addInputs(0, input0).addInputs(1, input1)
+ .build();
+}
+
+void postprocess(Exchange exchange) {
+ // How to post-process the response differs depending on the output types
+ // of the model.
+ var response = exchange.getMessage().getBody(ModelInferResponse.class);
+ var content = response.getRawOutputContents(0);
+ var buffer =
content.asReadOnlyByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+ var ints = new ArrayList<Integer>(buffer.remaining());
+ while (buffer.hasRemaining()) {
+ ints.add(buffer.get());
+ }
+ exchange.getMessage().setBody(ints);
+}
+----
+
+.Specify the model name and version with headers
+[source,java]
+----
+from("direct:infer-with-headers")
+ .setBody(constant(createRequest()))
+ .setHeader(KServeConstants.MODEL_NAME, constant("simple"))
+ .setHeader(KServeConstants.MODEL_VERSION, constant("1"))
+ .to("kserve:infer")
+ .process(this::postprocess)
+ .log("Result: ${body}");
+
+// ... Same as the previous example
+----
+
+=== ModelReady API
+
+.Check if a model is ready
+[source,java]
+----
+from("direct:model-ready")
+ .to("kserve:model-ready?modelName=simple&modelVersion=1")
+ .log("Status: ${body.ready}");
+----
+
+.Specify the model name and version with headers
+[source,java]
+----
+from("direct:model-ready-with-headers")
+ .setHeader(KServeConstants.MODEL_NAME, constant("simple"))
+ .setHeader(KServeConstants.MODEL_VERSION, constant("1"))
+ .to("kserve:model-ready")
+ .log("Status: ${body.ready}");
+----
+
+=== ModelMetadata API
+
+.Fetch model metadata
+[source,java]
+----
+from("direct:model-metadata")
+ .to("kserve:model-metadata?modelName=simple&modelVersion=1")
+ .log("Metadata: ${body}");
+----
+
+.Specify the model name and version with headers
+[source,java]
+----
+from("direct:model-metadata-with-headers")
+ .setHeader(KServeConstants.MODEL_NAME, constant("simple"))
+ .setHeader(KServeConstants.MODEL_VERSION, constant("1"))
+ .to("kserve:model-metadata")
+ .log("Metadata: ${body}");
+----
+
+=== ServerReady API
+
+.Check if the server is ready
+[source,java]
+----
+from("direct:server-ready")
+ .to("kserve:server-ready")
+ .log("Status: ${body.ready}");
+----
+
+=== ServerLive API
+
+.Check if the server is live
+[source,java]
+----
+from("direct:server-live")
+ .to("kserve:server-live")
+ .log("Status: ${body.live}");
+----
+
+=== ServerMetadata API
+
+.Fetch server metadata
+[source,java]
+----
+from("direct:server-metadata")
+ .to("kserve:server-metadata")
+ .log("Metadata: ${body}");
+----
+
+include::spring-boot:partial$starter.adoc[]
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeComponent.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeComponent.java
new file mode 100644
index 00000000000..ea8aaccf019
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeComponent.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Component;
+import org.apache.camel.support.HealthCheckComponent;
+
+@Component("kserve")
+public class KServeComponent extends HealthCheckComponent {
+
+ @Metadata
+ private KServeConfiguration configuration = new KServeConfiguration();
+
+ public KServeComponent() {
+ super();
+ }
+
+ public KServeComponent(CamelContext context) {
+ super(context);
+ }
+
+ protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) throws Exception {
+ KServeConfiguration configuration
+ = this.configuration != null ? this.configuration.copy() : new
KServeConfiguration();
+ Endpoint endpoint = new KServeEndpoint(uri, this, remaining,
configuration);
+ setProperties(endpoint, parameters);
+ return endpoint;
+ }
+
+ public KServeConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * The configuration.
+ */
+ public void setConfiguration(KServeConfiguration configuration) {
+ this.configuration = configuration;
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConfiguration.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConfiguration.java
new file mode 100644
index 00000000000..4fdda0a3241
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConfiguration.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import io.grpc.ChannelCredentials;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriParams;
+
+@UriParams
+@Configurer
+public class KServeConfiguration implements Cloneable {
+
+ @UriParam(label = "common", defaultValue = "localhost:8001")
+ private String target = "localhost:8001";
+
+ @UriParam(label = "security")
+ private ChannelCredentials credentials;
+
+ @UriParam(label = "common")
+ private String modelName;
+
+ @UriParam(label = "common")
+ private String modelVersion;
+
+ public String getTarget() {
+ return target;
+ }
+
+ /**
+ * The target URI of the client. See:
+ *
https://grpc.github.io/grpc-java/javadoc/io/grpc/Grpc.html#newChannelBuilder%28java.lang.String,io.grpc.ChannelCredentials%29
+ */
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public ChannelCredentials getCredentials() {
+ return credentials;
+ }
+
+ /**
+ * The credentials of the client.
+ */
+ public void setCredentials(ChannelCredentials credentials) {
+ this.credentials = credentials;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * The name of the model used for inference.
+ */
+ public void setModelName(String modelName) {
+ this.modelName = modelName;
+ }
+
+ public String getModelVersion() {
+ return modelVersion;
+ }
+
+ /**
+ * The version of the model used for inference.
+ */
+ public void setModelVersion(String modelVersion) {
+ this.modelVersion = modelVersion;
+ }
+
+ public KServeConfiguration copy() {
+ try {
+ return (KServeConfiguration) clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeCamelException(e);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConstants.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConstants.java
new file mode 100644
index 00000000000..1465f834763
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConstants.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import org.apache.camel.spi.Metadata;
+
+/**
+ * Constants used in Camel KServe component.
+ */
+public interface KServeConstants {
+
+ @Metadata(description = "The name of the model used for inference.",
javaType = "String")
+ String MODEL_NAME = "CamelKServeModelName";
+
+ @Metadata(description = "The version of the model used for inference.",
javaType = "String")
+ String MODEL_VERSION = "CamelKServeModelVersion";
+
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConverter.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConverter.java
new file mode 100644
index 00000000000..af9380d8d77
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeConverter.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import inference.GrpcPredictV2;
+import org.apache.camel.Converter;
+
+/**
+ * Converter methods to convert from / to KServe types.
+ */
+@Converter(generateLoader = true)
+public class KServeConverter {
+
+ @Converter
+ public static GrpcPredictV2.ModelInferRequest toModelInferRequest(
+ GrpcPredictV2.ModelInferRequest.Builder builder) {
+ return builder.build();
+ }
+
+ @Converter
+ public static GrpcPredictV2.ModelReadyRequest toModelReadyRequest(
+ GrpcPredictV2.ModelReadyRequest.Builder builder) {
+ return builder.build();
+ }
+
+ @Converter
+ public static GrpcPredictV2.ModelMetadataRequest toModelMetadataRequest(
+ GrpcPredictV2.ModelMetadataRequest.Builder builder) {
+ return builder.build();
+ }
+
+ @Converter
+ public static GrpcPredictV2.ServerReadyRequest toServerReadyRequest(
+ GrpcPredictV2.ServerReadyRequest.Builder builder) {
+ return builder.build();
+ }
+
+ @Converter
+ public static GrpcPredictV2.ServerLiveRequest toServerLiveRequest(
+ GrpcPredictV2.ServerLiveRequest.Builder builder) {
+ return builder.build();
+ }
+
+ @Converter
+ public static GrpcPredictV2.ServerMetadataRequest toServerMetadataRequest(
+ GrpcPredictV2.ServerMetadataRequest.Builder builder) {
+ return builder.build();
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeEndpoint.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeEndpoint.java
new file mode 100644
index 00000000000..74e6af43a9e
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeEndpoint.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import inference.GRPCInferenceServiceGrpc;
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.InsecureChannelCredentials;
+import io.grpc.ManagedChannel;
+import org.apache.camel.Category;
+import org.apache.camel.Consumer;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriPath;
+import org.apache.camel.support.DefaultEndpoint;
+
+@UriEndpoint(firstVersion = "4.10.0", scheme = "kserve", title = "KServe",
+ syntax = "kserve:api", producerOnly = true,
+ category = { Category.AI }, headersClass = KServeConstants.class)
+public class KServeEndpoint extends DefaultEndpoint {
+
+ /**
+ * The KServe API spec: <a href=
+ *
"https://github.com/kserve/open-inference-protocol/blob/main/specification/protocol/inference_grpc.md">open-inference-protocol/specification/protocol/inference_grpc.md</a>
+ */
+ @UriPath(enums =
"infer,model/ready,model/metadata,server/ready,server/live,server/metadata",
+ description = "The KServe API")
+ @Metadata(required = true)
+ private final String api;
+
+ @UriParam
+ private KServeConfiguration configuration;
+
+ private ManagedChannel channel;
+ private GRPCInferenceServiceGrpc.GRPCInferenceServiceBlockingStub
inferenceService;
+
+ public KServeEndpoint(String uri, KServeComponent component, String path,
+ KServeConfiguration configuration) {
+ super(uri, component);
+ this.api = path;
+ this.configuration = configuration;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ super.doInit();
+
+ ChannelCredentials credentials = configuration.getCredentials() != null
+ ? configuration.getCredentials()
+ : InsecureChannelCredentials.create();
+ channel = Grpc.newChannelBuilder(configuration.getTarget(),
credentials).build();
+ inferenceService = GRPCInferenceServiceGrpc.newBlockingStub(channel);
+ }
+
+ @Override
+ public void doStop() throws Exception {
+ super.doStop();
+
+ // Close the channel
+ channel.shutdown();
+ }
+
+ @Override
+ public Producer createProducer() {
+ return new KServeProducer(this);
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) {
+ throw new UnsupportedOperationException("Consumer not supported");
+ }
+
+ public String getApi() {
+ return api;
+ }
+
+ public KServeConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(KServeConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ public GRPCInferenceServiceGrpc.GRPCInferenceServiceBlockingStub
getInferenceService() {
+ return inferenceService;
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeProducer.java
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeProducer.java
new file mode 100644
index 00000000000..40ee8b259e0
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/main/java/org/apache/camel/component/kserve/KServeProducer.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve;
+
+import java.util.Optional;
+
+import com.google.protobuf.GeneratedMessageV3;
+import inference.GRPCInferenceServiceGrpc;
+import inference.GrpcPredictV2;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.support.DefaultProducer;
+
+public class KServeProducer extends DefaultProducer {
+
+ private final String api;
+ private final GRPCInferenceServiceGrpc.GRPCInferenceServiceBlockingStub
inferenceService;
+
+ public KServeProducer(KServeEndpoint endpoint) {
+ super(endpoint);
+ this.api = endpoint.getApi();
+ this.inferenceService = endpoint.getInferenceService();
+ }
+
+ @Override
+ public KServeEndpoint getEndpoint() {
+ return (KServeEndpoint) super.getEndpoint();
+ }
+
+ @Override
+ public void process(Exchange exchange) {
+ GeneratedMessageV3 response = switch (api) {
+ case "infer" -> infer(exchange);
+ case "model/ready" -> modelReady(exchange);
+ case "model/metadata" -> modelMetadata(exchange);
+ case "server/ready" -> serverReady();
+ case "server/live" -> serverLive();
+ case "server/metadata" -> serverMetadata();
+ default -> throw new IllegalArgumentException("Unsupported API: "
+ api);
+ };
+ exchange.getMessage().setBody(response);
+ }
+
+ private GrpcPredictV2.ModelInferResponse infer(Exchange exchange) {
+ Message message = exchange.getMessage();
+ GrpcPredictV2.ModelInferRequest request =
message.getBody(GrpcPredictV2.ModelInferRequest.class);
+
+ KServeConfiguration configuration = getEndpoint().getConfiguration();
+ GrpcPredictV2.ModelInferRequest.Builder builder =
GrpcPredictV2.ModelInferRequest.newBuilder();
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_NAME,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelName()))
+ .ifPresent(builder::setModelName);
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_VERSION,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelVersion()))
+ .ifPresent(builder::setModelVersion);
+ if (request != null) {
+ builder.mergeFrom(request);
+ }
+
+ return inferenceService.modelInfer(builder.build());
+ }
+
+ private GrpcPredictV2.ModelReadyResponse modelReady(Exchange exchange) {
+ Message message = exchange.getMessage();
+ GrpcPredictV2.ModelReadyRequest request =
message.getBody(GrpcPredictV2.ModelReadyRequest.class);
+
+ KServeConfiguration configuration = getEndpoint().getConfiguration();
+ GrpcPredictV2.ModelReadyRequest.Builder builder =
GrpcPredictV2.ModelReadyRequest.newBuilder();
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_NAME,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelName()))
+ .ifPresent(builder::setName);
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_VERSION,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelVersion()))
+ .ifPresent(builder::setVersion);
+ if (request != null) {
+ builder.mergeFrom(request);
+ }
+
+ return inferenceService.modelReady(builder.build());
+ }
+
+ private GrpcPredictV2.ModelMetadataResponse modelMetadata(Exchange
exchange) {
+ Message message = exchange.getMessage();
+ GrpcPredictV2.ModelMetadataRequest request =
message.getBody(GrpcPredictV2.ModelMetadataRequest.class);
+
+ KServeConfiguration configuration = getEndpoint().getConfiguration();
+ GrpcPredictV2.ModelMetadataRequest.Builder builder =
GrpcPredictV2.ModelMetadataRequest.newBuilder();
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_NAME,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelName()))
+ .ifPresent(builder::setName);
+ Optional.ofNullable(message.getHeader(KServeConstants.MODEL_VERSION,
String.class))
+ .or(() -> Optional.ofNullable(configuration.getModelVersion()))
+ .ifPresent(builder::setVersion);
+ if (request != null) {
+ builder.mergeFrom(request);
+ }
+
+ return inferenceService.modelMetadata(builder.build());
+ }
+
+ private GrpcPredictV2.ServerReadyResponse serverReady() {
+ GrpcPredictV2.ServerReadyRequest.Builder builder =
GrpcPredictV2.ServerReadyRequest.newBuilder();
+ return inferenceService.serverReady(builder.build());
+ }
+
+ private GrpcPredictV2.ServerLiveResponse serverLive() {
+ GrpcPredictV2.ServerLiveRequest.Builder builder =
GrpcPredictV2.ServerLiveRequest.newBuilder();
+ return inferenceService.serverLive(builder.build());
+ }
+
+ private GrpcPredictV2.ServerMetadataResponse serverMetadata() {
+ GrpcPredictV2.ServerMetadataRequest.Builder builder =
GrpcPredictV2.ServerMetadataRequest.newBuilder();
+ return inferenceService.serverMetadata(builder.build());
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/main/proto/grpc_predict_v2.proto
b/components/camel-ai/camel-kserve/src/main/proto/grpc_predict_v2.proto
new file mode 100644
index 00000000000..5605f8fdfa7
--- /dev/null
+++ b/components/camel-ai/camel-kserve/src/main/proto/grpc_predict_v2.proto
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ */
+// Copyright 2020 kubeflow.org.
+//
+// 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
+//
+// http://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.
+
+syntax = "proto3";
+package inference;
+
+// Inference Server GRPC endpoints.
+service GRPCInferenceService
+{
+ // The ServerLive API indicates if the inference server is able to receive
+ // and respond to metadata and inference requests.
+ rpc ServerLive(ServerLiveRequest) returns (ServerLiveResponse) {}
+
+ // The ServerReady API indicates if the server is ready for inferencing.
+ rpc ServerReady(ServerReadyRequest) returns (ServerReadyResponse) {}
+
+ // The ModelReady API indicates if a specific model is ready for inferencing.
+ rpc ModelReady(ModelReadyRequest) returns (ModelReadyResponse) {}
+
+ // The ServerMetadata API provides information about the server. Errors are
+ // indicated by the google.rpc.Status returned for the request. The OK code
+ // indicates success and other codes indicate failure.
+ rpc ServerMetadata(ServerMetadataRequest) returns (ServerMetadataResponse) {}
+
+ // The per-model metadata API provides information about a model. Errors are
+ // indicated by the google.rpc.Status returned for the request. The OK code
+ // indicates success and other codes indicate failure.
+ rpc ModelMetadata(ModelMetadataRequest) returns (ModelMetadataResponse) {}
+
+ // The ModelInfer API performs inference using the specified model. Errors
are
+ // indicated by the google.rpc.Status returned for the request. The OK code
+ // indicates success and other codes indicate failure.
+ rpc ModelInfer(ModelInferRequest) returns (ModelInferResponse) {}
+}
+
+message ServerLiveRequest {}
+
+message ServerLiveResponse
+{
+ // True if the inference server is live, false if not live.
+ bool live = 1;
+}
+
+message ServerReadyRequest {}
+
+message ServerReadyResponse
+{
+ // True if the inference server is ready, false if not ready.
+ bool ready = 1;
+}
+
+message ModelReadyRequest
+{
+ // The name of the model to check for readiness.
+ string name = 1;
+
+ // The version of the model to check for readiness. If not given the
+ // server will choose a version based on the model and internal policy.
+ string version = 2;
+}
+
+message ModelReadyResponse
+{
+ // True if the model is ready, false if not ready.
+ bool ready = 1;
+}
+
+message ServerMetadataRequest {}
+
+message ServerMetadataResponse
+{
+ // The server name.
+ string name = 1;
+
+ // The server version.
+ string version = 2;
+
+ // The extensions supported by the server.
+ repeated string extensions = 3;
+}
+
+message ModelMetadataRequest
+{
+ // The name of the model.
+ string name = 1;
+
+ // The version of the model to check for readiness. If not given the
+ // server will choose a version based on the model and internal policy.
+ string version = 2;
+}
+
+message ModelMetadataResponse
+{
+ // Metadata for a tensor.
+ message TensorMetadata
+ {
+ // The tensor name.
+ string name = 1;
+
+ // The tensor data type.
+ string datatype = 2;
+
+ // The tensor shape. A variable-size dimension is represented
+ // by a -1 value.
+ repeated int64 shape = 3;
+ }
+
+ // The model name.
+ string name = 1;
+
+ // The versions of the model available on the server.
+ repeated string versions = 2;
+
+ // The model's platform. See Platforms.
+ string platform = 3;
+
+ // The model's inputs.
+ repeated TensorMetadata inputs = 4;
+
+ // The model's outputs.
+ repeated TensorMetadata outputs = 5;
+}
+
+message ModelInferRequest
+{
+ // An input tensor for an inference request.
+ message InferInputTensor
+ {
+ // The tensor name.
+ string name = 1;
+
+ // The tensor data type.
+ string datatype = 2;
+
+ // The tensor shape.
+ repeated int64 shape = 3;
+
+ // Optional inference input tensor parameters.
+ map<string, InferParameter> parameters = 4;
+
+ // The tensor contents using a data-type format. This field must
+ // not be specified if "raw" tensor contents are being used for
+ // the inference request.
+ InferTensorContents contents = 5;
+ }
+
+ // An output tensor requested for an inference request.
+ message InferRequestedOutputTensor
+ {
+ // The tensor name.
+ string name = 1;
+
+ // Optional requested output tensor parameters.
+ map<string, InferParameter> parameters = 2;
+ }
+
+ // The name of the model to use for inferencing.
+ string model_name = 1;
+
+ // The version of the model to use for inference. If not given the
+ // server will choose a version based on the model and internal policy.
+ string model_version = 2;
+
+ // Optional identifier for the request. If specified will be
+ // returned in the response.
+ string id = 3;
+
+ // Optional inference parameters.
+ map<string, InferParameter> parameters = 4;
+
+ // The input tensors for the inference.
+ repeated InferInputTensor inputs = 5;
+
+ // The requested output tensors for the inference. Optional, if not
+ // specified all outputs produced by the model will be returned.
+ repeated InferRequestedOutputTensor outputs = 6;
+
+ // The data contained in an input tensor can be represented in "raw"
+ // bytes form or in the repeated type that matches the tensor's data
+ // type. To use the raw representation 'raw_input_contents' must be
+ // initialized with data for each tensor in the same order as
+ // 'inputs'. For each tensor, the size of this content must match
+ // what is expected by the tensor's shape and data type. The raw
+ // data must be the flattened, one-dimensional, row-major order of
+ // the tensor elements without any stride or padding between the
+ // elements. Note that the FP16 and BF16 data types must be represented as
+ // raw content as there is no specific data type for a 16-bit float type.
+ //
+ // If this field is specified then InferInputTensor::contents must
+ // not be specified for any input tensor.
+ repeated bytes raw_input_contents = 7;
+}
+
+message ModelInferResponse
+{
+ // An output tensor returned for an inference request.
+ message InferOutputTensor
+ {
+ // The tensor name.
+ string name = 1;
+
+ // The tensor data type.
+ string datatype = 2;
+
+ // The tensor shape.
+ repeated int64 shape = 3;
+
+ // Optional output tensor parameters.
+ map<string, InferParameter> parameters = 4;
+
+ // The tensor contents using a data-type format. This field must
+ // not be specified if "raw" tensor contents are being used for
+ // the inference response.
+ InferTensorContents contents = 5;
+ }
+
+ // The name of the model used for inference.
+ string model_name = 1;
+
+ // The version of the model used for inference.
+ string model_version = 2;
+
+ // The id of the inference request if one was specified.
+ string id = 3;
+
+ // Optional inference response parameters.
+ map<string, InferParameter> parameters = 4;
+
+ // The output tensors holding inference results.
+ repeated InferOutputTensor outputs = 5;
+
+ // The data contained in an output tensor can be represented in
+ // "raw" bytes form or in the repeated type that matches the
+ // tensor's data type. To use the raw representation 'raw_output_contents'
+ // must be initialized with data for each tensor in the same order as
+ // 'outputs'. For each tensor, the size of this content must match
+ // what is expected by the tensor's shape and data type. The raw
+ // data must be the flattened, one-dimensional, row-major order of
+ // the tensor elements without any stride or padding between the
+ // elements. Note that the FP16 and BF16 data types must be represented as
+ // raw content as there is no specific data type for a 16-bit float type.
+ //
+ // If this field is specified then InferOutputTensor::contents must
+ // not be specified for any output tensor.
+ repeated bytes raw_output_contents = 6;
+}
+
+// An inference parameter value. The Parameters message describes a
+// “name”/”value” pair, where the “name” is the name of the parameter
+// and the “value” is a boolean, integer, or string corresponding to
+// the parameter.
+message InferParameter
+{
+ // The parameter value can be a string, an int64, a boolean
+ // or a message specific to a predefined parameter.
+ oneof parameter_choice
+ {
+ // A boolean parameter value.
+ bool bool_param = 1;
+
+ // An int64 parameter value.
+ int64 int64_param = 2;
+
+ // A string parameter value.
+ string string_param = 3;
+ }
+}
+
+// The data contained in a tensor represented by the repeated type
+// that matches the tensor's data type. Protobuf oneof is not used
+// because oneofs cannot contain repeated fields.
+message InferTensorContents
+{
+ // Representation for BOOL data type. The size must match what is
+ // expected by the tensor's shape. The contents must be the flattened,
+ // one-dimensional, row-major order of the tensor elements.
+ repeated bool bool_contents = 1;
+
+ // Representation for INT8, INT16, and INT32 data types. The size
+ // must match what is expected by the tensor's shape. The contents
+ // must be the flattened, one-dimensional, row-major order of the
+ // tensor elements.
+ repeated int32 int_contents = 2;
+
+ // Representation for INT64 data types. The size must match what
+ // is expected by the tensor's shape. The contents must be the
+ // flattened, one-dimensional, row-major order of the tensor elements.
+ repeated int64 int64_contents = 3;
+
+ // Representation for UINT8, UINT16, and UINT32 data types. The size
+ // must match what is expected by the tensor's shape. The contents
+ // must be the flattened, one-dimensional, row-major order of the
+ // tensor elements.
+ repeated uint32 uint_contents = 4;
+
+ // Representation for UINT64 data types. The size must match what
+ // is expected by the tensor's shape. The contents must be the
+ // flattened, one-dimensional, row-major order of the tensor elements.
+ repeated uint64 uint64_contents = 5;
+
+ // Representation for FP32 data type. The size must match what is
+ // expected by the tensor's shape. The contents must be the flattened,
+ // one-dimensional, row-major order of the tensor elements.
+ repeated float fp32_contents = 6;
+
+ // Representation for FP64 data type. The size must match what is
+ // expected by the tensor's shape. The contents must be the flattened,
+ // one-dimensional, row-major order of the tensor elements.
+ repeated double fp64_contents = 7;
+
+ // Representation for BYTES data type. The size must match what is
+ // expected by the tensor's shape. The contents must be the flattened,
+ // one-dimensional, row-major order of the tensor elements.
+ repeated bytes bytes_contents = 8;
+}
diff --git
a/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeEndpointIT.java
b/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeEndpointIT.java
new file mode 100644
index 00000000000..4bfde361087
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeEndpointIT.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve.it;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import com.google.protobuf.ByteString;
+import inference.GrpcPredictV2;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.kserve.KServeConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class KServeEndpointIT extends KServeITSupport {
+
+ private static final String TEST_MODEL = "simple";
+ private static final String TEST_MODEL_VERSION = "1";
+
+ @Test
+ void testInfer() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelInferResponse.class)
+ .simple("${body.rawOutputContentsCount").isEqualTo(2);
+
+ var request = createInferRequest();
+ template.sendBody("direct:infer", request);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+
+ var response =
mock.getReceivedExchanges().get(0).getMessage().getBody(GrpcPredictV2.ModelInferResponse.class);
+ assertInferResponse(response);
+ }
+
+ @Test
+ void testInfer_version() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelInferResponse.class)
+ .simple("${body.rawOutputContentsCount").isEqualTo(2);
+
+ var request = createInferRequest();
+ template.sendBody("direct:infer_version", request);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+
+ var response =
mock.getReceivedExchanges().get(0).getMessage().getBody(GrpcPredictV2.ModelInferResponse.class);
+ assertInferResponse(response);
+ }
+
+ @Test
+ void testInfer_headers() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelInferResponse.class)
+ .simple("${body.rawOutputContentsCount").isEqualTo(2);
+
+ var request = createInferRequest();
+ template.send("direct:infer_headers", e -> {
+ e.getMessage().setHeader(KServeConstants.MODEL_NAME, TEST_MODEL);
+ e.getMessage().setHeader(KServeConstants.MODEL_VERSION,
TEST_MODEL_VERSION);
+ e.getMessage().setBody(request);
+ });
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+
+ var response =
mock.getReceivedExchanges().get(0).getMessage().getBody(GrpcPredictV2.ModelInferResponse.class);
+ assertInferResponse(response);
+ }
+
+ private GrpcPredictV2.ModelInferRequest createInferRequest() {
+ var ints0 = IntStream.range(1,
17).boxed().collect(Collectors.toList());
+ var content0 =
GrpcPredictV2.InferTensorContents.newBuilder().addAllIntContents(ints0);
+ var input0 =
GrpcPredictV2.ModelInferRequest.InferInputTensor.newBuilder()
+
.setName("INPUT0").setDatatype("INT32").addShape(1).addShape(16)
+ .setContents(content0);
+
+ var ints1 = IntStream.range(0,
16).boxed().collect(Collectors.toList());
+ var content1 =
GrpcPredictV2.InferTensorContents.newBuilder().addAllIntContents(ints1);
+ var input1 =
GrpcPredictV2.ModelInferRequest.InferInputTensor.newBuilder()
+
.setName("INPUT1").setDatatype("INT32").addShape(1).addShape(16)
+ .setContents(content1);
+
+ return GrpcPredictV2.ModelInferRequest.newBuilder()
+ .addInputs(0, input0).addInputs(1, input1)
+ .build();
+ }
+
+ private void assertInferResponse(GrpcPredictV2.ModelInferResponse
response) {
+ var output0 = toList(response.getRawOutputContents(0));
+ // output0 = input0 + input1
+ assertEquals(List.of(1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25,
27, 29, 31), output0);
+ var output1 = toList(response.getRawOutputContents(1));
+ // output1 = input0 - input1
+ assertEquals(List.of(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
output1);
+ }
+
+ private List<Integer> toList(ByteString content) {
+ var buffer =
content.asReadOnlyByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+ var list = new ArrayList<Integer>(buffer.remaining());
+ while (buffer.hasRemaining()) {
+ list.add(buffer.get());
+ }
+ return list;
+ }
+
+ @Test
+ void testModelReady() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelReadyResponse.class)
+ .simple("${body.ready}").isEqualTo("true");
+
+ template.sendBody("direct:model-ready", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testModelReady_version() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelReadyResponse.class)
+ .simple("${body.ready}").isEqualTo("true");
+
+ template.sendBody("direct:model-ready_version", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testModelReady_headers() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ModelReadyResponse.class)
+ .simple("${body.ready}").isEqualTo("true");
+
+ template.send("direct:model-ready_headers", e -> {
+ e.getMessage().setHeader(KServeConstants.MODEL_NAME, TEST_MODEL);
+ e.getMessage().setHeader(KServeConstants.MODEL_VERSION,
TEST_MODEL_VERSION);
+ });
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testModelMetadata() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
mock.expectedBodyReceived().body(GrpcPredictV2.ModelMetadataResponse.class);
+ mock.message(0).body().simple("${body.name}").isEqualTo(TEST_MODEL);
+
mock.message(0).body().simple("${body.getVersions(0)}").isEqualTo(TEST_MODEL_VERSION);
+
+ template.sendBody("direct:model-metadata", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testModelMetadata_version() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
mock.expectedBodyReceived().body(GrpcPredictV2.ModelMetadataResponse.class);
+ mock.message(0).body().simple("${body.name}").isEqualTo(TEST_MODEL);
+
mock.message(0).body().simple("${body.getVersions(0)}").isEqualTo(TEST_MODEL_VERSION);
+
+ template.sendBody("direct:model-metadata_version", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testModelMetadata_headers() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
mock.expectedBodyReceived().body(GrpcPredictV2.ModelMetadataResponse.class);
+ mock.message(0).body().simple("${body.name}").isEqualTo(TEST_MODEL);
+
mock.message(0).body().simple("${body.getVersions(0)}").isEqualTo(TEST_MODEL_VERSION);
+
+ template.send("direct:model-metadata_headers", e -> {
+ e.getMessage().setHeader(KServeConstants.MODEL_NAME, TEST_MODEL);
+ e.getMessage().setHeader(KServeConstants.MODEL_VERSION,
TEST_MODEL_VERSION);
+ });
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testServerReady() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ServerReadyResponse.class)
+ .simple("${body.ready}").isEqualTo("true");
+
+ template.sendBody("direct:server-ready", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testServerLive() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodyReceived()
+ .body(GrpcPredictV2.ServerLiveResponse.class)
+ .simple("${body.live}").isEqualTo("true");
+
+ template.sendBody("direct:server-live", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Test
+ void testServerMetadata() throws Exception {
+ var mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
mock.expectedBodyReceived().body(GrpcPredictV2.ServerMetadataResponse.class);
+ // IT uses Triton Inference Server
+ mock.message(0).body().simple("${body.name}").isEqualTo("triton");
+ mock.message(0).body().simple("${body.version}").isNotNull();
+
mock.message(0).body().simple("${body.extensionsCount}").isNotEqualTo(0);
+
+ template.sendBody("direct:server-metadata", null);
+
+ mock.await(1, TimeUnit.SECONDS);
+ mock.assertIsSatisfied();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:infer")
+ .toF("kserve:infer?modelName=%s", TEST_MODEL)
+ .to("mock:result");
+ from("direct:infer_version")
+ .toF("kserve:infer?modelName=%s&modelVersion=%s",
TEST_MODEL, TEST_MODEL_VERSION)
+ .to("mock:result");
+ from("direct:infer_headers")
+ .to("kserve:infer")
+ .to("mock:result");
+ from("direct:model-ready")
+ .toF("kserve:model/ready?modelName=%s", TEST_MODEL)
+ .to("mock:result");
+ from("direct:model-ready_version")
+
.toF("kserve:model/ready?modelName=%s&modelVersion=%s", TEST_MODEL,
TEST_MODEL_VERSION)
+ .to("mock:result");
+ from("direct:model-ready_headers")
+ .to("kserve:model/ready")
+ .to("mock:result");
+ from("direct:model-metadata")
+ .toF("kserve:model/metadata?modelName=%s", TEST_MODEL)
+ .to("mock:result");
+ from("direct:model-metadata_version")
+
.toF("kserve:model/metadata?modelName=%s&modelVersion=%s", TEST_MODEL,
TEST_MODEL_VERSION)
+ .to("mock:result");
+ from("direct:model-metadata_headers")
+ .to("kserve:model/metadata")
+ .to("mock:result");
+ from("direct:server-ready")
+ .toF("kserve:server/ready")
+ .to("mock:result");
+ from("direct:server-live")
+ .toF("kserve:server/live")
+ .to("mock:result");
+ from("direct:server-metadata")
+ .toF("kserve:server/metadata")
+ .to("mock:result");
+ }
+ };
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeITSupport.java
b/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeITSupport.java
new file mode 100644
index 00000000000..62db7c3c52a
--- /dev/null
+++
b/components/camel-ai/camel-kserve/src/test/java/org/apache/camel/component/kserve/it/KServeITSupport.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.component.kserve.it;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.kserve.KServeComponent;
+import org.apache.camel.test.infra.triton.services.TritonService;
+import org.apache.camel.test.infra.triton.services.TritonServiceFactory;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class KServeITSupport extends CamelTestSupport {
+
+ @RegisterExtension
+ static TritonService service = TritonServiceFactory.createService();
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ var context = super.createCamelContext();
+ var component = context.getComponent("kserve", KServeComponent.class);
+ var configuration = component.getConfiguration();
+ configuration.setTarget("localhost:" + service.grpcPort());
+ return context;
+ }
+}
diff --git
a/components/camel-ai/camel-kserve/src/test/resources/log4j2.properties
b/components/camel-ai/camel-kserve/src/test/resources/log4j2.properties
new file mode 100644
index 00000000000..552135d8f5c
--- /dev/null
+++ b/components/camel-ai/camel-kserve/src/test/resources/log4j2.properties
@@ -0,0 +1,28 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You 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
+##
+## http://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.
+## ---------------------------------------------------------------------------
+
+appender.file.type = File
+appender.file.name = file
+appender.file.fileName = target/camel-kserve-test.log
+appender.file.layout.type = PatternLayout
+appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
+rootLogger.level = INFO
+rootLogger.appenderRef.file.ref = file
diff --git a/components/camel-ai/pom.xml b/components/camel-ai/pom.xml
index db94f0489a8..23c3e3c73d9 100644
--- a/components/camel-ai/pom.xml
+++ b/components/camel-ai/pom.xml
@@ -36,6 +36,7 @@
<modules>
<module>camel-chatscript</module>
<module>camel-djl</module>
+ <module>camel-kserve</module>
<module>camel-langchain4j-chat</module>
<module>camel-langchain4j-core</module>
<module>camel-langchain4j-embeddings</module>
diff --git a/docs/components/modules/ROOT/examples/json/kserve.json
b/docs/components/modules/ROOT/examples/json/kserve.json
new file mode 120000
index 00000000000..71cc8ea13b2
--- /dev/null
+++ b/docs/components/modules/ROOT/examples/json/kserve.json
@@ -0,0 +1 @@
+../../../../../../components/camel-ai/camel-kserve/src/generated/resources/META-INF/org/apache/camel/component/kserve/kserve.json
\ No newline at end of file
diff --git a/docs/components/modules/ROOT/nav.adoc
b/docs/components/modules/ROOT/nav.adoc
index 2a726e80bfa..48c93dc972b 100644
--- a/docs/components/modules/ROOT/nav.adoc
+++ b/docs/components/modules/ROOT/nav.adoc
@@ -7,6 +7,7 @@
** xref:ai-summary.adoc[AI]
*** xref:chatscript-component.adoc[ChatScript]
*** xref:djl-component.adoc[Deep Java Library]
+*** xref:kserve-component.adoc[KServe]
*** xref:langchain4j-chat-component.adoc[LangChain4j Chat]
*** xref:langchain4j-embeddings-component.adoc[LangChain4j Embeddings]
*** xref:langchain4j-tools-component.adoc[LangChain4j Tools]
diff --git a/docs/components/modules/ROOT/pages/kserve-component.adoc
b/docs/components/modules/ROOT/pages/kserve-component.adoc
new file mode 120000
index 00000000000..f1d9609220b
--- /dev/null
+++ b/docs/components/modules/ROOT/pages/kserve-component.adoc
@@ -0,0 +1 @@
+../../../../../components/camel-ai/camel-kserve/src/main/docs/kserve-component.adoc
\ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index be164cc902f..77638a24229 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -1746,6 +1746,11 @@
<artifactId>camel-knative-http</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-kserve</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-kubernetes</artifactId>
diff --git a/pom.xml b/pom.xml
index 7a87c2f02b8..0c8fdf695d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -260,6 +260,7 @@
<exclude>**/*.event</exclude>
<exclude>**/*.gif</exclude>
<exclude>**/*.gpg</exclude>
+ <exclude>**/*.graphdef</exclude>
<exclude>**/*.graphql*</exclude>
<exclude>**/*.ics</exclude>
<exclude>**/*.joor</exclude>
@@ -276,6 +277,7 @@
<exclude>**/*.params</exclude>
<exclude>**/*.parquet</exclude>
<exclude>**/*.pb</exclude>
+ <exclude>**/*.pbtxt</exclude>
<exclude>**/*.pem</exclude>
<exclude>**/*.pfx</exclude>
<exclude>**/*.pgp</exclude>
diff --git a/test-infra/camel-test-infra-all/pom.xml
b/test-infra/camel-test-infra-all/pom.xml
index 021950e7509..d66f3db70b2 100644
--- a/test-infra/camel-test-infra-all/pom.xml
+++ b/test-infra/camel-test-infra-all/pom.xml
@@ -246,6 +246,11 @@
<artifactId>camel-test-infra-tensorflow-serving</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-triton</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
<build>
@@ -550,4 +555,4 @@
</plugins>
</build>
-</project>
\ No newline at end of file
+</project>
diff --git a/components/camel-ai/pom.xml
b/test-infra/camel-test-infra-triton/pom.xml
similarity index 50%
copy from components/camel-ai/pom.xml
copy to test-infra/camel-test-infra-triton/pom.xml
index db94f0489a8..68b88518cfe 100644
--- a/components/camel-ai/pom.xml
+++ b/test-infra/camel-test-infra-triton/pom.xml
@@ -17,37 +17,36 @@
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
- <modelVersion>4.0.0</modelVersion>
-
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
+ <artifactId>camel-test-infra-parent</artifactId>
<groupId>org.apache.camel</groupId>
- <artifactId>components</artifactId>
+ <relativePath>../camel-test-infra-parent/pom.xml</relativePath>
<version>4.10.0-SNAPSHOT</version>
</parent>
- <artifactId>camel-ai-parent</artifactId>
- <packaging>pom</packaging>
- <name>Camel :: AI :: Parent</name>
- <description>Camel AI parent</description>
-
- <modules>
- <module>camel-chatscript</module>
- <module>camel-djl</module>
- <module>camel-langchain4j-chat</module>
- <module>camel-langchain4j-core</module>
- <module>camel-langchain4j-embeddings</module>
- <module>camel-langchain4j-tokenizer</module>
- <module>camel-langchain4j-tools</module>
- <module>camel-langchain4j-web-search</module>
- <module>camel-milvus</module>
- <module>camel-pinecone</module>
- <module>camel-qdrant</module>
- <module>camel-tensorflow-serving</module>
- <module>camel-torchserve</module>
- </modules>
-
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>camel-test-infra-triton</artifactId>
+
+ <name>Camel :: Test Infra :: Triton Inference Server</name>
+ <description>Triton Inference Server test infrastructure for
Camel</description>
+
+ <properties>
+ <assembly.skipAssembly>false</assembly.skipAssembly>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-common</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <version>${testcontainers-version}</version>
+ </dependency>
+ </dependencies>
</project>
-
diff --git
a/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/common/TritonProperties.java
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/common/TritonProperties.java
new file mode 100644
index 00000000000..106d8289b10
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/common/TritonProperties.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.common;
+
+public class TritonProperties {
+ public static final String TRITON_HTTP_PORT = "triton.http.port";
+ public static final String TRITON_GPRC_PORT = "triton.grpc.port";
+ public static final String TRITON_METRICS_PORT = "triton.metrics.port";
+ public static final String TRITON_CONTAINER = "triton.container";
+
+ private TritonProperties() {
+ }
+}
diff --git
a/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonInfraService.java
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonInfraService.java
new file mode 100644
index 00000000000..b4e16901901
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonInfraService.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.services;
+
+import org.apache.camel.test.infra.common.services.InfrastructureService;
+
+public interface TritonInfraService extends InfrastructureService {
+
+ int httpPort();
+
+ int grpcPort();
+
+ int metricsPort();
+}
diff --git
a/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonLocalContainerInfraService.java
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonLocalContainerInfraService.java
new file mode 100644
index 00000000000..77a52680f68
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonLocalContainerInfraService.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.services;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import org.apache.camel.test.infra.common.services.ContainerService;
+import org.apache.camel.test.infra.triton.common.TritonProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+public class TritonLocalContainerInfraService implements TritonInfraService,
ContainerService<GenericContainer<?>> {
+ private static final Logger LOG =
LoggerFactory.getLogger(TritonLocalContainerInfraService.class);
+
+ public static final int HTTP_PORT = 8000;
+ public static final int GRPC_PORT = 8001;
+ public static final int METRICS_PORT = 8002;
+
+ private static final String CONTAINER_COMMAND = "tritonserver
--model-repository=/models";
+
+ private final GenericContainer<?> container;
+
+ public TritonLocalContainerInfraService() {
+ String imageName = LocalPropertyResolver.getProperty(
+ TritonLocalContainerInfraService.class,
+ TritonProperties.TRITON_CONTAINER);
+
+ container = initContainer(imageName);
+ }
+
+ @SuppressWarnings("resource")
+ protected GenericContainer<?> initContainer(String imageName) {
+ return new GenericContainer<>(DockerImageName.parse(imageName))
+ .withExposedPorts(HTTP_PORT, GRPC_PORT, METRICS_PORT)
+
.withCopyFileToContainer(MountableFile.forClasspathResource("models"),
"/models")
+ .waitingFor(Wait.forListeningPorts(HTTP_PORT, GRPC_PORT,
METRICS_PORT))
+ .withCommand(CONTAINER_COMMAND);
+ }
+
+ @Override
+ public void registerProperties() {
+ System.setProperty(TritonProperties.TRITON_HTTP_PORT,
String.valueOf(httpPort()));
+ System.setProperty(TritonProperties.TRITON_GPRC_PORT,
String.valueOf(grpcPort()));
+ System.setProperty(TritonProperties.TRITON_METRICS_PORT,
String.valueOf(metricsPort()));
+ }
+
+ @Override
+ public void initialize() {
+ LOG.info("Trying to start the Triton Inference Server container");
+
+ container.start();
+ registerProperties();
+
+ LOG.info("Triton Inference Server instance running at {}, {} and {}",
httpPort(), grpcPort(), metricsPort());
+ }
+
+ @Override
+ public void shutdown() {
+ LOG.info("Stopping the Triton Inference Server container");
+ container.stop();
+ }
+
+ @Override
+ public GenericContainer<?> getContainer() {
+ return container;
+ }
+
+ @Override
+ public int httpPort() {
+ return container.getMappedPort(HTTP_PORT);
+ }
+
+ @Override
+ public int grpcPort() {
+ return container.getMappedPort(GRPC_PORT);
+ }
+
+ @Override
+ public int metricsPort() {
+ return container.getMappedPort(METRICS_PORT);
+ }
+}
diff --git
a/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonRemoteInfraService.java
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonRemoteInfraService.java
new file mode 100644
index 00000000000..72c0fb49f08
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/java/org/apache/camel/test/infra/triton/services/TritonRemoteInfraService.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.services;
+
+import org.apache.camel.test.infra.triton.common.TritonProperties;
+
+public class TritonRemoteInfraService implements TritonInfraService {
+
+ @Override
+ public void registerProperties() {
+ // NO-OP
+ }
+
+ @Override
+ public void initialize() {
+ registerProperties();
+ }
+
+ @Override
+ public void shutdown() {
+ // NO-OP
+ }
+
+ @Override
+ public int httpPort() {
+ String value = System.getProperty(TritonProperties.TRITON_HTTP_PORT,
"8000");
+ return Integer.parseInt(value);
+ }
+
+ @Override
+ public int grpcPort() {
+ String value = System.getProperty(TritonProperties.TRITON_GPRC_PORT,
"8001");
+ return Integer.parseInt(value);
+ }
+
+ @Override
+ public int metricsPort() {
+ String value =
System.getProperty(TritonProperties.TRITON_METRICS_PORT, "8002");
+ return Integer.parseInt(value);
+ }
+}
diff --git
a/test-infra/camel-test-infra-triton/src/main/resources/META-INF/MANIFEST.MF
b/test-infra/camel-test-infra-triton/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..e69de29bb2d
diff --git
a/test-infra/camel-test-infra-triton/src/main/resources/models/simple/1/model.graphdef
b/test-infra/camel-test-infra-triton/src/main/resources/models/simple/1/model.graphdef
new file mode 100644
index 00000000000..d7409a44290
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/resources/models/simple/1/model.graphdef
@@ -0,0 +1,21 @@
+
+@
+INPUT0Placeholder*
+shape:���������*
+dtype0
+@
+INPUT1Placeholder*
+dtype0*
+shape:���������
+2
+ADDAddINPUT0INPUT1" /device:CPU:0*
+T0
+2
+SUBSubINPUT0INPUT1" /device:CPU:0*
+T0
+!
+OUTPUT0IdentityADD*
+T0
+!
+OUTPUT1IdentitySUB*
+T0"
\ No newline at end of file
diff --git
a/test-infra/camel-test-infra-triton/src/main/resources/models/simple/config.pbtxt
b/test-infra/camel-test-infra-triton/src/main/resources/models/simple/config.pbtxt
new file mode 100644
index 00000000000..b33ac77a514
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/resources/models/simple/config.pbtxt
@@ -0,0 +1,27 @@
+name: "simple"
+platform: "tensorflow_graphdef"
+max_batch_size: 8
+input [
+ {
+ name: "INPUT0"
+ data_type: TYPE_INT32
+ dims: [ 16 ]
+ },
+ {
+ name: "INPUT1"
+ data_type: TYPE_INT32
+ dims: [ 16 ]
+ }
+]
+output [
+ {
+ name: "OUTPUT0"
+ data_type: TYPE_INT32
+ dims: [ 16 ]
+ },
+ {
+ name: "OUTPUT1"
+ data_type: TYPE_INT32
+ dims: [ 16 ]
+ }
+]
diff --git
a/test-infra/camel-test-infra-triton/src/main/resources/org/apache/camel/test/infra/triton/services/container.properties
b/test-infra/camel-test-infra-triton/src/main/resources/org/apache/camel/test/infra/triton/services/container.properties
new file mode 100644
index 00000000000..73c0a101b80
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/main/resources/org/apache/camel/test/infra/triton/services/container.properties
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You 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
+##
+## http://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.
+## ---------------------------------------------------------------------------
+triton.container=nvcr.io/nvidia/tritonserver:24.12-py3
diff --git
a/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonService.java
b/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonService.java
new file mode 100644
index 00000000000..f0762778350
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonService.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.services;
+
+import org.apache.camel.test.infra.common.services.ContainerTestService;
+import org.apache.camel.test.infra.common.services.TestService;
+
+/**
+ * Test infra service for Triton Inference Server
+ */
+public interface TritonService extends TestService, TritonInfraService,
ContainerTestService {
+}
diff --git
a/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonServiceFactory.java
b/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonServiceFactory.java
new file mode 100644
index 00000000000..9544f917657
--- /dev/null
+++
b/test-infra/camel-test-infra-triton/src/test/java/org/apache/camel/test/infra/triton/services/TritonServiceFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.camel.test.infra.triton.services;
+
+import org.apache.camel.test.infra.common.services.SimpleTestServiceBuilder;
+
+public final class TritonServiceFactory {
+ private TritonServiceFactory() {
+ }
+
+ public static SimpleTestServiceBuilder<TritonService> builder() {
+ return new SimpleTestServiceBuilder<>("triton");
+ }
+
+ public static TritonService createService() {
+ return builder()
+ .addLocalMapping(TritonLocalContainerService::new)
+ .addRemoteMapping(TritonRemoteService::new)
+ .build();
+ }
+
+ public static class TritonLocalContainerService extends
TritonLocalContainerInfraService
+ implements TritonService {
+ }
+
+ public static class TritonRemoteService extends TritonRemoteInfraService
implements TritonService {
+ }
+}
diff --git a/test-infra/pom.xml b/test-infra/pom.xml
index 6b8e7e08d1f..b79f7c84de1 100644
--- a/test-infra/pom.xml
+++ b/test-infra/pom.xml
@@ -89,6 +89,7 @@
<module>camel-test-infra-hivemq</module>
<module>camel-test-infra-torchserve</module>
<module>camel-test-infra-tensorflow-serving</module>
+ <module>camel-test-infra-triton</module>
<module>camel-test-infra-all</module>
</modules>