iT邦幫忙

2025 iThome 鐵人賽

DAY 1
0
生成式 AI

當 .NET 遇見 AI Agents:用 Semantic Kernel × MCP 打造智慧協作應用系列 第 1

Day 1: 踏入 AI Agent 的世界 - 認識 Semantic Kernel

  • 分享至 

  • xImage
  •  

這幾年來,生成式 AI 的應用一路從產出文字、圖片,逐步進化到「做事」的 AI Agent。這個發展過程真的很有趣,AI 從「會聊天的模型」變成「會幫你做事的助理」,而且還越來越像個能自己判斷該怎麼做的夥伴。

但關於什麼是 AI Agent,如果你上 Google 查一下,會看到一堆不同的定義,有的偏重技術、有的強調應用,但有共識的應該是 AI Agent 是一種「以任務為導向」的 AI,不是等著我們下一步一步的指令,而是能自己想辦法完成任務。可以根據目標,自主決定要怎麼做、中間需要什麼工具,甚至如果遇到問題,會自己找方法解決。

也就是說,AI Agent 不只是懂自然語言(模型),很重要的是還要能跟外部系統互動(工具),像是真正去執行某些工作——不管是查資料、操作系統、叫 API,甚至跑一段自動化流程。從這個角度來看,它已經不是單純的「聊天機器人」了,而是具備「自主性」和「行動力」的智能程式。

像是 OpenAI 推出的 Agent SDK,就是很明確就是往這個方向前進。讓開發者可以打造出能根據使用者需求,自動判斷接下來該做什麼的 Agent。舉個例子好了:如果你要訂一張機票,不需要一步步告訴它先查航班、再比價、再訂票,而是你只需要說「幫我訂一張下週從台北飛東京的機票」,Agent 就會自己搞定整個流程,中間該叫哪些 API、要怎麼處理資料,都不需要你操心。(當然這中間會涉及一些安全性/隱私等問題,我們先不討論以免失焦)

但問題是每一個平台都有自已的一套API,對於開發者來說,如何在不同平台間切換,或是同時使用多個平台的API,就會是一個很大的挑戰。所以這幾年就看到很多類似 LangChain 這樣的框架出現,幫助開發者整合不同的AI模型和API,讓我們可以專注在商業邏輯的撰寫,而不是花太多時間在處理各種API的差異。以 Python 語言來說,大家都知道 LangChain,而.NET我推薦的就是 Semantic Kernel啦。

Semantic Kernel 是 Microsoft 開發的開源工具,目標對齊 LangChain 專門幫助開發者整合各種AI模型和API。提供一的介面,讓開發者可以輕鬆在不同的AI平台之間切換,並且能夠同時使用多個平台的API。隨著AI技術的快速發展,Semantic Kernel也在不斷更新,包含 RAG 的應用,而現在當然包含了 AI Agent 的實作支援。這樣的工具對於想要打造 AI Agent 的開發者來說,可以減少許多開發上的障礙。

這次的鐵人賽,打算再次以 Semantic Kernel 為主軸,實現多種的 AI Agent 原型打造。從概念到實際的程式碼範例。相對的對於簡單的生成任務,著重的篇數就不會那麼多。不過現在不管是LangChain 或是 Semantic Kernel,都是進版的非常快速,所以如果有任何新的功能或是改變,都是正常現象,所以每一篇文章的內容,所使用的版本可能會隨著時間有所不同。而以 Semantic Kernel來說,如果你在前二年的鐵人賽有看過我撰寫的文章,那麼現在的版本已經和當時有很大的差異了。因此一開始我還是針對目前最新的版本來做基本使用的介紹。

認識 Semantic Kernel

身為 .NET 開發者,不管你有沒有接觸過 Semantic Kernel,接下來的內容都會對你有幫助。因為我會從最基礎的 Semantic Kernel 核心開始講起,讓你能夠一步步了解這個框架。

簡單來說,Semantic Kernel 就像是 AI 世界的瑞士刀。它是 Microsoft 開發的開源工具,專門幫我們處理 AI 整合的各種麻煩事。目前它支援 C#、Python 和 Java,所以習慣用 Python、Java 語言也能上手。

Semantic Kernel 核心元素

想像一下,你在組裝一台電腦。有主機板、CPU、記憶體、硬碟等零件,每個都有自己的功能,但只有當它們正確組合在一起時,才能變成一台能用的電腦。Semantic Kernel 也是這樣,它由幾個核心元件組成,每個都扮演著不可或缺的角色。

