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

  1. Make the streaming agent emit tool-call updates without preceding text updates in the same stream (or emit them as separate replies)
  2. If text-then-tool-call is required by your model flow, bypass PrintMessageMiddleware for that agent
  3. 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

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


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/a786915a11f30a30. Report an issue: GitHub.