iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
生成式 AI

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

Day 5: Semantic Kernel 實戰:詳解 Tools 的撰寫與掛載技巧

  • 分享至 

  • xImage
  •  

延續昨天的 AI Agent Tools 設計原則,今天將透過實際的程式碼範例深入實作層面,介紹如何使用 Semantic Kernel 撰寫 Tools(工具函式)並將其掛載到 Kernel 中,以實現 AI Agent 中不可或缺的 Function Calling 。

Semantic Kernel Function Calling 概述

在前面的文章內容裡,大家應該已經理解到 AI Agent 之所以能力強大,很大一部份原因取決於其強大的 Function Calling 功能。它允許 AI 模型呼叫開發者定義的函式,實現與現有程式碼的互動,進而自動化商業流程、產生程式碼片段等多種應用。然而實際 LLM 的 Function Calling 機制通常相當複雜,涉及函式定義的序列化、參數傳遞、錯誤處理等多個環節。這些細節當然開發者可以自行實作,但通常較為繁瑣且容易出錯。而 Semantic Kernel 簡化了 Function Calling 的開發及使用過程,自動處理函式描述和參數向模型的傳遞,以及模型與程式碼之間的雙向溝通。

先釐清:Semantic Kernel 的 Function Calling 在幹嘛?

Semantic Kernel 會把你註冊進 Kernel 的函式(含參數)序列化成 JSON Schema,連同聊天過程送給模型;模型回來的不是話、而是「要呼叫哪個函式+參數」,Semantic Kernel 便會根據這些資訊把 Function 叫起來,並把結果再塞回聊天歷史,直到模型給出最終回答或迭代上限為止,而這個流程可以全自動,也可以半自動(手動調用)

基礎 Tools 撰寫方式

1. 使用 KernelFunction 屬性

在 Semantic Kernel 中,最簡單的方式是使用 [KernelFunction] 屬性並使用[Description] 標註所寫的 C# 方法用途與參數描述,這個關鍵的標註會被 Semantic Kernel 用來生成函式的 JSON Schema,並提供給 LLM 模型使用,是一個非常重要的步驟。以下是一個簡單的天氣查詢工具範例:

public class WeatherServicePlugin
{
    private static readonly Dictionary<string, int> CityTemperatures = new Dictionary<string, int>
        {
            { "Taipei", 28 },
            { "Kaohsiung", 31 },
            { "Taichung", 27 },
            { "Tainan", 30 },
            { "Hsinchu", 26 }
        };

    [KernelFunction]
    [Description("Retrieves the today temperature of the city.")]
    public int Get_Today_Temperature(
        [Description("The name of the city to get the temperature for.The city names in the weather data are in English")]
            string city)
    {
        if (CityTemperatures.TryGetValue(city, out int temp))
        {
            return temp;
        }
        // 模擬未知城市的溫度
        return 25;
    }
}

這個範例中,請注意到關於 city 參數的描述特別強調「城市名稱是英文」,這是因為模型在理解參數時,會依賴這些描述來決定如何填入參數值,清楚且具體的描述能夠大幅提升模型正確呼叫函式的機率。所以當使用者詢問「台北今天幾度?」時,模型能夠理解並自動將 city 參數設為 "Taipei"。是的,它會自動把「台北」翻譯成「Taipei」,這就是 LLM 的強大之處。

2. 複雜參數類型的處理

Semantic Kernel 也支援複雜的參數類型,包括陣列或是自定義物件。以下是一個企業人力資源管理系統的範例:

public enum EmployeeLevel
{
    Junior,
    Senior,
    Lead
}

public enum Department
{
    Engineering,
    Sales,
    Marketing
}

public enum SkillCategory
{
    Programming,
    ProjectManagement,
    Communication,
    Leadership
}

public class EmployeeSearchCriteria
{
    public List<Department> Departments { get; set; } = new();
    public List<EmployeeLevel> Levels { get; set; } = new();
    public List<string> Skills { get; set; } = new();
    public int? MinExperience { get; set; }
    public int? MaxExperience { get; set; }
    public bool? IsRemoteWorker { get; set; }
}

public class HRManagementPlugin
{
    private static readonly List<Employee> _employees = GenerateEmployeeData();

