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

spring-integration.mddocs/

Spring Integration

Using OpenAI-compatible model factory with Spring dependency injection.

Basic Spring Configuration

Kotlin Configuration

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import com.embabel.agent.openai.OpenAiCompatibleModelFactory
import com.embabel.common.ai.model.PricingModel
import io.micrometer.observation.ObservationRegistry
import org.springframework.beans.factory.annotation.Value
import java.time.LocalDate

@Configuration
class LlmConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun openAiModelFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun gpt4Service(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )

    @Bean
    fun embeddingService(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleEmbeddingService(
            model = "text-embedding-3-small",
            provider = "OpenAI"
        )
}

Application Properties

openai.api.key=sk-...

Or environment variable:

export OPENAI_API_KEY=sk-...

With environment variable in properties:

openai.api.key=${OPENAI_API_KEY}

Multiple Models Configuration

Configure multiple models with different characteristics:

@Configuration
class MultiModelConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun openAiModelFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean("cheapModel")
    fun cheapModel(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-3.5-turbo",
            pricingModel = PricingModel.usdPer1MTokens(0.5, 1.5),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2021, 9, 1)
        )

    @Bean("powerfulModel")
    fun powerfulModel(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )

    @Bean("latestModel")
    fun latestModel(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-5-turbo",
            pricingModel = PricingModel.usdPer1MTokens(10.0, 30.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2024, 10, 1),
            optionsConverter = Gpt5ChatOptionsConverter
        )
}

Usage in components:

@Service
class MyService(
    @Qualifier("cheapModel") private val cheapModel: LlmService<*>,
    @Qualifier("powerfulModel") private val powerfulModel: LlmService<*>
) {
    fun processSimpleTask() {
        // Use cheap model for simple tasks
        val result = cheapModel.createMessageSender(options).send(prompt)
    }

    fun processComplexTask() {
        // Use powerful model for complex tasks
        val result = powerfulModel.createMessageSender(options).send(prompt)
    }
}

Multiple Providers Configuration

Configure both OpenAI and custom providers:

@Configuration
class MultiProviderConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun openAiFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun azureFactory(
        @Value("\${azure.openai.api.key}") apiKey: String,
        @Value("\${azure.openai.base.url}") baseUrl: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = baseUrl,
            apiKey = apiKey,
            completionsPath = "/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview",
            embeddingsPath = "/openai/deployments/embeddings/embeddings?api-version=2024-02-15-preview",
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun localFactory(): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = "http://localhost:11434",
            apiKey = null,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean("openAiGpt4")
    fun openAiGpt4(openAiFactory: OpenAiCompatibleModelFactory) =
        openAiFactory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )

    @Bean("azureGpt4")
    fun azureGpt4(azureFactory: OpenAiCompatibleModelFactory) =
        azureFactory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "Azure OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )

    @Bean("localLlama")
    fun localLlama(localFactory: OpenAiCompatibleModelFactory) =
        localFactory.openAiCompatibleLlm(
            model = "llama3:70b",
            pricingModel = PricingModel.ALL_YOU_CAN_EAT,
            provider = "Ollama",
            knowledgeCutoffDate = null
        )
}

Application properties:

openai.api.key=sk-...
azure.openai.api.key=...
azure.openai.base.url=https://your-resource.openai.azure.com

Custom HTTP Client Configuration

Configure custom timeouts and HTTP behavior:

@Configuration
class CustomHttpConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun customRequestFactory(): SimpleClientHttpRequestFactory {
        return SimpleClientHttpRequestFactory().apply {
            setConnectTimeout(10000)   // 10 seconds
            setReadTimeout(300000)     // 5 minutes
        }
    }

    @Bean
    fun openAiModelFactory(
        @Value("\${openai.api.key}") apiKey: String,
        customRequestFactory: SimpleClientHttpRequestFactory
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry,
            requestFactory = ObjectProvider.of(customRequestFactory)
        )
    }

    @Bean
    fun llmService(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )
}

Custom Retry Configuration

Configure retry behavior at the service level:

