.NET: [Breaking] RenameAgentRunResponse and AgentRunResponseUpdate classes (#3197)

* rename AgentRunResponse and AgentRunResponseUpdate classes - part1

* rename varialbles, parameters, methods and tests

* rollback unnecessary changes
This commit is contained in:
SergeyMenshykh
2026-01-14 10:27:41 +00:00
committed by GitHub
parent 8b1449024e
commit c70e594e6c
189 changed files with 1002 additions and 1002 deletions
+18 -18
View File
@@ -163,8 +163,8 @@ foreach (var update in response.Messages)
### Option 2 Run: Container with Primary and Secondary Properties, RunStreaming: Stream of Primary + Secondary
Run returns a new response type that has separate properties for the Primary Content and the Secondary Updates leading up to it.
The Primary content is available in the `AgentRunResponse.Messages` property while Secondary updates are in a new `AgentRunResponse.Updates` property.
`AgentRunResponse.Text` returns the Primary content text.
The Primary content is available in the `AgentResponse.Messages` property while Secondary updates are in a new `AgentResponse.Updates` property.
`AgentResponse.Text` returns the Primary content text.
Since streaming would still need to return an `IAsyncEnumerable` of updates, the design would differ from non-streaming.
With non-streaming Primary and Secondary content is split into separate lists, while with streaming it's combined in one stream.
@@ -232,24 +232,24 @@ await foreach (var update in responses)
```csharp
class Agent
{
public abstract Task<AgentRunResponse> RunAsync(
public abstract Task<AgentResponse> RunAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
public abstract IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
}
class AgentRunResponse : ChatResponse
class AgentResponse : ChatResponse
{
}
public class AgentRunResponseUpdate : ChatResponseUpdate
public class AgentResponseUpdate : ChatResponseUpdate
{
}
```
@@ -265,20 +265,20 @@ The new types could also exclude properties that make less sense for agents, lik
```csharp
class Agent
{
public abstract Task<AgentRunResponse> RunAsync(
public abstract Task<AgentResponse> RunAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
public abstract IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
}
class AgentRunResponse // Compare with ChatResponse
class AgentResponse // Compare with ChatResponse
{
public string Text { get; } // Aggregation of TextContent from messages.
@@ -294,12 +294,12 @@ class AgentRunResponse // Compare with ChatResponse
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}
// Not Included in AgentRunResponse compared to ChatResponse
// Not Included in AgentResponse compared to ChatResponse
public ChatFinishReason? FinishReason { get; set; }
public string? ConversationId { get; set; }
public string? ModelId { get; set; }
public class AgentRunResponseUpdate // Compare with ChatResponseUpdate
public class AgentResponseUpdate // Compare with ChatResponseUpdate
{
public string Text { get; } // Aggregation of TextContent from Contents.
@@ -317,7 +317,7 @@ public class AgentRunResponseUpdate // Compare with ChatResponseUpdate
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
}
// Not Included in AgentRunResponseUpdate compared to ChatResponseUpdate
// Not Included in AgentResponseUpdate compared to ChatResponseUpdate
public ChatFinishReason? FinishReason { get; set; }
public string? ConversationId { get; set; }
public string? ModelId { get; set; }
@@ -360,7 +360,7 @@ public class ChatFinishReason
### Option 2: Add another property on responses for AgentRun
```csharp
class AgentRunResponse
class AgentResponse
{
...
public AgentRun RunReference { get; set; } // Reference to long running process
@@ -368,7 +368,7 @@ class AgentRunResponse
}
public class AgentRunResponseUpdate
public class AgentResponseUpdate
{
...
public AgentRun RunReference { get; set; } // Reference to long running process
@@ -424,7 +424,7 @@ Note that where an agent doesn't support structured output, it may also be possi
See [Structured Outputs Support](#structured-outputs-support) for a comparison on what other agent frameworks and protocols support.
To support a good user experience for structured outputs, I'm proposing that we follow the pattern used by MEAI.
We would add a generic version of `AgentRunResponse<T>`, that allows us to get the agent result already deserialized into our preferred type.
We would add a generic version of `AgentResponse<T>`, that allows us to get the agent result already deserialized into our preferred type.
This would be coupled with generic overload extension methods for Run that automatically builds a schema from the supplied type and updates
the run options.
@@ -438,14 +438,14 @@ class Movie
public int ReleaseYear { get; set; }
}
AgentRunResponse<Movie[]> response = agent.RunAsync<Movie[]>("What are the top 3 children's movies of the 80s.");
AgentResponse<Movie[]> response = agent.RunAsync<Movie[]>("What are the top 3 children's movies of the 80s.");
Movie[] movies = response.Result
```
If we only support requesting a schema at agent creation time or where an agent has a built in schema, the following would be the preferred approach:
```csharp
AgentRunResponse response = agent.RunAsync("What are the top 3 children's movies of the 80s.");
AgentResponse response = agent.RunAsync("What are the top 3 children's movies of the 80s.");
Movie[] movies = response.TryParseStructuredOutput<Movie[]>();
```
@@ -463,7 +463,7 @@ Option 2 chosen so that we can vary Agent responses independently of Chat Client
### StructuredOutputs Decision
We will not support structured output per run request, but individual agents are free to allow this on the concrete implementation or at construction time.
We will however add support for easily extracting a structured output type from the `AgentRunResponse`.
We will however add support for easily extracting a structured output type from the `AgentResponse`.
## Addendum 1: AIContext Derived Types for different response types / Gap Analysis (Work in progress)
@@ -54,7 +54,7 @@ The table below represents the majority of the naming changes discussed in issue
| *Mcp* & *Http* | *MCP* & *HTTP* | accepted | Acronyms should be uppercased in class names, according to PEP 8. | None |
| `agent.run_streaming` | `agent.run_stream` | accepted | Shorter and more closely aligns with AutoGen and Semantic Kernel names for the same methods. | None |
| `workflow.run_streaming` | `workflow.run_stream` | accepted | In sync with `agent.run_stream` and shorter and more closely aligns with AutoGen and Semantic Kernel names for the same methods. | None |
| AgentRunResponse & AgentRunResponseUpdate | AgentResponse & AgentResponseUpdate | rejected | Rejected, because it is the response to a run invocation and AgentResponse is too generic. | None |
| AgentResponse & AgentResponseUpdate | AgentResponse & AgentResponseUpdate | rejected | Rejected, because it is the response to a run invocation and AgentResponse is too generic. | None |
| *Content | * | rejected | Rejected other content type renames (removing `Content` suffix) because it would reduce clarity and discoverability. | Item was also considered, but rejected as it is very similar to Content, but would be inconsistent with dotnet. |
| ChatResponse & ChatResponseUpdate | Response & ResponseUpdate | rejected | Rejected, because Response is too generic. | None |
+6 -6
View File
@@ -161,11 +161,11 @@ while (response.ApprovalRequests.Count > 0)
response = await agent.RunAsync(messages, thread);
}
class AgentRunResponse
class AgentResponse
{
...
// A new property on AgentRunResponse to aggregate the ApprovalRequestContent items from
// A new property on AgentResponse to aggregate the ApprovalRequestContent items from
// the response messages (Similar to the Text property).
public IEnumerable<ApprovalRequestContent> ApprovalRequests { get; set; }
@@ -251,11 +251,11 @@ while (response.UserInputRequests.Any())
response = await agent.RunAsync(messages, thread);
}
class AgentRunResponse
class AgentResponse
{
...
// A new property on AgentRunResponse to aggregate the UserInputRequestContent items from
// A new property on AgentResponse to aggregate the UserInputRequestContent items from
// the response messages (Similar to the Text property).
public IReadOnlyList<UserInputRequestContent> UserInputRequests { get; set; }
@@ -366,11 +366,11 @@ while (response.UserInputRequests.Any())
response = await agent.RunAsync(messages, thread);
}
class AgentRunResponse
class AgentResponse
{
...
// A new property on AgentRunResponse to aggregate the UserInputRequestContent items from
// A new property on AgentResponse to aggregate the UserInputRequestContent items from
// the response messages (Similar to the Text property).
public IEnumerable<UserInputRequestContent> UserInputRequests { get; set; }
@@ -115,7 +115,7 @@ public class AIAgent
}
}
public async Task<AgentRunResponse> RunAsync(
public async Task<AgentResponse> RunAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
@@ -135,7 +135,7 @@ public class AIAgent
return context.Response ?? throw new InvalidOperationException("Agent execution did not produce a response");
}
protected abstract Task<AgentRunResponse> ExecuteCoreLogicAsync(
protected abstract Task<AgentResponse> ExecuteCoreLogicAsync(
IReadOnlyCollection<ChatMessage> messages,
AgentThread? thread,
AgentRunOptions? options,
@@ -190,7 +190,7 @@ internal sealed class GuardrailCallbackAgent : DelegatingAIAgent
public GuardrailCallbackAgent(AIAgent innerAgent) : base(innerAgent) { }
public override async Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
public override async Task<AgentResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
var filteredMessages = this.FilterMessages(messages);
Console.WriteLine($"Guardrail Middleware - Filtered messages: {new ChatResponse(filteredMessages).Text}");
@@ -202,14 +202,14 @@ internal sealed class GuardrailCallbackAgent : DelegatingAIAgent
return response;
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var filteredMessages = this.FilterMessages(messages);
await foreach (var update in this.InnerAgent.RunStreamingAsync(filteredMessages, thread, options, cancellationToken))
{
if (update.Text != null)
{
yield return new AgentRunResponseUpdate(update.Role, this.FilterContent(update.Text));
yield return new AgentResponseUpdate(update.Role, this.FilterContent(update.Text));
}
else
{
@@ -252,7 +252,7 @@ internal sealed class RunningCallbackHandlerAgent : DelegatingAIAgent
this._func = func;
}
public override async Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
public override async Task<AgentResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
var context = new AgentInvokeCallbackContext(this, messages, thread, options, isStreaming: false, cancellationToken);
@@ -469,7 +469,7 @@ public sealed class CallbackEnabledAgent : DelegatingAIAgent
this._callbacksProcessor = callbackMiddlewareProcessor ?? new();
}
public override async Task<AgentRunResponse> RunAsync(
public override async Task<AgentResponse> RunAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
@@ -541,7 +541,7 @@ public abstract class AgentContext
public class AgentRunContext : AgentContext
{
public IList<ChatMessage> Messages { get; set; }
public AgentRunResponse? Response { get; set; }
public AgentResponse? Response { get; set; }
public AgentThread? Thread { get; }
public AgentRunContext(AIAgent agent, IList<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options)
@@ -687,7 +687,7 @@ This section considers different options for exposing the `RunId`, `Status`, and
#### 4.1. As AIContent
The `AsyncRunContent` class will represent a long-running operation initiated and managed by an agent/LLM.
Items of this content type will be returned in a chat message as part of the `AgentRunResponse` or `ChatResponse`
Items of this content type will be returned in a chat message as part of the `AgentResponse` or `ChatResponse`
response to represent the long-running operation.
The `AsyncRunContent` class has two properties: `RunId` and `Status`. The `RunId` identifies the
@@ -1162,29 +1162,29 @@ For cancellation and deletion of long-running operations, new methods will be ad
public abstract class AIAgent
{
// Existing methods...
public Task<AgentRunResponse> RunAsync(string message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { ... }
public IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(string message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { ... }
public Task<AgentResponse> RunAsync(string message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { ... }
public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(string message, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { ... }
// New methods for uncommon operations
public virtual Task<AgentRunResponse?> CancelRunAsync(string id, AgentCancelRunOptions? options = null, CancellationToken cancellationToken = default)
public virtual Task<AgentResponse?> CancelRunAsync(string id, AgentCancelRunOptions? options = null, CancellationToken cancellationToken = default)
{
return Task.FromResult<AgentRunResponse?>(null);
return Task.FromResult<AgentResponse?>(null);
}
public virtual Task<AgentRunResponse?> DeleteRunAsync(string id, AgentDeleteRunOptions? options = null, CancellationToken cancellationToken = default)
public virtual Task<AgentResponse?> DeleteRunAsync(string id, AgentDeleteRunOptions? options = null, CancellationToken cancellationToken = default)
{
return Task.FromResult<AgentRunResponse?>(null);
return Task.FromResult<AgentResponse?>(null);
}
}
// Agent that supports update and cancellation
public class CustomAgent : AIAgent
{
public override async Task<AgentRunResponse?> CancelRunAsync(string id, AgentCancelRunOptions? options = null, CancellationToken cancellationToken = default)
public override async Task<AgentResponse?> CancelRunAsync(string id, AgentCancelRunOptions? options = null, CancellationToken cancellationToken = default)
{
var response = await this._client.CancelRunAsync(id, options?.Thread?.ConversationId);
return ConvertToAgentRunResponse(response);
return ConvertToAgentResponse(response);
}
// No overload for DeleteRunAsync as it's not supported by the underlying API
@@ -1195,7 +1195,7 @@ AIAgent agent = new CustomAgent();
AgentThread thread = agent.GetNewThread();
AgentRunResponse response = await agent.RunAsync("What is the capital of France?");
AgentResponse response = await agent.RunAsync("What is the capital of France?");
response = await agent.CancelRunAsync(response.ResponseId, new AgentCancelRunOptions { Thread = thread });
```
@@ -1251,10 +1251,10 @@ public class AgentRunOptions
AIAgent agent = ...; // Get an instance of an AIAgent
// Start a long-running execution for the prompt if supported by the underlying API
AgentRunResponse response = await agent.RunAsync("<prompt>", new AgentRunOptions { AllowLongRunningResponses = true });
AgentResponse response = await agent.RunAsync("<prompt>", new AgentRunOptions { AllowLongRunningResponses = true });
// Start a quick prompt
AgentRunResponse response = await agent.RunAsync("<prompt>");
AgentResponse response = await agent.RunAsync("<prompt>");
```
**Pros:**
@@ -1279,7 +1279,7 @@ Below are the details of the option selected for chat clients that is also selec
#### 3.1 Continuation Token of a Custom Type
This option suggests using `ContinuationToken` to encapsulate all properties representing a long-running operation. The continuation token will be returned by agents in the
`ContinuationToken` property of the `AgentRunResponse` and `AgentRunResponseUpdate` responses to indicate that the response is part of a long-running operation. A null value
`ContinuationToken` property of the `AgentResponse` and `AgentResponseUpdate` responses to indicate that the response is part of a long-running operation. A null value
of the property will indicate that the response is not part of a long-running operation or the long-running operation has been completed. Callers will set the token in the
`ContinuationToken` property of the `AgentRunOptions` class in follow-up calls to the `Run{Streaming}Async` methods to indicate that they want to "continue" the long-running
operation identified by the token.
@@ -1313,18 +1313,18 @@ public class AgentRunOptions
public ResponseContinuationToken? ContinuationToken { get; set; }
}
public class AgentRunResponse
public class AgentResponse
{
public ResponseContinuationToken? ContinuationToken { get; }
}
public class AgentRunResponseUpdate
public class AgentResponseUpdate
{
public ResponseContinuationToken? ContinuationToken { get; }
}
// Usage example
AgentRunResponse response = await agent.RunAsync("What is the capital of France?");
AgentResponse response = await agent.RunAsync("What is the capital of France?");
AgentRunOptions options = new() { ContinuationToken = response.ContinuationToken };
+2 -2
View File
@@ -36,7 +36,7 @@ Chosen option: "Current approach with internal event types and framework-native
- Protects consumers from protocol changes by keeping AG-UI events internal
- Maintains framework abstractions through conversion at boundaries
- Uses existing framework types (AgentRunResponseUpdate, ChatMessage) for public API
- Uses existing framework types (AgentResponseUpdate, ChatMessage) for public API
- Focuses on core text streaming functionality
- Leverages existing properties (ConversationId, ResponseId, ErrorContent) instead of custom types
- Provides bidirectional client and server support
@@ -69,7 +69,7 @@ Chosen option: "Current approach with internal event types and framework-native
3. **Agent Factory Pattern** - `MapAGUIAgent` uses factory function `(messages) => AIAgent` to allow request-specific agent configuration supporting multi-tenancy
4. **Bidirectional Conversion Architecture** - Symmetric conversion logic in shared namespace compiled into both packages for server (`AgentRunResponseUpdate` → AG-UI events) and client (AG-UI events → `AgentRunResponseUpdate`)
4. **Bidirectional Conversion Architecture** - Symmetric conversion logic in shared namespace compiled into both packages for server (`AgentResponseUpdate` → AG-UI events) and client (AG-UI events → `AgentResponseUpdate`)
5. **Thread Management** - `AGUIAgentThread` stores only `ThreadId` with thread ID communicated via `ConversationId`; applications manage persistence for parity with other implementations and to be compliant with the protocol. Future extensions will support having the server manage the conversation.
+5 -5
View File
@@ -125,7 +125,7 @@ The proposed solution is to add helper methods which allow developers to either
- [Foundry SDK] Create a `PersistentAgentsClient`
- [Foundry SDK] Create a `PersistentAgent` using the `PersistentAgentsClient`
- [Foundry SDK] Retrieve an `AIAgent` using the `PersistentAgentsClient`
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentRunResponse`
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentResponse`
- [Foundry SDK] Clean up the agent
@@ -156,7 +156,7 @@ await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
- [Foundry SDK] Create a `PersistentAgentsClient`
- [Foundry SDK] Create a `AIAgent` using the `PersistentAgentsClient`
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentRunResponse`
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentResponse`
- [Foundry SDK] Clean up the agent
```csharp
@@ -184,7 +184,7 @@ await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
- [Foundry SDK] Create a `PersistentAgentsClient`
- [Foundry SDK] Create a `AIAgent` using the `PersistentAgentsClient`
- [Agent Framework SDK] Optionally create an `AgentThread` for the agent run
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentRunResponse`
- [Agent Framework SDK] Invoke the `AIAgent` instance and access response from the `AgentResponse`
- [Foundry SDK] Clean up the agent and the agent thread
```csharp
@@ -227,7 +227,7 @@ await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
- [Foundry SDK] Create a `PersistentAgentsClient`
- [Foundry SDK] Create multiple `AIAgent` instances using the `PersistentAgentsClient`
- [Agent Framework SDK] Create a `SequentialOrchestration` and add all of the agents to it
- [Agent Framework SDK] Invoke the `SequentialOrchestration` instance and access response from the `AgentRunResponse`
- [Agent Framework SDK] Invoke the `SequentialOrchestration` instance and access response from the `AgentResponse`
- [Foundry SDK] Clean up the agents
```csharp
@@ -281,7 +281,7 @@ SequentialOrchestration orchestration =
// Run the orchestration
string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
Console.WriteLine($"\n# INPUT: {input}\n");
AgentRunResponse result = await orchestration.RunAsync(input);
AgentResponse result = await orchestration.RunAsync(input);
Console.WriteLine($"\n# RESULT: {result}");
// Cleanup