要讓 AI 代理 (Agent) 從一個只會紙上談兵的「應答者」,進化為能解決實際問題的「行動者」,關鍵在於賦予其與外部世界互動的能力。在這個領域,Microsoft 的開源框架 Semantic Kernel (SK) 提供了一套極為優雅且強大的解決方案。其核心設計哲學是:讓大型語言模型 (LLM) 專注於其擅長的語意推理,而將確定性的操作交給傳統程式碼來穩定執行,並由一個統一的「核心 (Kernel)」來協調這一切。
一、Semantic Kernel 的核心架構
SK 的架構圍繞著三個核心元件:Kernel、Plugins (外掛) 和 Functions (函式)。
Kernel (核心):整個系統的協調者與執行引擎。它負責載入外掛、解析使用者的意圖、規劃執行步驟,並呼叫底層的 AI 服務或原生程式碼。
Plugins (外掛):組織相關「技能」的容器。一個外掛可以包含多個功能相近的函式,例如
WeatherPlugin
可能包含get_current_weather
和get_weather_forecast
兩個函式。Functions (函式):這是 SK 的精髓所在,它將「技能」明確地劃分為兩種型別:
- 語意函式 (Semantic Function):由自然語言提示 (Prompt) 定義。它負責處理需要理解、推理、創造力的任務。本質上是將 LLM 的能力封裝成一個可呼叫的函式。
- 原生函式 (Native Function):由實際的程式碼 (如 Python, C#) 實現。它負責執行具體的、確定性的操作,如 API 呼叫、檔案讀寫或數據計算。
caption=“圖表一:Semantic Kernel 核心架構。此組件圖展示了 Kernel 作為中樞,如何協調語意函式與原生函式,並與外部服務互動。” alt=“一個展示 Semantic Kernel 核心架構的組件圖。Kernel 作為核心,其下有包含語意函式和原生函式的外掛。Kernel 透過呼叫 LLM 來執行語意函式,透過呼叫外部 API 來執行原生函式。”
二、實戰:構建電影推薦代理
讓我們透過一個電影推薦系統的例子,來看看語意函式和原生函式如何無縫協同工作。
步驟 1:建立原生函式 (感知能力)
首先,我們建立一個原生函式,用於從檔案系統中讀取使用者已經看過的電影清單。這個任務需要精確的檔案 I/O 操作,非常適合用原生函式來實現。
# plugins/MySeenMoviesDatabase/database.py
from semantic_kernel.functions import kernel_function
class MySeenMoviesDatabase:
"""一個管理使用者已觀看電影列表的簡單資料庫。"""
@kernel_function(
description="載入使用者已觀看電影的列表",
name="LoadSeenMovies",
)
def load_seen_movies(self) -> str:
"""從 seen_movies.txt 檔案讀取電影清單,並以逗號分隔的字串形式返回。"""
try:
with open("seen_movies.txt", 'r', encoding='utf-8') as file:
lines = [line.strip() for line in file.readlines()]
return ', '.join(lines)
except Exception as e:
return f"讀取檔案時發生錯誤: {e}"
@kernel_function
裝飾器將一個普通的 Python 方法,註冊為 SK 可以識別和調度的「技能」。
步驟 2:建立語意函式 (思考與決策)
接下來,我們建立一個語意函式,它的任務是根據使用者已觀看的電影列表,推薦一部新電影。這個任務需要理解使用者的品味和電影領域的知識,非常適合由 LLM 來完成。
{{/* plugins/Recommender/Recommend_Movies/skprompt.txt */}}
你是一位資深的電影推薦專家,你需要為使用者推薦一部他從未看過的電影。
這是使用者已經看過的電影清單:{{MySeenMoviesDatabase.LoadSeenMovies}}.
請根據這份清單,推薦一部風格相似但使用者未看過的新電影,並只回傳電影名稱。
這個提示範本的關鍵在於 {{MySeenMoviesDatabase.LoadSeenMovies}}
。這是一個嵌入式函式呼叫,它告訴 SK 在執行這個語意函式之前,必須先呼叫名為 MySeenMoviesDatabase.LoadSeenMovies
的原生函式,並將其回傳結果(電影清單字串)填充到提示中。
步驟 3:協調與執行 (Kernel)
最後,我們將這兩個函式作為外掛載入到 Kernel 中,並觸發執行流程。
import semantic_kernel as sk
import asyncio
# 初始化 Kernel 並加入 AI 服務 (此處省略)
kernel = sk.Kernel()
# ...
# 註冊原生函式外掛
from plugins.MySeenMoviesDatabase.database import MySeenMoviesDatabase
kernel.import_plugin_from_object(MySeenMoviesDatabase(), "MySeenMoviesDatabase")
# 註冊語意函式外掛
recommender_plugin = kernel.import_plugin_from_prompt_directory("plugins", "Recommender")
recommend_function = recommender_plugin["Recommend_Movies"]
# 執行函式
async def run_recommendation():
result = await kernel.invoke(recommend_function)
print(f"為您推薦: {result}")
asyncio.run(run_recommendation())
caption="圖表二:語意與原生函式協同流程。此循序圖展示了當 `Recommend_Movies` 語意函式被呼叫時,Kernel 如何協調其內部的原生函式呼叫,最終完成任務。"
alt="一個展示語意與原生函式協同流程的循序圖。使用者呼叫 Kernel,Kernel 執行語意函式,語意函式請求執行原生函式,Kernel 執行原生函式並將結果填入提示,然後呼叫 LLM,最終將結果返回給使用者。"
結論:構建具備行動能力的 AI 代理
Semantic Kernel 透過其獨特的語意與原生函式組合機制,為開發者提供了一個強大而靈活的框架。
- 模組化與可擴充套件性:透過將功能封裝在獨立的外掛中,系統變得易於維護和擴充。
- 職責分離:讓 LLM 專注於其擅長的語言理解和推理,而將確定性的操作交給穩定的程式碼來執行。
- 強大的整合能力:可以輕鬆地將現有的 API 或服務包裝成原生函式,快速賦予 AI 代理新的能力。
在實際開發中,成功的關鍵在於找到語意和原生功能的正確平衡點。掌握這種平衡,將幫助你構建出既能發揮 LLM 創造力,又能保持高效、可靠執行的下一代 AI 應用。