冷眸

微软企业级 AI 框架 Semantic Kernel 详解,LangChain 最强替代

· 冷眸

人工智能 (AI) 正迅速改变我们的世界,从智能助手到自动驾驶汽车,AI 的应用无处不在。对开发者而言,快速构建功能强大的 AI 应用是一个挑战。LangChain 作为一款流行的 AI 开发框架,虽然因其丰富的预制组件而广受欢迎,但由于其抽象再抽象、封装再封装,灵活性非常低,且框架的学习、理解及复杂业务逻辑调试的成本很高,使其难以大规模应用于企业业务线上部署。随着 AI 领域的快速发展,微软开源的 Semantic Kernel 框架应运而生,其独特的优势和强大功能可能使其成为 LangChain 的强力替代品。

经过笔者亲子踩坑,将在本文探讨 Semantic Kernel 的核心特点和优势,提供快速入门实践指导,并与 LangChain 的局限性进行比较,旨在帮助开发者选择并入门这个企业级 AI 应用开发框架。

1、什么是 Semantic Kernel

Semantic kernel是微软开源的一个轻量级的开源开发套件,它让你可以轻松构建 AI Agent,并将最新的 AI 模型集成到您的 C#、Python 或 Java 代码库中;它提供了一个简单易用的 API,可以连接到各种 AI 服务,例如 OpenAI、Azure OpenAI 和 Hugging Face 等;并且它作为业高效的中间件,能够快速交付企级解决方案。

semantic_kernel

1.1、核心特性:

  • Kernel:依赖管理容器,管理所有运行AI 应用所需的服务(Services)和插件(Plugins)。
  • Agent:提供了构建任何类型的 Agent(ChatBot、Copilot、Autonomous)的能力。
  • AI Service:支持主流 AI 服务 OpenAI、Azure OpenAI 和 Hugging Face 等便捷接入。
  • Plugin:一类工具的集合。可便捷实现复杂功能,包括本地代码插件、OpenAPI接口插件等。
  • Planning:将函数调用作为规划(plan)和执行(execute)任务的主要方式。
  • Persona:通设置 System Prompt 实现设定的,而且实现方式也非常的方便。

插件(Plugin)是 Semantic Kernel 的一大特色

1.2、优势

  1. 企业级:Semantic Kernel 为微软及其他500强公司提供了灵活、模块化的 AI 解决方案。并且可以轻松扩展以支持语音和视频等其他模式;在安全与可观察性方面,该框架具备先进的安全性增强功能,包括遥测支持、钩子和过滤器,确保您可以安全地部署大规模的AI解决方案。
  2. 自动化业务流程:Semantic Kernel 可以将A I Prompts 与现有API结合,自动执行业务操作。它作为中间件,将AI模型的请求转换为函数调用(Function Call),并处理其结果。
  3. 高度可扩展:你可以通过添加现有代码作为插件,利用一系列现成的连接器来集成AI服务。它还使用OpenAPI规范,便于与团队中的其他开发人员共享和扩展。
  4. 多语言支持:兼容 C#、Python 和 Java,适合各种技术背景的开发者。
  5. 面向未来设计:Semantic Kernel 设计前瞻,能够随着技术进步轻松连接最新的AI模型。新模型推出时,可以简单地进行替换,无需重写整个代码库。

2、Semantic Kernel 的核心特性

2.1、Kernel is All You Need

Kernel 是 Semantic Kernel 的核心组件。简单来说,Kernel是一个依赖管理容器,管理那些运行你的 AI 应用所需的所有服务(Services)和插件(Plugins)。你需要将 AI 应用所有依赖的服务和插件提供给Kernel,在必要的时候 AI 应用会自动使用它们。

组件 说明
服务 包括所需的 AI 服务(OpenAI)和其他服务(例如日志记录、HTTP 服务等),因此可以支持跨所有语言的服务依赖项引入。
插件 是一类工具函数的集合,被 AI 服务用来执行,例如,搜索插件可以包含google搜索、bing搜索等工具

kernel

