Skip to content

Commit 8cf5640

Browse files
committed
feat(backend/WebApi: implement SSE streaming for agent responses
Added support for streaming agent responses using Server-Sent Events (SSE). This allows real-time updates to be sent to clients as the agent processes messages. The implementation includes a new service for handling SSE streaming and a mapper for event types based on agent responses. Modified files (5): - Program.cs: Updated application startup to include logging and middleware - AgentEventMapper.cs: Added helper methods for mapping agent updates - SseStreamingService.cs: Implemented streaming logic for agent responses - TaskAgentService.cs: Removed obsolete service implementation - TaskAgent.WebApi.csproj: Updated project dependencies and added logs folder
1 parent a050c97 commit 8cf5640

16 files changed

Lines changed: 782 additions & 1349 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace TaskAgent.WebApi.Constants;
2+
3+
/// <summary>
4+
/// Constants for Agent API and SSE event types
5+
/// </summary>
6+
public static class AgentConstants
7+
{
8+
/// <summary>
9+
/// SSE Content-Type header value
10+
/// </summary>
11+
public const string SSE_CONTENT_TYPE = "text/event-stream";
12+
13+
/// <summary>
14+
/// SSE Cache-Control header value
15+
/// </summary>
16+
public const string SSE_CACHE_CONTROL = "no-cache";
17+
18+
/// <summary>
19+
/// SSE Connection header value
20+
/// </summary>
21+
public const string SSE_CONNECTION = "keep-alive";
22+
23+
/// <summary>
24+
/// User role identifier for message parsing
25+
/// </summary>
26+
public const string USER_ROLE = "USER";
27+
28+
/// <summary>
29+
/// Custom SSE event type for thread state serialization
30+
/// </summary>
31+
public const string EVENT_THREAD_STATE = "THREAD_STATE";
32+
33+
/// <summary>
34+
/// SSE event type for run errors
35+
/// </summary>
36+
public const string EVENT_RUN_ERROR = "RUN_ERROR";
37+
38+
/// <summary>
39+
/// Error type identifier for agent errors
40+
/// </summary>
41+
public const string ERROR_AGENT_ERROR = "AgentError";
42+
43+
/// <summary>
44+
/// SSE event type for text message content
45+
/// </summary>
46+
public const string EVENT_TEXT_MESSAGE_CONTENT = "TEXT_MESSAGE_CONTENT";
47+
48+
/// <summary>
49+
/// SSE event type for tool call start
50+
/// </summary>
51+
public const string EVENT_TOOL_CALL_START = "TOOL_CALL_START";
52+
53+
/// <summary>
54+
/// SSE event type for tool call result
55+
/// </summary>
56+
public const string EVENT_TOOL_CALL_RESULT = "TOOL_CALL_RESULT";
57+
58+
/// <summary>
59+
/// SSE event type for generic run updates
60+
/// </summary>
61+
public const string EVENT_RUN_UPDATE = "RUN_UPDATE";
62+
}

src/backend/services/TaskAgent/src/TaskAgent.WebApi/Constants/ApiRoutes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ namespace TaskAgent.WebApi.Constants;
55
/// </summary>
66
public static class ApiRoutes
77
{
8-
public const string CHAT = "api/chat";
8+
public const string AGUI = "/agui";
99
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Text.Json;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.AI;
4+
using TaskAgent.Application.DTOs.Requests;
5+
using TaskAgent.WebApi.Constants;
6+
using TaskAgent.WebApi.Services;
7+
8+
namespace TaskAgent.WebApi.Controllers;
9+
10+
/// <summary>
11+
/// Custom AG-UI streaming endpoint with serialized state support for conversation continuity.
12+
/// </summary>
13+
[ApiController]
14+
[Route("api/[controller]")]
15+
public class AgentController : ControllerBase
16+
{
17+
private readonly SseStreamingService _streamingService;
18+
private readonly ILogger<AgentController> _logger;
19+
20+
public AgentController(
21+
SseStreamingService streamingService,
22+
ILogger<AgentController> logger
23+
)
24+
{
25+
_streamingService = streamingService;
26+
_logger = logger;
27+
}
28+
29+
/// <summary>
30+
/// Streams agent responses with Server-Sent Events (SSE), including thread state.
31+
/// </summary>
32+
[HttpPost("chat")]
33+
public async Task ChatAsync(
34+
[FromBody] AgentRequest request,
35+
CancellationToken cancellationToken
36+
)
37+
{
38+
try
39+
{
40+
// Convert request messages to ChatMessage list
41+
List<ChatMessage> messages =
42+
request.Messages?.Select(MapToChatMessage).ToList() ?? [];
43+
44+
// Stream response using the service
45+
await _streamingService.StreamToResponseAsync(
46+
Response,
47+
messages,
48+
request.SerializedState,
49+
cancellationToken
50+
);
51+
}
52+
catch (Exception ex)
53+
{
54+
_logger.LogError(ex, "Error processing agent request");
55+
await WriteErrorEventAsync(ex.Message, cancellationToken);
56+
}
57+
}
58+
59+
private static ChatMessage MapToChatMessage(AgentMessage message)
60+
{
61+
ChatRole role =
62+
message.Role?.ToUpperInvariant() == AgentConstants.USER_ROLE
63+
? ChatRole.User
64+
: ChatRole.Assistant;
65+
66+
return new ChatMessage(role, new List<AIContent> { new TextContent(message.Content ?? "") });
67+
}
68+
69+
private async Task WriteErrorEventAsync(string errorMessage, CancellationToken cancellationToken)
70+
{
71+
string errorEvent = JsonSerializer.Serialize(
72+
new
73+
{
74+
type = AgentConstants.EVENT_RUN_ERROR,
75+
error = AgentConstants.ERROR_AGENT_ERROR,
76+
message = errorMessage,
77+
}
78+
);
79+
80+
await Response.WriteAsync($"data: {errorEvent}\n\n", cancellationToken);
81+
}
82+
}

src/backend/services/TaskAgent/src/TaskAgent.WebApi/Controllers/ChatController.cs

Lines changed: 0 additions & 240 deletions
This file was deleted.

0 commit comments

Comments
 (0)