Una vez comprendidos los conceptos básicos de Spring AI para interactuar con los modelos de la forma más sencilla, vamos a revisar conceptos más avanzados para poder crear aplicaciones más potentes, exprimiendo muchas de las grandes ventajas de la IA.

Algunas de las features que exploraremos a continuación pasan desapercibidas en la gran mayoría de los casos, pero son importantes de cara a la experiencia de usuario o integración con otras aplicaciones. Es el caso del contexto de los prompts, de forma que cuando realizamos múltiples preguntas a un chatbot, pueda comprender las alusiones indirectas a ciertos términos:

simonrodriguez@simonrodriguez:~$ ollama run llama3.2:1b »>> cual es la población de España hasta el año 2023? Solo indica el número de habitantes Según los datos del Instituto Nacional de Estadística (INE) de España, en el año 2023 la población total de España es de aproximadamente 47.761.000 habitantes. »>> y que idioma se habla? El idioma oficial de España es el español, y está dividido en varias variantes regionales. Algunos de los idiomas más comunes son: * Español (castellano) * Catalán (catalan) * Gallego (gallego) * Balear (barcelonesa) * Vasconés (vascuno) * Aranés * Asturian * Basque Además, también hay dialectos y variedades regionales del español que no son oficiales pero se hablan en diferentes partes de España.

En este ejemplo se puede ver cómo, en la segunda pregunta, no necesitamos especificar que nos referimos a España.

Otra de las funcionalidades que se revisará es la opción de usar “herramientas” (tools) para enriquecer la información que posee el modelo. Esto se debe a que los LLMs son modelos preentrenados, lo que significa que tienen información hasta una fecha concreta, no pudiendo ofrecer resultados en tiempo real.

simonrodriguez@simonrodriguez:~$ ollama run llama3.2:1b >>> tell me your cut off date My knowledge cutoff is currently December 2023, but I have access to more recent information via internet search. >>> Send a message (/? for help)

Las aplicaciones más conocidas, como ChatGPT, DeepSeek y demás, también obtienen dichos datos de tiempo real por funcionalidades externas al propio LLM.

Advisors

Los Advisors permiten interceptar, modificar y enriquecer las interacciones con el modelo. Entre los beneficios se incluyen el uso de patrones comunes de IA, transformar los datos enviados y recibidos de los LLMs y portabilidad entre modelos y casos de uso.

Componentes principales

Como se puede ver en la siguiente imagen, el API se basa en las clases CallAroundAdvisor y CallAroundAdvisorChain (y sus homólogas para el uso de streaming), además de AdvisedRequest y AdvisedResponse para interactuar con la petición y respuesta. Estas últimas entidades, además, incluyen un adviseContext para compartir el estado a través de la cadena de ejecución.

el API se basa en las clases CallAroundAdvisor y CallAroundAdvisorChain (y sus homólogas para el uso de streaming), además de AdvisedRequest y AdvisedResponse para interactuar con la petición y respuesta. Estas últimas entidades además incluyen un adviseContext para compartir el estado a través de la cadena de ejecución.

NextAroundCall y nextAroundStream son los métodos clave para poder examinar los datos del prompt, customizarlos, invocar la siguiente entidad en la cadena de ejecución de advisors, bloquear la petición, examinar la respuesta y poder lanzar excepciones. También existen métodos como getOrder (indica el orden del Advisor en la cadena de ejecución) y getName (nombre único del Advisor).

La cadena de ejecución creada por Spring AI permite la ejecución secuencial de múltiples advisors ordenados por el método getOrder. Los valores más bajos se ejecutan primero, siendo el último Advisor el que envía la petición al LLM. En la siguiente imagen se puede ver el flujo de interacción entre los Advisors y el modelo.