由于 Kernel 管理着所有服务和插件,因此 Semantic Kernel 的每个组件都基于它来驱动你的 AI 应用。这非常的 Amazing,因为意味着你作为开发者,有个统一的地方可以配置来管理和监控你的 AI 应用。 比如,当你从 Kernel 调用一个 Prompt 时,Kernel会做如下事情:

  1. 选择最佳的 AI 服务来运行 Prompt。
  2. 使用提供的 Prompt 模板构建 Prompt。
  3. 将 Prompt 发送到 AI 服务。
  4. 接收并解析响应结果。
  5. 最后将来自LLM的响应结果返回给你的应用程序。

在这整个过程中,你可以在上述任何步骤中间执行诸如日志记录、向用户提供更新等任何操作,因为这整个过程都在一个地方(Kernel)完成,名副其实的 Kernel is All You Need

在 Semantic Kernel 中定义Kernel十分方便:

# Initialize the kernel
kernel = Kernel()

2.2、支持任何类型 Agent 构建

Agent 是基于软件的实体,利用 AI 模型为你完成工作。它们被构建用来执行广泛的任务,并根据它们的工作性质有不同的称呼:

  • ChatBot:一个被构建用来回答问题的 Agent 称为“ChatBot”,因为它提供了聊天的体验,这些Agent通常依托于自己的数据,比如公司的文档。
  • Copilot: 一个被构建来协助你完成工作的 Agent 通常被称为“Copilot”。这些 Agent 通过提供建议帮助你完成任务,如编写电子邮件或创建其他办公文档,你可以选择接受或拒绝这些建议。
  • Autonomous:一个被构建来处理重复任务的 Agent 通常被称为“Autonomous”。它可以在不需要干预的情况下响应你的请求并执行操作,它是代替你工作,而不是与你一起工作。

Semantic Kernel 提供了构建任何类型的 Agent 的能力,不需要你是 AI 专家。

在 Semantic Kernel 中,一个 Agent 主要由三个核心构建模块组成:插件(Plugin)、规划器(Planner)和角色(Persona)。 build blocks

2.3、轻松接入 AI 服务

AI 服务是指基于大模型等能力提供的一系列服务,比如ChatGPT等。

Semantic Kernel 的主要功能之一是其能够将不同的AI服务(Services)添加到 Kernel 中,包括 OpenAI、Azure OpenAI 和 Hugging Face 等。这使你可以轻松地替换不同的AI服务,以比较它们的性能,并利用最适合你需求的服务。

比如在接入Azure的GPT-4 Chat服务并添加到 Kernel 的代码如下,是不是非常便捷

# Add Azure OpenAI chat completion
kernel.add_service(
    AzureChatCompletion(
      deployment_name="your_models_deployment_name",
      api_key="your_api_key",
      base_url="your_base_url",
    )
)

在Semantic Kernel中,有目前最主流的 AI 服务的接口。下表是在不同语言上的支持情况。

服务 C# Python Java
Chat completion
Text generation
Embedding generation (Experimental)
Text-to-image (Experimental)
Image-to-text (Experimental)
Text-to-audio (Experimental)
Audio-to-text (Experimental)

2.4、灵活的插件架构

插件(Plugin)是 Semantic Kernel 的一个关键组成部分。如果你已经使用过 ChatGPT 或 Microsoft 365 中的 Copilot 扩展的插件,那么你就已经对它有所了解。通过插件,可以将现有的 API 封装成一个集合,供 AI 应用使用。这使 AI 应用能够执行它原本无法执行的操作。

plugin
s

Semantic Kernel 本质上是在利用函数调用(function calling)功能让 LLM 进行规划(planning)并调用 API。通过函数调用,LLM 可以请求(即调用)特定的函数。Semantic Kernel 随后将请求发送给你定义的合适的函数,并将结果返回给 LLM,以便 LLM 生成最终结果。

在企业业务场景中,插件非常有价值,因为它对一组功能进行了封装,还能很好地与依赖注入配合使用。在插件的构造函数中,可以注入执行插件工作所需的服务(例如,数据库连接、HTTP 客户端等),这在其它不支持插件的架构中难以实现的。

Note

在其它框架中,functions 通常称为“tools”或“actions”。 在 Semantic Kernel 中,仍然使用“functions”来称呼,因为从本质上来看依然是定义在代码中的函数。

