Microsoft代理框架的优点
- 简化的 API:降低了复杂性和样本代码。
- 更好的性能:优化的对象创建和内存使用情况。
- 统一接口:跨不同 AI 提供程序的一致模式。
- 增强的开发人员体验:更直观且可发现 API。
以下部分总结了语义内核代理框架和 Microsoft Agent Framework 之间的主要区别,以帮助迁移代码。
1. 命名空间更新
语义内核
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
代理框架
代理框架命名空间位于 < a0/> 下
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. 工具注册
语义内核
若要将函数公开为工具,必须:
- 使用
[KernelFunction]特性修饰函数。 - 具有类
Plugin或使用KernelPluginFactory包装函数。 - 必须添加
Kernel插件。 - 传递给
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.Text 或 AgentRunResponse.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 支持 ChatClientAgentRunOptions。
ChatClientAgent 为基于基础推理服务生成代理,因此支持推理选项,例如 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-ai, agent-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)。 所有代理都与 AgentProtocol (from 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()
然后,通过以下三种方式之一创建线程:
- 如果代理设置了一个
thread_id(或类似conversation_id内容),它将在该 ID 的基础服务中创建一个线程。 一旦线程有一个service_thread_id线程,就不能再使用它将消息存储在内存中。 这仅适用于具有服务端线程概念的代理。 例如 Azure AI Foundry 代理和 OpenAI 助手。 - 如果代理有一个
chat_message_store_factory集,它将使用该工厂创建消息存储,并使用该存储来创建内存中线程。 然后,它不能再与参数设置为的storeTrue代理一起使用。 - 如果上述两个设置均未设置,则会将其视为
uninitialized使用方式,具体取决于其使用方式,它将成为内存中线程或服务线程。
代理框架
注释
OpenAI 响应引入了一个新的聊天模型,简化了对话的处理方式。 与现在弃用的 OpenAI 助手模型相比,这简化了托管线程管理。 有关详细信息,请参阅 OpenAI 助手迁移指南。
代理框架在类型中 AgentThread 没有线程删除 API,因为并非所有提供程序都支持托管线程或线程删除,随着更多的提供程序转向基于响应的体系结构,这变得更加常见。
如果需要删除线程,并且提供程序允许这样做,调用方 应 跟踪创建的线程,并在必要时通过提供程序的 sdk 删除它们。
OpenAI 助手提供程序:
# OpenAI Assistants threads have self-deletion method in Semantic Kernel
await thread.delete_async()
5. 工具注册
语义内核
若要将函数公开为工具,必须:
- 使用
@kernel_function修饰器修饰函数。 - 具有类
Plugin或使用内核插件工厂包装函数。 - 必须添加
Kernel插件。 - 传递给
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.text 或 str(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。