Spring AI crea una AdvisedRequest a partir del Prompt del usuario con un AdvisorContext vacío.  Cada Advisor en la cadena procesa la petición, pudiendo modificarla o bloquearla, debiendo en este caso crear la respuesta.  El último Advisor creado por el framework, envía la petición al Chat Model.  La respuesta del modelo se envía de vuelta a la cadena de Advisors convirtiéndola en AdvisedResponse.  Cada Advisor puede procesar o modificar la respuesta.  La AdvisedResponse se retorna al cliente.
  1. Spring AI crea una AdvisedRequest a partir del prompt del usuario con un AdvisorContext vacío.
  2. Cada Advisor en la cadena procesa la petición, pudiendo modificarla o bloquearla, debiendo en este caso crear la respuesta.
  3. El último Advisor creado por el framework, envía la petición al Chat Model.
  4. La respuesta del modelo se envía de vuelta a la cadena de Advisors convirtiéndola en AdvisedResponse.
  5. Cada Advisor puede procesar o modificar la respuesta.
  6. La AdvisedResponse se retorna al cliente.

Orden de Advisors

El orden de ejecución de los Advisors en la cadena viene determinado por el método getOrder. Algunos de los puntos a tener en cuenta para su ejecución son:

  • Los Advisors con un orden menor se ejecutan primero. Valores más altos se interpretan como una prioridad más baja.
  • La cadena de ejecución de Advisors funciona como una pila:
    • El primer Advisor en la cadena es el primero que procesa la petición.
    • Este primer Advisor es el último que procesa la respuesta.
  • Para controlar el orden de ejecución:
    • Configura el orden cerca de Ordered.HIGHEST_PRECEDENCE para asegurarse que el Advisor se ejecuta primero en la cadena (primero para procesar la petición, último para procesar la respuesta)
    • Configura el orden cerca de Ordered.LOWEST_PRECEDENCE para asegurarse que el Advisor se ejecuta al final en la cadena (último para procesar la petición, primero para procesar la respuesta)
  • Si varios Advisors tienen el mismo valor de orden, su orden de ejecución no se puede garantizar.

En el caso de que se necesite ejecutar un Advisor primero en la petición y en la respuesta:

  • Usa distintos Advisors para cada “lado”.
  • Configura cada uno con valores de orden distintos.
  • Usa el Advisor Context para compartir el estado entre ellos.

Implementar un Advisor

Para crear un Advisor se tiene que implementar CallAroundAdvisor con su método aroundCall:

public class CustomAdvisor implements CallAroundAdvisor {

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        AdvisedResponse originalResponse = chain.nextAroundCall(this.beforeCall(advisedRequest));
        return this.afterCall(originalResponse);
    }
//Metodos para completar el comportamiento
}

Advisors proporcionados por el framework

Spring AI proporciona algunos Advisors ya implementados:

  1. Chat Memory:
  • MessageChatMemoryAdvisor: obtiene datos de la memoria y los añade como una colección de mensajes en el prompt. Esto permite mantener el histórico de conversación, aunque no todos los modelos lo soportan.
memoryChatClient = chatClientBuilder
    .clone()
    .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
    .build();
  • PromptChatMemoryAdvisor: obtiene datos de la memoria y los incluye en el prompt system.
  • VectorStoreChatMemoryAdvisor: obtiene datos de una VectorStore y los incluye en el prompt system. Este Advisor es útil para buscar y obtener información relevante de una forma eficiente con cantidades muy grandes de datos.
  1. Question Answering:
  • QuestionAnswerAdvisor: hace uso de una VectorStore para proporcionar capacidades de pregunta-respuesta implementando el patrón RAG.
  1. Content Safety:
  • SafeGuardAdvisor: advisor para prevenir que el modelo genere contenido inapropiado.

Mejores prácticas

  • Cada Advisor debería estar centrado en una tarea específica.
  • Usa el AdviseContext para compartir el estado entre Advisors cuando sea necesario.
  • Implementa los Advisors pensando en la máxima flexibilidad (versión normal y de streaming).
  • Analizar bien el orden de los Advisors en la cadena de ejecución para un flujo de datos correcto.

Structured Output

Este API ayuda a convertir la salida de un modelo a un formato estructurado para facilitar la integración con otros componentes/aplicaciones.

Antes de realizar la llamada al modelo, el Converter incluye instrucciones al prompt para que el modelo genere la respuesta en el formato deseado. Posteriormente el Converter transforma la salida en instancias del formato requerido, lo que implica transformar texto en la estructura de datos correspondiente, como puede ser JSON, XML o entidades de dominio.

