多模型协同与智能体(Agent)架构
多个 AI 角色的协作 :一个规划者拆解任务,一个研究员搜索信息,一个执行者调用工具,一个审核者检查结果。这种 多智能体(Multi-Agent) 架构,正是迈向真正自主 AI 系统的关键一步。
将深入探讨 Semantic Kernel 如何支持多模型协同。将学习 群聊模式 ,让多个 Agent 像人类团队一样对话;掌握 规划器(Planner) ,使 AI 能够自动编写和执行 C# 代码;最后,我们将实现 流式输出,通过 Server-Sent Events(SSE)让 Web 应用获得实时响应体验。
1 基于 Semantic Kernel 的群聊模式(Group Chat)
1.1 什么是群聊模式?
在群聊模式中,每个 Agent 都是一个独立的 Kernel 实例,拥有自己的系统提示词、插件和模型配置。它们通过一个 协调器 进行通信,模拟人类团队的协作流程。例如,一个"软件架构师"Agent 负责拆解需求,一个"代码生成器"Agent 负责编写代码,一个"代码审查者"Agent 负责检查质量。
1.2 Semantic Kernel 中的 Agent 抽象
SK 在 Microsoft.SemanticKernel.Agents 包中提供了官方的 Agent 抽象,包括:
- ChatCompletionAgent:基于聊天模型的 Agent,支持多轮对话。
- CodeAgent:专门用于生成和执行代码的 Agent。
- GroupChat:管理多个 Agent 的对话和转轮逻辑。
安装:
bash
dotnet add package Microsoft.SemanticKernel.Agents
1.3 构建多 Agent 团队
构建一个简单的三 Agent 团队,用于解决技术问题:
csharp
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
// 创建 Kernel 和 Agent
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(...)
.Build();
var architect = new ChatCompletionAgent
{
Name = "Architect",
Instructions = """
You are a software architect. Analyze the user's request and break it down
into clear, actionable tasks. Output the tasks as a numbered list.
Do not write code.
""",
Kernel = kernel
};
var coder = new ChatCompletionAgent
{
Name = "Coder",
Instructions = """
You are a senior developer. Based on the tasks provided by the Architect,
write clean, efficient C# code. Include comments and error handling.
Output only the code, no extra text.
""",
Kernel = kernel
};
var reviewer = new ChatCompletionAgent
{
Name = "Reviewer",
Instructions = """
You are a code reviewer. Analyze the code written by the Coder.
Point out potential bugs, performance issues, or style problems.
Suggest improvements. If the code is perfect, say "Approved".
""",
Kernel = kernel
};
1.4 群聊协调器:GroupChat
SK 的 GroupChat 提供了一个简单的协调器,可以管理 Agent 之间的对话。但它默认是顺序发言的,需要我们自己实现转轮逻辑。我们可以手动控制对话流程,更灵活。
手动控制流程示例:
csharp
public class AgentOrchestrator
{
private readonly ChatCompletionAgent _architect;
private readonly ChatCompletionAgent _coder;
private readonly ChatCompletionAgent _reviewer;
public async Task<string> ProcessAsync(string userRequest)
{
var chat = new ChatHistory();
chat.AddUserMessage(userRequest);
// 1. 架构师拆解任务
var architectResponse = await _architect.GetResponseAsync(chat);
chat.AddAssistantMessage(architectResponse.Content);
var tasks = architectResponse.Content;
// 2. 编码员根据任务编写代码
var coderResponse = await _coder.GetResponseAsync(chat);
chat.AddAssistantMessage(coderResponse.Content);
var code = coderResponse.Content;
// 3. 审查员审查代码
var reviewerResponse = await _reviewer.GetResponseAsync(chat);
chat.AddAssistantMessage(reviewerResponse.Content);
var review = reviewerResponse.Content;
// 判断是否需要迭代
if (review.Contains("Approved"))
{
return $"**Code:**\n```csharp\n{code}\n```\n\n**Review:** {review}";
}
else
{
// 可以迭代:将审查意见加入对话,让 Coder 修改
var revisedCoderResponse = await _coder.GetResponseAsync(chat);
return revisedCoderResponse.Content;
}
}
}
1.5 高级群聊模式:动态角色与投票
对于更复杂的场景,可以引入 动态角色选择 和 投票机制。例如,多个专家 Agent 分别提出方案,由一个"裁判"Agent 投票决定最佳方案。SK 的 Agent 包支持为每个 Agent 指定不同的模型和参数,实现"混合专家"架构。
实现投票模式:
csharp
var agents = new List<ChatCompletionAgent> { expertA, expertB, expertC };
var chat = new ChatHistory();
chat.AddUserMessage("How to implement a distributed lock in Redis?");
var suggestions = new List<string>();
foreach (var agent in agents)
{
var response = await agent.GetResponseAsync(chat);
suggestions.Add(response.Content);
}
// 裁判 Agent 综合评估
var judge = new ChatCompletionAgent
{
Name = "Judge",
Instructions = "You are a judge. Evaluate the following suggestions and pick the best one, explaining why.",
Kernel = kernel
};
var judgeChat = new ChatHistory();
judgeChat.AddUserMessage($"Suggestions:\n{string.Join("\n---\n", suggestions)}");
var finalVerdict = await judge.GetResponseAsync(judgeChat);
1.6 群聊模式的挑战与最佳实践
- 状态管理:多轮对话会产生大量历史记录,容易超出上下文窗口。建议定期压缩或总结历史。
- 模型选择:不同 Agent 可以用不同模型,例如规划者用 GPT-4,代码生成用专门的 Codex 模型。
- 错误处理:Agent 可能输出不符合预期格式的内容,需要解析和重试机制。
- 可观测性:为每个 Agent 的调用添加 OpenTelemetry 追踪,记录耗时、输入输出,便于调试。
2 规划器(Planner):让 AI 自动编写 C# 代码执行任务
规划器是 Semantic Kernel 最强大的功能之一。它允许 LLM 根据用户意图,自动生成一个包含多个函数调用的 执行计划 ,甚至可以 生成 C# 代码并动态执行。这为实现"AI 编程"铺平了道路。
2.1 规划器的工作原理
规划器本质上是一个特殊的提示词,它引导 LLM 分析可用插件,生成一个步骤列表。SK 提供了两种规划器:
- FunctionCallingStepPlanner:基于函数调用,将计划表示为一连串的函数调用。
- HandlebarsPlanner:使用 Handlebars 模板语法生成包含逻辑分支的计划。
2.2 使用 FunctionCallingStepPlanner
安装:
bash
dotnet add package Microsoft.SemanticKernel.Planners
基础用法:
csharp
using Microsoft.SemanticKernel.Planning;
var planner = new FunctionCallingStepPlanner(kernel);
var plan = await planner.CreatePlanAsync("What's the weather in London and send an email summary to john@example.com");
// 执行计划
var result = await plan.InvokeAsync(kernel);
Console.WriteLine(result);
这个计划会自动分解为两步:
- 调用
WeatherPlugin.GetWeather("London") - 调用
EmailPlugin.SendEmailAsync("john@example.com", "Weather Update", result_from_step1)
2.3 自定义规划器:代码生成与动态编译
当内置插件无法满足需求时,规划器可以生成 C# 代码,然后动态编译执行。这需要谨慎启用,因为它涉及代码执行的安全风险。
启用代码生成规划器 (需安装 Microsoft.SemanticKernel.Planning.CodeGeneration):
csharp
using Microsoft.SemanticKernel.Planning.CodeGeneration;
var codePlanner = new CodeGenPlanner(kernel);
var plan = await codePlanner.CreatePlanAsync("Calculate Fibonacci(10) and write result to a file.");
SK 会生成类似以下的 C# 代码,然后使用 Microsoft.CodeAnalysis 动态编译并执行。这在数据转换、复杂计算等场景非常有用。
安全考虑:
- 在生产环境中,应限制代码生成能力,仅允许受信任的模型和用户。
- 使用沙箱环境执行生成的代码,例如在隔离的 AppDomain 或容器中。
- 设置执行超时和资源限制。
2.4 规划器的最佳实践
- 插件描述至关重要 :清晰的插件描述能显著提升规划器的准确性。务必为每个函数和参数添加详细的
[Description]。 - 限制函数数量 :提供给规划器的函数越多,选择越困难。可以通过
Kernel.Plugins.Filter动态过滤,只暴露与当前任务相关的插件。 - 计划验证与重试:生成计划后,可以检查其是否合理,如出现循环调用或缺失必要参数时,重新生成计划。
- 人工介入:对于高风险操作(如删除数据),规划器应请求用户确认后再执行。
3 流式输出与 Server-Sent Events(SSE)在 ASP.NET Core 中的实现
在聊天应用中,用户期望实时看到 AI 的回复,而不是等待完整响应。流式输出(Streaming)正是为此而生。Semantic Kernel 原生支持流式调用,而 ASP.NET Core 可以通过 Server-Sent Events(SSE) 或 WebSockets 将这些增量数据推送到前端。
3.1 Semantic Kernel 中的流式调用
SK 提供了 InvokePromptStreamingAsync 方法,用于获取流式响应。
csharp
var streamingResult = kernel.InvokePromptStreamingAsync("Write a poem about AI");
await foreach (var update in streamingResult)
{
Console.Write(update); // 逐块输出
}
对于函数调用,同样支持流式输出:
csharp
var streamingResult = kernel.InvokeStreamingAsync<MyFunctionResult>(...);
3.2 ASP.NET Core 中的 SSE 实现
SSE 是一种轻量级的推送技术,基于 HTTP,适合单向实时数据流。在 .NET 中,通过 IResult 扩展方法 Results.Stream 可以轻松实现。
控制器/端点示例:
csharp
[ApiController]
[Route("api/chat")]
public class ChatController : ControllerBase
{
private readonly Kernel _kernel;
[HttpGet("stream")]
public async IAsyncEnumerable<string> StreamChat([FromQuery] string message)
{
var prompt = "You are a helpful assistant. " + message;
var streamingResult = _kernel.InvokePromptStreamingAsync(prompt);
await foreach (var chunk in streamingResult)
{
// 发送每个块
yield return chunk;
}
}
}
在 Program.cs 中配置:
csharp
app.MapGet("/api/chat/stream", async (string message, Kernel kernel) =>
{
var streaming = kernel.InvokePromptStreamingAsync($"You are a helpful assistant. {message}");
return Results.Stream(async (stream) =>
{
var writer = new StreamWriter(stream);
await foreach (var chunk in streaming)
{
await writer.WriteAsync(chunk);
await writer.FlushAsync();
}
}, "text/plain");
});
3.3 使用 SignalR 实现双向流式通信
对于需要双向通信的场景(如多轮对话),SignalR 是更合适的选择。它可以建立持久连接,支持从服务器推送流式数据,也允许客户端随时发送消息。
SignalR Hub 示例:
csharp
public class ChatHub : Hub
{
private readonly Kernel _kernel;
public ChatHub(Kernel kernel) => _kernel = kernel;
public async IAsyncEnumerable<string> StreamMessage(string userMessage)
{
var prompt = "You are a helpful assistant. " + userMessage;
var streaming = _kernel.InvokePromptStreamingAsync(prompt);
await foreach (var chunk in streaming)
{
yield return chunk;
}
}
}
前端调用(JavaScript):
javascript
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
await connection.start();
// 调用流式方法
const stream = connection.stream("StreamMessage", "What is AI?");
stream.subscribe({
next: (chunk) => {
document.getElementById("response").innerHTML += chunk;
},
complete: () => console.log("Done")
});
3.4 流式输出的错误处理与取消
- 取消令牌 :在流式方法中,支持
CancellationToken,允许客户端取消长时间运行的生成。 - 错误恢复:流可能因网络中断而中止,需要在客户端实现重连和续传机制。
- 超时:设置合理的超时,避免连接被防火墙或代理关闭。
csharp
public async IAsyncEnumerable<string> StreamMessage(
string userMessage,
CancellationToken cancellationToken)
{
var streaming = _kernel.InvokePromptStreamingAsync(
prompt: $"You are a helpful assistant. {userMessage}",
cancellationToken: cancellationToken);
await foreach (var chunk in streaming.WithCancellation(cancellationToken))
{
yield return chunk;
}
}
3.5 流式输出的最佳实践
- 前端渲染:使用 Markdown 实时渲染库(如 marked.js)在收到每个块后增量渲染。
- 心跳机制 :SSE 可能被负载均衡器超时断开,定期发送注释行(
: heartbeat)维持连接。 - 压缩:对于大量文本,可考虑在传输层启用 gzip 压缩。
- 观测性:在服务端记录流式请求的开始、结束和中断事件,便于监控和计费。
总结
探索了多模型协同与智能体架构的核心技术:
- 群聊模式:通过多个 Agent 协作,模拟人类团队,解决复杂问题。
- 规划器(Planner):让 AI 自动生成执行计划,甚至编写 C# 代码,实现动态任务编排。
- 流式输出:通过 SSE 或 SignalR 提供实时交互体验,提升应用响应性。
掌握了这些能力,你就可以构建真正自主、协作的 AI 系统。