Skip to content

Conversation

@geffzhang
Copy link
Collaborator

@geffzhang geffzhang commented Dec 27, 2025

User description

This pull request introduces support for enhanced telemetry and diagnostics, adds new plugins and agent types, and improves configuration flexibility for OpenTelemetry exporters. The most significant changes include adding Langfuse integration for OpenTelemetry tracing, introducing new plugins (GiteeAI and A2A), and providing utility extensions for diagnostics and activity tracking.

Telemetry & Diagnostics Enhancements:

  • Added Langfuse integration for OpenTelemetry tracing, enabling secure export of telemetry data with configurable authentication and endpoint via LangfuseSettings. This includes conditional configuration based on app settings and support for custom OTLP exporters. (src/BotSharp.ServiceDefaults/Extensions.cs, src/BotSharp.ServiceDefaults/LangfuseSettings.cs) [1] [2] [3] [4] [5]
  • Introduced new diagnostic helper classes: ActivityExtensions for tagging and error handling in activities, and AppContextSwitchHelper for reading configuration switches and environment variables. (src/Infrastructure/BotSharp.Abstraction/Diagnostics/ActivityExtensions.cs, src/Infrastructure/BotSharp.Abstraction/Diagnostics/AppContextSwitchHelper.cs) [1] [2]
  • Enabled model diagnostics with sensitive data via AppContext switches for GenAI features. (src/BotSharp.ServiceDefaults/Extensions.cs)

Plugin & Agent Type Additions:

  • Added BotSharp.Plugin.GiteeAI and BotSharp.Plugin.A2A projects to the solution, with associated build and project configuration. (BotSharp.sln, Directory.Packages.props) [1] [2] [3] [4] [5] [6]
  • Introduced a new agent type constant A2ARemote for Microsoft Agent Framework integration. (src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs)

Other Updates:

  • Commented out the initialization of the mcpservice project in the application host, possibly for deployment or testing purposes. (src/BotSharp.AppHost/Program.cs)

PR Type

Enhancement, Tests


Description

  • Adds A2A (Agent-to-Agent) protocol integration for remote agent communication

  • Implements OpenTelemetry diagnostics with Langfuse tracing support

  • Introduces GiteeAI plugin for chat and embedding providers

  • Adds diagnostic helpers for activity tracking and model telemetry

  • Integrates tracing in routing, function execution, and chat completion flows


Diagram Walkthrough

flowchart LR
  A["A2A Remote Agent"] -->|"Agent-to-Agent Protocol"| B["A2AService"]
  B -->|"Delegate Task"| C["A2ADelegationFn"]
  D["OpenTelemetry"] -->|"Langfuse Integration"| E["ModelDiagnostics"]
  E -->|"Activity Tracing"| F["Chat/Function Execution"]
  G["GiteeAI Plugin"] -->|"Chat & Embedding"| H["LLM Provider"]
  I["ActivityExtensions"] -->|"Tag & Error Handling"| F
Loading

File Walkthrough

