将语义内核迁移到代理框架指南

Microsoft代理框架的优点

  • 简化的 API:降低了复杂性和样本代码。
  • 更好的性能:优化的对象创建和内存使用情况。
  • 统一接口:跨不同 AI 提供程序的一致模式。
  • 增强的开发人员体验:更直观且可发现 API。

以下部分总结了语义内核代理框架和 Microsoft Agent Framework 之间的主要区别,以帮助迁移代码。

1. 命名空间更新

语义内核

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;

代理框架

代理框架命名空间位于 < a0/> 下 。 代理框架使用核心 AI 消息和内容类型在 Microsoft.Extensions.AI 组件之间进行通信。

using Microsoft.Extensions.AI;
using Microsoft.Agents.AI;

2. 代理创建简化

语义内核

语义内核中的每个代理都依赖于一个 Kernel 实例,如果未提供,则为空 Kernel

 Kernel kernel = Kernel
    .AddOpenAIChatClient(modelId, apiKey)
    .Build();

 ChatCompletionAgent agent = new() { Instructions = ParrotInstructions, Kernel = kernel };

在创建使用它的本地代理类之前,Azure AI Foundry 需要在云中创建代理资源。

PersistentAgentsClient azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());

PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(
    deploymentName,
    instructions: ParrotInstructions);

AzureAIAgent agent = new(definition, azureAgentClient);

代理框架

通过所有主要提供程序提供的扩展,在 Agent Framework 中创建代理会更简单。

AIAgent openAIAgent = chatClient.CreateAIAgent(instructions: ParrotInstructions);
AIAgent azureFoundryAgent = await persistentAgentsClient.CreateAIAgentAsync(instructions: ParrotInstructions);
AIAgent openAIAssistantAgent = await assistantClient.CreateAIAgentAsync(instructions: ParrotInstructions);

此外,对于托管代理提供程序,还可以使用 GetAIAgent 该方法从现有托管代理中检索代理。

AIAgent azureFoundryAgent = await persistentAgentsClient.GetAIAgentAsync(agentId);

3. 代理线程创建

语义内核

调用方必须知道线程类型并手动创建它。

// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient);
AgentThread thread = new AzureAIAgentThread(this.Client);
AgentThread thread = new OpenAIResponseAgentThread(this.Client);

代理框架

代理负责创建线程。

// New.
AgentThread thread = agent.GetNewThread();

4. 托管代理线程清理

此情况仅适用于少数仍提供托管线程的 AI 提供程序。

语义内核

线程具有 self 删除方法。

OpenAI 助手提供程序:

await thread.DeleteAsync();

代理框架

注释

OpenAI 响应引入了一个新的聊天模型,简化了对话的处理方式。 与现已弃用的 OpenAI 助手模型相比,此更改简化了托管线程管理。 有关详细信息,请参阅 OpenAI 助手迁移指南

代理框架在类型中 AgentThread 没有线程删除 API,因为并非所有提供程序都支持托管线程或线程删除。 随着更多的提供程序转向基于响应的体系结构,这种设计将变得更加常见。

如果需要删除线程,并且提供程序允许它,调用方 跟踪创建的线程,并在必要时通过提供程序的 SDK 将其删除。

OpenAI 助手提供程序:

await assistantClient.DeleteThreadAsync(thread.ConversationId);

5. 工具注册

语义内核

若要将函数公开为工具,必须:

  1. 使用 [KernelFunction] 特性修饰函数。
  2. 具有类 Plugin 或使用 KernelPluginFactory 包装函数。
  3. 必须添加 Kernel 插件。
  4. 传递给 Kernel 代理。
KernelFunction function = KernelFunctionFactory.CreateFromMethod(GetWeather);
KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("KernelPluginName", [function]);
Kernel kernel = ... // Create kernel
kernel.Plugins.Add(plugin);

ChatCompletionAgent agent = new() { Kernel = kernel, ... };

代理框架

在 Agent Framework 中,在单个调用中,可以直接在代理创建过程中注册工具。

AIAgent agent = chatClient.CreateAIAgent(tools: [AIFunctionFactory.Create(GetWeather)]);

6. 代理非流式处理调用

方法名称中可查看主要差异,包括返回InvokeRun类型和参数AgentRunOptions

语义内核

非流式处理使用流模式 IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> 返回多个代理消息。

await foreach (AgentResponseItem<ChatMessageContent> result in agent.InvokeAsync(userInput, thread, agentOptions))
{
    Console.WriteLine(result.Message);
}

代理框架

