¿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 3 días Cargando comentarios…
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:
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.
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.
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.
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.
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.
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:
En el caso de que se necesite ejecutar un Advisor primero en la petición y en la respuesta:
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
}
Spring AI proporciona algunos Advisors ya implementados:
memoryChatClient = chatClientBuilder
.clone()
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
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.
En estos momentos Spring AI proporciona las siguientes implementaciones de Converters:
@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);
}
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:
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.
Los conceptos y componentes importantes para el funcionamiento del Tool Calling son:
A continuación veremos varias formas de definir y ejecutar las “herramientas”.
Se proporcionan dos formas de crear “herramientas” desde métodos:
@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.
@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:
Otra forma de crear “herramientas” es a través de funciones. De la misma forma que los métodos, existen dos formas:
@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;
}
}
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:
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.
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:
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:
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”.
Creamos una app con los siguientes endpoints para ejemplificar lo visto hasta ahora:
En este enlace se puede descargar el código de la aplicación de ejemplo.
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
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.