Skip to content

Commit 323c590

Browse files
tzolovilayaperumalg
authored andcommitted
feat(advisors): Enable recursive advisor execution with two new built-in advisors
Add support for recursive (repetitive) advisor execution patterns, allowing advisors to re-invoke themselves or the remaining chain multiple times. This enables advanced patterns like retry logic, iterative refinement, and multi-pass processing. - Add AdvisorUtils.copyChainAfterAdvisor() utility to enable recursive chain invocation - Implement ToolCallAdvisor for recursive tool calling with configurable tool execution - Implement StructuredOutputValidationAdvisor for recursive output validation with retry logic - Add MCP JSON Jackson2 dependency for JSON schema validation - Add test suites for both new advisors and utility methods - Add documentation for recursive advisor patterns refactor(advisor): enhance StructuredOutputValidationAdvisor with validation feedback loop Refactor retry logic to provide validation error feedback to LLM for self-correction. Extract validation into separate method and augment prompts with error messages on retry. Add comprehensive test coverage for various validation scenarios including nested objects, lists, malformed JSON, and type mismatches. Improve StructuredOutputValidationAdvisor logic feat: Add return direct support and null safety to ToolCallAdvisor Implements return direct functionality allowing tools to bypass the LLM and return results directly to clients. Adds null safety checks for chatResponse and comprehensive test coverage. refactor: Move chain copying logic from AdvisorUtils to CallAdvisorChain interface - Add CallAdvisorChain.copy(CallAdvisor after) method to replace AdvisorUtils.copyChainAfterAdvisor() - Implement copy() method in DefaultAroundAdvisorChain - Update StructuredOutputValidationAdvisor and ToolCallAdvisor to use new copy() API - Add ObjectMapper parameter support to StructuredOutputValidationAdvisor for custom JSON processing - Improve ToolCallAdvisor return direct logic using break instead of flag - Move tests from AdvisorUtilsTests to DefaultAroundAdvisorChainTests - Update documentation to reflect API changes This refactoring improves the API design by moving chain manipulation logic closer to where it belongs (on the chain itself) rather than in a utility class. Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent 0b0024e commit 323c590

File tree

16 files changed

+2428
-3
lines changed

16 files changed

+2428
-3
lines changed

spring-ai-client-chat/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
<version>${project.version}</version>
4545
</dependency>
4646

47+
<dependency>
48+
<groupId>io.modelcontextprotocol.sdk</groupId>
49+
<artifactId>mcp-json-jackson2</artifactId>
50+
<version>${mcp.sdk.version}</version>
51+
</dependency>
52+
4753
<dependency>
4854
<groupId>com.fasterxml.jackson.module</groupId>
4955
<artifactId>jackson-module-jsonSchema</artifactId>

spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/AdvisorUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
/**
2626
* Utilities to work with advisors.
27+
*
28+
* @author Christian Tzolov
2729
*/
2830
public final class AdvisorUtils {
2931

spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.ai.chat.client.advisor.api.Advisor;
3131
import org.springframework.ai.chat.client.advisor.api.BaseAdvisorChain;
3232
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
33+
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
3334
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
3435
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext;
3536
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationConvention;
@@ -133,6 +134,24 @@ public Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest)
133134
});
134135
}
135136

137+
@Override
138+
public CallAdvisorChain copy(CallAdvisor after) {
139+
140+
Assert.notNull(after, "The after call advisor must not be null");
141+
142+
List<CallAdvisor> callAdvisors = this.getCallAdvisors();
143+
144+
int afterAdvisorIndex = callAdvisors.indexOf(after);
145+
146+
if (afterAdvisorIndex < 0) {
147+
throw new IllegalArgumentException("The specified advisor is not part of the chain: " + after.getName());
148+
}
149+
150+
var remainingCallAdvisors = callAdvisors.subList(afterAdvisorIndex + 1, callAdvisors.size());
151+
152+
return DefaultAroundAdvisorChain.builder(this.getObservationRegistry()).pushAll(remainingCallAdvisors).build();
153+
}
154+
136155
@Override
137156
public List<CallAdvisor> getCallAdvisors() {
138157
return this.originalCallAdvisors;

spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/SimpleLoggerAdvisor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@ public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest
8888
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse);
8989
}
9090

91-
private void logRequest(ChatClientRequest request) {
91+
protected void logRequest(ChatClientRequest request) {
9292
logger.debug("request: {}", this.requestToString.apply(request));
9393
}
9494

95-
private void logResponse(ChatClientResponse chatClientResponse) {
95+
protected void logResponse(ChatClientResponse chatClientResponse) {
9696
logger.debug("response: {}", this.responseToString.apply(chatClientResponse.chatResponse()));
9797
}
9898

0 commit comments

Comments
 (0)