Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ef105a
Fix unhandled exception logging (#59)
mohdali Jun 9, 2025
c20c07d
Updates base version to 3.7.0
sfmskywalker Jun 12, 2025
22ceac8
Update ElsaVersion to 3.7.0-preview.3055 in Directory.Build.props
sfmskywalker Jun 12, 2025
dd65fd2
IKernelFactory
Jul 3, 2025
4928844
IKernelFactory
Jul 3, 2025
df20936
Implementing SK function that supports external tools
Jul 3, 2025
164dfe7
Add 'develop/*' branch to push configuration in packages.yml
sfmskywalker Jul 3, 2025
93a6edf
Restoring .csproj
Jul 3, 2025
efaf412
Restoring Directory.Packages.props
Jul 3, 2025
a98cb7b
Replace var name
Jul 3, 2025
637ac2e
Disable project references in Directory.Build.props and update packag…
sfmskywalker Jul 3, 2025
166ac5e
Update package version prefix logic to include 'develop/' branches
sfmskywalker Jul 3, 2025
eef3791
Merge pull request #64 from pugafran/IKernelFactory
sfmskywalker Jul 3, 2025
b25ff87
Update AlterationHandlerContext.cs
sfmskywalker Jul 4, 2025
34426b4
Update BackgroundAlterationJobDispatcher.cs
sfmskywalker Jul 4, 2025
12b0c03
Update WorkflowExecutionLogStore.cs
sfmskywalker Jul 4, 2025
a9871f4
Update AlterationHandlerContext.cs
sfmskywalker Jul 4, 2025
f5e1e3c
Update BackgroundAlterationJobDispatcher.cs
sfmskywalker Jul 4, 2025
279ff59
Update WorkflowExecutionLogStore.cs
sfmskywalker Jul 4, 2025
87ffdf1
Update MongoDbStore.cs
sfmskywalker Jul 4, 2025
1d41492
Merge pull request #65 from elsa-workflows/sfmskywalker-patch-1
sfmskywalker Jul 4, 2025
c790230
Update MongoDbFeature.cs
sfmskywalker Jul 4, 2025
5a8d4ad
Update ExpressionHelpers.cs
sfmskywalker Jul 4, 2025
cb07d49
Merge pull request #66 from elsa-workflows/sfmskywalker-patch-2
sfmskywalker Jul 4, 2025
cb5d23c
Merge pull request #67 from elsa-workflows/sfmskywalker-patch-3
sfmskywalker Jul 4, 2025
d777342
Update WorkflowInstanceStore.cs
sfmskywalker Jul 4, 2025
e7a3fdd
Merge pull request #68 from elsa-workflows/sfmskywalker-patch-4
sfmskywalker Jul 4, 2025
b3f610d
Merge remote-tracking branch 'origin/develop/3.6.0'
sfmskywalker Jul 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 'main'
- 'develop/*'
- 'bug/*'
- 'issue/*'
- 'patch/*'
Expand All @@ -12,7 +13,7 @@ on:
release:
types: [ prereleased, published ]
env:
base_version: '3.6.0'
base_version: '3.7.0'
feedz_feed_source: 'https://f.feedz.io/elsa-workflows/elsa-3/nuget/index.json'
nuget_feed_source: 'https://api.nuget.org/v3/index.json'

Expand All @@ -30,7 +31,7 @@ jobs:
PACKAGE_PREFIX=$(echo $BRANCH_NAME | rev | cut -d/ -f1 | rev | tr '_' '-')

# If the branch name is main, use the preview version. Otherwise, use the branch name as the version prefix.
if [[ "${BRANCH_NAME}" == "main" || "${BRANCH_NAME}" =~ ^rc/ ]]; then
if [[ "${BRANCH_NAME}" == "main" || "${BRANCH_NAME}" =~ ^develop/ ]]; then
PACKAGE_PREFIX="preview"
fi

Expand Down
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<UseProjectReferences>true</UseProjectReferences>
<UseProjectReferences>false</UseProjectReferences>
</PropertyGroup>
<PropertyGroup>
<Authors>Elsa Workflows Community</Authors>
Expand Down Expand Up @@ -41,8 +41,8 @@
</PropertyGroup>

<PropertyGroup Label="PackageVersions">
<ElsaVersion>3.6.0-preview.3056</ElsaVersion>
<ElsaStudioVersion>3.6.0-preview.1087</ElsaStudioVersion>
<ElsaVersion>3.7.0-preview.3055</ElsaVersion>
<ElsaStudioVersion>3.6.0-preview.1029</ElsaStudioVersion>
<MicrosoftVersion>9.0.6</MicrosoftVersion>
</PropertyGroup>

Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@
<PackageVersion Include="Elsa.Workflows.Management" Version="$(ElsaVersion)" />
<PackageVersion Include="Elsa.Workflows.Runtime" Version="$(ElsaVersion)" />
<PackageVersion Include="Elsa.Workflows.Runtime.Distributed" Version="$(ElsaVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.PromptTemplates.Handlebars" Version="1.59.0" />
</ItemGroup>
<ItemGroup Label="Elsa Studio">
<PackageVersion Include="Elsa.Studio" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Core" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Shared" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Workflows" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="$(ElsaStudioVersion)"/>
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="$(ElsaStudioVersion)" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Antlr4.Runtime.Standard" Version="4.13.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context

var agentInvoker = context.GetRequiredService<AgentInvoker>();
var result = await agentInvoker.InvokeAgentAsync(AgentName, functionInput, context.CancellationToken);
var json = result.FunctionResult.GetValue<string>();
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Provides Agent activities</Description>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.SemanticKernel.PromptTemplates.Handlebars" />
</ItemGroup>

<ItemGroup Label="Elsa" Condition="'$(UseProjectReferences)' != 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static OpenAIPromptExecutionSettings ToOpenAIPromptExecutionSettings(this
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
ResponseFormat = agentConfig.ExecutionSettings.ResponseFormat,
ChatSystemPrompt = agentConfig.PromptTemplate,
ServiceId = "default"
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ public static class FunctionResultExtensions
public static async Task<JsonElement> AsJsonElementAsync(this Task<InvokeAgentResult> resultTask)
{
var result = await resultTask;
return result.FunctionResult.AsJsonElement();
return result.ChatMessageContent.AsJsonElement();
}

public static async Task<JsonElement> AsJsonElementAsync(this Task<FunctionResult> 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<string>()!;
return JsonSerializer.Deserialize<JsonElement>(response);
var content = result.Content?.Trim();

if (string.IsNullOrWhiteSpace(content))
throw new InvalidOperationException("The message content is empty.");

try
{
return JsonSerializer.Deserialize<JsonElement>(content!);
}
catch (JsonException ex)
{
throw new InvalidOperationException($"Error deserializing the message content as JSON:\n{content}", ex);
}

}
}
10 changes: 3 additions & 7 deletions src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
return JsonSerializer.Deserialize(json, targetType);
}


}
40 changes: 34 additions & 6 deletions src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs
Original file line number Diff line number Diff line change
@@ -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<InvokeAgentResult> InvokeAgentAsync(string agentName, IDictionary<string, object?> input, CancellationToken cancellationToken = default)
{
Expand All @@ -21,9 +23,9 @@ public async Task<InvokeAgentResult> 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<string, PromptExecutionSettings>
Expand All @@ -46,10 +48,36 @@ public async Task<InvokeAgentResult> 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<IChatCompletionService>();

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

var response = await chatCompletion.GetChatMessageContentAsync(
chatHistory,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);

return new(agentConfig, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Elsa.Agents;

public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger<KernelFactory> logger)
public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger<KernelFactory> logger) : IKernelFactory
{
public Kernel CreateKernel(KernelConfig kernelConfig, string agentName)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,26 @@ public void Succeed(Func<Task> commitAction)
CommitAction = commitAction;
}