    /// <summary>
    /// 產生模擬員工資料
    /// </summary>
    private static List<Employee> GenerateEmployeeData()
    {
        var random = new Random(42); // 固定種子以獲得一致的結果
        var employees = new List<Employee>();

        var names = new[] { "王小明", "李小華", "張大偉", "陳美玲", "林志強", "黃淑芬", "吳建國", "劉雅婷",
                            "蔡政宏", "鄭惠美", "楊承翰", "許雅雯", "馬志豪", "周美惠", "趙家豪", "錢雅萍",
                            "孫建華", "李志明", "王美玲", "陳大偉", "林小華", "張雅婷", "吳志強", "劉美惠",
                            "蔡建國", "鄭志豪", "楊雅雯", "許承翰", "馬美玲", "周志強" };

        var departments = Enum.GetValues<Department>();
        var levels = Enum.GetValues<EmployeeLevel>();
        var skills = Enum.GetValues<SkillCategory>();

        for (int i = 0; i < 30; i++)
        {
            var employeeSkills = new List<SkillCategory>();
            var skillCount = random.Next(1, 4); // 每人1-3項技能

            for (int j = 0; j < skillCount; j++)
            {
                var skill = skills[random.Next(skills.Length)];
                if (!employeeSkills.Contains(skill))
                    employeeSkills.Add(skill);
            }

            employees.Add(new Employee
            {
                Id = $"EMP{(i + 1):D3}",
                Name = names[i],
                Department = departments[random.Next(departments.Length)],
                Level = levels[random.Next(levels.Length)],
                Skills = employeeSkills,
                ExperienceYears = random.Next(1, 16), // 1-15年經驗
                IsRemoteWorker = random.NextDouble() > 0.7, // 30%機率為遠端工作者
                JoinDate = DateTime.Now.AddDays(-random.Next(365 * 10)) // 過去10年內加入
            });
        }

        return employees;
    }

    [KernelFunction]
    [Description("根據複雜條件搜尋員工,支援多維度篩選")]
    public async Task<string> SearchEmployees(
        [Description("要搜尋的部門陣列")] Department[]? departments = null,
        [Description("員工職級陣列")] EmployeeLevel[]? levels = null,
        [Description("必備技能陣列")] SkillCategory[]? requiredSkills = null,
        [Description("最少工作經驗年數")] int minExperience = 0,
        [Description("最多工作經驗年數,不限制則輸入-1")] int maxExperience = -1,
        [Description("是否限定遠端工作者,true=僅遠端,false=僅非遠端,空白=不限制")] string remoteOnly = "")
    {
        // 處理陣列參數並轉換為 List
        var departmentList = departments?.ToList() ?? new List<Department>();
        var levelList = levels?.ToList() ?? new List<EmployeeLevel>();
        var skillList = requiredSkills?.ToList() ?? new List<SkillCategory>();

        bool? isRemoteWorker = null;
        if (!string.IsNullOrWhiteSpace(remoteOnly))
        {
            if (bool.TryParse(remoteOnly, out var remote))
                isRemoteWorker = remote;
        }

        // 模擬搜尋邏輯的延遲
        await Task.Delay(100);

        // 實際執行員工搜尋
        var query = _employees.AsQueryable();

        // 部門篩選
        if (departmentList.Any())
        {
            query = query.Where(e => departmentList.Contains(e.Department));
        }

        // 職級篩選
        if (levelList.Any())
        {
            query = query.Where(e => levelList.Contains(e.Level));
        }

        // 技能篩選(員工必須具備所有要求的技能)
        if (skillList.Any())
        {
            query = query.Where(e => skillList.All(skill => e.Skills.Contains(skill)));
        }

        // 經驗年數篩選
        query = query.Where(e => e.ExperienceYears >= minExperience);
        if (maxExperience != -1)
        {
            query = query.Where(e => e.ExperienceYears <= maxExperience);
        }

        // 遠端工作篩選
        if (isRemoteWorker.HasValue)
        {
            query = query.Where(e => e.IsRemoteWorker == isRemoteWorker.Value);
        }

        var matchedEmployees = query.ToList();

        var searchCriteria = new EmployeeSearchCriteria
        {
            Departments = departmentList,
            Levels = levelList,
            Skills = skillList.Select(s => s.ToString()).ToList(),
            MinExperience = minExperience,
            MaxExperience = maxExperience == -1 ? null : maxExperience,
            IsRemoteWorker = isRemoteWorker
        };

        // 產生搜尋結果
        var searchResults = new
        {
            criteria = searchCriteria,
            totalFound = matchedEmployees.Count,
            matchedEmployees = matchedEmployees.Select(e => new
            {
                id = e.Id,
                name = e.Name,
                department = e.Department.ToString(),
                level = e.Level.ToString(),
                skills = e.Skills.Select(s => s.ToString()).ToArray(),
                experienceYears = e.ExperienceYears,
                isRemoteWorker = e.IsRemoteWorker,
                joinDate = e.JoinDate.ToString("yyyy-MM-dd")
            }).ToArray(),
            searchTimestamp = DateTime.UtcNow
        };

        return System.Text.Json.JsonSerializer.Serialize(searchResults, new System.Text.Json.JsonSerializerOptions
        {
            WriteIndented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        });
    }

