microsoft/autogen · error · InvalidOperationException
The recent update is not a ToolCallMessage
Error message
The recent update is not a ToolCallMessage
What it means
Thrown by PrintMessageMiddleware while folding a streaming reply. A ToolCallMessageUpdate arrived but the message accumulated so far (recentUpdate) is not null and is not a ToolCallMessage (typically a TextMessage), so the tool-call delta cannot be merged and an InvalidOperationException is thrown.
Source
Thrown at dotnet/src/AutoGen.Core/Middleware/PrintMessageMiddleware.cs:96
}
}
else if (message is ToolCallMessageUpdate toolCallUpdate)
{
if (recentUpdate is null)
{
recentUpdate = new ToolCallMessage(toolCallUpdate);
yield return message;
}
else if (recentUpdate is ToolCallMessage recentToolCallMessage)
{
recentToolCallMessage.Update(toolCallUpdate);
yield return message;
}
else
{
throw new InvalidOperationException("The recent update is not a ToolCallMessage");
}
}
else if (message is IMessage imessage)
{
recentUpdate = imessage;
yield return imessage;
}
else
{
throw new InvalidOperationException("The message is not a valid message");
}
}
Console.WriteLine();
if (recentUpdate is not null && recentUpdate is not TextMessage)
{
Console.WriteLine(recentUpdate.FormatMessage());
}View on GitHub (pinned to 027ecf0a37)
Solutions
- Make the streaming agent emit tool-call updates without preceding text updates in the same stream (or emit them as separate replies)
- If text-then-tool-call is required by your model flow, bypass PrintMessageMiddleware for that agent
- Reorder middleware so a connector normalizes the stream (e.g. GeminiMessageConnector) before PrintMessageMiddleware sees it
Example fix
// before: yield return new TextMessageUpdate(...); ... yield return new ToolCallMessageUpdate(...);
// after: keep one update type per stream
if (hasToolCalls) { yield return new ToolCallMessageUpdate(...); } // no text updates before it Defensive patterns
Strategy: validation
Validate before calling
// Ensure tool-call streams don't follow text updates from the same agent
bool sawTextUpdate = false;
await foreach (var m in agent.GenerateStreamingReplyAsync(msgs, ct))
{
if (m is TextMessageUpdate) sawTextUpdate = true;
if (sawTextUpdate && m is ToolCallMessageUpdate)
throw new InvalidOperationException("Tool-call update after text update in same stream");
} Type guard
static bool IsToolCallUpdateStream(IEnumerable<IMessage> replayed) =>
replayed.All(m => m is ToolCallMessageUpdate or ToolCallMessage); Try / catch
try { await EnumerateStreamAsync(printingAgent, msgs, ct); }
catch (InvalidOperationException e) when (e.Message.Contains("not a ToolCallMessage"))
{ /* skip printing for this agent or split the stream */ } Prevention
- Emit tool-call deltas in a dedicated stream without text preamble
- Normalize streams with a provider connector middleware before printing middleware
When it happens
Trigger: An IStreamingAgent's GenerateStreamingReplyAsync yields a TextMessageUpdate (creating a TextMessage accumulator) and later yields a ToolCallMessageUpdate in the same stream; the else branch at PrintMessageMiddleware.cs:96 fires.
Common situations: Function-calling models that stream some text before emitting tool-call arguments, custom agents mixing text deltas and tool-call deltas, or middleware that converts early chunks into complete messages.
Related errors
- The first message is ToolCallMessage, but the update message
- From mismatch
- FunctionMap is not available
- The message is not a valid message
- The recent update is not a TextMessage
AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15).
Data as JSON: /api/errors/a786915a11f30a30.
Report an issue: GitHub.