Skip to content

Commit 99b3150

Browse files
committed
Implement AI-based grouping
1 parent 8671768 commit 99b3150

File tree

6 files changed

+148
-4
lines changed

6 files changed

+148
-4
lines changed

Directory.Packages.props

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
2323
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
2424
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
25+
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.0.0-preview.9.24556.5" />
2526
<PackageVersion Include="Microsoft.Extensions.AI.Ollama" Version="9.0.0-preview.9.24556.5" />
2627
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
2728
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
2829
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
2930
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
30-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
31+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
3132
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
32-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
33+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
3334
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
3435
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
3536
<PackageVersion Include="Microsoft.Playwright.NUnit" Version="1.28.0" />

src/Return.Web/Components/NoteLane.razor

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@
107107
</p>
108108
</div>
109109
}
110+
111+
@if (IsGroupingAllowed() && this.Contents is { Notes.Count: > 0 })
112+
{
113+
<p>
114+
<button class="button is-link is-outlined @(IsAutoGrouping ? "is-loading" : "")" @onclick="@AutoGroupNotes">
115+
<i class="fa-solid fa-quote-left"></i>
116+
Automatically group notes
117+
</button>
118+
</p>
119+
}
110120

111121

112122
</CascadingValue>

src/Return.Web/Components/NoteLaneBase.cs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ namespace Return.Web.Components;
1010
#nullable disable
1111

1212
using System;
13+
using System.Collections.Generic;
14+
using System.Diagnostics;
1315
using System.Diagnostics.CodeAnalysis;
16+
using System.Linq;
17+
using System.Text;
1418
using System.Threading.Tasks;
1519
using Application.Common.Models;
1620
using Application.NoteGroups.Commands;
@@ -258,6 +262,133 @@ protected async Task AddNoteGroup() {
258262
}
259263
}
260264