非流式处理返回包含多个消息的代理响应的单个 AgentRunResponse 消息。 运行的文本结果在 AgentRunResponse.TextAgentRunResponse.ToString(). 作为响应的一部分创建的所有消息都会在 AgentRunResponse.Messages 列表中返回。 这可能包括工具调用消息、函数结果、推理更新和最终结果。

AgentRunResponse agentResponse = await agent.RunAsync(userInput, thread);

7. 代理流式处理调用

主要区别在于方法名称的返回InvokeRun类型和参数AgentRunOptions

语义内核

await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(userInput, thread))
{
    Console.Write(update);
}

代理框架

代理框架具有类似的流式处理 API 模式,主要区别在于它返回 AgentRunResponseUpdate 包含每个更新更多的代理相关信息的对象。

返回由 AIAgent 基础的任何服务生成的所有更新。 代理的文本结果可通过连接 AgentRunResponse.Text 值来提供。

await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(userInput, thread))
{
    Console.Write(update); // Update is ToString() friendly
}

8. 工具函数签名

问题:语义内核插件方法需要 [KernelFunction] 属性。

public class MenuPlugin
{
    [KernelFunction] // Required.
    public static MenuItem[] GetMenu() => ...;
}

解决方案:代理框架可以直接使用没有属性的方法。

public class MenuTools
{
    [Description("Get menu items")] // Optional description.
    public static MenuItem[] GetMenu() => ...;
}

9. 选项配置

问题:语义内核中的复杂选项设置。

OpenAIPromptExecutionSettings settings = new() { MaxTokens = 1000 };
AgentInvokeOptions options = new() { KernelArguments = new(settings) };

解决方案:代理框架中的简化选项。

ChatClientAgentRunOptions options = new(new() { MaxOutputTokens = 1000 });

重要

此示例演示如何将特定于实现的选项传递给 .ChatClientAgent 并非所有 AIAgents 支持 ChatClientAgentRunOptionsChatClientAgent 为基于基础推理服务生成代理,因此支持推理选项,例如 MaxOutputTokens

10. 依赖关系注入

语义内核

Kernel服务容器中需要注册才能创建代理,因为每个代理抽象都需要使用Kernel属性进行初始化。

语义内核将 Agent 类型用作代理的基本抽象类。

services.AddKernel().AddProvider(...);
serviceContainer.AddKeyedSingleton<SemanticKernel.Agents.Agent>(
    TutorName,
    (sp, key) =>
        new ChatCompletionAgent()
        {
            // Passing the kernel is required.
            Kernel = sp.GetRequiredService<Kernel>(),
        });

代理框架

代理框架将 AIAgent 类型作为基抽象类提供。

services.AddKeyedSingleton<AIAgent>(() => client.CreateAIAgent(...));

11. 代理类型合并

语义内核

语义内核为各种服务提供特定的代理类,例如:

  • ChatCompletionAgent 用于基于聊天完成的推理服务。
  • OpenAIAssistantAgent 用于 OpenAI 助手服务。
  • AzureAIAgent 用于 Azure AI Foundry 代理服务。

代理框架

代理框架通过单个代理类型 ChatClientAgent支持所有提到的服务。

ChatClientAgent 可用于使用提供实现 IChatClient 接口的 SDK 的任何基础服务生成代理。

主要差异

下面是语义内核代理框架和 Microsoft Agent Framework 之间的主要差异摘要,可帮助你迁移代码。

1. 打包和导入更新

语义内核

语义内核包作为安装并 semantic-kernel 导入为 semantic_kernel。 该包还包含许多 extras 可以安装的依赖项,以便为不同的 AI 提供程序和其他功能安装不同的依赖项。

from semantic_kernel import Kernel
from semantic_kernel.agents import ChatCompletionAgent

代理框架

代理框架包作为安装并 agent-framework 导入为 agent_framework。 代理框架的构建方式不同,它具有包含核心功能的核心包 agent-framework-core ,然后有多个包依赖于该核心包,例如 agent-framework-azure-aiagent-framework-mem0等等 agent-framework-copilotstudio。运行 pip install agent-framework 时,它将安装核心包和 所有 包,以便快速开始使用所有功能。 准备好减少包数量时,因为你知道需要什么,因此只能安装所需的包,因此,例如,如果仅计划使用 Azure AI Foundry 和 Mem0,则只能安装这两个包:pip install agent-framework-azure-ai agent-framework-mem0agent-framework-core是这两个包的依赖项,因此将自动安装。

尽管包已拆分,但导入都是来自 agent_framework的,或者是模块。 例如,若要导入 Azure AI Foundry 的客户端,需要执行以下作:

from agent_framework.azure import AzureAIAgentClient

许多最常用的类型直接从以下项 agent_framework导入:

from agent_framework import ChatMessage, ChatAgent

2. 代理类型合并

语义内核