    [KernelFunction]
    [Description("查看所有員工的基本資訊")]
    public async Task<string> GetAllEmployees()
    {
        await Task.Delay(50);

        var allEmployeesInfo = new
        {
            totalEmployees = _employees.Count,
            employees = _employees.Select(e => new
            {
                id = e.Id,
                name = e.Name,
                department = e.Department.ToString(),
                level = e.Level.ToString(),
                skills = e.Skills.Select(s => s.ToString()).ToArray(),
                experienceYears = e.ExperienceYears,
                isRemoteWorker = e.IsRemoteWorker,
                joinDate = e.JoinDate.ToString("yyyy-MM-dd")
            }).ToArray(),
            timestamp = DateTime.UtcNow
        };

        return System.Text.Json.JsonSerializer.Serialize(allEmployeesInfo, new System.Text.Json.JsonSerializerOptions
        {
            WriteIndented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        });
    }
}

3. 非同步方法支援

Semantic Kernel 完全支援非同步方法操作:

public class WeatherServicePlugin
{
    private static readonly Dictionary<string, int> CityTemperatures = new Dictionary<string, int>
    {
        { "Taipei", 28 },
        { "Kaohsiung", 31 },
        { "Taichung", 27 },
        { "Tainan", 30 },
        { "Hsinchu", 26 }
    };

    [KernelFunction]
    [Description("Retrieves the today temperature of the city.")]
    public async Task<int> Get_Today_Temperature(
        [Description("The name of the city to get the temperature for.The city names in the weather data are in English")]
            string city)
    {
        // 模擬非同步操作,例如從 API 獲取天氣資料
        await Task.Delay(500); // 模擬 500ms 的網路延遲

        if (CityTemperatures.TryGetValue(city, out int temp))
        {
            return temp;
        }
        // 模擬未知城市的溫度
        return 25;
    }
}

Tools 的掛載與管理

1. 基本掛載方式

將撰寫好的 Tools 掛載到 Kernel 非常簡單,可以使用 AddFromType<T> 方法,這裡要特別說明的是,Semantic Kernel 支援為每個 Plugin 指定 Plugin Name,類似於命名空間(namespace)的概念,這樣可以幫助模型識別不同類型的工具,避免命名衝突並提升工具的可發現性。另外特別要提醒的是,僅掛載必要的 Plugin,避免讓模型面對過多選擇,這會增加模型選錯工具的風險。

// 建立 Kernel 並掛載 Plugin
Kernel kernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion(
        apiKey: Config.OpenAI_ApiKey,
        modelId: Config.ModelId)
    .Build();

// 從類型掛載 Plugin,並指定 Plugin Name 
kernel.Plugins.AddFromType<WeatherServicePlugin>("WeatherService");
kernel.Plugins.AddFromType<HRManagementPlugin>("HRManagement");

2. 以角色導向的 Plugin 管理

根據 Anthropic 的建議,當一個 Agent 掛載所有工具時,容易造成 LLM 的選擇困難,進而增加選錯工具的風險。所以比較好的做法是,應該根據 Agent 的角色來選擇合適的工具:

public static class PluginManager
{
    public static void ConfigureForCustomerService(Kernel kernel)
    {
        // 客服專員只需要客戶相關工具集
        kernel.Plugins.AddFromType<CustomerServicePlugin>("CustomerService");
        kernel.Plugins.AddFromType<OrderManagementPlugin>("OrderManagement");
    }

