领先一步
VMware 提供培训和认证,助您加速进步。
了解更多
Spring AI ChatClient 提供了一个流畅的 API,用于与 AI 模型进行通信。流畅的 API 提供了构建提示的组成部分的方法,这些组成部分作为输入传递给 AI 模型。
顾问是流畅 API 的关键组成部分,它们拦截、修改和增强 AI 驱动的交互。其主要优点包括封装常见的生成式 AI 模式、转换发送到和来自大型语言模型 (LLM) 的数据,以及为各种模型和用例提供可移植性。
顾问会处理 ChatClientRequest 和 ChatClientResponse 对象。框架会根据顾问的 getOrder() 值(值越低越先执行)将顾问串联起来,最后一个顾问会调用 LLM。
Spring AI 提供内置顾问,用于对话记忆、检索增强生成 (RAG)、日志记录和护栏。开发人员还可以创建自定义顾问。
典型顾问结构
public class MyAdvisor implements CallAdvisor {
// 1. Sets the advisor orders
public int getOrder() { return MY_ADVISOR_ORDER; }
// 2. Sets the advisor name
public String getName() { return MY_ADVISOR_NAME; }
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 3. Pre-process the request (modify, validate, log, etc.)
request = preProcess(request);
// 4. Call the next advisor in the chain
ChatClientResponse response = chain.nextCall(request);
// 5. Post-process the response (modify, validate, log, etc.)
return postProcess(response);
}
}
从版本 1.1.0-M4 开始,Spring AI 引入了递归顾问,支持多次循环执行顾问链,以支持迭代工作流
传统的单次执行顾问模式无法充分处理这些场景。
递归顾问会多次循环执行下游顾问链,重复调用 LLM,直到满足某个条件。
CallAdvisorChain.copy(CallAdvisor after) 方法会创建一个只包含下游顾问的子链,从而在保持适当的顺序和可观测性的同时实现受控迭代,并防止上游顾问重复执行。
该图说明了递归顾问如何通过允许流程多次循环返回顾问链来启用迭代处理。与传统的单次执行不同,递归顾问可以根据响应条件重新访问先前的顾问,从而在客户端、顾问和 LLM 之间创建复杂的迭代工作流。
以下是实现递归顾问的基本模式
public class MyRecursiveAdvisor implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// Call the chain initially
ChatClientResponse response = chain.nextCall(request);
// Check if we need to retry
while (!isConditionMet(response)) {
// Modify the request based on the response
ChatClientRequest modifiedRequest = modifyRequest(request, response);
// Create a sub-chain and recurse
response = chain.copy(this).nextCall(modifiedRequest);
}
return response;
}
}
与非递归顾问的关键区别在于使用 chain.copy(this).nextCall(...) 而不是 chain.nextCall(...) 来迭代内部链的副本。这确保了每次迭代都通过完整的下游链,允许其他顾问观察和拦截,同时保持适当的可观测性。
⚠️ 重要提示
递归顾问是 Spring AI 1.1.0-M4 中一个新的实验性功能。它们仅支持非流式传输,需要仔细的顾问排序,并且由于多次 LLM 调用而可能增加成本。
对于维护外部状态的内部顾问要特别小心——它们可能需要额外关注以在迭代过程中保持正确性。
始终设置终止条件和重试限制,以防止无限循环。
考虑您的用例是更受益于递归顾问,还是更受益于在您的应用程序代码中围绕 ChatClient 调用实现显式 while 循环。
Spring AI 1.1.0-M4 提供两个递归顾问
默认的工具调用支持
默认情况下,Spring AI 工具执行在每个 ChatModel 实现内部使用 ToolCallingManager 实现。这意味着工具调用请求和响应流程对于 ChatClient 顾问是不透明的,因为它发生在顾问执行链之外。
ToolCallAdvisor
ToolCallAdvisor 利用用户控制的工具执行,在顾问链中实现工具调用循环,提供对工具执行的显式控制,而不是委托给模型的内部工具执行。
示例用法
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
public record Request(String location) {}
var weatherTool = FunctionToolCallback.builder("getWeather", (Request request) -> "15.0°C")
.description("Gets the weather for a location")
.inputType(Request.class)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(weatherTool) // Tools registration
.defaultAdvisors(toolCallAdvisor) // Tool Call Execution as Advisor
.build();
String response = chatClient.prompt()
.user("What's the weather in Paris and Amsterdam and convert the temperature to Fahrenheit?")
.call()
.content();
注意:默认情况下,ToolCallAdvisor 的顺序设置为接近 Ordered.HIGHEST_PRECEDENCE,以确保顾问在链中首先执行(请求处理时首先执行,响应处理时最后执行),从而允许内部顾问拦截和处理工具请求和响应消息。
当工具执行具有 returnDirect=true 时,顾问会执行该工具,检测到该标志,中断循环,并直接将输出返回给客户端,而无需将其发送给 LLM。当工具的输出是最终答案时,这可以减少延迟。
💡 演示项目:请参阅 递归顾问演示项目中的 ToolCallAdvisor 的完整工作示例。
StructuredOutputValidationAdvisor 会根据生成的模式验证结构化 JSON 输出,并在验证失败时重试
ObjectMapper示例用法
public record ActorFilms(String actor, List<String> movies) {}
var validationAdvisor = StructuredOutputValidationAdvisor.builder()
.outputType(ActorFilms.class)
.maxRepeatAttempts(3)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(validationAdvisor)
.build();
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for Tom Hanks")
.call()
.entity(ActorFilms.class);
当验证失败时,顾问会使用错误详细信息增强提示,并重试,直到达到配置的最大尝试次数。
注意:默认情况下,StructuredOutputValidationAdvisor 的顺序设置为接近 Ordered.LOWEST_PRECEDENCE,以确保顾问在链的末尾执行(但在模型调用之前),这意味着它在请求处理时最后执行,在响应处理时首先执行。
递归顾问会增加 LLM 调用的次数,从而影响:
为了优化,请设置合理的重试限制并监控迭代次数。尽可能缓存中间结果。