This guide explains how to prepare an Apache Camel component for usage with the Forage plugin extension and how to create new bean providers and factories.
Integrating a component with Forage involves two main steps:
- Modify the Apache Camel component to support a factory-based approach
- Create the corresponding Forage bean factory and provider that can be configured through properties
First, add a factory parameter to your Camel component's configuration. This allows users to specify a factory that will create the required beans.
Example from camel-langchain4j-assistant:
@UriParam(description = "The agent factory to use for creating agents if no Agent is provided")
@Metadata(autowired = true)
private AgentFactory agentFactory;In your producer's doStart() method, initialize the factory and provide it with the Camel context:
@Override
protected void doStart() throws Exception {
super.doStart();
agentFactory = endpoint.getConfiguration().getAgentFactory();
if (agentFactory != null) {
agentFactory.setCamelContext(this.endpoint.getCamelContext());
}
}When processing exchanges, use the factory to create beans if available, falling back to direct configuration if not:
public void process(Exchange exchange) throws Exception {
// ... other processing logic
Agent agent;
if (agentFactory != null) {
agent = agentFactory.createAgent();
} else {
agent = endpoint.getConfiguration().getAgent();
}
// ... continue processing with the agent
}If you're creating an entirely new category of components (e.g., vector databases), you need to create both core and common modules:
Create a core component that contains the foundational interfaces:
Example: core/forage-vector-db
core/forage-vector-db/
├── src/main/java/io/kaoto/forage/core/vector/db/
│ ├── VectorDBFactory.java # Factory interface
│ └── VectorDBProvider.java # Provider interface
└── pom.xml
VectorDBFactory.java:
package io.kaoto.forage.core.vector.db;
import org.apache.camel.CamelContext;
public interface VectorDBFactory {
void setCamelContext(CamelContext camelContext);
VectorDatabase createVectorDB();
}VectorDBProvider.java:
package io.kaoto.forage.core.vector.db;
import io.kaoto.forage.core.common.BeanProvider;
public interface VectorDBProvider extends BeanProvider<VectorDatabase> {
// Inherits create() and create(String id) methods from BeanProvider
}Create a common component with default implementations:
Example: library/vector-dbs/forage-default-vector-db-factory
library/vector-dbs/forage-default-vector-db-factory/
├── src/main/java/io/kaoto/forage/vector/db/
│ └── DefaultVectorDBFactory.java # Default factory implementation
└── pom.xml
DefaultVectorDBFactory.java:
package io.kaoto.forage.vector.db;
import io.kaoto.forage.core.vector.db.VectorDBFactory;
import io.kaoto.forage.core.vector.db.VectorDBProvider;
import java.util.ServiceLoader;
public class DefaultVectorDBFactory implements VectorDBFactory {
private CamelContext camelContext;
@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
@Override
public VectorDatabase createVectorDB() {
ServiceLoader<VectorDBProvider> providers = ServiceLoader.load(VectorDBProvider.class);
VectorDBProvider provider = providers.findFirst()
.orElseThrow(() -> new IllegalStateException("No VectorDBProvider found"));
return provider.newVectorDB();
}
}Create specific provider implementations for each technology:
Example: library/vector-dbs/forage-milvus
library/vector-dbs/forage-milvus/
├── src/main/java/io/kaoto/forage/vector/db/milvus/
│ ├── MilvusProvider.java # Provider implementation
│ └── MilvusConfig.java # Configuration class
├── src/main/resources/META-INF/services/
│ └── io.kaoto.forage.core.vector.db.VectorDBProvider
└── pom.xml
MilvusProvider.java:
package io.kaoto.forage.vector.db.milvus;
import io.kaoto.forage.core.vector.db.VectorDBProvider;
import io.kaoto.forage.core.annotations.ForageBean;
@ForageBean(value = "milvus", components = {"camel-langchain4j-embeddings"}, description = "Milvus vector database provider")
public class MilvusProvider implements VectorDBProvider {
@Override
public VectorDatabase create(String id) {
MilvusConfig config = new MilvusConfig(id);
return MilvusClient.builder()
.host(config.host())
.port(config.port())
.username(config.username())
.password(config.password())
.build();
}
}Configuration in Forage uses a two-class pattern supporting named/prefixed configurations:
MilvusConfigEntries.java:
package io.kaoto.forage.vector.db.milvus;
import io.kaoto.forage.core.util.config.ConfigEntries;
import io.kaoto.forage.core.util.config.ConfigModule;
import io.kaoto.forage.core.util.config.ConfigTag;
public final class MilvusConfigEntries extends ConfigEntries {
public static final ConfigModule HOST = ConfigModule.of(
MilvusConfig.class, "forage.milvus.host",
"Milvus server host", "Host", "localhost", "string", false, ConfigTag.COMMON);
public static final ConfigModule PORT = ConfigModule.of(
MilvusConfig.class, "forage.milvus.port",
"Milvus server port", "Port", "19530", "integer", false, ConfigTag.COMMON);
public static final ConfigModule USERNAME = ConfigModule.of(
MilvusConfig.class, "forage.milvus.username",
"Username for authentication", "Username", null, "string", false, ConfigTag.COMMON);
public static final ConfigModule PASSWORD = ConfigModule.of(
MilvusConfig.class, "forage.milvus.password",
"Password for authentication", "Password", null, "string", true, ConfigTag.COMMON);
static {
initModules(MilvusConfigEntries.class, HOST, PORT, USERNAME, PASSWORD);
}
}MilvusConfig.java:
package io.kaoto.forage.vector.db.milvus;
import io.kaoto.forage.core.util.config.AbstractConfig;
import static io.kaoto.forage.vector.db.milvus.MilvusConfigEntries.HOST;
import static io.kaoto.forage.vector.db.milvus.MilvusConfigEntries.PORT;
import static io.kaoto.forage.vector.db.milvus.MilvusConfigEntries.USERNAME;
import static io.kaoto.forage.vector.db.milvus.MilvusConfigEntries.PASSWORD;
public class MilvusConfig extends AbstractConfig {
public MilvusConfig() {
this(null);
}
public MilvusConfig(String prefix) {
super(prefix, MilvusConfigEntries.class);
}
@Override
public String name() {
return "forage-vector-db-milvus";
}
public String host() {
return get(HOST).orElse(HOST.defaultValue());
}
public Integer port() {
return get(PORT).map(Integer::parseInt).orElse(Integer.parseInt(PORT.defaultValue()));
}
public String username() {
return get(USERNAME).orElse(null);
}
public String password() {
return get(PASSWORD).orElse(null);
}
}Create the ServiceLoader registration file:
src/main/resources/META-INF/services/io.kaoto.forage.core.vector.db.VectorDBProvider:
io.kaoto.forage.vector.db.milvus.MilvusProvider
Provider module pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>io.kaoto.forage</groupId>
<artifactId>vector-dbs</artifactId>
<version>1.1-SNAPSHOT</version>
</parent>
<artifactId>forage-vector-db-milvus</artifactId>
<name>Forage :: Library :: Vector DBs :: Milvus</name>
<dependencies>
<dependency>
<groupId>io.kaoto.forage</groupId>
<artifactId>forage-core-vector-db</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.kaoto.forage</groupId>
<artifactId>forage-core-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.4</version>
</dependency>
</dependencies>
</project>Reference the factory class in your Camel route:
from("direct:vector-search")
.to("vector-db:search?connectionFactory=#class:io.kaoto.forage.vector.db.DefaultVectorDBFactory");Make sure to include the specific provider on your classpath:
<dependency>
<groupId>io.kaoto.forage</groupId>
<artifactId>forage-vector-db-milvus</artifactId>
<version>1.1-SNAPSHOT</version>
</dependency>Configure using environment variables:
export FORAGE_MILVUS_HOST="milvus.example.com"
export FORAGE_MILVUS_PORT="19530"
export FORAGE_MILVUS_USERNAME="admin"
export FORAGE_MILVUS_PASSWORD="password123"Or using system properties:
-Dforage.milvus.host=milvus.example.com
-Dforage.milvus.port=19530
-Dforage.milvus.username=admin
-Dforage.milvus.password=password123Or using configuration files (forage-vector-db-milvus.properties):
forage.milvus.host=milvus.example.com
forage.milvus.port=19530
forage.milvus.username=admin
forage.milvus.password=password123For multi-instance setups using named configurations:
# Environment variables (prefix becomes uppercase)
export FORAGE_MILVUS_HOST="default.milvus.com" # Default config
export forage.vectorstore.milvus.host="vs.milvus.com" # "vectorstore" config
export forage.embeddings.milvus.host="emb.milvus.com" # "embeddings" config
# System properties
-Dforage.milvus.host=default.milvus.com # Default config
-Dforage.vectorstore.milvus.host=vs.milvus.com # "vectorstore" config
-Dforage.embeddings.milvus.host=emb.milvus.com # "embeddings" configIn your provider code:
@ForageBean(value = "milvus-multi", components = {"camel-langchain4j-embeddings"}, description = "Multi-instance Milvus vector database provider")
public class MultiInstanceMilvusProvider implements VectorDBProvider {
@Override
public VectorDatabase create(String id) {
// Use named configuration with the provided id
MilvusConfig config = new MilvusConfig(id);
return MilvusClient.builder()
.host(config.host())
.port(config.port())
.build();
}
}MANDATORY: All provider classes must be annotated with @ForageBean:
@ForageBean(value = "unique-name", components = {"supported-component"}, description = "What it does")
public class YourProvider implements ProviderInterface {
// Implementation
}Annotation Parameters:
value: Unique identifier for the bean (e.g., "milvus", "azure-openai", "message-window")components: Supported Camel components (String array)description: Human-readable description of what the provider doesfeature: Feature group (e.g., "Chat Model", "Memory") — optionalconfigClass: Config class for properties file association — optional
Required Import:
import io.kaoto.forage.core.annotations.ForageBean;MANDATORY: All factory classes must be annotated with @ForageFactory:
@ForageFactory(
value = "unique-factory-name",
components = {"supported-component"},
description = "What the factory creates",
type = FactoryType.AGENT)
public class YourFactory implements FactoryInterface {
// Implementation
}The annotation parameters:
value: Unique identifier for the factory (e.g., "default-agent", "multi-agent", "redis-memory")components: Supported Camel components (String array)description: Human-readable description of what the factory createstype: Type of objects this factory creates (enumFactoryType, e.g.,FactoryType.AGENT)
Required Import:
import io.kaoto.forage.core.annotations.ForageFactory;- Always follow the Forage two-class configuration pattern with
ConfigandConfigEntriesclasses - Create a
*ConfigEntriesclass extendingConfigEntrieswith static fields and maps - Support both default and named/prefixed configurations in the main
Configclass - Use meaningful environment variable names with a consistent prefix (e.g.,
FORAGE_MILVUS_*) - Use dot-notation for ConfigModule names (e.g.,
forage.milvus.host) - Provide sensible defaults where appropriate
- Document all configuration parameters comprehensively including named configuration examples
BeanProvider Pattern:
- All provider interfaces must extend
BeanProvider<T>for consistency - Implement the
create(String id)method to support named configurations - Use the provided
idparameter to create named configurations:new Config(id) - The
create()method is provided automatically and callscreate(null)for default configuration
- Don't forget to create the ServiceLoader resource file
- Use the full interface name as the filename
- List all implementations in the file
- Provide clear error messages when configuration is missing
- Use
MissingConfigExceptionfor required parameters - Validate configuration values where appropriate
- Create unit tests for your provider and configuration classes
- Test the ServiceLoader discovery mechanism
- Verify that configuration sources work correctly
- Add comprehensive Javadoc to all public classes and methods
- Include usage examples in class-level documentation
- Document configuration precedence clearly
Here's how your final module structure should look:
forage/
├── core/
│ └── forage-core-vector-db/ # Core interfaces
├── library/
│ └── vector-dbs/
│ ├── forage-default-vector-db-factory/ # Common factory
│ ├── forage-vector-db-milvus/ # Milvus implementation
│ ├── forage-vector-db-pinecone/ # Pinecone implementation
│ └── forage-vector-db-weaviate/ # Weaviate implementation
└── docs/
└── contributing-beans.md # This guide
This structure allows for easy extension with new vector database providers while maintaining a consistent API and configuration approach across all implementations.