Skip to content

Commit ff071d7

Browse files
authored
119 ISemanticPluginCompatible (#120)
* removed virtual, renamed to match content * Semantic Kernel discoverability via ISemanticPluginCompatible
1 parent 134c772 commit ff071d7

File tree

10 files changed

+142
-36
lines changed

10 files changed

+142
-36
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Goodtocode.SemanticKernel.Core.Application.Abstractions;
22

3-
public interface IAuthorsPlugin
3+
public interface IAuthorsPlugin : ISemanticPluginCompatible
44
{
5-
Task<IAuthorResponse> GetAuthorNameAsync(Guid authorId, CancellationToken cancellationToken);
5+
Task<IAuthorResponse> GetAuthorByIdAsync(Guid authorId, CancellationToken cancellationToken);
6+
Task<ICollection<IAuthorResponse>> GetAuthorsByNameAsync(string name, CancellationToken cancellationToken);
67
}

src/Core.Application/Abstractions/IChatMessagesPlugin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Goodtocode.SemanticKernel.Core.Application.Abstractions;
22

3-
public interface IChatMessagesPlugin
3+
public interface IChatMessagesPlugin : ISemanticPluginCompatible
44
{
55
Task<IEnumerable<string>> ListRecentMessagesAsync(DateTime? startDate, DateTime? endDate, CancellationToken cancellationToken);
66
Task<IEnumerable<string>> GetChatMessagesAsync(Guid sessionId, CancellationToken cancellationToken);

src/Core.Application/Abstractions/IChatSessionsPlugin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Goodtocode.SemanticKernel.Core.Application.Abstractions;
22

3-
public interface IChatSessionsPlugin
3+
public interface IChatSessionsPlugin : ISemanticPluginCompatible
44
{
55
Task<IEnumerable<string>> ListRecentSessionsAsync(DateTime? startDate, DateTime? endDate, CancellationToken cancellationToken);
66
Task<string> UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Goodtocode.SemanticKernel.Core.Application.Abstractions;
2+
3+
public interface ISemanticPluginCompatible
4+
{
5+
string PluginName { get; }
6+
string FunctionName { get; }
7+
Dictionary<string, object> Parameters { get; }
8+
9+
}

src/Core.Application/ChatCompletion/ChatSessionDto.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class ChatSessionDto
88
public string Title { get; set; } = string.Empty;
99
public Guid AuthorId { get; set; } = Guid.Empty;
1010
public DateTimeOffset Timestamp { get; set; }
11-
public virtual ICollection<ChatMessageDto>? Messages { get; set; }
11+
public ICollection<ChatMessageDto>? Messages { get; set; }
1212

1313
public static ChatSessionDto CreateFrom(ChatSessionEntity? entity)
1414
{

src/Core.Application/TextGeneration/TextPromptDto.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class TextPromptDto
99
public Guid AuthorId { get; set; } = Guid.Empty;
1010
public string Prompt { get; set; } = string.Empty;
1111
public DateTimeOffset Timestamp { get; set; }
12-
public virtual ICollection<TextResponseDto>? Responses { get; set; }
12+
public ICollection<TextResponseDto>? Responses { get; set; }
1313

1414
public static TextPromptDto CreateFrom(TextPromptEntity? entity)
1515
{

src/Infrastructure.SemanticKernel/Plugins/AuthorsPlugin.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Goodtocode.SemanticKernel.Core.Application.Abstractions;
2+
using Microsoft.EntityFrameworkCore;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.SemanticKernel;
45
using System.ComponentModel;
@@ -13,14 +14,28 @@ public class AuthorResponse : IAuthorResponse
1314
public string? Message { get; set; }
1415
}
1516

17+
1618
public sealed class AuthorsPlugin(IServiceProvider serviceProvider) : IAuthorsPlugin
1719
{
1820
private readonly IServiceProvider _serviceProvider = serviceProvider;
1921

20-
[KernelFunction("get_author")]
21-
[Description("Returns structured author info including name, status, and explanation.")]
22-
async Task<IAuthorResponse> IAuthorsPlugin.GetAuthorNameAsync(Guid authorId, CancellationToken cancellationToken)
22+
public string PluginName => "AuthorsPlugin";
23+
public string FunctionName => _currentFunctionName;
24+
public Dictionary<string, object> Parameters => _currentParameters;
25+
26+
private string _currentFunctionName = string.Empty;
27+
private Dictionary<string, object> _currentParameters = new();
28+
29+
[KernelFunction("get_author_by_id")]
30+
[Description("Returns structured author info by ID including name, status, and explanation.")]
31+
public async Task<IAuthorResponse> GetAuthorByIdAsync(Guid authorId, CancellationToken cancellationToken)
2332
{
33+
_currentFunctionName = "get_author_by_id";
34+
_currentParameters = new()
35+
{
36+
{ "authorId", authorId }
37+
};
38+
2439
using var scope = _serviceProvider.CreateScope();
2540
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
2641
var author = await context.Authors.FindAsync([authorId, cancellationToken], cancellationToken: cancellationToken);
@@ -46,4 +61,44 @@ async Task<IAuthorResponse> IAuthorsPlugin.GetAuthorNameAsync(Guid authorId, Can
4661
: "Author found."
4762
};
4863
}
64+
65+
[KernelFunction("get_authors_by_name")]
66+
[Description("Returns structured author info by name including ID, status, and explanation.")]
67+
public async Task<ICollection<IAuthorResponse>> GetAuthorsByNameAsync(string name, CancellationToken cancellationToken)
68+
{
69+
_currentFunctionName = "get_authors_by_name";
70+
_currentParameters = new()
71+
{
72+
{ "name", name }
73+
};
74+
75+
using var scope = _serviceProvider.CreateScope();
76+
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
77+
var authors = await context.Authors
78+
.Where(x => x.Name != null && EF.Functions.Like(x.Name, $"%{name}%"))
79+
.ToListAsync(cancellationToken);
80+
81+
if (authors.Count == 0)
82+
{
83+
return [ new AuthorResponse
84+
{
85+
AuthorId = Guid.Empty,
86+
Name = name,
87+
Status = "NotFound",
88+
Message = "No author found with the specified name."
89+
} ];
90+
}
91+
else
92+
{
93+
return [.. authors.Select(a => new AuthorResponse
94+
{
95+
AuthorId = a.Id,
96+
Name = a.Name,
97+
Status = string.IsNullOrWhiteSpace(a.Name) ? "Partial" : "Found",
98+
Message = string.IsNullOrWhiteSpace(a.Name)
99+
? "Author exists but name is not yet linked to Entra External ID."
100+
: "Author found."
101+
})];
102+
}
103+
}
49104
}

src/Infrastructure.SemanticKernel/Plugins/ChatMessagesPlugin.cs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,38 @@
66

77
namespace Goodtocode.SemanticKernel.Infrastructure.SemanticKernel.Plugins;
88

9-
public sealed class ChatMessagesPlugin(IServiceProvider serviceProvider) : IChatMessagesPlugin
9+
public sealed class ChatMessagesPlugin(IServiceProvider serviceProvider)
10+
: IChatMessagesPlugin, ISemanticPluginCompatible
1011
{
1112
private readonly IServiceProvider _serviceProvider = serviceProvider;
1213

14+
public string PluginName => "ChatMessagesPlugin";
15+
public string FunctionName => _currentFunctionName;
16+
public Dictionary<string, object> Parameters => _currentParameters;
17+
18+
private string _currentFunctionName = string.Empty;
19+
private Dictionary<string, object> _currentParameters = [];
20+
1321
[KernelFunction("list_messages")]
14-
[Description("Retrieves the most recent messages from all chat sessions, optionally filtered by a start and/or end date. Results are ordered from newest to oldest.")]
22+
[Description("Retrieves the most recent messages from all chat sessions.")]
1523
public async Task<IEnumerable<string>> ListRecentMessagesAsync(DateTime? startDate = null, DateTime? endDate = null,
1624
CancellationToken cancellationToken = default)
1725
{
18-
// Resolve ISemanticKernelContext from a new scope to ensure correct EF Core lifetime management.
26+
_currentFunctionName = "list_messages";
27+
_currentParameters = new()
28+
{
29+
{ "startDate", startDate ?? DateTime.UtcNow.AddDays(-7) },
30+
{ "endDate", endDate ?? DateTime.UtcNow.AddSeconds(1)}
31+
};
32+
1933
using var scope = _serviceProvider.CreateScope();
2034
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
2135

2236
var query = context.ChatMessages.AsQueryable();
37+
if (startDate.HasValue) query = query.Where(x => x.Timestamp >= startDate.Value);
38+
if (endDate.HasValue) query = query.Where(x => x.Timestamp <= endDate.Value);
2339

24-
if (startDate.HasValue)
25-
query = query.Where(x => x.Timestamp >= startDate.Value);
26-
if (endDate.HasValue)
27-
query = query.Where(x => x.Timestamp <= endDate.Value);
28-
29-
var messages = await query
30-
.OrderByDescending(x => x.Timestamp)
31-
.ToListAsync(cancellationToken);
32-
40+
var messages = await query.OrderByDescending(x => x.Timestamp).ToListAsync(cancellationToken);
3341
return messages.Select(m => $"{m.ChatSessionId}: {m.Timestamp:u} - {m.Role}: {m.Content}");
3442
}
3543

@@ -38,14 +46,19 @@ public async Task<IEnumerable<string>> ListRecentMessagesAsync(DateTime? startDa
3846
public async Task<IEnumerable<string>> GetChatMessagesAsync(Guid sessionId,
3947
CancellationToken cancellationToken = default)
4048
{
41-
// Get ISemanticKernelContext directly instead of constructor DI to allow this plugin to be registered via AddSingleton() and not scoped due to EF.
49+
_currentFunctionName = "get_messages";
50+
_currentParameters = new()
51+
{
52+
{ "sessionId", sessionId }
53+
};
54+
4255
using var scope = _serviceProvider.CreateScope();
4356
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
4457

4558
var messages = await context.ChatMessages
46-
.Where(x => x.ChatSessionId == sessionId)
59+
.Where(x => x.ChatSessionId == sessionId)
4760
.ToListAsync(cancellationToken);
4861

49-
return messages.Select(m => $"{m.ChatSessionId}: {m.Timestamp} - {m.Role}: {m.Content}");
62+
return messages.Select(m => $"{m.ChatSessionId}: {m.Timestamp:u} - {m.Role}: {m.Content}");
5063
}
51-
}
64+
}

src/Infrastructure.SemanticKernel/Plugins/ChatSessionsPlugin.cs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,35 @@
66

77
namespace Goodtocode.SemanticKernel.Infrastructure.SemanticKernel.Plugins;
88

9-
public sealed class ChatSessionsPlugin(IServiceProvider serviceProvider) : IChatSessionsPlugin
9+
public interface ISemanticPluginCompatible
10+
{
11+
string PluginName { get; }
12+
string FunctionName { get; }
13+
Dictionary<string, object> Parameters { get; }
14+
}
15+
16+
public sealed class ChatSessionsPlugin(IServiceProvider serviceProvider) : IChatSessionsPlugin, ISemanticPluginCompatible
1017
{
1118
private readonly IServiceProvider _serviceProvider = serviceProvider;
1219

20+
public string PluginName => "ChatSessionsPlugin";
21+
public string FunctionName => _currentFunctionName;
22+
public Dictionary<string, object> Parameters => _currentParameters;
23+
24+
private string _currentFunctionName = string.Empty;
25+
private Dictionary<string, object> _currentParameters = new();
26+
1327
[KernelFunction("list_sessions")]
1428
[Description("Retrieves a list of recent chat sessions. Optionally, filter results by start and/or end date to narrow the search.")]
15-
public async Task<IEnumerable<string>> ListRecentSessionsAsync(
16-
DateTime? startDate = null,
17-
DateTime? endDate = null,
18-
CancellationToken cancellationToken = default)
29+
public async Task<IEnumerable<string>> ListRecentSessionsAsync(DateTime? startDate = null, DateTime? endDate = null, CancellationToken cancellationToken = default)
1930
{
20-
// Get ISemanticKernelContext directly instead of constructor DI to allow this plugin to be registered via AddSingleton() and not scoped due to EF.
31+
_currentFunctionName = "list_sessions";
32+
_currentParameters = new()
33+
{
34+
{ "startDate", startDate ?? DateTime.UtcNow.AddDays(-7) },
35+
{ "endDate", endDate ?? DateTime.UtcNow.AddSeconds(1)}
36+
};
37+
2138
using var scope = _serviceProvider.CreateScope();
2239
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
2340

@@ -37,19 +54,30 @@ public async Task<IEnumerable<string>> ListRecentSessionsAsync(
3754

3855
[KernelFunction("change_title")]
3956
[Description("Changes the title on this chat session.")]
40-
public async Task<string> UpdateChatSessionTitleAsync(Guid sessionId, string newTitle,
41-
CancellationToken cancellationToken = default)
57+
public async Task<string> UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken = default)
4258
{
43-
// Get ISemanticKernelContext directly instead of constructor DI to allow this plugin to be registered via AddSingleton() and not scoped due to EF.
59+
_currentFunctionName = "change_title";
60+
_currentParameters = new()
61+
{
62+
{ "sessionId", sessionId },
63+
{ "newTitle", newTitle }
64+
};
65+
4466
using var scope = _serviceProvider.CreateScope();
4567
var context = scope.ServiceProvider.GetRequiredService<ISemanticKernelContext>();
4668

4769
var chatSession = await context.ChatSessions
4870
.FirstOrDefaultAsync(x => x.Id == sessionId, cancellationToken: cancellationToken);
49-
chatSession!.Title = newTitle;
71+
72+
if (chatSession == null)
73+
{
74+
return $"Session {sessionId} not found.";
75+
}
76+
77+
chatSession.Title = newTitle;
5078
context.ChatSessions.Update(chatSession);
5179
await context.SaveChangesAsync(cancellationToken);
5280

5381
return $"{chatSession.Id}: {chatSession.Timestamp} - {chatSession.Title}: {chatSession.Author?.Name}";
5482
}
55-
}
83+
}

0 commit comments

Comments
 (0)