如果用一個生活化的比喻來說明:把 Semantic Kernel 想像成一家餐廳,那麼...
Kernel 就像是餐廳的經理,負責統籌全場。當客人(使用者)點餐時,經理會協調廚房(AI 模型)、服務生(Plugins)、甚至是外送員(外部 API),確保整個服務流程順暢。在程式碼裡,Kernel 是你最常打交道的對象,所有的指令都是透過它來發號施令。

// 就像聘請一位經理
var kernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion(modelId: "gpt-4", apiKey: "your-key")
    .Build();
  • Connector(連接器)- 溝通橋樑

Connector 就像是翻譯官。不管是要連接 OpenAI、Azure OpenAI、還是 Google Gemini,Connector 都能幫你搞定溝通問題。如果哪天想換個 AI 模型,只要換個 Connector,其他程式碼幾乎不用改!這正也是解決不同平台 API 整合的關鍵。

  • Plugin(外掛)與 Functions(函數)- 工具箱

這是實作 AI Agent 最關鍵的部分!Plugin 就像是給 AI 配備的工具箱。裡面可以放各種工具(Functions),比如:

  • 查詢天氣的溫度計
  • 發送郵件的信封
  • 查資料庫的放大鏡

Function 可以是兩種類型:

  1. Method Function(方法函數):用 C# 寫的實際程式碼,能做 AI 做不到的事,像是查資料庫、讀檔案、呼叫外部 API
  2. Prompt Function(提示函數):AI模型本身就有能做的事,預先定義好的Prompt提示詞模板,像是「請摘要這段文字」、「翻譯成英文」
[KernelFunction]
[Description("Retrieves the today temperature of the city.")]
public int GetTemperature(
        [Description("The name of the city to get the temperature for.The city names in the weather data are in English")]
            string city)
{
    // 真正去查詢天氣的程式碼
}
  • Memory(記憶庫)- AI 的筆記本

Memory 讓 AI 能記住之前的對話內容、學習過的知識,甚至是你上傳的文件內容。有了 Memory,AI 就像是有了自己的筆記本,能夠累積經驗、持續學習。短期記憶(Chat History)和長期記憶(Knowledge Base)都可以透過 Memory 來實現。短期記憶通常用於對話場景,讓 AI 能記住前幾輪的對話內容;長期記憶則是用來存放較永久的資訊,像是產品手冊、公司政策等,簡單來說長期記憶就就是用來打造 RAG 應用。

  • 工具機制(Tools) - 讓 AI 真正「動」起來

這是整個系統最神奇的地方!透過 Function Calling 機制,AI 不只會思考,還能行動。當使用者說「幫我查台北的天氣」時:

  1. AI 理解意圖:「喔,使用者想知道天氣」
  2. AI 查看工具箱:「我有查天氣的工具嗎?有!」
  3. AI 使用工具:自動呼叫 GetWeather("台北")
  4. AI 整理結果:「台北今天 28 度,適合外出」

整個過程完全自動化,你不需要寫一堆 if-else 來判斷使用者想做什麼。AI 會自己判斷、自己選工具、自己執行。

Semantic Kernel 早期有個 Planner 元件專門做規劃,但隨著現在模型能力的提升,直接用 Function Calling 更簡單有效,所以在 1.0 版之後就改用這個方式了。也就沒有 Planner 這個元件了。

Semantic Kernel 對於AI Agent帶來了什麼助益?

以大家熟悉的客服系統來說, Semantic Kernel 的整個開發體驗會大幅提升。

以前的做法是這樣的:

  1. 客戶問:「我的訂單到哪了?」(有沒有人還記得LUIS這個服務,從文字中解析出使用者的意圖)
  2. AI 回答:「請提供訂單編號」
  3. 客戶給了編號
  4. 我要寫程式解析 AI 的回應(有沒有人還記得LUIS這個服務,從文字中解析出實體關鍵字)
  5. 再寫程式去查詢資料庫
  6. 把結果包裝好給 AI
  7. AI 才能告訴客戶答案

其實滿累的對吧?

而 Semantic Kernel 則是:

  1. 我把查詢訂單的功能註冊成一個「function」
  2. 客戶問:「訂單 12345 到哪了?」
  3. AI 自動識別意圖,呼叫查詢功能
  4. 直接回答:「您的訂單已經送達囉!」

就這麼簡單!感覺就像是給 AI 裝上了手腳,它終於能自己動手做事了。

初探 Semantic Kernel

先讓我們用一個基本的生成範例來看看如何使用 Semantic Kernel 。這個範例連接到 OpenAI 的聊天服務,並建立一個簡單的聊天機器人範例,不包含 function calling 的功能,也不具短期記憶對話功能,實務上LLM應用的APP也不總是以Chat為出發點,很多時候是以單一任務為主。

