Skip to Content
Examples.NETSemantic Kernel

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 transport
  • Veriproof.Sdk.Annotations — typed governance attributes
  • Veriproof.Sdk.Instrumentation.SemanticKernel — automatic SK span capture
  • Microsoft.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.SemanticKernel

Set 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

SpanKey attributes
ai.sessionveriproof.session.intent, veriproof.risk.level, veriproof.governance.decision.*
ai.function.classify_applicationgen_ai.tool.name, gen_ai.tool.description, gen_ai.usage.*
ai.function.assess_loan_eligibilitygen_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

Last updated on