From 4347281fcd0c5aafddbd33d29f5abbfafca9d379 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 26 Oct 2025 12:24:41 +0800 Subject: [PATCH] :bug: Fix some issues in AI agent --- DysonNetwork.Insight/Thought/README.md | 24 +++++++++++ .../Thought/ThoughtController.cs | 40 +++++++++++++------ .../Thought/ThoughtProvider.cs | 6 +-- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/DysonNetwork.Insight/Thought/README.md b/DysonNetwork.Insight/Thought/README.md index 670401a..2dc0403 100644 --- a/DysonNetwork.Insight/Thought/README.md +++ b/DysonNetwork.Insight/Thought/README.md @@ -129,6 +129,30 @@ All endpoints require authentication through the current user session. Sequence The POST endpoint returns a stream of assistant responses using Server-Sent Events format. Clients should handle the streaming response and display messages incrementally. +### Streaming Message Format + +The streaming response sends several types of JSON messages: + +- **Text messages**: `{"type": "text", "data": "..." }` +- **Function calls**: `{"type": "function_call", "data": {...} }` (when AI uses tools) +- **Topic updates**: `{"type": "topic", "data": "..." }` (sent at end if topic was generated) +- **Thought completion**: `{"type": "thought", "data": {...} }` (sent at end with saved thought details) + +All streaming chunks during generation use the SSE event format: +``` +data: {"type": "...", "data": ...} + +``` + +Final messages (topic and thought) use custom event types: +``` +topic: {"type": "topic", "data": "..."} + +thought: {"type": "thought", "data": {...}} +``` + +Clients should parse these JSON messages and handle different types appropriately, such as displaying text in real-time and processing tool calls. + ## Implementation Notes - Built with ASP.NET Core and Semantic Kernel diff --git a/DysonNetwork.Insight/Thought/ThoughtController.cs b/DysonNetwork.Insight/Thought/ThoughtController.cs index a92eacc..51d0f79 100644 --- a/DysonNetwork.Insight/Thought/ThoughtController.cs +++ b/DysonNetwork.Insight/Thought/ThoughtController.cs @@ -58,7 +58,7 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) "When you talk to user, you can add some modal particles and emoticons to your response to be cute, but prevent use a lot of emojis." + "Your father (creator) is @littlesheep. (prefer calling him 父亲 in chinese)\n" + "\n" + - "The ID on the Solar Network is UUID, so mostly hard to read, so do not show ID to user unless user ask to do so or necessary.\n"+ + "The ID on the Solar Network is UUID, so mostly hard to read, so do not show ID to user unless user ask to do so or necessary.\n" + "\n" + "Your aim is to helping solving questions for the users on the Solar Network.\n" + "And the Solar Network is the social network platform you live on.\n" + @@ -105,14 +105,27 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) kernel: kernel )) { - // Write each chunk to the HTTP response as SSE - var data = chunk.ToString(); - accumulatedContent.Append(data); - if (string.IsNullOrEmpty(data)) continue; + // Process each item in the chunk for detailed streaming + foreach (var item in chunk.Items) + { + var messageJson = item switch + { + StreamingTextContent textContent => + JsonSerializer.Serialize(new { type = "text", data = textContent.Text ?? "" }), + StreamingFunctionCallUpdateContent functionCall => + JsonSerializer.Serialize(new { type = "function_call", data = functionCall }), + _ => + JsonSerializer.Serialize(new { type = "unknown", data = item }) + }; - var bytes = Encoding.UTF8.GetBytes(data); - await Response.Body.WriteAsync(bytes); - await Response.Body.FlushAsync(); + // Write a structured JSON message to the HTTP response as SSE + var messageBytes = Encoding.UTF8.GetBytes($"data: {messageJson}\n\n"); + await Response.Body.WriteAsync(messageBytes, 0, messageBytes.Length); + await Response.Body.FlushAsync(); + } + + // Accumulate content for saving (only text content) + accumulatedContent.Append(chunk.Content ?? ""); } // Save assistant thought @@ -122,15 +135,16 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) // Write the topic if it was newly set, then the thought object as JSON to the stream using (var streamBuilder = new MemoryStream()) { - await streamBuilder.WriteAsync("\n\ndata: "u8.ToArray()); + await streamBuilder.WriteAsync("\n\n"u8.ToArray()); if (topic != null) { var topicJson = JsonSerializer.Serialize(new { type = "topic", data = sequence.Topic ?? "" }); - await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"{topicJson}\n\n")); + await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"topic: {topicJson}\n\n")); } - var thoughtJson = JsonSerializer.Serialize(new { type = "thought", data = savedThought }, GrpcTypeHelper.SerializerOptions); - await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"data: {thoughtJson}\n\n")); + var thoughtJson = JsonSerializer.Serialize(new { type = "thought", data = savedThought }, + GrpcTypeHelper.SerializerOptions); + await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"thought: {thoughtJson}\n\n")); var outputBytes = streamBuilder.ToArray(); await Response.Body.WriteAsync(outputBytes); await Response.Body.FlushAsync(); @@ -188,4 +202,4 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) return Ok(thoughts); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Insight/Thought/ThoughtProvider.cs b/DysonNetwork.Insight/Thought/ThoughtProvider.cs index 2590887..2f99ced 100644 --- a/DysonNetwork.Insight/Thought/ThoughtProvider.cs +++ b/DysonNetwork.Insight/Thought/ThoughtProvider.cs @@ -98,7 +98,7 @@ public class ThoughtProvider string? afterIso = null, string? beforeIso = null, bool includeReplies = false, - int? pinned = null, + string? pinned = null, bool onlyMedia = false, bool shuffle = false ) => @@ -112,7 +112,7 @@ public class ThoughtProvider OrderBy = orderBy, Query = query, IncludeReplies = includeReplies, - Pinned = pinned.HasValue ? (PostPinMode)pinned : default, + Pinned = !string.IsNullOrEmpty(pinned) && int.TryParse(pinned, out int p) ? (PostPinMode)p : default, OnlyMedia = onlyMedia, Shuffle = shuffle }; @@ -167,4 +167,4 @@ public class ThoughtProvider throw new InvalidOperationException("Unknown provider: " + ModelProviderType); } } -} \ No newline at end of file +}