Skip to content

Commit

Permalink
Implement AI-based grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebazzz committed Nov 14, 2024
1 parent 8671768 commit 99b3150
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.0.0-preview.9.24556.5" />
<PackageVersion Include="Microsoft.Extensions.AI.Ollama" Version="9.0.0-preview.9.24556.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageVersion Include="Microsoft.Playwright.NUnit" Version="1.28.0" />
Expand Down
10 changes: 10 additions & 0 deletions src/Return.Web/Components/NoteLane.razor
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@
</p>
</div>
}

@if (IsGroupingAllowed() && this.Contents is { Notes.Count: > 0 })
{
<p>
<button class="button is-link is-outlined @(IsAutoGrouping ? "is-loading" : "")" @onclick="@AutoGroupNotes">
<i class="fa-solid fa-quote-left"></i>
Automatically group notes
</button>
</p>
}


</CascadingValue>
Expand Down
131 changes: 131 additions & 0 deletions src/Return.Web/Components/NoteLaneBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ namespace Return.Web.Components;
#nullable disable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Application.Common.Models;
using Application.NoteGroups.Commands;
Expand Down Expand Up @@ -258,6 +262,133 @@ protected async Task AddNoteGroup() {
}
}

protected bool IsAutoGrouping { get; set; } = false;

protected async Task AutoGroupNotes()
{
IsAutoGrouping = true;
this.StateHasChanged();

ChatOptions chatOptions = new()
{
Tools = [
AIFunctionFactory.Create(
this.MakeNoteGroup,
new AIFunctionFactoryCreateOptions
{
Name = "Create note group",
Description = "Makes a group with a specified title and IDs of the relevant notes.",
Parameters = [
new("title") { Description = "The name of the group to create", IsRequired = true, ParameterType = typeof(string)},
new("noteIds") { Description = "An array of note IDs of the notes to put into this group", IsRequired = true, ParameterType = typeof(int[])},
],
ReturnParameter = new()
{
Description = "Indication of the note group created",
}
}
)
],
ToolMode = ChatToolMode.RequireSpecific("Create note group"),
TopP = 1.2f,
TopK = 25
};

List<ChatMessage> chatMessages =
[
new(
ChatRole.System,
$@"Please group similar notes together using the following constraints:
1. Only group notes that fit in a group.
2. If a note cannot be grouped together with multiple other notes, then ignore.
3. Only group notes with the same subject.
4. To group notes, invoke the ""Create note group"" tool.
5. Do not to put a single note in multiple groups.
6. Give each group a title of 5 words maximum that summarizes the notes in the group.
What now follows is a list of notes to divide into groups. Do not response with a summary, please invoke the tool.
Each note is starts with [NOTE ID]. Each note ends with [END NOTE].
Example note with ID 123:
[NOTE 123] Some text here [END NOTE]"
)
];

StringBuilder stringBuilder = new();

foreach (RetrospectiveNote note in this.Contents.Notes)
{
stringBuilder.AppendLine($"[NOTE {note.Id}] {note.Text} [END NOTE]");
}

chatMessages.Add(new ChatMessage(ChatRole.User, stringBuilder.ToString()));

long startTime = Stopwatch.GetTimestamp();

IChatClient client = new ChatClientBuilder()
.UseFunctionInvocation(f =>
{
f.RetryOnError = true;
})
.UseLogging(this.Logger)
.Use(this.ChatClient);

Logger.LogDebug("Invoking AI with {Count} messages", chatMessages.Count);
try
{
ChatCompletion response = await client.CompleteAsync(chatMessages, chatOptions);

Logger.LogTrace("AI response: {@RawResponse}", response);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error invoking AI");
}
finally
{
IsAutoGrouping = false;
}

Logger.LogDebug("Completed AI invocation in {Elapsed}", Stopwatch.GetElapsedTime(startTime));
}


private async Task<string> MakeNoteGroup(string title, int[] noteIds)
{
try
{
Logger.LogInformation("AI tool callback: Make note group with title {Title} and ids {@NoteIds}",
title,
noteIds);

IEnumerable<string> notes = this.Contents.Notes.Where(x => noteIds.Contains(x.Id)).Select(x => x.Text);
foreach (string note in notes) {
Logger.LogDebug("Note selected for group {Title}: {NoteText}", title, note);
}

RetrospectiveNoteGroup result = await this.Mediator.Send(new AddNoteGroupCommand(this.RetroId.StringId, this.Lane.Id));
result.Title = title;

this.Contents.Groups.Add(result);
await this.Mediator.Send(new UpdateNoteGroupCommand(this.RetroId.StringId, result.Id, title));

foreach (int noteId in noteIds)
{
if (this.ExecuteNoteMove(noteId, result.Id))
{
await this.Mediator.Send(new MoveNoteCommand(noteId, result.Id));
}
}

return $"Created note group \"{title}\" and with notes: {String.Join(",", noteIds)}";
}
catch (Exception ex)
{
Logger.LogError(ex, "Error processing AI invocation");
return $"An error occured making group {title}";
}
}

public Task OnNoteAdded(NoteAddedNotification notification) {
if (notification.LaneId != this.Lane?.Id ||
notification.RetroId != this.RetroId.StringId ||
Expand Down
1 change: 1 addition & 0 deletions src/Return.Web/Return.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<ItemGroup>
<PackageReference Include="Blazored.FluentValidation" />
<PackageReference Include="Markdown" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.Ollama" />

<PackageReference Include="NReco.Logging.File" />
Expand Down
3 changes: 2 additions & 1 deletion src/Return.Web/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"Override": {
"Return": "Verbose",
"System": "Information",
"Microsoft": "Information"
"Microsoft": "Information",
"Microsoft.Extensions.AI": "Debug"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/Return.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

"AI": {
"Url": null,
"Model": "llama3.2:1b" // https://ollama.com/search?c=tools
"Model": "llama3.2:3b" // https://ollama.com/search?c=tools
},

"Database": {
Expand Down

0 comments on commit 99b3150

Please sign in to comment.