Added a way to read what the AI received from your sources (#1100)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-10-26 10:00:01 +01:00
1 parent e0d8e18f34
commit cbc4ce5413
229 files changed
+12739 -1269

No files matched your search

@@ -10,7 +10,8 @@ namespace AIStudio.Tests.Tools;
/// The sources below an answer are links the user opens, and they travel into every export. The
/// classic RAG process and Semantic Search both take them from here, so a passage has to name the
/// same sources whichever of the two found it. A source has to open where the passage is, and
/// nothing may become a link which does not lead anywhere sensible.
/// nothing may become a link which does not lead anywhere sensible. What names nothing to open
/// still becomes a source, named as text, since the user reads under it what the AI received.
/// </remarks>
[TestFixture]
public sealed class RetrievalContextSourcesTests
@@ -48,11 +49,48 @@ public sealed class RetrievalContextSourcesTests
}
[Test]
public void ARelativePathBecomesNoSource()
public void ARelativePathBecomesASourceWithoutLink()
{
var sources = TextContext(path: Path.Combine("docs", "handbook.pdf")).ToSources();
Assert.That(sources, Is.Empty, "A relative path would point somewhere else depending on where it is opened from.");
Assert.Multiple(() =>
{
Assert.That(sources, Has.Count.EqualTo(1), "The AI received the passage, so it needs a source to be read at.");
Assert.That(sources[0].Title, Is.EqualTo("Handbooks"));
Assert.That(sources[0].HasNoLink(), Is.True, "A relative path would point somewhere else depending on where it is opened from.");
Assert.That(sources[0].TryGetDocumentLocation(out _), Is.False);
});
}
[Test]
public void APathOnlyTheDataSourceKnowsBecomesASourceWithoutLink()
{
var sources = TextContext(path: "db://collection/42").ToSources();
Assert.Multiple(() =>
{
Assert.That(sources, Has.Count.EqualTo(1));
Assert.That(sources[0].HasNoLink(), Is.True, "An ERI server may name its content in a way no program here can open.");
});
}
[Test]
public void ThePassagesOfOneDocumentGatherUnderOneSource()
{
List<Source> sources = [..TextContext(path: "db://collection/42").ToSources()];
sources.MergeSources(TextContext(path: "db://collection/42").ToSources());
sources.MergeSources(TextContext(path: "db://collection/43").ToSources());
Assert.That(sources, Has.Count.EqualTo(2), "The same document is one source; another document is another.");
}
[Test]
public void AContextWithoutAnyNameGetsNoSource()
{
var context = TextContext(path: "db://collection/42", dataSourceName: string.Empty);
Assert.That(context.ToSources(), Is.Empty, "A source without a name would be an entry nobody can tell apart.");
}
[Test]
@@ -69,9 +107,9 @@ public sealed class RetrievalContextSourcesTests
private static string AbsolutePath(string fileName) => Path.GetFullPath(Path.Combine(Path.GetTempPath(), fileName));
private static RetrievalTextContext TextContext(string path, string referenceTitle = "", string referenceLink = "", IReadOnlyList<string>? links = null) => new()
private static RetrievalTextContext TextContext(string path, string referenceTitle = "", string referenceLink = "", IReadOnlyList<string>? links = null, string dataSourceName = "Handbooks") => new()
{
DataSourceName = "Handbooks",
DataSourceName = dataSourceName,
Category = RetrievalContentCategory.TEXT,
Type = RetrievalContentType.TEXT_DOCUMENT,
Path = path,