Semantic Kernel Example
This example builds a loan advisory application that uses Microsoft Semantic Kernel with a VeriProof governance filter. Every function invocation, model call, and tool use is automatically captured and routed to VeriProof.
Packages used
Veriproof.Sdk.Core— OTel exporter and transportVeriproof.Sdk.Annotations— typed governance attributesVeriproof.Sdk.Instrumentation.SemanticKernel— automatic SK span captureMicrosoft.SemanticKernel— kernel, plugins, and chat completion
Prerequisites
dotnet add package Veriproof.Sdk.Core
dotnet add package Veriproof.Sdk.Annotations
dotnet add package Veriproof.Sdk.Instrumentation.SemanticKernel
dotnet add package Microsoft.SemanticKernelSet environment variables:
VERIPROOF_API_KEY=vp_live_...
VERIPROOF_APP_ID=loan-advisory-service
OPENAI_API_KEY=sk-...Step 1 — Configure DI
// Program.cs
using OpenTelemetry.Trace;
using Veriproof.Sdk;
using Veriproof.Sdk.Instrumentation.SemanticKernel;
using Microsoft.SemanticKernel;
var builder = WebApplication.CreateBuilder(args);
// Register the VeriProof Semantic Kernel filter
builder.Services.AddVeriproofSemanticKernelInstrumentation(options =>
{
options.CaptureRenderedPrompt = false; // set true to send prompt text to VeriProof
options.CaptureFunctionResult = false;
options.CaptureToolDescription = true;
});
// Configure the 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")!;
})
);
// Build the Semantic Kernel — VeriproofKernelFilter injected automatically from DI
builder.Services.AddKernel()
.AddOpenAIChatCompletion(
modelId: "gpt-4.1",
apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY")!
);
builder.Services.AddSingleton<LoanAdvisoryService>();Step 2 — Define the plugin
// LoanAnalysisPlugin.cs
using Microsoft.SemanticKernel;
using System.ComponentModel;
public sealed class LoanAnalysisPlugin
{
[KernelFunction("classify_application")]
[Description("Classify a loan application risk tier based on credit profile.")]
public string ClassifyApplication(
[Description("Applicant identifier")] string applicantId,
[Description("Credit score 300–850")] int creditScore)
{
return creditScore switch
{
>= 750 => "LOW_RISK",
>= 650 => "MEDIUM_RISK",
_ => "HIGH_RISK"
};
}
[KernelFunction("assess_loan_eligibility")]
[Description("Assess loan eligibility and recommend an amount.")]
public string AssessEligibility(
[Description("Risk tier from classify_application")] string riskTier,
[Description("Requested loan amount in USD")] decimal requestedAmount)
{
return riskTier switch
{
"LOW_RISK" => $"ELIGIBLE — recommended up to {requestedAmount:C0}",
"MEDIUM_RISK" => $"ELIGIBLE_WITH_CONDITIONS — recommended up to {requestedAmount * 0.75m:C0}",
_ => "INELIGIBLE"
};
}
}Step 3 — Invoke the kernel with governance annotations
// LoanAdvisoryService.cs
using System.Diagnostics;
using Microsoft.SemanticKernel;
using Veriproof.Sdk.Annotations;
using Veriproof.Sdk.Annotations.Models;
public sealed class LoanAdvisoryService(Kernel kernel, ILogger<LoanAdvisoryService> logger)
{
private static readonly ActivitySource Tracer =
new("LoanAdvisoryService");
public async Task<string> EvaluateAsync(
string applicantId,
int creditScore,
decimal requestedAmount,
CancellationToken ct = default)
{
// Open a session-level span — SK function spans appear as children
using var session = Tracer.StartActivity("ai.session");
session?
.SetSessionIntent(SessionIntent.advisory)
.SetTransactionId($"loan:{applicantId}:{DateTimeOffset.UtcNow:yyyyMMdd}")
.SetDataSensitivity(DataSensitivity.Restricted)
.SetRegulatoryScope("ECOA")
.SetHumanReviewRequired(false);
try
{
kernel.Plugins.AddFromObject(new LoanAnalysisPlugin(), "LoanAnalysis");
// Step 1: classify (automatic SK span created by VeriproofKernelFilter)
var classifyResult = await kernel.InvokeAsync(
"LoanAnalysis", "classify_application",
new KernelArguments
{
["applicantId"] = applicantId,
["creditScore"] = creditScore
},
cancellationToken: ct
);
var riskTier = classifyResult.GetValue<string>() ?? "HIGH_RISK";
// Step 2: eligibility assessment (automatic SK span)
var eligibilityResult = await kernel.InvokeAsync(
"LoanAnalysis", "assess_loan_eligibility",
new KernelArguments
{
["riskTier"] = riskTier,
["requestedAmount"] = requestedAmount
},
cancellationToken: ct
);
var recommendation = eligibilityResult.GetValue<string>() ?? "INELIGIBLE";
// Enrich the session span with governance decision
var riskLevel = riskTier == "HIGH_RISK" ? RiskLevel.High
: riskTier == "MEDIUM_RISK" ? RiskLevel.Medium
: RiskLevel.Low;
session?
.SetRiskLevel(riskLevel)
.SetSessionOutcome(SessionOutcome.success)
.SetGovernanceDecision(
DecisionContext.WithOptions(
prompt: $"Loan eligibility for applicant {applicantId}",
decision: recommendation,
decisionType: DecisionType.Approval,
options: new[] { "ELIGIBLE", "ELIGIBLE_WITH_CONDITIONS", "INELIGIBLE" }
)
);
if (riskLevel == RiskLevel.High)
{
session?.AddRiskFactor(
RiskFactor.TechnicalIssue("Credit score below minimum threshold"));
}
logger.LogInformation(
"Loan evaluation complete. Applicant={ApplicantId} Risk={RiskTier} Decision={Decision}",
applicantId, riskTier, recommendation);
return recommendation;
}
catch (Exception ex)
{
session?.SetSessionOutcome(SessionOutcome.error);
throw;
}
}
}What gets captured
| Span | Key attributes |
|---|---|
ai.session | veriproof.session.intent, veriproof.risk.level, veriproof.governance.decision.* |
ai.function.classify_application | gen_ai.tool.name, gen_ai.tool.description, gen_ai.usage.* |
ai.function.assess_loan_eligibility | gen_ai.tool.name, gen_ai.tool.description, gen_ai.usage.* |
The two function spans are automatically nested under ai.session because they run inside its Activity scope.
Next steps
- Governance Session Builder example — manual
Activitytracing without Semantic Kernel - AutoGen multi-agent example — multi-agent orchestration with
AutoGenOTelAdapter - Semantic Kernel SDK reference — full
SemanticKernelAdapterOptionstable
Last updated on