Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
*
* @author Thomas Vitale
* @since 1.0.0
* @deprecated only introduced to smooth the transition to the new APIs and ensure
* backward compatibility
*/
@Deprecated
public enum ChatClientAttributes {

//@formatter:off
Expand All @@ -33,7 +30,6 @@ public enum ChatClientAttributes {
ADVISORS("spring.ai.chat.client.advisors"),
@Deprecated // Only for backward compatibility until the next release.
CHAT_MODEL("spring.ai.chat.client.model"),
@Deprecated // Only for backward compatibility until the next release.
OUTPUT_FORMAT("spring.ai.chat.client.output.format"),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a great approach using the advisor context for this, but we might re-evaluate later when considering the new structured output support.

@Deprecated // Only for backward compatibility until the next release.
USER_PARAMS("spring.ai.chat.client.user.params"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,11 +496,13 @@ private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest c

private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest,
@Nullable String outputFormat) {
ChatClientRequest formattedChatClientRequest = StringUtils.hasText(outputFormat)
? augmentPromptWithFormatInstructions(chatClientRequest, outputFormat) : chatClientRequest;

if (outputFormat != null) {
chatClientRequest.context().put(ChatClientAttributes.OUTPUT_FORMAT.getKey(), outputFormat);
}

ChatClientObservationContext observationContext = ChatClientObservationContext.builder()
.request(formattedChatClientRequest)
.request(chatClientRequest)
.advisors(advisorChain.getCallAdvisors())
.stream(false)
.withFormat(outputFormat)
Expand All @@ -510,7 +512,7 @@ private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest c
DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry);
var chatClientResponse = observation.observe(() -> {
// Apply the advisor chain that terminates with the ChatModelCallAdvisor.
return advisorChain.nextCall(formattedChatClientRequest);
return advisorChain.nextCall(chatClientRequest);
});
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@

package org.springframework.ai.chat.client.advisor;

import org.springframework.ai.chat.client.ChatClientAttributes;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.Map;

Expand All @@ -46,9 +49,29 @@ private ChatModelCallAdvisor(ChatModel chatModel) {
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAroundAdvisorChain chain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");

ChatResponse chatResponse = chatModel.call(chatClientRequest.prompt());
ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);

ChatResponse chatResponse = chatModel.call(formattedChatClientRequest.prompt());
return ChatClientResponse.builder()
.chatResponse(chatResponse)
.context(Map.copyOf(formattedChatClientRequest.context()))
.build();
}

private static ChatClientRequest augmentWithFormatInstructions(ChatClientRequest chatClientRequest) {
String outputFormat = (String) chatClientRequest.context().get(ChatClientAttributes.OUTPUT_FORMAT.getKey());

if (!StringUtils.hasText(outputFormat)) {
return chatClientRequest;
}

Prompt augmentedPrompt = chatClientRequest.prompt()
.augmentUserMessage(userMessage -> userMessage.mutate()
.text(userMessage.getText() + System.lineSeparator() + outputFormat)
.build());

return ChatClientRequest.builder()
.prompt(augmentedPrompt)
.context(Map.copyOf(chatClientRequest.context()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ void qaCustomPromptTemplate() {
evaluateRelevancy(question, chatResponse);
}

@Test
void qaOutputConverter() {
Copy link
Contributor Author

@ThomasVitale ThomasVitale May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case fails if we remove the change in DefaultChatClient. Unfortunately we didn't have any integration test combining output converter with advisors.

String question = "Where does the adventure of Anacletus and Birba take place?";

QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(this.pgVectorStore).build();

Answer answer = ChatClient.builder(this.openAiChatModel)
.build()
.prompt(question)
.advisors(qaAdvisor)
.call()
.entity(Answer.class);

assertThat(answer).isNotNull();

System.out.println(answer);
assertThat(answer.content()).containsIgnoringCase("Highlands");
}

private record Answer(String content) {
}

private void evaluateRelevancy(String question, ChatResponse chatResponse) {
EvaluationRequest evaluationRequest = new EvaluationRequest(question,
chatResponse.getMetadata().get(QuestionAnswerAdvisor.RETRIEVED_DOCUMENTS),
Expand Down