Antes de realizar la llamada al modelo, el Converter incluye instrucciones al prompt para que el modelo genere la respuesta en el formato deseado. Posteriormente, el Converter transforma la salida en instancias del formato requerido, lo que implica transformar texto en la estructura de datos correspondiente, como puede ser JSON, XML o entidades de dominio.

Converters proporcionados por Spring AI

En estos momentos Spring AI proporciona las siguientes implementaciones de Converters:

AbstractConversionServiceOutputConverter, AbstractMessageOutputConverter, BeanOutputConverter, MapOutputConverter, ListOutputConverter
  • AbstractConversionServiceOutputConverter: ofrece un GenericConversionService para transformar la salida del LLM en el formato deseado. No tiene una implementación de FormatProvider por defecto.
  • AbstractMessageOutputConverter: ofrece un MessageConverter para transformar la salida del LLM en el formato deseado. No tiene una implementación de FormatProvider por defecto.
  • BeanOutputConverter: configurado con una clase Java o un ParameterizedTypeReference que usa el FormatProvider para construir un JSON compatible con la clase Java. Seguidamente, usa el ObjectMapper para deserializar el JSON en una instancia de la clase correspondiente. Este es el Converter empleado en ejemplos de posts anteriores al hacer la llamada con el método entity(). Este Converter soporta el uso de la anotación @JsonPropertyOrder para indicar el orden concreto en el que aparecen las propiedades en el JSON.
  • MapOutputConverter: extiende la funcionalidad de AbstractMessageOutputConverter con una implementación del FormatProvider para construir un JSON. Además, incluye una implementación de del converter que usa el MessageConverter para transformar el JSON en una instancia de la clase Map (java.util.Map<String, Object>).
@GetMapping("/map")
Map<String, Object> getStandardOutputMap() {


    MapOutputConverter outputConverter = new MapOutputConverter();

    String format = outputConverter.getFormat();
    String template = """
            Dame una lista de las 3 criptomonedas mas importantes con su nombre, su abreviatura y una breve descripcion.
            Ejemplo: Bitcoin -> "abreviatura": "BTC", "descripcion": "la criptomoneda mas importante".
            {format}
            """;


    PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format));
    Prompt prompt = new Prompt(promptTemplate.createMessage());


    return chatClient.prompt(prompt)
            .call()
            .entity(outputConverter);
}
  • ListOutputConverter: extiende de AbstractConversionServiceOutputConverter e incluye una implementación de FormatProvider adaptada a las salidas en forma de lista delimitada por comas. El converter hace uso del ConversionService para transformar la salida en una instancia de la clase List (java.util.List).

Tool Calling

La integración con “herramientas” permite al modelo ejecutar funciones de cliente para acceder a información o ejecutar tareas cuando sea necesario. Sus principales usos son:

  • Recuperación de información: estas “herramientas” permiten recuperar información de múltiples fuentes externas (bbdd, sistema de ficheros, web service, etc) proporcionando más información a los modelos para que puedan responder cuestiones que sin esta información no serían capaces.
  • Realización de tareas: “herramientas” que realizan ciertas tareas como envío de emails, guardar registros en bbdd, enviar un formulario, etc, con la finalidad de automatizar tareas.

Es importante indicar que este patrón no es una funcionalidad que proporcionen los modelos, sino una que proporciona la aplicación en sí. El modelo realiza la petición a la “herramienta” con los argumentos, pero quien ejecuta la función es la aplicación.

Funcionamiento

Los conceptos y componentes importantes para el funcionamiento del Tool Calling son:

