Semantic Kernel中Function Calling

ChatCompletion最强大功能之一就是能够从模型中调用函数。这让你可以基于现有代码创建聊天机器人,实现自动化业务流程、生成代码片段等功能。

在 Semantic Kernel 中,函数调用的过程被大幅简化:它会自动将你的函数和参数描述给模型,并处理模型与代码之间的沟通。了解这一机制对优化代码、充分发挥函数调用的优势至关重要。

自动函数调用的工作原理

函数调用的工作原理可以分为以下几个步骤:

步骤描述
1. 序列化函数

将所有可用的函数(及其输入参数)使用 JSON Schema 进行序列化。

2. 发送消息和函数到模型

将序列化后的函数(以及当前聊天历史)作为输入发送到模型。

3. 模型处理输入

模型处理输入,并生成响应。响应可以是普通的聊天消息,也可以是一个或多个函数调用。

4. 处理模型返回的响应

如果返回的是普通聊天消息,直接返回给调用方。如果返回的是函数调用,则解析出函数名称及其参数。

5. 执行函数

使用提取出的函数名称和参数,在 Kernel 中调用相应的函数。

6. 返回函数结果

函数调用的结果会作为聊天历史的一部分发送回模型。然后,步骤 2-6 会重复执行,直到模型返回普通的聊天消息或达到最大迭代次数。

如下Function Calling的流程

Semantic Kernel function calling

模拟函数调用

除了用户(User)、助手(Assistant)和系统(System)这几种角色外,你还可以使用工具(Tool)角色来模拟函数调用。这对于让 AI 学习如何使用插件,或者在对话中提供额外的背景信息非常有帮助。比如,如果你想让 AI 了解用户的信息(比如过敏情况),但又不希望用户每次都手动输入,也不想让 AI 反复询问,你可以用 工具(Tool) 角色直接把这些信息提供给 AI。

下面是一个示例,我们通过模拟调用“用户插件”,把用户的过敏信息直接提供给助手,让 AI 知道用户的饮食限制。

模拟函数调用在提供当前用户的详细信息时特别有用。如今的大型语言模型(LLM)对用户信息非常敏感。即使你在系统消息中提供了用户信息,LLM 仍然可能选择忽略它。但如果你通过用户消息(User Message)或工具消息(Tool Message)提供这些信息,LLM 更有可能正确使用它。

// 添加一个来自助手的模拟函数调用
chatHistory.Add(
    new() {
        Role = AuthorRole.Assistant,
        Items = [
            new FunctionCallContent(
                functionName: "get_user_allergies", // 函数名称:获取用户过敏信息
                pluginName: "User", // 插件名称:用户(User)
                id: "0001", // 调用 ID
                arguments: new () { {"username", "laimonisdumins"} } // 参数:用户名 laimonisdumins
            ),
            new FunctionCallContent(
                functionName: "get_user_allergies", // 函数名称:获取用户过敏信息
                pluginName: "User", // 插件名称:用户(User)
                id: "0002", // 调用 ID
                arguments: new () { {"username", "emavargova"} } // 参数:用户名 emavargova
            )
        ]
    }
);

// 添加来自工具角色的模拟函数返回结果
chatHistory.Add(
    new() {
        Role = AuthorRole.Tool,
        Items = [
            new FunctionResultContent(
                functionName: "get_user_allergies", // 函数名称:获取用户过敏信息
                pluginName: "User", // 插件名称:用户(User)
                id: "0001", // 调用 ID
                result: "{ \"allergies\": [\"peanuts\", \"gluten\"] }"// 返回结果:用户对花生和麸质过敏
            )
        ]
    }
);
chatHistory.Add(
    new() {
        Role = AuthorRole.Tool,
        Items = [
            new FunctionResultContent(
                functionName: "get_user_allergies", // 函数名称:获取用户过敏信息
                pluginName: "User", // 插件名称:用户(User)
                id: "0002", // 调用 ID
                result: "{ \"allergies\": [\"dairy\", \"soy\"] }"// 返回结果:用户对乳制品和大豆过敏
            )
        ]
    }
);

