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

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 --pre 时,它将安装核心包和 所有 包,以便快速开始使用所有功能。 准备好减少包数量时,因为你知道需要什么,因此只能安装所需的包,因此,例如,如果仅计划使用 Azure AI Foundry 和 Mem0,则只能安装这两个包:pip install agent-framework-azure-ai agent-framework-mem0 --preagent-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 无法提供的其他输入的工具,例如连接、机密等。

兼容性:使用 KernelFunction 作为代理框架工具

如果现有语义内核代码包含 KernelFunction 实例(来自提示或方法),则可以使用该方法 .as_agent_framework_tool 将其转换为 Agent Framework 工具。

重要

此功能需要 semantic-kernel 版本 1.38 或更高版本。

从提示模板使用 KernelFunction

from semantic_kernel import Kernel
from semantic_kernel.functions import KernelFunctionFromPrompt
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings
from semantic_kernel.prompt_template import KernelPromptTemplate, PromptTemplateConfig
from agent_framework.openai import OpenAIResponsesClient

# Create a kernel with services and plugins
kernel = Kernel()
# will get the api_key and model_id from the environment
kernel.add_service(OpenAIChatCompletion(service_id="default"))

# Create a function from a prompt template that uses plugin functions
function_definition = """
Today is: {{time.date}}
Current time is: {{time.time}}

Answer to the following questions using JSON syntax, including the data used.
Is it morning, afternoon, evening, or night (morning/afternoon/evening/night)?
Is it weekend time (weekend/not weekend)?
"""

prompt_template_config = PromptTemplateConfig(template=function_definition)
prompt_template = KernelPromptTemplate(prompt_template_config=prompt_template_config)

# Create a KernelFunction from the prompt
kernel_function = KernelFunctionFromPrompt(
    description="Determine the kind of day based on the current time and date.",
    plugin_name="TimePlugin",
    prompt_execution_settings=OpenAIChatPromptExecutionSettings(service_id="default", max_tokens=100),
    function_name="kind_of_day",
    prompt_template=prompt_template,
)

# Convert the KernelFunction to an Agent Framework tool
agent_tool = kernel_function.as_agent_framework_tool(kernel=kernel)

# Use the tool with an Agent Framework agent
agent = OpenAIResponsesClient(model_id="gpt-4o").create_agent(tools=agent_tool)
response = await agent.run("What kind of day is it?")
print(response.text)

从方法中使用 KernelFunction

from semantic_kernel.functions import kernel_function
from agent_framework.openai import OpenAIResponsesClient

# Create a plugin class with kernel functions
@kernel_function(name="get_weather", description="Get the weather for a location")
def get_weather(self, location: str) -> str:
    return f"The weather in {location} is sunny."

# Get the KernelFunction and convert it to an Agent Framework tool
agent_tool = get_weather.as_agent_framework_tool()

# Use the tool with an Agent Framework agent
agent = OpenAIResponsesClient(model_id="gpt-4o").create_agent(tools=agent_tool)
response = await agent.run("What's the weather in Seattle?")
print(response.text)

将 VectorStore 与 create_search_function 配合使用

还可以使用语义内核的 VectorStore 与 Agent Framework 集成。 create_search_function矢量存储集合中的方法返回一个KernelFunction可转换为 Agent Framework 工具的方法。

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAITextEmbedding
from semantic_kernel.connectors.azure_ai_search import AzureAISearchCollection
from semantic_kernel.functions import KernelParameterMetadata
from agent_framework.openai import OpenAIResponsesClient

# Define your data model
class HotelSampleClass:
    HotelId: str
    HotelName: str
    Description: str
    # ... other fields

# Create an Azure AI Search collection
collection = AzureAISearchCollection[str, HotelSampleClass](
    record_type=HotelSampleClass,
    embedding_generator=OpenAITextEmbedding()
)

async with collection:
    await collection.ensure_collection_exists()
    # Load your records into the collection
    # await collection.upsert(records)

    # Create a search function from the collection
    search_function = collection.create_search_function(
        description="A hotel search engine, allows searching for hotels in specific cities.",
        search_type="keyword_hybrid",
        filter=lambda x: x.Address.Country == "USA",
        parameters=[
            KernelParameterMetadata(
                name="query",
                description="What to search for.",
                type="str",
                is_required=True,
                type_object=str,
            ),
            KernelParameterMetadata(
                name="city",
                description="The city that you want to search for a hotel in.",
                type="str",
                type_object=str,
            ),
            KernelParameterMetadata(
                name="top",
                description="Number of results to return.",
                type="int",
                default_value=5,
                type_object=int,
            ),
        ],
        string_mapper=lambda x: f"(hotel_id: {x.record.HotelId}) {x.record.HotelName} - {x.record.Description}",
    )

    # Convert the search function to an Agent Framework tool
    search_tool = search_function.as_agent_framework_tool()

    # Use the tool with an Agent Framework agent
    agent = OpenAIResponsesClient(model_id="gpt-4o").create_agent(
        instructions="You are a travel agent that helps people find hotels.",
        tools=search_tool
    )
    response = await agent.run("Find me a hotel in Seattle")
    print(response.text)

此模式适用于任何语义内核 VectorStore 连接器(Azure AI 搜索、Qdrant、Pinecone 等),允许你通过 Agent Framework 代理利用现有的矢量搜索基础结构。

此兼容性层允许逐步将代码从语义内核迁移到 Agent Framework,重用现有 KernelFunction 实现,同时利用 Agent Framework 简化的代理创建和执行模式。

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

后续步骤