Incluir la definición de la “herramienta” que se quiere proporcionar al modelo. Esta definición está compuesta por: nombre, descripción y el esquema de los parámetros de entrada  Cuando el modelo decide llamar a una “herramienta”, envía una respuesta con el nombre de la herramienta y los parámetros de entrada adaptados según el esquema definido.  La aplicación ejecuta la “herramienta”.  El resultado de la “herramienta” es procesado por la aplicación.  La aplicación envía el resultado al modelo.  El modelo genera la respuesta final usando la respuesta de la “herramienta” como contexto adicional.
  1. Incluir la definición de la “herramienta” que se quiere proporcionar al modelo. Esta definición está compuesta por: nombre, descripción y el esquema de los parámetros de entrada.
  2. Cuando el modelo decide llamar a una “herramienta”, envía una respuesta con el nombre de la herramienta y los parámetros de entrada adaptados según el esquema definido.
  3. La aplicación ejecuta la “herramienta”.
  4. El resultado de la “herramienta” es procesado por la aplicación.
  5. La aplicación envía el resultado al modelo.
  6. El modelo genera la respuesta final usando la respuesta de la “herramienta” como contexto adicional.

A continuación veremos varias formas de definir y ejecutar las “herramientas”.

Métodos como herramientas

Se proporcionan dos formas de crear “herramientas” desde métodos:

  • Declarativa: anotar el método con @Tool pudiendo personalizarlo con los parámetros name, description, returnDirect o resultConverter. El método en sí puede ser static o instance, pudiendo tener cualquier tipo de visibilidad (public, protected, package-private o private). La clase que contiene el método puede ser una top-level o nested y también puede tener cualquier tipo de visibilidad.

    En cuanto a los parámetros, se puede definir cualquier número de ellos (también sin parámetro) y de casi todos los tipos (más adelante se hablará de las limitaciones de esta definición). A mayores, existe la anotación @ToolParam para indicar información adicional como una descripción de los parámetros o si estos son obligatorios (por defecto los parámetros son obligatorios). La definición para el valor de retorno del método es similar, pudiendo devolverse casi todos los tipos incluido el void y, en caso de retornar un valor, este debe ser de tipo serializable.

    Para indicar la “herramienta” a ejecutar se usa el método tools() de ChatClient, teniendo efecto solo para esta petición:
@Slf4j
public class BitcoinTool {

    @Tool(description = "Get the current price of bitcoin cripto currency in euros")
    Long getBitcoinPriceInEuros() {

        BitcoinInfo bitcoinInfo = null;
        String apiResponse = "";
        try {
            apiResponse = RestClient.create().get().uri(new URI("https://cex.io/api/last_price/BTC/EUR")).retrieve()
                    .body(String.class);

            bitcoinInfo = new ObjectMapper().readValue(apiResponse, BitcoinInfo.class);

        } catch (URISyntaxException | JsonProcessingException e) {
            e.printStackTrace();
        }

        log.info("The current bitcoin price in euros is {}", bitcoinInfo.getLprice());
        return bitcoinInfo.getLprice();
    }
}
@GetMapping("/btc")
String toolCallingBtcPrice() {


    return this.chatClient.prompt().user("Give me the current bitcoin price")
    .tools(new BitcoinTool()).call().content();
    }

También se pueden indicar las “herramientas” por defecto en el ChatClient.Builder con el método defaultTools() (así la herramienta tiene efecto sobre todas las peticiones), teniendo un comportamiento similar los casos para la clase ChatModel.

  • Programática: se debe construir un MethodToolCallback indicando el método correspondiente y su información a través de los parámetros toolDefinition, toolMetadata, toolMethod, toolObject y toolCallResultConverter.

    En cuanto a los parámetros, clases contenedoras y retornos posibles del método, este tipo de definición presenta el mismo tipo de posibilidades y limitaciones que la opción declarativa. También se emplean los mismos métodos a la hora de su ejecución (tanto para ChatClient como para ChatModel):
@Slf4j
public class PurchaseOrderTool {
    