@Configuration
class RetryConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun customRetryTemplate(): RetryTemplate {
        return RetryTemplate().apply {
            setBackOffPolicy(ExponentialBackOffPolicy().apply {
                initialInterval = 1000
                multiplier = 2.0
                maxInterval = 10000
            })
            setRetryPolicy(SimpleRetryPolicy(3))
        }
    }

    @Bean
    fun openAiModelFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun llmService(
        factory: OpenAiCompatibleModelFactory,
        customRetryTemplate: RetryTemplate
    ) = factory.openAiCompatibleLlm(
        model = "gpt-4",
        pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
        provider = "OpenAI",
        knowledgeCutoffDate = LocalDate.of(2023, 4, 1),
        retryTemplate = customRetryTemplate
    )
}

Profile-Based Configuration

Different configurations for different environments:

@Configuration
class ProfileBasedConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    @Profile("production")
    fun productionModelFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    @Profile("development")
    fun developmentModelFactory(): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = "http://localhost:11434",
            apiKey = null,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun llmService(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = if (isProduction()) "gpt-4" else "llama3:70b",
            pricingModel = if (isProduction())
                PricingModel.usdPer1MTokens(30.0, 60.0)
                else PricingModel.ALL_YOU_CAN_EAT,
            provider = if (isProduction()) "OpenAI" else "Ollama",
            knowledgeCutoffDate = if (isProduction())
                LocalDate.of(2023, 4, 1) else null
        )

    private fun isProduction(): Boolean {
        // Implementation depends on your needs
        return System.getenv("SPRING_PROFILES_ACTIVE") == "production"
    }
}

Java Configuration

Basic Configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.embabel.agent.openai.OpenAiCompatibleModelFactory;
import com.embabel.agent.openai.OpenAiChatOptionsConverter;
import com.embabel.common.ai.model.PricingModel;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.ObjectProviders;
import java.time.LocalDate;

@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,                      // baseUrl
            apiKey,                    // apiKey
            null,                      // completionsPath
            null,                      // embeddingsPath
            observationRegistry,       // observationRegistry
            ObjectProviders.empty()    // requestFactory
        );
    }

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

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

Multiple Models in Java

@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("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("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 Java 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);
    }
}

Conditional Bean Registration

Register beans conditionally based on properties:

@Configuration
class ConditionalConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    @ConditionalOnProperty(name = ["openai.enabled"], havingValue = "true")
    fun openAiFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    @ConditionalOnProperty(name = ["azure.openai.enabled"], havingValue = "true")
    fun azureFactory(
        @Value("\${azure.openai.api.key}") apiKey: String,
        @Value("\${azure.openai.base.url}") baseUrl: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = baseUrl,
            apiKey = apiKey,
            completionsPath = "/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview",
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }
}

Application properties:

openai.enabled=true
openai.api.key=sk-...

azure.openai.enabled=false
azure.openai.api.key=...
azure.openai.base.url=...

Integration with Spring AI Chat Memory

@Configuration
class ChatMemoryConfiguration(
    private val observationRegistry: ObservationRegistry
) {

    @Bean
    fun openAiModelFactory(
        @Value("\${openai.api.key}") apiKey: String
    ): OpenAiCompatibleModelFactory {
        return OpenAiCompatibleModelFactory(
            baseUrl = null,
            apiKey = apiKey,
            completionsPath = null,
            embeddingsPath = null,
            observationRegistry = observationRegistry
        )
    }

    @Bean
    fun llmService(factory: OpenAiCompatibleModelFactory) =
        factory.openAiCompatibleLlm(
            model = "gpt-4",
            pricingModel = PricingModel.usdPer1MTokens(30.0, 60.0),
            provider = "OpenAI",
            knowledgeCutoffDate = LocalDate.of(2023, 4, 1)
        )

    @Bean
    fun chatMemory(): ChatMemory {
        return InMemoryChatMemory()
    }

    @Bean
    fun chatClient(
        llmService: LlmService<*>,
        chatMemory: ChatMemory
    ): ChatClient {
        return ChatClient(llmService, chatMemory)
    }
}

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