多模型协同与智能体(Agent)架构

多模型协同与智能体(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);

这个计划会自动分解为两步:

  1. 调用 WeatherPlugin.GetWeather("London")
  2. 调用 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 系统。

相关推荐
阳光是sunny15 小时前
Vue 项目怎么做用户行为全链路监控?轻量插件方案详解
前端·面试·架构
EMA21 小时前
Docker虚拟化失败解决方案
架构
李斯维1 天前
从历史的角度看 Android 软件架构
android·架构·android jetpack
JouYY1 天前
聊一下多 Agent 编排架构的应用实践
架构·llm·agent
Sunia1 天前
《AgentX 专栏》10-生产部署:3台2C4G云服务器把企业级Agent真正跑起来的完整方案
java·架构
ZhengEnCi2 天前
Q01-高并发点赞系统架构设计
架构
笨鸟飞不快2 天前
从 MVC 到 DDD:一次真实的渐进式迁移实录
后端·架构
这个DBA有点耶3 天前
GROUP BY优化全解:如何写出既不丢数据又飞快的分组查询
数据库·mysql·架构
锋行天下3 天前
我试图优化 Vite 的拆包,结果首屏慢了 10 倍
前端·vue.js·架构