Refactor advisor architecture in Spring AI
This commit introduces a major overhaul of the advisor system in Spring AI, improving modularity, type safety, and consistency Core Changes: - Replace RequestAdvisor and ResponseAdvisor with CallAroundAdvisor and StreamAroundAdvisor - Introduce AdvisedRequest and AdvisedResponse classes for better encapsulation - Deprecate RequestResponseAdvisor in favor of new advisor types - Remove AdvisorObservableHelper class Advisor Implementation Updates: - Update AbstractChatMemoryAdvisor, MessageChatMemoryAdvisor, PromptChatMemoryAdvisor, QuestionAnswerAdvisor, SafeGuardAroundAdvisor, SimpleLoggerAdvisor, and VectorStoreChatMemoryAdvisor to implement new advisor interfaces - Remove CacheAroundAdvisor (functionality likely moved elsewhere) - Make CallAroundAdvisor and StreamAroundAdvisor extend Ordered interface Client and Chain Management: - Modify DefaultChatClient to use new advisor chain approach - Refactor DefaultAroundAdvisorChain for better ordering and observation - Implement builder pattern for advisor chain construction in DefaultChatClient - Separate call and stream advisors in DefaultAroundAdvisorChain Observation and Context Handling: - Update observation conventions and context handling in advisors - Add order field to AdvisorObservationContext - Modify DefaultAdvisorObservationConvention to include order in high cardinality key values Testing and Integration: - Refactor ChatClientAdvisorTests and add new AdvisorsTests - Update integration tests to reflect new advisor structure - Enhance AdvisorsTests to verify correct advisor execution order New Features: - Generalize the Protect From Blocking functionality across all advisors - Add (experimental) Re2 advisor to enhance reasoning capabilities of LLMs - Add disabled Re2 test in OpenAiChatClientIT Documentation: - Add Advisors documentation - Enhance advisors documentation with order explanation and Re2 example Advisor Ordering: - Introduce Advisor constants for precedence ordering - Update AbstractChatMemoryAdvisor to use new precedence constant - Improve advisor ordering and management in DefaultAroundAdvisorChain.Builder - Remove redundant reordering logic from DefaultAroundAdvisorChain These changes aim to provide a more flexible and powerful advisor system, allowing for easier implementation of complex AI-driven interactions Co-authored-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
This commit is contained in:
committed by
Mark Pollack
parent
c81972ec45
commit
6fc76b7f9b
@@ -28,11 +28,11 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.api.RequestAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.ResponseAdvisor;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
@@ -65,7 +65,7 @@ public class OpenAiPaymentTransactionIT {
|
||||
record TransactionStatusResponse(String id, String status) {
|
||||
}
|
||||
|
||||
private static class LoggingAdvisor implements RequestAdvisor, ResponseAdvisor {
|
||||
private static class LoggingAdvisor implements CallAroundAdvisor {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LoggingAdvisor.class);
|
||||
|
||||
@@ -74,7 +74,23 @@ public class OpenAiPaymentTransactionIT {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
|
||||
|
||||
advisedRequest = this.before(advisedRequest);
|
||||
|
||||
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
|
||||
|
||||
this.observeAfter(advisedResponse);
|
||||
|
||||
return advisedResponse;
|
||||
}
|
||||
|
||||
private AdvisedRequest before(AdvisedRequest request) {
|
||||
logger.info("System text: \n" + request.systemText());
|
||||
logger.info("System params: " + request.systemParams());
|
||||
logger.info("User text: \n" + request.userText());
|
||||
@@ -86,10 +102,8 @@ public class OpenAiPaymentTransactionIT {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
|
||||
logger.info("Response: " + response);
|
||||
return response;
|
||||
private void observeAfter(AdvisedResponse advisedResponse) {
|
||||
logger.info("Response: " + advisedResponse.response());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -64,6 +65,38 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
record ActorsFilms(String actor, List<String> movies) {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Although the Re2 advisor improves the response correctness it is not always guarantied to work.")
|
||||
void re2() {
|
||||
// .user(" Could Scooby Doo fit in a Kangaroo Pouch? Choices: (A) Yes (B) No")
|
||||
// .user("Roger has 5 tennis balls. He buys 2 more cans of tennis " +
|
||||
// "balls. Each can has 3 tennis balls. How many tennis balls " +
|
||||
// "does he have now?")
|
||||
|
||||
String REASON_QUESTION = """
|
||||
What do these words have in common?
|
||||
Freight Stone Often Canine.
|
||||
""";
|
||||
|
||||
// @formatter:off
|
||||
ChatClient chatClient = ChatClient.builder(chatModel)
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue()).build())
|
||||
.defaultUser(REASON_QUESTION)
|
||||
.build();
|
||||
|
||||
String response = chatClient.prompt()
|
||||
.advisors(new ReReadingAdvisor())
|
||||
.call()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
logger.info("" + response);
|
||||
assertThat(response.toLowerCase().replace("(", " ").replace(")", " ").replace("\"", " ").replace("\"", " "))
|
||||
.contains(" eight", " one", " ten", " nine");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void call() {
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.openai.chat.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
|
||||
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* Drawing inspiration from the human strategy of re-reading, this advisor implements a
|
||||
* re-reading strategy for LLM reasoning, dubbed RE2, to enhance understanding in the
|
||||
* input phase. Based on the article:
|
||||
* <a href="https://arxiv.org/pdf/2309.06275">Re-Reading Improves Reasoning in Large
|
||||
* Language Models</a>
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
|
||||
|
||||
private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
|
||||
{re2_input_query}
|
||||
Read the question again: {re2_input_query}
|
||||
""";
|
||||
|
||||
private final String re2AdviseTemplate;
|
||||
|
||||
private int order = 0;
|
||||
|
||||
public ReReadingAdvisor() {
|
||||
this(DEFAULT_RE2_ADVISE_TEMPLATE);
|
||||
}
|
||||
|
||||
public ReReadingAdvisor(String re2AdviseTemplate) {
|
||||
this.re2AdviseTemplate = re2AdviseTemplate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
private AdvisedRequest before(AdvisedRequest advisedRequest) {
|
||||
|
||||
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
|
||||
advisedUserParams.put("re2_input_query", advisedRequest.userText());
|
||||
|
||||
return AdvisedRequest.from(advisedRequest)
|
||||
.withUserText(this.re2AdviseTemplate)
|
||||
.withUserParams(advisedUserParams)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
|
||||
return chain.nextAroundCall(this.before(advisedRequest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
|
||||
return chain.nextAroundStream(this.before(advisedRequest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public ReReadingAdvisor withOrder(int order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,11 +28,11 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.api.RequestAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.ResponseAdvisor;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
|
||||
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper.Builder.SchemaType;
|
||||
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
|
||||
@@ -65,7 +65,7 @@ public class VertexAiGeminiPaymentTransactionIT {
|
||||
record TransactionStatusResponse(String id, String status) {
|
||||
}
|
||||
|
||||
private static class LoggingAdvisor implements RequestAdvisor, ResponseAdvisor {
|
||||
private static class LoggingAdvisor implements CallAroundAdvisor {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LoggingAdvisor.class);
|
||||
|
||||
@@ -75,7 +75,18 @@ public class VertexAiGeminiPaymentTransactionIT {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
|
||||
var response = chain.nextAroundCall(before(advisedRequest));
|
||||
observeAfter(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private AdvisedRequest before(AdvisedRequest request) {
|
||||
logger.info("System text: \n" + request.systemText());
|
||||
logger.info("System params: " + request.systemParams());
|
||||
logger.info("User text: \n" + request.userText());
|
||||
@@ -87,10 +98,8 @@ public class VertexAiGeminiPaymentTransactionIT {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
|
||||
logger.info("Response: " + response);
|
||||
return response;
|
||||
private void observeAfter(AdvisedResponse advisedResponse) {
|
||||
logger.info("Response: " + advisedResponse.response());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user