/// <summary>
/// Marks the alteration as succeeded.
/// </summary>
public void Succeed(Action commitAction)
{
Succeed();
CommitAction = () =>
{
commitAction();
return Task.CompletedTask;
};
}

/// <summary>
/// Marks the alteration as succeeded.
/// </summary>
public void Succeed(string message)
{
HasSucceeded = true;
Log($"Alteration {Alteration.GetType().Name} succeeded", message, LogLevel.Information);
Log($"Alteration {Alteration.GetType().Name} succeeded", message);
}

/// <summary>
Expand All @@ -116,6 +129,19 @@ public void Succeed(string message, Func<Task> commitAction)
Succeed(message);
CommitAction = commitAction;
}

/// <summary>
/// Marks the alteration as succeeded.
/// </summary>
public void Succeed(string message, Action commitAction)
{
Succeed(message);
CommitAction = () =>
{
commitAction();
return Task.CompletedTask;
};
}

/// <summary>
/// Marks the alteration as failed.
Expand All @@ -126,4 +152,4 @@ public void Fail(string? message = null)
HasFailed = true;
Log($"Alteration {Alteration.GetType().Name} failed", message ?? $"{Alteration.GetType().Name} failed", LogLevel.Error);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
using Elsa.Alterations.Core.Contracts;
using Elsa.Mediator.Contracts;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Alterations.Services;

/// <summary>
/// Dispatches an alteration job for execution using an in-memory channel.
/// </summary>
public class BackgroundAlterationJobDispatcher : IAlterationJobDispatcher
public class BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IServiceScopeFactory scopeFactory) : IAlterationJobDispatcher
{
private readonly IJobQueue _jobQueue;
private readonly IAlterationJobRunner _alterationJobRunner;

/// <summary>
/// Initializes a new instance of the <see cref="BackgroundAlterationJobDispatcher"/> class.
/// </summary>
public BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IAlterationJobRunner alterationJobRunner)
{
_jobQueue = jobQueue;
_alterationJobRunner = alterationJobRunner;
}

/// <inheritdoc />
public ValueTask DispatchAsync(string jobId, CancellationToken cancellationToken = default)
{
_jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct));
jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct));
return default;
}

private async Task ExecuteJobAsync(string alterationJobId, CancellationToken cancellationToken)
{
await _alterationJobRunner.RunAsync(alterationJobId, cancellationToken);
using var scope = scopeFactory.CreateScope();
var alterationJobRunner = scope.ServiceProvider.GetRequiredService<IAlterationJobRunner>();
await alterationJobRunner.RunAsync(alterationJobId, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,14 @@ private async void OnChanged(object sender, FileSystemEventArgs e)
if (task == null)
return;

await task;
try
{
await task;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing change for path {Path}", e.FullPath);
}
}

private async void OnDeleted(object sender, FileSystemEventArgs e)
Expand All @@ -77,7 +84,14 @@ private async void OnDeleted(object sender, FileSystemEventArgs e)
if (task == null)
return;

await task;
try
{
await task;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing delete for path {Path}", e.FullPath);
}
}

private async Task LoadDropInAssemblyAsync(string fullPath)
Expand Down Expand Up @@ -140,4 +154,4 @@ public override void Dispose()

base.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ private bool ShouldSerializePayload(WorkflowExecutionLogRecord source)
}

private static IQueryable<WorkflowExecutionLogRecord> Filter(IQueryable<WorkflowExecutionLogRecord> queryable, WorkflowExecutionLogRecordFilter filter) => filter.Apply(queryable);
}
}
}
Loading
Loading