¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Simón Rodríguez Hace 4 días Cargando comentarios…
Todo el mundo sabe que, desde hace un tiempo, la IA está de moda (¡¿quién, a estas alturas, no ha escuchado hablar de ChatGPT, DeepSeek o Gemini?!) y, como nos comentó un compañero en su post, también ha llegado a Spring. Teniendo en cuenta toda la comunidad ya existente de Spring/Java y las miles de empresas con aplicaciones desplegadas en esta tecnología queriéndose subir a la ola de la IA… ¿qué mejor forma de integrarla que, simplemente, añadiendo una dependencia o módulo y evitando así múltiples quebraderos de cabeza por desarrollar con distintos frameworks y/o tecnologías? Es por esto mismo que vamos a explorar dicho módulo de IA para ver todo lo que nos ofrece.
Al entrar en la documentación de Spring AI vemos que tiene múltiples secciones que iremos revisando para entender qué nos aporta cada una de cara al desarrollo.
En la propia descripción nos indican que la finalidad de Spring AI es integrar los datos y APIs de empresa con los modelos de IA.
Como ya se comentó en el post de inteligencia artificial y Spring Boot, Spring AI nos proporciona muchas ventajas. Pero, antes de todo, desde el enfoque de un perfil de desarrollo de Spring que desconoce los conceptos básicos de IA, la propia documentación nos recomienda conocer ciertos términos para poder entender posteriormente cómo funciona Spring AI. Estos conceptos son:
Básicamente, son los algoritmos diseñados para procesar y generar la información que replican el cerebro humano. Estos algoritmos son capaces de extraer patrones sobre datos para poder hacer predicciones, crear textos, imágenes y resolver otro tipo de problemas.
En este momento, Spring AI soporta inputs y outputs como lenguaje, imágenes y audio, además de vectores/embeddings para casos de uso más avanzados.
Sirven como base para guiar al modelo de cara a los resultados concretos. En este caso, no se debe pensar solo en los input text, sino también en los “roles” asociados a ese input de cara a proporcionar un contexto. Por ejemplo: ”eres un meteorólogo. Dime qué tiempo va a hacer hoy.” Para simplificar esta interacción, se suelen crear plantillas de prompts como: ”dime un chiste
Los embeddings son representaciones numéricas de texto, imágenes o vídeos que capturan las relaciones entre las entradas. Es decir, convierten las entradas en arrays de números llamados vectores, los cuales están diseñados para indicar el significado de las entradas. Calculando la distancia entre la representación vectorial de, por ejemplo, dos textos, se puede conocer la similitud entre esos textos.
Los tokens se pueden considerar como la unidad más pequeña de información que usa un LLM para funcionar. Para procesar la entrada por un LLM, las palabras se transforman en tokens (un token corresponde aproximadamente con el 75% de una palabra) y para la respuesta, los tokens se transforman en palabras.
Los tokens son importantes en los LLMs porque son en lo que se basan para la facturación (tanto tokens de entrada como de salida), además de definir los límites de uso de cada modelo. Es decir, cada modelo tiene límites de tokens que se pueden informar en la entrada.
Teniendo ya en mente los conceptos básicos, empecemos a bucear en las funcionalidades de Spring AI y sus posibilidades.
El ChatClient ofrece un API para comunicarse con el modelo IA a través de métodos para crear las distintas partes de los prompts.
Se puede crear por autoconfiguración o de forma programática:
@RestController
public class TravelController {
private final ChatClient chatClient;
public TravelController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/travel-recommendation")
String generation(@RequestBody String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
public class ChatClientCodeController {
private ChatModel myChatModel;
private ChatClient chatClient;
public ChatClientCodeController(ChatModel myChatModel) {
this.myChatModel = myChatModel;
this.chatClient = ChatClient.create(this.myChatModel);
// this.chatClient = ChatClient.builder(this.myChatModel).build();
}
@GetMapping("/chat-client-programmatically")
String chatClientGeneration(@RequestBody String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
@GetMapping("/travel-recommendation/chat-response")
ChatResponse travelRecommendationResponse(@RequestBody String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.chatResponse();
}
@GetMapping("/travel-recommendation/entity")
TravelRecommendation travelRecommendationEntity(@RequestBody String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.entity(TravelRecommendation.class);
}
public class TravelRecommendation {
private List<City> cities;
}
public class City {
private String name;
}
Haciendo uso de configuraciones por defecto, es posible solo tener que informar el input del usuario en tiempo de ejecución. Por ejemplo, para indicar el system input (con o sin parámetros). El system input indica el comportamiento básico del agente como, por ejemplo, que acote a cierta temática y actúe como un chatbot de viajes, y se podría configurar:
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a travel chat bot that only recommends 3 cities under 50000 population")
.build();
}
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a travel chat bot that only recommends 3 cities under {population} population")
.build();
}
@GetMapping("/travel-recommendation-population")
String travelRecommendationPopulation(@RequestBody String userInput, @RequestParam Long population) {
return this.chatClient.prompt()
.system(sp -> sp.param("population", population))
.user(userInput)
.call()
.content();
}
Otras configuraciones por defecto ya en el ChatClient.Builder, que también pueden sobreescribirse en tiempo de ejecución con métodos similares, son:
Para la comunicación con los modelos, Spring AI nos ofrece un API. Este API soporta modelos de Chat, Text to Image, Audio Transcription, Text to Speech y Embeddings, de forma síncrona y asíncrona. Además proporciona funcionalidades específicas de cada modelo. Soporta modelos de distintos proveedores como OpenAI, Microsoft, Amazon, Google, Hugging Face, etc.
En primer lugar nos centraremos en las funcionalidades relacionadas con el chat, pero se puede ver que el funcionamiento para el resto de modelos es muy similar.
Este API ofrece la posibilidad de integrar funcionalidades de chat con IA a partir de LLMs para generar respuestas en lenguaje natural.
Este API funciona enviado un prompt al modelo de IA que genera una respuesta a la conversación basada en sus datos de entrenamiento y su comprensión de los patrones de lenguaje natural. Al recibir la respuesta, se puede devolver directamente o usarla para realizar más funcionalidades. El API se ha diseñado pensando en la simplicidad y portabilidad para interactuar entre los distintos modelos de la forma más transparente posible.
A continuación comentamos las principales clases de este API:
public interface ChatModel extends Model<Prompt, ChatResponse> {
default String call(String message) {...}
@Override
ChatResponse call(Prompt prompt);
}
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions modelOptions;
@Override
public ChatOptions getOptions() {...}
@Override
public List<Message> getInstructions() {...}
}
public interface Content {
String getContent();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
MessageType getMessageType();
}
public interface MediaContent extends Content {
Collection<Media> getMedia();
}
Existen varias implementaciones de Message que corresponden con las categorías que los modelos pueden procesar.
Antes de enviar el mensaje al LLM, se distingue entre los distintos roles (system, user, function, assistant) a través del MessageType para saber cómo tiene que actuar el mensaje. En ciertos modelos que no soportan roles, el UserMessage actúa como la categoría estándar o por defecto.
public interface ChatOptions extends ModelOptions {
String getModel();
Float getFrequencyPenalty();
Integer getMaxTokens();
Float getPresencePenalty();
List<String> getStopSequences();
Float getTemperature();
Integer getTopK();
Float getTopP();
ChatOptions copy();
}
Todo esto permite sobrescribir las opciones a informar al modelo en tiempo de ejecución para cada petición, existiendo una configuración por defecto para la aplicación, lo que permite una mayor flexibilidad.
El flujo de interacción con el modelo sería:
public class ChatResponse implements ModelResponse<Generation> {
private final ChatResponseMetadata chatResponseMetadata;
private final List<Generation> generations;
@Override
public ChatResponseMetadata getMetadata() {...}
@Override
public List<Generation> getResults() {...}
}
Puesto que el API de ChatModel está construido sobre el API genérico de Model, nos permite cambiar de forma transparente entre los distintos servicios de IA manteniendo el mismo código.
En la siguiente imagen se puede ver las relaciones entre las distintas clases del API de Model de Spring AI:
En la documentación existe una sección en donde se comparan los distintos modelos basados en las funcionalidades que poseen:
Para los ejemplos de implementación se ha seleccionado Ollama, debido a que es uno de los que más funcionalidades ofrece y, sobre todo, por la posibilidad de ejecutarlo en local para no incurrir en costes, ya que la gran mayoría de los modelos públicos son de pago.
Ollama
Con Ollama es posible ejecutar varios LLMs de forma local. En esta sección se verán algunos de los temas importantes de cara a su configuración y uso.
Prerrequisitos
Crear una instancia de Ollama a través de:
Instalación de Ollama en local
Para poder comprobar las posibilidades que nos ofrece Ollama, instalaremos una instancia en nuestro ordenador. Para ello, simplemente se descargará Ollama como se indica en su página oficial (en este caso usaremos Linux - Ubuntu).
curl -fsSL https://ollama.com/install.sh | sh
Una vez descargado Ollama (puede tardar un tiempo), se descarga un modelo. En este caso, se ejecutará el modelo llama3.2:1b (pero se puede comprobar que existen muchos más con distintas características) puesto que es más ligero a través del siguiente comando:
ollama run llama3.2:1b
Como puede observarse al lanzar el comando anterior, ya muestra el prompt para interactuar con él:
Pudiendo hacerle preguntas:
Autoconfiguración
Spring AI ofrece autoconfiguración para Spring Boot para la integración de Ollama a través de la dependencia:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
De cara a esta autoconfiguración, se pueden indicar propiedades base o propiedades de chat para las distintas funcionalidades, siendo algunas de las más importantes:
Property | Descripción | Default |
---|---|---|
spring.ai.ollama.base-url | URL donde se ejecuta el servidor de Ollama | localhost:11434 |
spring.ai.ollama.init.pull-model-strategy | Si se deben descargar los modelos al inicio de la aplicación y cómo | never |
spring.ai.ollama.init.chat.additional-models | Modelos adicionales además del configurado por defecto en las propierties | [ ] |
spring.ai.ollama.chat.enabled | Habilitar el chat model de Ollama | true |
spring.ai.ollama.chat.options.model | El nombre del modelo a usar | mistral |
spring.ai.ollama.chat.options.num-ctx | Configura el tamaño de la context window empleada para generar el siguiente token | 2048 |
spring.ai.ollama.chat.options.top-k | Reduce la probabilidad de generar respuestas sin sentido. Un valor alto (100) dará respuestas más diversas, mientras que un valor más bajo (10) será más conservador | 40 |
spring.ai.ollama.chat.options.top-p | Funciona conjuntamente con la propiedad top-k. Un valor alto (0.95) hará que se responda de una forma más diversa, mientras que valores más bajos (0.5) generará respuestas más enfocadas y conservadoras | 0.9 |
spring.ai.ollama.chat.options.temperature | Configura la temperatura del modelo. Valores más altos harán que el modelo responda de una forma más creativa | 0.8 |
Opciones en tiempo de ejecución
En tiempo de ejecución se pueden sobrescribir las opciones por defecto a enviar al prompt como, por ejemplo, la “temperatura”:
@GetMapping("/city-name-generation")
String cityNameGeneration() {
return chatModel
.call(new Prompt("Inventa 5 nombres de ciudades.",
OllamaOptions.builder()
.model(OllamaModel.LLAMA3_2_1B)
.temperature(0.4)
.build()))
.getResult().getOutput().getText();
}
Descarga de modelos
Spring AI Ollama puede descargar automáticamente modelos cuando no existen en la instancia Ollama. Existen tres formas de descargar los modelos:
En la siguiente imagen se puede observar cómo se descarga el modelo inexistente al arrancar la aplicación:
Los modelos definidos por properties se pueden descargar en el arranque, indicando propiedades como el tipo de estrategia, el timeout o el número máximo de reintentos:
spring:
ai:
ollama:
init:
pull-model-strategy: always
timeout: 60s
max-retries: 1
También es posible iniciar otros modelos en el arranque de cara a usarlos posteriormente en tiempo de ejecución:
spring:
ai:
ollama:
init:
pull-model-strategy: always
chat:
additional-models:
- llama3.2
- qwen2.5
Existiendo además la posibilidad de excluir ciertos tipos de modelos:
spring:
ai:
ollama:
init:
pull-model-strategy: always
chat:
include: false
Ollama APIClient
A modo de nota informativa, la siguiente imagen muestra las interfaces y clases en el Ollama API (aunque el uso de este API no está recomendado. En su lugar, es mejor usar OllamaChatModel):
Hemos visto algunos de los conceptos importantes a tener en cuenta para trabajar con los LLMs, así como los principales APIs de Spring AI que nos permiten interactuar con dichos LLMs. Podemos constatar cómo el módulo está pensado de forma abstracta para poder conectarnos con los últimos modelos de LLM (OpenAI, Gemini, Anthropic, Mistral, etc), haciendo más foco en Ollama por la cantidad de funcionalidades que aporta, además de poder ejecutarse en local y no ser exclusivamente de pago.
En sucesivos post se continuará explorando otras partes del módulo en el camino a construir e integrar aplicaciones de IA con Spring.
Referencias
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.