Skip to content

Commit 3b01de0

Browse files
authored
Add Knowledge Base References (#333)
1 parent 976f1c8 commit 3b01de0

File tree

110 files changed

+3556
-1167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+3556
-1167
lines changed

Directory.Packages.props

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,24 @@
99
<PackageVersion Include="CrestApps.AgentSkills.OrchardCore" Version="1.0.0-preview-0001" />
1010
<PackageVersion Include="CrestApps.AgentSkills.Mcp.OrchardCore" Version="1.0.0-preview-0001" />
1111
<PackageVersion Include="DocumentFormat.OpenXml" Version="3.4.1" />
12-
<PackageVersion Include="FluentFTP" Version="52.1.0" />
12+
<PackageVersion Include="FluentFTP" Version="53.0.2" />
1313
<PackageVersion Include="JsonSchema.Net" Version="7.4.0" />
1414
<PackageVersion Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00017" />
15+
<PackageVersion Include="Markdig" Version="1.0.0" />
16+
<PackageVersion Include="Microsoft.Extensions.DataIngestion" Version="10.3.0-preview.1.26109.11" />
17+
<PackageVersion Include="Microsoft.Extensions.DataIngestion.Markdig" Version="10.3.0-preview.1.26109.11" />
18+
<PackageVersion Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="2.0.0" />
1519
<PackageVersion Include="MongoDB.Driver" Version="3.4.0" />
1620
<PackageVersion Include="NuGet.Versioning" Version="7.0.1" />
1721
<PackageVersion Include="OllamaSharp" Version="5.4.16" />
18-
<PackageVersion Include="SSH.NET" Version="2024.2.0" />
22+
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
1923
<PackageVersion Include="YesSql.Abstractions" Version="5.4.7" />
2024
<PackageVersion Include="YesSql.Core" Version="5.4.7" />
21-
<PackageVersion Include="ModelContextProtocol" Version="0.8.0-preview.1" />
22-
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.8.0-preview.1" />
23-
<PackageVersion Include="ModelContextProtocol.Core" Version="0.8.0-preview.1" />
25+
<PackageVersion Include="ModelContextProtocol" Version="1.0.0" />
26+
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="1.0.0" />
27+
<PackageVersion Include="ModelContextProtocol.Core" Version="1.0.0" />
2428
<PackageVersion Include="PdfPig" Version="0.1.13" />
25-
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.25" />
29+
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.26" />
2630
</ItemGroup>
2731
<ItemGroup>
2832
<!-- OrchardCore Packages -->
@@ -87,27 +91,27 @@
8791
<PackageVersion Include="Azure.Messaging.EventGrid" Version="5.0.0" />
8892
<PackageVersion Include="Azure.Search.Documents" Version="11.7.0" />
8993
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.3" />
90-
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.2.0" />
91-
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
92-
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.2.0-preview.1.26063.2" />
94+
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.3.0" />
95+
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.3.0" />
96+
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />
9397
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="10.0.0-preview.1.25559.3" />
9498
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
95-
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.2.0" />
99+
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.3.0" />
96100
</ItemGroup>
97101
<ItemGroup>
98102
<!-- Testing Packages -->
99103
<PackageVersion Include="Moq" Version="4.20.72" />
100-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
104+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
101105
<PackageVersion Include="xunit.v3" Version="3.2.2" />
102106
<PackageVersion Include="xunit.analyzers" Version="1.27.0" />
103107
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
104108
<PackageVersion Include="xunit.runner.inproc" Version="3.2.0" />
105109
</ItemGroup>
106110
<ItemGroup>
107111
<!-- Aspire Packages -->
108-
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.1.0" />
112+
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.1.1" />
109113
<PackageVersion Include="Aspire.Hosting.Elasticsearch" Version="13.1.0" />
110-
<PackageVersion Include="Aspire.Hosting.Redis" Version="13.1.0" />
114+
<PackageVersion Include="Aspire.Hosting.Redis" Version="13.1.1" />
111115
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="13.1.1" />
112116
</ItemGroup>
113117
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace CrestApps.OrchardCore.AI;
2+
3+
/// <summary>
4+
/// Provides well-known keys for <see cref="Models.AICompletionContext.AdditionalProperties"/>.
5+
/// These keys are used by orchestration handlers to communicate context availability
6+
/// to downstream consumers such as <see cref="IToolRegistryProvider"/> implementations.
7+
/// </summary>
8+
public static class AICompletionContextKeys
9+
{
10+
/// <summary>
11+
/// When set to <see langword="true"/> in <see cref="Models.AICompletionContext.AdditionalProperties"/>,
12+
/// indicates that documents are available for the current session. This enables
13+
/// document processing system tools (e.g., <c>search_documents</c>, <c>list_documents</c>)
14+
/// to be included in the tool registry.
15+
/// </summary>
16+
public const string HasDocuments = "HasDocuments";
17+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using CrestApps.OrchardCore.AI.Models;
2+
3+
namespace CrestApps.OrchardCore.AI;
4+
5+
/// <summary>
6+
/// Per-invocation context for AI operations, providing isolation between concurrent
7+
/// SignalR hub method calls. Each hub invocation creates its own instance via
8+
/// <see cref="AIInvocationScope.Begin"/>, and AI tools retrieve it via
9+
/// <see cref="AIInvocationScope.Current"/>.
10+
///
11+
/// <para>
12+
/// <b>Why this exists:</b> In SignalR, <c>HttpContext</c> is shared across all hub
13+
/// method invocations on the same WebSocket connection. If a user sends multiple
14+
/// messages concurrently, writing to <c>HttpContext.Items</c> causes data leaks
15+
/// between invocations. This class uses <see cref="System.Threading.AsyncLocal{T}"/>
16+
/// (via <see cref="AIInvocationScope"/>) to provide truly invocation-scoped storage
17+
/// that flows correctly through async/await chains without any cross-invocation
18+
/// contamination.
19+
/// </para>
20+
///
21+
/// <para>
22+
/// <b>For AI tools:</b> Tools are registered as singletons and the AI model does not
23+
/// pass any invocation identifier when calling them. Tools retrieve the current
24+
/// invocation context by calling <c>AIInvocationScope.Current</c>, which returns the
25+
/// context for the async execution flow that is calling the tool — even when multiple
26+
/// invocations are in flight simultaneously on different threads or continuations.
27+
/// </para>
28+
/// </summary>
29+
public sealed class AIInvocationContext
30+
{
31+
private int _referenceIndex;
32+
33+
/// <summary>
34+
/// Gets or sets the <see cref="AIToolExecutionContext"/> for the current invocation,
35+
/// providing provider, connection, and resource information.
36+
/// </summary>
37+
public AIToolExecutionContext ToolExecutionContext { get; set; }
38+
39+
/// <summary>
40+
/// Gets or sets the data source identifier for the current invocation.
41+
/// Used by <c>DataSourceSearchTool</c> to scope searches to the correct data source.
42+
/// </summary>
43+
public string DataSourceId { get; set; }
44+
45+
/// <summary>
46+
/// Gets the dictionary of citation references collected during tool execution
47+
/// (e.g., from <c>DataSourceSearchTool</c> and <c>SearchDocumentsTool</c>).
48+
/// Keyed by the citation marker (e.g., "[doc:1]") with the reference metadata as value.
49+
/// </summary>
50+
public Dictionary<string, AICompletionReference> ToolReferences { get; } = new(StringComparer.OrdinalIgnoreCase);
51+
52+
/// <summary>
53+
/// Gets a general-purpose property bag for extensibility.
54+
/// Handlers and tools can store arbitrary per-invocation data here.
55+
/// </summary>
56+
public Dictionary<string, object> Items { get; } = new(StringComparer.OrdinalIgnoreCase);
57+
58+
/// <summary>
59+
/// Returns the next unique reference index for citation markers (e.g., [doc:1], [doc:2], ...).
60+
/// This method is thread-safe and ensures a monotonically increasing counter across all
61+
/// handlers and tools within the same invocation, preventing index collisions between
62+
/// <c>DataSourcePreemptiveRagHandler</c>, <c>DocumentPreemptiveRagHandler</c>,
63+
/// <c>DataSourceSearchTool</c>, and <c>SearchDocumentsTool</c>.
64+
/// </summary>
65+
/// <returns>The next 1-based reference index.</returns>
66+
public int NextReferenceIndex()
67+
=> Interlocked.Increment(ref _referenceIndex);
68+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace CrestApps.OrchardCore.AI;
2+
3+
/// <summary>
4+
/// Manages an <see cref="AsyncLocal{T}"/>-backed ambient context for AI invocations.
5+
/// Each SignalR hub method call (or any entry point) creates a scope with
6+
/// <see cref="Begin"/>, and any code running in that async flow can access the
7+
/// context via <see cref="Current"/>.
8+
///
9+
/// <para>
10+
/// <b>Usage in hubs:</b>
11+
/// <code>
12+
/// using var scope = AIInvocationScope.Begin();
13+
/// // scope.Context is the AIInvocationContext for this invocation.
14+
/// // All downstream code (handlers, tools) can call AIInvocationScope.Current.
15+
/// </code>
16+
/// </para>
17+
///
18+
/// <para>
19+
/// <b>Usage in AI tools (singletons):</b>
20+
/// <code>
21+
/// var context = AIInvocationScope.Current;
22+
/// var dataSourceId = context?.DataSourceId;
23+
/// </code>
24+
/// Because <see cref="AsyncLocal{T}"/> flows through async/await continuations,
25+
/// each concurrent invocation sees its own context — even when multiple invocations
26+
/// share the same <c>HttpContext</c> in a SignalR WebSocket connection.
27+
/// </para>
28+
/// </summary>
29+
public static class AIInvocationScope
30+
{
31+
private static readonly AsyncLocal<AIInvocationContext> _current = new();
32+
33+
/// <summary>
34+
/// Gets the <see cref="AIInvocationContext"/> for the current async execution flow,
35+
/// or <c>null</c> if no scope has been started.
36+
/// </summary>
37+
public static AIInvocationContext Current => _current.Value;
38+
39+
/// <summary>
40+
/// Begins a new invocation scope with a fresh <see cref="AIInvocationContext"/>.
41+
/// The returned <see cref="Scope"/> must be disposed to clear the context.
42+
/// </summary>
43+
/// <returns>A disposable scope that clears the context on disposal.</returns>
44+
public static Scope Begin()
45+
=> new(new AIInvocationContext());
46+
47+
/// <summary>
48+
/// Begins a new invocation scope with the specified <see cref="AIInvocationContext"/>.
49+
/// The returned <see cref="Scope"/> must be disposed to clear the context.
50+
/// </summary>
51+
/// <param name="context">The invocation context to make current.</param>
52+
/// <returns>A disposable scope that clears the context on disposal.</returns>
53+
public static Scope Begin(AIInvocationContext context)
54+
{
55+
ArgumentNullException.ThrowIfNull(context);
56+
57+
return new Scope(context);
58+
}
59+
60+
/// <summary>
61+
/// A disposable wrapper that sets and clears the <see cref="AIInvocationScope.Current"/> context.
62+
/// </summary>
63+
public readonly struct Scope : IDisposable
64+
{
65+
/// <summary>
66+
/// Gets the <see cref="AIInvocationContext"/> associated with this scope.
67+
/// </summary>
68+
public AIInvocationContext Context { get; }
69+
70+
internal Scope(AIInvocationContext context)
71+
{
72+
Context = context;
73+
_current.Value = context;
74+
}
75+
76+
/// <summary>
77+
/// Clears the current invocation context, preventing data from leaking
78+
/// into subsequent operations on the same thread.
79+
/// </summary>
80+
public void Dispose()
81+
{
82+
_current.Value = null;
83+
}
84+
}
85+
}

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/AIToolExecutionContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ namespace CrestApps.OrchardCore.AI;
33
/// <summary>
44
/// Ambient context that provides AI tool implementations with
55
/// the provider, connection, and resource information of the current request.
6-
/// Stored in <c>HttpContext.Items</c> by the caller (e.g., AIChatHub).
6+
/// Stored in <see cref="AIInvocationContext.ToolExecutionContext"/> and accessible
7+
/// via <c>AIInvocationScope.Current?.ToolExecutionContext</c>.
78
/// </summary>
89
public sealed class AIToolExecutionContext
910
{

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/DataSourceSearchResult.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public sealed class DataSourceSearchResult
2525
/// </summary>
2626
public int ChunkIndex { get; set; }
2727

28+
/// <summary>
29+
/// Gets or sets the reference type that identifies the kind of source
30+
/// (e.g., "Content" for Orchard Core content items, or the source index profile type).
31+
/// </summary>
32+
public string ReferenceType { get; set; }
33+
2834
/// <summary>
2935
/// Gets or sets the similarity score.
3036
/// </summary>

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/DocumentChunkSearchResult.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ public sealed class DocumentChunkSearchResult
1010
/// </summary>
1111
public Models.ChatInteractionDocumentChunk Chunk { get; set; }
1212

13+
/// <summary>
14+
/// Gets or sets the unique key that identifies the source document.
15+
/// </summary>
16+
public string DocumentKey { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the original file name of the source document.
20+
/// </summary>
21+
public string FileName { get; set; }
22+
1323
/// <summary>
1424
/// Gets or sets the similarity score.
1525
/// </summary>

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/IAILinkGenerator.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace CrestApps.OrchardCore.AI;
2+
3+
/// <summary>
4+
/// Resolves links for AI completion references based on the reference type.
5+
/// Implementations are registered as keyed services using the reference type as the key,
6+
/// allowing different strategies for different source types (e.g., content items, documents, custom).
7+
/// </summary>
8+
public interface IAIReferenceLinkResolver
9+
{
10+
/// <summary>
11+
/// Resolves a link URL for the given reference.
12+
/// </summary>
13+
/// <param name="referenceId">The unique identifier of the referenced resource.</param>
14+
/// <param name="metadata">Optional metadata associated with the reference.</param>
15+
/// <returns>The resolved link URL, or <c>null</c> if no link can be generated.</returns>
16+
string ResolveLink(string referenceId, IDictionary<string, object> metadata);
17+
}

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/IDataSourceVectorSearchService.cs renamed to src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/IDataSourceContentManager.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
namespace CrestApps.OrchardCore.AI;
44

5-
/// <summary>
6-
/// Interface for searching data source embeddings in various index providers.
7-
/// Implementations should be registered as keyed services using the provider name.
8-
/// </summary>
9-
public interface IDataSourceVectorSearchService
5+
public interface IDataSourceContentManager
106
{
117
/// <summary>
128
/// Searches for document chunks that are similar to the provided embedding vector.
@@ -25,4 +21,16 @@ Task<IEnumerable<DataSourceSearchResult>> SearchAsync(
2521
int topN,
2622
string filter = null,
2723
CancellationToken cancellationToken = default);
24+
25+
/// <summary>
26+
/// Deletes all document chunks belonging to the specified data source from the index.
27+
/// </summary>
28+
/// <param name="indexProfile">The index profile to delete from.</param>
29+
/// <param name="dataSourceId">The data source ID whose documents should be deleted.</param>
30+
/// <param name="cancellationToken">Cancellation token.</param>
31+
/// <returns>The number of documents deleted.</returns>
32+
Task<long> DeleteByDataSourceIdAsync(
33+
IndexProfile indexProfile,
34+
string dataSourceId,
35+
CancellationToken cancellationToken = default);
2836
}

0 commit comments

Comments
 (0)