Added the guided chat to the BeyondFacts Lab (#1177)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-09 09:55:01 +01:00
1 parent e77645f44c
commit 79e0c80031
55 files changed
+3481 -72

No files matched your search

@@ -1,12 +1,14 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using AIStudio.Assistants.BeyondFactsLab;
using AIStudio.Chat;
using AIStudio.Settings;
using AIStudio.Tests.Tools.Databases;
using AIStudio.Tools;
using AIStudio.Tools.DataBackup;
using AIStudio.Tools.Databases.ProvenanceStore;
using AIStudio.Tools.RAG;
using AIStudio.Tools.Services;
using Microsoft.Extensions.Logging.Abstractions;
@@ -72,7 +74,7 @@ public sealed class ProvenanceCleanupServiceTests
var unchangedChat = await this.StoreUnchangedChatAsync();
var unreferenced = await this.store.Client.StoreAsync(unchangedChat.ChatId, ProvenanceKind.ATTACHMENT, "of a message removed long ago", null, TOKEN);
await RunAsync(this.store, RestoreStateWith(report: null));
await this.RunAsync(RestoreStateWith(report: null));
var entries = await this.store.Client.GetEntriesAsync([unreferenced!.Value], TOKEN);
var lastCleanup = await this.store.Client.GetLastCleanupAsync(TOKEN);
@@ -89,7 +91,7 @@ public sealed class ProvenanceCleanupServiceTests
var unchangedChat = await this.StoreUnchangedChatAsync();
var unreferenced = await this.store.Client.StoreAsync(unchangedChat.ChatId, ProvenanceKind.ATTACHMENT, "of a message the restored chat does not have", null, TOKEN);
await RunAsync(this.store, RestoreStateWith(new(DataRestoreOutcome.APPLIED, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)));
await this.RunAsync(RestoreStateWith(new(DataRestoreOutcome.APPLIED, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty)));
var entries = await this.store.Client.GetEntriesAsync([unreferenced!.Value], TOKEN);
Assert.That(entries, Is.Empty, "The restored chat came from another moment than the store, so the time of the last cleanup tells nothing about it.");
@@ -104,7 +106,7 @@ public sealed class ProvenanceCleanupServiceTests
await File.WriteAllTextAsync(Path.Join(pendingDirectory, DataRestoreFormat.READY_FILE_NAME), string.Empty, TOKEN);
await File.WriteAllTextAsync(Path.Join(pendingDirectory, DataRestoreFormat.JOURNAL_FILE_NAME), "{}", TOKEN);
await RunAsync(this.store, RestoreStateWith(report: null));
await this.RunAsync(RestoreStateWith(report: null));
var entries = await this.store.Client.GetEntriesAsync([entry!.Value], TOKEN);
var lastCleanup = await this.store.Client.GetLastCleanupAsync(TOKEN);
@@ -115,6 +117,49 @@ public sealed class ProvenanceCleanupServiceTests
}
}
[Test]
public async Task WhatAChatOfTheLabReceivedSurvivesTheCleanup()
{
// The cleanup reads the chats of the lab without asking whether the lab is switched on; nothing in this test
// switches it on:
var labStore = this.CreateLabStore();
var project = await labStore.CreateAsync("Panel");
var location = new BflThreadLocation(project.ProjectId, BflStep.KNOWLEDGE);
var referenced = await this.store.Client.StoreAsync(location.ChatId, ProvenanceKind.WEB_PAGE, "of a page the chat of the lab read", null, TOKEN);
var unreferenced = await this.store.Client.StoreAsync(location.ChatId, ProvenanceKind.WEB_PAGE, "of an answer the person removed", null, TOKEN);
var thread = BflThreadFactory.Create(location);
thread.Blocks.Add(new ContentBlock
{
ContentType = ContentType.TEXT,
Role = ChatRole.AI,
Content = new ContentText { Text = "Laut der Seite …", SourcePassages = [new SourcePassage { SourceUrl = "https://example.org/", ProvenanceId = referenced }] }
});
await labStore.SaveThreadAsync(location, thread);
await this.RunAsync(RestoreStateWith(report: null));
var entries = await this.store.Client.GetEntriesAsync([referenced!.Value, unreferenced!.Value], TOKEN);
Assert.That(entries.Keys, Is.EquivalentTo(new[] { referenced.Value }), "The chat of the lab is read like any other chat.");
}
[Test]
public async Task WhatAChatOfADeletedPreparationReceivedIsRemoved()
{
var labStore = this.CreateLabStore();
var project = await labStore.CreateAsync("Panel");
var location = new BflThreadLocation(project.ProjectId, BflStep.KNOWLEDGE);
await labStore.SaveThreadAsync(location, BflThreadFactory.Create(location));
var entry = await this.store.Client.StoreAsync(location.ChatId, ProvenanceKind.WEB_PAGE, "of a page the chat of the lab read", null, TOKEN);
await labStore.DeleteAsync(project.ProjectId);
await this.RunAsync(RestoreStateWith(report: null));
var entries = await this.store.Client.GetEntriesAsync([entry!.Value], TOKEN);
Assert.That(entries, Is.Empty);
}
private BeyondFactsLabStore CreateLabStore() => new(NullLogger<BeyondFactsLabStore>.Instance, new BflStorageOptions { DataDirectory = this.testDirectory });
/// <summary>
/// Stores a chat whose file was written long before the last cleanup, which is noted as if it ran a minute ago.
/// </summary>
@@ -130,12 +175,12 @@ public sealed class ProvenanceCleanupServiceTests
return chat;
}
private static async Task RunAsync(TemporaryProvenanceStore store, DataRestoreService dataRestoreService)
private async Task RunAsync(DataRestoreService dataRestoreService)
{
// Only entries stored before the start of the service may be removed, and the test stored them just now:
await Task.Delay(TimeSpan.FromMilliseconds(10), TOKEN);
using var service = new ProvenanceCleanupService(store.CreateClientProvider(), dataRestoreService, NullLogger<ProvenanceCleanupService>.Instance);
using var service = new ProvenanceCleanupService(this.store.CreateClientProvider(), dataRestoreService, this.CreateLabStore(), NullLogger<ProvenanceCleanupService>.Instance);
await service.StartAsync(TOKEN);
await service.ExecuteTask!;
}