iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0

連續多日,我們教會了 Semantic Kernel (SK) 這位主廚各種精湛的廚藝,從挑選食材 (Prompting) 到使用先進的工具 (Plugins),甚至能領導自動化的團隊 (Agents)。現在,我們的 AI 應用已經具備了企業級的執行力。

但經營一家頂級餐廳,光會做菜可不夠!你必須時刻知道:客人點餐花了多少時間?哪道菜最耗成本?哪個廚師環節是瓶頸?

在 AI 應用中,這就是 可觀測性 (Observability) 的價值。我們需要一個「餐廳儀表板」,來監控 AI 系統的健康狀況、性能、成本,以及行為。

在 Semantic Kernel 中,我們可以使用業界標準的 OpenTelemetry 來實現這個目標,將 AI 的「內心戲」完全透明化!

📊 Observability 的三大支柱:日誌、指標與追蹤

可觀測性主要由三個核心元素構成,它們就像是餐廳的日誌、會計報表和監視器:

  1. 日誌 (Logging): 記錄事件的詳細文字描述 (例如:函式呼叫的開始與結束、錯誤訊息)。
  2. 指標 (Metrics): 數值化的數據,用於量化系統的性能 (例如:Token 消耗量、呼叫延遲時間、錯誤率)。
  3. 追蹤 (Tracing): 顯示一個請求 (例如:使用者提問) 從頭到尾流經了哪些服務與函式,以及每個步驟花了多少時間。

透過 SK 對 OpenTelemetry 的原生支援,我們能輕鬆地將這些資訊從 Kernel 內部撈出來,並導向各種監控系統(例如:Azure Application Insights、Prometheus、Jaeger)。

⚙️ 實戰:設定 OpenTelemetry 追蹤 AI 的互動流程

這裡我們以 Tracing (追蹤) 為例,示範如何設定 OpenTelemetry,將 Kernel 內部所有函式呼叫和 LLM 互動的細節記錄下來,輸出到 Console 供我們觀察。

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console

然後,在我們的程式中設定 HostOpenTelemetry

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 格式的輸出。這些就是我們的「餐廳監視器」畫面:

  1. Span 結構: 你會看到整個請求被一個主要的 Span (追蹤區間) 包裹,例如 Microsoft.SemanticKernel.Kernel.InvokePromptAsync
  2. LLM 呼叫細節: 裡面會包含 Microsoft.SemanticKernel.AIService.ChatCompletion 的 Span,這告訴你 SK 是何時、用哪個模型 (gpt-4o)、發送了什麼 prompt 給 LLM,以及 LLM 的初次回覆 (它決定呼叫 Add 函式)。
  3. Plugin 執行: 你會看到一個 Microsoft.SemanticKernel.Function.Run 的 Span,專門記錄 CalculatorPlugin.Add 函式的執行時間和參數。
  4. 往復流程: 由於這是 Function Calling,你會看到兩次 ChatCompletion 呼叫:第一次是 LLM 決定工具,第二次是 LLM 收到工具結果後生成最終答案。每一個步驟的延遲時間 (Latency) 都被精確記錄下來。

透過這個儀表板,你就能知道你的 AI 應用是否因為 Function Calling 的往復導致延遲過高?哪一個 Plugin 函式的執行時間過長?以及每次互動究竟消耗了多少 Token(雖然 Token 消耗通常是 Metrics 的部分,但也會作為 Span 的屬性被記錄)。


上一篇
Day 26: 安全第一的饗宴:用 Filters 確保 AI 的責任與安全
系列文
AI 全餐,好吃嗎?用 Semantic Kernel 打造你的客製化滿漢全席!27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言