Skip to content

Commit

Permalink
aspire-ize semantic workbench
Browse files Browse the repository at this point in the history
using localhost globally

working example with aspire and uvicorn integration

dynamic python port

python assistant port defined via env var

python assistant read workbench service url from env

reverting port changes for python services

using correct env var for python assistant and dockerfile for FE

use custom env var for workbench service endpoint in app

correct injection of workbench servic endoint

ensure correct env var are used in dotnet example 3

deploy via azd using workaround for dockerfile

using correct env var for workbench_service url in python assistants

working deployment with azd

don't pass port during frontend docker build - not needed

working deployment with hardcoded appids in wk service

update doc

app registration id is now picked from .env in docker

working deployment of dotnet example

working assistant deployment

reorders apphost

reorders packages

cleans up

adds doc

uses new start assistant

imrpove doc

updates doc

fixes assistant container params

differentiate aspire dockerfile from already existing ones

cleans workbench app dockerfile

Minor code style changes

back to 127.0.0.1 on workbench config

fixes workbench app docker-entrypoint logs

revert space on start

removed next-steps.md

removes sshd from Dockerfile workbench service and assistant

reverts .env.example

reverts settings.py

improve readme

improves readme requirements

fixes folder for apphost in doc

Update aspire-orchestrator/SemanticWorkbench.Aspire.Hosting.Extensions/PathNormalizer.cs

Co-authored-by: Devis Lucato <dluc@users.noreply.github.com>

Update aspire-orchestrator/SemanticWorkbench.Aspire.Hosting.Extensions/PathNormalizer.cs

Co-authored-by: Devis Lucato <dluc@users.noreply.github.com>

using arg value for vite_semanticworkbenchurl

moves dockerfileextensions to separate class

aspire overwrites correct var env for agents

adds copyright

Revert "using arg value for vite_semanticworkbenchurl"

This reverts commit b0d15ae.

deletes aspire-manifest

uses workaround for workbench service url in frontend

dockerignore .env files

dockerignore .azure folder

adds comment on agent autoreference

adds doc for dotnet dev certs

use env var for entra ID config

read entraid config from appsettings

adds default values for app registration

Code style

Update workbench-service/.env.example

Add comment to new docker file

Code style

Revert code style changes

Revert changes to agent 3 csproj

Update examples/dotnet/dotnet-03-simple-chatbot/Program.cs

Update examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj

Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py

Update workbench-app/src/Constants.ts

Update workbench-app/src/global.d.ts

Fix SLN filename

Fix project names

Revert code style changes

Revert changes to agent 3 csproj

Update examples/dotnet/dotnet-03-simple-chatbot/Program.cs

Update examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj

Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py

Update workbench-app/src/Constants.ts

Update workbench-app/src/global.d.ts

Fix SLN filename

Fix project names

Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py

Update workbench-app/src/Constants.ts

Update workbench-app/src/global.d.ts

Fix SLN filename

Fix project names

Reorg projects

Fix URLs and ping errors

Handle ping exceptions

Improve logging

Change ping frequency

Auto detect port in use and better handle some exceptions

Rebasing

Update workbench-service/.env.example

Update workbench-app/.env.example

Fix merge

Fix merge

Fix merge
  • Loading branch information
tommasodotNET authored and dluc committed Nov 26, 2024
1 parent 03a58b3 commit a87e1c0
Show file tree
Hide file tree
Showing 30 changed files with 1,566 additions and 1 deletion.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
**/__pycache__
**/.pytest_cache
**/.venv
.azure
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.azure
407 changes: 407 additions & 0 deletions aspire-orchestrator/.editorconfig

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions aspire-orchestrator/Aspire.AppHost/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.azure
28 changes: 28 additions & 0 deletions aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>b43d9a6d-f7bd-491d-b1f9-82372d537e4e</UserSecretsId>
<RootNamespace>SemanticWorkbench.Aspire.AppHost</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Python" Version="9.0.0" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.NodeJS.Extensions" Version="9.0.1-beta.77" />
</ItemGroup>

<ItemGroup>
<ProjectReference
Include="..\..\examples\dotnet\dotnet-03-simple-chatbot\dotnet-03-simple-chatbot.csproj" />
<ProjectReference Include="..\Aspire.Extensions\Aspire.Extensions.csproj" IsAspireProjectResource="false" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions aspire-orchestrator/Aspire.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.

