Added research on the web to the BeyondFacts Lab (#1186)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-10 18:17:44 +01:00
1 parent e5c0931a60
commit 9a676101f7
66 files changed
+3609 -70

No files matched your search

@@ -0,0 +1,117 @@
using AIStudio.Assistants.BeyondFactsLab;
using AIStudio.Tools;
using AIStudio.Tools.ToolCallingSystem;
namespace AIStudio.Tests.BeyondFactsLab;
/// <summary>
/// Checks how the BeyondFacts Lab reads a research from the answer of an agent and the calls of its tools.
/// </summary>
[TestFixture]
public sealed class BflResearchTests
{
private static readonly DateTimeOffset CREATED_AT_UTC = new(2026, 11, 10, 14, 0, 0, TimeSpan.Zero);
private static readonly BflResearchDetail[] DETAILS =
[
new() { FieldId = BflFieldCatalog.FORMAT, Value = "Podium" },
new() { FieldId = BflFieldCatalog.EVENT_TOPIC, Value = "Klimapolitik" },
];
[Test]
public void AResearchKeepsWhatLeftAIStudio()
{
ToolInvocationTrace[] invocations =
[
Call(2, ToolSelectionRules.WEB_SEARCH_TOOL_ID, BflResearch.QUERY_ARGUMENT, "Podium Klimapolitik 2026"),
Call(1, ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, BflResearch.URL_ARGUMENT, "https://example.org/event"),
Call(3, ToolSelectionRules.WEB_SEARCH_TOOL_ID, BflResearch.QUERY_ARGUMENT, "Podium Klimapolitik 2026"),
Call(4, ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, BflResearch.URL_ARGUMENT, "https://example.org/unknown-title"),
Call(5, ToolSelectionRules.WEB_SEARCH_TOOL_ID, BflResearch.QUERY_ARGUMENT, "Blocked query", wasExecuted: false),
Call(6, ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, BflResearch.URL_ARGUMENT, "not an address"),
Call(7, "calculate", "expression", "1 + 1"),
];
Source[] sources =
[
new("Event page", "https://example.org/event", SourceOrigin.TOOL),
new("Written by the model", "https://example.org/unknown-title", SourceOrigin.LLM),
];
var research = BflResearch.Of("## Veranstaltung\n\nEin Podium.", invocations, sources, DETAILS, CREATED_AT_UTC);
using (Assert.EnterMultipleScope())
{
Assert.That(research.Text, Is.EqualTo("## Veranstaltung\n\nEin Podium."));
Assert.That(research.SearchQueries, Is.EqualTo(new[] { "Podium Klimapolitik 2026" }));
Assert.That(research.Pages.Select(page => page.Url), Is.EqualTo(new[] { "https://example.org/event", "https://example.org/unknown-title" }));
Assert.That(research.Pages.Select(page => page.Title), Is.EqualTo(new[] { "Event page", "https://example.org/unknown-title" }));
Assert.That(research.Details.Select(detail => detail.Value), Is.EqualTo(new[] { "Podium", "Klimapolitik" }));
Assert.That(research.Details, Has.None.SameAs(DETAILS[0]), "The research keeps a copy of the details.");
Assert.That(research.CreatedAtUtc, Is.EqualTo(CREATED_AT_UTC));
}
}
[Test]
public void ACodeBlockAroundTheAnswerIsTakenOff()
{
var research = BflResearch.Of("```markdown\n## Debatte\n\nText\n```", [], [], DETAILS, CREATED_AT_UTC);
Assert.That(research.Text, Is.EqualTo("## Debatte\n\nText"));
}
[Test]
public void AVeryLongAnswerIsShortenedAtTheEndOfALine()
{
var line = new string('x', 99);
var answer = string.Join("\n", Enumerable.Repeat(line, 1_000));
var research = BflResearch.Of(answer, [], [], DETAILS, CREATED_AT_UTC);
using (Assert.EnterMultipleScope())
{
Assert.That(research.Text.Length, Is.LessThanOrEqualTo(BflResearch.MAX_TEXT_CHARACTERS));
Assert.That(research.Text, Does.EndWith(line));
}
}
[Test]
public void ALongTitleIsShortened()
{
var title = new string('T', BflResearch.MAX_TITLE_CHARACTERS + 50);
var research = BflResearch.Of("Text", [Call(1, ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, BflResearch.URL_ARGUMENT, "https://example.org/")], [new(title, "https://example.org/", SourceOrigin.TOOL)], DETAILS, CREATED_AT_UTC);
Assert.That(research.Pages.Single().Title, Has.Length.EqualTo(BflResearch.MAX_TITLE_CHARACTERS).And.EndWith("…"));
}
[Test]
public void AnAnswerWithoutTextGivesAnEmptyResearch()
{
var research = BflResearch.Of(" \n ", [], [], DETAILS, CREATED_AT_UTC);
Assert.That(research.Text, Is.Empty);
}
[Test]
public void AResearchIsOutdatedOnceThePublicDetailsChanged()
{
var research = BflResearch.Of("Text", [], [], DETAILS, CREATED_AT_UTC);
using (Assert.EnterMultipleScope())
{
Assert.That(research.IsOutdated([new() { FieldId = BflFieldCatalog.FORMAT, Value = "Podium" }, new() { FieldId = BflFieldCatalog.EVENT_TOPIC, Value = "Klimapolitik" }]), Is.False);
Assert.That(research.IsOutdated([new() { FieldId = BflFieldCatalog.FORMAT, Value = "Podium" }, new() { FieldId = BflFieldCatalog.EVENT_TOPIC, Value = "Energiepolitik" }]), Is.True);
Assert.That(research.IsOutdated([new() { FieldId = BflFieldCatalog.FORMAT, Value = "Podium" }]), Is.True);
Assert.That(research.IsOutdated([.. DETAILS, new() { FieldId = BflFieldCatalog.EVENT_LINK, Value = "https://example.org/" }]), Is.True);
}
}
private static ToolInvocationTrace Call(int order, string toolId, string argument, string value, bool wasExecuted = true) => new()
{
Order = order,
ToolId = toolId,
WasExecuted = wasExecuted,
Status = wasExecuted ? ToolInvocationTraceStatus.SUCCESS : ToolInvocationTraceStatus.BLOCKED,
Arguments = new() { [argument] = value },
};
}