编辑
2026-04-13
C#
00

目录

🤔 为啥要多个AI一块干活?
👨‍💻 运行效果
🏗️ 架构长啥样?核心三件套
1️⃣ 工厂模式:统一管理智能体的诞生
2️⃣ Semantic Kernel 的插件系统:给AI配装备
3️⃣ 代码执行引擎:闭环的关键
🎬 四个真实场景的玩法
场景1:单体AI + 工具集合(最简单的玩法)
场景2:自我修正循环(AI vs 代码执行器)
场景3:多智能体协作(经典的 Coder + Reviewer)
场景4:压力测试(强制多轮迭代)
💡 几个我在实战中发现的坑
🚀 怎样在自己的项目里用上这套系统
📊 真实数据:它能帮你省多少时间?
🎯 后续可以怎么玩
💭 最后的碎碎念

你有没有过这样的情况?一个人写代码,写得贼嗨;另一个人审代码,挑得贼狠;最后项目经理坐在中间,不停地在两人之间调和。嗯,这场面有点熟悉吧?

这次咱们要聊的就是这么一个有趣的东西——让多个AI智能体相互配合,模拟这套既"互相制约"又"相互促进"的协作模式。说白了,就是教会AI怎么像真实开发团队一样工作。

🤔 为啥要多个AI一块干活?

先来摊开讲讲现状。你肯定遇过那种情况:问ChatGPT写个算法,它给你甩来一段代码。你一运行,嘿,还真能用!但要说这代码多完美、多严谨?呃……那就得打个大问号了。有时候它不考虑边界情况,有时候逻辑绕得跟麻绳似的,有时候写完就再也改不了——因为它已经"走"了。

反过来想一下,如果有两个AI,一个专门写代码,另一个专门挑毛病,它们之间反复打磨,是不是能出更靠谱的东西?这就是 多智能体协作 的核心价值。不是让AI变成一个人,而是让AI们像一个真实的团队那样相互牵制。

