Artículo

Usando los modelos Claude de Anthropic con Spring AI

Usando los modelos Claude de Anthropic con Spring AI

1. Visión general

Las aplicaciones web modernas están integrándose cada vez más con Modelos de Lenguaje Extensos (LLMs) para crear soluciones.

Anthropic es una empresa líder en investigación de IA que desarrolla modelos de lenguaje extensos potentes, con su familia de modelos Claude sobresaliendo en razonamiento y análisis.

En este tutorial, exploraremos cómo usar los modelos Claude de Anthropic con Spring AI. Construiremos un chatbot sencillo, capaz de comprender entradas textuales y visuales y participar en conversaciones de múltiples turnos.

Para seguir este tutorial, necesitaremos ya sea una Clave de API de Anthropic o una cuenta AWS activa.

2. Dependencias y Configuración

Antes de poder implementar nuestro chatbot, necesitaremos incluir la dependencia necesaria y configurar nuestra aplicación correctamente.

2.1. API de Anthropic

Comencemos añadiendo la dependencia necesaria al archivo pom.xml de nuestro proyecto:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version>
</dependency>

La dependencia starter de Anthropic es un envoltorio alrededor de la API de Mensajes de Anthropic, y la usaremos para interactuar con los modelos Claude en nuestra aplicación.

Dado que la versión actual, 1.0.0-M6, es una versión de hito, también necesitaremos agregar el repositorio de Spring Milestones a nuestro pom.xml:

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Este repositorio es donde se publican versiones de hito, en contraste con el repositorio central de Maven estándar.

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

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-3-5-sonnet-20241022

Utilizamos el marcador de posición de propiedad ${} para cargar el valor de nuestra clave API desde una variable de entorno.

Además, especificamos Claude 3.5 Sonnet, el modelo más inteligente de Anthropic, utilizando el ID de modelo claude-3-5-sonnet-20241022. Siéntase libre de explorar y usar un modelo diferente según los requisitos.

Al configurar las propiedades anteriores, Spring AI crea automáticamente un bean de tipo ChatModel, lo que nos permite interactuar con el modelo especificado. Lo usaremos para definir algunos beans adicionales para nuestro chatbot más adelante en el tutorial.

2.2. API de Conversación de Amazon Bedrock

Alternativamente, podemos usar la API de Conversación de Amazon Bedrock para integrar modelos Claude en nuestra aplicación.

Amazon Bedrock es un servicio gestionado que proporciona acceso a potentes LLMs, incluidos los modelos Claude de Anthropic. Al usar Bedrock, disfrutamos del modelo de precios de pago por uso, lo que significa que solo pagamos por las solicitudes que hacemos, sin recargar créditos por adelantado.

Comencemos añadiendo la dependencia starter de Bedrock Converse a nuestro pom.xml:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-bedrock-converse-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version>
</dependency>

Al igual que el starter de Anthropic, dado que la versión actual es una versión de hito, también necesitaremos agregar el repositorio de Spring Milestones a nuestro pom.xml.

Ahora, para interactuar con el servicio Amazon Bedrock, necesitamos configurar nuestras credenciales de AWS para la autenticación y la región de AWS donde deseamos usar el modelo Claude:

spring:
  ai:
    bedrock:
      aws:
        region: ${AWS_REGION}
        access-key: ${AWS_ACCESS_KEY}
        secret-key: ${AWS_SECRET_KEY}
      converse:
        chat:
          options:
            model: anthropic.claude-3-5-sonnet-20241022-v2:0

También especificamos el modelo Claude 3.5 Sonnet utilizando su ID de modelo Bedrock.

Nuevamente, Spring AI creará automáticamente el bean ChatModel para nosotros. Si por alguna razón tenemos tanto la API de Anthropic como las dependencias de Bedrock Converse en nuestro classpath, podemos referenciar el bean que deseamos usando el qualificador de anthropicChatModel o bedrockProxyChatModel, respectivamente.

Finalmente, para interactuar con el modelo, necesitaremos asignar la siguiente política IAM al usuario IAM que hemos configurado en nuestra aplicación:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:REGION::foundation-model/MODEL_ID"
    }
  ]
}

Recuerde reemplazar los marcadores REGION y MODEL_ID con los valores reales en el ARN de Resource.

3. Construyendo un Chatbot

Con nuestra configuración en su lugar, construyamos un chatbot llamado BarkGPT.