var builder = DistributedApplication.CreateBuilder(args);

var authority = builder.AddParameterFromConfiguration("authority", "EntraId:Authority");
var clientId = builder.AddParameterFromConfiguration("clientId", "EntraId:ClientId");

// Workbench backend
var workbenchService = builder.AddWorkbenchService("workbenchservice", projectDirectory: Path.Combine("..", "..", "workbench-service"), clientId: clientId);
var workbenchServiceEndpoint = workbenchService.GetSemanticWorkbenchEndpoint(builder.ExecutionContext.IsPublishMode);

// Workbench frontend
var workbenchApp = builder.AddViteApp("workbenchapp", workingDirectory: Path.Combine("..", "..", "workbench-app"), packageManager: "pnpm")
.WithPnpmPackageInstallation()
.WithEnvironment(name: "VITE_SEMANTIC_WORKBENCH_SERVICE_URL", workbenchServiceEndpoint)
.WaitFor(workbenchService)
.PublishAsDockerFile([
new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_CLIENT_ID", clientId.Resource.Value),
new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_AUTHORITY", authority.Resource.Value),
new DockerBuildArg("SSHD_ENABLED", "false")
]);

// Sample Python agent
builder.AddAssistantApp("skill-assistant", projectDirectory: Path.Combine("..", "..", "assistants", "skill-assistant"), assistantModuleName: "skill-assistant")
.WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint);

// Sample .NET agent
builder.AddProject<Projects.dotnet_03_simple_chatbot>("simple-chatbot-dotnet")
.WithReference(workbenchServiceEndpoint)
.WaitFor(workbenchService)
.WithEnvironment(name: "Workbench__WorkbenchEndpoint", workbenchServiceEndpoint);

if (!builder.ExecutionContext.IsPublishMode)
{
workbenchApp.WithHttpsEndpoint(env: "PORT");
}

builder.Build().Run();
29 changes: 29 additions & 0 deletions aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17149;http://localhost:15061",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21236",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22204"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15061",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19243",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20261"
}
}
}
}
13 changes: 13 additions & 0 deletions aspire-orchestrator/Aspire.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"EntraID": {
"ClientId": "22cb77c3-ca98-4a26-b4db-ac4dcecba690",
"Authority": "https://login.microsoftonline.com/common"
}
}
17 changes: 17 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<nullable>enable</nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AdditionalPackageTags>semantic workbench extensions</AdditionalPackageTags>
<Description>An Aspire integration for Semantic Workbench.</Description>
<RootNamespace>Aspire.Hosting.Extensions</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
</ItemGroup>

</Project>
36 changes: 36 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Aspire.Hosting;

public static class DockerFileExtensions
{
public static IResourceBuilder<ExecutableResource> PublishAsDockerImage(this IResourceBuilder<ExecutableResource> builder,
string? dockerContext = null,
string? dockerFilePath = "Dockerfile",
Action<IResourceBuilder<ContainerResource>>? configure = null)
{
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
return builder;
}

// Bait and switch the ExecutableResource with a ContainerResource
builder.ApplicationBuilder.Resources.Remove(builder.Resource);

var container = new ExecutableContainerResource(builder.Resource);
var cb = builder.ApplicationBuilder.AddResource(container);
cb.WithImage(builder.Resource.Name);
cb.WithDockerfile(contextPath: dockerContext ?? builder.Resource.WorkingDirectory, dockerfilePath: dockerFilePath);
// Clear the runtime args
cb.WithArgs(c => c.Args.Clear());

configure?.Invoke(cb);

return builder;
}

private sealed class ExecutableContainerResource(ExecutableResource er) : ContainerResource(er.Name)
{
public override ResourceAnnotationCollection Annotations => er.Annotations;
}
}
24 changes: 24 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Aspire.Hosting.Extensions;

internal static class PathNormalizer
{
/// <summary>
/// Normalizes the path for the current platform.
/// </summary>
/// <param name="path">The path value.</param>
/// <returns>Returns the normalized path value for the current platform.</returns>
public static string NormalizePathForCurrentPlatform(this string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return path;
}

// Fix slashes
path = path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);

return Path.GetFullPath(path);
}
}
88 changes: 88 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.

using Aspire.Hosting.ComponentModel;
using Aspire.Hosting.Extensions;

