Share how retrieved passages become sources

This commit is contained in:
Thorsten Sommer committed 2026-09-24 15:44:05 +02:00
1 parent 90db777ad5
commit 31410e52f5
3 files changed
+155 -60

No files matched your search

@@ -146,4 +146,75 @@ public static class IRetrievalContextExtensions
contextBuilder.Append(sanitized);
}
}
/// <summary>
/// The sources a retrieval context lends to an answer, as they are listed below it.
/// </summary>
/// <remarks>
/// The reference comes first: the title and link of the passage itself where the data source
/// names them, e.g., a local file with its page, and otherwise the data source and the path.
/// The further links of the context follow. Only what can be opened becomes a source, i.e., a
/// web address or a file with an absolute path. A relative path would point elsewhere depending
/// on where it is opened from.
/// </remarks>
/// <param name="retrievalContext">The retrieval context.</param>
/// <returns>The sources, which may be none.</returns>
public static IReadOnlyList<Source> ToSources(this IRetrievalContext retrievalContext)
{
var sources = new List<Source>();
AddSource(sources, GetReferenceTitle(retrievalContext), GetReferenceLink(retrievalContext));
foreach (var link in retrievalContext.Links)
AddSource(sources, retrievalContext.DataSourceName, link);
return sources;
}
private static void AddSource(ICollection<Source> sources, string title, string link)
{
if (string.IsNullOrWhiteSpace(title) || !TryNormalizeSourceLink(link, out var normalizedLink))
return;
sources.Add(new Source(title, normalizedLink, SourceOrigin.RAG));
}
private static string GetReferenceTitle(IRetrievalContext retrievalContext) =>
retrievalContext is RetrievalTextContext { ReferenceTitle: { Length: > 0 } referenceTitle }
? referenceTitle
: retrievalContext.DataSourceName;
private static string GetReferenceLink(IRetrievalContext retrievalContext) =>
retrievalContext is RetrievalTextContext { ReferenceLink: { Length: > 0 } referenceLink }
? referenceLink
: retrievalContext.Path;
private static bool TryNormalizeSourceLink(string link, out string normalizedLink)
{
normalizedLink = string.Empty;
if (string.IsNullOrWhiteSpace(link))
return false;
if (Uri.TryCreate(link, UriKind.Absolute, out var absoluteUri) && IsSupportedSourceUri(absoluteUri))
{
normalizedLink = absoluteUri.AbsoluteUri;
return true;
}
try
{
if (!Path.IsPathRooted(link))
return false;
normalizedLink = new Uri(Path.GetFullPath(link)).AbsoluteUri;
return true;
}
catch
{
return false;
}
}
private static bool IsSupportedSourceUri(Uri uri) =>
string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|| string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
|| string.Equals(uri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase);
}
@@ -209,7 +209,7 @@ public sealed class AISrcSelWithRetCtxVal : IRagProcess
var ragSources = new List<ISource>();
foreach (var retrievalContext in dataContexts)
ragSources.AddRange(CreateSources(retrievalContext));
ragSources.AddRange(retrievalContext.ToSources());
// Merge the sources, avoiding duplicates:
aiAnswerSources.MergeSources(ragSources);
@@ -219,63 +219,4 @@ public sealed class AISrcSelWithRetCtxVal : IRagProcess
}
#endregion
private static IReadOnlyList<ISource> CreateSources(IRetrievalContext retrievalContext)
{
var sources = new List<ISource>();
AddSource(sources, GetReferenceTitle(retrievalContext), GetReferenceLink(retrievalContext));
foreach (var link in retrievalContext.Links)
AddSource(sources, retrievalContext.DataSourceName, link);
return sources;
}
private static void AddSource(ICollection<ISource> sources, string title, string link)
{
if (string.IsNullOrWhiteSpace(title) || !TryNormalizeSourceLink(link, out var normalizedLink))
return;
sources.Add(new Source(title, normalizedLink, SourceOrigin.RAG));
}
private static string GetReferenceTitle(IRetrievalContext retrievalContext) =>
retrievalContext is RetrievalTextContext { ReferenceTitle: { Length: > 0 } referenceTitle }
? referenceTitle
: retrievalContext.DataSourceName;
private static string GetReferenceLink(IRetrievalContext retrievalContext) =>
retrievalContext is RetrievalTextContext { ReferenceLink: { Length: > 0 } referenceLink }
? referenceLink
: retrievalContext.Path;
private static bool TryNormalizeSourceLink(string link, out string normalizedLink)
{
normalizedLink = string.Empty;
if (string.IsNullOrWhiteSpace(link))
return false;
if (Uri.TryCreate(link, UriKind.Absolute, out var absoluteUri) && IsSupportedSourceUri(absoluteUri))
{
normalizedLink = absoluteUri.AbsoluteUri;
return true;
}
try
{
if (!Path.IsPathRooted(link))
return false;
normalizedLink = new Uri(Path.GetFullPath(link)).AbsoluteUri;
return true;
}
catch
{
return false;
}
}
private static bool IsSupportedSourceUri(Uri uri) =>
string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|| string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
|| string.Equals(uri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase);
}