☰
用 .NET + Microsoft Agent Framework 搭一个 AI 美女聊天群组(一):Aspire 编排与 TaoToken 配置起步
2026/9/28 7:22:42 网站建设 项目流程

1. 为什么 .NET 开发者需要一个「群聊骨架」来上手 Agent Framework

如果你写过 Semantic Kernel 的单 Agent 应用,再回头看 Microsoft Agent Framework,第一反应大概率是:多智能体协作的代码量并没有想象中那么夸张,真正让人卡住的是「怎么把多个 Agent 跑起来、怎么让它们互相说话、模型 Key 从哪来」。我这次的目标很具体:用 .NET 搭一个 AI 美女聊天群组,让几个性格不同的角色在同一个会话里轮流发言,而第一步不是写 Prompt,而是先把 Aspire 编排和模型通道跑通。

Microsoft Agent Framework 是微软在多智能体方向上的新一代框架,它把 Handoff、GroupChat、Sequential、Concurrent 这些协作模式做成了内置工作流,同时支持 .NET 和 Python。对 .NET 开发者来说,它最大的价值是:你不需要自己手写消息路由和状态机,框架会帮你把「谁该说话、说完传给谁」这件事管起来。适合谁?适合已经会 C#、写过 Web API、想从单 Agent 过渡到多 Agent 协作的开发者。

这一篇是系列的第一篇,只做一件事:把 Aspire AppHost 和 Agent 注册的最小骨架搭起来,再通过 TaoToken 统一 Key/API 通道完成一次真实的模型调用。跑通之后,你会看到群聊消息在多个 Agent 之间流转,而不是只有一个机器人在自说自话。后面几篇再逐步加角色人设、路由策略和持久化。

2. TaoToken 前置:统一 Key 与 API 通道

在写 Agent 代码之前,先把模型通道确定下来。多 Agent 群聊会频繁调用模型,如果每个 Agent 各配一套 Key,管理成本会很高。TaoToken 的思路是提供一个统一的 API 入口,你只需要一个 Key,就能在同一个通道里切换不同模型,Agent 侧只认一个 BaseUrl 和一个 ApiKey。

你需要先拿到两样东西:一个可用的 API Key,以及确认调用地址。控制台入口在这里:

控制台与 Key 管理:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

创建 Key 的页面:

API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

调用时使用的 API 根地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 OpenAI 兼容的 BaseUrl 使用。也就是说,在 .NET 里你可以继续用OpenAI的 SDK,只把Endpoint指向 TaoToken 即可,代码几乎不用改。

这里有个容易踩的坑:很多人会把控制台地址和 API 地址搞混。控制台是给人看的网页,API 地址是给程序调用的。你在appsettings.json里填的必须是https://taotoken.net/api,而不是控制台那个带一堆参数的链接。另外,Key 不要硬编码进代码,放进用户机密或环境变量,后面配置章节会给具体做法。

如果你只是想先验证模型通不通,不想马上写代码,可以用模型对话页面直接试:

模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

3. 可复制配置:Aspire AppHost 与 Agent 注册骨架

3.1 项目结构与 Aspire 编排

先建一个 Aspire 解决方案,包含三个项目:AgentGroupChat.AppHost(编排入口)、AgentGroupChat.AgentHost(后端 API 与 Agent 逻辑)、AgentGroupChat.Web(前端,本篇可以先不细做)。AppHost 的Program.cs是整个本地编排的起点:

var builder = DistributedApplication.CreateBuilder(args); // 后端 Agent 服务 var agentHost = builder.AddProject<Projects.AgentGroupChat_AgentHost>("agenthost") .WithEnvironment("TAOTOKEN_API_KEY", builder.Configuration["TAOTOKEN_API_KEY"]) .WithEnvironment("TAOTOKEN_BASE_URL", "https://taotoken.net/api"); // 前端引用后端,等待后端就绪 builder.AddProject<Projects.AgentGroupChat_Web>("webfrontend") .WithExternalHttpEndpoints() .WithReference(agentHost) .WaitFor(agentHost); builder.Build().Run();