语义内核为各种服务提供特定的代理类,例如 ChatCompletionAgent、AzureAIAgent、OpenAIAssistantAgent 等。请参阅 语义内核中的代理类型

代理框架

在代理框架中,大多数代理都是使用 ChatAgent 可用于所有 ChatClient 基于服务的代理,例如 Azure AI Foundry、OpenAI ChatCompletion 和 OpenAI 响应。 还有两个额外的代理: CopilotStudioAgent 用于 Copilot Studio 和 A2AAgent A2A。

所有内置代理都基于 BaseAgent (from agent_framework import BaseAgent)。 所有代理都与 AgentProtocolfrom agent_framework import AgentProtocol) 接口一致。

3. 代理创建简化

语义内核

语义内核中的每个代理都依赖于一个 Kernel 实例,如果未提供,则其值为空 Kernel

from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion

agent = ChatCompletionAgent(
    service=OpenAIChatCompletion(),
    name="Support",
    instructions="Answer in one sentence.",
)

代理框架

可以通过两种方式直接在 Agent Framework 中创建代理:

from agent_framework.azure import AzureAIAgentClient
from agent_framework import ChatMessage, ChatAgent

agent = ChatAgent(chat_client=AzureAIAgentClient(credential=AzureCliCredential()), instructions="You are a helpful assistant")

或者,使用聊天客户端提供的便利方法:

from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
agent = AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(instructions="You are a helpful assistant")

直接方法公开可为代理设置的所有可能参数。 尽管便利方法有一个子集,但仍可以传入同一组参数,因为它在内部调用直接方法。

4. 代理线程创建

语义内核

调用方必须知道线程类型并手动创建它。

from semantic_kernel.agents import ChatHistoryAgentThread

thread = ChatHistoryAgentThread()

代理框架

可以要求代理为你创建新线程。

agent = ...
thread = agent.get_new_thread()

然后,通过以下三种方式之一创建线程:

  1. 如果代理设置了一个 thread_id (或类似 conversation_id 内容),它将在该 ID 的基础服务中创建一个线程。 一旦线程有一个 service_thread_id线程,就不能再使用它将消息存储在内存中。 这仅适用于具有服务端线程概念的代理。 例如 Azure AI Foundry 代理和 OpenAI 助手。
  2. 如果代理有一个 chat_message_store_factory 集,它将使用该工厂创建消息存储,并使用该存储来创建内存中线程。 然后,它不能再与参数设置为的storeTrue代理一起使用。
  3. 如果上述两个设置均未设置,则会将其视为 uninitialized 使用方式,具体取决于其使用方式,它将成为内存中线程或服务线程。

代理框架

注释

OpenAI 响应引入了一个新的聊天模型,简化了对话的处理方式。 与现在弃用的 OpenAI 助手模型相比,这简化了托管线程管理。 有关详细信息,请参阅 OpenAI 助手迁移指南

代理框架在类型中 AgentThread 没有线程删除 API,因为并非所有提供程序都支持托管线程或线程删除,随着更多的提供程序转向基于响应的体系结构,这变得更加常见。

如果需要删除线程,并且提供程序允许这样做,调用方 跟踪创建的线程,并在必要时通过提供程序的 sdk 删除它们。

OpenAI 助手提供程序:

# OpenAI Assistants threads have self-deletion method in Semantic Kernel
await thread.delete_async()

5. 工具注册

语义内核

若要将函数公开为工具,必须:

  1. 使用 @kernel_function 修饰器修饰函数。
  2. 具有类 Plugin 或使用内核插件工厂包装函数。
  3. 必须添加 Kernel 插件。
  4. 传递给 Kernel 代理。
from semantic_kernel.functions import kernel_function

class SpecialsPlugin:
    @kernel_function(name="specials", description="List daily specials")
    def specials(self) -> str:
        return "Clam chowder, Cobb salad, Chai tea"

agent = ChatCompletionAgent(
    service=OpenAIChatCompletion(),
    name="Host",
    instructions="Answer menu questions accurately.",
    plugins=[SpecialsPlugin()],
)

代理框架

在单个调用中,可以直接在代理创建过程中注册工具。 代理框架没有插件包装多个函数的概念,但如果需要,仍可以执行此作。

创建工具的最简单方法是创建 Python 函数:

def get_weather(location: str) -> str:
    """Get the weather for a given location."""
    return f"The weather in {location} is sunny."

agent = chat_client.create_agent(tools=get_weather)

注释

参数tools同时存在于代理创建、run方法和run_stream方法和get_responseget_streaming_response方法上,它允许你提供工具作为列表或单个函数。

函数的名称随后将成为工具的名称,docstring 将成为工具的说明,还可以向参数添加说明:

