AutoGen.NET 实战:用 OpenAIChatRequestMessageConnector 让 OpenAIChatAgent 支持 TextMessage、ImageMessage 等内置消息类型
本文介绍 AutoGen.NET 中 OpenAIChatAgent 默认消息类型的限制,以及如何通过 OpenAIChatRequestMessageConnector 这个消息连接器中间件,让 OpenAI Chat Agent 能够收发 AutoGen 内置消息类型(TextMessage、ImageMessage、MultiModalMessage、ToolCallMessage 等),并结合源码说明其类型映射规则、strictMode 行为与流式回复处理机制,帮助你写出跨模型、跨 Agent 可复用的消息代码。
1. 默认限制:OpenAIChatAgent 只认 OpenAI 客户端的原始消息类型
在 AutoGen.NET(即 .NET 版 AutoGen,代码位于 dotnet 目录)中,OpenAIChatAgent 是一个直接对接 OpenAI 聊天客户端的 Agent。如果不做任何额外注册,它默认只支持 IMessage<T> 这一种消息形式,其中 T 是 OpenAI 客户端库(如 Azure.AI.OpenAI / OpenAI)返回的原始请求或响应消息类型。也就是说,你发给它的消息是 MessageEnvelope.Create(new UserChatMessage(...)) 这样的原始 SDK 消息,收到的回复则是 MessageEnvelope<ChatCompletion>。
示例代码中对这一默认行为的注释写得很明确:
// OpenAIChatAgent supports the following message types:
// - IMessage<ChatRequestMessage> where ChatRequestMessage is from Azure.AI.OpenAI
var helloMessage = new UserChatMessage("Hello");
// Use MessageEnvelope.Create to create an IMessage<ChatRequestMessage>
var chatMessageContent = MessageEnvelope.Create(helloMessage);
var reply = await openAIChatAgent.SendAsync(chatMessageContent);
// The type of reply is MessageEnvelope<ChatCompletion> where ChatResponseMessage is from Azure.AI.OpenAI
reply.Should().BeOfType<MessageEnvelope<ChatCompletion>>();
这种设计的好处是与 OpenAI SDK 零阻抗对接,但缺点也很直接:一旦你的系统里存在其他 Agent(如基于 Semantic Kernel、Anthropic、Gemini 的 Agent)或基于 AutoGen 核心消息类型的编排逻辑,消息类型就无法互通。AutoGen 在 AutoGen.Core 中定义了一套与具体 LLM 提供商无关的内置消息类型,包括:
- TextMessage:携带
Role(System/User/Assistant)、Content文本与From(消息来源 Agent 名); - ImageMessage:图片消息,可用
Data(字节 + MediaType)或Url构造; - MultiModalMessage:由若干
TextMessage/ImageMessage组合而成的多模态消息; - ToolCallMessage 与 ToolCallResultMessage:函数调用请求与结果。
要让 OpenAIChatAgent 也接受并产出这些内置类型,就需要注册 OpenAIChatRequestMessageConnector。
2. OpenAIChatRequestMessageConnector:双向消息转换器
OpenAIChatRequestMessageConnector(源码)实现了 IMiddleware 与 IStreamingMiddleware 两个接口,因此它可以同时处理普通回复和流式回复。从源码结构看,它在 Agent 调用的中间件链中完成两件事:
- 入站转换:调用
agent.GenerateReplyAsync之前,先通过ProcessIncomingMessages把 AutoGen 内置消息逐条转换为 OpenAI 的ChatMessage列表; - 出站转换:Agent 返回
IMessage<ChatCompletion>后,通过PostProcessMessage把ChatCompletion还原为 AutoGen 内置消息(TextMessage或ToolCallMessage)。
构造函数只暴露一个参数:
/// <param name="strictMode">If true, the connector will throw an InvalidOperationException
/// when the message type is not supported. If false, it will ignore the unsupported message type.</param>
public OpenAIChatRequestMessageConnector(bool strictMode = false)
strictMode 默认为 false:遇到无法识别的消息类型时直接忽略(不出现在发给模型的请求中);设为 true 时则抛出 InvalidOperationException,方便在开发阶段尽早暴露类型问题。
3. 注册连接器:一行扩展方法即可启用
官方文档给出的用法非常简洁——导入命名空间后调用 RegisterMessageConnector() 扩展方法即可。完整代码摘自 OpenAICodeSnippet.cs(对应文档中 using_statement 与 register_openai_chat_message_connector 两个代码块):
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
// ... 先创建一个 OpenAIChatAgent(略,见上文 create_openai_chat_agent 代码块)...
// register message connector to support more message types
var agentWithConnector = openAIChatAgent
.RegisterMessageConnector();
// now the agentWithConnector supports more message types
var messages = new IMessage[]
{
MessageEnvelope.Create(new UserChatMessage("Hello")),
new TextMessage(Role.Assistant, "Hello", from: "user"),
new MultiModalMessage(Role.Assistant,
[
new TextMessage(Role.Assistant, "Hello", from: "user"),
],
from: "user"),
};
foreach (var message in messages)
{
reply = await agentWithConnector.SendAsync(message);
reply.Should().BeOfType<TextMessage>();
reply.As<TextMessage>().From.Should().Be("assistant");
}
注册后的 Agent 返回类型从 MessageEnvelope<ChatCompletion> 变成了 AutoGen 的 TextMessage,且 From 字段被设置为 Agent 名(示例中断言为 "assistant")。
扩展方法定义在 OpenAIAgentExtension.cs,它有两个重载,分别接受 OpenAIChatAgent 和 MiddlewareStreamingAgent<OpenAIChatAgent>(后者允许你在已经挂过一层中间件的 Agent 上再注册连接器)。其内部实现只是:
public static MiddlewareStreamingAgent<OpenAIChatAgent> RegisterMessageConnector(
this OpenAIChatAgent agent, OpenAIChatRequestMessageConnector? connector = null)
{
if (connector == null)
{
connector = new OpenAIChatRequestMessageConnector();
}
return agent.RegisterStreamingMiddleware(connector);
}
即默认创建一个 strictMode = false 的连接器,并以流式中间件形式注册。你也可以自行 new OpenAIChatRequestMessageConnector(strictMode: true) 传入,获得严格模式行为。返回类型为 MiddlewareStreamingAgent<OpenAIChatAgent>,意味着它同时保留了 SendAsync(非流式)和 GenerateStreamingReplyAsync(流式)两条链路。
4. 入站转换规则详解:哪些内置类型会被转成什么
ProcessIncomingMessages(OpenAIChatRequestMessageConnector.cs)中支持的类型及转换规则如下,这部分是从源码逐条确认的实现事实:
| AutoGen 内置消息 | 附加条件 | 转换结果 |
|---|---|---|
IMessage<ChatMessage> |
任意 | 原样透传(短路,不再转换) |
TextMessage |
任意 | 按 Role 与 From 映射(见下文) |
ImageMessage |
From 为空或 From != agent.Name |
单图 UserChatMessage(含图片内容项) |
MultiModalMessage |
From 为空或 From != agent.Name |
图文混合 UserChatMessage |
ToolCallMessage |
From 为空或 From == agent.Name |
携带 ToolCalls 的 AssistantChatMessage |
ToolCallResultMessage |
任意 | 每个非空结果一条 ToolChatMessage |
AggregateMessage<ToolCallMessage, ToolCallResultMessage> |
任意 | 见下文说明 |
| 其他类型 | strictMode == false |
忽略(不进入请求) |
| 其他类型 | strictMode == true |
抛出 InvalidOperationException |
几条值得注意的规则:
TextMessage 的角色映射(ProcessTextMessage):
Role == System→SystemChatMessage,并把From写入ParticipantName;From == agent.Name(即消息来自 Agent 自己)→AssistantChatMessage;From为空时按Role决定是UserChatMessage还是AssistantChatMessage;- 其余情况一律转为
UserChatMessage,并带上ParticipantName。
图片与多模态消息的来源约束:ProcessImageMessage / ProcessMultiModalMessage 中都显式检查 agent.Name == message.From,若图片消息声称来自 Agent 自己会抛出 ArgumentException("ImageMessage is not supported when message.From is same with agent")。从源码结构看,这与 OpenAI 接口的现实约束一致——模型侧不产出图片。MultiModalMessage 内部只接受 TextMessage 和 ImageMessage 两种子项,其他子类型会抛 NotImplementedException。图片内容项支持两种构造方式:仅有 Url 时创建 URL 图片项,否则用 Data 字节流加 MediaType(CreateChatMessageImageContentItemFromImageMessage)。
工具消息的配对处理:ToolCallMessage 只有在 From 为空或等于 Agent 名时才被转换(其他来源会抛 ArgumentException),说明它代表的是"本 Agent 发出的调用";ToolCallResultMessage 中每个 Result 非空的工具调用都会被转换为一条 ToolChatMessage;而 AggregateMessage<ToolCallMessage, ToolCallResultMessage>(函数调用中间件产出的"调用+结果"聚合消息)来自 Agent 自身时拆分为"Assistant 工具调用消息 + 若干工具结果消息",来自其他 Agent 时则降级为携带结果文本的 UserChatMessage(ProcessFunctionCallMiddlewareMessage)。
5. 出站转换:ChatCompletion 如何还原为 AutoGen 消息
回复侧由 PostProcessMessage 处理(源码):
- 若返回内容是
IMessage<ChatCompletion>:FinishReason == ContentFilter→ 抛出InvalidOperationException(内容被风控过滤);- 返回多于一个 choice → 抛出异常;
ToolCalls非空 → 生成ToolCallMessage(保留文本内容到Content属性);- 内容为文本 → 生成
TextMessage(Role.Assistant, text, from),这正是示例中reply.Should().BeOfType<TextMessage>()能成立的原因; - 其他情况(如空响应)抛
InvalidOperationException。
- 非
ChatCompletion类型:strictMode == false时原样透传,strictMode == true时抛异常。
这一设计让上层代码拿到的是统一的 AutoGen 消息抽象:普通回答是 TextMessage,需要函数调用时是 ToolCallMessage,可以直接接入 FunctionCallMiddleware 或组聊(Group Chat)流程,而不必关心底层 OpenAI 响应结构。
6. 流式回复:文本增量与工具调用的流式聚合
由于连接器同时实现 IStreamingMiddleware,流式场景同样可用。从 流式 InvokeAsync 的实现 看,其行为是:
- 每个
StreamingChatCompletionUpdate中,若增量是单个文本片段,立即以TextMessageUpdate形式 yield 给调用方——即流式输出对上层呈现为 AutoGen 的文本增量消息; - 工具调用增量(
ToolCallUpdates)不逐段直接透传,而是按Index缓冲拼接FunctionName、FunctionArguments、ToolCallId,期间以ToolCallMessageUpdate持续上报累积状态; - 流结束后,若存在工具调用,则把各工具调用合并为一条完整的
ToolCallMessage(Content为全部文本拼接结果)作为最后一个消息 yield。
这意味着即便底层是流式分片,消费端拿到的仍是完整的 AutoGen 消息语义:文本用 TextMessageUpdate 增量消费,工具调用最终收齐为一条 ToolCallMessage。
7. 测试视角的验证
单元测试 OpenAIMessageTests.cs 对转换逻辑有系统性覆盖,可以用作行为对照:
BasicMessageTest一次性喂入TextMessage(System/User/Assistant 三种角色)、ImageMessage(URL 形式)、MultiModalMessage(文本+图片)、ToolCallMessage、ToolCallResultMessage、AggregateMessage<ToolCallMessage, ToolCallResultMessage>等全部支持类型,调用ProcessIncomingMessages并做审批测试(Approval Test)比对转换结果,确保转换行为稳定;ItProcessUserTextMessageAsync用EchoAgent包一层断言中间件,验证new TextMessage(Role.User, "Hello", "user")最终被转换为UserChatMessage且ParticipantName为"user";ItShortcutChatRequestMessageAsync验证MessageEnvelope.Create(new UserChatMessage("hello"))这类原始 SDK 消息会走短路路径,不再转换。
此外,函数调用示例展示了连接器与 FunctionCallMiddleware 的组合用法:先 .RegisterMessageConnector() 再注册函数调用中间件,随后直接 SendAsync("what is the weather in Seattle?") 即可触发工具调用并拿到文本结果——说明注册连接器后,Agent 可以直接用字符串/内置消息类型完成完整的函数调用闭环。
8. 小结
- 默认情况下
OpenAIChatAgent只收发 OpenAI 客户端库的原始消息类型(IMessage<T>),适合与 SDK 深度耦合的场景; - 需要与 AutoGen 其他组件互通时,调用
RegisterMessageConnector()扩展方法,将OpenAIChatRequestMessageConnector作为流式中间件注册,即可无缝收发TextMessage、ImageMessage、MultiModalMessage、ToolCallMessage、ToolCallResultMessage及聚合消息; - 转换遵循严格的来源(
From)与角色(Role)规则,图片/多模态消息只接受非 Agent 来源,工具消息只接受 Agent 自身来源; - 通过
strictMode控制未知消息类型是忽略还是抛异常;流式场景下文本增量以TextMessageUpdate输出,工具调用在流末聚合为一条ToolCallMessage; - 关键实现可参考 OpenAIChatRequestMessageConnector.cs、OpenAIAgentExtension.cs,行为验证可参考 OpenAIMessageTests.cs 与 OpenAICodeSnippet.cs。
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