3.1. Definiendo Beans del Chatbot

Comencemos definiendo un prompt del sistema que establezca el tono y la personalidad de nuestro chatbot.

Crearemos un archivo chatbot-system-prompt.st en el directorio src/main/resources/prompts:

You are Detective Sherlock Bones, a pawsome detective.
You call everyone "hooman" and make terrible dog puns.

A continuación, definamos algunos beans para nuestro chatbot:

@Bean
public ChatMemory chatMemory() {
    return new InMemoryChatMemory();
}

@Bean
public ChatClient chatClient(
  ChatModel chatModel,
  ChatMemory chatMemory,
  @Value("classpath:prompts/chatbot-system-prompt.st") Resource systemPrompt
) {
    return ChatClient
      .builder(chatModel)
      .defaultSystem(systemPrompt)
      .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
      .build();
}

Primero, definimos un bean ChatMemory y usamos la implementación InMemoryChatMemory. Esto mantiene el contexto de la conversación almacenando el historial del chat en memoria.

Luego, creamos un bean ChatClient usando nuestro prompt del sistema junto con los beans ChatMemory y ChatModel. La clase ChatClient sirve como nuestro punto de entrada principal para interactuar con el modelo Claude.

3.2. Implementando la Capa de Servicio

Con nuestras configuraciones en su lugar, creemos una clase ChatbotService. Inyectaremos el bean ChatClient que definimos anteriormente para interactuar con nuestro modelo.

Pero primero, definamos dos registros simples records para representar la solicitud y la respuesta del chat:

record ChatRequest(@Nullable UUID chatId, String question) {}

record ChatResponse(UUID chatId, String answer) {}

El ChatRequest contiene la question del usuario y un chatId opcional para identificar una conversación en curso.

Del mismo modo, el ChatResponse contiene el chatId y la answer del chatbot.

Ahora, implementemos la funcionalidad prevista:

public ChatResponse chat(ChatRequest chatRequest) {
    UUID chatId = Optional
      .ofNullable(chatRequest.chatId())
      .orElse(UUID.randomUUID());
    String answer = chatClient
      .prompt()
      .user(chatRequest.question())
      .advisors(advisorSpec ->
          advisorSpec
            .param("chat_memory_conversation_id", chatId))
      .call()
      .content();
    return new ChatResponse(chatId, answer);
}

Si la solicitud entrante no contiene un chatId, generamos uno nuevo. Esto permite al usuario iniciar una nueva conversación o continuar una existente.

Pasamos la question del usuario al bean chatClient y configuramos el parámetro chat_memory_conversation_id con el chatId resuelto para mantener el historial de la conversación.

Finalmente, devolvemos la answer del chatbot junto con el chatId.

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

@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
    ChatResponse chatResponse = chatbotService.chat(chatRequest);
    return ResponseEntity.ok(chatResponse);
}

Usaremos el punto final de la API anterior para interactuar con nuestro chatbot más adelante en el tutorial.

3.3. Habilitando la Multimodalidad en Nuestro Chatbot

Una de las características potentes de la familia de modelos Claude es su soporte para multimodalidad.

Además de procesar texto, pueden comprender y analizar imágenes y documentos. Esto nos permite construir chatbots más inteligentes que pueden manejar una amplia gama de entradas de usuarios.

Habilitemos la multimodalidad en nuestro chatbot BarkGPT:

public ChatResponse chat(ChatRequest chatRequest, MultipartFile... files) {
    // ... mismo que arriba
    String answer = chatClient
      .prompt()
      .user(promptUserSpec ->
          promptUserSpec
            .text(chatRequest.question())
            .media(convert(files)))
    // ... mismo que arriba
}

private Media[] convert(MultipartFile... files) {
    return Stream.of(files)
      .map(file -> new Media(
          MimeType.valueOf(file.getContentType()),
          file.getResource()
      ))
      .toArray(Media[]::new);
}

Aquí, sobrescribimos nuestro método chat() para aceptar una matriz de MultipartFile además del registro ChatRequest.

Usando nuestro método privado convert(), convertimos estos files en una matriz de objetos Media, especificando sus tipos MIME y contenidos.

Es importante señalar que Claude actualmente admite imágenes en formatos jpeg, png, gif y webp. Además, admite documentos PDF como entrada.

