Artículo

Configuración de Múltiples LLMs en Spring AI

Configuración de Múltiples LLMs en Spring AI

1. Visión general

Las aplicaciones modernas están integrando cada vez más Large Language Models (LLMs) para construir soluciones inteligentes. Si bien un solo LLM puede manejar múltiples tareas, depender únicamente de un modelo no siempre es el enfoque óptimo.

Los modelos diferentes se especializan en distintas capacidades, algunos sobresalen en análisis técnico mientras otros se desempeñan mejor en redacción creativa. Además, podríamos preferir modelos ligeros y rentables para tareas simples, reservando modelos potentes para tareas complejas.

En este tutorial, exploraremos cómo integrar múltiples LLMs en una aplicación Spring Boot usando Spring AI.

Configuraremos modelos de diferentes proveedores, así como múltiples modelos del mismo proveedor. Luego, construiremos sobre esta configuración para implementar un chatbot resiliente capaz de cambiar automáticamente entre modelos cuando ocurran fallas.

2. Configuración de LLMs de proveedores diferentes

Empecemos configurando dos LLMs de diferentes proveedores en nuestra aplicación.

Para nuestra demostración, usaremos OpenAI y Anthropic como proveedores de modelos de IA.

2.1. Configuración de un LLM primario

Comenzaremos configurando un modelo OpenAI como nuestro LLM primario.

Primero, agreguemos la dependencia necesaria al archivo pom.xml de nuestro proyecto:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.2</version>
</dependency>

La dependencia starter de OpenAI es un envoltorio alrededor de la API de Chat Completions de OpenAI, lo que nos permite interactuar con modelos OpenAI en nuestra aplicación.

A continuación, configuremos nuestra clave API de OpenAI y nuestro modelo de chat en el archivo application.yaml:

spring:
  ai:
    open-ai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: ${PRIMARY_LLM}
          temperature: 1

Utilizamos el marcador de posición de propiedad ${} para cargar los valores de nuestras propiedades desde variables de entorno. Además, establecemos la temperatura a 1 ya que los modelos más recientes de OpenAI solo aceptan este valor por defecto.

Al configurar las propiedades anteriores, Spring AI crea automáticamente un bean de tipo OpenAiChatModel. Utilicémoslo para definir un bean ChatClient, que sirve como punto de entrada principal para interactuar con nuestro LLM:

@Configuration
class ChatbotConfiguration {

    @Bean
    @Primary
    ChatClient primaryChatClient(OpenAiChatModel chatModel) {
        return ChatClient.create(chatModel);
    }
}

En nuestra clase ChatbotConfiguration, utilizamos el bean OpenAiChatModel para crear un bean ChatClient para nuestro LLM primario.

Anotamos este bean con @Primary. Spring Boot lo inyectará automáticamente en nuestros componentes cuando autoconectemos un bean ChatClient sin usar un calificador.

2.2. Configuración de un LLM secundario

Ahora, configuremos un modelo de Anthropic para actuar como nuestro LLM secundario.

Primero, agreguemos la dependencia starter de Anthropic a nuestro archivo pom.xml:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
    <version>1.0.2</version>
</dependency>

Esta dependencia es un envoltorio alrededor de la API de Mensajes de Anthropic y nos proporciona las clases necesarias para establecer una conexión e interactuar con modelos Anthropic.

A continuación, definamos las propiedades de configuración para nuestro modelo secundario:

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: ${SECONDARY_LLM}

Al igual que en la configuración del LLM primario, cargamos la clave API de Anthropic y el ID del modelo desde variables de entorno.

Finalmente, creemos un bean ChatClient dedicado para nuestro modelo secundario:

@Bean
ChatClient secondaryChatClient(AnthropicChatModel chatModel) {
    return ChatClient.create(chatModel);
}

Aquí, creamos un bean secondaryChatClient usando el bean AnthropicChatModel que Spring AI auto-configura por nosotros.

3. Configuración de LLMs del mismo proveedor

Con frecuencia, los LLMs que necesitamos configurar pueden pertenecer al mismo proveedor de IA.

Spring AI no admite nativamente este escenario, ya que la auto-configuración crea solo un bean ChatModel por proveedor. Tendremos que definir manualmente un bean ChatModel para el modelo adicional(s).

Exploremos este proceso y configuraremos un segundo modelo Anthropic en nuestra aplicación:

spring:
  ai:
    anthropic:
      chat:
        options:
          tertiary-model: ${TERTIARY_LLM}

En nuestro application.yaml, bajo la configuración de Anthropic, hemos añadido una propiedad personalizada para contener el nombre del modelo del LLM terciario.

A continuación, definamos los beans necesarios para nuestro LLM terciario:

@Bean
ChatModel tertiaryChatModel(
    AnthropicApi anthropicApi,
    AnthropicChatModel anthropicChatModel,
    @Value("${spring.ai.anthropic.chat.options.tertiary-model}") String tertiaryModelName
) {
    AnthropicChatOptions chatOptions = anthropicChatModel.getDefaultOptions().copy();
    chatOptions.setModel(tertiaryModelName);
    return AnthropicChatModel.builder()
      .anthropicApi(anthropicApi)
      .defaultOptions(chatOptions)
      .build();
}

@Bean
ChatClient tertiaryChatClient(@Qualifier("tertiaryChatModel") ChatModel tertiaryChatModel) {
    return ChatClient.create(tertiaryChatModel);
}

Primero, para crear nuestro bean ChatModel personalizado, inyectamos el bean AnthropicApi auto-configurado, el bean AnthropicChatModel predeterminado que usamos para crear el ChatClient de nuestro LLM secundario, y la propiedad del nombre del modelo terciario usando @Value.

Copiamos las opciones predeterminadas del bean AnthropicChatModel existente y simplemente sobrescribimos el nombre del modelo.

Esta configuración asume que ambos modelos Anthropic comparten la misma clave API y otras configuraciones. Si necesitamos especificar propiedades diferentes, podemos personalizar las AnthropicChatOptions aún más.

Finalmente, utilizamos el tertiaryChatModel personalizado para crear un tercer bean ChatClient en nuestra clase de configuración.

4. Exploración de un caso de uso práctico

Con nuestra configuración de múltiples modelos en su lugar, implementemos un caso de uso práctico. Construiremos un chatbot resiliente que vuelva automáticamente al modelo alternativo en secuencia cuando el modelo principal falle.

4.1. Construyendo un chatbot resiliente

Para implementar la lógica de retroceso, utilizaremos Spring Retry.

Creemos una nueva clase ChatbotService y autoconectemos los tres beans ChatClient que hemos definido. Luego, definamos el punto de entrada para nuestro chatbot que use el LLM primario:

@Retryable(retryFor = Exception.class, maxAttempts = 3)
String chat(String prompt) {
    logger.debug("Attempting to process prompt '{}' with primary LLM. Attempt #{}",
        prompt, RetrySynchronizationManager.getContext().getRetryCount() + 1);
    return primaryChatClient
      .prompt(prompt)
      .call()
      .content();
}

Aquí, **creamos un método chat() que usa el bean primaryChatClient. Anotamos este método con @Retryable, configurándolo para hacer hasta tres intentos en caso de cualquier Exception.

A continuación, definamos un método de recuperación:

@Recover
String chat(Exception exception, String prompt) {
    logger.warn("Primary LLM failure. Error received: {}", exception.getMessage());
    logger.debug("Attempting to process prompt '{}' with secondary LLM", prompt);
    try {
        return secondaryChatClient
          .prompt(prompt)
          .call()
          .content();
    } catch (Exception e) {
        logger.warn("Secondary LLM failure: {}", e.getMessage());
        logger.debug("Attempting to process prompt '{}' with tertiary LLM", prompt);
        return tertiaryChatClient
          .prompt(prompt)
          .call()
          .content();
    }
}

La anotación @Recover marca nuestro método chat() sobrecargado como la alternativa si el método original chat() falla y agota los reintentos configurados.

Primero intentamos obtener una respuesta del secondaryChatClient. Si también falla, hacemos un último intento con el bean tertiaryChatClient.

Utilizamos esta implementación rudimentaria de try-catch ya que Spring Retry solo permite un método de recuperación por firma de método. Sin embargo, en una aplicación de producción, deberíamos considerar una solución más sofisticada como Resilience4j.

Ahora que hemos implementado nuestra capa de servicio, exponemos una API REST encima de ella:

@PostMapping("/api/chatbot/chat")
ChatResponse chat(@RequestBody ChatRequest request) {
    String response = chatbotService.chat(request.prompt);
    return new ChatResponse(response);
}

record ChatRequest(String prompt) {}
record ChatResponse(String response) {}