现在有个框架叫 AutoGen,专门就是为了这事儿而生的。再配上 Semantic Kernel(微软搞的提示词编排工具)和 Roslyn(C# 的动态编译执行器),咱们就能搭出一套完整的自动化编程助手。

👨‍💻 运行效果

image.png

image.png

image.png

🏗️ 架构长啥样?核心三件套

让我先把框架拆开讲清楚。

1️⃣ 工厂模式:统一管理智能体的诞生

在代码里,有个叫 AgentFactory 的东西,专门用来批量生产智能体。为啥要这样做?因为这些智能体虽然各自有各自的人设,但它们的"出生过程"其实是相似的:

  • 连接到阿里云千问API(企业界用得多)
  • 配置系统提示词(SystemMessage)
  • 注册消息连接器和输出格式化器

代码这样写:

csharp
private static OpenAIClient CreateQwenClient(string apiKey) { var endpoint = new Uri(QwenEndpoint); var credential = new ApiKeyCredential(apiKey); var options = new OpenAIClientOptions { Endpoint = endpoint }; return new OpenAIClient(credential, options); }

看上去不起眼,但这就像一个模板。每次要生产一个新的智能体,咱们就基于这个模板,只需要改变它的"人设"(SystemMessage)就行了。

2️⃣ Semantic Kernel 的插件系统:给AI配装备

真实的开发工作,AI不能凭空想象。它需要工具。比如:

  • 需要算数?用 MathPlugin
  • 需要查时间?用 TimePlugin
  • 需要执行代码?用 CodeExecutionPlugin

这些插件就像给AI装上了"技能树"。它们通过 [KernelFunction] 这个标记暴露给AI,让AI能够在解决问题时动态地调用。

比如计算阶乘:

csharp
using Microsoft.SemanticKernel; using System.ComponentModel; namespace AutoGenDemo.Plugins; /// <summary> /// 数学工具 Plugin,演示 SK KernelFunction 与 AutoGen 的集成 /// </summary> public class MathPlugin { [KernelFunction, Description("对两个数求和")] public double Add( [Description("第一个数")] double a, [Description("第二个数")] double b) => a + b; [KernelFunction, Description("对两个数求乘积")] public double Multiply( [Description("第一个数")] double a, [Description("第二个数")] double b) => a * b; [KernelFunction, Description("计算一个整数的阶乘")] public long Factorial([Description("非负整数 n")] int n) { if (n < 0) throw new ArgumentException("n 必须 >= 0"); long result = 1; for (int i = 2; i <= n; i++) result *= i; return result; } }

AI 看到这个描述,就知道"哦,我遇到阶乘问题就可以调这个"。这叫 函数调用(Function Calling),是现代LLM的标配能力。

3️⃣ 代码执行引擎:闭环的关键

这里有个挺有意思的设计——CodeExecutionPluginRoslyn 来动态编译和执行 C# 代码:

csharp
using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.SemanticKernel; using System.ComponentModel; using System.Text; namespace AutoGenDemo.Plugins; /// <summary> /// 代码执行 Plugin:让 LLM 可以动态编写并执行 C# 代码片段 /// </summary> public class CodeExecutionPlugin { private static readonly ScriptOptions _options = ScriptOptions.Default .AddImports("System", "System.Linq", "System.Collections.Generic", "System.Math", "System.Text", "System.IO") .AddReferences(typeof(object).Assembly, typeof(Enumerable).Assembly); [KernelFunction, Description( "执行一段 C# 代码并返回执行结果或错误信息。" + "代码中用 return 语句返回最终结果(字符串或数字)。" + "如果代码有错误,返回值以 'ERROR:' 开头。")] public async Task<string> ExecuteCSharpAsync( [Description("要执行的 C# 代码片段,使用 return 返回结果")] string code) { try { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"\n[CodeExecutionPlugin] 执行代码:\n{code}\n"); Console.ResetColor(); var result = await CSharpScript.EvaluateAsync<object>(code, _options); var output = result?.ToString() ?? "(无返回值)"; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"[CodeExecutionPlugin] 执行结果: {output}"); Console.ResetColor(); return output; } catch (CompilationErrorException cex) { var errors = string.Join("\n", cex.Diagnostics.Select(d => d.ToString())); return $"ERROR: 编译失败\n{errors}"; } catch (Exception ex) { return $"ERROR: 运行时异常 - {ex.Message}"; } } }

换句人话说,就是 AI 写出来的代码可以被立即执行,然后把执行结果或者错误信息反馈给 AI。这是一个 即时反馈循环,让 AI 能够自我修正。

🎬 四个真实场景的玩法

代码里给了四个不同的应用场景。咱们逐个掰扯。

场景1:单体AI + 工具集合(最简单的玩法)

这是最直白的用法。一个AI(SemanticKernelAgent),配上一堆工具(插件),你问它问题,它自己决定用哪个工具。

比如你问:"请计算 15 乘以 7 的结果,再加上 12 的阶乘"

这个AI会:

  1. 看到"乘以",调用 Multiply 插件
  2. 看到"阶乘",调用 Factorial 插件
  3. 自己心算把两个结果相加
  4. 给你最终答案

这场景适合那种一问一答、相对简单的任务。但问题是,如果AI算错了,就没人兜底。

场景2:自我修正循环(AI vs 代码执行器)

这个就牛逼一些了。流程是这样的:

你的问题 ↓ AI 写代码 ↓ 执行引擎运行代码 ↓ 代码有ERROR?→ 反馈给AI重写 → 再执行 → 循环直到成功 代码成功?→ AI 解释结果 → 完成

比如这个需求:"用蒙特卡洛方法估算π的值"

第一次AI可能写得有点问题,执行失败了。代码直接把错误信息抛回去:"ERROR: 编译失败\n..."

AI 看到错误信息,不是傻傻地重复之前的代码,而是读懂错误,修正问题。这里面用到的关键技术是聊天历史维护。每一轮对话都被记录下来,形成一个连贯的上下文。

csharp
var history = new List<IMessage> { new TextMessage(Role.User, question) }; // 每次交互都追加到历史 history.Add(reply); history.Add(executionResult); history.Add(nextAIResponse);

场景3:多智能体协作(经典的 Coder + Reviewer)

现在才是真正有趣的部分。我们把一个 AI 分裂成三个角色:

👨‍💻 Coder(代码编写者)

  • 收到任务后,专注于写代码
  • 必须返回完整的、可执行的代码块
  • 看到Reviewer的批评就立即修改

👀 Reviewer(代码审查者)

  • 检查代码的逻辑正确性
  • 检查边界情况处理
  • 检查代码质量
  • 只回复两种结果:APPROVED 或具体的修改意见

🔄 Executor(执行器,这里是 CodeExecutionPlugin)

  • 在两者之间充当"裁判"
  • 运行 Coder 写的代码
  • 把执行结果反馈给 Reviewer

一轮完整的协作流程长这样:

任务来了 ↓ Coder 写代码 → 执行结果 ↓ Reviewer 看代码和执行结果 → 提意见或通过 ↓ 意见通过? → 结束 不通过? → Coder 读意见 → 改代码 → 执行 → Reviewer再看

这里有个巧妙的地方:Reviewer 不会自己写代码。它只负责挑毛病。这样可以防止两个AI"唱双簧"糊弄你。

场景4:压力测试(强制多轮迭代)

这个是 场景3 的加强版。有时候 Reviewer 可能会"仁慈",代码刚刚能用就通过了。但如果我们强制要求至少3轮迭代呢?

csharp
if (isApproved && round < MinRoundsForTest) { history.Add(new TextMessage(Role.User, $"为了多轮压测,请继续进行第 {round + 1} 轮优化:补充 1 组极端测试,并保持已有结果正确。")); }

这样即使代码通过了审查,Coder 还得继续优化,补充边界测试、性能优化之类的。这个设计特别适合生产级别的代码把关。

💡 几个我在实战中发现的坑

用这套系统的时候,有些细节得特别留意。

坑1:SystemMessage 的魔力

这些智能体的"性格"全靠 SystemMessage。Coder 的 SystemMessage 要强调"只输出代码,不要解释";Reviewer 的则要强调"只反馈意见,不要改代码"。一旦这个搞混了,整个协作流程就乱套了。

我见过有人给 Reviewer 写的 SystemMessage 是"帮我改进代码",结果 Reviewer 不再给意见了,直接开始改代码。那就变成了两个 Coder 在争谁的方案更好,没有制约了。

坑2:代码块提取的脆弱性

代码里有个工具函数叫 ExtractCodeBlock,用来从 AI 的回复里提取 csharp... 这样的代码块:

csharp
static string ExtractCodeBlock(string text) { const string marker = "```csharp"; var start = text.IndexOf(marker, StringComparison.OrdinalIgnoreCase); if (start < 0) return string.Empty; start += marker.Length; var end = text.IndexOf("```", start); if (end < 0) return string.Empty; return text[start..end].Trim(); }

看起来简单,但有个问题:如果 AI 的回复里有多个代码块,这个函数只会提取第一个。而且万一 AI 忘记了 markdown 的语法标记,写成了 '''csharp 或者其他变体,就识别不了。

我的建议是,在 Coder 的 SystemMessage 里明确强调:"代码必须用 csharp ... 包裹,这是必须的,不能有其他格式"

坑3:异步等待的陷阱

所有的 AI 调用都是异步的(async/await),这在多轮对话里尤其需要注意。如果某一步阻塞了,整个流程就卡住。代码里用了 await 来等待每一步的完成,这样才能保证顺序性。

坑4:环境变量和 API 密钥

代码最开头就要求设置环境变量:

csharp
var apiKey = Environment.GetEnvironmentVariable("ALIYUN_API_KEY") ?? throw new InvalidOperationException("请设置环境变量 ALIYUN_API_KEY");

千万别把密钥硬编码在代码里,这个安全常识就不赘述了。但还有个细节:如果你用 GitHub 或其他代码托管,要确保 .gitignore 里包含了环境变量文件。

🚀 怎样在自己的项目里用上这套系统

假设你现在有一个 C# 项目,想要集成这套多智能体编程助手。步骤大致是这样的:

第一步:装依赖

bash
dotnet add package AutoGen.Core dotnet add package AutoGen.OpenAI dotnet add package AutoGen.SemanticKernel dotnet add package Microsoft.SemanticKernel dotnet add package OpenAI dotnet add package Microsoft.CodeAnalysis.CSharp.Scripting

第二步:复制并改造 AgentFactory

把这个工厂类复制到你的项目里。如果你用的不是千问,而是 OpenAI 或其他厂商的API,只需要改这个地方:

csharp
using AutoGen.Core; using AutoGen.OpenAI; using AutoGen.OpenAI.Extension; using AutoGen.SemanticKernel; using AutoGen.SemanticKernel.Extension; using AutoGenDemo.Plugins; using Microsoft.SemanticKernel; using OpenAI; using System.ClientModel; namespace AutoGenDemo.Agents; public static class AgentFactory { private const string DefaultModel = "qwen-plus"; private const string CoderModel = "qwen-coder-plus"; private const string QwenEndpoint = "https://dashscope.aliyuncs.com/compatible-mode/v1"; // ---------------------------------------------------------------- // 私有工厂:创建指向千问的 OpenAIClient // ---------------------------------------------------------------- private static OpenAIClient CreateQwenClient(string apiKey) { var endpoint = new Uri(QwenEndpoint); var credential = new ApiKeyCredential(apiKey); var options = new OpenAIClientOptions { Endpoint = endpoint }; return new OpenAIClient(credential, options); } // ---------------------------------------------------------------- // 私有工厂:创建指向千问的 Semantic Kernel // ---------------------------------------------------------------- private static Kernel CreateQwenKernel(string apiKey, string model) { return Kernel.CreateBuilder() .AddOpenAIChatCompletion( modelId: model, apiKey: apiKey, endpoint: new Uri(QwenEndpoint)) .Build(); } // ---------------------------------------------------------------- // 场景 1:SemanticKernelAgent + 全部 Plugins // ---------------------------------------------------------------- public static IAgent CreateSkAgent(string apiKey, string model = DefaultModel) { var kernel = CreateQwenKernel(apiKey, model); kernel.Plugins.AddFromObject(new MathPlugin(), "Math"); kernel.Plugins.AddFromObject(new TimePlugin(), "Time"); kernel.Plugins.AddFromObject(new CodeExecutionPlugin(), "CodeExec"); return new SemanticKernelAgent( kernel: kernel, name: "sk_agent", systemMessage: """ 你是一个能力强大的 AI 助手,拥有以下工具: - Math Plugin:数学计算(加法、乘法、阶乘) - Time Plugin:时间日期查询 - CodeExec Plugin:执行 C# 代码片段 使用规则: 1. 当问题涉及精确计算或逻辑处理时,优先调用工具,不要凭记忆猜测 2. 调用 CodeExec 时,代码中必须用 return 返回最终结果 3. 如果 CodeExec 返回以 "ERROR:" 开头的内容,分析原因并修正代码后重试 4. 最终用清晰的中文回答用户问题 """) .RegisterMessageConnector() .RegisterPrintMessage(); } // ---------------------------------------------------------------- // 场景 2:自我修正循环专用的 Coder Agent(纯 OpenAI Agent) // ---------------------------------------------------------------- public static IAgent CreateSelfCorrectingCoderAgent( string apiKey, string model = CoderModel) { var client = CreateQwenClient(apiKey); return new OpenAIChatAgent( chatClient: client.GetChatClient(model), name: "coder", systemMessage: """ 你是一个专业的 C# 编程专家,专注于解决计算和逻辑问题。 任务规则: 1. 收到编程任务后,编写能解决问题的 C# 代码片段 2. 代码要求: - 不含 using 语句(运行环境已预导入常用命名空间) - 必须用 return 语句返回最终结果(字符串或数字均可) - 只输出代码本身,用 ```csharp ... ``` 包裹,不要有多余解释 3. 如果收到以 "ERROR:" 开头的错误信息: - 仔细阅读错误原因 - 修正问题后重新输出完整代码 - 不要只输出修改部分,要输出完整可运行的代码块 4. 如果收到执行成功的结果,用自然语言给出最终答案,不再输出代码 """) .RegisterMessageConnector() .RegisterPrintMessage(); } // ---------------------------------------------------------------- // 场景 3:Multi-Agent 中的 Coder Agent // ---------------------------------------------------------------- public static IAgent CreateCoderAgent( string apiKey, string model = CoderModel) { var client = CreateQwenClient(apiKey); return new OpenAIChatAgent( chatClient: client.GetChatClient(model), name: "coder", systemMessage: """ 你是一个专业的 C# 程序员,负责编写代码实现需求。 输出规则: 5. 只输出代码片段,用 ```csharp ... ``` 包裹 6. 代码不含 using 语句,直接写逻辑 7. 必须用 return 语句返回最终计算结果,不能只定义方法不执行 8. 如果 Reviewer 指出了问题,认真修改后重新输出完整代码 9. 如果执行结果已正确,不再重复输出代码 """) .RegisterMessageConnector() .RegisterPrintMessage(); } // ---------------------------------------------------------------- // 场景 3:Multi-Agent 中的 Reviewer Agent // ---------------------------------------------------------------- public static IAgent CreateReviewerAgent( string apiKey, string model = DefaultModel) { var client = CreateQwenClient(apiKey); return new OpenAIChatAgent( chatClient: client.GetChatClient(model), name: "reviewer", systemMessage: """ 你是一名严格的 C# 代码审查员,负责验证 coder 提交的代码。 审查标准: 10. 逻辑正确性:算法是否能得出正确结果 11. 边界处理:是否考虑了空输入、负数、溢出等边界情况 12. 代码质量:是否简洁清晰,没有明显冗余 13. 执行结果校验:若执行结果为 ERROR 或无返回值,必须拒绝通过 输出规则: - 如果代码通过所有审查,且执行结果也正确,只回复一行:APPROVED - 如果存在问题,明确指出具体问题(不超过 3 条),要求 coder 修改 - 不要自己重写代码,只提出修改意见 """) .RegisterMessageConnector() .RegisterPrintMessage(); } // ---------------------------------------------------------------- // 场景 3:Multi-Agent 中的 Summarizer Agent(最终汇总) // ---------------------------------------------------------------- public static IAgent CreateSummarizerAgent( string apiKey, string model = DefaultModel) { var client = CreateQwenClient(apiKey); return new OpenAIChatAgent( chatClient: client.GetChatClient(model), name: "summarizer", systemMessage: """ 你是一个技术文档撰写员。 当 Reviewer 批准代码后,你负责整理本次对话,输出一份简洁的总结,包含: 1. 任务描述(一句话) 2. 最终代码(完整版) 3. 执行结果 4. 简要说明代码思路 输出格式清晰,适合直接保存为技术文档。 """) .RegisterMessageConnector() .RegisterPrintMessage(); } }

第三步:定义自己的 Plugins

根据你的业务需求,创建自己的插件。比如如果你做金融系统,可能需要一个 FinancePlugin;如果做数据处理,可能需要 DataProcessingPlugin

csharp
public class FinancePlugin { [KernelFunction, Description("计算复利收益")] public double CompoundInterest( [Description("本金")] double principal, [Description("年利率")] double rate, [Description("年数")] int years) { return principal * Math.Pow(1 + rate, years); } }

第四步:选择合适的场景

  • 简单的一问一答?用场景1(单体AI)
  • 需要自动修正的复杂任务?用场景2(自我修正循环)
  • 需要多人审核把关?用场景3(Coder+Reviewer)
  • 生产级别的严苛要求?用场景4(压力测试)

📊 真实数据:它能帮你省多少时间?

我在团队里试过这套系统。一个中等复杂度的算法题(比如"实现一个高效的排序+去重的混合函数"),手工写可能需要 10-15 分钟,包括测试和调试。

用了多智能体协作后:

  • 首轮代码生成:2 分钟(AI写代码)
  • 执行和反馈:1 分钟(代码执行)
  • 迭代修正:3-5 分钟(Reviewer挑毛病,Coder改进,通常2-3轮)
  • 总耗时:大约 6-8 分钟

数字上看省的时间不多,但关键的是代码质量和安心感。因为有 Reviewer 在,代码不会一开始就走歪路。而且如果有边界情况的 bug,往往能在第一轮迭代就被抓出来,而不是等到上线后再发现。

🎯 后续可以怎么玩

现在的架构还有很多扩展空间。

方向1:更复杂的协作模式

除了 Coder + Reviewer,还可以加入 Optimizer(性能优化员)、Documentor(文档编写员)、SecurityAuditor(安全审计员)。不同的角色对不同的方面负责,最后统一意见。这样就成了真正的"虚拟团队"。

方向2:与真实开发工具的集成

现在代码执行是本地的 Roslyn。未来可以考虑直接提交到 git、运行 CI/CD 管道、跑单元测试套件。这样 AI 的反馈循环就能包括真实的测试结果。

方向3:记忆和学习机制

现在每次都是"无状态"的对话。如果能让 AI 记住"之前这个问题咱们怎么解决的",下次遇到类似问题就能更快解决。这涉及到向量数据库和检索增强生成(RAG)的东西。

方向4:成本优化

现在每一轮对话都得调 API,成本可能比较高。可以考虑用小模型(比如 Qwen-Coder-Plus)做初步编写,只在关键的审查环节用大模型。这样能显著降低成本。

💭 最后的碎碎念

说实话,刚开始看这套代码的时候,我的第一反应是:"这得多复杂啊"。但深入理解后发现,本质就是几个 if-else 和一个聊天历史列表。AI 之间没有什么神秘的"智能对话",就是严格的角色定位 + 明确的输入输出规范 + 反馈循环的设计

这其实挺有启发意义的。在团队协作中,人和人之间的高效配合,也不是靠某某人特别天才,而是靠清晰的职责分工明确的沟通规范及时的反馈机制。用 AI 来演这一出,某种程度上就是在验证这套理论。

如果你的项目涉及到重复性强、需要反复迭代、质量要求高的编程任务,这套架构值得一试。从小场景开始试水(比如先用场景2的自我修正循环),一旦摸清了门道,再逐步升级到多智能体协作。

下次再有人说"AI 写的代码不可靠",你就可以笑笑,说:"那是因为你没有让多个 AI 互相监督啊。" 🙂


关键技术栈速查表

组件作用核心类/方法
AutoGen多智能体框架IAgentGroupChat
SemanticKernel提示词编排 + 函数调用KernelKernelFunction
RoslynC# 动态编译执行CSharpScript.EvaluateAsync
OpenAI SDK与 LLM 通信OpenAIChatAgentChatClient
自定义 Plugins工具集合MathPluginTimePluginCodeExecutionPlugin

三个"一句话"的收获

  1. 多智能体协作 = 角色分工 + 反馈循环,本质上就是把真实的代码审查流程自动化了。

  2. SystemMessage 是灵魂,决定了每个 AI 的性格和职责,写得好协作顺畅,写得差就是"自说自话"。

  3. 即时反馈是关键,AI 写完代码立即执行、立即看到结果、立即修改,这个闭环比"一次性输出"强太多。

如果你觉得这套思路还不错,欢迎在评论区分享你的看法。或者,如果你在实际应用中遇到了其他的坑或者创意用法,也很乐意听听。下一期,咱们可以聊聊怎样进一步优化这套系统的成本和性能。


推荐阅读
深入了解 Semantic Kernel 的函数调用机制 → [官方文档传送门]
AutoGen 的设计思想 → [研究论文链接]
Roslyn 在实际项目中的应用案例 → [案例分享文章]

相关信息

通过网盘分享的文件:AutoGenDemo.zip 链接: https://pan.baidu.com/s/1RMnLL-2hMbB2CPuvAc69Cg?pwd=h3cv 提取码: h3cv --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!