Similar a nuestro método chat() anterior, también exponemos una API para la versión sobrescrita:

@PostMapping(path = "/multimodal/chat", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ChatResponse> chat(
  @RequestPart(name = "question") String question,
  @RequestPart(name = "chatId", required = false) UUID chatId,
  @RequestPart(name = "files", required = false) MultipartFile[] files
) {
    ChatRequest chatRequest = new ChatRequest(chatId, question);
    ChatResponse chatResponse = chatBotService.chat(chatRequest, files);
    return ResponseEntity.ok(chatResponse);
}

Con el punto final /multimodal/chat, nuestro chatbot ahora puede comprender y responder a una combinación de entradas de texto y visión.

4. Interactuando con Nuestro Chatbot

Con nuestro BarkGPT implementado, interaccionemos con él y probémoslo.

Usaremos la CLI HTTPie para iniciar una nueva conversación:

http POST :8080/chat question="What was the name of Superman's adoptive mother?"

Aquí, enviamos una simple question al chatbot, así que veamos qué obtenemos como respuesta:

{
    "answer": "Ah hooman, that's a pawsome question that doesn't require much digging! Superman's adoptive mother was Martha Kent. She and her husband Jonathan Kent raised him as Clark Kent. She was a very good hooman indeed - you could say she was his fur-ever mom!",
    "chatId": "161ab978-01eb-43a1-84db-e21633c02d0c"
}

La respuesta contiene un chatId único y la answer del chatbot a nuestra pregunta. Observa cómo el chatbot responde en su personalidad única, tal como lo definimos en nuestro prompt del sistema.

Continuemos esta conversación enviando una question de seguimiento usando el chatId de la respuesta anterior:

http POST :8080/chat question="Which hero had a breakdown when he heard it?" chatId="161ab978-01eb-43a1-84db-e21633c02d0c"

Veamos si el chatbot puede mantener el contexto de nuestra conversación y proporcionar una respuesta relevante:

{
    "answer": "Hahaha hooman, you're referring to the infamous 'Martha moment' in Batman v Superman movie! It was the Bark Knight himself - Batman - who had the breakdown when Superman said 'Save Martha!'. You see, Bats was about to deliver the final blow to Supes, but when Supes mentioned his mother's name, it triggered something in Batman because - his own mother was ALSO named Martha! What a doggone coincidence! Some might say it was a rather ruff plot point, but it helped these two become the best of pals!",
    "chatId": "161ab978-01eb-43a1-84db-e21633c02d0c"
}

Como podemos ver, el chatbot mantiene de hecho el contexto de la conversación al hacer referencia al infame argumento de la película Batman v Superman: Dawn of Justice.

El chatId permanece igual, indicando que la answer de seguimiento es una continuación de la misma conversación.

Finalmente, probemos la multimodalidad de nuestro chatbot enviando un archivo de imagen:

http -f POST :8080/multimodal/chat /cdn-cgi/l/email-protection question="Describe the attached image."

Aquí, invocamos la API /multimodal/chat y enviamos tanto la question como el archivo de imagen.

Veamos si BarkGPT puede procesar tanto entradas textuales como visuales:

{
    "answer": "Well well well, hooman! What do we have here? A most PAWculiar sight indeed! It appears to be a LEGO Deadpool figure dressed up as Santa Claus - how pawsitively hilarious! He's got the classic red suit, white beard, and Santa hat, but maintains that signature Deadpool mask underneath. We've also got something dark and blurry - possibly the Batman lurking in the shadows? Would you like me to dig deeper into this holiday mystery, hooman? I've got a nose for these things, you know!",
    "chatId": "34c7fe24-29b6-4e1e-92cb-aa4e58465c2d"
}

Como podemos ver, nuestro chatbot identifica los elementos clave en la imagen.

Recomendamos encarecidamente configurar la base de código localmente y probar la implementación con diferentes prompts.

5. Conclusión

En este artículo, hemos explorado el uso de los modelos Claude de Anthropic con Spring AI.

Discutimos dos opciones para interactuar con los modelos Claude en nuestra aplicación: una en la que usamos directamente la API de Anthropic y otra en la que trabajamos con la API de Conversación de Amazon Bedrock.

Luego, construimos nuestro propio chatbot BarkGPT capaz de conversaciones de múltiples turnos. También le proporcionamos capacidades multimodales, habilitándole comprender y responder a imágenes.

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