Skip to content

Commit

Permalink
Code(API::ExecuteNativeAction): Implement feature for execute native …
Browse files Browse the repository at this point in the history
…action
  • Loading branch information
ktutak1337 committed Jun 11, 2024
1 parent 8231076 commit 8b9b068
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace StellarChat.Server.Api.Features.Actions.ExecuteNativeAction;

internal sealed record ExecuteNativeAction([Required] Guid Id, [Required] Guid ChatId, string Message) : ICommand<string>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace StellarChat.Server.Api.Features.Actions.ExecuteNativeAction;

public class ExecuteNativeActionEndpoint : IEndpoint
{
public void Expose(IEndpointRouteBuilder endpoints)
{
var actions = endpoints.MapGroup("/actions").WithTags("Actions");

actions.MapPost("execute", async ([FromBody] ExecuteNativeActionRequest request, IMediator mediator) =>
{
var id = Guid.NewGuid();
var command = request.Adapt<ExecuteNativeAction>();

command = command with { Id = id };
var result = await mediator.Send(command);

return Results.Ok(result);
})
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.WithOpenApi(operation => new(operation)
{
Summary = "Executes a new action."
});
}

public void Register(IServiceCollection services, IConfiguration configuration) { }

public void Use(IApplicationBuilder app) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Microsoft.SemanticKernel;
using StellarChat.Server.Api.Features.Actions.CreateNativeAction;
using StellarChat.Server.Api.Features.Actions.Webhooks.Services;
using StellarChat.Server.Api.Features.Chat.CarryConversation;

namespace StellarChat.Server.Api.Features.Actions.ExecuteNativeAction;

internal sealed class ExecuteNativeActionHandler : ICommandHandler<ExecuteNativeAction, string>
{
private readonly INativeActionRepository _nativeActionRepository;
private readonly IHttpClientService _httpClientService;
private readonly IChatContext _chatContext;
private readonly Kernel _kernel;
private readonly IHubContext<ChatHub, IChatHub> _hubContext;
private readonly TimeProvider _clock;
private readonly ILogger<CreateNativeActionHandler> _logger;

public ExecuteNativeActionHandler(
INativeActionRepository nativeActionRepository,
IHttpClientService httpClientService,
IChatContext chatContext,
Kernel kernel,
IHubContext<ChatHub, IChatHub> hubContext,
TimeProvider clock,
ILogger<CreateNativeActionHandler> logger)
{
_nativeActionRepository = nativeActionRepository;
_httpClientService = httpClientService;
_chatContext = chatContext;
_kernel = kernel;
_hubContext = hubContext;
_clock = clock;
_logger = logger;
}

public async ValueTask<string> Handle(ExecuteNativeAction command, CancellationToken cancellationToken)
{
var (id, chatId, message) = command;

var action = await _nativeActionRepository.GetAsync(id) ?? throw new NativeActionNotFoundException(id);

action.Metaprompt = action.Metaprompt.IsEmpty()
? string.Empty
: _clock.ReplaceDatePlaceholder(action.Metaprompt);

string semanticResponse = string.Empty;

await _chatContext.SetChatInstructions(chatId, action.Metaprompt);
await _chatContext.ExtractChatHistoryAsync(chatId);

var userMessage = CreateUserMessage(chatId, message);
await _chatContext.SaveChatMessageAsync(chatId, userMessage);

var botMessage = CreateBotMessage(chatId, content: string.Empty);
var botResponseMessage = await _chatContext.StreamResponseToClientAsync(chatId, action.Model, botMessage, _hubContext);

semanticResponse = botResponseMessage.Content;

if (action.IsRemoteAction)
{
var response = await _httpClientService.PostAsync(action.Webhook!.Url, semanticResponse, action.Webhook.Headers, cancellationToken);
botResponseMessage.Content = response;
await _hubContext.Clients.All.ReceiveChatMessageChunk(response);
await _chatContext.SaveChatMessageAsync(chatId, botResponseMessage);

return response;
}

await _chatContext.SaveChatMessageAsync(chatId, botResponseMessage);

return semanticResponse;
}

private ChatMessage CreateUserMessage(Guid chatId, string message, ChatMessageType messageType = ChatMessageType.Message)
{
var now = _clock.GetLocalNow();

var userMessage = ChatMessage.Create(Guid.NewGuid(), chatId, messageType, Author.User, message, tokenCount: 0, now);
return userMessage;
}

private ChatMessage CreateBotMessage(Guid chatId, string content, ChatMessageType messageType = ChatMessageType.Message)
{
var now = _clock.GetLocalNow();

var chatMessage = ChatMessage.Create(Guid.NewGuid(), chatId, messageType, Author.Bot, content, tokenCount: 0, now);
return chatMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace StellarChat.Shared.Contracts.Actions;

public sealed record ExecuteNativeActionRequest([property: JsonIgnore][Required] Guid Id, [Required] Guid ChatId, string Message);
3 changes: 3 additions & 0 deletions src/Shared/StellarChat.Shared.Infrastructure/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ public static string ToFormatSize(this long bytes)
return $"{number:n2} {suffixes[counter]}";
}

public static string ReplaceDatePlaceholder(this TimeProvider timeProvider, string prompt)
=> prompt.Replace("{DATE}", timeProvider.GetLocalNow().ToString());

public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
=> app.Use((ctx, next) =>
{
Expand Down

0 comments on commit 8b9b068

Please sign in to comment.