在模拟工具(Tool)返回结果时,必须始终提供与之对应的函数调用 ID。这是让 AI 理解返回结果上下文的重要信息。某些大型语言模型(LLM),比如 OpenAI,如果缺少 ID 或 ID 与函数调用不匹配,可能会抛出错误。

函数调用的示例

C# 版的函数调用示例,模拟了一个书籍订购插件(OrderBookPlugin),它提供了添加书籍到购物车、从购物车中移除书籍、获取购物车内容等功能。

配置Azure OpenAI

builder.Services.AddSingleton(sp =>
{
    var kernel = Kernel.CreateBuilder()
     .AddAzureOpenAIChatCompletion(
         deploymentName: "gpt-4.1", // 你在 Azure 上配置的模型部署名称
         endpoint: "",
         apiKey: ""
     ).Build();
    var logger=kernel.LoggerFactory;
    kernel.ImportPluginFromType<TimePlugin>("Time");
    kernel.ImportPluginFromType<OrderBookPlugin>("OrderBook");
    return kernel;
});

新增 OrderBookPlugin

public classOrderBookPlugin
    {
        privatereadonly List<Book> _bookMenu;
        privatereadonly Dictionary<Guid, Cart> _userCarts;
        privatereadonly List<Cart> _carts;
        privatereadonly List<BookTag> _bookTags;

        // 模拟的数据
        public OrderBookPlugin()
        {
            // 假设的书籍菜单
            _bookMenu = new List<Book>
            {
                new Book { Id = 1, Name = "自控力", Price = 8.99 },        // The Power of Self-Control -> 自控力
                new Book { Id = 2, Name = "人类简史", Price = 9.99 },       // Sapiens: A Brief History of Humankind -> 人类简史
                new Book { Id = 3, Name = "活着", Price = 10.99 },          // To Live -> 活着
                new Book { Id = 4, Name = "百年孤独", Price = 12.99 },      // One Hundred Years of Solitude -> 百年孤独
                new Book { Id = 5, Name = "围城", Price = 11.99 },          // Fortress Besieged -> 围城
                new Book { Id = 6, Name = "平凡的世界", Price = 10.49 }     // Ordinary World -> 平凡的世界
            };
            // 模拟的书籍标签
            _bookTags = new List<BookTag>
            {
                new BookTag { Id = 1, Name = "励志" },     // Motivational -> 励志
                new BookTag { Id = 2, Name = "历史" },     // History -> 历史
                new BookTag { Id = 3, Name = "小说" },     // Novel -> 小说
                new BookTag { Id = 4, Name = "哲学" },     // Philosophy -> 哲学
                new BookTag { Id = 5, Name = "社会学" },   // Sociology -> 社会学
                new BookTag { Id = 6, Name = "文学" }      // Literature -> 文学
            };

            // 模拟用户购物车
            _userCarts = new Dictionary<Guid, Cart>();
            _carts = new List<Cart>();
        }

        [KernelFunction("get_book_menu")]
        public Task<List<Book>> GetBookMenuAsync()
        {
            return Task.FromResult(_bookMenu);
        }

        [KernelFunction("add_book_to_cart")]
        [Description("添加书籍到用户购物车;返回新添加的书籍并更新购物车")]
        public Task<CartDelta> AddBookToCart(
            [Description("书名,例如《自控力》")] string name,
            [Description("数量")] int quantity = 1)
        {
            // 假设用户只有一个购物车,并使用随机 ID 来生成 cartId
            Guid cartId = Guid.NewGuid();
            var book = _bookMenu.FirstOrDefault(b => b.Name == name);

            if (book == null) return Task.FromResult(new CartDelta { Success = false });

            var cart = new Cart
            {
                Id = cartId,
                Items = new List<CartItem>
                {
                    new CartItem { Book = book, Quantity = quantity }
                },
                TotalPrice = book.Price * quantity
            };

            _userCarts[cartId] = cart;
            _carts.Add(cart);

            return Task.FromResult(new CartDelta { Success = true, Cart = cart });
        }

        [KernelFunction("remove_book_from_cart")]
        public Task<RemoveBookResponse> RemoveBookFromCart(int bookId)
        {
            // 假设我们从用户的购物车移除指定的书籍
            var cart = _carts.FirstOrDefault(c => c.Items.Any(i => i.Book.Id == bookId));
            if (cart == null) return Task.FromResult(new RemoveBookResponse { Success = false });

            var item = cart.Items.FirstOrDefault(i => i.Book.Id == bookId);
            if (item != null)
            {
                cart.Items.Remove(item);
                cart.TotalPrice -= item.Book.Price * item.Quantity;
            }

            return Task.FromResult(new RemoveBookResponse { Success = true });
        }

        [KernelFunction("get_book_from_cart")]
        [Description("返回用户购物车中书籍的详情; 使用此方法,而不是依赖之前的消息,因为购物车可能已经发生了变化.")]
        public Task<Book> GetBookFromCart(int bookId)
        {
            var cartItem = _carts.SelectMany(c => c.Items).FirstOrDefault(i => i.Book.Id == bookId);
            return Task.FromResult(cartItem?.Book);
        }

        [KernelFunction("get_cart")]
        [Description("返回用户当前的购物车,包含总价和购物车中的所有商品.")]
        public Task<List<Cart>> GetCart()
        {
            // 这里假设只返回第一个用户的购物车
            return Task.FromResult(_carts);
        }

        [KernelFunction("checkout")]
        [Description("这个函数主要负责将用户购物车中的商品进行结算.")]
        public Task<CheckoutResponse> Checkout()
        {
            // 模拟结账过程
            double totalAmount = 0;
            _carts.ForEach(cart => totalAmount += cart.TotalPrice);

            if (_carts == null) return Task.FromResult(new CheckoutResponse { Success = false });
            _carts.Clear(); // 清空购物车
            return Task.FromResult(new CheckoutResponse { Success = true, TotalAmount = totalAmount });
        }
    }