namespace Aspire.Hosting;

public static class UvAppHostingExtensions
{
public static IResourceBuilder<UvAppResource> AddUvApp(
this IDistributedApplicationBuilder builder,
string name,
string projectDirectory,
string scriptPath,
params string[] scriptArgs)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.AddUvApp(name, scriptPath, projectDirectory, ".venv", scriptArgs);
}

private static IResourceBuilder<UvAppResource> AddUvApp(this IDistributedApplicationBuilder builder,
string name,
string scriptPath,
string projectDirectory,
string virtualEnvironmentPath,
params string[] args)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(scriptPath);

string wd = projectDirectory ?? Path.Combine("..", name);

projectDirectory = PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, wd));

var virtualEnvironment = new VirtualEnvironment(Path.IsPathRooted(virtualEnvironmentPath)
? virtualEnvironmentPath
: Path.Join(projectDirectory, virtualEnvironmentPath));

var instrumentationExecutable = virtualEnvironment.GetExecutable("opentelemetry-instrument");
// var pythonExecutable = virtualEnvironment.GetRequiredExecutable("python");
// var projectExecutable = instrumentationExecutable ?? pythonExecutable;

string[] allArgs = args is { Length: > 0 }
? ["run", scriptPath, .. args]
: ["run", scriptPath];

var projectResource = new UvAppResource(name, projectDirectory);

var resourceBuilder = builder.AddResource(projectResource)
.WithArgs(allArgs)
.WithArgs(context =>
{
// If the project is to be automatically instrumented, add the instrumentation executable arguments first.
if (!string.IsNullOrEmpty(instrumentationExecutable))
{
AddOpenTelemetryArguments(context);
// // Add the python executable as the next argument so we can run the project.
// context.Args.Add(pythonExecutable!);
}
});

if (!string.IsNullOrEmpty(instrumentationExecutable))
{
resourceBuilder.WithOtlpExporter();

// Make sure to attach the logging instrumentation setting, so we can capture logs.
// Without this you'll need to configure logging yourself. Which is kind of a pain.
resourceBuilder.WithEnvironment("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED", "true");
}

return resourceBuilder;
}

private static void AddOpenTelemetryArguments(CommandLineArgsCallbackContext context)
{
context.Args.Add("--traces_exporter");
context.Args.Add("otlp");

context.Args.Add("--logs_exporter");
context.Args.Add("console,otlp");

context.Args.Add("--metrics_exporter");
context.Args.Add("otlp");
}
}
9 changes: 9 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/UvAppResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Aspire.Hosting.ComponentModel;

public class UvAppResource(string name, string workingDirectory)
: ExecutableResource(name, "uv", workingDirectory), IResourceWithServiceDiscovery
{
internal const string HttpEndpointName = "http";
}
39 changes: 39 additions & 0 deletions aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Aspire.Hosting.ComponentModel;

internal sealed class VirtualEnvironment(string virtualEnvironmentPath)
{
/// <summary>
/// Locates an executable in the virtual environment.
/// </summary>
/// <param name="name">The name of the executable.</param>
/// <returns>Returns the path to the executable if it exists in the virtual environment.</returns>
public string? GetExecutable(string name)
{
if (OperatingSystem.IsWindows())
{
string[] allowedExtensions = [".exe", ".cmd", ".bat"];

return allowedExtensions
.Select(allowedExtension => Path.Join(virtualEnvironmentPath, "Scripts", name + allowedExtension))
.FirstOrDefault(File.Exists);
}

var executablePath = Path.Join(virtualEnvironmentPath, "bin", name);
return File.Exists(executablePath) ? executablePath : null;
}

/// <summary>
/// Locates a required executable in the virtual environment.
/// </summary>
/// <param name="name">The name of the executable.</param>
/// <returns>The path to the executable in the virtual environment.</returns>
/// <exception cref="DistributedApplicationException">Gets thrown when the executable couldn't be located.</exception>
public string GetRequiredExecutable(string name)
{
return this.GetExecutable(name) ?? throw new DistributedApplicationException(
$"The executable {name} could not be found in the virtual environment at '{virtualEnvironmentPath}' . " +
"Make sure the virtual environment is initialized and the executable is installed.");
}
}
Loading

0 comments on commit a87e1c0

Please sign in to comment.