下图是一个 Writer 插件示例,里面包含了 ShortPoem 和 StoryGen 等 functions,这些 functions 就是其它框架中的“tools”或“actions”

writer_plungin

在 Semantic Kernel 中定义插件主要有两种方式(在快速实践中会进一步举例说明):使用代码定义 functions 或使用 OpenAPI 规范的 API。通过第一种方式你可以在代码库中编写函数的具体功能和细节,可以利用您已有的依赖和服务。通过第二种方式你可以直接接入 OpenAPI 规范的 API 作为插件,这种方式可以十分方便的跨不同的编程语言和平台共享。

2.5、AI 驱动的插件规划

当 AI 应用拥有多个插件时,就需要一种方法让 AI 应用能够自主的选择合适的一个或多个插件来解决问题,这就需要Planning了。

OpenAI 引入了一种让模型使用函数的原生方法:函数调用(function calling),其他 AI 模型如 Gemini、Claude 和 Mistral 也已采用函数调用作为核心能力。由于这个能力的支持,Semantic Kernel 将函数调用作为规划(plan)和执行(execute)任务的主要方式。

那 Semantic kerenl 是如何知道什么时候该调用什么工具、什么时候调用结束的呢。这其实是通过一个反馈循环来实现的,LLM 会根据用户问请求和插件功能的描述,决定当下需要调用的插件以及具体的函数和参数,Semantic Kernel 会将函数执行结果反馈给 LLM,由 LLM 决定下一步是继续调用插件还是直接回复。

以下就是 Semantic Kernel 基于插件的 AI 驱动的规划过程:

  1. 为每个函数创建一个 JSON 格式的函数描述
  2. 为 LLM 提供聊天历史记录和上述函数描述
  3. 分析 LLM 的响应结果,确定它是回复消息还是调用函数
  4. 如果 LLM 想要调用函数,则需要分析 LLM 响应结果中的函数名称和参数
  5. 使用正确的参数调用函数
  6. 返回函数的结果给 LLM,以便决定它接下来应执行的操作
  7. 重复步骤 2-6,直到 LLM 已完成任务

整个反馈循环的过程全部由 Semantic Kernel 支持。规划(planning)什么时候调用调用哪个函数则完全由 LLM 决定,包括函数调用出错 Semantic Kernel 都做了很好的处理,我们只需要定义好插件即可。

2.6、必不可少角色

角色(Persona)通常称为“元提示(Meta Prompt)”或“指令(Instruction)”,用于定义 AI 应用的响应方式或风格。 通过角色的设定,你可以定义 Agent 规划任务、生成回复以及与用户交互的方式和风格。

在Semantic Kernel中,经常将这些 Prompt 描述为“角色”,主要是通设置 System Prompt 实现设定的,而且实现方式也非常的方便。

chat_history = ChatHistory()
chat_history.add_system_message("""
    You are a technical support specialist for a software company.
    Your primary task is to assist users with technical issues,
    such as installation problems, software bugs, and feature
    inquiries. Use technical jargon appropriately, but ensure that
    explanations are easy to understand. If a problem is too complex,
    suggest advanced troubleshooting steps or escalate to a higher-level
    support team using the escalate tool.
""")

定义有效的角色需要仔细考虑 Agent 的目标受众以及 Agent 即将执行的具体任务。以下是一些角色定义的实践建议:

步骤 描述
明确目标 在创建角色之前,清晰地定义 Agent 需要达成的目标。这包括了解 Agent 将执行的任务以及你期望它与用户的交互方式和风格。
提供示例和场景 为了帮助 Agent 更好地理解其角色,提供展示角色行动的示例和场景,比如示例对话、用户故事等,展示 Agent 在不同情况下是如何响应的。
提供备用方案 指导 Agent 在遇到未知情况或无法提供回答时应该怎么做。比如包括寻求用户帮助、提供替代解决方案、或直接说明没有明确答案等。
迭代和优化 角色设定不是固定不变的,根据用户的反馈和评估情况不断的优化和调整角色设定。

3、放弃LangChain