    public static void ConfigureForHRService(Kernel kernel)
    {
        // 人力資源服務工具集
        kernel.Plugins.AddFromType<HRManagementPlugin>("HRManagement");
    }

    public static void ConfigureForSalesAgent(Kernel kernel)
    {
        // 銷售代表需要銷售相關工具集
        kernel.Plugins.AddFromType<CustomerServicePlugin>("CustomerService");
        kernel.Plugins.AddFromType<SalesPlugin>("Sales");
        kernel.Plugins.AddFromType<ProductCatalogPlugin>("ProductCatalog");
    }
}

// 使用方式
Kernel customerServiceKernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion(apiKey: apiKey, modelId: modelId)
    .Build();

PluginManager.ConfigureForCustomerService(customerServiceKernel);

整合應用範例

把上述的概念整合起來,成為一個完整 Agent 服務範例:

/// <summary>
/// AI Agent 管理器
/// </summary>
public static class AgentManager
{
    /// <summary>
    /// 啟動客戶服務助手
    /// </summary>
    /// <returns></returns>
    public static async Task RunCustomerServiceAgent()
    {
        var agent = new CustomerServiceAgent();
        await agent.ProcessCustomerQueryAsync();
    }

    /// <summary>
    /// 啟動天氣服務助手
    /// </summary>
    /// <returns></returns>
    public static async Task RunWeatherServiceAgent()
    {
        var agent = new WeatherServiceAgent();
        await agent.ProcessWeatherQueryAsync();
    }

    /// <summary>
    /// 啟動人力資源助手
    /// </summary>
    /// <returns></returns>
    public static async Task RunHRManagementAgent()
    {
        var agent = new HRManagementAgent();
        await agent.ProcessHRQueryAsync();
    }

    /// <summary>
    /// 啟動訂單管理助手
    /// </summary>
    /// <returns></returns>
    public static async Task RunOrderManagementAgent()
    {
        var agent = new OrderManagementAgent();
        await agent.ProcessOrderQueryAsync();
    }
}

/// <summary>
/// Plugin 管理器,用於配置不同 AI Agent 所需的工具集
/// </summary>
public static class PluginManager
{
    public static void ConfigureForCustomerService(Kernel kernel)
    {
        // 客服專員只需要客戶相關工具集
        kernel.Plugins.AddFromType<CustomerServicePlugin>("CustomerService");
        kernel.Plugins.AddFromType<OrderManagementPlugin>("OrderManagement");
    }
    public static void ConfigureForWeatherService(Kernel kernel)
    {
        // 天氣服務相關工具集
        kernel.Plugins.AddFromType<WeatherServicePlugin>("WeatherService");
    }

    public static void ConfigureForHRService(Kernel kernel)
    {
        // 人力資源服務工具集
        kernel.Plugins.AddFromType<HRManagementPlugin>("HRManagement");
    }

    public static void ConfigureForOrderManagement(Kernel kernel)
    {
        // 訂單管理專用工具集
        kernel.Plugins.AddFromType<OrderManagementPlugin>("OrderManagement");
    }
}

/// <summary>
/// 訂單管理插件,提供查詢訂單狀態和處理退換貨申請的功能
/// </summary>
public class OrderManagementPlugin
{
    // 模擬的訂單資料(訂單編號 -> 訂單資訊)
    private readonly Dictionary<string, object> _orders = new Dictionary<string, object>
    {
        { "A001", new { orderId = "A001", status = "已出貨", customerName = "王小明", amount = 1500 } },
        { "A002", new { orderId = "A002", status = "處理中", customerName = "李小華", amount = 2300 } },
        { "A003", new { orderId = "A003", status = "已取消", customerName = "張小美", amount = 980 } },
        { "A004", new { orderId = "A004", status = "已完成", customerName = "陳大明", amount = 3200 } }
    };

    // 查詢訂單狀態的方法
    [KernelFunction]
    [Description("Retrieves the order status by order ID.")]
    public string GetOrderStatus(
        [Description("The ID of the order to retrieve the status for.")]
        string orderId)
    {
        if (string.IsNullOrWhiteSpace(orderId))
        {
            return "訂單編號不可為空";
        }
        if (_orders.TryGetValue(orderId, out var order))
        {
            return System.Text.Json.JsonSerializer.Serialize(order);
        }
        else
        {
            return "查無此訂單";
        }
    }

