連續多日,我們教會了 Semantic Kernel (SK) 這位主廚各種精湛的廚藝,從挑選食材 (Prompting) 到使用先進的工具 (Plugins),甚至能領導自動化的團隊 (Agents)。現在,我們的 AI 應用已經具備了企業級的執行力。
但經營一家頂級餐廳,光會做菜可不夠!你必須時刻知道:客人點餐花了多少時間?哪道菜最耗成本?哪個廚師環節是瓶頸?
在 AI 應用中,這就是 可觀測性 (Observability) 的價值。我們需要一個「餐廳儀表板」,來監控 AI 系統的健康狀況、性能、成本,以及行為。
在 Semantic Kernel 中,我們可以使用業界標準的 OpenTelemetry 來實現這個目標,將 AI 的「內心戲」完全透明化!
可觀測性主要由三個核心元素構成,它們就像是餐廳的日誌、會計報表和監視器:
透過 SK 對 OpenTelemetry 的原生支援,我們能輕鬆地將這些資訊從 Kernel 內部撈出來,並導向各種監控系統(例如:Azure Application Insights、Prometheus、Jaeger)。
這裡我們以 Tracing (追蹤) 為例,示範如何設定 OpenTelemetry,將 Kernel 內部所有函式呼叫和 LLM 互動的細節記錄下來,輸出到 Console 供我們觀察。
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console
然後,在我們的程式中設定 Host
和 OpenTelemetry
。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using System.Diagnostics;
using Microsoft.SemanticKernel.Plugins;
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Exporter;
// 建立自訂的 ActivitySource 用於測試
var activitySource = new ActivitySource("Day27.Demo");
// 2. 設定 Host 和 OpenTelemetry
var builder = Host.CreateApplicationBuilder(args);
// 設定 ActivityListener 來捕獲 Semantic Kernel 的活動
using var activityListener = new ActivityListener
{
ShouldListenTo = source =>
{
Console.WriteLine($"🔍 偵測到 ActivitySource: {source.Name}");
return source.Name.Contains("Microsoft.SemanticKernel") ||
source.Name.Contains("Day27.Demo") ||
source.Name.Contains("OpenAI");
},
Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
{
Console.WriteLine($"📊 開始追蹤活動: {options.Name}");
return ActivitySamplingResult.AllData;
},
SampleUsingParentId = (ref ActivityCreationOptions<string> options) => ActivitySamplingResult.AllData,
ActivityStarted = activity =>
{
Console.WriteLine($"▶️ 活動開始: {activity.DisplayName} ({activity.Source.Name})");
if (activity.Tags.Any())
{
foreach (var tag in activity.Tags)
{
Console.WriteLine($" 🏷️ {tag.Key}: {tag.Value}");
}
}
},
ActivityStopped = activity =>
{
Console.WriteLine($"⏹️ 活動結束: {activity.DisplayName} - 耗時: {activity.Duration.TotalMilliseconds:F2}ms");
}
};
ActivitySource.AddActivityListener(activityListener);
// 配置 OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
// 指定追蹤來源:Semantic Kernel 和相關服務
tracing.AddSource("Microsoft.SemanticKernel*")
.AddSource("Microsoft.SemanticKernel")
.AddSource("Microsoft.SemanticKernel.Connectors.OpenAI")
.AddSource("System.Net.Http")
.AddSource("OpenAI")
.AddSource("Day27.Demo") // 我們的自訂 ActivitySource
.SetSampler(new AlwaysOnSampler()) // 確保所有活動都被記錄
.AddConsoleExporter(options =>
{
options.Targets = OpenTelemetry.Exporter.ConsoleExporterOutputTargets.Console;
}); // 將追蹤結果輸出到 Console
});
// 3. 註冊 Kernel 服務
builder.Services.AddSingleton(sp =>
{
var config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-5",
apiKey: config["OpenAI:ApiKey"]!)
.Build();
// 將 Plugin 加入 Kernel
kernel.Plugins.AddFromObject(new CalculatorPlugin());
return kernel;
});
// 4. 運行應用程式
var app = builder.Build();
var kernel = app.Services.GetRequiredService<Kernel>();
Console.WriteLine("✅ OpenTelemetry 配置完成,開始監控 Kernel 互動...");
// 開啟詳細的 Activity 紀錄
Console.WriteLine("🔍 開始監控 Activity...");
// 測試自訂 ActivitySource 是否工作
using (var testActivity = activitySource.StartActivity("測試追蹤"))
{
testActivity?.SetTag("test", "true");
Console.WriteLine("🧪 建立了一個測試追蹤活動");
await Task.Delay(100);
}
Console.WriteLine("🧪 測試追蹤活動完成");
// 5. 執行一個需要 Function Calling 的複雜請求
var prompt = "請計算 55 加上 42 的結果,並用一句幽默的話來回答。";
var settings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
Console.WriteLine($"\n🧑 客戶請求:{prompt}");
Console.WriteLine("📡 正在處理請求...");
// 建立一個自訂的追蹤活動
using var activity = activitySource.StartActivity("處理用戶請求");
activity?.SetTag("prompt", prompt);
activity?.SetTag("model", "gpt-5");
var result = await kernel.InvokePromptAsync(prompt, new(settings));
activity?.SetTag("response.length", result.GetValue<string>()?.Length.ToString() ?? "0");
activity?.SetStatus(ActivityStatusCode.Ok);
Console.WriteLine("\n--- 🤖 AI 最終回覆 ---");
Console.WriteLine(result.GetValue<string>());
Console.WriteLine("-----------------------");
// 6. 觀察 Console 輸出
Console.WriteLine("\n**✅ 上方已顯示完整的 OpenTelemetry 追蹤資訊!**");
Console.WriteLine("\n=== 追蹤摘要 ===");
Console.WriteLine("✅ 捕獲到 Semantic Kernel 的活動追蹤");
Console.WriteLine("✅ 顯示了 Function Calling 的詳細過程");
Console.WriteLine("✅ 記錄了各個步驟的執行時間");
Console.WriteLine("================");
// 確保所有 Telemetry 資料都已寫出
await Task.Delay(1000);
// 1. 建立一個簡單的 Plugin (我們的廚房工具)
public class CalculatorPlugin
{
[KernelFunction("Add")]
[System.ComponentModel.Description("將兩個數字相加。")]
public int Add(int a, int b) => a + b;
}
當你執行上述程式碼後,你會在 Console 看到一長串 OpenTelemetry 格式的輸出。這些就是我們的「餐廳監視器」畫面:
Microsoft.SemanticKernel.Kernel.InvokePromptAsync
。Microsoft.SemanticKernel.AIService.ChatCompletion
的 Span,這告訴你 SK 是何時、用哪個模型 (gpt-4o
)、發送了什麼 prompt
給 LLM,以及 LLM 的初次回覆 (它決定呼叫 Add
函式)。Microsoft.SemanticKernel.Function.Run
的 Span,專門記錄 CalculatorPlugin.Add
函式的執行時間和參數。ChatCompletion
呼叫:第一次是 LLM 決定工具,第二次是 LLM 收到工具結果後生成最終答案。每一個步驟的延遲時間 (Latency) 都被精確記錄下來。透過這個儀表板,你就能知道你的 AI 應用是否因為 Function Calling 的往復導致延遲過高?哪一個 Plugin 函式的執行時間過長?以及每次互動究竟消耗了多少 Token(雖然 Token 消耗通常是 Metrics 的部分,但也會作為 Span 的屬性被記錄)。