diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b31b218..7bd645a5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,6 +38,7 @@ + @@ -45,7 +46,7 @@ - + diff --git a/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs b/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs index 89f097ae..9dce26cd 100644 --- a/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs +++ b/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs @@ -47,7 +47,11 @@ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context var agentInvoker = context.GetRequiredService(); var result = await agentInvoker.InvokeAgentAsync(AgentName, functionInput, context.CancellationToken); - var json = result.FunctionResult.GetValue(); + var json = result.ChatMessageContent.Content?.Trim(); + + if (string.IsNullOrWhiteSpace(json)) + throw new InvalidOperationException("The message content is empty or null."); + var outputType = context.ActivityDescriptor.Outputs.Single().Type; // If the target type is object, we want the JSON to be deserialized into an ExpandoObject for dynamic field access. diff --git a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj index 0879dc1d..77e8b563 100644 --- a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj +++ b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj @@ -3,7 +3,7 @@ Provides Agent activities elsa extension module agents semantic kernel llm ai - + diff --git a/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs new file mode 100644 index 00000000..7b14d61d --- /dev/null +++ b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs @@ -0,0 +1,8 @@ +using Microsoft.SemanticKernel; +namespace Elsa.Agents; + +public interface IKernelFactory +{ + Kernel CreateKernel(KernelConfig kernelConfig, AgentConfig agentConfig); + Kernel CreateKernel(KernelConfig kernelConfig, string agentName); +} \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj index 28b86c8a..bdb4be65 100644 --- a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj +++ b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs b/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs index 2a4cdc6f..5afbbab3 100644 --- a/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs +++ b/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs @@ -20,6 +20,7 @@ public static OpenAIPromptExecutionSettings ToOpenAIPromptExecutionSettings(this ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, ResponseFormat = agentConfig.ExecutionSettings.ResponseFormat, ChatSystemPrompt = agentConfig.PromptTemplate, + ServiceId = "default" }; } diff --git a/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs b/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs index ba02314f..90b66954 100644 --- a/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs +++ b/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs @@ -8,18 +8,24 @@ public static class FunctionResultExtensions public static async Task AsJsonElementAsync(this Task resultTask) { var result = await resultTask; - return result.FunctionResult.AsJsonElement(); + return result.ChatMessageContent.AsJsonElement(); } - - public static async Task AsJsonElementAsync(this Task resultTask) - { - var result = await resultTask; - return result.AsJsonElement(); - } - - public static JsonElement AsJsonElement(this FunctionResult result) + + public static JsonElement AsJsonElement(this ChatMessageContent result) { - var response = result.GetValue()!; - return JsonSerializer.Deserialize(response); + var content = result.Content?.Trim(); + + if (string.IsNullOrWhiteSpace(content)) + throw new InvalidOperationException("The message content is empty."); + + try + { + return JsonSerializer.Deserialize(content!); + } + catch (JsonException ex) + { + throw new InvalidOperationException($"Error deserializing the message content as JSON:\n{content}", ex); + } + } } \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs b/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs index ee2732b9..d7b032ba 100644 --- a/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs +++ b/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs @@ -3,12 +3,8 @@ namespace Elsa.Agents; -public record InvokeAgentResult(AgentConfig Function, FunctionResult FunctionResult) +public record InvokeAgentResult(AgentConfig Function, ChatMessageContent ChatMessageContent) { - public object? ParseResult() - { - var targetType = Type.GetType(Function.OutputVariable.Type) ?? typeof(JsonElement); - var json = FunctionResult.GetValue(); - return JsonSerializer.Deserialize(json, targetType); - } + + } \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs index a40458c5..907caae4 100644 --- a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs +++ b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs @@ -1,12 +1,14 @@ using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; +using Microsoft.SemanticKernel.PromptTemplates.Handlebars; #pragma warning disable SKEXP0010 #pragma warning disable SKEXP0001 namespace Elsa.Agents; -public class AgentInvoker(KernelFactory kernelFactory, IKernelConfigProvider kernelConfigProvider) +public class AgentInvoker(IKernelFactory kernelFactory, IKernelConfigProvider kernelConfigProvider) { public async Task InvokeAgentAsync(string agentName, IDictionary input, CancellationToken cancellationToken = default) { @@ -21,9 +23,9 @@ public async Task InvokeAgentAsync(string agentName, IDiction MaxTokens = executionSettings.MaxTokens, PresencePenalty = executionSettings.PresencePenalty, FrequencyPenalty = executionSettings.FrequencyPenalty, - ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, ResponseFormat = executionSettings.ResponseFormat, ChatSystemPrompt = agentConfig.PromptTemplate, + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; var promptExecutionSettingsDictionary = new Dictionary @@ -46,10 +48,36 @@ public async Task InvokeAgentAsync(string agentName, IDiction AllowDangerouslySetContent = true }).ToList() }; - - var kernelFunction = kernel.CreateFunctionFromPrompt(promptTemplateConfig); + + var templateFactory = new HandlebarsPromptTemplateFactory(); + + var promptConfig = new PromptTemplateConfig + { + Template = agentConfig.PromptTemplate, + TemplateFormat = "handlebars", + Name = agentConfig.FunctionName + }; + + var promptTemplate = templateFactory.Create(promptConfig); + var kernelArguments = new KernelArguments(input); - var result = await kernelFunction.InvokeAsync(kernel, kernelArguments, cancellationToken: cancellationToken); - return new(agentConfig, result); + string renderedPrompt = await promptTemplate.RenderAsync(kernel, kernelArguments); + + ChatHistory chatHistory = []; + chatHistory.AddUserMessage(renderedPrompt); + + IChatCompletionService chatCompletion = kernel.GetRequiredService(); + + OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() + { + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() + }; + + var response = await chatCompletion.GetChatMessageContentAsync( + chatHistory, + executionSettings: openAIPromptExecutionSettings, + kernel: kernel); + + return new(agentConfig, response); } } \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs b/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs index bdc738f4..9b8dfaf8 100644 --- a/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs +++ b/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs @@ -7,7 +7,7 @@ namespace Elsa.Agents; -public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger logger) +public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger logger) : IKernelFactory { public Kernel CreateKernel(KernelConfig kernelConfig, string agentName) {