265+
protected bool IsAutoGrouping { get; set; } = false;
266+
267+
protected async Task AutoGroupNotes()
268+
{
269+
IsAutoGrouping = true;
270+
this.StateHasChanged();
271+
272+
ChatOptions chatOptions = new()
273+
{
274+
Tools = [
275+
AIFunctionFactory.Create(
276+
this.MakeNoteGroup,
277+
new AIFunctionFactoryCreateOptions
278+
{
279+
Name = "Create note group",
280+
Description = "Makes a group with a specified title and IDs of the relevant notes.",
281+
Parameters = [
282+
new("title") { Description = "The name of the group to create", IsRequired = true, ParameterType = typeof(string)},
283+
new("noteIds") { Description = "An array of note IDs of the notes to put into this group", IsRequired = true, ParameterType = typeof(int[])},
284+
],
285+
ReturnParameter = new()
286+
{
287+
Description = "Indication of the note group created",
288+
}
289+
}
290+
)
291+
],
292+
ToolMode = ChatToolMode.RequireSpecific("Create note group"),
293+
TopP = 1.2f,
294+
TopK = 25
295+
};
296+
297+
List<ChatMessage> chatMessages =
298+
[
299+
new(
300+
ChatRole.System,
301+
$@"Please group similar notes together using the following constraints:
302+
1. Only group notes that fit in a group.
303+
2. If a note cannot be grouped together with multiple other notes, then ignore.
304+
3. Only group notes with the same subject.
305+
4. To group notes, invoke the ""Create note group"" tool.
306+
5. Do not to put a single note in multiple groups.
307+
6. Give each group a title of 5 words maximum that summarizes the notes in the group.
308+
309+
What now follows is a list of notes to divide into groups. Do not response with a summary, please invoke the tool.
310+
Each note is starts with [NOTE ID]. Each note ends with [END NOTE].
311+
312+
Example note with ID 123:
313+
[NOTE 123] Some text here [END NOTE]"
314+
)
315+
];
316+
317+
StringBuilder stringBuilder = new();
318+
319+
foreach (RetrospectiveNote note in this.Contents.Notes)
320+
{
321+
stringBuilder.AppendLine($"[NOTE {note.Id}] {note.Text} [END NOTE]");
322+
}
323+
324+
chatMessages.Add(new ChatMessage(ChatRole.User, stringBuilder.ToString()));
325+
326+
long startTime = Stopwatch.GetTimestamp();
327+
328+
IChatClient client = new ChatClientBuilder()
329+
.UseFunctionInvocation(f =>
330+
{
331+
f.RetryOnError = true;
332+
})
333+
.UseLogging(this.Logger)
334+
.Use(this.ChatClient);
335+
336+
Logger.LogDebug("Invoking AI with {Count} messages", chatMessages.Count);
337+
try
338+
{
339+
ChatCompletion response = await client.CompleteAsync(chatMessages, chatOptions);
340+
341+
Logger.LogTrace("AI response: {@RawResponse}", response);
342+
}
343+
catch (Exception ex)
344+
{
345+
Logger.LogError(ex, "Error invoking AI");
346+
}
347+
finally
348+
{
349+
IsAutoGrouping = false;
350+
}
351+
352+
Logger.LogDebug("Completed AI invocation in {Elapsed}", Stopwatch.GetElapsedTime(startTime));
353+
}
354+
355+
356+
private async Task<string> MakeNoteGroup(string title, int[] noteIds)
357+
{
358+
try
359+
{
360+
Logger.LogInformation("AI tool callback: Make note group with title {Title} and ids {@NoteIds}",
361+
title,
362+
noteIds);
363+
364+
IEnumerable<string> notes = this.Contents.Notes.Where(x => noteIds.Contains(x.Id)).Select(x => x.Text);
365+
foreach (string note in notes) {
366+
Logger.LogDebug("Note selected for group {Title}: {NoteText}", title, note);
367+
}
368+
369+
RetrospectiveNoteGroup result = await this.Mediator.Send(new AddNoteGroupCommand(this.RetroId.StringId, this.Lane.Id));
370+
result.Title = title;
371+
372+
this.Contents.Groups.Add(result);
373+
await this.Mediator.Send(new UpdateNoteGroupCommand(this.RetroId.StringId, result.Id, title));
374+
375+
foreach (int noteId in noteIds)
376+
{
377+
if (this.ExecuteNoteMove(noteId, result.Id))
378+
{
379+
await this.Mediator.Send(new MoveNoteCommand(noteId, result.Id));
380+
}
381+
}
382+
383+
return $"Created note group \"{title}\" and with notes: {String.Join(",", noteIds)}";
384+
}
385+
catch (Exception ex)
386+
{
387+
Logger.LogError(ex, "Error processing AI invocation");
388+
return $"An error occured making group {title}";
389+
}
390+
}
391+
261392
public Task OnNoteAdded(NoteAddedNotification notification) {
262393
if (notification.LaneId != this.Lane?.Id ||
263394
notification.RetroId != this.RetroId.StringId ||

src/Return.Web/Return.Web.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="Blazored.FluentValidation" />
1515
<PackageReference Include="Markdown" />
16+
<PackageReference Include="Microsoft.Extensions.AI" />
1617
<PackageReference Include="Microsoft.Extensions.AI.Ollama" />
1718

1819
<PackageReference Include="NReco.Logging.File" />

src/Return.Web/appsettings.Development.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"Override": {
66
"Return": "Verbose",
77
"System": "Information",
8-
"Microsoft": "Information"
8+
"Microsoft": "Information",
9+
"Microsoft.Extensions.AI": "Debug"
910
}
1011
}
1112
},

src/Return.Web/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
"AI": {
2323
"Url": null,
24-
"Model": "llama3.2:1b" // https://ollama.com/search?c=tools
24+
"Model": "llama3.2:3b" // https://ollama.com/search?c=tools
2525
},
2626

2727
"Database": {

0 commit comments

Comments
 (0)