Skip to content

Commit

Permalink
Add agent management features
Browse files Browse the repository at this point in the history
Introduce agent management components, including pages for listing and handling agent workflows, dialogs for creating agents with Blazor components, and backend API endpoints for generating unique agent names. This enhances functionality for managing agent workflows within the application.
  • Loading branch information
sfmskywalker committed Aug 19, 2024
1 parent 81cbe2f commit a7f46ae
Show file tree
Hide file tree
Showing 30 changed files with 296 additions and 74 deletions.
22 changes: 22 additions & 0 deletions Elsa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations",
scripts\migrations\efcore-3.3.sh = scripts\migrations\efcore-3.3.sh
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3}"
ProjectSection(SolutionItems) = preProject
scripts\docker\build-and-run-all-in-one-web-docker.sh = scripts\docker\build-and-run-all-in-one-web-docker.sh
scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml
scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468}"
ProjectSection(SolutionItems) = preProject
scripts\k8s\elsa-studio\deployment.yaml = scripts\k8s\elsa-studio\deployment.yaml
scripts\k8s\elsa-studio\service.yaml = scripts\k8s\elsa-studio\service.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Agents.Models", "src\modules\Elsa.Agents.Models\Elsa.Agents.Models.csproj", "{052C4F97-5909-4D88-AA49-4B0D9058A0A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -989,6 +1004,10 @@ Global
{E75762D2-FD12-4165-810B-628C28CB2562}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.Build.0 = Release|Any CPU
{052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1162,6 +1181,9 @@ Global
{CFEC357F-0863-4458-A6C4-5D7AA0E44759} = {50470834-4CD8-479A-8B58-0A1869BA5D37}
{E75762D2-FD12-4165-810B-628C28CB2562} = {50470834-4CD8-479A-8B58-0A1869BA5D37}
{3666CF13-1353-4620-9472-506670214A6A} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E}
{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E}
{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468} = {B32DB9B2-AD6C-48A5-8682-4373CB045185}
{052C4F97-5909-4D88-AA49-4B0D9058A0A9} = {50470834-4CD8-479A-8B58-0A1869BA5D37}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
Expand Down
2 changes: 1 addition & 1 deletion scripts/docker/build-and-run-all-in-one-web-docker.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
docker build -t elsa-server:latest -f ./docker/ElsaServer.Dockerfile .
docker build -t elsa-server:latest -f ../../docker/ElsaServer.Dockerfile .
docker run -t -i -e ASPNETCORE_ENVIRONMENT='Development' -e HTTP_PORTS=8080 -p 13000:8080 elsa-server:local
2 changes: 1 addition & 1 deletion scripts/k8s/elsa-server/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kind: Deployment
metadata:
name: elsa-server-deployment
spec:
replicas: 3
replicas: 1
selector:
matchLabels:
app: elsa-server
Expand Down
25 changes: 25 additions & 0 deletions scripts/k8s/elsa-studio/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: elsa-studio-deployment
spec:
replicas: 1
selector:
matchLabels:
app: elsa-studio
template:
metadata:
labels:
app: elsa-studio
spec:
containers:
- name: elsa-studio
imagePullPolicy: Never
image: elsa-studio:latest
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: Development
- name: "ELSASERVER__URL"
value: "http://localhost:8001/elsa/api"
12 changes: 12 additions & 0 deletions scripts/k8s/elsa-studio/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: elsa-studio-service
spec:
type: LoadBalancer
sessionAffinity: None
ports:
- port: 9001
targetPort: 8080
selector:
app: elsa-studio
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
using Elsa.Agents.Persistence.Contracts;
using Elsa.Agents.Persistence.Entities;
using Elsa.Agents.Persistence.Filters;
using Elsa.Extensions;
using Elsa.Workflows.Contracts;
using JetBrains.Annotations;

namespace Elsa.Agents.Api.Endpoints.Agents.Create;

/// Lists all registered API keys.
[UsedImplicitly]
public class Endpoint(IAgentStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint<AgentDto, AgentDefinition>
public class Endpoint(IAgentStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint<AgentInputModel, AgentModel>
{
/// <inheritdoc />
public override void Configure()
Expand All @@ -19,7 +20,7 @@ public override void Configure()
}

/// <inheritdoc />
public override async Task<AgentDefinition> ExecuteAsync(AgentDto req, CancellationToken ct)
public override async Task<AgentModel> ExecuteAsync(AgentInputModel req, CancellationToken ct)
{
var isNameUnique = await IsNameUniqueAsync(req.Name, ct);

Expand Down Expand Up @@ -51,7 +52,7 @@ public override async Task<AgentDefinition> ExecuteAsync(AgentDto req, Cancellat
};

await store.AddAsync(newEntity, ct);
return newEntity;
return newEntity.ToModel();
}

private async Task<bool> IsNameUniqueAsync(string name, CancellationToken ct)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Elsa.Abstractions;
using Elsa.Agents.Persistence.Contracts;
using Elsa.Agents.Persistence.Filters;
using JetBrains.Annotations;

namespace Elsa.Agents.Api.Endpoints.Agents.GenerateUniqueName;

/// Lists all registered API keys.
[UsedImplicitly]
public class Endpoint(IAgentStore store) : ElsaEndpointWithoutRequest<GenerateUniqueNameResponse>
{
/// <inheritdoc />
public override void Configure()
{
Post("/ai/actions/agents/generate-unique-name");
ConfigurePermissions("ai/agents:write");
}

/// <inheritdoc />
public override async Task<GenerateUniqueNameResponse> ExecuteAsync(CancellationToken ct)
{
var newName = await GenerateUniqueNameAsync(ct);
return new(newName);
}

private async Task<string> GenerateUniqueNameAsync(CancellationToken cancellationToken)
{
const int maxAttempts = 100;
var attempt = 0;

while (attempt < maxAttempts)
{
var name = $"Agent {++attempt}";
var isUnique = await IsNameUniqueAsync(name, cancellationToken);

if (isUnique)
return name;
}

throw new Exception($"Failed to generate a unique workflow name after {maxAttempts} attempts.");
}

private async Task<bool> IsNameUniqueAsync(string name, CancellationToken ct)
{
var filter = new AgentDefinitionFilter
{
Name = name
};
return await store.FindAsync(filter, ct) == null;
}
}
7 changes: 4 additions & 3 deletions src/modules/Elsa.Agents.Api/Endpoints/Agents/Get/Endpoint.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Elsa.Abstractions;
using Elsa.Agents.Persistence.Contracts;
using Elsa.Agents.Persistence.Entities;
using Elsa.Extensions;
using JetBrains.Annotations;

namespace Elsa.Agents.Api.Endpoints.Agents.Get;

/// Gets an agent.
[UsedImplicitly]
public class Endpoint(IAgentStore store) : ElsaEndpoint<Request, AgentDefinition>
public class Endpoint(IAgentStore store) : ElsaEndpoint<Request, AgentModel>
{
/// <inheritdoc />
public override void Configure()
Expand All @@ -17,7 +18,7 @@ public override void Configure()
}

/// <inheritdoc />
public override async Task<AgentDefinition> ExecuteAsync(Request req, CancellationToken ct)
public override async Task<AgentModel> ExecuteAsync(Request req, CancellationToken ct)
{
var entity = await store.GetAsync(req.Id, ct);

Expand All @@ -27,6 +28,6 @@ public override async Task<AgentDefinition> ExecuteAsync(Request req, Cancellati
return null!;
}

return entity;
return entity.ToModel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Elsa.Abstractions;
using Elsa.Agents.Persistence.Contracts;
using Elsa.Agents.Persistence.Filters;
using JetBrains.Annotations;

namespace Elsa.Agents.Api.Endpoints.Agents.IsUniqueName;

/// Checks if a name is unique.
[UsedImplicitly]
public class Endpoint(IAgentStore store) : ElsaEndpoint<IsUniqueNameRequest, IsUniqueNameResponse>
{
/// <inheritdoc />
public override void Configure()
{
Post("/ai/queries/agents/is-unique-name");
ConfigurePermissions("ai/agents:write");
}

/// <inheritdoc />
public override async Task<IsUniqueNameResponse> ExecuteAsync(IsUniqueNameRequest req, CancellationToken ct)
{
var isUnique = await IsNameUniqueAsync(req.Name, req.Id, ct);
return new(isUnique);
}

private async Task<bool> IsNameUniqueAsync(string name, string? notId, CancellationToken ct)
{
var filter = new AgentDefinitionFilter
{
Name = name,
NotId = notId,
};
return await store.FindAsync(filter, ct) == null;
}
}
8 changes: 5 additions & 3 deletions src/modules/Elsa.Agents.Api/Endpoints/Agents/List/Endpoint.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Elsa.Abstractions;
using Elsa.Agents.Persistence.Contracts;
using Elsa.Agents.Persistence.Entities;
using Elsa.Extensions;
using Elsa.Models;
using JetBrains.Annotations;

namespace Elsa.Agents.Api.Endpoints.Agents.List;

/// Lists all agents.
[UsedImplicitly]
public class Endpoint(IAgentStore store) : ElsaEndpointWithoutRequest<ListResponse<AgentDefinition>>
public class Endpoint(IAgentStore store) : ElsaEndpointWithoutRequest<ListResponse<AgentModel>>
{
/// <inheritdoc />
public override void Configure()
Expand All @@ -18,9 +19,10 @@ public override void Configure()
}

/// <inheritdoc />
public override async Task<ListResponse<AgentDefinition>> ExecuteAsync(CancellationToken ct)
public override async Task<ListResponse<AgentModel>> ExecuteAsync(CancellationToken ct)
{
var entities = await store.ListAsync(ct);
return new ListResponse<AgentDefinition>(entities.ToList());
var models = entities.Select(x => x.ToModel()).ToList();
return new ListResponse<AgentModel>(models);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Elsa.Agents.Api.Endpoints.Agents.Update;

/// Updates an agent.
[UsedImplicitly]
public class Endpoint(IAgentStore store) : ElsaEndpoint<AgentDto, AgentDefinition>
public class Endpoint(IAgentStore store) : ElsaEndpoint<AgentInputModel, AgentDefinition>
{
/// <inheritdoc />
public override void Configure()
Expand All @@ -18,7 +18,7 @@ public override void Configure()
}

/// <inheritdoc />
public override async Task<AgentDefinition> ExecuteAsync(AgentDto req, CancellationToken ct)
public override async Task<AgentDefinition> ExecuteAsync(AgentInputModel req, CancellationToken ct)
{
var id = Route<string>("id")!;
var entity = await store.GetAsync(id, ct);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Elsa.Agents;
using Elsa.Agents.Persistence.Entities;

// ReSharper disable once CheckNamespace
namespace Elsa.Extensions;

public static class AgentDefinitionExtensions
{
public static AgentModel ToModel(this AgentDefinition agentDefinition)
{
return new AgentModel
{
Id = agentDefinition.Id,
Name = agentDefinition.Name,
Description = agentDefinition.Description,
Agents = agentDefinition.AgentConfig.Agents,
ExecutionSettings = agentDefinition.AgentConfig.ExecutionSettings,
InputVariables = agentDefinition.AgentConfig.InputVariables,
OutputVariable = agentDefinition.AgentConfig.OutputVariable,
Services = agentDefinition.AgentConfig.Services,
Plugins = agentDefinition.AgentConfig.Plugins,
FunctionName = agentDefinition.AgentConfig.FunctionName,
PromptTemplate = agentDefinition.AgentConfig.PromptTemplate
};
}
}
1 change: 1 addition & 0 deletions src/modules/Elsa.Agents.Core/Elsa.Agents.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Elsa.Agents.Models\Elsa.Agents.Models.csproj" />
<ProjectReference Include="..\Elsa.Common\Elsa.Common.csproj" />
</ItemGroup>

Expand Down
49 changes: 49 additions & 0 deletions src/modules/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;

#pragma warning disable SKEXP0001
#pragma warning disable SKEXP0010

namespace Elsa.Agents;

public static class AgentConfigExtensions
{
public static OpenAIPromptExecutionSettings ToOpenAIPromptExecutionSettings(this AgentConfig agentConfig)
{
return new OpenAIPromptExecutionSettings
{
Temperature = agentConfig.ExecutionSettings.Temperature,
TopP = agentConfig.ExecutionSettings.TopP,
MaxTokens = agentConfig.ExecutionSettings.MaxTokens,
PresencePenalty = agentConfig.ExecutionSettings.PresencePenalty,
FrequencyPenalty = agentConfig.ExecutionSettings.FrequencyPenalty,
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
ResponseFormat = agentConfig.ExecutionSettings.ResponseFormat,
ChatSystemPrompt = agentConfig.PromptTemplate,
};
}

public static PromptTemplateConfig ToPromptTemplateConfig(this AgentConfig agentConfig)
{
var promptExecutionSettingsDictionary = new Dictionary<string, PromptExecutionSettings>
{
[PromptExecutionSettings.DefaultServiceId] = agentConfig.ToOpenAIPromptExecutionSettings(),
};

return new PromptTemplateConfig
{
Name = agentConfig.FunctionName,
Description = agentConfig.Description,
Template = agentConfig.PromptTemplate,
ExecutionSettings = promptExecutionSettingsDictionary,
AllowDangerouslySetContent = true,
InputVariables = agentConfig.InputVariables.Select(x => new InputVariable
{
Name = x.Name,
Description = x.Description,
IsRequired = true,
AllowDangerouslySetContent = true
}).ToList()
};
}
}
Loading

0 comments on commit a7f46ae

Please sign in to comment.