    public void createBitcoinPurchaseOrder(@ToolParam(description = "Bitcoin amount") Integer bitcoinAmount, 
            @ToolParam(description = "Current bitcoin euros price") Long currentBitcoinEurosPrice) {
        log.info("Create a purchase order for {} btc with each btc at {} euros price", bitcoinAmount, currentBitcoinEurosPrice);
    }
}
@GetMapping("/btc-purchase-order")
String toolCallingBtcPriceSavePurchaseOrder() {


    Method method = ReflectionUtils.findMethod(PurchaseOrderTool.class, "createBitcoinPurchaseOrder", Integer.class, Long.class);
    ToolCallback toolCallback = MethodToolCallback.builder()
        .toolDefinition(ToolDefinition.builder(method)
            .description("Create bitcoin purchase order of bitcoin amount at current bitcoin euros price")
            .build())
        .toolMethod(method)
        .toolObject(new PurchaseOrderTool())
        .build();


    return this.chatClient.prompt().user("Give me the current bitcoin price in euros and create a bitcoin purchase order of 10 bitcoins at this current bitcoin euros price")
        .tools(new BitcoinTool())
        .tools(toolCallback)
        .call()
        .content();
}

Por otro lado, existen las siguientes limitaciones en el uso de métodos como “herramientas”. Básicamente, los siguientes tipos no se soportan como parámetros ni retornos de los métodos:

  • Optional
  • Tipos asíncronos (CompletableFuture, Future)
  • Tipos reactivos (Flow, Mono, Flux)
  • Tipos funcionales (Function, Supplier, Consumer). Estos tipos están soportados pero enfocados de otra forma como se puede comprobar en la siguiente sección.

Funciones como herramientas

Otra forma de crear “herramientas” es a través de funciones. De la misma forma que los métodos, existen dos formas:

  • Dinámica: se pueden definir las funciones como beans de Spring y que se resuelvan dinámicamente en tiempo de ejecución gracias a la clase ToolCallbackResolver. El nombre del bean será usado como el nombre de la “herramienta” y la anotación @Description para la descripción, para que el modelo pueda entender cuándo y cómo llamar a la “herramienta”. Además, se puede hacer uso de la anotación @ToolParam para indicar información adicional de los parámetros.

    Como parte negativa de esta aproximación, no se asegura la seguridad de tipos (type safety). Para solucionarlo se puede indicar el nombre de la “herramienta” de forma explícita en la anotación @Bean y guardar el valor en una constante para usarlo en las peticiones de chat.
@Configuration(proxyBeanMethods = false)
class WeatherTools {
    
    public static final String CURRENT_WEATHER_TOOL = "currentWeather";
WeatherService weatherService = new WeatherService();

    @Bean(CURRENT_WEATHER_TOOL)
    @Description("Get the weather in location")
    Function<WeatherRequest, WeatherResponse> currentWeather() {
        return weatherService;
    }
}
  • Programática: creando una FunctionToolCallback indicando los parámetros name, toolFunction, description, inputType, inputSchema, toolMetadata y toolCallResultConverter. Su declaración y uso es muy similar a la sección correspondiente de métodos como herramientas.

    Los parámetros de entrada y salida pueden ser Void o POJOs (deben ser serializables). Tanto la función como los tipos de entrada y salida deben ser public.

Aún con todo esto, existen las siguientes limitaciones en el uso de funciones como “herramientas”, no soportando los siguientes tipos como parámetros de entrada o salida de las funciones:

  • Tipos primitivos
  • Optional
  • Tipos de colección (List, Map, Array, Set)
  • Tipos asíncronos (CompletableFuture, Future)
  • Tipos reactivos (Flow, Mono, Flux)

Nota: se debe remarcar la importancia de crear una buena descripción para las “herramientas”, puesto que esto puede llevar a que el modelo no entienda que tiene que usar esa “herramienta” o usarla de forma incorrecta para generar una respuesta coherente.

Ejecución de herramientas

La ejecución de las “herramientas” se realiza mediante un proceso manejado por la interfaz ToolCallingManager. La implementación por defecto es a través de la clase DefaultToolCallingManager aunque se puede personalizar creando un bean de ToolCallingManager.

En la siguiente imagen se puede ver el flujo por defecto:

Se incluye la definición de la “herramienta” en la petición de chat (ChatRequest) y se llama al modelo que recibe dicha petición.  Cuando el modelo decide llamar a una “herramienta” envía una respuesta (ChatResponse) con el nombre de la “herramienta” y los parámetros correspondientes.  ChatModel envía la petición al ToolCallingManager.  El ToolCallingManager identifica y ejecuta la “herramienta”.  El resultado de la ejecución se devuelve al ToolCallingManager.  El ToolCallingManager devuelve el resultado al ChatModel.  El ChatModel envía el resultado al modelo.  El modelo genera la respuesta final usando el resultado de la “herramienta” como información adicional.
  1. Se incluye la definición de la “herramienta” en la petición de chat (ChatRequest) y se llama al modelo que recibe dicha petición.
  2. 2 Cuando el modelo decide llamar a una “herramienta”, envía una respuesta (ChatResponse) con el nombre de la “herramienta” y los parámetros correspondientes.
  3. ChatModel envía la petición al ToolCallingManager.
  4. El ToolCallingManager identifica y ejecuta la “herramienta”.
  5. El resultado de la ejecución se devuelve al ToolCallingManager.
  6. El ToolCallingManager devuelve el resultado al ChatModel.
  7. El ChatModel envía el resultado al modelo.
  8. El modelo genera la respuesta final usando el resultado de la “herramienta” como información adicional.

Descubrimiento de herramientas

La principal forma de usar las “herramientas” es de la forma que se ha explicado en las secciones anteriores (Métodos y funciones como herramientas), pero Spring AI también ofrece la opción de obtener “herramientas” de forma dinámica en tiempo de ejecución con la interfaz ToolCallbackResolver y los nombres de las “herramientas”.

public interface ToolCallbackResolver {

    @Nullable
    ToolCallback resolve(String toolName);

}

Por defecto, las instancias que implementan ToolCallbackResolver serían:

  • SpringBeanToolCallbackResolver: para resolver “herramientas” derivadas de beans de Spring de tipo Function, Supplier, Consumer o BiFunction.
  • StaticToolCallbackResolver: para resolver “herramientas” derivadas de una lista estática de instancias de ToolCallback.

Especificación

En la documentación también se detalla la especificación de cara a tener más control sobre la personalización de las “herramientas”.

Demo

Creamos una app con los siguientes endpoints para ejemplificar lo visto hasta ahora:

  • /advisor/: endpoint de interacción cuya finalidad es probar que el LLM no tiene memoria. La idea es realizar primero una llamada indicando nuestro nombre para, en la siguiente, preguntarle cuál es nuestro nombre.
Hola, me llamo Jose. ¡Hola! bienvenido a nuestro espacio. en qué puedo ayudarte hoy, josé? tienes alguna pregunta o necesitas algo en particular? estoy aquí para escucharte y responder a tus preguntas de manera amable y objetiva.
¿Cuál es mi nombre? No puedo conocer tu nombre. ¿Hay algo más en lo que pueda ayudarte?
  • /advisor/memory: prueba que, incluyendo el advisor de MessageChatMemoryAdvisor, el LLM tiene una memoria de la conversación. Igual que en el endpoint anterior, primero nos presentamos y luego le preguntamos cuál es nuestro nombre.
Hola, mi nombre es Jose. ¡Hola, Jose! ¿En qué puedo ayudarte hoy? ¿Hay algo en particular que te gustaría hablar o necesitas información sobre algún tema en particular? Estoy aquí para escucharte y responder a tus preguntas.
¿Cuál es mi nombre? Tu nombre es Jose, como mencionaste anteriormente. ¿Qué tal? ¿Te parece bien?
  • /advisor/custom: endpoint que hace uso de un advisor personalizado. Básicamente, lo que hace es enviar el input del usuario al modelo en mayúsculas y la respuesta del modelo la devuelve al usuario en minúsculas. Se puede ver el comportamiento en los logs y la propia respuesta del endpoint.