3.1、LangChain的优缺点

  • LangChain 的优点:
  1. LangChain提供了一系列丰富的组件和工具,初期被认为是一个带来便利的产品。
  2. 易于集成,使得开发人员理论上能快速地从想法转变为可运行的代码。
  3. 在早期阶段,LangChain适合原型制作,可以快速实现对LLM(Large Language Models)的调用和利用。
  • LangChain的缺点:
  1. 不灵活性:随着使用需求的复杂化,LangChain的不灵活性显露出来,导致其变成生产力的阻碍而不是源泉。
  2. 高度抽象:LangChain的高度抽象使代码难以理解和维护,增加了项目的认知负荷。
  3. 抽象层叠:LangChain在其他高级抽象之上叠加了更多的抽象,这增加了调试和理解代码的难度。
  4. 框架缺乏灵活性:LangChain的框架设计很难适应快速变化的人工智能和LLM领域,导致难以融入新的概念和想法。
  5. 缺乏透明度:由于LangChain的许多底层细节被抽象化,导致技术团队无法轻松编写或调整底层代码。

3.2、选择 Semantic Kernel 放弃 Langchain 的理由

  1. Langchain充满抽象概念,不易于学习和理解。
  2. Langchain不够灵活,对复杂业务逻辑的调试成本高,难以大规模企业线上部署。
  3. Semantic Kernel作为一个轻量级开发套件,为企业提供了更加灵活、模块化的解决方案。
  4. Semantic Kernel兼容多种编程语言,并提供了强大的服务和插件管理,有利于企业标准化和规范化AI应用开发。
  5. Semantic Kernel面向未来设计,易于连接最新的AI模型,并且支持新模型的无缝替换

以下是一个关于 Langchain 与 Semantic Kernel 的直观比较:

特性/框架 Langchain Semantic Kernel
设计理念 高度抽象与封装 高度模块化与灵活性
插件架构 抽象概念多,使得代码复杂 灵活、易于集成现有API和服务
系统化配置和管理 复杂多样,让人一头雾水 唯一核心组件Kernel提供所有系统化配置和管理
灵活性 不灵活,与快速变化的AI场景不够协同 面向未来设计,易于适应新技术
学习成本 抽象层次多,学习成本较高 设计直观,初学者门槛低
编程语言支持 不包含详情 支持C#、Python和Java
企业适用性 适用性有限,复杂业务调试困难 专为企业设计,强调模块化和灵活性

微软推出的开源 Semantic Kernel 是一款强大的 AI SDK,携企业级实力和未来洞察,开创智能应用构建新方案。其自动化、模块化设计预示着AI开发的新风向。对替换 LangChain 而言,Semantic Kernel 绝对是理想的选择。

4、Semantic Kernel 快速实践

4.1、接入 ChatGPT 试试

接入 LLM 是进行一系列 Agent 开发的最基本能力,因此我们先接入一个 Azure 提供的 GPT 服务试试,参考Getting Started以及文档,实践了下 LLM 的非流式和流式接入。

import os
import sys

## 因为是使用的.env对LLM进行的配置,所以需要指明.env路径,具体参考Getting Started
notebook_dir = os.path.abspath("")
sys.path.append(notebook_dir)

# 导入Azure服务和服务配置以及聊天历史类
from semantic_kernel.connectors.ai.open_ai import (
    AzureChatCompletion,
    AzureChatPromptExecutionSettings,
)
from semantic_kernel.contents import ChatHistory

# 选择azure服务类型
service_id = "azure"

# 实例化一个Azure服务
azure_chat_service = AzureChatCompletion(
    service_id=service_id,
)

# 对实例进行相关设置
azure_prompt_execution_settings = AzureChatPromptExecutionSettings(
    service_id=service_id,
    max_tokens=150,
    temperature=0.7,
    top_p=1,
    frequency_penalty=0.5,
    presence_penalty=0.5,
)

# 构造Message,通过ChatHistory高效实现
content = "You are an AI assistant that helps people find information."
chat = ChatHistory()
chat.add_system_message(content)
chat.add_user_message("你是谁?")

