AutoGen.NET 图像对话实战:用 OpenAIChatAgent 实现 ImageMessage 与 MultiModalMessage 视觉聊天
本文基于 AutoGen.NET(dotnet 端 AutoGen)官方向导,讲解如何让一个 Agent 处理图片输入:以 OpenAIChatAgent(后端模型为 gpt-4o 系列)为例,完整走通安装 NuGet 包、创建视觉 Agent、构造 ImageMessage / MultiModalMessage 图像消息、以及通过 SendAsync 生成回复的全过程,并结合仓库源码剖析消息是如何被中间件转换成 OpenAI 图像请求的。读完后你能在自己的 .NET 项目中复制出可运行的“看图问答”代码,并理解 AutoGen 消息体系在多模态场景下的设计约束。
注意:要与 Agent 进行图像对话,Agent 背后的模型必须支持图像输入。官方文档给出的部分支持图像输入的模型列表包括:gpt-4o、gemini-1.5、llava、claude-3 等。本示例使用 gpt-4o 系列模型作为 Agent 的后端模型。
完整可运行的代码示例位于 Image_Chat_With_Agent.cs。
Step 1: 安装 AutoGen 包
首先使用以下命令安装 AutoGen NuGet 包:
dotnet add package AutoGen
示例工程还引用了 AutoGen.OpenAI 相关包(提供 OpenAIChatAgent 及其扩展方法),运行前需要设置 OPENAI_API_KEY 环境变量。从 LLMConfiguration.cs 可以看到,示例通过 LLMConfiguration.GetOpenAIGPT4o_mini() 创建指向 gpt-4o-mini 的 ChatClient:
public static ChatClient GetOpenAIGPT4o_mini()
{
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
var modelId = "gpt-4o-mini";
return new OpenAIClient(openAIKey).GetChatClient(modelId);
}
即:文档中笼统表述的“gpt-4o 模型”,在仓库当前示例代码里具体落地为 gpt-4o-mini,这是当前示例可运行的一套模型选择。
Step 2: 添加 using 语句
按照 示例源码,需要引入以下命名空间:
using AutoGen.Core; // ImageMessage、MultiModalMessage、SendAsync 等核心类型
using AutoGen.OpenAI; // OpenAIChatAgent
using AutoGen.OpenAI.Extension; // RegisterMessageConnector 扩展方法
Step 3: 创建 OpenAIChatAgent
创建 Agent 的核心代码(对应示例中 Create_Agent 代码块):
var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
var agent = new OpenAIChatAgent(
chatClient: gpt4o,
name: "agent",
systemMessage: "You are a helpful AI assistant")
.RegisterMessageConnector() // convert OpenAI message to AutoGen message
.RegisterPrintMessage();
从 OpenAIChatAgent.cs 的构造函数签名看,OpenAIChatAgent 支持如下参数:
| 参数 | 类型 | 说明 |
|---|---|---|
chatClient |
ChatClient |
OpenAI SDK 的 ChatClient,模型选择由它决定 |
name |
string |
Agent 名称 |
systemMessage |
string? |
系统消息,默认 "You are a helpful AI assistant" |
temperature |
float? |
采样温度,内部默认 0.7(见 CreateChatCompletionOptions) |
maxTokens |
int? |
最大生成 token 数,内部默认 1024 |
seed |
int? |
随机种子,设置后输出趋于确定性 |
responseFormat |
ChatResponseFormat? |
设置为 JSON 格式可启用 json mode |
functions |
IEnumerable<ChatTool>? |
注册的工具函数 |
链式调用的两个扩展方法各司其职:
RegisterMessageConnector():注册OpenAIChatRequestMessageConnector中间件,负责把 AutoGen 的IMessage(包括ImageMessage、MultiModalMessage)转换成 OpenAI 的ChatMessage请求,并把 OpenAI 的回复转换回TextMessage/ToolCallMessage。见 OpenAIAgentExtension.cs;RegisterPrintMessage():注册打印中间件,在控制台输出对话内容,便于调试。见 PrintMessageMiddlewareExtension.cs。
另外,OpenAIChatAgent 的 CreateChatMessages 逻辑显示:如果传入的消息中没有系统消息,且构造时指定了 systemMessage,系统消息会被自动前置到对话最前面——所以示例里不显式传 system message 也能生效。
Step 4: 构造图像消息
AutoGen 中构造图像消息有两种方式:ImageMessage 与 MultiModalMessage。区别在于:ImageMessage 只携带单张图像;MultiModalMessage 允许把文本、图像等多种模态组合在一条消息中。
4.1 使用 ImageMessage 构造图像消息
对应示例 Prepare_Image_Input 代码块:
var backgoundImagePath = Path.Combine("resource", "images", "background.png");
var imageBytes = File.ReadAllBytes(backgoundImagePath);
var imageMessage = new ImageMessage(Role.User, BinaryData.FromBytes(imageBytes, "image/png"));
图片按相对路径 resource/images/background.png 读取。仓库中对应的测试图片资源位于 background.png,其他图像示例(如 Example05_Dalle_And_GPT4V.cs)也使用同一资源路径。
从 ImageMessage.cs 源码看,ImageMessage 提供了三个构造函数,覆盖三种图片来源:
ImageMessage(Role role, string url, string? from = null, string? mimeType = null)- 支持普通 URL 或 data URI。data URI 必须形如
data:[<mediatype>][;base64],<data>,否则抛出ArgumentException(见 ImageMessage.cs#L24-L37); - 普通 URL 场景下,若未显式传
mimeType,会按扩展名推断:.png→image/png、.jpg/.jpeg→image/jpeg、.gif→image/gif、.bmp→image/bmp、.webp→image/webp、.svg→image/svg+xml;扩展名无法识别且不传mimeType时抛异常(见 ImageMessage.cs#L42-L55)。
- 支持普通 URL 或 data URI。data URI 必须形如
ImageMessage(Role role, Uri uri, ...):字符串 URL 构造函数的Uri版本。ImageMessage(Role role, BinaryData data, string? from = null):从二进制数据构造(本示例使用的方式)。注意data不能为空,且data.MediaType必须非空(即BinaryData.FromBytes(bytes, "image/png")中必须带上媒体类型),否则抛ArgumentException(见 ImageMessage.cs#L66-L82)。
此外,ImageMessage 还提供 BuildDataUri() 方法,把 Data 编码为 data:<mimeType>;base64,... 形式的 data URI,供序列化与调试使用。
4.2 使用 MultiModalMessage 构造多模态消息
对应示例 Prepare_Multimodal_Input 代码块:
var textMessage = new TextMessage(Role.User, "what's in the picture");
var multimodalMessage = new MultiModalMessage(Role.User, [textMessage, imageMessage]);
从 MultiModalMessage.cs 的 Validate() 方法看,构造时会做两条强校验:
- 子消息的
From属性必须与聚合消息的From一致,否则抛ArgumentException; - 子消息只能是
TextMessage或ImageMessage,其他类型(如工具调用消息)不允许聚合进来。
仓库中还有一个多图像批量输入的实际用法可以参考 Example15_GPT4V_BinaryDataImageMessage.cs:它遍历 resource/images 目录下的所有图片,按扩展名映射媒体类型后逐个构造 ImageMessage,最后用 MultiModalMessage 打包成一条用户消息发给视觉 Agent——适合一次发多张图的场景。
Step 5: 生成响应
对应示例 Chat_With_Agent 代码块:
var reply = await agent.SendAsync("what's in the picture", chatHistory: [imageMessage]);
// or use multimodal message to generate reply
reply = await agent.SendAsync(multimodalMessage);
SendAsync 定义在 AgentExtension.cs,与单 Agent 交互相关的重载有两个:
SendAsync(this IAgent agent, IMessage? message = null, IEnumerable<IMessage>? chatHistory = null, CancellationToken ct = default):发送任意类型的IMessage(本例中的MultiModalMessage走这条路径);SendAsync(this IAgent agent, string message, IEnumerable<IMessage>? chatHistory = null, ...):字符串便捷重载,内部会包装成TextMessage(Role.User, message)(见 AgentExtension.cs#L51-L60)。
两条路径的共同行为是:SendAsync 会把 chatHistory 排在前面、message 追加到末尾,合并成完整消息列表后调用 agent.GenerateReplyAsync(messages)(见 AgentExtension.cs#L21-L42)。因此“文字 + 图片”组合实际上有两种等价写法:
- 图片放在
chatHistory里,当前问题用字符串参数发送:agent.SendAsync("what's in the picture", chatHistory: [imageMessage]); - 把文本和图像聚合进一条
MultiModalMessage直接发送:agent.SendAsync(multimodalMessage)。
回复默认是 TextMessage 类型(示例中用 reply.Should().BeOfType<TextMessage>() 做断言,见 示例源码)。
底层原理:图像消息如何变成 OpenAI 请求
理解上面两步写法能正常工作的关键,在于 RegisterMessageConnector() 注册的 OpenAIChatRequestMessageConnector 中间件。它在 ProcessIncomingMessages 中按类型分派:
ImageMessage(且From不是当前 Agent 自己)→ProcessImageMessage:通过ChatMessageContentPart.CreateImagePart(...)构造图像内容项——若消息带Url则创建 URI 图像部分,否则用Data+ 媒体类型创建二进制图像部分,最终包装为一条UserChatMessage(见 OpenAIChatRequestMessageConnector.cs#L265-L300);MultiModalMessage→ProcessMultiModalMessage:遍历子消息,TextMessage转文本部分、ImageMessage转图像部分,合并成一条多部分UserChatMessage;- 回复方向:
ChatCompletion中的文本内容被转换回TextMessage,工具调用则转换回ToolCallMessage(PostProcessChatCompletions,见 OpenAIChatRequestMessageConnector.cs#L153-L206)。
由此可以得到几条实践约束:
ImageMessage/MultiModalMessage的From不能等于 Agent 名称(即“助手自己发出图像”不被支持),中间件会直接抛ArgumentException;ImageMessage必须二选一携带Url或Data,且二进制数据必须带媒体类型,这与构造函数校验一致;MultiModalMessage只能聚合TextMessage与ImageMessage,无法混入工具消息等其他类型;- 中间件支持
strictMode构造参数:开启后遇到不支持的消息类型会抛异常,默认关闭时静默忽略(见 OpenAIChatRequestMessageConnector.cs#L34-L37)。
运行前提与扩展阅读
- 运行前提:设置
OPENAI_API_KEY环境变量;使用支持图像输入的视觉模型(gpt-4o 系列等);图像资源按相对路径resource/images/background.png放置在工作目录下。 - 换用其他后端模型实现同样的图像对话,可参考:
- 用 Gemini 做图像对话:Image-chat-with-gemini
- 用本地 LLaVA 模型做图像对话:Chat-with-llava
- 更多消息类型的定义可查阅 AutoGen.Core/Message 目录下的
TextMessage、ImageMessage、MultiModalMessage、ToolCallMessage等源文件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00