Skip to Content
Examples.NETAutoGen Multi-Agent

AutoGen Multi-Agent Example

This example builds a two-agent loan approval workflow — an Orchestrator that coordinates the process and a LoanAnalyst that evaluates applications. All agent-to-model calls are traced automatically via AutoGenOTelAdapter.

Packages used

  • Veriproof.Sdk.Core — OTel exporter
  • Veriproof.Sdk.Annotations — governance attributes
  • Veriproof.Sdk.Instrumentation.AutoGenAutoGenOTelAdapter, AgentRunHandle
  • Microsoft.Extensions.AIIChatClient abstraction

Prerequisites

dotnet add package Veriproof.Sdk.Core dotnet add package Veriproof.Sdk.Annotations dotnet add package Veriproof.Sdk.Instrumentation.AutoGen dotnet add package Microsoft.Extensions.AI dotnet add package Microsoft.Extensions.AI.OpenAI

Configure DI

// Program.cs using Microsoft.Extensions.AI; using OpenTelemetry.Trace; using Veriproof.Sdk; using Veriproof.Sdk.Instrumentation.AutoGen; var builder = WebApplication.CreateBuilder(args); // 1. Register base chat client (model-level) builder.Services.AddSingleton<IChatClient>(_ => new OpenAI.OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!) .AsChatClient("gpt-4.1") ); // 2. Register AutoGen adapter builder.Services.AddVeriproofAutoGenInstrumentation(options => { options.GenAiSystem = "autogen"; options.CaptureMessageContent = false; }); // 3. Configure VeriProof OTel exporter builder.Services.AddOpenTelemetry() .WithTraces(traces => traces .AddSource("*") .AddVeriproofTracing(options => { options.ApiKey = Environment.GetEnvironmentVariable("VERIPROOF_API_KEY")!; options.ApplicationId = Environment.GetEnvironmentVariable("VERIPROOF_APP_ID")!; }) ); builder.Services.AddSingleton<LoanApprovalWorkflow>();

Workflow implementation

// LoanApprovalWorkflow.cs using System.Diagnostics; using Microsoft.Extensions.AI; using Veriproof.Sdk.Annotations; using Veriproof.Sdk.Annotations.Models; using Veriproof.Sdk.Instrumentation.AutoGen; public sealed class LoanApprovalWorkflow( AutoGenOTelAdapter adapter, IChatClient chatClient, ILogger<LoanApprovalWorkflow> logger) { public async Task<LoanDecision> EvaluateAsync( string applicationId, string applicantId, int creditScore, decimal requestedAmount, CancellationToken ct = default) { // ── Open agent_run span — covers the whole workflow ────────────────── using var run = adapter.StartAgentRun( workflowName: "LoanApprovalWorkflow", agentName: "Orchestrator", sessionId: applicantId ); run.SetTag("veriproof.transaction.id", $"loan:{applicationId}") .SetTag("loan.application.id", applicationId) .SetTag("loan.applicant.id", applicantId) .SetTag("loan.credit_score", creditScore.ToString()) .SetTag("loan.requested_amount", requestedAmount.ToString("F2")); try { // ── Agent 1: LoanAnalyst ───────────────────────────────────────── var analystClient = adapter.WrapClient( inner: chatClient, agentName: "LoanAnalyst", defaultModelId: "gpt-4.1" ); var analystMessages = new ChatMessage[] { new(ChatRole.System, "You are an expert loan analyst. Given the applicant's credit profile, " + "return a JSON object with: " + "{ \"riskTier\": \"LOW|MEDIUM|HIGH\", \"recommendation\": \"APPROVE|CONDITIONAL|DENY\", " + "\"confidenceScore\": 0.0-1.0, \"justification\": \"...\" }"), new(ChatRole.User, $"Application ID: {applicationId}. " + $"Applicant: {applicantId}. " + $"Credit score: {creditScore}. " + $"Requested amount: {requestedAmount:C0}.") }; var analystResponse = await analystClient.GetResponseAsync(analystMessages, ct: ct); var analystOutput = analystResponse.Message.Text ?? "{}"; logger.LogInformation("LoanAnalyst response: {Output}", analystOutput); // ── Agent 2: ComplianceReviewer ────────────────────────────────── var reviewerClient = adapter.WrapClient( inner: chatClient, agentName: "ComplianceReviewer", defaultModelId: "gpt-4.1" ); var reviewMessages = new ChatMessage[] { new(ChatRole.System, "You are a compliance officer. Review the analyst's recommendation against " + "regulatory requirements (ECOA, Fair Lending). " + "Return JSON: { \"compliant\": true|false, \"finalDecision\": \"APPROVE|DENY\", " + "\"complianceNotes\": \"...\" }"), new(ChatRole.User, $"Analyst output: {analystOutput}") }; var reviewResponse = await reviewerClient.GetResponseAsync(reviewMessages, ct: ct); var reviewOutput = reviewResponse.Message.Text ?? "{}"; logger.LogInformation("ComplianceReviewer response: {Output}", reviewOutput); // ── Parse decision ─────────────────────────────────────────────── var approved = reviewOutput.Contains("\"APPROVE\"", StringComparison.OrdinalIgnoreCase); var riskLevel = analystOutput.Contains("LOW", StringComparison.OrdinalIgnoreCase) ? RiskLevel.Low : analystOutput.Contains("MEDIUM", StringComparison.OrdinalIgnoreCase) ? RiskLevel.Medium : RiskLevel.High; // ── Enrich the agent_run span with governance outcome ────────── run.SetTag("veriproof.session.intent", "transaction") .SetTag("veriproof.session.outcome", approved ? "success" : "failure") .SetTag("veriproof.risk.level", riskLevel.ToString().ToLowerInvariant()) .SetTag("veriproof.governance.decision", approved ? "APPROVE" : "DENY") .SetTag("loan.compliance.reviewed", "true"); if (riskLevel >= RiskLevel.Medium) { // Add a typed risk factor via Activity extension Activity.Current? .AddRiskFactor(RiskFactor.LowConfidence( $"Credit score {creditScore} in medium-risk range")); } return new LoanDecision( ApplicationId: applicationId, Approved: approved, RiskLevel: riskLevel, AnalystOutput: analystOutput, ReviewOutput: reviewOutput ); } catch (Exception ex) { run.SetFailed(ex); throw; } } } public record LoanDecision( string ApplicationId, bool Approved, RiskLevel RiskLevel, string AnalystOutput, string ReviewOutput );

Span hierarchy

agent_run (LoanApprovalWorkflow / Orchestrator) │ veriproof.transaction.id = "loan:APP-001" │ veriproof.session.outcome = "success" │ veriproof.risk.level = "medium" │ veriproof.governance.decision = "APPROVE" ├── chat_completion │ gen_ai.agent.name = "LoanAnalyst" │ gen_ai.request.model = "gpt-4.1" │ gen_ai.usage.input_tokens = 287 │ gen_ai.usage.output_tokens = 94 └── chat_completion gen_ai.agent.name = "ComplianceReviewer" gen_ai.request.model = "gpt-4.1" gen_ai.usage.input_tokens = 193 gen_ai.usage.output_tokens = 61

AgentRunHandle quick reference

MethodDescription
SetTag(key, value)Add an OTel attribute to the agent_run span; returns this
SetFailed(Exception)Mark span as failed; sets error.type and records the exception
TraceIdW3C-format trace ID — use to correlate with logs
SpanIdSpan ID of the agent_run span
Dispose()End the span (called automatically at end of using block)

Next steps

Last updated on