    [KernelFunction]
    [Description("處理退換貨申請")]
    public string ProcessRefundRequest(
        [Description("訂單編號")] string orderId,
        [Description("退換貨原因")] string reason)
    {
        if (string.IsNullOrWhiteSpace(orderId) || string.IsNullOrWhiteSpace(reason))
        {
            return "訂單編號和退換貨原因不可為空";
        }

        if (_orders.TryGetValue(orderId, out var order))
        {
            var refundRequest = new
            {
                refundId = $"REF-{System.DateTime.Now:yyyyMMddHHmmss}",
                orderId = orderId,
                reason = reason,
                status = "已受理",
                requestTime = System.DateTime.Now
            };
            return $"退換貨申請已受理:{System.Text.Json.JsonSerializer.Serialize(refundRequest)}";
        }
        else
        {
            return "查無此訂單,無法處理退換貨申請";
        }
    }
}

/// <summary>
/// 天氣服務插件,提供查詢今日天氣的功能
/// </summary>
public class WeatherServicePlugin
{
    private static readonly Dictionary<string, int> CityTemperatures = new Dictionary<string, int>
    {
        { "Taipei", 28 },
        { "Kaohsiung", 31 },
        { "Taichung", 27 },
        { "Tainan", 30 },
        { "Hsinchu", 26 }
    };

    [KernelFunction]
    [Description("Retrieves the today temperature of the city.")]
    public async Task<int> Get_Today_Temperature(
        [Description("The name of the city to get the temperature for.The city names in the weather data are in English")]
            string city)
    {
        // 模擬非同步操作,例如從 API 獲取天氣資料
        await Task.Delay(500); // 模擬 500ms 的網路延遲

        if (CityTemperatures.TryGetValue(city, out int temp))
        {
            return temp;
        }
        // 模擬未知城市的溫度
        return 25;
    }
}


Console.Clear();
Console.WriteLine("=== Day 5: Semantic Kernel AI Agent 系統 ===");
Console.WriteLine("請選擇要執行的服務:");
Console.WriteLine("1. 客戶服務助手 - 客戶資訊查詢、訂單處理、退換貨");
Console.WriteLine("2. 天氣服務助手 - 台灣各城市天氣查詢");
Console.WriteLine("3. 人力資源助手 - 員工搜尋、HR管理建議");
Console.WriteLine("4. 訂單管理助手 - 專業訂單查詢與處理");
Console.WriteLine("5. 退出程式");
Console.Write("請輸入選項 (1-5): ");

var choice = Console.ReadLine()?.Trim();

switch (choice)
{
    case "1":
        Console.WriteLine("\n啟動客戶服務助手...");
        await AgentManager.RunCustomerServiceAgent();
        break;
    case "2":
        Console.WriteLine("\n啟動天氣服務助手...");
        await AgentManager.RunWeatherServiceAgent();
        break;
    case "3":
        Console.WriteLine("\n啟動人力資源助手...");
        await AgentManager.RunHRManagementAgent();
        break;
    case "4":
        Console.WriteLine("\n啟動訂單管理助手...");
        await AgentManager.RunOrderManagementAgent();
        break;
    case "5":
        Console.WriteLine("\n謝謝使用 AI Agent 系統,再見!");
        return;
    case "":
        Console.WriteLine("\n⚠️ 請輸入有效的選項數字。");
        break;
    default:
        Console.WriteLine($"\n⚠️ 無效的選項:'{choice}'");
        Console.WriteLine("請輸入 1-5 之間的數字。");
        break;
}

實際看一下運行結果

=== Day 5: Semantic Kernel AI Agent 系統 ===
請選擇要執行的服務:
1. 客戶服務助手 - 客戶資訊查詢、訂單處理、退換貨
2. 天氣服務助手 - 台灣各城市天氣查詢
3. 人力資源助手 - 員工搜尋、HR管理建議
4. 訂單管理助手 - 專業訂單查詢與處理
5. 退出程式
請輸入選項 (1-5): 1