本文範例使用 Microsoft.SemanticKernel 套件,版本 1.64.0

using Microsoft.SemanticKernel;

namespace day1;

class Program
{
    static async Task Main(string[] args)
    {
        // 連接到 OpenAI 的聊天服務,並建立一個簡單的聊天機器人範例
        // 這個範例不包含 function calling 的功能
        // 不具短期記憶對話功能
        Kernel kernel = Kernel.CreateBuilder()
                       .AddOpenAIChatCompletion(
                           apiKey: Config.OpenAI_ApiKey,
                           modelId: Config.ModelId)
                       .Build();

        while (true)
        {
            Console.Write("You: ");
            var input = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
                break;

            var response = await kernel.InvokePromptAsync(input);
            Console.WriteLine($"AI: {response} \n\n");
        }

        Console.WriteLine("\n Bye!");
    }
}

程式碼說明

  1. Kernel 建立與設定

    • 透過 Kernel.CreateBuilder() 建立 Kernel 物件,並用 .AddOpenAIChatCompletion() 設定 OpenAI 的 API 金鑰與模型名稱。
    • .Build() 產生 Kernel 實例。
  2. 互動式對話迴圈

    • 使用 while (true) 進行 for 迴圈,持續等待使用者輸入。
    • 當輸入為空或輸入 "exit"(不分大小寫)時,跳出 for 迴圈結束聊天。
  3. 呼叫 AI 回應

    • 透過 kernel.InvokePromptAsync(input) 將使用者的提問輸入送給 LLM,取得 AI 回應。
    • 回應結果直接顯示在主控台。
  4. 注意事項

    • 這個範例僅示範最基本的聊天互動,不包含 function calling不具備短期記憶,每次輸入都視為獨立對話。
    • 此範例用來理解 Semantic Kernel 的最小可執行範例。

這樣的架構讓你能夠很快體驗 Semantic Kernel 與 OpenAI 聊天服務的整合,後續只要擴充 Kernel 設定或加入 Plugin/Function,就能打造更進階的 AI Agent。

具備短期聊天歷史的範例

延伸上面的範例,我們可以加入短期記憶的功能,讓AI能夠記住之前的對話內容,進而提供更連貫的回應。以下是修改後的程式碼範例:

// 連接到 OpenAI 的聊天服務,並建立一個簡單的聊天機器人範例
// 這個範例不包含 function calling 的功能
// 具備短期記憶對話功能
Kernel kernel = Kernel.CreateBuilder()
                .AddOpenAIChatCompletion(
                    apiKey: Config.OpenAI_ApiKey,
                    modelId: Config.ModelId)
                .Build();

// 建立對話歷史
ChatHistory history = [];
// 從 kernel 取得聊天服務
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();


// 開始聊天對話
Console.Write("User > ");
string? userInput;
while ((userInput = Console.ReadLine()) is not null)
{

    if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
        break;

    // 加入使用者訊息到對話歷史
    history.AddUserMessage(userInput);

    // 以串流方式獲取 AI 回應
    var result = chatCompletionService.GetStreamingChatMessageContentsAsync(
                        history,
                        kernel: kernel);

    // Stream the results
    string fullMessage = "";
    var first = true;
    await foreach (var content in result)
    {
        if (content.Role.HasValue && first)
        {
            Console.Write("Assistant > ");
            first = false;
        }
        Console.Write(content.Content);
        fullMessage += content.Content;
    }
    Console.WriteLine();
    // 加入 AI 回應到對話歷史
    history.AddAssistantMessage(fullMessage);

    // Get user input again
    Console.Write("User > ");
}

Console.WriteLine("\n Bye!");

程式碼說明

