.NET: Avoid duplicate AG-UI conversation history - #7936
.NET: Avoid duplicate AG-UI conversation history#7936Javier Calvarro Nelson (javiercn) wants to merge 3 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require stored history to remain an ordered prefix of the incoming AG-UI transcript, avoiding quadratic overlap searches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Prevents AG-UI clients from replaying conversation history already stored locally or by providers.
Changes:
- Filters stored history using message IDs.
- Uses the final assistant boundary for provider-managed conversations.
- Adds focused regression tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
AGUIEndpointRouteBuilderExtensions.cs |
Filters incoming AG-UI transcripts before agent execution. |
AGUIEndpointRouteBuilderExtensionsTests.cs |
Tests local, provider-managed, incremental, and custom-agent cases. |
Suppressed comments (1)
dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs:213
- This fallback makes deduplication fail whenever the stored tail has no
MessageId, which is a normal supported provider path:ChatClientAgentdeliberately passes through null IDs, while AGUI.Server 0.0.5 generates a wire-only ID for such updates. On the next turn the client sends that generated ID, but the persisted assistant message still has null here, so the complete transcript is returned and duplicated again. Persist/synchronize the emitted AG-UI ID with stored history, or add a conservative fallback match for ID-less stored messages, and cover the endpoint flow with provider updates that omit IDs.
if (incomingMessages.Count <= storedMessages.Count)
{
return incomingMessages;
}
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| return incomingMessages; | ||
| } | ||
|
|
||
| if (chatClientAgent.ChatHistoryProvider is InMemoryChatHistoryProvider historyProvider && |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (1 commit(s)): 869da0fb235f
Model: gpt-5.6-sol
Overview
The review found 2 verified inline finding(s).
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs
| } | ||
|
|
||
| if (chatClientAgent.ChatHistoryProvider is InMemoryChatHistoryProvider historyProvider && | ||
| session.TryGetInMemoryChatHistory(out List<ChatMessage>? storedMessages, historyProvider.StateKeys[0]) && |
There was a problem hiding this comment.
This reads restored history with AgentSessionExtensions' default serializer options, even when the InMemoryChatHistoryProvider was configured with custom JsonSerializerOptions. A session containing custom AIContent can deserialize successfully through the provider but throw here on every subsequent AG-UI request. Please retrieve the messages through the configured provider (or pass its serializer options) and cover a serialized/restored session using provider-specific options.
| ctx.Input.ThreadId = threadId; | ||
|
|
||
| var session = await hostAgent.GetOrCreateSessionAsync(threadId, cancellationToken).ConfigureAwait(false); | ||
| var messages = GetMessagesForRun(aiAgent, session, ctx.Messages); |
There was a problem hiding this comment.
AGUIEndpointRouteBuilderExtensions.cs:148-155,198-200 restores only the thread’s current provider session and trims every provider-backed transcript after its last assistant message without consulting ParentRunId, so branching from an earlier run executes against the current tip and loses the branch history; a safe fix must distinguish true branches from ordinary ParentRunId continuations so existing history deduplication is preserved.
Motivation & Context
AG-UI clients send the complete conversation transcript on each turn. When
MapAGUIServerrestores aChatClientAgentsession, that prior history is already available through either the localChatHistoryProvideror a provider-managed conversation. Forwarding the full transcript again duplicates earlier messages, increasing token usage and potentially degrading model responses.Description & Review Guide
ChatClientAgentsessions and narrows incoming AG-UI transcripts to the new suffix. For readable in-memory history, it verifies in one backward pass that stored message IDs are an ordered prefix of the incoming transcript; for provider-managed conversations, it keeps messages after the final assistant boundary. First turns, incremental-only requests, reordered/non-prefix history, and unknown custom agents remain unchanged. Regression tests cover complete ordered history, non-prefix history, repeated user text, local and provider client-tool-result continuations, and custom agents.Related Issue
Fixes #7930
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.