Dame información de España. Escribe solo 1 línea. hasta mi fecha de conocimiento en diciembre de 2023, españa era un país europeo y una monarquía constitucional con una monarquía constitucional, gobernada por el rey felipe vi desde el año 1975.
c.e.s.d.s.advisors.CustomAdvisor Input transformed DAME INFORMACION DE ESPANA. ESCRIBE SOLO 1 LINEA. c.e.s.d.s.advisors.CustomAdvisor Peticion al modelo AdvisedRequest chatModel=org.springframework.a1. c.e.s.d.s.advisors.CustomAdvisor Orignal response AdvisedResponse response=ChatResponse metadata={
  • /structured-output/map: endpoint que formatea la salida como un Map con la información de 3 criptomonedas. Se puede ver que no tiene un comportamiento consistente para reflejar la salida en el formato indicado (como se ha visto en ejemplos anteriores, las transformaciones de JSON o formatos en concreto parece que no es precisa por parte del LLM) llegando incluso a dar error en la llamada.
{ "Criptomonedas": { "Bitcoin": { "abreviatura": "ВТС", "descripcion": "la criptomoneda mas importante" { "Ethereum": { "abreviatura": "ETH", "descripcion": "la segunda criptomoneda mas importante" "Tether": { "abreviatura": "USDT", "descripcion": "moneda de reserva fuerte" }
"ethereum": "{"abreviatura":"ETH", "descripcion":"la segunda criptomoneda mas importante"}", "litecoin": "{"abreviatura":"LTC","descripcion":"la tercera criptomoneda mas importante"}", "bitcoin": "{"abreviatura":"BTC", "descripcion":"la criptomoneda mas importante"}"
  • /structured-output/list: endpoint que formatea la salida como un List de 5 criptomonedas.
“Bitcoin”, “Ethereum”, “Bitcoin Cash”, “Litecoin”, “Dogecoin”,
  • /tool-calling/: endpoint con el que se prueba que el modelo no tiene acceso a la información en tiempo real como, por ejemplo, el precio del Bitcoin en estos momentos.
I'm not able to provide real-time financial data or current prices for cryptocurrencies like Bitcoin. However, I can suggest some options for you to find the current Bitcoin price. You can check popular cryptocurrency exchange websites such as Coinbase, Binance, or Kraken, which often provide up-to-date pricing information for Bitcoin and other cryptocurrencies. You can also try searching online for "Bitcoin price" or "current Bitcoin price" to find various sources that may have the information you're looking for. Additionally, you can check reputable financial news websites and apps, such as Bloomberg, CNBC, or CoinMarketCap, which often provide up-to-date pricing information for cryptocurrencies, including Bitcoin.
  • /tool-calling/btc: endpoint que hace uso de una “herramienta” para poder obtener el precio actual del Bitcoin en euros.
The current Bitcoin price is approximately 63600 EUR. Please note that cryptocurrency prices can fluctuate rapidly and may change significantly over time. This information is provided for informational purposes only and should not be considered as investment advice. If you would like to know the current price of Bitcoin in USD, I can try to provide an update.
  • /tool-calling/btc-purchase-order: endpoint que gestiona dos “herramientas”, una implementada de forma declarativa y otra de forma programática. Con una herramienta se obtiene el precio actual del Bitcoin en euros y, al mismo tiempo, se crea una orden de compra de 10 bitcoins para ese precio. Se debe indicar que al menos con el modelo empleado (llama3.2:1b) no se consigue crear la orden de compra de forma correcta debido a que no interpreta correctamente el precio a enviar como parámetro, creando la orden con precios aleatorios. Las llamadas a dichas “herramientas” se pueden ver en los logs.
c.e.s.d.springai demo.tools.BitcoinTool : The current bitcoin price in euros is 63600 c.e.s.d.s.tools.Purchase0rderTool : Create a purchase order for 10 btc with each btc at 1000 euros price
  • /tool-calling/function: endpoint que gestiona una “herramienta” de “tipo function”. Básicamente, funciona como los endpoints anteriores, creando una “compra” de Bitcoin que se puede ver en los logs.
Creating bitcoin purchase order with function criptomarketservice criptomarketResponse/currency=BC. amount=2, currencyPrice=80000, totalPrice=160000)

En este enlace se puede descargar el código de la aplicación de ejemplo.

Conclusión

En este capítulo de Spring AI se han analizado distintas formas de enriquecer o transformar la entrada y salida del modelo para la generación de respuestas coherentes, incluso con información en tiempo real, aunque es cierto que todavía quedan detalles por pulir.

En siguientes posts nos centraremos en el famoso RAG y las funcionalidades que lo soportan.

Referencias

Cuéntanos qué te parece.

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.