Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions BotSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.ChartHandle
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.ExcelHandler", "src\Plugins\BotSharp.Plugin.ExcelHandler\BotSharp.Plugin.ExcelHandler.csproj", "{FC63C875-E880-D8BB-B8B5-978AB7B62983}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.GiteeAI", "src\Plugins\BotSharp.Plugin.GiteeAI\BotSharp.Plugin.GiteeAI.csproj", "{50B57066-3267-1D10-0F72-D2F5CC494F2C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.ImageHandler", "src\Plugins\BotSharp.Plugin.ImageHandler\BotSharp.Plugin.ImageHandler.csproj", "{242F2D93-FCCE-4982-8075-F3052ECCA92C}"
EndProject
Global
Expand Down Expand Up @@ -621,6 +623,14 @@ Global
{FC63C875-E880-D8BB-B8B5-978AB7B62983}.Release|Any CPU.Build.0 = Release|Any CPU
{FC63C875-E880-D8BB-B8B5-978AB7B62983}.Release|x64.ActiveCfg = Release|Any CPU
{FC63C875-E880-D8BB-B8B5-978AB7B62983}.Release|x64.Build.0 = Release|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Debug|x64.ActiveCfg = Debug|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Debug|x64.Build.0 = Debug|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Release|Any CPU.Build.0 = Release|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Release|x64.ActiveCfg = Release|Any CPU
{50B57066-3267-1D10-0F72-D2F5CC494F2C}.Release|x64.Build.0 = Release|Any CPU
{242F2D93-FCCE-4982-8075-F3052ECCA92C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{242F2D93-FCCE-4982-8075-F3052ECCA92C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{242F2D93-FCCE-4982-8075-F3052ECCA92C}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -700,6 +710,7 @@ Global
{B067B126-88CD-4282-BEEF-7369B64423EF} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
{0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{FC63C875-E880-D8BB-B8B5-978AB7B62983} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{50B57066-3267-1D10-0F72-D2F5CC494F2C} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{242F2D93-FCCE-4982-8075-F3052ECCA92C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
4 changes: 2 additions & 2 deletions src/BotSharp.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

var apiService = builder.AddProject<Projects.WebStarter>("apiservice")
.WithExternalHttpEndpoints();
var mcpService = builder.AddProject<Projects.BotSharp_PizzaBot_MCPServer>("mcpservice")
.WithExternalHttpEndpoints();
//var mcpService = builder.AddProject<Projects.BotSharp_PizzaBot_MCPServer>("mcpservice")
// .WithExternalHttpEndpoints();

builder.AddNpmApp("BotSharpUI", "../../../BotSharp-UI")
.WithReference(apiService)
Expand Down
52 changes: 49 additions & 3 deletions src/BotSharp.ServiceDefaults/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using BotSharp.Langfuse;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ServiceDiscovery;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog;

Expand Down Expand Up @@ -45,6 +49,10 @@

public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
// Enable model diagnostics with sensitive data.
AppContext.SetSwitch("BotSharp.Experimental.GenAI.EnableOTelDiagnostics", true);
AppContext.SetSwitch("BotSharp.Experimental.GenAI.EnableOTelDiagnosticsSensitive", true);

builder.Logging.AddOpenTelemetry(logging =>
{ // Use Serilog
Log.Logger = new LoggerConfiguration()
Expand Down Expand Up @@ -87,10 +95,28 @@
})
.WithTracing(tracing =>
{
tracing.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("apiservice", serviceVersion: "1.0.0")
)
.AddSource("BotSharp")
.AddSource("BotSharp.Abstraction.Diagnostics")
.AddSource("BotSharp.Core.Routing.Executor");

tracing.AddAspNetCoreInstrumentation()
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
.AddHttpClientInstrumentation()
//.AddOtlpExporter(options =>
//{
// //options.Endpoint = new Uri(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"] ?? "http://localhost:4317");
// options.Endpoint = new Uri(host);
// options.Protocol = OtlpExportProtocol.HttpProtobuf;
// options.Headers = $"Authorization=Basic {base64EncodedAuth}";
//})
;


});

builder.AddOpenTelemetryExporters();
Expand All @@ -100,14 +126,34 @@

private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var langfuseSection = builder.Configuration.GetSection("Langfuse");
var useLangfuse = langfuseSection != null;
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);

if (useOtlpExporter)
{
builder.Services.Configure<OpenTelemetryLoggerOptions>(logging => logging.AddOtlpExporter());
builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter());
builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());

if (useLangfuse)
{
var publicKey = langfuseSection.GetValue<string>(nameof(LangfuseSettings.PublicKey)) ?? string.Empty;

Check warning on line 139 in src/BotSharp.ServiceDefaults/Extensions.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Possible null reference argument for parameter 'configuration' in 'string? ConfigurationBinder.GetValue<string>(IConfiguration configuration, string key)'.

Check warning on line 139 in src/BotSharp.ServiceDefaults/Extensions.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Possible null reference argument for parameter 'configuration' in 'string? ConfigurationBinder.GetValue<string>(IConfiguration configuration, string key)'.

Check warning on line 139 in src/BotSharp.ServiceDefaults/Extensions.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Possible null reference argument for parameter 'configuration' in 'string? ConfigurationBinder.GetValue<string>(IConfiguration configuration, string key)'.
var secretKey = langfuseSection.GetValue<string>(nameof(LangfuseSettings.SecretKey)) ?? string.Empty;
var host = langfuseSection.GetValue<string>(nameof(LangfuseSettings.Host)) ?? string.Empty;
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}");
string base64EncodedAuth = Convert.ToBase64String(plainTextBytes);

builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter(options =>
{
options.Endpoint = new Uri(host);
options.Protocol = OtlpExportProtocol.HttpProtobuf;
options.Headers = $"Authorization=Basic {base64EncodedAuth}";
})
);
}
else
{
builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());
}
}

// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
Expand Down
19 changes: 19 additions & 0 deletions src/BotSharp.ServiceDefaults/LangfuseSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BotSharp.Langfuse;

/// <summary>
/// Langfuse Settings
/// </summary>
public class LangfuseSettings
{
public string SecretKey { get; set; }

Check warning on line 14 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Non-nullable property 'SecretKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 14 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Non-nullable property 'SecretKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 14 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Non-nullable property 'SecretKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public string PublicKey { get; set; }

Check warning on line 16 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Non-nullable property 'PublicKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 16 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Non-nullable property 'PublicKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 16 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Non-nullable property 'PublicKey' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public string Host { get; set; }

Check warning on line 18 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Non-nullable property 'Host' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 18 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Non-nullable property 'Host' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 18 in src/BotSharp.ServiceDefaults/LangfuseSettings.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Non-nullable property 'Host' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace BotSharp.Abstraction.Diagnostics;

[ExcludeFromCodeCoverage]
public static class ActivityExtensions
{
/// <summary>
/// Starts an activity with the appropriate tags for a kernel function execution.
/// </summary>
public static Activity? StartFunctionActivity(this ActivitySource source, string functionName, string functionDescription)
{
const string OperationName = "execute_tool";

return source.StartActivityWithTags($"{OperationName} {functionName}", [
new KeyValuePair<string, object?>("gen_ai.operation.name", OperationName),
new KeyValuePair<string, object?>("gen_ai.tool.name", functionName),
new KeyValuePair<string, object?>("gen_ai.tool.description", functionDescription)
], ActivityKind.Internal);
}

/// <summary>
/// Starts an activity with the specified name and tags.
/// </summary>
public static Activity? StartActivityWithTags(this ActivitySource source, string name, IEnumerable<KeyValuePair<string, object?>> tags, ActivityKind kind = ActivityKind.Internal)
=> source.StartActivity(name, kind, default(ActivityContext), tags);

/// <summary>
/// Adds tags to the activity.
/// </summary>
public static Activity SetTags(this Activity activity, ReadOnlySpan<KeyValuePair<string, object?>> tags)
{
foreach (var tag in tags)
{
activity.SetTag(tag.Key, tag.Value);
}
;

return activity;
}

/// <summary>
/// Adds an event to the activity. Should only be used for events that contain sensitive data.
/// </summary>
public static Activity AttachSensitiveDataAsEvent(this Activity activity, string name, IEnumerable<KeyValuePair<string, object?>> tags)
{
activity.AddEvent(new ActivityEvent(
name,
tags: [.. tags]
));

return activity;
}

/// <summary>
/// Sets the error status and type on the activity.
/// </summary>
public static Activity SetError(this Activity activity, Exception exception)
{
activity.SetTag("error.type", exception.GetType().FullName);
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
return activity;
}

public static async IAsyncEnumerable<TResult> RunWithActivityAsync<TResult>(
Func<Activity?> getActivity,
Func<IAsyncEnumerable<TResult>> operation,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
using var activity = getActivity();

ConfiguredCancelableAsyncEnumerable<TResult> result;

try
{
result = operation().WithCancellation(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (activity is not null)
{
activity.SetError(ex);
throw;
}

var resultEnumerator = result.ConfigureAwait(false).GetAsyncEnumerator();

try
{
while (true)
{
try
{
if (!await resultEnumerator.MoveNextAsync())
{
break;
}
}
catch (Exception ex) when (activity is not null)
{
activity.SetError(ex);
throw;
}

yield return resultEnumerator.Current;
}
}
finally
{
await resultEnumerator.DisposeAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace BotSharp.Abstraction.Diagnostics;

/// <summary>
/// Helper class to get app context switch value
/// </summary>
[ExcludeFromCodeCoverage]
internal static class AppContextSwitchHelper
{
/// <summary>
/// Returns the value of the specified app switch or environment variable if it is set.
/// If the switch or environment variable is not set, return false.
/// The app switch value takes precedence over the environment variable.
/// </summary>
/// <param name="appContextSwitchName">The name of the app switch.</param>
/// <param name="envVarName">The name of the environment variable.</param>
/// <returns>The value of the app switch or environment variable if it is set; otherwise, false.</returns>
public static bool GetConfigValue(string appContextSwitchName, string envVarName)
{
if (AppContext.TryGetSwitch(appContextSwitchName, out bool value))
{
return value;
}

string? envVarValue = Environment.GetEnvironmentVariable(envVarName);
if (envVarValue != null && bool.TryParse(envVarValue, out value))
{
return value;
}

return false;
}
}
Loading
Loading