啟動客戶服務助手...
=== 客戶服務助手 ===
您好!我是您的客戶服務助手,可以幫您查詢客戶資訊、訂單狀態和處理退換貨。
輸入 'exit' 結束對話。

您 > 查一下訂單A003的情況
助手 > 訂單A003的情況如下:
- 訂單狀態:已取消
- 訂購人:張小美
- 訂單金額:980元

如果您需要了解更多細節或有其他需求,請隨時告訴我。
您 > A001訂單要退貨
助手 > 請問您申請退貨的原因是什麼呢?這有助於我們為您加快處理並提供更完善的服務。
您 > 品質不良
助手 > 您的A001訂單退貨申請(原因:品質不良)已成功受理,目前狀態為「已受理」。我們將盡快處理您的退貨需求,有最新進度會再通知您。
=== Day 5: Semantic Kernel AI Agent 系統 ===
請選擇要執行的服務:
1. 客戶服務助手 - 客戶資訊查詢、訂單處理、退換貨
2. 天氣服務助手 - 台灣各城市天氣查詢
3. 人力資源助手 - 員工搜尋、HR管理建議
4. 訂單管理助手 - 專業訂單查詢與處理
5. 退出程式
請輸入選項 (1-5): 3

啟動人力資源助手...
=== 人力資源管理助手 ===
您好!我是您的HR助手,可以幫您搜尋符合條件的員工和提供人力資源建議。
輸入 'exit' 結束對話。

您 > 工程部資深人員
HR助手 > 在工程部(Engineering)裡,目前有以下資深人員(Senior):

1. 李小華
   - 技能:Programming、Project Management、Communication
   - 工作經驗:4年
   - 是否遠端:否

2. 李志明
   - 技能:Programming、Leadership
   - 工作經驗:10年
   - 是否遠端:否

如需進一步查看他們的詳細資料或進行後續行動,請隨時告知!
您 > 找有專案管理技能的
HR助手 > 以下是具備專案管理(Project Management)技能的員工:

1. 李小華(工程部|資深|4年經驗|非遠端)
2. 張大偉(工程部|主管|13年經驗|非遠端)
3. 劉雅婷(行銷部|初級|7年經驗|非遠端)
4. 蔡政宏(工程部|主管|1年經驗|非遠端)
5. 楊承翰(行銷部|資深|7年經驗|非遠端)
6. 馬志豪(工程部|主管|3年經驗|非遠端)
7. 趙家豪(行銷部|初級|4年經驗|非遠端)
8. 孫建華(行銷部|主管|1年經驗|非遠端)
9. 吳志強(銷售部|初級|14年經驗|遠端)
10. 劉美惠(銷售部|資深|15年經驗|遠端)
11. 蔡建國(銷售部|主管|4年經驗|遠端)
12. 鄭志豪(工程部|主管|10年經驗|非遠端) 

最佳實務總結

基於 Anthropic 的建議和 Semantic Kernel 的特性,以下是 Tools 開發的建議實務做法:

  1. 精簡但實用:只開發真正需要的工具,避免功能重疊
  2. 清楚的描述:為每個函式和參數提供清楚的描述
  3. 錯誤處理:提供具體且可操作的錯誤訊息
  4. 回傳優化:保持回傳資料的簡潔和相關性
  5. 命名規範:使用一致且描述性的命名方式
  6. 角色導向:根據 Agent 角色選擇合適的工具集

結語

透過今天的詳細介紹,深入了解如何使用 Semantic Kernel 撰寫和掛載 Tools。從基礎的函式標註到複雜參數類型的處理,再到根據 Agent 角色選擇合適的工具集管理,這些技巧有助於打造更強大及更可靠的 AI Agent。

明天將會探討 Function Choice Behavior 的詳細配置,看如何精確控制 AI Agent 的函式選擇和呼叫行為。"自動"就意味著會有出錯的風險,了解如何調整這些行為將有助於提升 Agent 的穩定性和降低風險。


上一篇
Day 4: 從 Anthropic 經驗看 AI Agent 的 Tools 設計與挑戰
下一篇
Day 6: Semantic Kernel Function Choice Behavior 深度解析:精確控制 AI Agent 的函式呼叫行為
系列文
當 .NET 遇見 AI Agents:用 Semantic Kernel × MCP 打造智慧協作應用7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言