Skip to content

Commit b345a6f

Browse files
authored
Cleanup AI Profile and remove Connection Name (#392)
1 parent 5588c16 commit b345a6f

File tree

90 files changed

+1355
-710
lines changed

Some content is hidden

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

90 files changed

+1355
-710
lines changed

.github/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ If CloudSmith is inaccessible, only asset builds and code analysis are possible.
285285
- **Database IDs**: Use `IdGenerator.GenerateId()` when creating database IDs manually. Generated IDs are always 26 characters long.
286286
- **Date/time**: Never use `DateTime.UtcNow`. Always inject `IClock` in the constructor (e.g., `IClock clock`) and store it as `private readonly IClock _clock = clock;`, then call `_clock.UtcNow` in methods
287287
- **Localization extraction**: When using `ILocalizer`, the property/variable must be named `S`, and localized strings must use the literal pattern `S["This is a localized string"]`. Do not use variables inside the brackets because extraction tooling looks specifically for `S["..."]`.
288+
- **Settings UI casing**: Use sentence case for settings labels, hints, and warning headings. Keep placement tab/card/column names and admin menu labels in title case.
289+
- **Catalog entry handlers**: When a feature must react to create, update, or delete operations for catalog-backed models, prefer `CatalogEntryHandlerBase<T>` registered as `ICatalogEntryHandler<T>` and route write operations through the matching catalog manager so the handler events actually run. Do not rely on raw store writes alone when handler lifecycle behavior is required.
290+
- **Deferred third-party work**: When a catalog handler must call indexing systems, external APIs, or other third-party/background-style services, follow the deferred task pattern used by `ChatInteractionHandler` and `AIMemoryEntryHandler`: capture the affected models/IDs in the scoped handler and schedule the work with `ShellScope.AddDeferredTask(...)` so the external work runs later instead of inline during the catalog event.
288291
- **One type per file**: Every public type must live in its own file. The file name must always match the type name (e.g., `MyService.cs` for `class MyService`)
289292
- **Global usings**: Do not add `GlobalUsings.cs` files or new global using directives; prefer explicit file-level usings
290293
- **sealed classes**: Seal all classes by default (`sealed class`), **except** ViewModel classes that are consumed by any Orchard Core display driver — those must remain unsealed because the framework creates runtime proxies for them and proxies cannot be created from sealed types

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
</PropertyGroup>
88
<ItemGroup>
99
<!-- Miscellaneous Packages -->
10-
<PackageVersion Include="CrestApps.AgentSkills.OrchardCore" Version="1.0.0-preview-0002" />
11-
<PackageVersion Include="CrestApps.AgentSkills.Mcp.OrchardCore" Version="1.0.0-preview-0002" />
10+
<PackageVersion Include="CrestApps.AgentSkills.OrchardCore" Version="1.0.0-preview-0003" />
11+
<PackageVersion Include="CrestApps.AgentSkills.Mcp.OrchardCore" Version="1.0.0-preview-0003" />
1212
<PackageVersion Include="DocumentFormat.OpenXml" Version="3.4.1" />
1313
<PackageVersion Include="Fluid.Core" Version="2.31.0" />
1414
<PackageVersion Include="FluentFTP" Version="53.0.2" />

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/Models/AIProfile.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ public sealed class AIProfile : SourceCatalogEntry, INameAwareModel, IDisplayTex
3030
public string Description { get; set; }
3131

3232
/// <summary>
33-
/// Gets or sets the connection name to use for this profile.
33+
/// Gets or sets the legacy connection name for the profile.
34+
/// Retained for backward compatibility with older stored profiles.
3435
/// </summary>
36+
[Obsolete("Use ChatDeploymentId and UtilityDeploymentId. The selected deployment determines the connection.")]
3537
public string ConnectionName { get; set; }
3638

3739
/// <summary>
@@ -106,6 +108,13 @@ private string _deploymentIdBackingField
106108
/// </summary>
107109
public JsonObject Settings { get; init; } = [];
108110

111+
public string GetLegacyConnectionName()
112+
{
113+
#pragma warning disable CS0618 // Type or member is obsolete
114+
return ConnectionName;
115+
#pragma warning restore CS0618 // Type or member is obsolete
116+
}
117+
109118
/// <summary>
110119
/// Creates a deep copy of the current profile.
111120
/// </summary>
@@ -121,7 +130,9 @@ public AIProfile Clone()
121130
Type = Type,
122131
Description = Description,
123132
OrchestratorName = OrchestratorName,
133+
#pragma warning disable CS0618 // Type or member is obsolete
124134
ConnectionName = ConnectionName,
135+
#pragma warning restore CS0618 // Type or member is obsolete
125136
ChatDeploymentId = ChatDeploymentId,
126137
UtilityDeploymentId = UtilityDeploymentId,
127138
TitleType = TitleType,

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/Models/AIProviderConnection.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,34 @@ public string ProviderName
4242

4343
public string OwnerId { get; set; }
4444

45+
public string GetLegacyChatDeploymentName()
46+
{
47+
#pragma warning disable CS0618 // Type or member is obsolete
48+
return ChatDeploymentName;
49+
#pragma warning restore CS0618 // Type or member is obsolete
50+
}
51+
52+
public string GetLegacyEmbeddingDeploymentName()
53+
{
54+
#pragma warning disable CS0618 // Type or member is obsolete
55+
return EmbeddingDeploymentName;
56+
#pragma warning restore CS0618 // Type or member is obsolete
57+
}
58+
59+
public string GetLegacyImageDeploymentName()
60+
{
61+
#pragma warning disable CS0618 // Type or member is obsolete
62+
return ImagesDeploymentName;
63+
#pragma warning restore CS0618 // Type or member is obsolete
64+
}
65+
66+
public string GetLegacyUtilityDeploymentName()
67+
{
68+
#pragma warning disable CS0618 // Type or member is obsolete
69+
return UtilityDeploymentName;
70+
#pragma warning restore CS0618 // Type or member is obsolete
71+
}
72+
4573
public AIProviderConnection Clone()
4674
{
4775
return new AIProviderConnection

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/Models/ProfileTemplateMetadata.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,22 @@ public sealed class ProfileTemplateMetadata
1313
public AIProfileType? ProfileType { get; set; }
1414

1515
/// <summary>
16-
/// Gets or sets the connection name to pre-fill.
16+
/// Gets or sets the legacy connection name used by older templates.
17+
/// Retained for backward compatibility with existing template metadata.
1718
/// </summary>
19+
[Obsolete("Use ChatDeploymentId and UtilityDeploymentId. The selected deployment determines the connection.")]
1820
public string ConnectionName { get; set; }
1921

22+
/// <summary>
23+
/// Gets or sets the chat deployment identifier to pre-fill.
24+
/// </summary>
25+
public string ChatDeploymentId { get; set; }
26+
27+
/// <summary>
28+
/// Gets or sets the utility deployment identifier to pre-fill.
29+
/// </summary>
30+
public string UtilityDeploymentId { get; set; }
31+
2032
/// <summary>
2133
/// Gets or sets the name of the orchestrator to use.
2234
/// </summary>

src/Core/CrestApps.OrchardCore.AI.Core/Handlers/AIDeploymentProfileHandler.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,6 @@ private static Task PopulateAsync(AIProfile profile, JsonNode data)
4646
profile.ChatDeploymentId = deploymentId;
4747
}
4848

49-
var connectionName = data[nameof(AIProfile.ConnectionName)]?.GetValue<string>()?.Trim();
50-
51-
if (!string.IsNullOrEmpty(connectionName))
52-
{
53-
profile.ConnectionName = connectionName;
54-
}
55-
5649
return Task.CompletedTask;
5750
}
5851
}

