Skip to content

Commit 9aee4aa

Browse files
sobychackotzolov
authored andcommitted
GH-876: stream.content() swallowing blank space
* Fixing an issue where calling stream.content() method swallows leading blank spaces. Resolves #1089 Resolves #1089
1 parent aa18a67 commit 9aee4aa

File tree

4 files changed

+78
-7
lines changed

4 files changed

+78
-7
lines changed

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,55 @@ void streamCompletenessTest() throws InterruptedException {
107107
});
108108
}
109109

110+
@Test
111+
void streamCompletenessTestWithChatResponse() throws InterruptedException {
112+
UserMessage userMessage = new UserMessage("Who is George Washington? - use first as 1st");
113+
Prompt prompt = new Prompt(List.of(userMessage));
114+
115+
StringBuilder answer = new StringBuilder();
116+
CountDownLatch latch = new CountDownLatch(1);
117+
118+
ChatClient chatClient = ChatClient.builder(openAiChatModel).build();
119+
120+
Flux<ChatResponse> chatResponseFlux = chatClient.prompt(prompt)
121+
.stream()
122+
.chatResponse()
123+
.doOnNext(chatResponse -> {
124+
String responseContent = chatResponse.getResults().get(0).getOutput().getContent();
125+
answer.append(responseContent);
126+
})
127+
.doOnComplete(() -> {
128+
logger.info(answer.toString());
129+
latch.countDown();
130+
});
131+
chatResponseFlux.subscribe();
132+
assertThat(latch.await(120, TimeUnit.SECONDS)).isTrue();
133+
assertThat(answer).contains("the 1st ");
134+
}
135+
136+
@Test
137+
void ensureChatResponseAsContentDoesNotSwallowBlankSpace() throws InterruptedException {
138+
UserMessage userMessage = new UserMessage("Who is George Washington? - use first as 1st");
139+
Prompt prompt = new Prompt(List.of(userMessage));
140+
141+
StringBuilder answer = new StringBuilder();
142+
CountDownLatch latch = new CountDownLatch(1);
143+
144+
ChatClient chatClient = ChatClient.builder(openAiChatModel).build();
145+
146+
Flux<String> chatResponseFlux = chatClient.prompt(prompt)
147+
.stream()
148+
.content()
149+
.doOnNext(answer::append)
150+
.doOnComplete(() -> {
151+
logger.info(answer.toString());
152+
latch.countDown();
153+
});
154+
chatResponseFlux.subscribe();
155+
assertThat(latch.await(120, TimeUnit.SECONDS)).isTrue();
156+
assertThat(answer).contains("the 1st ");
157+
}
158+
110159
@Test
111160
void streamRoleTest() {
112161
UserMessage userMessage = new UserMessage(

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/testutils/AbstractIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.ai.openai.testutils;
1718

1819
import java.util.List;
@@ -31,6 +32,7 @@
3132
import org.springframework.ai.image.ImageModel;
3233
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
3334
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
35+
import org.springframework.ai.openai.OpenAiChatModel;
3436
import org.springframework.beans.factory.annotation.Autowired;
3537
import org.springframework.beans.factory.annotation.Value;
3638
import org.springframework.core.io.Resource;
@@ -48,6 +50,9 @@ public abstract class AbstractIT {
4850
@Autowired
4951
protected StreamingChatModel streamingChatModel;
5052

53+
@Autowired
54+
protected OpenAiChatModel openAiChatModel;
55+
5156
@Autowired
5257
protected OpenAiAudioTranscriptionModel transcriptionModel;
5358

spring-ai-core/src/main/java/org/springframework/ai/chat/client/ChatClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 - 2024 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -162,7 +162,7 @@ interface StreamPromptResponseSpec {
162162

163163
Flux<ChatResponse> chatResponse();
164164

165-
public Flux<String> content();
165+
Flux<String> content();
166166

167167
}
168168

spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.springframework.ai.chat.client;
218

319
import java.io.IOException;
@@ -44,6 +60,7 @@
4460
* @author Christian Tzolov
4561
* @author Josh Long
4662
* @author Arjen Poutsma
63+
* @author Soby Chacko
4764
* @since 1.0.0
4865
*/
4966
public class DefaultChatClient implements ChatClient {
@@ -204,7 +221,7 @@ protected Map<String, Object> params() {
204221

205222
public static class DefaultAdvisorSpec implements AdvisorSpec {
206223

207-
private List<RequestResponseAdvisor> advisors = new ArrayList<>();
224+
private final List<RequestResponseAdvisor> advisors = new ArrayList<>();
208225

209226
private final Map<String, Object> params = new HashMap<>();
210227

@@ -446,7 +463,7 @@ public Flux<String> content() {
446463
return "";
447464
}
448465
return r.getResult().getOutput().getContent();
449-
}).filter(v -> StringUtils.hasLength(v));
466+
}).filter(StringUtils::hasLength);
450467
}
451468

452469
}
@@ -473,7 +490,7 @@ public static class DefaultChatClientRequestSpec implements ChatClientRequestSpe
473490

474491
private final Map<String, Object> systemParams = new HashMap<>();
475492

476-
private List<RequestResponseAdvisor> advisors = new ArrayList<>();
493+
private final List<RequestResponseAdvisor> advisors = new ArrayList<>();
477494

478495
private final Map<String, Object> advisorParams = new HashMap<>();
479496

@@ -804,7 +821,7 @@ public Flux<String> content() {
804821
return "";
805822
}
806823
return r.getResult().getOutput().getContent();
807-
}).filter(v -> StringUtils.hasText(v));
824+
}).filter(StringUtils::hasLength);
808825
}
809826

810827
}
@@ -825,7 +842,7 @@ public CallPromptResponseSpec call() {
825842
}
826843

827844
public StreamPromptResponseSpec stream() {
828-
return new DefaultStreamPromptResponseSpec((StreamingChatModel) this.chatModel, this.prompt);
845+
return new DefaultStreamPromptResponseSpec(this.chatModel, this.prompt);
829846
}
830847

831848
}

0 commit comments

Comments
 (0)