OpenAI compatible model factory for the Embabel Agent Framework
Using OpenAI-compatible model factory with Spring dependency injection.
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"
)
}openai.api.key=sk-...Or environment variable:
export OPENAI_API_KEY=sk-...With environment variable in properties:
openai.api.key=${OPENAI_API_KEY}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)
}
}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.comConfigure 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)
)
}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
)
}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"
}
}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
);
}
}@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
);
}
}@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);
}
}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=...@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@0.3.0