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 exporterVeriproof.Sdk.Annotations— governance attributesVeriproof.Sdk.Instrumentation.AutoGen—AutoGenOTelAdapter,AgentRunHandleMicrosoft.Extensions.AI—IChatClientabstraction
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.OpenAIConfigure 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 = 61AgentRunHandle quick reference
| Method | Description |
|---|---|
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 |
TraceId | W3C-format trace ID — use to correlate with logs |
SpanId | Span ID of the agent_run span |
Dispose() | End the span (called automatically at end of using block) |
Next steps
- Semantic Kernel example — auto-instrumentation using
VeriproofKernelFilter - Governance Session Builder example — manual
Activitytracing - AutoGen SDK reference — full
AutoGenOTelAdapterAPI
Last updated on