用户温泉 發表於 2025-10-28 22:53:00

.NET开发上手Microsoft Agent Framework(一)从开发一个AI美女聊天群组开始

<h2 id="前言">前言</h2>
<p>在AI快速发展的今天,微软推出了多个AI开发框架,从早期的AutoGen到Semantic Kernel,再到最新的Microsoft Agent Framework。很多开发者可能会有疑问:为什么微软要推出这么多框架?它们之间有什么区别?本文将通过一个实际的AI美女聊天群组项目,带你深入理解Microsoft Agent Framework,掌握多智能体开发的核心概念。</p>
<p>本文的示例代码已开源:agent-framework-tutorial-code/agent-groupchat</p>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202510/1690009-20251028221439575-1520228988.jpg" alt="效果截图" loading="lazy"></p>
<ul>
<li>视频演示</li>
</ul>
<h2 id="为什么微软要推出microsoft-agent-framework">为什么微软要推出Microsoft Agent Framework?</h2>
<h3 id="autogen-vs-semantic-kernel-vs-agent-framework">AutoGen vs Semantic Kernel vs Agent Framework</h3>
<p>在讲解新框架之前,我们先理解一下微软AI框架的演进路径:</p>
<p><strong>AutoGen(研究导向)</strong></p>
<ul>
<li>最早期的多智能体研究框架</li>
<li>侧重学术研究和实验性功能</li>
<li>Python为主,生态相对独立</li>
</ul>
<p><strong>Semantic Kernel(应用导向)</strong></p>
<ul>
<li>面向生产环境的AI应用开发框架</li>
<li>强大的插件系统和内存管理</li>
<li>多语言支持(C#、Python、Java)</li>
<li>适合单一智能体应用</li>
</ul>
<p><strong>Microsoft Agent Framework(企业导向)</strong></p>
<ul>
<li>专为多智能体协作设计</li>
<li>内置工作流编排能力(Sequential、Concurrent、Handoff、GroupChat)</li>
<li>支持Handoff转移模式和GroupChat管理模式</li>
<li>与Azure AI Foundry深度集成</li>
<li>同时支持.NET和Python</li>
</ul>
<h3 id="agent-framework的核心优势">Agent Framework的核心优势</h3>
<ol>
<li><strong>原生多智能体支持</strong>:无需手动管理智能体间的通信,框架自动处理消息路由</li>
<li><strong>声明式工作流</strong>:通过<code>AgentWorkflowBuilder</code>构建复杂协作场景</li>
<li><strong>内置编排模式</strong>:
<ul>
<li><strong>Handoff模式</strong>:智能体通过function calling实现控制权转移</li>
<li><strong>GroupChat模式</strong>:通过<code>GroupChatManager</code>选择下一个发言智能体(支持RoundRobin、Prompt-based等策略)</li>
</ul>
</li>
<li><strong>状态管理</strong>:支持checkpoint存储,可恢复中断的工作流</li>
</ol>
<h2 id="大模型基础知识科普">大模型基础知识科普</h2>
<p>在使用框架之前,我们需要理解大模型的工作原理。很多开发者对大模型有神秘感,其实它本质上就是一个HTTP API调用。</p>
<h3 id="llm-api的本质">LLM API的本质</h3>
<p>让我们用curl演示一个最简单的OpenAI API调用:</p>
<pre><code class="language-bash">curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
    "model": "gpt-4o-mini",
    "messages": [
      {
      "role": "user",
      "content": "你好,请介绍一下自己"
      }
    ]
}'
</code></pre>
<p>响应结果:</p>
<pre><code class="language-json">{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-4o-mini",
"choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "你好!我是一个AI助手,可以回答问题、提供建议..."
    },
    "finish_reason": "stop"
}]
}
</code></pre>
<p><strong>关键点</strong>:</p>
<ol>
<li>LLM就是一个普通的HTTP接口</li>
<li>输入:对话历史(messages数组)</li>
<li>输出:AI生成的回复(content字段)</li>
<li>所有复杂的Agent功能都是框架基于这个简单API构建的</li>
</ol>
<h3 id="函数调用function-calling">函数调用(Function Calling)</h3>
<p>函数调用是让LLM能够操作外部工具的关键机制。</p>
<p><strong>工作流程</strong>:</p>
<ol>
<li>开发者定义可用的函数(工具)</li>
<li>LLM根据用户意图决定调用哪个函数</li>
<li>框架执行函数并获取结果</li>
<li>将结果返回给LLM继续对话</li>
</ol>
<p>示例 - 定义天气查询函数:</p>
<pre><code class="language-json">{
"name": "get_weather",
"description": "查询指定城市的天气信息",
"parameters": {
    "type": "object",
    "properties": {
      "city": {
      "type": "string",
      "description": "城市名称,例如:北京"
      }
    },
    "required": ["city"]
}
}
</code></pre>
<p>LLM的调用响应:</p>
<pre><code class="language-json">{
"role": "assistant",
"content": null,
"function_call": {
    "name": "get_weather",
    "arguments": "{\"city\": \"北京\"}"
}
}
</code></pre>
<p><strong>重点理解</strong>:</p>
<ul>
<li>LLM不会直接执行函数,只是"建议"调用</li>
<li>框架负责解析并执行函数</li>
<li>执行结果需要再次发送给LLM才能生成最终回复</li>
</ul>
<h3 id="mcpmodel-context-protocol">MCP(Model Context Protocol)</h3>
<p>MCP是新兴的标准化协议,用于LLM与外部工具的通信。</p>
<p><strong>MCP的优势</strong>:</p>
<ol>
<li><strong>标准化接口</strong>:不同工具遵循统一协议</li>
<li><strong>动态工具发现</strong>:运行时加载工具</li>
<li><strong>安全隔离</strong>:工具在独立进程运行</li>
</ol>
<p>在我们的示例项目中,使用MCP集成了阿里云通义万相图片生成能力。</p>
<h2 id="agent-groupchat项目解析">agent-groupchat项目解析</h2>
<h3 id="项目架构">项目架构</h3>
<p>项目采用.NET Aspire编排,前后端分离架构:</p>
<pre><code>agent-groupchat/
├── AgentGroupChat.AppHost/         # Aspire编排入口
│   └── Program.cs                  # 服务编排配置

├── AgentGroupChat.AgentHost/       # 后端API服务(.NET 9)
│   ├── Services/
│   │   ├── AgentChatService.cs   # 核心聊天服务
│   │   ├── WorkflowManager.cs      # 工作流管理
│   │   ├── AgentRepository.cs      # 智能体配置管理
│   │   └── AgentGroupRepository.cs # 群组管理
│   ├── Models/
│   │   ├── AgentProfile.cs         # 智能体模型
│   │   └── AgentGroup.cs         # 群组模型
│   └── Program.cs                  # API端点

├── AgentGroupChat.Web/             # Blazor WebAssembly前端
│   ├── Components/
│   │   ├── Pages/
│   │   │   ├── Home.razor          # 聊天主页面
│   │   │   └── Admin.razor         # 管理后台
│   │   └── Layout/
│   │       └── MainLayout.razor    # 主布局
│   ├── Services/
│   │   └── AgentHostClient.cs      # API客户端
│   └── Program.cs                  # 前端入口

└── AgentGroupChat.ServiceDefaults/ # 共享服务配置
    └── Extensions.cs               # OpenTelemetry/健康检查
</code></pre>
<h3 id="aspire编排说明">Aspire编排说明</h3>
<p><strong>什么是.NET Aspire?</strong></p>
<p>.NET Aspire是微软推出的云原生应用编排框架,简化分布式应用的开发和部署:</p>
<ul>
<li><strong>服务发现</strong>:自动解析服务地址,前端无需硬编码API地址</li>
<li><strong>统一启动</strong>:一个命令启动所有服务</li>
<li><strong>可观测性</strong>:内置OpenTelemetry遥测数据收集</li>
<li><strong>Dashboard</strong>:实时查看服务状态、日志、指标</li>
</ul>
<p><strong>AppHost配置</strong>(<code>AgentGroupChat.AppHost/Program.cs</code>):</p>
<pre><code class="language-csharp">var builder = DistributedApplication.CreateBuilder(args);

// 添加后端API服务
var agentHost = builder.AddProject&lt;Projects.AgentGroupChat_AgentHost&gt;("agenthost");

// 添加Blazor前端,引用后端服务
builder.AddProject&lt;Projects.AgentGroupChat_Web&gt;("webfrontend")
    .WithExternalHttpEndpoints()// 暴露外部访问端口
    .WithReference(agentHost)      // 注入agenthost服务发现信息
    .WaitFor(agentHost);         // 等待后端启动完成

builder.Build().Run();
</code></pre>
<p><strong>服务发现原理</strong>:</p>
<p>前端通过Aspire自动获取后端地址(<code>Program.cs</code>):</p>
<pre><code class="language-csharp">// Web项目的Program.cs
var agentHostUrl = builder.Configuration["AgentHostUrl"] ?? "https://localhost:7390";
builder.Services.AddScoped(sp =&gt; new HttpClient { BaseAddress = new Uri(agentHostUrl) });
</code></pre>
<p>Aspire会自动将<code>agenthost</code>服务的实际地址注入到配置中。</p>
<h3 id="智能体定义">智能体定义</h3>
<p>项目创建了6个性格各异的AI美女角色,组成"AI世界公馆":</p>
<p><strong>艾莲 (Elena)</strong></p>
<pre><code class="language-csharp">new PersistedAgentProfile
{
    Id = "elena",
    Name = "艾莲",
    Avatar = "🧠",
    SystemPrompt = "你是艾莲,一位来自巴黎的人文学者,专注于哲学、艺术和文学研究...",
    Description = "巴黎研究员,擅长哲学、艺术与思辨分析",
    Personality = "理性、深邃,喜欢引经据典,用哲学视角看世界"
}
</code></pre>
<p><strong>莉子 (Rina)</strong></p>
<pre><code class="language-csharp">new PersistedAgentProfile
{
    Id = "rina",
    Name = "莉子",
    Avatar = "🎮",
    SystemPrompt = "你是莉子,来自东京的元气少女,热爱动漫、游戏和可爱的事物...",
    Description = "东京元气少女,热爱动漫、游戏和可爱事物",
    Personality = "活泼、热情,说话带感叹号,喜欢用可爱的emoji"
}
</code></pre>
<p>其他角色:</p>
<ul>
<li><strong>克洛伊 (Chloe)</strong>:纽约科技极客</li>
<li><strong>安妮 (Annie)</strong>:洛杉矶时尚博主</li>
<li><strong>苏菲 (Sophie)</strong>:伦敦哲学诗人</li>
</ul>
<h3 id="智能路由实现">智能路由实现</h3>
<p>这是项目的核心亮点 - Triage Agent自动将用户消息路由到最合适的AI角色。</p>
<p><strong>Triage Agent配置</strong>:</p>
<pre><code class="language-csharp">var triageSystemPrompt = @"你是AI世界公馆的智能路由系统。

【核心规则】
1. 永远不要生成文本回复 - 你对用户完全透明
2. 立即调用handoff函数,不需要解释
3. 不要确认、问候或回应 - 只默默路由

【路由策略】
1. **直接提及**:用户用 @ 提到角色名,立即路由到该角色
2. **话题匹配**:
   - 哲学/艺术/文学 → 艾莲
   - 动漫/游戏/萌文化 → 莉子
   - 科技/编程/AI → 克洛伊
   - 时尚/美妆/生活 → 安妮
   - 诗歌/文学/情感 → 苏菲
3. **语气风格**:活泼→莉子,理性→艾莲,冷静→克洛伊
4. **上下文连贯**:查看对话历史,如果上一条是某专家回复且话题相关,继续路由到该专家

示例:
- ""@莉子 推荐动漫"" → handoff_to_rina
- ""如何学习机器学习?"" → handoff_to_chloe
- ""最新的时尚趋势是什么?"" → handoff_to_annie
";
</code></pre>
<p><strong>Handoff实现</strong>:</p>
<pre><code class="language-csharp">// 创建Handoff工作流
var workflow = _workflowManager.GetOrCreateWorkflow(groupId);

// 运行工作流
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));

// 监听事件流
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
    if (evt is AgentRunUpdateEvent agentUpdate)
    {
      // 检测到specialist agent执行
      if (agentUpdate.ExecutorId != "triage")
      {
            var profile = _agentRepository.Get(agentUpdate.ExecutorId);
            
            // 提取LLM生成的文本
            var textContent = agentUpdate.Update.Contents
                .OfType&lt;TextContent&gt;()
                .FirstOrDefault();
            
            // 构建响应
            summaries.Add(new ChatMessageSummary
            {
                AgentId = agentUpdate.ExecutorId,
                AgentName = profile?.Name,
                AgentAvatar = profile?.Avatar,
                Content = textContent?.Text,
                IsUser = false
            });
      }
    }
}
</code></pre>
<h3 id="关键技术点">关键技术点</h3>
<p><strong>1. 动态智能体加载</strong></p>
<p>智能体配置存储在LiteDB中,支持运行时动态更新:</p>
<pre><code class="language-csharp">public class AgentRepository
{
    public List&lt;PersistedAgentProfile&gt; GetAllEnabled()
    {
      return _collection
            .Find(a =&gt; a.Enabled)
            .ToList();
    }
   
    public void Upsert(PersistedAgentProfile agent)
    {
      _collection.Upsert(agent);
    }
}
</code></pre>
<p><strong>2. 工作流管理</strong></p>
<p>每个智能体组有独立的工作流实例:</p>
<pre><code class="language-csharp">public class WorkflowManager
{
    private readonly Dictionary&lt;string, Workflow&gt; _workflows = new();
   
    public Workflow GetOrCreateWorkflow(string groupId)
    {
      if (!_workflows.TryGetValue(groupId, out var workflow))
      {
            var group = _groupRepository.Get(groupId);
            workflow = BuildHandoffWorkflow(group);
            _workflows = workflow;
      }
      return workflow;
    }
}
</code></pre>
<p><strong>3. 消息持久化</strong></p>
<p>使用LiteDB存储会话历史:</p>
<pre><code class="language-csharp">public class PersistedSessionService
{
    public void AddMessage(string sessionId, ChatMessageSummary message)
    {
      var doc = new BsonDocument
      {
            ["SessionId"] = sessionId,
            ["AgentId"] = message.AgentId,
            ["Content"] = message.Content,
            ["Timestamp"] = message.Timestamp,
            ["IsUser"] = message.IsUser
      };
      
      _messagesCollection.Insert(doc);
    }
}
</code></pre>
<h2 id="国内用户运行指南">国内用户运行指南</h2>
<h3 id="方式一使用阿里云百炼平台">方式一:使用阿里云百炼平台</h3>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202510/1690009-20251028222223856-1864861733.png" alt="img" loading="lazy"></p>
<ol>
<li><strong>获取API密钥</strong></li>
</ol>
<p>访问 阿里云百炼,创建应用并获取API Key。</p>
<ol start="2">
<li><strong>配置appsettings.json</strong></li>
</ol>
<pre><code class="language-json">{
"DefaultModelProvider": "OpenAI",
"OpenAI": {
    "BaseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "ModelName": "qwen-plus",
    "ApiKey": "sk-your-api-key"
}
}
</code></pre>
<ol start="3">
<li><strong>配置MCP生图</strong></li>
</ol>
<p>需要开通MCP生图服务</p>
<p>使用的key也是和百炼的模型key一致</p>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202510/1690009-20251028222517138-409894711.png" alt="img" loading="lazy"></p>
<pre><code class="language-json">{
"McpServers": {
    "Servers": [
      {
      "Id": "dashscope-text-to-image",
      "Name": "DashScope Text-to-Image",
      "Endpoint": "https://dashscope.aliyuncs.com/api/v1/mcps/TextToImage/sse",
      "AuthType": "Bearer",
      "BearerToken": "",
      "TransportMode": "Sse",
      "Enabled": true,
      "Description": "阿里云 DashScope 文生图服务,用于生成图像"
      }
    ]
}
}
</code></pre>
<ol start="4">
<li><strong>运行项目</strong></li>
</ol>
<p><strong>方式A:使用Aspire一键启动(推荐)</strong></p>
<pre><code class="language-bash">cd agent-groupchat
dotnet run --project AgentGroupChat.AppHost
</code></pre>
<p>Aspire Dashboard会自动打开(<code>http://localhost:15220</code>),显示:</p>
<ul>
<li><strong>agenthost</strong>:后端API服务</li>
<li><strong>webfrontend</strong>:Blazor前端</li>
</ul>
<p>直接点击webfrontend的URL即可访问应用。</p>
<p><strong>方式B:独立启动各服务</strong></p>
<p>终端1 - 启动后端:</p>
<pre><code class="language-bash">cd agent-groupchat/AgentGroupChat.AgentHost
dotnet run
# 记下端口,如 https://localhost:7390
</code></pre>
<p>终端2 - 启动前端(需先配置后端地址):</p>
<p>编辑<code>AgentGroupChat.Web/wwwroot/appsettings.json</code>:</p>
<pre><code class="language-json">{
"AgentHostUrl": "https://localhost:7390"
}
</code></pre>
<p>然后启动:</p>
<pre><code class="language-bash">cd agent-groupchat/AgentGroupChat.Web
dotnet run
</code></pre>
<p>访问前端地址(如<code>https://localhost:5001</code>)</p>
<h3 id="方式二使用deepseek">方式二:使用DeepSeek</h3>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202510/1690009-20251028222255385-1659064413.png" alt="img" loading="lazy"></p>
<ol>
<li><strong>获取API密钥</strong></li>
</ol>
<p>访问 DeepSeek开放平台</p>
<ol start="2">
<li><strong>配置</strong></li>
</ol>
<pre><code class="language-json">{
"DefaultModelProvider": "OpenAI",
"OpenAI": {
    "BaseUrl": "https://api.deepseek.com/v1",
    "ModelName": "deepseek-chat",
    "ApiKey": "sk-your-api-key"
}
}
</code></pre>
<h3 id="方式三使用azure-ai-foundry">方式三:使用Azure AI Foundry</h3>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202510/1690009-20251028222318259-1078875047.png" alt="img" loading="lazy"></p>
<ol>
<li><strong>创建Azure OpenAI资源</strong></li>
</ol>
<p>访问 AI Foundry,创建Azure OpenAI服务。</p>
<ol start="2">
<li><strong>部署模型</strong></li>
</ol>
<p>在AI Foundry中部署 <code>gpt-4o-mini</code> 模型。</p>
<ol start="3">
<li><strong>配置</strong></li>
</ol>
<pre><code class="language-json">{
"DefaultModelProvider": "AzureOpenAI",
"AzureOpenAI": {
    "Endpoint": "https://your-resource.openai.azure.com/",
    "DeploymentName": "gpt-4o-mini",
    "ApiKey": "your-api-key"
}
}
</code></pre>
<h2 id="实战演示">实战演示</h2>
<h3 id="测试场景1直接提及">测试场景1:直接提及</h3>
<p><strong>输入</strong>:<code>@莉子 推荐几部最近的热门动漫</code></p>
<p><strong>路由过程</strong>:</p>
<ol>
<li>Triage Agent检测到 <code>@莉子</code></li>
<li>调用 <code>handoff_to_rina</code> 函数</li>
<li>莉子接收消息并回复</li>
</ol>
<p><strong>输出</strong>:</p>
<pre><code>莉子:哇!最近的新番超棒的呢!✨
强烈推荐《葬送的芙莉莲》,这部番真的是神作级别!
还有《咒术回战》第二季也超燃的!
如果喜欢轻松搞笑的,《关于我转生变成史莱姆这档事》第三季也很有趣哦~ (◕‿◕✿)
</code></pre>
<h3 id="测试场景2话题路由">测试场景2:话题路由</h3>
<p><strong>输入</strong>:<code>最近在学习机器学习,有什么建议吗?</code></p>
<p><strong>路由过程</strong>:</p>
<ol>
<li>Triage Agent识别"机器学习"属于科技话题</li>
<li>路由到克洛伊(科技专家)</li>
</ol>
<p><strong>输出</strong>:</p>
<pre><code>克洛伊:机器学习入门的话,建议从这几个方面开始:

1. **数学基础**:线性代数、概率统计、微积分
2. **编程**:Python是首选,熟练使用NumPy、Pandas
3. **经典算法**:先理解监督学习(线性回归、决策树)
4. **框架**:PyTorch或TensorFlow二选一
5. **实战项目**:Kaggle上有很多适合新手的数据集

推荐课程:吴恩达的Machine Learning课程(Coursera)
</code></pre>
<h3 id="测试场景3上下文连贯">测试场景3:上下文连贯</h3>
<p><strong>对话1</strong>:</p>
<pre><code>用户:什么是存在主义?
艾莲:存在主义认为"存在先于本质",强调个体的自由选择和责任...
</code></pre>
<p><strong>对话2</strong>(紧接上文):</p>
<pre><code>用户:那萨特的观点具体是什么?
系统:检测到话题延续,继续路由到艾莲
艾莲:萨特是存在主义的代表人物,他在《存在与虚无》中提出...
</code></pre>
<h2 id="核心代码详解">核心代码详解</h2>
<h3 id="1-workflowmanager核心逻辑">1. WorkflowManager核心逻辑</h3>
<pre><code class="language-csharp">public class WorkflowManager
{
    private Workflow BuildHandoffWorkflow(AgentGroup group)
    {
      // 1. 创建Triage Agent
      var triageAgent = new ChatClientAgent(
            _chatClient,
            group.TriageSystemPrompt ?? DefaultTriagePrompt,
            "triage",
            "Routes messages to appropriate agent"
      );
      
      // 2. 加载组内所有智能体
      var specialists = group.AgentIds
            .Select(id =&gt; _agentRepository.Get(id))
            .Where(a =&gt; a != null)
            .Select(a =&gt; new ChatClientAgent(
                _chatClient,
                a.SystemPrompt,
                a.Id,
                a.Description
            ))
            .ToList();
      
      // 3. 构建Handoff工作流
      var builder = AgentWorkflowBuilder
            .CreateHandoffBuilderWith(triageAgent)
            .WithHandoffs(triageAgent, specialists)// Triage可以切换到任何专家
            .WithHandoffs(specialists, triageAgent); // 专家可以切回Triage
      
      return builder.Build();
    }
}
</code></pre>
<h3 id="2-事件流处理">2. 事件流处理</h3>
<pre><code class="language-csharp">public async Task&lt;List&lt;ChatMessageSummary&gt;&gt; SendMessageAsync(
    string message,
    string sessionId,
    string? groupId = null)
{
    var summaries = new List&lt;ChatMessageSummary&gt;();
   
    // 添加用户消息
    summaries.Add(new ChatMessageSummary
    {
      Content = message,
      IsUser = true,
      Timestamp = DateTime.UtcNow
    });
   
    // 获取工作流
    var workflow = _workflowManager.GetOrCreateWorkflow(groupId);
   
    // 加载历史消息
    var messages = LoadHistoryMessages(sessionId);
    messages.Add(new AIChatMessage(ChatRole.User, message));
   
    // 运行工作流
    await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
    await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
   
    string? currentExecutorId = null;
    ChatMessageSummary? currentSummary = null;
   
    // 处理事件流
    await foreach (WorkflowEvent evt in run.WatchStreamAsync())
    {
      if (evt is AgentRunUpdateEvent agentUpdate)
      {
            // 跳过Triage Agent的输出(它只负责路由)
            var executorIdPrefix = agentUpdate.ExecutorId.Split('_');
            if (executorIdPrefix.Equals("triage", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }
            
            // 检测到新的specialist agent
            if (agentUpdate.ExecutorId != currentExecutorId)
            {
                currentExecutorId = agentUpdate.ExecutorId;
                var profile = _agentRepository.Get(currentExecutorId);
               
                currentSummary = new ChatMessageSummary
                {
                  AgentId = currentExecutorId,
                  AgentName = profile?.Name ?? currentExecutorId,
                  AgentAvatar = profile?.Avatar ?? "🤖",
                  Content = "",
                  IsUser = false,
                  Timestamp = DateTime.UtcNow
                };
               
                summaries.Add(currentSummary);
            }
            
            // 累积文本内容
            if (currentSummary != null)
            {
                var textContent = agentUpdate.Update.Contents
                  .OfType&lt;TextContent&gt;()
                  .FirstOrDefault();
               
                if (textContent != null &amp;&amp; !string.IsNullOrWhiteSpace(textContent.Text))
                {
                  currentSummary.Content += textContent.Text;
                }
            }
      }
    }
   
    // 保存到数据库
    SaveMessages(sessionId, summaries);
   
    return summaries.Where(s =&gt; !s.IsUser).ToList();
}
</code></pre>
<h3 id="3-blazor-webassembly前端">3. Blazor WebAssembly前端</h3>
<p><strong>为什么选择Blazor WASM?</strong></p>
<ul>
<li>完全在浏览器运行,无需服务器端SignalR连接</li>
<li>与后端API完全解耦,便于扩展</li>
<li>利用.NET生态,C#编写前端逻辑</li>
</ul>
<p><strong>API客户端封装</strong>(<code>AgentHostClient.cs</code>):</p>
<pre><code class="language-csharp">public class AgentHostClient
{
    private readonly HttpClient _httpClient;

    public AgentHostClient(HttpClient httpClient, ILogger&lt;AgentHostClient&gt; logger)
    {
      _httpClient = httpClient;
      _logger = logger;
    }

    // 发送消息
    public async Task&lt;List&lt;ChatMessageSummary&gt;&gt; SendMessageAsync(
      string sessionId, string message, string? groupId = null)
    {
      var request = new { Message = message, SessionId = sessionId, GroupId = groupId };
      var response = await _httpClient.PostAsJsonAsync("api/chat", request);
      return await response.Content.ReadFromJsonAsync&lt;List&lt;ChatMessageSummary&gt;&gt;() ?? [];
    }

    // 获取智能体列表
    public async Task&lt;List&lt;AgentProfile&gt;&gt; GetAgentsAsync()
    {
      return await _httpClient.GetFromJsonAsync&lt;List&lt;AgentProfile&gt;&gt;("api/agents") ?? [];
    }
}
</code></pre>
<p><strong>主页面交互</strong>(<code>Home.razor</code>):</p>
<pre><code class="language-razor">@page "/"
@inject AgentHostClient AgentHostClient
@inject IJSRuntime JSRuntime

&lt;MudContainer MaxWidth="MaxWidth.False"&gt;
    &lt;MudPaper Elevation="2" Class="chat-container"&gt;
      &lt;!-- 消息列表 --&gt;
      &lt;div id="messages-container" class="messages-area"&gt;
            @foreach (var msg in _messages)
            {
                @if (msg.IsUser)
                {
                  &lt;div class="user-message"&gt;@msg.Content&lt;/div&gt;
                }
                else
                {
                  &lt;div class="agent-message"&gt;
                        &lt;MudAvatar&gt;@msg.AgentAvatar&lt;/MudAvatar&gt;
                        &lt;div&gt;
                            &lt;strong&gt;@msg.AgentName&lt;/strong&gt;
                            &lt;div&gt;@((MarkupString)Markdown.ToHtml(msg.Content))&lt;/div&gt;
                        &lt;/div&gt;
                  &lt;/div&gt;
                }
            }
      &lt;/div&gt;

      &lt;!-- 输入框 --&gt;
      &lt;MudTextField @bind-Value="_inputMessage"
                      Placeholder="输入消息..."
                      OnKeyDown="HandleKeyPress" /&gt;
      &lt;MudButton OnClick="SendMessage" Disabled="_isSending"&gt;发送&lt;/MudButton&gt;
    &lt;/MudPaper&gt;
&lt;/MudContainer&gt;

@code {
    private string _inputMessage = "";
    private List&lt;ChatMessageSummary&gt; _messages = new();
    private bool _isSending = false;

    private async Task SendMessage()
    {
      if (string.IsNullOrWhiteSpace(_inputMessage)) return;
      
      _isSending = true;
      try
      {
            // 调用后端API
            var response = await AgentHostClient.SendMessageAsync(
                _currentSession.Id,
                _inputMessage,
                _currentSession.GroupId
            );
            
            // 更新消息列表
            _messages.AddRange(response);
            
            // 自动滚动到底部
            await JSRuntime.InvokeVoidAsync("smoothScrollToBottom", "messages-container");
      }
      finally
      {
            _isSending = false;
            _inputMessage = "";
      }
    }

    // Enter键发送,Shift+Enter换行
    private async Task HandleKeyPress(KeyboardEventArgs e)
    {
      if (e.Key == "Enter" &amp;&amp; !e.ShiftKey)
      {
            await SendMessage();
      }
    }
}
</code></pre>
<p><strong>MudBlazor组件库</strong>:</p>
<p>项目使用MudBlazor构建现代化UI:</p>
<pre><code class="language-razor">&lt;!-- 卡片容器 --&gt;
&lt;MudPaper Elevation="2" Class="pa-4"&gt;
    &lt;MudText Typo="Typo.h5"&gt;标题&lt;/MudText&gt;
&lt;/MudPaper&gt;

&lt;!-- 输入框 --&gt;
&lt;MudTextField @bind-Value="value" Label="标签" Variant="Variant.Outlined" /&gt;

&lt;!-- 按钮 --&gt;
&lt;MudButton Variant="Variant.Filled" Color="Color.Primary"&gt;按钮&lt;/MudButton&gt;

&lt;!-- 头像 --&gt;
&lt;MudAvatar Color="Color.Primary"&gt;🧠&lt;/MudAvatar&gt;
</code></pre>
<h2 id="总结与展望">总结与展望</h2>
<p>通过这个AI美女聊天群组项目,我们学习了:</p>
<ol>
<li><strong>大模型基础</strong>:理解LLM API、Function Calling和MCP协议</li>
<li><strong>多智能体架构</strong>:掌握Handoff模式和Triage智能路由</li>
<li><strong>Agent Framework</strong>:使用<code>AgentWorkflowBuilder</code>构建Handoff工作流</li>
<li><strong>Aspire编排</strong>:通过.NET Aspire实现服务发现和统一启动</li>
<li><strong>Blazor WASM</strong>:前后端分离架构,完全客户端渲染</li>
<li><strong>实战技巧</strong>:动态加载、状态管理、LiteDB持久化</li>
</ol>
<h3 id="参考资源">参考资源</h3>
<p><strong>官方文档</strong></p>
<ul>
<li>Microsoft Agent Framework 文档</li>
<li>Agent Framework GitHub</li>
<li>Azure AI Foundry</li>
</ul>
<p><strong>国内平台</strong></p>
<ul>
<li>阿里云百炼</li>
<li>DeepSeek API</li>
</ul>
<p><strong>示例代码</strong></p>
<ul>
<li>agent-groupchat 完整代码</li>
</ul>
<hr>
<p><em>如果这篇文章对你有帮助,欢迎点赞收藏!有任何问题也欢迎在评论区讨论。</em></p><br><br>
来源:https://www.cnblogs.com/GreenShade/p/19172851
頁: [1]
查看完整版本: .NET开发上手Microsoft Agent Framework(一)从开发一个AI美女聊天群组开始