src/Core/CrestApps.OrchardCore.AI.Core/Handlers/AIProfileHandler.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,6 @@ private static Task PopulateAsync(AIProfile profile, JsonNode data, bool isNew)
157157
profile.ChatDeploymentId = deploymentId;
158158
}
159159

160-
var connectionName = data[nameof(AIProfile.ConnectionName)]?.GetValue<string>()?.Trim();
161-
162-
if (!string.IsNullOrEmpty(connectionName))
163-
{
164-
profile.ConnectionName = connectionName;
165-
}
166-
167160
var welcomeMessage = data[nameof(AIProfile.WelcomeMessage)]?.GetValue<string>()?.Trim();
168161

169162
if (!string.IsNullOrEmpty(welcomeMessage))

src/Core/CrestApps.OrchardCore.AI.Core/Indexes/AIProfileIndex.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ public sealed class AIProfileIndex : CatalogItemIndex, INameAwareIndex, ISourceA
2828
/// </summary>
2929
public string Description { get; set; }
3030

31-
/// <summary>
32-
/// Gets or sets the connection name associated with the profile.
33-
/// </summary>
34-
public string ConnectionName { get; set; }
35-
3631
/// <summary>
3732
/// Gets or sets the deployment identifier associated with the profile.
3833
/// </summary>

src/Core/CrestApps.OrchardCore.AI.Core/Services/DataExtractionService.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace CrestApps.OrchardCore.AI.Core.Services;
1313
public sealed class DataExtractionService
1414
{
1515
private readonly IAIClientFactory _clientFactory;
16+
private readonly IAIDeploymentManager _deploymentManager;
1617
private readonly IAITemplateService _aiTemplateService;
1718
private readonly IClock _clock;
1819
private readonly AIProviderOptions _providerOptions;
@@ -23,9 +24,11 @@ public DataExtractionService(
2324
IAITemplateService aiTemplateService,
2425
IOptions<AIProviderOptions> providerOptions,
2526
IClock clock,
26-
ILogger<DataExtractionService> logger)
27+
ILogger<DataExtractionService> logger,
28+
IAIDeploymentManager deploymentManager = null)
2729
{
2830
_clientFactory = clientFactory;
31+
_deploymentManager = deploymentManager;
2932
_aiTemplateService = aiTemplateService;
3033
_clock = clock;
3134
_providerOptions = providerOptions.Value;
@@ -253,14 +256,29 @@ private ExtractionChangeSet ApplyExtraction(
253256

254257
private async Task<IChatClient> GetChatClientAsync(AIProfile profile)
255258
{
259+
if (_deploymentManager != null)
260+
{
261+
var deployment = await _deploymentManager.ResolveAsync(
262+
AIDeploymentType.Utility,
263+
deploymentId: profile.UtilityDeploymentId,
264+
providerName: profile.Source)
265+
?? await _deploymentManager.ResolveAsync(
266+
AIDeploymentType.Chat,
267+
deploymentId: profile.ChatDeploymentId,
268+
providerName: profile.Source);
269+
270+
if (deployment != null && !string.IsNullOrEmpty(deployment.ConnectionName) && !string.IsNullOrEmpty(deployment.Name))
271+
{
272+
return await _clientFactory.CreateChatClientAsync(profile.Source, deployment.ConnectionName, deployment.Name);
273+
}
274+
}
275+
256276
if (!_providerOptions.Providers.TryGetValue(profile.Source, out var provider))
257277
{
258278
return null;
259279
}
260280

261-
var connectionName = !string.IsNullOrEmpty(profile.ConnectionName)
262-
? profile.ConnectionName
263-
: provider.DefaultConnectionName;
281+
var connectionName = provider.DefaultConnectionName;
264282

265283
if (string.IsNullOrEmpty(connectionName) || !provider.Connections.TryGetValue(connectionName, out var connection))
266284
{

src/Core/CrestApps.OrchardCore.AI.Core/Services/PostSessionProcessingService.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace CrestApps.OrchardCore.AI.Core.Services;
1919
public sealed class PostSessionProcessingService
2020
{
2121
private readonly IAIClientFactory _clientFactory;
22+
private readonly IAIDeploymentManager _deploymentManager;
2223
private readonly IAIToolsService _toolsService;
2324
private readonly IAITemplateService _aiTemplateService;
2425
private readonly IServiceProvider _serviceProvider;
@@ -36,9 +37,11 @@ public PostSessionProcessingService(
3637
DefaultAIOptions defaultOptions,
3738
IServiceProvider serviceProvider,
3839
IClock clock,
39-
ILoggerFactory loggerFactory)
40+
ILoggerFactory loggerFactory,
41+
IAIDeploymentManager deploymentManager = null)
4042
{
4143
_clientFactory = clientFactory;
44+
_deploymentManager = deploymentManager;
4245
_toolsService = toolsService;
4346
_aiTemplateService = aiTemplateService;
4447
_serviceProvider = serviceProvider;
@@ -792,14 +795,29 @@ private Dictionary<string, PostSessionResult> ApplyResults(
792795

793796
private async Task<IChatClient> GetChatClientAsync(AIProfile profile)
794797
{
798+
if (_deploymentManager != null)
799+
{
800+
var deployment = await _deploymentManager.ResolveAsync(
801+
AIDeploymentType.Utility,
802+
deploymentId: profile.UtilityDeploymentId,
803+
providerName: profile.Source)
804+
?? await _deploymentManager.ResolveAsync(
805+
AIDeploymentType.Chat,
806+
deploymentId: profile.ChatDeploymentId,
807+
providerName: profile.Source);
808+
809+
if (deployment != null && !string.IsNullOrEmpty(deployment.ConnectionName) && !string.IsNullOrEmpty(deployment.Name))
810+
{
811+
return await _clientFactory.CreateChatClientAsync(profile.Source, deployment.ConnectionName, deployment.Name);
812+
}
813+
}
814+
795815
if (!_providerOptions.Providers.TryGetValue(profile.Source, out var provider))
796816
{
797817
return null;
798818
}
799819

800-
var connectionName = !string.IsNullOrEmpty(profile.ConnectionName)
801-
? profile.ConnectionName
802-
: provider.DefaultConnectionName;
820+
var connectionName = provider.DefaultConnectionName;
803821

804822
if (string.IsNullOrEmpty(connectionName) || !provider.Connections.TryGetValue(connectionName, out var connection))
805823
{

0 commit comments

Comments
 (0)