CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-openai

OpenAI compatible model factory for the Embabel Agent Framework

Overview
Eval results
Files

java-usage.mddocs/

Java Usage

Java-specific examples and patterns for using Embabel Agent OpenAI.

Core Imports

import com.embabel.agent.openai.OpenAiCompatibleModelFactory;
import com.embabel.agent.openai.OpenAiChatOptionsConverter;
import com.embabel.agent.openai.Gpt5ChatOptionsConverter;
import com.embabel.agent.openai.StandardOpenAiOptionsConverter;
import com.embabel.common.ai.model.PricingModel;
import com.embabel.agent.api.LlmService;
import com.embabel.common.ai.embedding.EmbeddingService;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.ObjectProviders;
import org.springframework.retry.support.RetryTemplate;
import java.time.LocalDate;

Creating a Factory

Basic Factory

import io.micrometer.observation.ObservationRegistry;

ObservationRegistry observationRegistry = ObservationRegistry.create();

OpenAiCompatibleModelFactory factory = new OpenAiCompatibleModelFactory(
    null,                      // baseUrl - null for OpenAI default
    "sk-...",                  // apiKey
    null,                      // completionsPath
    null,                      // embeddingsPath
    observationRegistry,       // observationRegistry
    ObjectProviders.empty()    // requestFactory - empty for default
);

Custom Provider

OpenAiCompatibleModelFactory customFactory = new OpenAiCompatibleModelFactory(
    "http://localhost:8000",   // baseUrl
    null,                      // apiKey - null if no auth
    "/v1/chat/completions",    // completionsPath
    "/v1/embeddings",          // embeddingsPath
    observationRegistry,
    ObjectProviders.empty()
);

Creating LLM Services

Basic LLM Service

LlmService<?> gpt4Service = factory.openAiCompatibleLlm(
    "gpt-4",                                      // model
    PricingModel.usdPer1MTokens(30.0, 60.0),     // pricingModel
    "OpenAI",                                     // provider
    LocalDate.of(2023, 4, 1),                    // knowledgeCutoffDate
    OpenAiChatOptionsConverter.INSTANCE,         // optionsConverter - note .INSTANCE
    RetryUtils.DEFAULT_RETRY_TEMPLATE            // retryTemplate
);

Important: Kotlin singleton objects require .INSTANCE in Java.

Using Default Options Converter

// OpenAiChatOptionsConverter is the default
LlmService<?> service = factory.openAiCompatibleLlm(
    "gpt-3.5-turbo",
    PricingModel.usdPer1MTokens(0.5, 1.5),
    "OpenAI",
    LocalDate.of(2021, 9, 1),
    OpenAiChatOptionsConverter.INSTANCE,  // Explicitly pass default
    RetryUtils.DEFAULT_RETRY_TEMPLATE
);

GPT-5 Service

LlmService<?> gpt5Service = factory.openAiCompatibleLlm(
    "gpt-5-turbo",
    PricingModel.usdPer1MTokens(10.0, 30.0),
    "OpenAI",
    LocalDate.of(2024, 10, 1),
    Gpt5ChatOptionsConverter.INSTANCE,    // Special converter for GPT-5
    RetryUtils.DEFAULT_RETRY_TEMPLATE
);

With Standard Converter

LlmService<?> service = factory.openAiCompatibleLlm(
    "gpt-4",
    PricingModel.usdPer1MTokens(30.0, 60.0),
    "OpenAI",
    LocalDate.of(2023, 4, 1),
    StandardOpenAiOptionsConverter.INSTANCE,  // Explicit parameter support
    RetryUtils.DEFAULT_RETRY_TEMPLATE
);

Creating Embedding Services

EmbeddingService embeddingService = factory.openAiCompatibleEmbeddingService(
    "text-embedding-3-small",  // model
    "OpenAI"                    // provider
);

Spring Configuration

Basic Configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;

@Configuration
public class LlmConfiguration {

    private final ObservationRegistry observationRegistry;