Aquí, definimos un POST /api/chatbot/chat, que acepta un prompt, lo pasa a la capa de servicio y finalmente envuelve y devuelve la response en un record ChatResponse.

4.2. Probando nuestro chatbot

Finalmente, probemos nuestro chatbot para verificar que el mecanismo de retroceso funciona correctamente.

Iniciemos nuestra aplicación con variables de entorno que especifiquen nombres de modelo inválidos para nuestros LLMs primario y secundario, pero un nombre válido para el LLM terciario:

OPENAI_API_KEY=.... \
ANTHROPIC_API_KEY=.... \
PRIMARY_LLM=gpt-100 \
SECONDARY_LLM=claude-opus-200 \
TERTIARY_LLM=claude-3-haiku-20240307 \
mvn spring-boot:run

En nuestro comando, gpt-100 y claude-opus-200 son nombres de modelo inválidos que causarán errores de API, mientras que claude-3-haiku-20240307 es un modelo válido de Anthropic.

A continuación, usemos la CLI de HTTPie para invocar el endpoint de la API e interactuar con nuestro chatbot:

http POST :8080/api/chatbot/chat prompt="What is the capital of France?"

Aquí, enviamos un prompt simple al chatbot, veamos qué recibimos como respuesta:

{
    "response": "The capital of France is Paris."
}

Como podemos ver, a pesar de configurar LLMs primario y secundario inválidos, nuestro chatbot sigue proporcionando una respuesta correcta, confirmando que el sistema cae de vuelta al LLM terciario.

Para ver la lógica de retroceso en acción, examinemos también los registros de nuestra aplicación:

[2025-09-30 12:56:03] [DEBUG] [dev.codeja.multillm.ChatbotService] - Attempting to process prompt 'What is the capital of France?' with primary LLM. Attempt #1
[2025-09-30 12:56:05] [DEBUG] [dev.codeja.multillm.ChatbotService] - Attempting to process prompt 'What is the capital of France?' with primary LLM. Attempt #2
[2025-09-30 12:56:06] [DEBUG] [dev.codeja.multillm.ChatbotService] - Attempting to process prompt 'What is the capital of France?' with primary LLM. Attempt #3
[2025-09-30 12:56:07] [WARN] [dev.codeja.multillm.ChatbotService] - Primary LLM failure. Error received: HTTP 404 - {
    "error": {
        "message": "The model `gpt-100` does not exist or you do not have access to it.",
        "type": "invalid_request_error",
        "param": null,
        "code": "model_not_found"
    }
}
[2025-09-30 12:56:07] [DEBUG] [dev.codeja.multillm.ChatbotService] - Attempting to process prompt 'What is the capital of France?' with secondary LLM
[2025-09-30 12:56:07] [WARN] [dev.codeja.multillm.ChatbotService] - Secondary LLM failure: HTTP 404 - {"type":"error","error":{"type":"not_found_error","message":"model: claude-opus-200"},"request_id":"req_011CTeBrAY8rstsSPiJyv3sj"}
[2025-09-30 12:56:07] [DEBUG] [dev.codeja.multillm.ChatbotService] - Attempting to process prompt 'What is the capital of France?' with tertiary LLM

Los registros ilustran claramente el flujo de ejecución de nuestra solicitud.

Vemos tres intentos fallidos con el LLM primario. Luego, nuestro servicio intenta usar el LLM secundario, que también falla. Finalmente, invoca el LLM terciario que procesa el prompt y devuelve la respuesta que vimos.

Esto demuestra que nuestro mecanismo de retroceso funciona exactamente como se diseñó, asegurando que nuestro chatbot permanezca disponible incluso cuando varios LLMs fallen.

5. Conclusión

En este artículo, hemos explorado cómo integrar múltiples LLMs dentro de una sola aplicación Spring AI.

Primero, demostramos cómo la capa de abstracción de Spring AI simplifica la configuración de modelos de diferentes proveedores como OpenAI y Anthropic.

Luego, abordamos un escenario más complejo de configurar múltiples modelos del mismo proveedor, creando beans personalizados cuando la auto-configuración de Spring AI no es suficiente.

Finalmente, utilizamos esta configuración multi-modelo para construir un chatbot resiliente y altamente disponible. Usando Spring Retry, configuramos un patrón de retroceso en cascada que cambia automáticamente entre LLMs en caso de fallo.

Newsletter Semanal de Java

Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.

Sin spam. Cancela cuando quieras.

Compartir artículo