Relevant files
Enhancement
22 files
A2APlugin.cs
A2A plugin registration and dependency injection                 
+37/-0   
A2ADelegationFn.cs
Function callback for A2A agent delegation                             
+70/-0   
A2AAgentHook.cs
Agent hook for A2A remote agent loading                                   
+90/-0   
A2AConversationHook.cs
Conversation hook for A2A integration                                       
+13/-0   
A2AService.cs
A2A service for remote agent communication                             
+92/-0   
IA2AService.cs
Interface for A2A service operations                                         
+14/-0   
Extensions.cs
OpenTelemetry and Langfuse integration setup                         
+49/-3   
ActivityExtensions.cs
Extension methods for activity tagging and error handling
+119/-0 
AppContextSwitchHelper.cs
Helper for reading app context switches and environment variables
+35/-0   
ModelDiagnostics.cs
Model diagnostics with OTel semantic conventions                 
+394/-0 
AgentType.cs
Add A2ARemote agent type constant                                               
+5/-0     
FunctionCallbackExecutor.cs
Add activity tracing to function execution                             
+16/-2   
MCPToolExecutor.cs
Add activity tracing to MCP tool execution                             
+38/-25 
RoutingService.InvokeAgent.cs
Add agent invocation activity tracing                                       
+4/-1     
RoutingService.InvokeFunction.cs
Import diagnostics for function invocation                             
+1/-0     
RoutingService.cs
Include A2ARemote agents in routable agents                           
+2/-1     
GiteeAiPlugin.cs
GiteeAI plugin registration and DI setup                                 
+19/-0   
ChatCompletionProvider.cs
GiteeAI chat completion provider with tracing                       
+496/-0 
TextEmbeddingProvider.cs
GiteeAI text embedding provider implementation                     
+73/-0   
ProviderHelper.cs
Helper for GiteeAI client initialization                                 
+16/-0   
ChatCompletionProvider.cs
Add model diagnostics tracing to Azure OpenAI                       
+78/-66 
ChatCompletionProvider.cs
Add model diagnostics tracing to OpenAI                                   
+66/-60 
Configuration changes
9 files
A2ASettings.cs
Configuration settings for A2A integration                             
+23/-0   
BotSharp.Plugin.A2A.csproj
Project file for A2A plugin                                                           
+21/-0   
LangfuseSettings.cs
Configuration class for Langfuse settings                               
+19/-0   
Using.cs
Global using statements for GiteeAI plugin                             
+15/-0   
BotSharp.Plugin.GiteeAI.csproj
Project file for GiteeAI plugin                                                   
+31/-0   
WebStarter.csproj
Add A2A and GiteeAI plugin project references                       
+2/-0     
appsettings.json
Add Langfuse and A2A integration configuration                     
+58/-2   
BotSharp.sln
Add A2A and GiteeAI plugin projects to solution                   
+22/-0   
Program.cs
Comment out MCP service in app host                                           
+2/-2     
Documentation
1 files
README.md
Documentation for GiteeAI plugin                                                 
+8/-0     
Formatting
2 files
ConversationController.cs
Minor formatting cleanup in conversation controller           
+1/-1     
Program.cs
Reorder using statements for consistency                                 
+2/-3     
Dependencies
1 files
Directory.Packages.props
Add A2A NuGet package version management                                 
+1/-0     
Tests
3 files
PizzaBotPlugin.cs
Add SportKiosk A2A remote agent to test plugin                     
+2/-1     
BotSharp.Plugin.PizzaBot.csproj
Add SportKiosk agent data file reference                                 
+3/-0     
agent.json
SportKiosk A2A remote agent configuration                               
+14/-0   

geffzhang and others added 9 commits October 17, 2025 08:37
Introduced OpenTelemetry-based model diagnostics with Langfuse integration, including new helper classes and activity tracing for agent and function execution. Added BotSharp.Plugin.GiteeAI with chat and embedding providers, and updated solution/project files to register the new plugin. Enhanced tracing in routing, executor, and controller logic for improved observability.
This pull request introduces several updates to the solution and package management
@qodo-code-review
Copy link

qodo-code-review bot commented Dec 27, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Sensitive telemetry exposure

Description: OpenTelemetry GenAI diagnostics are force-enabled (including sensitive events) via
AppContext.SetSwitch and the code exports traces (optionally to Langfuse) while downstream
code attaches prompts/outputs as activity events/tags, creating a high risk of
unintentionally exfiltrating sensitive user prompts, model outputs, and tool/function
arguments to external telemetry backends.
Extensions.cs [52-156]

Referred Code
            // 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()
                // 对请求路径为 / heathz和 / metrics不进行日志记录
                .Filter.ByExcluding(
                    e => e.Properties.TryGetValue("RequestPath", out var value) && (value.ToString().StartsWith("\"/metrics\"") || value.ToString().StartsWith("\"/healthz\""))
                    )
#if DEBUG
                    .MinimumLevel.Information()
#else
    .MinimumLevel.Warning()
#endif
                    .WriteTo.Console()
                    .WriteTo.File("logs/log-.txt",
                        shared: true,
                        rollingInterval: RollingInterval.Day)
                    //.WriteTo.OpenTelemetry(options =>


 ... (clipped 84 lines)