    public LlmConfiguration(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @Bean
    public OpenAiCompatibleModelFactory openAiModelFactory(
            @Value("${openai.api.key}") String apiKey) {
        return new OpenAiCompatibleModelFactory(
            null,
            apiKey,
            null,
            null,
            observationRegistry,
            ObjectProviders.empty()
        );
    }

    @Bean
    public LlmService<?> gpt4Service(OpenAiCompatibleModelFactory factory) {
        return factory.openAiCompatibleLlm(
            "gpt-4",
            PricingModel.usdPer1MTokens(30.0, 60.0),
            "OpenAI",
            LocalDate.of(2023, 4, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }

    @Bean
    public EmbeddingService embeddingService(OpenAiCompatibleModelFactory factory) {
        return factory.openAiCompatibleEmbeddingService(
            "text-embedding-3-small",
            "OpenAI"
        );
    }
}

Multiple Models

@Configuration
public class MultiModelConfiguration {

    private final ObservationRegistry observationRegistry;

    public MultiModelConfiguration(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @Bean
    public OpenAiCompatibleModelFactory openAiModelFactory(
            @Value("${openai.api.key}") String apiKey) {
        return new OpenAiCompatibleModelFactory(
            null, apiKey, null, null,
            observationRegistry, ObjectProviders.empty()
        );
    }

    @Bean(name = "cheapModel")
    public LlmService<?> cheapModel(OpenAiCompatibleModelFactory factory) {
        return factory.openAiCompatibleLlm(
            "gpt-3.5-turbo",
            PricingModel.usdPer1MTokens(0.5, 1.5),
            "OpenAI",
            LocalDate.of(2021, 9, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }

    @Bean(name = "powerfulModel")
    public LlmService<?> powerfulModel(OpenAiCompatibleModelFactory factory) {
        return factory.openAiCompatibleLlm(
            "gpt-4",
            PricingModel.usdPer1MTokens(30.0, 60.0),
            "OpenAI",
            LocalDate.of(2023, 4, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }
}

Using in Service

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final LlmService<?> cheapModel;
    private final LlmService<?> powerfulModel;

    public MyService(
            @Qualifier("cheapModel") LlmService<?> cheapModel,
            @Qualifier("powerfulModel") LlmService<?> powerfulModel) {
        this.cheapModel = cheapModel;
        this.powerfulModel = powerfulModel;
    }

    public void processSimpleTask() {
        // Use cheap model
        var result = cheapModel.createMessageSender(options).send(prompt);
    }

    public void processComplexTask() {
        // Use powerful model
        var result = powerfulModel.createMessageSender(options).send(prompt);
    }
}

Custom Timeouts

import org.springframework.http.client.SimpleClientHttpRequestFactory;

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(10000);   // 10 seconds
requestFactory.setReadTimeout(300000);     // 5 minutes

OpenAiCompatibleModelFactory factory = new OpenAiCompatibleModelFactory(
    null,
    "your-api-key",
    null,
    null,
    observationRegistry,
    ObjectProvider.of(requestFactory)  // Custom request factory
);

Custom Retry Configuration

import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;

// Create custom retry template
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2.0);
backOffPolicy.setMaxInterval(10000);

SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3);  // 3 retries

RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.setRetryPolicy(retryPolicy);

// Use custom retry template
LlmService<?> service = factory.openAiCompatibleLlm(
    "gpt-4",
    PricingModel.usdPer1MTokens(30.0, 60.0),
    "OpenAI",
    LocalDate.of(2023, 4, 1),
    OpenAiChatOptionsConverter.INSTANCE,
    retryTemplate  // Custom retry configuration
);

Azure OpenAI

OpenAiCompatibleModelFactory azureFactory = new OpenAiCompatibleModelFactory(
    "https://your-resource.openai.azure.com",
    System.getenv("AZURE_OPENAI_API_KEY"),
    "/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview",
    "/openai/deployments/embeddings/embeddings?api-version=2024-02-15-preview",
    observationRegistry,
    ObjectProviders.empty()
);

LlmService<?> azureService = azureFactory.openAiCompatibleLlm(
    "gpt-4",
    PricingModel.usdPer1MTokens(30.0, 60.0),
    "Azure OpenAI",
    LocalDate.of(2023, 4, 1),
    OpenAiChatOptionsConverter.INSTANCE,
    RetryUtils.DEFAULT_RETRY_TEMPLATE
);

Local Models (Ollama)

OpenAiCompatibleModelFactory localFactory = new OpenAiCompatibleModelFactory(
    "http://localhost:11434",
    null,  // No API key
    null,
    null,
    observationRegistry,
    ObjectProviders.empty()
);

LlmService<?> localService = localFactory.openAiCompatibleLlm(
    "llama3:70b",
    PricingModel.ALL_YOU_CAN_EAT,
    "Ollama",
    null,  // No knowledge cutoff date
    OpenAiChatOptionsConverter.INSTANCE,
    RetryUtils.DEFAULT_RETRY_TEMPLATE
);

Reading from Environment Variables

OpenAiCompatibleModelFactory factory = new OpenAiCompatibleModelFactory(
    System.getenv("OPENAI_BASE_URL"),      // Can be null
    System.getenv("OPENAI_API_KEY"),
    null,
    null,
    observationRegistry,
    ObjectProviders.empty()
);

Pricing Models

Per-Token Pricing

PricingModel gpt4Pricing = PricingModel.usdPer1MTokens(30.0, 60.0);
PricingModel gpt35Pricing = PricingModel.usdPer1MTokens(0.5, 1.5);

All-You-Can-Eat Pricing

PricingModel freePricing = PricingModel.ALL_YOU_CAN_EAT;

Complete Example

import com.embabel.agent.openai.OpenAiCompatibleModelFactory;
import com.embabel.agent.openai.OpenAiChatOptionsConverter;
import com.embabel.common.ai.model.PricingModel;
import com.embabel.agent.api.LlmService;
import com.embabel.common.ai.embedding.EmbeddingService;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProviders;
import java.time.LocalDate;

public class OpenAiExample {

    public static void main(String[] args) {
        // Create observation registry
        ObservationRegistry observationRegistry = ObservationRegistry.create();

        // Create factory
        OpenAiCompatibleModelFactory factory = new OpenAiCompatibleModelFactory(
            null,
            System.getenv("OPENAI_API_KEY"),
            null,
            null,
            observationRegistry,
            ObjectProviders.empty()
        );

        // Create LLM service
        LlmService<?> llmService = factory.openAiCompatibleLlm(
            "gpt-4",
            PricingModel.usdPer1MTokens(30.0, 60.0),
            "OpenAI",
            LocalDate.of(2023, 4, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );

        // Create embedding service
        EmbeddingService embeddingService = factory.openAiCompatibleEmbeddingService(
            "text-embedding-3-small",
            "OpenAI"
        );

        // Use services...
        System.out.println("Services created successfully!");
    }
}

Extending in Java

import com.embabel.agent.openai.OpenAiCompatibleModelFactory;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.http.client.ClientHttpRequestFactory;

public class CustomOpenAiFactory extends OpenAiCompatibleModelFactory {

    public CustomOpenAiFactory(
            String baseUrl,
            String apiKey,
            String completionsPath,
            String embeddingsPath,
            ObservationRegistry observationRegistry,
            ObjectProvider<ClientHttpRequestFactory> requestFactory) {
        super(baseUrl, apiKey, completionsPath, embeddingsPath,
              observationRegistry, requestFactory);

        // Access protected logger
        logger.info("Custom factory initialized");
    }

    // Add custom methods
    public LlmService<?> createGpt4() {
        return openAiCompatibleLlm(
            "gpt-4",
            PricingModel.usdPer1MTokens(30.0, 60.0),
            "OpenAI",
            LocalDate.of(2023, 4, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }

    public LlmService<?> createGpt35Turbo() {
        return openAiCompatibleLlm(
            "gpt-3.5-turbo",
            PricingModel.usdPer1MTokens(0.5, 1.5),
            "OpenAI",
            LocalDate.of(2021, 9, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }
}

Common Patterns in Java

Factory Method Pattern

public class LlmServiceFactory {

    private final OpenAiCompatibleModelFactory factory;

    public LlmServiceFactory(OpenAiCompatibleModelFactory factory) {
        this.factory = factory;
    }

    public LlmService<?> createCheapModel() {
        return factory.openAiCompatibleLlm(
            "gpt-3.5-turbo",
            PricingModel.usdPer1MTokens(0.5, 1.5),
            "OpenAI",
            LocalDate.of(2021, 9, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }

    public LlmService<?> createPowerfulModel() {
        return factory.openAiCompatibleLlm(
            "gpt-4",
            PricingModel.usdPer1MTokens(30.0, 60.0),
            "OpenAI",
            LocalDate.of(2023, 4, 1),
            OpenAiChatOptionsConverter.INSTANCE,
            RetryUtils.DEFAULT_RETRY_TEMPLATE
        );
    }
}

Builder Pattern Wrapper

public class LlmServiceBuilder {

    private final OpenAiCompatibleModelFactory factory;
    private String model = "gpt-4";
    private PricingModel pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0);
    private String provider = "OpenAI";
    private LocalDate knowledgeCutoffDate = LocalDate.of(2023, 4, 1);
    private OptionsConverter<?> optionsConverter = OpenAiChatOptionsConverter.INSTANCE;
    private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE;

    public LlmServiceBuilder(OpenAiCompatibleModelFactory factory) {
        this.factory = factory;
    }

    public LlmServiceBuilder model(String model) {
        this.model = model;
        return this;
    }

    public LlmServiceBuilder pricingModel(PricingModel pricingModel) {
        this.pricingModel = pricingModel;
        return this;
    }

    public LlmServiceBuilder provider(String provider) {
        this.provider = provider;
        return this;
    }

    public LlmServiceBuilder knowledgeCutoffDate(LocalDate date) {
        this.knowledgeCutoffDate = date;
        return this;
    }

    public LlmServiceBuilder optionsConverter(OptionsConverter<?> converter) {
        this.optionsConverter = converter;
        return this;
    }

    public LlmServiceBuilder retryTemplate(RetryTemplate template) {
        this.retryTemplate = template;
        return this;
    }

    public LlmService<?> build() {
        return factory.openAiCompatibleLlm(
            model,
            pricingModel,
            provider,
            knowledgeCutoffDate,
            optionsConverter,
            retryTemplate
        );
    }
}

// Usage
LlmService<?> service = new LlmServiceBuilder(factory)
    .model("gpt-4-turbo")
    .pricingModel(PricingModel.usdPer1MTokens(10.0, 30.0))
    .knowledgeCutoffDate(LocalDate.of(2023, 12, 1))
    .build();

Key Differences from Kotlin

  1. Singleton Objects: Use .INSTANCE to access Kotlin singleton objects:

    OpenAiChatOptionsConverter.INSTANCE
    Gpt5ChatOptionsConverter.INSTANCE
    StandardOpenAiOptionsConverter.INSTANCE
  2. Named Parameters: Java doesn't have named parameters, so pass arguments in order

  3. Nullable Types: Java uses null where Kotlin uses null for nullable types

  4. Default Arguments: Java doesn't have default arguments, so you must pass all parameters explicitly

  5. ObjectProvider: Use ObjectProviders.empty() for empty providers:

    ObjectProviders.empty()
  6. Object Instantiation: Use ObjectProvider.of(instance) to wrap instances:

    ObjectProvider.of(requestFactory)

Install with Tessl CLI

npx tessl i tessl/maven-com-embabel-agent--embabel-agent-openai

docs

api-reference.md

configuration.md

extending.md

index.md

java-usage.md

options-converters.md

quickstart.md

spring-integration.md

use-cases.md

tile.json