Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/assistant #33

Merged
merged 3 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="OpenAI" Version="2.1.0-beta.2" />
<PackageReference Include="OpenAI" Version="2.1.0" />
<PackageReference Include="TwitchLib.Api" Version="3.10.0-preview-92a4359" />
<PackageReference Include="Twitchlib.Client" Version="4.0.0-preview-5ef35d539836f8bcd29bd836a23b9e853020acfa" />
<PackageReference Include="TwitchLib.EventSub.Websockets" Version="0.6.0-preview-dbc970c" />
Expand Down
30 changes: 10 additions & 20 deletions src/Application/Features/ProcessMessageCommand.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System.Text.Json;
using Application.Infrastructure.Pubg;
using TwitchLib.Client.Models;


namespace Application.Features;
public record ProcessMessageCommand(ChatMessage ChatMessage) : IRequest;
public record ProcessMessageCommand(ChatMessage ChatMessage, string ThreadId) : IRequest;

public class ProcessMessageCommandHandler(
IChatService chatService,
IAIClient aiClient,
IAssistantClient assistantClient,
IPubgAIClient aiPubgAIClient,
IModerationClient moderationClient,
IMediator mediator,
Expand All @@ -19,6 +19,7 @@ ILoggerFactory loggerFactory
{
private readonly IChatService chatService = chatService;
private readonly IAIClient aiClient = aiClient;
private readonly IAssistantClient assistantClient = assistantClient;
private readonly IPubgAIClient aiPubgAIClient = aiPubgAIClient;
private readonly IModerationClient moderationClient = moderationClient;
private readonly IMediator mediator = mediator;
Expand Down Expand Up @@ -84,6 +85,8 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
logger.LogTrace("Messages: {Messages}", string.Join(", ", messages));
logger.LogTrace("Summary: {Summary}", messages.GetExtractiveSummary(request.ChatMessage.Channel));

await assistantClient.AddMessage(request.ThreadId, request.ChatMessage.Username, string.Empty, request.ChatMessage.Message);

// Ignore messages from some users (like yourself)
if (
request.ChatMessage.Username.Equals(options.Username, StringComparison.CurrentCultureIgnoreCase)
Expand All @@ -96,10 +99,10 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
var randomResponseChance = random.NextDouble();
logger.LogDebug("Random value: {RandomValue}", randomResponseChance);

if (request.ChatMessage.Message.Contains(options.Username, StringComparison.CurrentCultureIgnoreCase))
if (request.ChatMessage.Message.Contains(options.Username, StringComparison.CurrentCultureIgnoreCase) || randomResponseChance > RandomResponseChance)
{
// If the message contains the bot's name, use only that message as a prompt
string completion = await aiClient.GetCompletion(request.ChatMessage.Message);
string completion = await assistantClient.RunAndWait(request.ThreadId);

logger.LogDebug("Completion ({Channel}): {Completion}", request.ChatMessage.Channel, completion);

Expand All @@ -116,21 +119,6 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
}
}
}
else if (randomResponseChance > RandomResponseChance)
{
// If this is a random response, use some of the chat history to generate a response
// This also empties the history queue

// Get the messages into a list
var historyMessages = new List<string>();
while (messages.TryDequeue(out var message))
{
historyMessages.Add(message.Message);
}
string completion = await aiClient.GetAwareCompletion(historyMessages);
logger.LogInformation("History aware completion ({Channel}): {Completion}", request.ChatMessage.Channel, completion);
await chatService.SendMessage(request.ChatMessage.Channel, completion, cancellationToken);
}
}
}

Expand Down Expand Up @@ -196,3 +184,5 @@ public string GetExtractiveSummary(string channel, int numSentences = 3)
}

public record HistoryMessage(string Channel, string Username, string Message, DateTime Timestamp);

#pragma warning restore OPENAI001
1 change: 1 addition & 0 deletions src/Application/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using OpenAI;
global using OpenAI.Assistants;
global using OpenAI.Audio;
global using System.Text.RegularExpressions;
global using TwitchLib.Client.Models;
Expand Down
1 change: 1 addition & 0 deletions src/Application/Infrastructure/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private static IServiceCollection AddOpenAI(this IServiceCollection services, IC
services.AddSingleton<IAIClient, AIClient>();
services.AddSingleton<IAudioClient, OpenAI.AudioClient>();
services.AddSingleton<IModerationClient, ModerationClient>();
services.AddSingleton<IAssistantClient, OpenAI.AssistantClient>();
return services;
}

Expand Down
1 change: 1 addition & 0 deletions src/Application/Infrastructure/OpenAI/AIClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ public class OpenAIClientOptions : IConfigurationOptions
public Guid SoundOutDeviceGuid { get; set; } = Guid.Empty;
public string AudioOutputPath { get; set; } = string.Empty;
public string GeneralSystemPrompt { get; set; } = string.Empty;
public string Assistant { get; set; } = string.Empty;
}
61 changes: 61 additions & 0 deletions src/Application/Infrastructure/OpenAI/AssisstantClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Application.Infrastructure.OpenAI;

#pragma warning disable OPENAI001

public interface IAssistantClient
{
Task<string> NewThread(ThreadCreationOptions threadCreationOptions);
Task AddMessage(string threadId, string nick, string role, string message);
Task<string> RunAndWait(string threadId);
}

public class AssistantClient(IOptionsMonitor<OpenAIClientOptions> optionsMonitor, ILogger<AssistantClient> logger) : IAssistantClient
{
readonly OpenAIClient openAIClient = new(optionsMonitor.CurrentValue.ApiKey);
private readonly ILogger<AssistantClient> logger = logger;

public async Task<string> NewThread(ThreadCreationOptions threadCreationOptions)
{
var thread = await openAIClient.GetAssistantClient().CreateThreadAsync(threadCreationOptions);
return thread.Value.Id;
}

public async Task AddMessage(string threadId, string nick, string role, string message)
{
var assistantClient = openAIClient.GetAssistantClient();
var messageCreationOptions = new MessageCreationOptions();
messageCreationOptions.Metadata.Add("nick", nick);
messageCreationOptions.Metadata.Add("role", role);

var chatMessage = $"{nick} ({role}): {message}";

await assistantClient.CreateMessageAsync(threadId, MessageRole.User, [MessageContent.FromText(chatMessage)], messageCreationOptions);
}

public async Task<string> RunAndWait(string threadId)
{
var assistantClient = openAIClient.GetAssistantClient();
var createRunClientResult = await assistantClient.CreateRunAsync(threadId, optionsMonitor.CurrentValue.Assistant);
var threadRun = createRunClientResult.Value;

do
{
Thread.Sleep(100);
threadRun = await assistantClient.GetRunAsync(threadRun.ThreadId, threadRun.Id);
} while (!threadRun.Status.IsTerminal);

var messages = assistantClient.GetMessages(threadRun.ThreadId, new MessageCollectionOptions() { Order = MessageCollectionOrder.Descending });

foreach (var message in messages)
{
foreach (var contentItem in message.Content)
{
logger.LogInformation("{Role}: {Message}", message.Role.ToString().ToUpper(), contentItem.Text);
}
}

return messages.First().Content.First().Text;
}
}

#pragma warning restore OPENAI001
13 changes: 11 additions & 2 deletions src/Application/Infrastructure/Twitch/ChatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Application.Infrastructure.Twitch;

#pragma warning disable OPENAI001

public interface IChatService
{
public Task StartAsync(string accessToken, CancellationToken cancellationToken);
Expand All @@ -13,12 +15,15 @@ public interface IChatService
public IReadOnlyList<JoinedChannel> JoinedChannels { get; }
}

public partial class ChatService(ILoggerFactory loggerFactory, ILogger<ChatService> logger, ChatOptions options, IMediator mediator) : IChatService
public partial class ChatService(ILoggerFactory loggerFactory, ILogger<ChatService> logger, ChatOptions options, IMediator mediator, IAssistantClient assistantClient) : IChatService
{
readonly TwitchClient client = new(loggerFactory: loggerFactory);
private readonly ILogger<ChatService> logger = logger;
private readonly ChatOptions options = options;
private readonly IMediator mediator = mediator;
private readonly IAssistantClient assistantClient = assistantClient;
private string threadId = string.Empty;


private Task Client_OnConnected(object? sender, OnConnectedEventArgs e)
{
Expand All @@ -40,7 +45,7 @@ private async Task Client_OnWhisperReceived(object? sender, OnWhisperReceivedArg

private async Task Client_OnMessageReceived(object? sender, OnMessageReceivedArgs e)
{
var processMessageCommand = new ProcessMessageCommand(e.ChatMessage);
var processMessageCommand = new ProcessMessageCommand(e.ChatMessage, threadId);
await mediator.Send(processMessageCommand);
}

Expand All @@ -52,6 +57,8 @@ private Task Client_OnJoinedChannel(object? sender, OnJoinedChannelArgs e)

public async Task StartAsync(string accessToken, CancellationToken cancellationToken)
{
threadId = await assistantClient.NewThread(new ThreadCreationOptions());

ConnectionCredentials credentials = new(options.Username, accessToken);
client.Initialize(credentials, options.Channel);

Expand Down Expand Up @@ -91,3 +98,5 @@ public async Task JoinChannel(string channel, CancellationToken cancellationToke

public IReadOnlyList<JoinedChannel> JoinedChannels => client.JoinedChannels;
}

#pragma warning restore OPENAI001
Loading
Loading