SSRF via remote agent

Description: The A2A integration makes outbound HTTP requests to agentEndpoint (configured per-agent)
and forwards user-provided text/context, which can enable SSRF/data exfiltration if an
attacker can influence A2A agent configuration (e.g., targeting internal services or
metadata endpoints).
A2AService.cs [31-90]

Referred Code
public async Task<AgentCard> GetCapabilitiesAsync(string agentEndpoint)
{
    var resolver = new A2ACardResolver(new Uri(agentEndpoint));
    return await resolver.GetAgentCardAsync();
}

public async Task<string> SendMessageAsync(string agentEndpoint, string text, string contextId, CancellationToken cancellationToken)
{

    if (!_clientCache.TryGetValue(agentEndpoint, out var client))
    {
        HttpClient httpclient = new HttpClient() {  Timeout = TimeSpan.FromSeconds(100) };

        client = new A2AClient(new Uri(agentEndpoint), httpclient);
        _clientCache[agentEndpoint] = client;
    }

    var messagePayload = new AgentMessage
    {
        Role = MessageRole.User, 
        ContextId = contextId,           


 ... (clipped 39 lines)
Hardcoded secrets in config

Description: The configuration includes Langfuse SecretKey/PublicKey values in appsettings.json, which
risks accidental credential exposure if committed to source control or distributed with
builds (even if currently placeholders).
appsettings.json [1048-1065]

Referred Code
"Langfuse": {
  "SecretKey": "sk-lf- ",
  "PublicKey": "pk-lf-",
  "Host": "https://us.cloud.langfuse.com/api/public/otel/v1/traces"
},
"A2AIntegration": {
  "Enabled": true,
  "DefaultTimeoutSeconds": 30,
  "Agents": [
    {
      "Id": "cdd9023f-a371-407a-43bf-f36ddccce340",
      "Name": "SportKiosk",
      "Description": "test",
      "Endpoint": "http://localhost:5020/"
    }
  ]

},
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unvalidated JSON parse: The function deserializes message.FunctionArgs without null/empty guarding or parse
failure handling, which can throw and break execution instead of degrading gracefully.

Referred Code
public async Task<bool> Execute(RoleDialogModel message)
{
    var args = JsonSerializer.Deserialize<JsonElement>(message.FunctionArgs);
    string queryText = string.Empty;
    if (args.TryGetProperty("user_query", out var queryProp))
    {
        queryText = queryProp.GetString();
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Leaks exception message: The code returns ex.Message directly in the assistant response, potentially exposing
internal details to end users.

Referred Code
catch (Exception ex)
{
    _logger.LogError(ex, ex.Message);
    responseMessage = new RoleDialogModel(AgentRole.Assistant, ex.Message)
    {
        CurrentAgentId = agent.Id,
        MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
        RenderedInstruction = string.Join("\r\n", renderedInstructions)
    };

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Logs model output: The code logs streamed model text and tool/function argument updates via
logger.LogInformation(...), which can contain sensitive user content or secrets.

Referred Code
logger.LogInformation(choice.ContentUpdate[0]?.Text);

if (!string.IsNullOrEmpty(choice.ContentUpdate[0]?.Text))
{
    var msg = new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty);

    await onStreamResponseReceived(msg);
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Sensitive diagnostics enabled: The PR enables sensitive OpenTelemetry diagnostics globally via AppContext.SetSwitch(...),
increasing the risk of exporting sensitive prompt/content data without explicit
environment-based gating.

Referred Code
// Enable model diagnostics with sensitive data.
AppContext.SetSwitch("BotSharp.Experimental.GenAI.EnableOTelDiagnostics", true);
AppContext.SetSwitch("BotSharp.Experimental.GenAI.EnableOTelDiagnosticsSensitive", true);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit context: New outbound A2A actions are logged without essential audit context (e.g., user identity
and explicit outcome), making reconstruction of who initiated remote calls unclear.

Referred Code
try
{
    _logger.LogInformation($"Sending A2A message to {agentEndpoint}. ContextId: {contextId}");


    var responseBase = await client.SendMessageAsync(sendParams, cancellationToken);

    if (responseBase is AgentMessage responseMsg)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Nonstandard identifier: The field name _iA2AService uses an inconsistent prefix pattern that reduces readability
and self-documentation compared to typical .NET conventions.

Referred Code
private readonly A2ASettings _settings;
private readonly IA2AService _iA2AService;

public A2AAgentHook(IServiceProvider services, IA2AService a2AService, A2ASettings settings)
    : base(services, new AgentSettings())
{
    _iA2AService = a2AService;
    _settings = settings;

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 27, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Refactor diagnostics to use dependency injection

Refactor the static diagnostics classes, such as ModelDiagnostics and
ActivityExtensions, to use dependency injection. This change will improve
testability and align with the application's existing architecture.

Examples:

src/Infrastructure/BotSharp.Abstraction/Diagnostics/ModelDiagnostics.cs [21-25]
public  static class ModelDiagnostics
{
    private static readonly string s_namespace = typeof(ModelDiagnostics).Namespace!;
    private static readonly ActivitySource s_activitySource = new(s_namespace);
src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs [56-148]
        using (var activity = ModelDiagnostics.StartCompletionActivity(null, _model, Provider, prompt, convService))
        {
            try
            {
                response = chatClient.CompleteChat(messages, options);
                value = response.Value;

                var reason = value.FinishReason;
                var content = value.Content;
                var text = content.FirstOrDefault()?.Text ?? string.Empty;

 ... (clipped 83 lines)

Solution Walkthrough:

Before:

// In a consumer class like ChatCompletionProvider.cs
public class ChatCompletionProvider : IChatCompletion
{
    public async Task<RoleDialogModel> GetChatCompletions(...)
    {
        // ...
        using (var activity = ModelDiagnostics.StartCompletionActivity(...))
        {
            // ... logic ...
            activity?.SetTag(ModelDiagnosticsTags.FinishReason, reason);
            // ...
        }
        // ...
    }
}

// In ModelDiagnostics.cs
public static class ModelDiagnostics
{
    private static readonly ActivitySource s_activitySource = new(...);
    public static Activity? StartCompletionActivity(...) { ... }
}

After:

// In a consumer class like ChatCompletionProvider.cs
public class ChatCompletionProvider : IChatCompletion
{
    private readonly IModelDiagnostics _diagnostics;

    public ChatCompletionProvider(..., IModelDiagnostics diagnostics)
    {
        _diagnostics = diagnostics;
    }

    public async Task<RoleDialogModel> GetChatCompletions(...)
    {
        using (var activity = _diagnostics.StartCompletionActivity(...))
        {
            // ... logic ...
            activity?.SetTag(_diagnostics.Tags.FinishReason, reason);
            // ...
        }
    }
}

// New IModelDiagnostics.cs interface and its implementation
public interface IModelDiagnostics { ... }
public class ModelDiagnostics : IModelDiagnostics { ... }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a significant architectural inconsistency, as the new diagnostics feature uses static classes (ModelDiagnostics) in a system that otherwise heavily relies on dependency injection, which negatively impacts testability and maintainability.

High
Possible issue
Avoid deadlocks by using async-await

Replace the blocking call .GetAwaiter().GetResult() with an asynchronous
implementation by overriding OnAgentLoadedAsync and using await to prevent
potential deadlocks.

src/Infrastructure/BotSharp.Plugin.A2A/Hooks/A2AAgentHook.cs [42-56]

-public override void OnAgentLoaded(Agent agent)
+public override async Task OnAgentLoaded(Agent agent)
 {
     // Check if this is an A2A remote agent
     if (agent.Type != AgentType.A2ARemote)
     {
         return;
     }
 
     var remoteConfig = _settings.Agents.FirstOrDefault(x => x.Id == agent.Id);
     if (remoteConfig != null)
     {
-        var agentCard = _iA2AService.GetCapabilitiesAsync(remoteConfig.Endpoint).GetAwaiter().GetResult();
+        var agentCard = await _iA2AService.GetCapabilitiesAsync(remoteConfig.Endpoint);
         agent.Name = agentCard.Name;
         agent.Description = agentCard.Description;
         ...
     }
-    base.OnAgentLoaded(agent);
+    await base.OnAgentLoaded(agent);
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies the use of .GetAwaiter().GetResult(), which is a bad practice that can lead to deadlocks. Refactoring to use the async OnAgentLoadedAsync hook is a significant improvement for application stability and performance.

Medium
Fix potential null reference exception

Fix a type mismatch by handling potential null values from JsonToDictionary
before assigning to argDict. This prevents potential runtime exceptions.

src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs [39-46]

 // Convert arguments to dictionary format expected by mcpdotnet
-Dictionary<string, object> argDict = JsonToDictionary(message.FunctionArgs);
+Dictionary<string, object> argDict = JsonToDictionary(message.FunctionArgs)
+                                        ?.Where(kvp => kvp.Value != null)
+                                        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value!)
+                                        ?? [];
 
 var clientManager = _services.GetRequiredService<McpClientManager>();
 var client = await clientManager.GetMcpClientAsync(_mcpServerId);
 
 // Call the tool through mcpdotnet
 var result = await client.CallToolAsync(_functionName, !argDict.IsNullOrEmpty() ? argDict : []);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a type mismatch between the return type of JsonToDictionary (Dictionary<string, object?>) and the variable argDict (Dictionary<string, object>), which could cause a compilation or runtime error.

Medium
Validate Langfuse config presence

Improve configuration validation by checking for the presence and validity of
required Langfuse settings like PublicKey, SecretKey, and Host, rather than just
the section's existence.

src/Infrastructure/BotSharp.ServiceDefaults/Extensions.cs [129-130]

 var langfuseSection = builder.Configuration.GetSection("Langfuse");
-var useLangfuse = langfuseSection != null;
+var publicKey = langfuseSection.GetValue<string>(nameof(LangfuseSettings.PublicKey));
+var secretKey = langfuseSection.GetValue<string>(nameof(LangfuseSettings.SecretKey));
+var host = langfuseSection.GetValue<string>(nameof(LangfuseSettings.Host));
+var useLangfuse = !string.IsNullOrWhiteSpace(publicKey)
+                  && !string.IsNullOrWhiteSpace(secretKey)
+                  && Uri.IsWellFormedUriString(host, UriKind.Absolute);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that simply checking for the existence of the Langfuse configuration section is insufficient. Validating the required keys within it prevents potential runtime errors and makes the configuration check more robust.

Medium
Learned
best practice
Add required null-safe service resolution

Ensure required services are not null before use; use GetRequiredService or add
a null-guard to avoid passing null into diagnostics code that dereferences the
service.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs [41-52]

-var convService = _services.GetService<IConversationStateService>();
+var convService = _services.GetRequiredService<IConversationStateService>();
 using (var activity = ModelDiagnostics.StartCompletionActivity(null, _model, Provider, prompt, convService))
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Improve defensive coding with precise null and state checks to prevent crashes.

Low
General
Remove redundant code setting a tag

Remove the redundant line that sets the ModelDiagnosticsTags.OutputTokens tag on
the activity twice.

src/Plugins/BotSharp.Plugin.GiteeAI/Providers/Chat/ChatCompletionProvider.cs [81-83]

 activity?.SetTag(ModelDiagnosticsTags.InputTokens, (tokenUsage?.InputTokenCount ?? 0) - (inputTokenDetails?.CachedTokenCount ?? 0));
 activity?.SetTag(ModelDiagnosticsTags.OutputTokens, tokenUsage?.OutputTokenCount ?? 0);
-activity?.SetTag(ModelDiagnosticsTags.OutputTokens, tokenUsage?.OutputTokenCount ?? 0);
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out a duplicated line of code that sets the same tag twice, and removing it improves code cleanliness.

Low
  • Update

@Oceania2018 Oceania2018 requested a review from iceljc December 27, 2025 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant