{"record":{"id":"a833257a36fb7b9a","repo":"microsoft/autogen","slug":"the-last-message-must-be-from-the-user-or-function","errorCode":null,"errorMessage":"The last message must be from the user or function","messagePattern":"The last message must be from the user or function","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"dotnet/src/AutoGen.Gemini/GeminiChatAgent.cs","lineNumber":179,"sourceCode":"            IMessage<Content> contentMessage => contentMessage.Content,\n            _ => throw new NotSupportedException($\"Message type {m.GetType()} is not supported.\")\n        });\n\n        // there are several rules applies to the messages that can be sent to Gemini in a multi-turn chat\n        // - The first message must be from the user or function\n        // - The (user|model) roles must alternate e.g. (user, model, user, model, ...)\n        // - The last message must be from the user or function\n\n        // check if the first message is from the user\n        if (geminiMessages.FirstOrDefault()?.Role != \"user\" && geminiMessages.FirstOrDefault()?.Role != \"function\")\n        {\n            throw new ArgumentException(\"The first message must be from the user or function\", nameof(messages));\n        }\n\n        // check if the last message is from the user\n        if (geminiMessages.LastOrDefault()?.Role != \"user\" && geminiMessages.LastOrDefault()?.Role != \"function\")\n        {\n            throw new ArgumentException(\"The last message must be from the user or function\", nameof(messages));\n        }\n\n        // merge continuous messages with the same role into one message\n        var mergedMessages = geminiMessages.Aggregate(new List<Content>(), (acc, message) =>\n        {\n            if (acc.Count == 0 || acc.Last().Role != message.Role)\n            {\n                acc.Add(message);\n            }\n            else\n            {\n                acc.Last().Parts.AddRange(message.Parts);\n            }\n\n            return acc;\n        });\n\n        var systemMessage = this.systemMessage switch","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/dotnet/src/AutoGen.Gemini/GeminiChatAgent.cs#L161-L197","documentation":"Thrown by GeminiChatAgent.BuildChatRequest because the Gemini chat API requires the last message to be from 'user' or 'function' (i.e. the request must end with a turn the model responds to). If the final Content has role 'model' (assistant), ArgumentException is thrown with paramName 'messages'.","triggerScenarios":"Appending the assistant's previous reply as the last element of the next request — common when you push the model's response onto the same list you send back, or when a tool-call assistant message is not followed by its function result.","commonSituations":"Chat loops that send the full history including the last assistant message without a new user/tool message; function-calling flows missing the ToolCallResultMessage after the model's tool-call turn.","solutions":["After each Gemini reply, only send the next request once a new user message or function result has been appended","In function-calling flows, always append the ToolCallResult/FunctionResponse message before calling GenerateReplyAsync again","Trim trailing model-role messages, or append a synthetic user message (e.g. \"continue\") to satisfy the ordering rule"],"exampleFix":"// before:\nhistory.Add(geminiReply); // model role now last\nawait agent.GenerateReplyAsync(history); // throws\n// after:\nhistory.Add(geminiReply);\nhistory.Add(new TextMessage(Role.User, \"please continue\"));\nawait agent.GenerateReplyAsync(history);","handlingStrategy":"validation","validationCode":"// Ensure the request ends on a user or function turn\nvar last = messages.LastOrDefault() as IMessage<Content>;\nif (last?.Content.Role is not (\"user\" or \"function\"))\n    messages = messages.Append(new TextMessage(Role.User, \"please continue\")).ToList();","typeGuard":"static bool EndsOnUserTurn(IEnumerable<IMessage> msgs) =>\n    (msgs.LastOrDefault() as IMessage<Content>)?.Content.Role is \"user\" or \"function\";","tryCatchPattern":"try { await geminiAgent.GenerateReplyAsync(messages, options, ct); }\ncatch (ArgumentException e) when (e.Message.Contains(\"last message must be from the user\"))\n{ /* append pending tool results or a user message, then retry */ }","preventionTips":["Always append tool/function results before the next GenerateReplyAsync in function-calling flows","Never send back a history ending with the model's own reply"],"tags":["autogen","gemini","chat-history","ordering"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}