这段代码做了三件事:注册后端服务、把 TaoToken 的 Key 和 BaseUrl 作为环境变量注入、让前端等待后端启动完成。Aspire 会自动做服务发现,前端不需要硬编码后端端口。

3.2 Agent 注册与模型客户端

在AgentHost里,先注册一个共享的ChatClient,所有 Agent 复用它。这里用 OpenAI 兼容的写法,把地址指向 TaoToken:

var builder = WebApplication.CreateBuilder(args); var apiKey = builder.Configuration["TAOTOKEN_API_KEY"] ?? throw new InvalidOperationException("缺少 TAOTOKEN_API_KEY"); var baseUrl = builder.Configuration["TAOTOKEN_BASE_URL"] ?? "https://taotoken.net/api"; builder.Services.AddSingleton(sp => { var options = new OpenAIClientOptions { Endpoint = new Uri(baseUrl) }; return new ChatClient( model: "gpt-4o-mini", credential: new ApiKeyCredential(apiKey), options: options); }); builder.Services.AddSingleton<AgentRegistry>();

AgentRegistry负责把角色定义转成框架里的 Agent 实例。先定义角色模型:

public record AgentProfile( string Id, string Name, string Avatar, string SystemPrompt, string Description); public class AgentRegistry { private readonly ChatClient _chatClient; private readonly List<AgentProfile> _profiles = new() { new("elena", "艾莲", "EL", "你是艾莲,一位专注哲学与文学的研究者,说话理性、喜欢引经据典。", "哲学与文学"), new("rina", "莉娜", "RI", "你是莉娜,来自东京的元气少女,热爱动漫和游戏,说话活泼带感叹号。", "动漫与游戏"), new("chloe", "克洛伊", "CH", "你是克洛伊,纽约科技博主,擅长用通俗语言解释编程和 AI。", "科技与编程") }; public AgentRegistry(ChatClient chatClient) => _chatClient = chatClient; public IReadOnlyList<AgentProfile> Profiles => _profiles; public ChatClientAgent CreateAgent(AgentProfile profile) => new(_chatClient, profile.SystemPrompt, profile.Id, profile.Description); }

3.3 settings.json 关键字段

后端appsettings.json只放非敏感配置,Key 走用户机密或环境变量:

{ "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "DefaultModel": "gpt-4o-mini", "AgentGroup": { "TriageAgentId": "triage", "MaxTurns": 6 } }

本地开发时用dotnet user-secrets存 Key:

cd AgentGroupChat.AgentHost dotnet user-secrets init dotnet user-secrets set "TAOTOKEN_API_KEY" "你的Key"

如果你更习惯用config.toml管理(比如配合某些 CLI 工具),对应字段可以这样写,注意它只是配置载体,程序读取逻辑要自己接:

[taotoken] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_model = "gpt-4o-mini"

4. 验证请求:群聊消息如何流转

4.1 最小工作流构建

先用 Handoff 模式搭一个最小群聊:一个 Triage Agent 负责路由,三个角色 Agent 负责回复。构建逻辑放在WorkflowManager里:

public class WorkflowManager { private readonly AgentRegistry _registry; private readonly ChatClient _chatClient; public WorkflowManager(AgentRegistry registry, ChatClient chatClient) { _registry = registry; _chatClient = chatClient; } public Workflow Build() { var triage = new ChatClientAgent( _chatClient, "你是路由系统,只负责把用户消息转给最合适的角色,不要自己回答。", "triage", "消息路由"); var specialists = _registry.Profiles .Select(p => _registry.CreateAgent(p)) .ToList(); var builder = AgentWorkflowBuilder .CreateHandoffBuilderWith(triage) .WithHandoffs(triage, specialists) .WithHandoffs(specialists, triage); return builder.Build(); } }

4.2 发送一条消息并观察事件流

在 API 端点里触发一次群聊,监听事件流,把非 Triage 的输出收集起来:

app.MapPost("/api/chat", async (ChatRequest req, WorkflowManager wm) => { var workflow = wm.Build(); var messages = new List<AIChatMessage> { new(ChatRole.User, req.Message) }; var results = new List<object>(); await using var run = await InProcessExecution.StreamAsync(workflow, messages); await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); await foreach (var evt in run.WatchStreamAsync()) { if (evt is AgentRunUpdateEvent update) { var id = update.ExecutorId.Split('_')[0]; if (id.Equals("triage", StringComparison.OrdinalIgnoreCase)) continue; var text = update.Update.Contents .OfType<TextContent>() .FirstOrDefault()?.Text; if (!string.IsNullOrWhiteSpace(text)) results.Add(new { agent = id, content = text }); } } return Results.Ok(results); });

4.3 启动与预期结果

用 Aspire 一键启动:

dotnet run --project AgentGroupChat.AppHost

Aspire Dashboard 会自动打开,你能看到agenthost和webfrontend两个服务都变成 Running。用 curl 打一条消息:

curl -X POST https://localhost:7390/api/chat \ -H "Content-Type: application/json" \ -d '{"message":"最近有什么好看的动漫推荐吗?"}'

预期结果是:Triage Agent 识别出「动漫」关键词,把消息路由到莉娜,返回的 JSON 里agent字段是rina,content是莉娜风格的回复。如果你看到agent是triage或者内容为空,说明路由没生效,往下看排查章节。

5. 本篇常见错排查

5.1 401 或鉴权失败

最常见的原因是 Key 没注入成功。先确认环境变量是否真的传进去了:在 Aspire Dashboard 里点开agenthost,看 Environment 里有没有TAOTOKEN_API_KEY。如果为空,检查 AppHost 里builder.Configuration["TAOTOKEN_API_KEY"]是否读到了值——AppHost 自己也需要能读到这个配置,可以在 AppHost 项目里也执行一次dotnet user-secrets set。

另一个原因是 BaseUrl 写成了控制台地址。记住 API 根地址是https://taotoken.net/api,不要带/chat或查询参数,SDK 会自己拼/v1/chat/completions这类路径。

5.2 模型返回 404 或 model not found

这通常是模型名写错了。TaoToken 通道里可用的模型名以控制台展示为准,不要凭记忆写。如果你在ChatClient里写的是gpt-4o-mini,但通道里实际叫别的名字,就会 404。建议先在模型对话页面确认模型名,再填进代码。

5.3 群聊只有 Triage 回复

如果返回结果里agent一直是triage,说明 Handoff 没触发。检查两点:一是 Triage 的 SystemPrompt 里是否明确要求「不要自己回答,只做路由」;二是WithHandoffs是否把 specialists 正确传进去了。我试过把 Triage 的 Prompt 写得太宽松,它会直接自己回答,导致路由失效。

5.4 Aspire 启动后前端连不上后端

Aspire 的服务发现依赖WithReference。如果前端报连接被拒绝,检查 AppHost 里是否给前端加了.WithReference(agentHost)。另外.WaitFor(agentHost)能避免前端比后端先启动导致的偶发失败,建议保留。

5.5 事件流里文本被截断

多 Agent 流式输出时,AgentRunUpdateEvent会分多次推送文本片段。如果你只取第一次的TextContent,就会丢内容。正确做法是按ExecutorId累积,同一个 Agent 的片段拼起来再输出。上面的示例为了简洁只取了首个片段,生产代码里要改成累积逻辑。

6. 下一步:把骨架跑顺再谈人设

到这里,Aspire 编排、TaoToken 通道、Agent 注册、群聊消息流转这四件事已经串起来了。你现在拥有的不是一个完整的聊天产品,而是一个可运行的最小骨架——它的价值在于,后面加角色、换路由策略、接持久化,都是在这个骨架上做增量,而不是推倒重来。

如果你在验证模型阶段想快速试不同 Prompt 的效果,可以直接用模型对话页面调,不用每次都改代码重启:

模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

接入文档里有 OpenAI 兼容调用的完整字段说明,遇到参数不确定时对照着看:

接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

如果你打算把这个群聊继续做成长期跑的编码助手或 Agent 应用,Key 和额度管理会变成日常问题,可以了解下 Coding Plan:

Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

下一篇会在这个骨架上加角色人设和 GroupChat 轮询策略,让多个 Agent 真正「聊起来」,而不是一问一答。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询