Function Calling 演示

Semantic Kernel 中的函数调用

Semantic Kernel(SK)提供了一个强大的函数调用机制,它封装了与模型交互的复杂细节,让你可以更专注于业务逻辑的实现。SK 自动处理函数注册、参数绑定、模型调用等步骤,大大简化了开发流程。

这里假设你在使用 OpenAI Function Calling(或 Azure OpenAI)时,手动处理每一步,并对比 SK 替你做了哪些工作。

原始流程步骤 vs Semantic Kernel 封装对比

步骤

Function Calling 原始做法(自己做)

SK 帮你封装

1. 函数注册与描述构建

• 手动用 JSON 构建每个函数的 schema(name、description、parameters)
• 指定类型、必填字段、枚举等

• 自动反射 [KernelFunction] 和参数描述
• 构建 JSON Schema

2. 组织聊天历史

• 自己构造 messages 列表:用户消息、助手回复、函数调用请求

• 用 ChatHistory 自动管理
• 自动记录调用内容

3. 将函数定义与聊天记录发给模型

• 把函数定义 + 聊天记录打包到 HTTP 请求体
• 发送给 /v1/chat/completions

•GetChatMessageContentAsync(...) 一句完成

4. 解析模型返回,判断是否为函数调用

• 检查 finish_reason == "function_call"
• 提取函数名、参数 JSON

• 自动检查响应结构
• 自动识别是否为工具调用

5. 参数绑定

• 把 JSON 字符串手动 JsonConvert.Deserialize()
• 处理类型不匹配 / 丢失字段

• 自动转换为 C# 参数类型
• 支持默认值、必填校验

6. 执行对应的函数逻辑

• 自己手动查找函数(基于名称)
• 调用函数 + try/catch

• 自动找到已注册插件函数并调用

7. 错误处理与恢复机制

• 捕获错误
• 生成 fallback 消息
• 通知模型

• 自动捕获异常,返回默认失败响应

8. 函数结果回写到聊天历史

• 自己构造 tool 角色的响应 JSON
• 添加到 messages 中

• 自动封装为 FunctionResultContent 并追加

9. 模型继续生成最终回复

• 再次发送新的消息上下文给模型
• 包含 tool result + prompt

• GetChatMessageContentAsync 自动完成

10. 多轮调用管理