from typing import Annotated

def get_weather(location: Annotated[str, "The location to get the weather for."]) -> str:
    """Get the weather for a given location."""
    return f"The weather in {location} is sunny."

最后,可以使用修饰器进一步自定义工具的名称和说明:

from typing import Annotated
from agent_framework import ai_function

@ai_function(name="weather_tool", description="Retrieves weather information for any location")
def get_weather(location: Annotated[str, "The location to get the weather for."])
    """Get the weather for a given location."""
    return f"The weather in {location} is sunny."

当创建具有多个工具的类作为方法时,这同样有效。

创建代理时,现在可以通过将函数工具传递给代理来将其 tools 传递给参数。

class Plugin:

    def __init__(self, initial_state: str):
        self.state: list[str] = [initial_state]

    def get_weather(self, location: Annotated[str, "The location to get the weather for."]) -> str:
        """Get the weather for a given location."""
        self.state.append(f"Requested weather for {location}. ")
        return f"The weather in {location} is sunny."

    def get_weather_details(self, location: Annotated[str, "The location to get the weather details for."]) -> str:
        """Get detailed weather for a given location."""
        self.state.append(f"Requested detailed weather for {location}. ")
        return f"The weather in {location} is sunny with a high of 25°C and a low of 15°C."

plugin = Plugin("Initial state")
agent = chat_client.create_agent(tools=[plugin.get_weather, plugin.get_weather_details])

... # use the agent

print("Plugin state:", plugin.state)

注释

还可以使用 @ai_function 类中的函数来自定义工具的名称和说明。

此机制还可用于需要 LLM 无法提供的其他输入的工具,例如连接、机密等。

6. 代理非流式处理调用

方法名称invokerun中可查看主要差异,返回类型(例如)AgentRunResponse和参数。

语义内核

非流式处理调用使用异步迭代器模式返回多个代理消息。

async for response in agent.invoke(
    messages=user_input,
    thread=thread,
):
    print(f"# {response.role}: {response}")
    thread = response.thread

获取最终响应有一种便捷的方法:

response = await agent.get_response(messages="How do I reset my bike tire?", thread=thread)
print(f"# {response.role}: {response}")

代理框架

非流式处理运行返回一个 AgentRunResponse 包含可包含多个消息的代理响应。 运行的文本结果在 response.textstr(response). 作为响应的一部分创建的所有消息都会在 response.messages 列表中返回。 这可能包括工具调用消息、函数结果、推理更新和最终结果。

agent = ...

response = await agent.run(user_input, thread)
print("Agent response:", response.text)

7. 代理流式处理调用

方法名称invokerun_stream与返回类型(AgentRunResponseUpdate)和参数之间的主要差异。

语义内核

async for update in agent.invoke_stream(
    messages="Draft a 2 sentence blurb.",
    thread=thread,
):
    if update.message:
        print(update.message.content, end="", flush=True)

代理框架

类似的流式处理 API 模式,其主要区别在于它返回 AgentRunResponseUpdate 对象,包括每个更新的更多代理相关信息。

将返回代理所生成的任何服务生成的所有内容。 代理的最终结果可通过将 update 值组合到单个响应中来获得。

from agent_framework import AgentRunResponse
agent = ...
updates = []
async for update in agent.run_stream(user_input, thread):
    updates.append(update)
    print(update.text)

full_response = AgentRunResponse.from_agent_run_response_updates(updates)
print("Full agent response:", full_response.text)

甚至可以直接执行此作:

from agent_framework import AgentRunResponse
agent = ...
full_response = AgentRunResponse.from_agent_response_generator(agent.run_stream(user_input, thread))
print("Full agent response:", full_response.text)

8. 选项配置

问题:语义内核中的复杂选项设置

from semantic_kernel.connectors.ai.open_ai import OpenAIPromptExecutionSettings

settings = OpenAIPromptExecutionSettings(max_tokens=1000)
arguments = KernelArguments(settings)

response = await agent.get_response(user_input, thread=thread, arguments=arguments)

解决方案:代理框架中的简化选项

代理框架允许将所有参数直接传递到相关方法,因此无需导入任何额外参数或创建任何选项对象,除非需要。 在内部,它使用ChatOptions对象,ChatClientsChatAgents也可以根据需要创建和传入对象。 这也会在一个 ChatAgent 中创建,用于保存选项,并且可以按调用重写。

agent = ...

response = await agent.run(user_input, thread, max_tokens=1000, frequency_penalty=0.5)

注释

上述内容特定于 a ChatAgent,因为其他代理可能有不同的选项,因此它们应全部接受 messages 为参数,因为该参数在定义中 AgentProtocol

后续步骤