1616package io .agentscope .examples .quickstart ;
1717
1818import io .agentscope .core .agent .Agent ;
19+ import io .agentscope .core .message .ContentBlock ;
1920import io .agentscope .core .message .Msg ;
2021import io .agentscope .core .message .MsgRole ;
2122import io .agentscope .core .message .TextBlock ;
23+ import io .agentscope .core .message .ThinkingBlock ;
2224import io .agentscope .examples .quickstart .util .MsgUtils ;
2325import java .io .BufferedReader ;
2426import java .io .IOException ;
2527import java .io .InputStreamReader ;
28+ import java .util .concurrent .atomic .AtomicBoolean ;
29+ import java .util .concurrent .atomic .AtomicReference ;
30+ import java .util .stream .Collectors ;
2631
2732/**
2833 * Utility class providing common functionality for examples.
2934 *
30- * <p>Features:
35+ * <p>
36+ * Features:
3137 *
3238 * <ul>
33- * <li>Interactive API key configuration
34- * <li>Chat loop implementation
35- * <li>Helper methods for user interaction
39+ * <li>Interactive API key configuration
40+ * <li>Chat loop implementation
41+ * <li>Helper methods for user interaction
3642 * </ul>
3743 */
3844public class ExampleUtils {
@@ -64,9 +70,9 @@ public static String getOpenAIApiKey() throws IOException {
6470 /**
6571 * Get API key from environment variable or interactive input.
6672 *
67- * @param envVarName environment variable name
73+ * @param envVarName environment variable name
6874 * @param serviceName service name for display
69- * @param helpUrl URL to get API key
75+ * @param helpUrl URL to get API key
7076 * @return API key
7177 * @throws IOException if input fails
7278 */
@@ -144,15 +150,96 @@ public static void startChat(Agent agent) throws IOException {
144150 .content (TextBlock .builder ().text (input ).build ())
145151 .build ();
146152
147- Msg response = agent . call ( userMsg ). block ( );
153+ System . out . print ( "Agent> " );
148154
149- if (response != null ) {
150- System .out .println ("Agent> " + MsgUtils .getTextContent (response ) + "\n " );
151- } else {
152- System .out .println ("Agent> [No response]\n " );
155+ try {
156+ // Try to use stream() first for real-time output
157+ AtomicBoolean hasPrintedThinkingHeader = new AtomicBoolean (false );
158+ AtomicBoolean hasPrintedTextHeader = new AtomicBoolean (false );
159+ AtomicBoolean hasPrintedTextSeparator = new AtomicBoolean (false );
160+ AtomicReference <String > lastThinkingContent = new AtomicReference <>("" );
161+ AtomicReference <String > lastTextContent = new AtomicReference <>("" );
162+
163+ agent .stream (userMsg )
164+ .doOnNext (
165+ event -> {
166+ Msg msg = event .getMessage ();
167+ for (ContentBlock block : msg .getContent ()) {
168+ if (block instanceof ThinkingBlock ) {
169+ printStreamContent (
170+ ((ThinkingBlock ) block ).getThinking (),
171+ lastThinkingContent ,
172+ hasPrintedThinkingHeader ,
173+ "> Thinking: " ,
174+ null );
175+ } else if (block instanceof TextBlock ) {
176+ printStreamContent (
177+ ((TextBlock ) block ).getText (),
178+ lastTextContent ,
179+ hasPrintedTextHeader ,
180+ "Text: " ,
181+ () -> {
182+ if (hasPrintedThinkingHeader .get ()
183+ && !hasPrintedTextSeparator
184+ .get ()) {
185+ System .out .print ("\n \n " );
186+ hasPrintedTextSeparator .set (true );
187+ }
188+ });
189+ }
190+ }
191+ })
192+ .blockLast ();
193+ } catch (Exception e ) {
194+ // Fallback to call() if streaming is not supported or fails
195+ if (e instanceof UnsupportedOperationException ) {
196+ System .err .println (
197+ "\n [Info] Streaming not supported by this agent. Falling back to"
198+ + " call()." );
199+ } else {
200+ System .err .println (
201+ "\n [Warning] Exception during streaming: " + e .getMessage ());
202+ e .printStackTrace ();
203+ System .err .println ("[Info] Falling back to call()." );
204+ }
205+
206+ Msg response = agent .call (userMsg ).block ();
207+ if (response != null ) {
208+ // Extract thinking and text separately to match streaming format
209+ String thinking =
210+ response .getContent ().stream ()
211+ .filter (block -> block instanceof ThinkingBlock )
212+ .map (block -> ((ThinkingBlock ) block ).getThinking ())
213+ .collect (Collectors .joining ("\n " ));
214+
215+ String text =
216+ response .getContent ().stream ()
217+ .filter (block -> block instanceof TextBlock )
218+ .map (block -> ((TextBlock ) block ).getText ())
219+ .collect (Collectors .joining ("\n " ));
220+
221+ boolean hasContent = false ;
222+ if (!thinking .isEmpty ()) {
223+ System .out .print ("> Thinking: " + thinking );
224+ hasContent = true ;
225+ }
226+ if (!text .isEmpty ()) {
227+ if (hasContent ) {
228+ System .out .print ("\n \n " );
229+ }
230+ System .out .print ("Text: " + text );
231+ hasContent = true ;
232+ }
233+ if (!hasContent ) {
234+ System .out .print ("[No response]" );
235+ }
236+ }
153237 }
238+
239+ System .out .println ("\n " );
240+
154241 } catch (Exception e ) {
155- System .err .println ("Error : " + e .getMessage ());
242+ System .err .println ("\n Error : " + e .getMessage ());
156243 e .printStackTrace ();
157244 }
158245 }
@@ -171,7 +258,7 @@ public static String readLine() throws IOException {
171258 /**
172259 * Print a welcome banner.
173260 *
174- * @param title example title
261+ * @param title example title
175262 * @param description example description
176263 */
177264 public static void printWelcome (String title , String description ) {
@@ -189,4 +276,49 @@ public static void printWelcome(String title, String description) {
189276 public static String extractTextFromMsg (Msg msg ) {
190277 return MsgUtils .getTextContent (msg );
191278 }
279+
280+ /**
281+ * Helper method to print streaming content.
282+ *
283+ * @param content content to print
284+ * @param lastContentRef reference to the last content for delta
285+ * calculation
286+ * @param hasPrintedHeaderRef reference to whether the header has been printed
287+ * @param header header to print
288+ * @param prePrintAction action to run before printing (e.g., adding
289+ * separators)
290+ */
291+ private static void printStreamContent (
292+ String content ,
293+ AtomicReference <String > lastContentRef ,
294+ AtomicBoolean hasPrintedHeaderRef ,
295+ String header ,
296+ Runnable prePrintAction ) {
297+ String lastContent = lastContentRef .get ();
298+ String toPrint ;
299+
300+ // Detect if cumulative or incremental
301+ if (content .startsWith (lastContent )) {
302+ // Cumulative: print only new part
303+ toPrint = content .substring (lastContent .length ());
304+ lastContentRef .set (content );
305+ } else {
306+ // Incremental: print as-is and append
307+ toPrint = content ;
308+ lastContentRef .set (lastContent + content );
309+ }
310+
311+ if (!toPrint .isEmpty ()) {
312+ if (prePrintAction != null ) {
313+ prePrintAction .run ();
314+ }
315+
316+ if (!hasPrintedHeaderRef .get ()) {
317+ System .out .print (header );
318+ hasPrintedHeaderRef .set (true );
319+ }
320+ System .out .print (toPrint );
321+ System .out .flush ();
322+ }
323+ }
192324}
0 commit comments