-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Description
When using Microsoft.Agents.AI with Azure OpenAI Responses API through AsIChatClient(), ChatClientAgent throws if a ChatHistoryProvider is configured, even when the request is explicitly stateless (store=false / StoredOutputEnabled = false).
The exception is:
Only ConversationId or ChatHistoryProvider may be used, but not both. The service returned a conversation id indicating server-side chat history management, but the agent has a ChatHistoryProvider configured.
From captured HTTP traffic:
- the request is sent with
"store": false - the final JSON response does not contain a
conversationfield - the final JSON response has
"previous_response_id": null
This suggests the issue is not caused by Azure OpenAI returning a real conversation id. Instead, it appears that somewhere in the adapter chain, a non-null ConversationId is surfaced to ChatClientAgent, causing MAF to incorrectly assume server-side history management.
From source inspection, ChatClientAgent throws whenever the adapted ChatResponse / ChatResponseUpdate contains a non-null ConversationId, regardless of whether the underlying provider is actually using a real server-side conversation.
Relevant MAF code:
RunCoreStreamingAsync(...)combines updates viaToChatResponse()and then callsUpdateSessionConversationId(...)RunCoreAsync(...)also callsUpdateSessionConversationId(...)UpdateSessionConversationId(...)throws if bothConversationIdandChatHistoryProviderare present
This is especially problematic for Azure OpenAI Responses scenarios where the caller explicitly wants to manage history client-side with ChatHistoryProvider.
Code Sample
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!;
string deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")!;
var azureClient = new AzureOpenAIClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
var responsesClient = azureClient.GetResponsesClient(deployment);
IChatClient chatClient = responsesClient.AsIChatClient();
var agent = new ChatClientAgent(
chatClient,
new ChatClientAgentOptions
{
Name = "ReproAgent",
ChatOptions = new ChatOptions
{
Instructions = "You are a helpful assistant.",
RawRepresentationFactory = _ => new CreateResponseOptions
{
StoredOutputEnabled = false
}
},
ChatHistoryProvider = new InMemoryChatHistoryProvider()
});
AgentSession session = await agent.CreateSessionAsync();
// Non-streaming
var response = await agent.RunAsync("Say hello.", session);
// Streaming
await foreach (var update in agent.RunStreamingAsync("Say hello.", session))
{
Console.Write(update.Text);
}
## Captured HTTP Request
{
"model": "gpt-4.1-mini",
"input": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "How would you define Zava business in just 5 words?"
}
]
}
],
"stream": true,
"store": false
}
## Captured HTTP Response
{
"id": "resp_0c09e9748364cb3c0169c573ecf76881909672744ee7074f94",
"object": "response",
"status": "completed",
"model": "gpt-4.1-mini",
"output": [
{
"type": "message",
"role": "assistant",
"status": "completed",
"content": [
{
"type": "output_text",
"text": "..."
}
]
}
],
"previous_response_id": null,
"store": false
}
Notably, there is no `conversation` field in the response.Error Messages / Stack Traces
Only ConversationId or ChatHistoryProvider may be used, but not both. The service returned a conversation id indicating server-side chat history management, but the agent has a ChatHistoryProvider configured.
at Microsoft.Agents.AI.ChatClientAgent.UpdateSessionConversationId(ChatClientAgentSession session, String responseConversationId, CancellationToken cancellationToken)
at Microsoft.Agents.AI.ChatClientAgent.<RunCoreStreamingAsync>d__29.MoveNext()
at Microsoft.Agents.AI.ChatClientAgent.<RunCoreStreamingAsync>d__29.System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(Int16 token)
at Microsoft.Agents.AI.AIAgent.<RunStreamingAsync>d__33.MoveNext()
at Microsoft.Agents.AI.AIAgent.<RunStreamingAsync>d__33.MoveNext()
at Microsoft.Agents.AI.AIAgent.<RunStreamingAsync>d__33.System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(Int16 token)Package Versions
Microsoft.Agents.AI 1.0.0-rc3, Microsoft.Extensions.AI 10.3.0, Microsoft.Agents.AI.OpenAI 1.0.0-rc3
.NET Version
.NET 10
Additional Context
A few observations that may help narrow this down:
- This reproduces with Azure OpenAI Responses API.
- The outgoing request explicitly includes
"store": false. - The final HTTP response does not contain a
conversationfield. - The final HTTP response has
"previous_response_id": null. - MAF throws only because
ChatClientAgentreceives a non-nullConversationIdfrom the adaptedChatResponse/ChatResponseUpdate. - This appears to happen even though the underlying HTTP response does not contain a real server-managed conversation id.
Potentially relevant MAF source:
dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.csdotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
Behavioral expectation:
- If
ChatHistoryProvideris configured and the request is stateless (store=false),ChatClientAgentshould not treat the returned value as a real server-side conversation id and should not throw.
Actual behavior:
ChatClientAgentthrows as if the provider were managing server-side history, even though the Responses request/response indicates a stateless flow.
Note
I am aware that the underlying adapter stack may be mapping Responses semantics into ConversationId in a way that is broader than a true server-managed conversation. Even so, from the MAF side this currently makes ChatHistoryProvider unusable for this scenario, despite the request being explicitly stateless.