這段程式碼示範如何讓 Semantic Kernel 聊天機器人具備「短期記憶」功能,讓 AI 能根據上下文持續對話。重點說明如下:

  1. Kernel 建立與聊天服務取得

    • 透過 Kernel.CreateBuilder() 並設定 OpenAI 參數,建立 Kernel 物件。
    • 使用 kernel.GetRequiredService<IChatCompletionService>() 取得聊天服務,方便後續串流回應。
  2. 對話歷史管理

    • 宣告 ChatHistory history = [],用來儲存每一輪對話的訊息(包含使用者與 AI 回應的訊息)。
    • 每次使用者輸入後,呼叫 history.AddUserMessage(userInput) 將訊息加入對話歷史記錄。
  3. 串流式 AI 回應

    • chatCompletionService.GetStreamingChatMessageContentsAsync(history, kernel: kernel) 取得 AI 回應,支援逐字串流顯示。
    • 使用 await foreach 方式即時顯示 AI 回覆內容,提升使用者體驗。
  4. AI 回應納入歷史

    • 將 AI 回應的內容組合後,呼叫 history.AddAssistantMessage(fullMessage),將訊息加入對話歷史記錄,讓後續對話能參考上下文。
  5. 結束條件

    • 當使用者輸入為空或 "exit" 時,跳出 for 迴圈結束對話。
  6. 優點與應用

    • 這種設計讓 AI 能根據多輪對話內容,產生更連貫、貼近上下文的回應。
    • 適合用於客服、諮詢等需要多輪互動的應用場景。
    • 有需要時,也可以持久化對話歷史,讓 AI 記住更長期的資訊,下次使用者進行對話時能夠調用。
    • 但需要注意對話歷史過長可能會超過模型的上下文限制,需要適時裁剪,這可以透過設定最大歷史長度來實現,當超過限制時,自動刪除最舊的對話記錄或是對歷史記錄進行摘要濃縮。有在使用 Claude Code的開發者也會發現 cc 每過一段時間就會自動幫你把對話歷史做摘要,這也是一種解決方案。

這個範例讓你體驗 Semantic Kernel 如何輕鬆實現短期記憶對話,為打造更智慧的 AI Agent 奠定基礎。

範例結果

User > 柴犬和秋田犬的外型很類似,從哪些特徵可以比較輕易的識別出來
Assistant > 柴犬和秋田犬的確外型有不少相似之處,但還是有幾個明顯的特徵可以較容易區分牠們:

1. **體型大小**
   - **柴犬(Shiba Inu)**屬中小型犬,肩高約35-41公分,體重約8-10公斤,體型較輕盈。
   - **秋田犬(Akita Inu)**屬大型犬,肩高約58-70公分,體重30-50公斤,體格壯碩、肌肉發達。

2. **臉型與表情**
   - **柴犬**臉型偏尖,五官集中,表情比較靈巧、銳利,有點像狐狸。
   - **秋田犬**臉型寬厚、較圓,表情沉穩,毛髮較豐厚,大眼睛給人比較溫和的感覺。

3. .....(以下省略1000字)


User > 你說二者的體重有差異,但你覺得一般人可以一眼看出體重嗎?

Assistant > 你問得很好!一般人其實在**現場看照片或影片時,確實不太可能直接判斷出體重的數字**,也很難一眼精確分辨兩隻狗狗到底有多重。

但是**體重差異會直接反映在體型大小上**,這其實才是直觀且容易分辨的——

### 怎麼一眼看出差別?
- **秋田犬體積就是「大上一號」**:成年秋田犬站在旁邊,可能比十歲小朋友還高,和成年人的膝蓋差不多;柴犬大多僅到小朋友膝蓋的位置。
- **柴犬比較嬌小精幹**:光用圖片看(尤其單獨、沒有標尺對比時),確實不容易判斷大小;但如果同時有兩犬站一起,或與人同框,就會很明顯。
- **抽象判斷**:秋田犬感覺「壯、圓、厚大」,柴犬則是「小、銳、俐落」。

### 除非同框,不然怎麼分?
當然,假如只看到一隻狗的照片、畫面裡沒有其他東西做比例參考,**單單憑體型確實還是不夠直觀**。
所以這時,**臉型(秋田圓、柴尖)、耳朵大小、毛蓬鬆度(秋田更厚密)、尾巴粗細**等五官/身體特徵,就變成更實用的判斷依據。

---

**小結論:**
- 體型(身形大小)是差異最大、最明顯的特點,但比較常需要有參照物(例如人),不然單看照片很容易混淆。
- 如果只有單獨照片,那就多留意臉型、表情、毛量、耳朵尾巴等細節。

希望這樣讓你更有信心現場分辨!

可以看在第二個提問中,針對「一般人是否能一眼看出體重」這個問題,AI能夠根據上下文理解使用者的疑問,識別是對象是前面提到的柴犬和秋田犬,進而給出更貼近的解答。這就是短期記憶對話的威力!

從這篇開始

第一天的內容用二個基礎範例來說明 Semantic Kernel 的使用。在這個系列文章中,我希望能透過有限的文字及範例,一起來認識 Semantic Kernel,打造出屬於自己的 AI Agent。

我們下篇見!


下一篇
Day 2:Function Calling — Semantic Kernel 如何讓 AI Agent 動起來?
系列文
當 .NET 遇見 AI Agents:用 Semantic Kernel × MCP 打造智慧協作應用3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言