# 非流式调用并输出
result = await azure_chat_service.get_chat_message_contents(
    chat_history=chat, 
    settings=azure_prompt_execution_settings
)
print(str(result[-1]))
你好!我是一个人工智能助手,可以帮助你查找信息、解答问题和进行对话。有什么我可以帮助你的吗?
# 流式调用和输出
stream = azure_chat_service.get_streaming_chat_message_contents(
    chat_history=chat, 
    settings=azure_prompt_execution_settings
)

async for token in stream:
    print(token[-1], end="")  # end = "" to avoid newlines
你好!我是一个人工智能助手,可以帮助你查找信息、解答问题和进行对话。有什么我可以帮助你的吗?

是不是非常的方便和简单。

4.2、定义第一个 Agent

尝试一个简单的灯具控制 Agent

import asyncio
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.functions.kernel_arguments import KernelArguments

from semantic_kernel.contents.chat_history import ChatHistory

from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
    AzureChatPromptExecutionSettings
)

from typing import Annotated
from semantic_kernel.functions import kernel_function

# 定义一个灯具控制的插件(Plugin,一系列工具的集合)
# LightsPlugin 包含 get_state 和 change_state 两个 functions,通过@kernel_function装饰器高效实现
class LightsPlugin:
    lights = [
        {"id": 1, "name": "Table Lamp", "is_on": False},
        {"id": 2, "name": "Porch lite", "is_on": False},
        {"id": 3, "name": "Chandelier", "is_on": True},
    ]

    @kernel_function(
        name="get_lights",
        description="Gets a list of lights and their current state",
    )
    def get_state(self) -> Annotated[str, "the output is a string"]:
        """Gets a list of lights and their current state."""
        return self.lights

    @kernel_function(
        name="change_state",
        description="Changes the state of the light",
    )
    def change_state(self, id: int, is_on: bool) -> Annotated[str, "the output is a string"]:
        """Changes the state of the light"""
        for light in self.lights:
            if light["id"] == id:
                light["is_on"] = is_on
                return light
        return None


# 定义主函数
async def main():
    # 初始化Kernel
    kernel = Kernel()

    # 添加Azure OpenAI chat服务,仍然是通过.env配置
    kernel.add_service(AzureChatCompletion(
        env_file_path="./.env"
    ))
    
    # 如果需要直接诶使用Azure服务,可以通过kernel直接获取,十分方便
    chat_completion: AzureChatCompletion = kernel.get_service(type=ChatCompletionClientBase)

    # 插件添加到kernel
    kernel.add_plugin(
        LightsPlugin(),
        plugin_name="Lights"
    )

    # 配置LLM工具调用的方式,auto自主决定
    execution_settings = AzureChatPromptExecutionSettings(tool_choice="auto")
    execution_settings.function_call_behavior = FunctionCallBehavior.EnableFunctions(auto_invoke=True, filters={})

    history = ChatHistory()

    userInput = None
    while True:
        userInput = input("User > ")
        if userInput == "exit":
            break

        history.add_user_message(userInput)

        result = (await chat_completion.get_chat_message_contents(
            chat_history=history,
            settings=execution_settings,
            kernel=kernel,
            arguments=KernelArguments()
        ))[0]

        print("Assistant > ", result)

if __name__ == "__main__":
    asyncio.run(main())
User > 有哪些灯?
Assistant >  我们有以下几盏灯:

1. 台灯 (Table Lamp) - 关闭状态
2. 门廊灯 (Porch lite) - 关闭状态
3. 吊灯 (Chandelier) - 打开状态
User > 把2打开,把3关了
Assistant >  操作完成 。

2. 门廊灯 (Porch lite) 已打开。
3. 吊灯 (Chandelier) 已关闭。
User > 完了吗,我看看状态
Assistant >  当前灯光状态如下:

1. 台灯 (Table Lamp) - 关闭状态
2. 门廊灯 (Porch lite) - 打开状态
3. 吊灯 (Chandelier) - 关闭状态
User > 

Reference

[1] https://learn.microsoft.com/en-us/semantic-kernel/overview/

[2] https://mp.weixin.qq.com/s/ZJTfCkWOrE8omRZEhrj_3g

[3] https://github.com/microsoft/semantic-kernel

[4] https://mp.weixin.qq.com/s/mtaXOGFw3852F1RsZNqF4Q