• 手动判断是否继续调用函数或结束对话

• 由 SK 自动判断并进行多轮对话或结束流程

总结

Semantic Kernel 帮你自动处理了函数注册、上下文维护、参数绑定、模型调用、函数执行和结果回写,让你几乎只需要写业务逻辑函数,剩下的复杂流程都由它接管了。这大大简化了函数调用的开发流程,让你可以更专注于业务逻辑的实现,而不是底层的细节处理。

源代码地址

https://github.com/bingbing-gui/AspNetCore-Skill/tree/master/src/09-AI-Agent/SemanticKernel/SK.FunctionCalling

Open AI 原生Function Calling

https://platform.openai.com/docs/guides/function-calling?api-mode=responses#additional-configurations

<think>好的,我需要回答用户关于Semantic Kernel的问题,特别是关于它的GitHub仓库和作为微软开源框架的信息。首先,我得回忆一下Semantic Kernel的基本概念,确保信息准确。Semantic Kernel是微软开发的一个开源框架,用于集成AI模型,比如大型语言模型(LLMs),到应用程序中。它支持多种编程语言,可能包括C#和Python,但需要确认。 接下来,用户提到了GitHub,所以必须提供正确的仓库链接。我需要查找微软官方的Semantic Kernel仓库,确保链接正确无误。通常微软的开源项目会放在GitHub的microsoft组织下,所以可能链接是github.com/microsoft/semantic-kernel。需要验证这一点。 然后,用户想了解它作为开源框架的特点。应该包括主要功能,比如插件架构、规划能力、与AI服务的集成,以及支持的模型类型,比如OpenAI、Azure OpenAI、Hugging Face等。还要提到支持的编程语言,例如C#、Python、Java,但需要确认最新的支持情况,可能Java还在测试阶段。 另外,用户可能对应用场景感兴趣,比如智能助手、自动化流程、数据增强等。需要举几个例子说明其用途。还要确保引用相关资源,如官方文档和GitHub仓库,并在回答末尾添加引用标识。 最后,生成相关问题时要考虑用户可能的后续疑问,比如与LangChain的区别、使用场景、核心功能、支持的模型,以及如何开始使用。确保问题相关且有帮助,引导用户进一步探索。 在回答过程中,需要遵循用户提供的格式要求,使用正确的LaTeX语法,虽然当前内容可能不需要数学公式,但需保持警惕。同时,用中文回答,结构清晰,分点说明,保持自然流畅。检查是否有任何错误或过时信息,比如框架的最新版本或新增功能,确保回答的准确性和可靠性。</think>### Semantic Kernel 简介 Semantic Kernel(SK)是微软推出的开源框架,旨在帮助开发者将大型语言模型(LLMs)等AI能力集成到应用程序中。它提供了一套工具和接口,支持灵活的任务规划、上下文管理以及与外部服务的交互。 ### GitHub 与开源信息 - **GitHub仓库**:官方仓库为 [microsoft/semantic-kernel](https://github.com/microsoft/semantic-kernel),包含代码、文档和示例[^1]。 - **框架特点**: 1. **插件架构**:支持扩展功能模块(如API、数据库),并通过自然语言调用。 2. **规划能力**:将复杂任务分解为可执行的子任务,例如自动生成代码或数据分析步骤。 3. **多模型支持**:兼容OpenAI、Azure OpenAI、Hugging Face等模型。 4. **多语言支持**:支持C#、Python、Java(实验阶段)等语言。 ### 应用场景 - **智能助手**:集成LLMs实现对话式交互(如客服机器人)。 - **自动化流程**:自动生成代码、文档或执行数据清洗。 - **知识增强**:结合外部数据源(如企业数据库)提升模型输出的准确性[^2]。 ### 示例代码(Python) ```python import semantic_kernel as sk from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion kernel = sk.Kernel() kernel.add_chat_service("chat", OpenAIChatCompletion("gpt-3.5-turbo", "API_KEY")) skill = kernel.import_semantic_skill_from_directory("skills", "SummarizeSkill") summary = kernel.run(skill["Summarize"], input_text="长文本内容...") print(summary) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值