mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.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:
committed by
GitHub
Unverified
parent
8b1449024e
commit
c70e594e6c
@@ -105,7 +105,7 @@ After completing migration, verify these specific items:
|
||||
1. **Compilation**: Execute `dotnet build` on all modified projects - zero errors required
|
||||
2. **Namespace Updates**: Confirm all `using Microsoft.SemanticKernel.Agents` statements are replaced
|
||||
3. **Method Calls**: Verify all `InvokeAsync` calls are changed to `RunAsync`
|
||||
4. **Return Types**: Confirm handling of `AgentRunResponse` instead of `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>`
|
||||
4. **Return Types**: Confirm handling of `AgentResponse` instead of `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>`
|
||||
5. **Thread Creation**: Validate all thread creation uses `agent.GetNewThread()` pattern
|
||||
6. **Tool Registration**: Ensure `[KernelFunction]` attributes are removed and `AIFunctionFactory.Create()` is used
|
||||
7. **Options Configuration**: Verify `AgentRunOptions` or `ChatClientAgentRunOptions` replaces `AgentInvokeOptions`
|
||||
@@ -119,7 +119,7 @@ Agent Framework provides functionality for creating and managing AI agents throu
|
||||
Key API differences:
|
||||
- Agent creation: Remove Kernel dependency, use direct client-based creation
|
||||
- Method names: `InvokeAsync` → `RunAsync`, `InvokeStreamingAsync` → `RunStreamingAsync`
|
||||
- Return types: `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` → `AgentRunResponse`
|
||||
- Return types: `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` → `AgentResponse`
|
||||
- Thread creation: Provider-specific constructors → `agent.GetNewThread()`
|
||||
- Tool registration: `KernelPlugin` system → Direct `AIFunction` registration
|
||||
- Options: `AgentInvokeOptions` → Provider-specific run options (e.g., `ChatClientAgentRunOptions`)
|
||||
@@ -166,8 +166,8 @@ Replace these method calls:
|
||||
| `thread.DeleteAsync()` | Provider-specific cleanup | Use provider client directly |
|
||||
|
||||
Return type changes:
|
||||
- `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` → `AgentRunResponse`
|
||||
- `IAsyncEnumerable<StreamingChatMessageContent>` → `IAsyncEnumerable<AgentRunResponseUpdate>`
|
||||
- `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` → `AgentResponse`
|
||||
- `IAsyncEnumerable<StreamingChatMessageContent>` → `IAsyncEnumerable<AgentResponseUpdate>`
|
||||
</api_changes>
|
||||
|
||||
<configuration_changes>
|
||||
@@ -191,8 +191,8 @@ Agent Framework changes these behaviors compared to Semantic Kernel Agents:
|
||||
1. **Thread Management**: Agent Framework automatically manages thread state. Semantic Kernel required manual thread updates in some scenarios (e.g., OpenAI Responses).
|
||||
|
||||
2. **Return Types**:
|
||||
- Non-streaming: Returns single `AgentRunResponse` instead of `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>`
|
||||
- Streaming: Returns `IAsyncEnumerable<AgentRunResponseUpdate>` instead of `IAsyncEnumerable<StreamingChatMessageContent>`
|
||||
- Non-streaming: Returns single `AgentResponse` instead of `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>`
|
||||
- Streaming: Returns `IAsyncEnumerable<AgentResponseUpdate>` instead of `IAsyncEnumerable<StreamingChatMessageContent>`
|
||||
|
||||
3. **Tool Registration**: Agent Framework uses direct function registration without requiring `[KernelFunction]` attributes.
|
||||
|
||||
@@ -397,7 +397,7 @@ await foreach (AgentResponseItem<ChatMessageContent> item in agent.InvokeAsync(u
|
||||
|
||||
**With this Agent Framework non-streaming pattern:**
|
||||
```csharp
|
||||
AgentRunResponse result = await agent.RunAsync(userInput, thread, options);
|
||||
AgentResponse result = await agent.RunAsync(userInput, thread, options);
|
||||
Console.WriteLine(result);
|
||||
```
|
||||
|
||||
@@ -411,7 +411,7 @@ await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(
|
||||
|
||||
**With this Agent Framework streaming pattern:**
|
||||
```csharp
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(userInput, thread, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(userInput, thread, options))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
@@ -420,8 +420,8 @@ await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(userInpu
|
||||
**Required changes:**
|
||||
1. Replace `agent.InvokeAsync()` with `agent.RunAsync()`
|
||||
2. Replace `agent.InvokeStreamingAsync()` with `agent.RunStreamingAsync()`
|
||||
3. Change return type handling from `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` to `AgentRunResponse`
|
||||
4. Change streaming type from `StreamingChatMessageContent` to `AgentRunResponseUpdate`
|
||||
3. Change return type handling from `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` to `AgentResponse`
|
||||
4. Change streaming type from `StreamingChatMessageContent` to `AgentResponseUpdate`
|
||||
5. Remove `await foreach` for non-streaming calls
|
||||
6. Access message content directly from result object instead of iterating
|
||||
</api_changes>
|
||||
@@ -661,7 +661,7 @@ await foreach (var result in agent.InvokeAsync(input, thread, options))
|
||||
```csharp
|
||||
ChatClientAgentRunOptions options = new(new ChatOptions { MaxOutputTokens = 1000 });
|
||||
|
||||
AgentRunResponse result = await agent.RunAsync(input, thread, options);
|
||||
AgentResponse result = await agent.RunAsync(input, thread, options);
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Access underlying content when needed:
|
||||
@@ -689,7 +689,7 @@ await foreach (var result in agent.InvokeAsync(input, thread, options))
|
||||
|
||||
**With this Agent Framework non-streaming usage pattern:**
|
||||
```csharp
|
||||
AgentRunResponse result = await agent.RunAsync(input, thread, options);
|
||||
AgentResponse result = await agent.RunAsync(input, thread, options);
|
||||
Console.WriteLine($"Tokens: {result.Usage.TotalTokenCount}");
|
||||
```
|
||||
|
||||
@@ -709,7 +709,7 @@ await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsyn
|
||||
|
||||
**With this Agent Framework streaming usage pattern:**
|
||||
```csharp
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, thread, options))
|
||||
{
|
||||
if (update.Contents.OfType<UsageContent>().FirstOrDefault() is { } usageContent)
|
||||
{
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -114,7 +114,7 @@ public static class Program
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
var updates = new List<ChatResponseUpdate>();
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: cancellationToken))
|
||||
{
|
||||
// Use AsChatResponseUpdate to access ChatResponseUpdate properties
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
@@ -19,12 +19,12 @@ internal sealed class AgenticUIAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -69,7 +69,7 @@ internal sealed class AgenticUIAgent : DelegatingAIAgent
|
||||
|
||||
yield return update;
|
||||
|
||||
yield return new AgentRunResponseUpdate(
|
||||
yield return new AgentResponseUpdate(
|
||||
new ChatResponseUpdate(role: ChatRole.System, stateEventsToEmit)
|
||||
{
|
||||
MessageId = "delta_" + Guid.NewGuid().ToString("N"),
|
||||
|
||||
+4
-4
@@ -20,12 +20,12 @@ internal sealed class PredictiveStateUpdatesAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -79,7 +79,7 @@ internal sealed class PredictiveStateUpdatesAgent : DelegatingAIAgent
|
||||
stateUpdate,
|
||||
this._jsonSerializerOptions.GetTypeInfo(typeof(DocumentState)));
|
||||
|
||||
yield return new AgentRunResponseUpdate(
|
||||
yield return new AgentResponseUpdate(
|
||||
new ChatResponseUpdate(role: ChatRole.Assistant, [new DataContent(stateBytes, "application/json")])
|
||||
{
|
||||
MessageId = "snapshot" + Guid.NewGuid().ToString("N"),
|
||||
|
||||
@@ -19,12 +19,12 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -63,7 +63,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
|
||||
var firstRunMessages = messages.Append(stateUpdateMessage);
|
||||
|
||||
var allUpdates = new List<AgentRunResponseUpdate>();
|
||||
var allUpdates = new List<AgentResponseUpdate>();
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
allUpdates.Add(update);
|
||||
@@ -76,14 +76,14 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
}
|
||||
}
|
||||
|
||||
var response = allUpdates.ToAgentRunResponse();
|
||||
var response = allUpdates.ToAgentResponse();
|
||||
|
||||
if (response.TryDeserialize(this._jsonSerializerOptions, out JsonElement stateSnapshot))
|
||||
{
|
||||
byte[] stateBytes = JsonSerializer.SerializeToUtf8Bytes(
|
||||
stateSnapshot,
|
||||
this._jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)));
|
||||
yield return new AgentRunResponseUpdate
|
||||
yield return new AgentResponseUpdate
|
||||
{
|
||||
Contents = [new DataContent(stateBytes, "application/json")]
|
||||
};
|
||||
|
||||
@@ -151,9 +151,9 @@ AIAgent agent = chatClient.CreateAIAgent(
|
||||
tools: []);
|
||||
|
||||
bool isFirstUpdate = true;
|
||||
AgentRunResponseUpdate? currentUpdate = null;
|
||||
AgentResponseUpdate? currentUpdate = null;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
@@ -190,19 +190,19 @@ if (currentUpdate != null)
|
||||
The `RunStreamingAsync` method:
|
||||
1. Sends messages to the server via HTTP POST
|
||||
2. Receives server-sent events (SSE) stream
|
||||
3. Parses events into `AgentRunResponseUpdate` objects
|
||||
3. Parses events into `AgentResponseUpdate` objects
|
||||
4. Yields updates as they arrive for real-time display
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Thread**: Represents a conversation context that persists across multiple runs (accessed via `ConversationId` property)
|
||||
- **Run**: A single execution of the agent for a given set of messages (identified by `ResponseId` property)
|
||||
- **AgentRunResponseUpdate**: Contains the response data with:
|
||||
- **AgentResponseUpdate**: Contains the response data with:
|
||||
- `ResponseId`: The unique run identifier
|
||||
- `ConversationId`: The thread/conversation identifier
|
||||
- `Contents`: Collection of content items (TextContent, ErrorContent, etc.)
|
||||
- **Run Lifecycle**:
|
||||
- The **first** `AgentRunResponseUpdate` in a run indicates the run has started
|
||||
- The **first** `AgentResponseUpdate` in a run indicates the run has started
|
||||
- Subsequent updates contain streaming content as the agent processes
|
||||
- The **last** `AgentRunResponseUpdate` in a run indicates the run has finished
|
||||
- The **last** `AgentResponseUpdate` in a run indicates the run has finished
|
||||
- If an error occurs, the update will contain `ErrorContent`
|
||||
@@ -25,7 +25,7 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
this._uri = baseUri;
|
||||
}
|
||||
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -37,7 +37,7 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
var contextId = threadId ?? Guid.NewGuid().ToString("N");
|
||||
|
||||
// Convert and send messages via A2A without try-catch in yield method
|
||||
var results = new List<AgentRunResponseUpdate>();
|
||||
var results = new List<AgentResponseUpdate>();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -60,7 +60,7 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
var responseMessage = message.ToChatMessage();
|
||||
if (responseMessage is { Contents.Count: > 0 })
|
||||
{
|
||||
results.Add(new AgentRunResponseUpdate(responseMessage.Role, responseMessage.Contents)
|
||||
results.Add(new AgentResponseUpdate(responseMessage.Role, responseMessage.Contents)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
@@ -90,7 +90,7 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
RawRepresentation = artifact,
|
||||
};
|
||||
|
||||
results.Add(new AgentRunResponseUpdate(chatMessage.Role, chatMessage.Contents)
|
||||
results.Add(new AgentResponseUpdate(chatMessage.Role, chatMessage.Contents)
|
||||
{
|
||||
MessageId = agentTask.Id,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
@@ -108,7 +108,7 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
{
|
||||
this._logger.LogError(ex, "Error running agent {AgentName} via A2A", agentName);
|
||||
|
||||
results.Add(new AgentRunResponseUpdate(ChatRole.Assistant, $"Error: {ex.Message}")
|
||||
results.Add(new AgentResponseUpdate(ChatRole.Assistant, $"Error: {ex.Message}")
|
||||
{
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
|
||||
@@ -19,7 +19,7 @@ internal abstract class AgentClientBase
|
||||
/// <param name="threadId">Optional thread identifier for conversation continuity.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>An asynchronous enumerable of agent response updates.</returns>
|
||||
public abstract IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace AgentWebChat.Web;
|
||||
/// </summary>
|
||||
internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) : AgentClientBase
|
||||
{
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -31,7 +31,7 @@ internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) :
|
||||
var openAiClient = new ChatClient(model: "myModel!", credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
|
||||
await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, cancellationToken: cancellationToken))
|
||||
{
|
||||
yield return new AgentRunResponseUpdate(update);
|
||||
yield return new AgentResponseUpdate(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace AgentWebChat.Web;
|
||||
/// </summary>
|
||||
internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentClientBase
|
||||
{
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -35,7 +35,7 @@ internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentC
|
||||
|
||||
await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, chatOptions, cancellationToken: cancellationToken))
|
||||
{
|
||||
yield return new AgentRunResponseUpdate(update);
|
||||
yield return new AgentResponseUpdate(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ public static class FunctionTriggers
|
||||
DurableAIAgent writer = context.GetAgent("WriterAgent");
|
||||
AgentThread writerThread = await writer.GetNewThreadAsync();
|
||||
|
||||
AgentRunResponse<TextResponse> initial = await writer.RunAsync<TextResponse>(
|
||||
AgentResponse<TextResponse> initial = await writer.RunAsync<TextResponse>(
|
||||
message: "Write a concise inspirational sentence about learning.",
|
||||
thread: writerThread);
|
||||
|
||||
AgentRunResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
|
||||
AgentResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
|
||||
message: $"Improve this further while keeping it under 25 words: {initial.Result.Text}",
|
||||
thread: writerThread);
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ public static class FunctionsTriggers
|
||||
DurableAIAgent chemist = context.GetAgent("ChemistAgent");
|
||||
|
||||
// Start both agent runs concurrently
|
||||
Task<AgentRunResponse<TextResponse>> physicistTask = physicist.RunAsync<TextResponse>(prompt);
|
||||
Task<AgentResponse<TextResponse>> physicistTask = physicist.RunAsync<TextResponse>(prompt);
|
||||
|
||||
Task<AgentRunResponse<TextResponse>> chemistTask = chemist.RunAsync<TextResponse>(prompt);
|
||||
Task<AgentResponse<TextResponse>> chemistTask = chemist.RunAsync<TextResponse>(prompt);
|
||||
|
||||
// Wait for both tasks to complete using Task.WhenAll
|
||||
await Task.WhenAll(physicistTask, chemistTask);
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ public static class FunctionTriggers
|
||||
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
|
||||
|
||||
// Step 1: Check if the email is spam
|
||||
AgentRunResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
message:
|
||||
$"""
|
||||
Analyze this email for spam content and return a JSON response with 'is_spam' (boolean) and 'reason' (string) fields:
|
||||
@@ -45,7 +45,7 @@ public static class FunctionTriggers
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
|
||||
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
|
||||
|
||||
AgentRunResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
message:
|
||||
$"""
|
||||
Draft a professional response to this email. Return a JSON response with a 'response' field containing the reply:
|
||||
|
||||
@@ -30,7 +30,7 @@ public static class FunctionTriggers
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
|
||||
// Step 1: Generate initial content
|
||||
AgentRunResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
@@ -26,7 +26,7 @@ public static class FunctionTriggers
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
|
||||
// Step 1: Generate initial content
|
||||
AgentRunResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
@@ -196,7 +196,7 @@ The `id` field is the Redis stream entry ID - use it as the `cursor` parameter t
|
||||
|
||||
2. **Agent invoked**: The durable entity (`AgentEntity`) is signaled to run the travel planner agent. This is fire-and-forget from the HTTP request's perspective.
|
||||
|
||||
3. **Responses captured**: As the agent generates responses, `RedisStreamResponseHandler` (implementing `IAgentResponseHandler`) extracts the text from each `AgentRunResponseUpdate` and publishes it to a Redis Stream keyed by session ID.
|
||||
3. **Responses captured**: As the agent generates responses, `RedisStreamResponseHandler` (implementing `IAgentResponseHandler`) extracts the text from each `AgentResponseUpdate` and publishes it to a Redis Stream keyed by session ID.
|
||||
|
||||
4. **Client polls Redis**: The HTTP response streams events by polling the Redis Stream. For SSE format, each event includes the Redis entry ID as the `id` field.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public readonly record struct StreamChunk(string EntryId, string? Text, bool IsD
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each agent session gets its own Redis Stream, keyed by session ID. The stream entries
|
||||
/// contain text chunks extracted from <see cref="AgentRunResponseUpdate"/> objects.
|
||||
/// contain text chunks extracted from <see cref="AgentResponseUpdate"/> objects.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
@@ -53,7 +53,7 @@ public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask OnStreamingResponseUpdateAsync(
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> messageStream,
|
||||
IAsyncEnumerable<AgentResponseUpdate> messageStream,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the current session ID from the DurableAgentContext
|
||||
@@ -73,7 +73,7 @@ public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
IDatabase db = this._redis.GetDatabase();
|
||||
int sequenceNumber = 0;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in messageStream.WithCancellation(cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in messageStream.WithCancellation(cancellationToken))
|
||||
{
|
||||
// Extract just the text content - this avoids serialization round-trip issues
|
||||
string text = update.Text;
|
||||
@@ -112,7 +112,7 @@ public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask OnAgentResponseAsync(AgentRunResponse message, CancellationToken cancellationToken)
|
||||
public ValueTask OnAgentResponseAsync(AgentResponse message, CancellationToken cancellationToken)
|
||||
{
|
||||
// This handler is optimized for streaming responses.
|
||||
// For non-streaming responses, we don't need to store in Redis since
|
||||
|
||||
@@ -19,7 +19,7 @@ AIAgent agent = agentCard.GetAIAgent();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run with a long-running task.
|
||||
AgentRunResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
|
||||
AgentResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
|
||||
@@ -212,7 +212,7 @@ dotnet run
|
||||
|
||||
1. `AGUIAgent` sends HTTP POST request to server
|
||||
2. Server responds with SSE stream
|
||||
3. Client parses events into `AgentRunResponseUpdate` objects
|
||||
3. Client parses events into `AgentResponseUpdate` objects
|
||||
4. Updates are displayed based on content type
|
||||
5. `ConversationId` maintains conversation context
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ try
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ try
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ try
|
||||
bool isFirstUpdate = true;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
|
||||
{
|
||||
approvalResponses.Clear();
|
||||
|
||||
List<AgentRunResponseUpdate> chatResponseUpdates = [];
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: default))
|
||||
List<AgentResponseUpdate> chatResponseUpdates = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: default))
|
||||
{
|
||||
chatResponseUpdates.Add(update);
|
||||
foreach (AIContent content in update.Contents)
|
||||
@@ -111,7 +111,7 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
|
||||
}
|
||||
}
|
||||
|
||||
AgentRunResponse response = chatResponseUpdates.ToAgentRunResponse();
|
||||
AgentResponse response = chatResponseUpdates.ToAgentResponse();
|
||||
messages.AddRange(response.Messages);
|
||||
foreach (AIContent approvalResponse in approvalResponses)
|
||||
{
|
||||
|
||||
+6
-6
@@ -22,17 +22,17 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentRunResponseAsync(cancellationToken);
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -166,8 +166,8 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
return result ?? messages;
|
||||
}
|
||||
|
||||
private static AgentRunResponseUpdate ProcessIncomingServerApprovalRequests(
|
||||
AgentRunResponseUpdate update,
|
||||
private static AgentResponseUpdate ProcessIncomingServerApprovalRequests(
|
||||
AgentResponseUpdate update,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
IList<AIContent>? updatedContents = null;
|
||||
@@ -215,7 +215,7 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
if (updatedContents is not null)
|
||||
{
|
||||
var chatUpdate = update.AsChatResponseUpdate();
|
||||
return new AgentRunResponseUpdate(new ChatResponseUpdate()
|
||||
return new AgentResponseUpdate(new ChatResponseUpdate()
|
||||
{
|
||||
Role = chatUpdate.Role,
|
||||
Contents = updatedContents,
|
||||
|
||||
+6
-6
@@ -22,17 +22,17 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentRunResponseAsync(cancellationToken);
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -172,8 +172,8 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
return result ?? messages;
|
||||
}
|
||||
|
||||
private static AgentRunResponseUpdate ProcessOutgoingApprovalRequests(
|
||||
AgentRunResponseUpdate update,
|
||||
private static AgentResponseUpdate ProcessOutgoingApprovalRequests(
|
||||
AgentResponseUpdate update,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
IList<AIContent>? updatedContents = null;
|
||||
@@ -207,7 +207,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
{
|
||||
var chatUpdate = update.AsChatResponseUpdate();
|
||||
// Yield a tool call update that represents the approval request
|
||||
return new AgentRunResponseUpdate(new ChatResponseUpdate()
|
||||
return new AgentResponseUpdate(new ChatResponseUpdate()
|
||||
{
|
||||
Role = chatUpdate.Role,
|
||||
Contents = updatedContents,
|
||||
|
||||
@@ -70,7 +70,7 @@ try
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
|
||||
@@ -35,18 +35,18 @@ internal sealed class StatefulAgent<TState> : DelegatingAIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentRunResponseAsync(cancellationToken);
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -64,7 +64,7 @@ internal sealed class StatefulAgent<TState> : DelegatingAIAgent
|
||||
messagesWithState.Add(stateMessage);
|
||||
|
||||
// Stream the response and update state when received
|
||||
await foreach (AgentRunResponseUpdate update in this.InnerAgent.RunStreamingAsync(messagesWithState, thread, options, cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in this.InnerAgent.RunStreamingAsync(messagesWithState, thread, options, cancellationToken))
|
||||
{
|
||||
// Check if this update contains a state snapshot
|
||||
foreach (AIContent content in update.Contents)
|
||||
|
||||
+6
-6
@@ -17,17 +17,17 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentRunResponseAsync(cancellationToken);
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -91,7 +91,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
var firstRunMessages = messages.Append(stateUpdateMessage);
|
||||
|
||||
// Collect all updates from first run
|
||||
var allUpdates = new List<AgentRunResponseUpdate>();
|
||||
var allUpdates = new List<AgentResponseUpdate>();
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
allUpdates.Add(update);
|
||||
@@ -104,7 +104,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
}
|
||||
}
|
||||
|
||||
var response = allUpdates.ToAgentRunResponse();
|
||||
var response = allUpdates.ToAgentResponse();
|
||||
|
||||
// Try to deserialize the structured state response
|
||||
if (response.TryDeserialize(this._jsonSerializerOptions, out JsonElement stateSnapshot))
|
||||
@@ -113,7 +113,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
byte[] stateBytes = JsonSerializer.SerializeToUtf8Bytes(
|
||||
stateSnapshot,
|
||||
this._jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)));
|
||||
yield return new AgentRunResponseUpdate
|
||||
yield return new AgentResponseUpdate
|
||||
{
|
||||
Contents = [new DataContent(stateBytes, "application/json")]
|
||||
};
|
||||
|
||||
@@ -14,5 +14,5 @@ A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
AIAgent agent = await agentCardResolver.GetAIAgentAsync();
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentRunResponse response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
AgentResponse response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
Console.WriteLine(response);
|
||||
|
||||
@@ -29,6 +29,6 @@ A2AClient a2aClient = new(new Uri("https://your-a2a-agent-host/echo"));
|
||||
AIAgent agent = a2aClient.GetAIAgent();
|
||||
|
||||
// Run the agent
|
||||
AgentRunResponse response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
AgentResponse response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
Console.WriteLine(response);
|
||||
```
|
||||
+4
-4
@@ -34,7 +34,7 @@ namespace SampleApp
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Create a thread if the user didn't supply one.
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
@@ -58,7 +58,7 @@ namespace SampleApp
|
||||
};
|
||||
await typedThread.MessageStore.InvokedAsync(invokedContext, cancellationToken);
|
||||
|
||||
return new AgentRunResponse
|
||||
return new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = Guid.NewGuid().ToString("N"),
|
||||
@@ -66,7 +66,7 @@ namespace SampleApp
|
||||
};
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Create a thread if the user didn't supply one.
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
@@ -92,7 +92,7 @@ namespace SampleApp
|
||||
|
||||
foreach (var message in responseMessages)
|
||||
{
|
||||
yield return new AgentRunResponseUpdate
|
||||
yield return new AgentResponseUpdate
|
||||
{
|
||||
AgentId = this.Id,
|
||||
AuthorName = message.AuthorName,
|
||||
|
||||
@@ -22,7 +22,7 @@ ChatClientAgent agentGenAI = new(
|
||||
name: JokerName,
|
||||
instructions: JokerInstructions);
|
||||
|
||||
AgentRunResponse response = await agentGenAI.RunAsync("Tell me a joke about a pirate.");
|
||||
AgentResponse response = await agentGenAI.RunAsync("Tell me a joke about a pirate.");
|
||||
Console.WriteLine($"Google GenAI client based agent response:\n{response}");
|
||||
|
||||
// Using a community driven Mscc.GenerativeAI.Microsoft package
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?",
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+2
-2
@@ -87,10 +87,10 @@ public class OpenAIChatClientAgent : DelegatingAIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override Task<AgentRunResponse> RunCoreAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
+2
-2
@@ -105,10 +105,10 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
protected sealed override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
+1
-1
@@ -64,4 +64,4 @@ while (userInputRequests.Count > 0)
|
||||
Console.WriteLine($"\nAgent: {response}");
|
||||
|
||||
// For streaming use:
|
||||
// Console.WriteLine($"\nAgent: {updates.ToAgentRunResponse()}");
|
||||
// Console.WriteLine($"\nAgent: {updates.ToAgentResponse()}");
|
||||
|
||||
@@ -24,7 +24,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
ChatClientAgent agent = chatClient.CreateAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
|
||||
|
||||
// Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input.
|
||||
AgentRunResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
|
||||
// Access the structured output via the Result property of the agent response.
|
||||
Console.WriteLine("Assistant Output:");
|
||||
@@ -44,7 +44,7 @@ var updates = agentWithPersonInfo.RunStreamingAsync("Please provide information
|
||||
|
||||
// Assemble all the parts of the streamed output, since we can only deserialize once we have the full json,
|
||||
// then deserialize the response into the PersonInfo class.
|
||||
PersonInfo personInfo = (await updates.ToAgentRunResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
PersonInfo personInfo = (await updates.ToAgentResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine($"Name: {personInfo.Name}");
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run.
|
||||
AgentRunResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", thread, options);
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", thread, options);
|
||||
|
||||
// Poll for background responses until complete.
|
||||
while (response.ContinuationToken is not null)
|
||||
|
||||
@@ -131,7 +131,7 @@ async ValueTask<object?> PerRequestFunctionCallingMiddleware(AIAgent agent, Func
|
||||
}
|
||||
|
||||
// This middleware redacts PII information from input and output messages.
|
||||
async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact PII information from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -171,7 +171,7 @@ async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, Ag
|
||||
}
|
||||
|
||||
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
|
||||
async Task<AgentRunResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact keywords from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -208,7 +208,7 @@ async Task<AgentRunResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messag
|
||||
}
|
||||
|
||||
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
|
||||
async Task<AgentRunResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run.
|
||||
AgentRunResponse response = await agent.RunAsync("Write a very long novel about otters in space.", thread, options);
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about otters in space.", thread, options);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
@@ -43,9 +43,9 @@ Console.WriteLine(response.Text);
|
||||
options = new() { AllowBackgroundResponses = true };
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
|
||||
AgentRunResponseUpdate? lastReceivedUpdate = null;
|
||||
AgentResponseUpdate? lastReceivedUpdate = null;
|
||||
// Start streaming.
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("Write a very long novel about otters in space.", thread, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a very long novel about otters in space.", thread, options))
|
||||
{
|
||||
// Output each update.
|
||||
Console.Write(update.Text);
|
||||
@@ -63,7 +63,7 @@ await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("Write a
|
||||
// Resume from interruption point.
|
||||
options.ContinuationToken = lastReceivedUpdate?.ContinuationToken;
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(thread, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(thread, options))
|
||||
{
|
||||
// Output each update.
|
||||
Console.Write(update.Text);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ AgentVersion agentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName:
|
||||
AIAgent jokerAgent = aiProjectClient.GetAIAgent(agentVersion);
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
await foreach (AgentRunResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+2
-2
@@ -32,11 +32,11 @@ Console.WriteLine(await jokerAgent.RunAsync("Now add some emojis to the joke and
|
||||
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
|
||||
thread = await jokerAgent.GetNewThreadAsync();
|
||||
await foreach (AgentRunResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
await foreach (AgentRunResponseUpdate update in jokerAgent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread))
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ Console.WriteLine(await existingAgent.RunAsync("What is the weather like in Amst
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
thread = await existingAgent.GetNewThreadAsync();
|
||||
await foreach (AgentRunResponseUpdate update in existingAgent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
await foreach (AgentResponseUpdate update in existingAgent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mo
|
||||
// Call the agent with approval-required function tools.
|
||||
// The agent will request approval before invoking the function.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
AgentRunResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
|
||||
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
|
||||
|
||||
// Check if there are any user input requests (approvals needed).
|
||||
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
+3
-3
@@ -35,7 +35,7 @@ ChatClientAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||
});
|
||||
|
||||
// Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input.
|
||||
AgentRunResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
AgentResponse<PersonInfo> response = await agent.RunAsync<PersonInfo>("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
|
||||
// Access the structured output via the Result property of the agent response.
|
||||
Console.WriteLine("Assistant Output:");
|
||||
@@ -57,11 +57,11 @@ ChatClientAgent agentWithPersonInfo = aiProjectClient.CreateAIAgent(
|
||||
});
|
||||
|
||||
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> updates = agentWithPersonInfo.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
IAsyncEnumerable<AgentResponseUpdate> updates = agentWithPersonInfo.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
|
||||
// Assemble all the parts of the streamed output, since we can only deserialize once we have the full json,
|
||||
// then deserialize the response into the PersonInfo class.
|
||||
PersonInfo personInfo = (await updates.ToAgentRunResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
PersonInfo personInfo = (await updates.ToAgentResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine($"Name: {personInfo.Name}");
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread)
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ internal sealed class SampleService(AIProjectClient client, AIAgent agent, IHost
|
||||
}
|
||||
|
||||
// Stream the output to the console as it is generated.
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, this._thread, cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, this._thread, cancellationToken: cancellationToken))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ ChatMessage message = new(ChatRole.User, [
|
||||
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(message, thread))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(message, thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+8
-8
@@ -52,18 +52,18 @@ AIAgent middlewareEnabledAgent = originalAgent
|
||||
AgentThread thread = await middlewareEnabledAgent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
|
||||
AgentRunResponse guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
|
||||
AgentResponse guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
|
||||
Console.WriteLine($"Guard railed response: {guardRailedResponse}");
|
||||
|
||||
Console.WriteLine("\n\n=== Example 2: PII detection ===");
|
||||
AgentRunResponse piiResponse = await middlewareEnabledAgent.RunAsync("My name is John Doe, call me at 123-456-7890 or email me at john@something.com");
|
||||
AgentResponse piiResponse = await middlewareEnabledAgent.RunAsync("My name is John Doe, call me at 123-456-7890 or email me at john@something.com");
|
||||
Console.WriteLine($"Pii filtered response: {piiResponse}");
|
||||
|
||||
Console.WriteLine("\n\n=== Example 3: Agent function middleware ===");
|
||||
|
||||
// Agent function middleware support is limited to agents that wraps a upstream ChatClientAgent or derived from it.
|
||||
|
||||
AgentRunResponse functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread);
|
||||
AgentResponse functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread);
|
||||
Console.WriteLine($"Function calling response: {functionCallResponse}");
|
||||
|
||||
// Special per-request middleware agent.
|
||||
@@ -78,7 +78,7 @@ AIAgent humanInTheLoopAgent = aiProjectClient.CreateAIAgent(
|
||||
tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather)))]);
|
||||
|
||||
// Using the ConsolePromptingApprovalMiddleware for a specific request to handle user approval during function calls.
|
||||
AgentRunResponse response = await humanInTheLoopAgent
|
||||
AgentResponse response = await humanInTheLoopAgent
|
||||
.AsBuilder()
|
||||
.Use(ConsolePromptingApprovalMiddleware, null)
|
||||
.Build()
|
||||
@@ -113,7 +113,7 @@ async ValueTask<object?> FunctionCallOverrideWeather(AIAgent agent, FunctionInvo
|
||||
}
|
||||
|
||||
// This middleware redacts PII information from input and output messages.
|
||||
async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact PII information from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -152,7 +152,7 @@ async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, Ag
|
||||
}
|
||||
|
||||
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
|
||||
async Task<AgentRunResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact keywords from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -189,9 +189,9 @@ async Task<AgentRunResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messag
|
||||
}
|
||||
|
||||
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
|
||||
async Task<AgentRunResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
AgentRunResponse response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
AgentResponse response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
|
||||
+2
-2
@@ -49,10 +49,10 @@ AIAgent agentOption2 = await aiProjectClient.CreateAIAgentAsync(
|
||||
|
||||
// Either invoke option1 or option2 agent, should have same result
|
||||
// Option 1
|
||||
AgentRunResponse response = await agentOption1.RunAsync("I need to solve the equation sin(x) + x^2 = 42");
|
||||
AgentResponse response = await agentOption1.RunAsync("I need to solve the equation sin(x) + x^2 = 42");
|
||||
|
||||
// Option 2
|
||||
// AgentRunResponse response = await agentOption2.RunAsync("I need to solve the equation sin(x) + x^2 = 42");
|
||||
// AgentResponse response = await agentOption2.RunAsync("I need to solve the equation sin(x) + x^2 = 42");
|
||||
|
||||
// Get the CodeInterpreterToolCallContent
|
||||
CodeInterpreterToolCallContent? toolCallContent = response.Messages.SelectMany(m => m.Contents).OfType<CodeInterpreterToolCallContent>().FirstOrDefault();
|
||||
|
||||
+7
-7
@@ -93,7 +93,7 @@ internal sealed class Program
|
||||
// Initial request with screenshot - start with Bing search page
|
||||
Console.WriteLine("Starting computer automation session (initial screenshot: cua_browser_search.png)...");
|
||||
|
||||
AgentRunResponse runResponse = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
AgentResponse response = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
|
||||
// Main interaction loop
|
||||
const int MaxIterations = 10;
|
||||
@@ -105,7 +105,7 @@ internal sealed class Program
|
||||
while (true)
|
||||
{
|
||||
// Poll until the response is complete.
|
||||
while (runResponse.ContinuationToken is { } token)
|
||||
while (response.ContinuationToken is { } token)
|
||||
{
|
||||
// Wait before polling again.
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
@@ -113,10 +113,10 @@ internal sealed class Program
|
||||
// Continue with the token.
|
||||
runOptions.ContinuationToken = token;
|
||||
|
||||
runResponse = await agent.RunAsync(thread, runOptions);
|
||||
response = await agent.RunAsync(thread, runOptions);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Agent response received (ID: {runResponse.ResponseId})");
|
||||
Console.WriteLine($"Agent response received (ID: {response.ResponseId})");
|
||||
|
||||
if (iteration >= MaxIterations)
|
||||
{
|
||||
@@ -128,7 +128,7 @@ internal sealed class Program
|
||||
Console.WriteLine($"\n--- Iteration {iteration} ---");
|
||||
|
||||
// Check for computer calls in the response
|
||||
IEnumerable<ComputerCallResponseItem> computerCallResponseItems = runResponse.Messages
|
||||
IEnumerable<ComputerCallResponseItem> computerCallResponseItems = response.Messages
|
||||
.SelectMany(x => x.Contents)
|
||||
.Where(c => c.RawRepresentation is ComputerCallResponseItem and not null)
|
||||
.Select(c => (ComputerCallResponseItem)c.RawRepresentation!);
|
||||
@@ -137,7 +137,7 @@ internal sealed class Program
|
||||
if (firstComputerCall is null)
|
||||
{
|
||||
Console.WriteLine("No computer call actions found. Ending interaction.");
|
||||
Console.WriteLine($"Final Response: {runResponse}");
|
||||
Console.WriteLine($"Final Response: {response}");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ internal sealed class Program
|
||||
|
||||
// Follow-up message with action result and new screenshot
|
||||
message = new(ChatRole.User, [content]);
|
||||
runResponse = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
response = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ public static class Program
|
||||
// re-render all messages on each update.
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
|
||||
{
|
||||
Dictionary<string, List<AgentRunResponseUpdate>> buffer = [];
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
Dictionary<string, List<AgentResponseUpdate>> buffer = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
{
|
||||
if (update.MessageId is null || string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
@@ -68,7 +68,7 @@ public static class Program
|
||||
}
|
||||
Console.Clear();
|
||||
|
||||
if (!buffer.TryGetValue(update.MessageId, out List<AgentRunResponseUpdate>? value))
|
||||
if (!buffer.TryGetValue(update.MessageId, out List<AgentResponseUpdate>? value))
|
||||
{
|
||||
value = [];
|
||||
buffer[update.MessageId] = value;
|
||||
|
||||
@@ -65,7 +65,7 @@ public static class SampleWorkflowProvider
|
||||
bool autoSend = true;
|
||||
IList<ChatMessage>? inputMessages = null;
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
AgentResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
context,
|
||||
agentName,
|
||||
@@ -102,7 +102,7 @@ public static class SampleWorkflowProvider
|
||||
bool autoSend = false;
|
||||
IList<ChatMessage>? inputMessages = null;
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
AgentResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
context,
|
||||
agentName,
|
||||
@@ -175,7 +175,7 @@ public static class SampleWorkflowProvider
|
||||
GOLD STAR!
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
AgentResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
@@ -196,7 +196,7 @@ public static class SampleWorkflowProvider
|
||||
Let's try again later...
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
AgentResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
|
||||
return default;
|
||||
|
||||
@@ -65,10 +65,10 @@ internal sealed class Program
|
||||
};
|
||||
ChatClientAgentRunOptions runOptions = new(chatOptions);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentResponseUpdates = agent.RunStreamingAsync(workflowInput, thread, runOptions);
|
||||
IAsyncEnumerable<AgentResponseUpdate> agentResponseUpdates = agent.RunStreamingAsync(workflowInput, thread, runOptions);
|
||||
|
||||
string? lastMessageId = null;
|
||||
await foreach (AgentRunResponseUpdate responseUpdate in agentResponseUpdates)
|
||||
await foreach (AgentResponseUpdate responseUpdate in agentResponseUpdates)
|
||||
{
|
||||
if (responseUpdate.MessageId != lastMessageId)
|
||||
{
|
||||
|
||||
@@ -111,8 +111,8 @@ public static class Program
|
||||
// re-render all messages on each update.
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
|
||||
{
|
||||
Dictionary<string, List<AgentRunResponseUpdate>> buffer = [];
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
Dictionary<string, List<AgentResponseUpdate>> buffer = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
{
|
||||
if (update.MessageId is null || string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
@@ -121,7 +121,7 @@ public static class Program
|
||||
}
|
||||
Console.Clear();
|
||||
|
||||
if (!buffer.TryGetValue(update.MessageId, out List<AgentRunResponseUpdate>? value))
|
||||
if (!buffer.TryGetValue(update.MessageId, out List<AgentResponseUpdate>? value))
|
||||
{
|
||||
value = [];
|
||||
buffer[update.MessageId] = value;
|
||||
|
||||
+5
-5
@@ -254,7 +254,7 @@ internal sealed class WriterExecutor : Executor
|
||||
Console.WriteLine($"\n=== Writer (Iteration {state.Iteration}) ===\n");
|
||||
|
||||
StringBuilder sb = new();
|
||||
await foreach (AgentRunResponseUpdate update in this._agent.RunStreamingAsync(message, cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in this._agent.RunStreamingAsync(message, cancellationToken: cancellationToken))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
@@ -313,10 +313,10 @@ internal sealed class CriticExecutor : Executor<ChatMessage, CriticDecision>
|
||||
Console.WriteLine($"=== Critic (Iteration {state.Iteration}) ===\n");
|
||||
|
||||
// Use RunStreamingAsync to get streaming updates, then deserialize at the end
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> updates = this._agent.RunStreamingAsync(message, cancellationToken: cancellationToken);
|
||||
IAsyncEnumerable<AgentResponseUpdate> updates = this._agent.RunStreamingAsync(message, cancellationToken: cancellationToken);
|
||||
|
||||
// Stream the output in real-time (for any rationale/explanation)
|
||||
await foreach (AgentRunResponseUpdate update in updates)
|
||||
await foreach (AgentResponseUpdate update in updates)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
@@ -326,7 +326,7 @@ internal sealed class CriticExecutor : Executor<ChatMessage, CriticDecision>
|
||||
Console.WriteLine("\n");
|
||||
|
||||
// Convert the stream to a response and deserialize the structured output
|
||||
AgentRunResponse response = await updates.ToAgentRunResponseAsync(cancellationToken);
|
||||
AgentResponse response = await updates.ToAgentResponseAsync(cancellationToken);
|
||||
CriticDecision decision = response.Deserialize<CriticDecision>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine($"Decision: {(decision.Approved ? "✅ APPROVED" : "❌ NEEDS REVISION")}");
|
||||
@@ -394,7 +394,7 @@ internal sealed class SummaryExecutor : Executor<CriticDecision, ChatMessage>
|
||||
string prompt = $"Present this approved content:\n\n{message.Content}";
|
||||
|
||||
StringBuilder sb = new();
|
||||
await foreach (AgentRunResponseUpdate update in this._agent.RunStreamingAsync(new ChatMessage(ChatRole.User, prompt), cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in this._agent.RunStreamingAsync(new ChatMessage(ChatRole.User, prompt), cancellationToken: cancellationToken))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
|
||||
@@ -49,16 +49,16 @@ internal sealed class AFAgentApplication : AgentApplication
|
||||
ChatMessage chatMessage = HandleUserInput(turnContext);
|
||||
|
||||
// Invoke the WeatherForecastAgent to process the message
|
||||
AgentRunResponse agentRunResponse = await this._agent.RunAsync(chatMessage, agentThread, cancellationToken: cancellationToken);
|
||||
AgentResponse agentResponse = await this._agent.RunAsync(chatMessage, agentThread, cancellationToken: cancellationToken);
|
||||
|
||||
// Check for any user input requests in the response
|
||||
// and turn them into adaptive cards in the streaming response.
|
||||
List<Attachment>? attachments = null;
|
||||
HandleUserInputRequests(agentRunResponse, ref attachments);
|
||||
HandleUserInputRequests(agentResponse, ref attachments);
|
||||
|
||||
// Check for Adaptive Card content in the response messages
|
||||
// and return them appropriately in the response.
|
||||
var adaptiveCards = agentRunResponse.Messages.SelectMany(x => x.Contents).OfType<AdaptiveCardAIContent>().ToList();
|
||||
var adaptiveCards = agentResponse.Messages.SelectMany(x => x.Contents).OfType<AdaptiveCardAIContent>().ToList();
|
||||
if (adaptiveCards.Count > 0)
|
||||
{
|
||||
attachments ??= [];
|
||||
@@ -70,7 +70,7 @@ internal sealed class AFAgentApplication : AgentApplication
|
||||
}
|
||||
else
|
||||
{
|
||||
turnContext.StreamingResponse.QueueTextChunk(agentRunResponse.Text);
|
||||
turnContext.StreamingResponse.QueueTextChunk(agentResponse.Text);
|
||||
}
|
||||
|
||||
// If created any adaptive cards, add them to the final message.
|
||||
@@ -134,9 +134,9 @@ internal sealed class AFAgentApplication : AgentApplication
|
||||
/// When the agent returns any user input requests, this method converts them into adaptive cards that
|
||||
/// asks the user to approve or deny the requests.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="AgentRunResponse"/> that may contain the user input requests.</param>
|
||||
/// <param name="response">The <see cref="AgentResponse"/> that may contain the user input requests.</param>
|
||||
/// <param name="attachments">The list of <see cref="Attachment"/> to which the adaptive cards will be added.</param>
|
||||
private static void HandleUserInputRequests(AgentRunResponse response, ref List<Attachment>? attachments)
|
||||
private static void HandleUserInputRequests(AgentResponse response, ref List<Attachment>? attachments)
|
||||
{
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
if (userInputRequests.Count > 0)
|
||||
|
||||
@@ -48,7 +48,7 @@ public class WeatherForecastAgent : DelegatingAIAgent
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await base.RunCoreAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed class A2AAgent : AIAgent
|
||||
=> new(new A2AAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(messages);
|
||||
|
||||
@@ -99,7 +99,7 @@ public sealed class A2AAgent : AIAgent
|
||||
{
|
||||
UpdateThread(typedThread, message.ContextId);
|
||||
|
||||
return new AgentRunResponse
|
||||
return new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = message.MessageId,
|
||||
@@ -113,7 +113,7 @@ public sealed class A2AAgent : AIAgent
|
||||
{
|
||||
UpdateThread(typedThread, agentTask.ContextId, agentTask.Id);
|
||||
|
||||
var response = new AgentRunResponse
|
||||
var response = new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = agentTask.Id,
|
||||
@@ -135,7 +135,7 @@ public sealed class A2AAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(messages);
|
||||
|
||||
@@ -291,9 +291,9 @@ public sealed class A2AAgent : AIAgent
|
||||
return null;
|
||||
}
|
||||
|
||||
private AgentRunResponseUpdate ConvertToAgentResponseUpdate(AgentMessage message)
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(AgentMessage message)
|
||||
{
|
||||
return new AgentRunResponseUpdate
|
||||
return new AgentResponseUpdate
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = message.MessageId,
|
||||
@@ -305,9 +305,9 @@ public sealed class A2AAgent : AIAgent
|
||||
};
|
||||
}
|
||||
|
||||
private AgentRunResponseUpdate ConvertToAgentResponseUpdate(AgentTask task)
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(AgentTask task)
|
||||
{
|
||||
return new AgentRunResponseUpdate
|
||||
return new AgentResponseUpdate
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = task.Id,
|
||||
@@ -318,9 +318,9 @@ public sealed class A2AAgent : AIAgent
|
||||
};
|
||||
}
|
||||
|
||||
private AgentRunResponseUpdate ConvertToAgentResponseUpdate(TaskUpdateEvent taskUpdateEvent)
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(TaskUpdateEvent taskUpdateEvent)
|
||||
{
|
||||
AgentRunResponseUpdate responseUpdate = new()
|
||||
AgentResponseUpdate responseUpdate = new()
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = taskUpdateEvent.TaskId,
|
||||
|
||||
@@ -146,12 +146,12 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentRunResponse"/> with the agent's output.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
|
||||
/// <remarks>
|
||||
/// This overload is useful when the agent has sufficient context from previous messages in the thread
|
||||
/// or from its initial configuration to generate a meaningful response without additional input.
|
||||
/// </remarks>
|
||||
public Task<AgentRunResponse> RunAsync(
|
||||
public Task<AgentResponse> RunAsync(
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
@@ -167,13 +167,13 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentRunResponse"/> with the agent's output.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="message"/> is <see langword="null"/>, empty, or contains only whitespace.</exception>
|
||||
/// <remarks>
|
||||
/// The provided text will be wrapped in a <see cref="ChatMessage"/> with the <see cref="ChatRole.User"/> role
|
||||
/// before being sent to the agent. This is a convenience method for simple text-based interactions.
|
||||
/// </remarks>
|
||||
public Task<AgentRunResponse> RunAsync(
|
||||
public Task<AgentResponse> RunAsync(
|
||||
string message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -194,9 +194,9 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentRunResponse"/> with the agent's output.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
public Task<AgentRunResponse> RunAsync(
|
||||
public Task<AgentResponse> RunAsync(
|
||||
ChatMessage message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -217,7 +217,7 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentRunResponse"/> with the agent's output.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method delegates to <see cref="RunCoreAsync"/> to perform the actual agent invocation. It handles collections of messages,
|
||||
@@ -229,7 +229,7 @@ public abstract class AIAgent
|
||||
/// The agent's response will also be added to <paramref name="thread"/> if one is provided.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Task<AgentRunResponse> RunAsync(
|
||||
public Task<AgentResponse> RunAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -246,7 +246,7 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentRunResponse"/> with the agent's output.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is the primary invocation method that implementations must override. It handles collections of messages,
|
||||
@@ -258,7 +258,7 @@ public abstract class AIAgent
|
||||
/// The agent's response will also be added to <paramref name="thread"/> if one is provided.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract Task<AgentRunResponse> RunCoreAsync(
|
||||
protected abstract Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -273,8 +273,8 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentRunResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
public IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
@@ -290,13 +290,13 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentRunResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="message"/> is <see langword="null"/>, empty, or contains only whitespace.</exception>
|
||||
/// <remarks>
|
||||
/// The provided text will be wrapped in a <see cref="ChatMessage"/> with the <see cref="ChatRole.User"/> role.
|
||||
/// Streaming invocation provides real-time updates as the agent generates its response.
|
||||
/// </remarks>
|
||||
public IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -317,9 +317,9 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentRunResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
public IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
ChatMessage message,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -340,18 +340,18 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentRunResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method delegates to <see cref="RunCoreStreamingAsync"/> to perform the actual streaming invocation. It provides real-time
|
||||
/// updates as the agent processes the input and generates its response, enabling more responsive user experiences.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each <see cref="AgentRunResponseUpdate"/> represents a portion of the complete response, allowing consumers
|
||||
/// Each <see cref="AgentResponseUpdate"/> represents a portion of the complete response, allowing consumers
|
||||
/// to display partial results, implement progressive loading, or provide immediate feedback to users.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -368,18 +368,18 @@ public abstract class AIAgent
|
||||
/// </param>
|
||||
/// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentRunResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is the primary streaming invocation method that implementations must override. It provides real-time
|
||||
/// updates as the agent processes the input and generates its response, enabling more responsive user experiences.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each <see cref="AgentRunResponseUpdate"/> represents a portion of the complete response, allowing consumers
|
||||
/// Each <see cref="AgentResponseUpdate"/> represents a portion of the complete response, allowing consumers
|
||||
/// to display partial results, implement progressive loading, or provide immediate feedback to users.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected abstract IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
|
||||
@@ -76,10 +76,10 @@ public static partial class AgentAbstractionsJsonUtilities
|
||||
|
||||
// Agent abstraction types
|
||||
[JsonSerializable(typeof(AgentRunOptions))]
|
||||
[JsonSerializable(typeof(AgentRunResponse))]
|
||||
[JsonSerializable(typeof(AgentRunResponse[]))]
|
||||
[JsonSerializable(typeof(AgentRunResponseUpdate))]
|
||||
[JsonSerializable(typeof(AgentRunResponseUpdate[]))]
|
||||
[JsonSerializable(typeof(AgentResponse))]
|
||||
[JsonSerializable(typeof(AgentResponse[]))]
|
||||
[JsonSerializable(typeof(AgentResponseUpdate))]
|
||||
[JsonSerializable(typeof(AgentResponseUpdate[]))]
|
||||
[JsonSerializable(typeof(ServiceIdAgentThread.ServiceIdAgentThreadState))]
|
||||
[JsonSerializable(typeof(InMemoryAgentThread.InMemoryAgentThreadState))]
|
||||
[JsonSerializable(typeof(InMemoryChatMessageStore.StoreState))]
|
||||
|
||||
+20
-20
@@ -24,30 +24,30 @@ namespace Microsoft.Agents.AI;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="AgentRunResponse"/> provides one or more response messages and metadata about the response.
|
||||
/// <see cref="AgentResponse"/> provides one or more response messages and metadata about the response.
|
||||
/// A typical response will contain a single message, however a response may contain multiple messages
|
||||
/// in a variety of scenarios. For example, if the agent internally invokes functions or tools, performs
|
||||
/// RAG retrievals or has other complex logic, a single run by the agent may produce many messages showing
|
||||
/// the intermediate progress that the agent made towards producing the agent result.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To get the text result of the response, use the <see cref="Text"/> property or simply call <see cref="ToString()"/> on the <see cref="AgentRunResponse"/>.
|
||||
/// To get the text result of the response, use the <see cref="Text"/> property or simply call <see cref="ToString()"/> on the <see cref="AgentResponse"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class AgentRunResponse
|
||||
public class AgentResponse
|
||||
{
|
||||
/// <summary>The response messages.</summary>
|
||||
private IList<ChatMessage>? _messages;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponse"/> class.</summary>
|
||||
public AgentRunResponse()
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponse"/> class.</summary>
|
||||
public AgentResponse()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponse"/> class.</summary>
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponse"/> class.</summary>
|
||||
/// <param name="message">The response message to include in this response.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
public AgentRunResponse(ChatMessage message)
|
||||
public AgentResponse(ChatMessage message)
|
||||
{
|
||||
_ = Throw.IfNull(message);
|
||||
|
||||
@@ -55,16 +55,16 @@ public class AgentRunResponse
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRunResponse"/> class from an existing <see cref="ChatResponse"/>.
|
||||
/// Initializes a new instance of the <see cref="AgentResponse"/> class from an existing <see cref="ChatResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="ChatResponse"/> from which to populate this <see cref="AgentRunResponse"/>.</param>
|
||||
/// <param name="response">The <see cref="ChatResponse"/> from which to populate this <see cref="AgentResponse"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This constructor creates an agent response that wraps an existing <see cref="ChatResponse"/>, preserving all
|
||||
/// metadata and storing the original response in <see cref="RawRepresentation"/> for access to
|
||||
/// the underlying implementation details.
|
||||
/// </remarks>
|
||||
public AgentRunResponse(ChatResponse response)
|
||||
public AgentResponse(ChatResponse response)
|
||||
{
|
||||
_ = Throw.IfNull(response);
|
||||
|
||||
@@ -78,10 +78,10 @@ public class AgentRunResponse
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRunResponse"/> class with the specified collection of messages.
|
||||
/// Initializes a new instance of the <see cref="AgentResponse"/> class with the specified collection of messages.
|
||||
/// </summary>
|
||||
/// <param name="messages">The collection of response messages, or <see langword="null"/> to create an empty response.</param>
|
||||
public AgentRunResponse(IList<ChatMessage>? messages)
|
||||
public AgentResponse(IList<ChatMessage>? messages)
|
||||
{
|
||||
this._messages = messages;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class AgentRunResponse
|
||||
|
||||
/// <summary>Gets or sets the raw representation of the run response from an underlying implementation.</summary>
|
||||
/// <remarks>
|
||||
/// If a <see cref="AgentRunResponse"/> is created to represent some underlying object from another object
|
||||
/// If a <see cref="AgentResponse"/> is created to represent some underlying object from another object
|
||||
/// model, this property can be used to store that original object. This can be useful for debugging or
|
||||
/// for enabling a consumer to access the underlying object model if needed.
|
||||
/// </remarks>
|
||||
@@ -226,11 +226,11 @@ public class AgentRunResponse
|
||||
public override string ToString() => this.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="AgentRunResponse"/> into a collection of <see cref="AgentRunResponseUpdate"/> instances
|
||||
/// Converts this <see cref="AgentResponse"/> into a collection of <see cref="AgentResponseUpdate"/> instances
|
||||
/// suitable for streaming scenarios.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of <see cref="AgentRunResponseUpdate"/> instances that collectively represent
|
||||
/// An array of <see cref="AgentResponseUpdate"/> instances that collectively represent
|
||||
/// the same information as this response.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
@@ -245,12 +245,12 @@ public class AgentRunResponse
|
||||
/// original message sequence.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public AgentRunResponseUpdate[] ToAgentRunResponseUpdates()
|
||||
public AgentResponseUpdate[] ToAgentResponseUpdates()
|
||||
{
|
||||
AgentRunResponseUpdate? extra = null;
|
||||
AgentResponseUpdate? extra = null;
|
||||
if (this.AdditionalProperties is not null || this.Usage is not null)
|
||||
{
|
||||
extra = new AgentRunResponseUpdate
|
||||
extra = new AgentResponseUpdate
|
||||
{
|
||||
AdditionalProperties = this.AdditionalProperties,
|
||||
};
|
||||
@@ -262,13 +262,13 @@ public class AgentRunResponse
|
||||
}
|
||||
|
||||
int messageCount = this._messages?.Count ?? 0;
|
||||
var updates = new AgentRunResponseUpdate[messageCount + (extra is not null ? 1 : 0)];
|
||||
var updates = new AgentResponseUpdate[messageCount + (extra is not null ? 1 : 0)];
|
||||
|
||||
int i;
|
||||
for (i = 0; i < messageCount; i++)
|
||||
{
|
||||
ChatMessage message = this._messages![i];
|
||||
updates[i] = new AgentRunResponseUpdate
|
||||
updates[i] = new AgentResponseUpdate
|
||||
{
|
||||
AdditionalProperties = message.AdditionalProperties,
|
||||
AuthorName = message.AuthorName,
|
||||
+42
-42
@@ -11,24 +11,24 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for working with <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> instances.
|
||||
/// Provides extension methods for working with <see cref="AgentResponse"/> and <see cref="AgentResponseUpdate"/> instances.
|
||||
/// </summary>
|
||||
public static class AgentRunResponseExtensions
|
||||
public static class AgentResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ChatResponse"/> from an <see cref="AgentRunResponse"/> instance.
|
||||
/// Creates a <see cref="ChatResponse"/> from an <see cref="AgentResponse"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="AgentRunResponse"/> to convert.</param>
|
||||
/// <param name="response">The <see cref="AgentResponse"/> to convert.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> built from the specified <paramref name="response"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="response"/>'s <see cref="AgentRunResponse.RawRepresentation"/> is already a
|
||||
/// If the <paramref name="response"/>'s <see cref="AgentResponse.RawRepresentation"/> is already a
|
||||
/// <see cref="ChatResponse"/> instance, that instance is returned directly.
|
||||
/// Otherwise, a new <see cref="ChatResponse"/> is created and populated with the data from the <paramref name="response"/>.
|
||||
/// The resulting instance is a shallow copy; any reference-type members (e.g. <see cref="AgentRunResponse.Messages"/>)
|
||||
/// The resulting instance is a shallow copy; any reference-type members (e.g. <see cref="AgentResponse.Messages"/>)
|
||||
/// will be shared between the two instances.
|
||||
/// </remarks>
|
||||
public static ChatResponse AsChatResponse(this AgentRunResponse response)
|
||||
public static ChatResponse AsChatResponse(this AgentResponse response)
|
||||
{
|
||||
Throw.IfNull(response);
|
||||
|
||||
@@ -47,19 +47,19 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ChatResponseUpdate"/> from an <see cref="AgentRunResponseUpdate"/> instance.
|
||||
/// Creates a <see cref="ChatResponseUpdate"/> from an <see cref="AgentResponseUpdate"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="responseUpdate">The <see cref="AgentRunResponseUpdate"/> to convert.</param>
|
||||
/// <param name="responseUpdate">The <see cref="AgentResponseUpdate"/> to convert.</param>
|
||||
/// <returns>A <see cref="ChatResponseUpdate"/> built from the specified <paramref name="responseUpdate"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="responseUpdate"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="responseUpdate"/>'s <see cref="AgentRunResponseUpdate.RawRepresentation"/> is already a
|
||||
/// If the <paramref name="responseUpdate"/>'s <see cref="AgentResponseUpdate.RawRepresentation"/> is already a
|
||||
/// <see cref="ChatResponseUpdate"/> instance, that instance is returned directly.
|
||||
/// Otherwise, a new <see cref="ChatResponseUpdate"/> is created and populated with the data from the <paramref name="responseUpdate"/>.
|
||||
/// The resulting instance is a shallow copy; any reference-type members (e.g. <see cref="AgentRunResponseUpdate.Contents"/>)
|
||||
/// The resulting instance is a shallow copy; any reference-type members (e.g. <see cref="AgentResponseUpdate.Contents"/>)
|
||||
/// will be shared between the two instances.
|
||||
/// </remarks>
|
||||
public static ChatResponseUpdate AsChatResponseUpdate(this AgentRunResponseUpdate responseUpdate)
|
||||
public static ChatResponseUpdate AsChatResponseUpdate(this AgentResponseUpdate responseUpdate)
|
||||
{
|
||||
Throw.IfNull(responseUpdate);
|
||||
|
||||
@@ -81,17 +81,17 @@ public static class AgentRunResponseExtensions
|
||||
|
||||
/// <summary>
|
||||
/// Creates an asynchronous enumerable of <see cref="ChatResponseUpdate"/> instances from an asynchronous
|
||||
/// enumerable of <see cref="AgentRunResponseUpdate"/> instances.
|
||||
/// enumerable of <see cref="AgentResponseUpdate"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="responseUpdates">The sequence of <see cref="AgentRunResponseUpdate"/> instances to convert.</param>
|
||||
/// <param name="responseUpdates">The sequence of <see cref="AgentResponseUpdate"/> instances to convert.</param>
|
||||
/// <returns>An asynchronous enumerable of <see cref="ChatResponseUpdate"/> instances built from <paramref name="responseUpdates"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="responseUpdates"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// Each <see cref="AgentRunResponseUpdate"/> is converted to a <see cref="ChatResponseUpdate"/> using
|
||||
/// Each <see cref="AgentResponseUpdate"/> is converted to a <see cref="ChatResponseUpdate"/> using
|
||||
/// <see cref="AsChatResponseUpdate"/>.
|
||||
/// </remarks>
|
||||
public static async IAsyncEnumerable<ChatResponseUpdate> AsChatResponseUpdatesAsync(
|
||||
this IAsyncEnumerable<AgentRunResponseUpdate> responseUpdates)
|
||||
this IAsyncEnumerable<AgentResponseUpdate> responseUpdates)
|
||||
{
|
||||
Throw.IfNull(responseUpdates);
|
||||
|
||||
@@ -102,71 +102,71 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines a sequence of <see cref="AgentRunResponseUpdate"/> instances into a single <see cref="AgentRunResponse"/>.
|
||||
/// Combines a sequence of <see cref="AgentResponseUpdate"/> instances into a single <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="updates">The sequence of updates to be combined into a single response.</param>
|
||||
/// <returns>A single <see cref="AgentRunResponse"/> that represents the combined state of all the updates.</returns>
|
||||
/// <returns>A single <see cref="AgentResponse"/> that represents the combined state of all the updates.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="updates"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// As part of combining <paramref name="updates"/> into a single <see cref="AgentRunResponse"/>, the method will attempt to reconstruct
|
||||
/// <see cref="ChatMessage"/> instances. This includes using <see cref="AgentRunResponseUpdate.MessageId"/> to determine
|
||||
/// As part of combining <paramref name="updates"/> into a single <see cref="AgentResponse"/>, the method will attempt to reconstruct
|
||||
/// <see cref="ChatMessage"/> instances. This includes using <see cref="AgentResponseUpdate.MessageId"/> to determine
|
||||
/// message boundaries, as well as coalescing contiguous <see cref="AIContent"/> items where applicable, e.g. multiple
|
||||
/// <see cref="TextContent"/> instances in a row may be combined into a single <see cref="TextContent"/>.
|
||||
/// </remarks>
|
||||
public static AgentRunResponse ToAgentRunResponse(
|
||||
this IEnumerable<AgentRunResponseUpdate> updates)
|
||||
public static AgentResponse ToAgentResponse(
|
||||
this IEnumerable<AgentResponseUpdate> updates)
|
||||
{
|
||||
_ = Throw.IfNull(updates);
|
||||
|
||||
AgentRunResponseDetails additionalDetails = new();
|
||||
AgentResponseDetails additionalDetails = new();
|
||||
ChatResponse chatResponse =
|
||||
AsChatResponseUpdatesWithAdditionalDetails(updates, additionalDetails)
|
||||
.ToChatResponse();
|
||||
|
||||
return new AgentRunResponse(chatResponse)
|
||||
return new AgentResponse(chatResponse)
|
||||
{
|
||||
AgentId = additionalDetails.AgentId,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously combines a sequence of <see cref="AgentRunResponseUpdate"/> instances into a single <see cref="AgentRunResponse"/>.
|
||||
/// Asynchronously combines a sequence of <see cref="AgentResponseUpdate"/> instances into a single <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="updates">The asynchronous sequence of updates to be combined into a single response.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a single <see cref="AgentRunResponse"/> that represents the combined state of all the updates.</returns>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a single <see cref="AgentResponse"/> that represents the combined state of all the updates.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="updates"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is the asynchronous version of <see cref="ToAgentRunResponse(IEnumerable{AgentRunResponseUpdate})"/>.
|
||||
/// This is the asynchronous version of <see cref="ToAgentResponse(IEnumerable{AgentResponseUpdate})"/>.
|
||||
/// It performs the same combining logic but operates on an asynchronous enumerable of updates.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// As part of combining <paramref name="updates"/> into a single <see cref="AgentRunResponse"/>, the method will attempt to reconstruct
|
||||
/// <see cref="ChatMessage"/> instances. This includes using <see cref="AgentRunResponseUpdate.MessageId"/> to determine
|
||||
/// As part of combining <paramref name="updates"/> into a single <see cref="AgentResponse"/>, the method will attempt to reconstruct
|
||||
/// <see cref="ChatMessage"/> instances. This includes using <see cref="AgentResponseUpdate.MessageId"/> to determine
|
||||
/// message boundaries, as well as coalescing contiguous <see cref="AIContent"/> items where applicable, e.g. multiple
|
||||
/// <see cref="TextContent"/> instances in a row may be combined into a single <see cref="TextContent"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static Task<AgentRunResponse> ToAgentRunResponseAsync(
|
||||
this IAsyncEnumerable<AgentRunResponseUpdate> updates,
|
||||
public static Task<AgentResponse> ToAgentResponseAsync(
|
||||
this IAsyncEnumerable<AgentResponseUpdate> updates,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(updates);
|
||||
|
||||
return ToAgentRunResponseAsync(updates, cancellationToken);
|
||||
return ToAgentResponseAsync(updates, cancellationToken);
|
||||
|
||||
static async Task<AgentRunResponse> ToAgentRunResponseAsync(
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> updates,
|
||||
static async Task<AgentResponse> ToAgentResponseAsync(
|
||||
IAsyncEnumerable<AgentResponseUpdate> updates,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
AgentRunResponseDetails additionalDetails = new();
|
||||
AgentResponseDetails additionalDetails = new();
|
||||
ChatResponse chatResponse = await
|
||||
AsChatResponseUpdatesWithAdditionalDetailsAsync(updates, additionalDetails, cancellationToken)
|
||||
.ToChatResponseAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new AgentRunResponse(chatResponse)
|
||||
return new AgentResponse(chatResponse)
|
||||
{
|
||||
AgentId = additionalDetails.AgentId,
|
||||
};
|
||||
@@ -174,8 +174,8 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
|
||||
private static IEnumerable<ChatResponseUpdate> AsChatResponseUpdatesWithAdditionalDetails(
|
||||
IEnumerable<AgentRunResponseUpdate> updates,
|
||||
AgentRunResponseDetails additionalDetails)
|
||||
IEnumerable<AgentResponseUpdate> updates,
|
||||
AgentResponseDetails additionalDetails)
|
||||
{
|
||||
foreach (var update in updates)
|
||||
{
|
||||
@@ -185,8 +185,8 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
|
||||
private static async IAsyncEnumerable<ChatResponseUpdate> AsChatResponseUpdatesWithAdditionalDetailsAsync(
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> updates,
|
||||
AgentRunResponseDetails additionalDetails,
|
||||
IAsyncEnumerable<AgentResponseUpdate> updates,
|
||||
AgentResponseDetails additionalDetails,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var update in updates.WithCancellation(cancellationToken).ConfigureAwait(false))
|
||||
@@ -196,7 +196,7 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateAdditionalDetails(AgentRunResponseUpdate update, AgentRunResponseDetails details)
|
||||
private static void UpdateAdditionalDetails(AgentResponseUpdate update, AgentResponseDetails details)
|
||||
{
|
||||
if (update.AgentId is { Length: > 0 })
|
||||
{
|
||||
@@ -204,7 +204,7 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AgentRunResponseDetails
|
||||
private sealed class AgentResponseDetails
|
||||
{
|
||||
public string? AgentId { get; set; }
|
||||
}
|
||||
+20
-20
@@ -16,54 +16,54 @@ namespace Microsoft.Agents.AI;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="AgentRunResponseUpdate"/> is so named because it represents updates
|
||||
/// <see cref="AgentResponseUpdate"/> is so named because it represents updates
|
||||
/// that layer on each other to form a single agent response. Conceptually, this combines the roles of
|
||||
/// <see cref="AgentRunResponse"/> and <see cref="ChatMessage"/> in streaming output.
|
||||
/// <see cref="AgentResponse"/> and <see cref="ChatMessage"/> in streaming output.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To get the text result of this response chunk, use the <see cref="Text"/> property or simply call <see cref="ToString()"/> on the <see cref="AgentRunResponseUpdate"/>.
|
||||
/// To get the text result of this response chunk, use the <see cref="Text"/> property or simply call <see cref="ToString()"/> on the <see cref="AgentResponseUpdate"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The relationship between <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> is
|
||||
/// codified in the <see cref="AgentRunResponseExtensions.ToAgentRunResponseAsync"/> and
|
||||
/// <see cref="AgentRunResponse.ToAgentRunResponseUpdates"/>, which enable bidirectional conversions
|
||||
/// The relationship between <see cref="AgentResponse"/> and <see cref="AgentResponseUpdate"/> is
|
||||
/// codified in the <see cref="AgentResponseExtensions.ToAgentResponseAsync"/> and
|
||||
/// <see cref="AgentResponse.ToAgentResponseUpdates"/>, which enable bidirectional conversions
|
||||
/// between the two. Note, however, that the provided conversions may be lossy, for example if multiple
|
||||
/// updates all have different <see cref="RawRepresentation"/> objects whereas there's only one slot for
|
||||
/// such an object available in <see cref="AgentRunResponse.RawRepresentation"/>.
|
||||
/// such an object available in <see cref="AgentResponse.RawRepresentation"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("[{Role}] {ContentForDebuggerDisplay}{EllipsesForDebuggerDisplay,nq}")]
|
||||
public class AgentRunResponseUpdate
|
||||
public class AgentResponseUpdate
|
||||
{
|
||||
/// <summary>The response update content items.</summary>
|
||||
private IList<AIContent>? _contents;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponseUpdate"/> class.</summary>
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
|
||||
[JsonConstructor]
|
||||
public AgentRunResponseUpdate()
|
||||
public AgentResponseUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponseUpdate"/> class.</summary>
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
|
||||
/// <param name="role">The role of the author of the update.</param>
|
||||
/// <param name="content">The text content of the update.</param>
|
||||
public AgentRunResponseUpdate(ChatRole? role, string? content)
|
||||
public AgentResponseUpdate(ChatRole? role, string? content)
|
||||
: this(role, content is null ? null : [new TextContent(content)])
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponseUpdate"/> class.</summary>
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
|
||||
/// <param name="role">The role of the author of the update.</param>
|
||||
/// <param name="contents">The contents of the update.</param>
|
||||
public AgentRunResponseUpdate(ChatRole? role, IList<AIContent>? contents)
|
||||
public AgentResponseUpdate(ChatRole? role, IList<AIContent>? contents)
|
||||
{
|
||||
this.Role = role;
|
||||
this._contents = contents;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponseUpdate"/> class.</summary>
|
||||
/// <param name="chatResponseUpdate">The <see cref="ChatResponseUpdate"/> from which to seed this <see cref="AgentRunResponseUpdate"/>.</param>
|
||||
public AgentRunResponseUpdate(ChatResponseUpdate chatResponseUpdate)
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
|
||||
/// <param name="chatResponseUpdate">The <see cref="ChatResponseUpdate"/> from which to seed this <see cref="AgentResponseUpdate"/>.</param>
|
||||
public AgentResponseUpdate(ChatResponseUpdate chatResponseUpdate)
|
||||
{
|
||||
_ = Throw.IfNull(chatResponseUpdate);
|
||||
|
||||
@@ -112,7 +112,7 @@ public class AgentRunResponseUpdate
|
||||
|
||||
/// <summary>Gets or sets the raw representation of the response update from an underlying implementation.</summary>
|
||||
/// <remarks>
|
||||
/// If a <see cref="AgentRunResponseUpdate"/> is created to represent some underlying object from another object
|
||||
/// If a <see cref="AgentResponseUpdate"/> is created to represent some underlying object from another object
|
||||
/// model, this property can be used to store that original object. This can be useful for debugging or
|
||||
/// for enabling a consumer to access the underlying object model if needed.
|
||||
/// </remarks>
|
||||
@@ -136,8 +136,8 @@ public class AgentRunResponseUpdate
|
||||
/// Some providers may consider streaming responses to be a single message, and in that case
|
||||
/// the value of this property may be the same as the response ID.
|
||||
///
|
||||
/// This value is used when <see cref="AgentRunResponseExtensions.ToAgentRunResponseAsync(IAsyncEnumerable{AgentRunResponseUpdate}, System.Threading.CancellationToken)"/>
|
||||
/// groups <see cref="AgentRunResponseUpdate"/> instances into <see cref="AgentRunResponse"/> instances.
|
||||
/// This value is used when <see cref="AgentResponseExtensions.ToAgentResponseAsync(IAsyncEnumerable{AgentResponseUpdate}, System.Threading.CancellationToken)"/>
|
||||
/// groups <see cref="AgentResponseUpdate"/> instances into <see cref="AgentResponse"/> instances.
|
||||
/// The value must be unique to each call to the underlying provider, and must be shared by
|
||||
/// all updates that are part of the same logical message within a streaming response.
|
||||
/// </remarks>
|
||||
+6
-6
@@ -8,18 +8,18 @@ namespace Microsoft.Agents.AI;
|
||||
/// Represents the response of the specified type <typeparamref name="T"/> to an <see cref="AIAgent"/> run request.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value expected from the agent.</typeparam>
|
||||
public abstract class AgentRunResponse<T> : AgentRunResponse
|
||||
public abstract class AgentResponse<T> : AgentResponse
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentRunResponse{T}"/> class.</summary>
|
||||
protected AgentRunResponse()
|
||||
/// <summary>Initializes a new instance of the <see cref="AgentResponse{T}"/> class.</summary>
|
||||
protected AgentResponse()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRunResponse{T}"/> class from an existing <see cref="ChatResponse"/>.
|
||||
/// Initializes a new instance of the <see cref="AgentResponse{T}"/> class from an existing <see cref="ChatResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="ChatResponse"/> from which to populate this <see cref="AgentRunResponse{T}"/>.</param>
|
||||
protected AgentRunResponse(ChatResponse response) : base(response)
|
||||
/// <param name="response">The <see cref="ChatResponse"/> from which to populate this <see cref="AgentResponse{T}"/>.</param>
|
||||
protected AgentResponse(ChatResponse response) : base(response)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ public class AgentRunOptions
|
||||
/// This property is used for background responses that can be activated via the <see cref="AllowBackgroundResponses"/>
|
||||
/// property if the <see cref="AIAgent"/> implementation supports them.
|
||||
/// Streamed background responses, such as those returned by default by <see cref="AIAgent.RunStreamingAsync(AgentThread?, AgentRunOptions?, System.Threading.CancellationToken)"/>
|
||||
/// can be resumed if interrupted. This means that a continuation token obtained from the <see cref="AgentRunResponseUpdate.ContinuationToken"/>
|
||||
/// can be resumed if interrupted. This means that a continuation token obtained from the <see cref="AgentResponseUpdate.ContinuationToken"/>
|
||||
/// of an update just before the interruption occurred can be passed to this property to resume the stream from the point of interruption.
|
||||
/// Non-streamed background responses, such as those returned by <see cref="AIAgent.RunAsync(AgentThread?, AgentRunOptions?, System.Threading.CancellationToken)"/>,
|
||||
/// can be polled for completion by obtaining the token from the <see cref="AgentRunResponse.ContinuationToken"/> property
|
||||
/// can be polled for completion by obtaining the token from the <see cref="AgentResponse.ContinuationToken"/> property
|
||||
/// and passing it via this property on subsequent calls to <see cref="AIAgent.RunAsync(AgentThread?, AgentRunOptions?, System.Threading.CancellationToken)"/>.
|
||||
/// </remarks>
|
||||
public ResponseContinuationToken? ContinuationToken { get; set; }
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class DelegatingAIAgent : AIAgent
|
||||
=> this.InnerAgent.DeserializeThreadAsync(serializedThread, jsonSerializerOptions, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -89,7 +89,7 @@ public abstract class DelegatingAIAgent : AIAgent
|
||||
=> this.InnerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
|
||||
@@ -58,7 +58,7 @@ public class CopilotStudioAgent : AIAgent
|
||||
=> new(new CopilotStudioAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override async Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -88,7 +88,7 @@ public class CopilotStudioAgent : AIAgent
|
||||
// TODO: Review list of ChatResponse properties to ensure we set all availble values.
|
||||
// Setting ResponseId and MessageId end up being particularly important for streaming consumers
|
||||
// so that they can tell things like response boundaries.
|
||||
return new AgentRunResponse(responseMessagesList)
|
||||
return new AgentResponse(responseMessagesList)
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = responseMessagesList.LastOrDefault()?.MessageId,
|
||||
@@ -96,7 +96,7 @@ public class CopilotStudioAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -125,7 +125,7 @@ public class CopilotStudioAgent : AIAgent
|
||||
// TODO: Review list of ChatResponse properties to ensure we set all availble values.
|
||||
// Setting ResponseId and MessageId end up being particularly important for streaming consumers
|
||||
// so that they can tell things like response boundaries.
|
||||
yield return new AgentRunResponseUpdate(message.Role, message.Contents)
|
||||
yield return new AgentResponseUpdate(message.Role, message.Contents)
|
||||
{
|
||||
AgentId = this.Id,
|
||||
AdditionalProperties = message.AdditionalProperties,
|
||||
|
||||
@@ -21,7 +21,7 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
? cancellationToken
|
||||
: services.GetService<IHostApplicationLifetime>()?.ApplicationStopping ?? CancellationToken.None;
|
||||
|
||||
public Task<AgentRunResponse> RunAgentAsync(RunRequest request)
|
||||
public Task<AgentResponse> RunAgentAsync(RunRequest request)
|
||||
{
|
||||
return this.Run(request);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
// IDE1006 and VSTHRD200 disabled to allow method name to match the common cross-platform entity operation name.
|
||||
#pragma warning disable IDE1006
|
||||
#pragma warning disable VSTHRD200
|
||||
public async Task<AgentRunResponse> Run(RunRequest request)
|
||||
public async Task<AgentResponse> Run(RunRequest request)
|
||||
#pragma warning restore VSTHRD200
|
||||
#pragma warning restore IDE1006
|
||||
{
|
||||
@@ -43,7 +43,7 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
if (request.Messages.Count == 0)
|
||||
{
|
||||
logger.LogInformation("Ignoring empty request");
|
||||
return new AgentRunResponse();
|
||||
return new AgentResponse();
|
||||
}
|
||||
|
||||
this.State.Data.ConversationHistory.Add(DurableAgentStateRequest.FromRunRequest(request));
|
||||
@@ -65,29 +65,29 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
try
|
||||
{
|
||||
// Start the agent response stream
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> responseStream = agentWrapper.RunStreamingAsync(
|
||||
IAsyncEnumerable<AgentResponseUpdate> responseStream = agentWrapper.RunStreamingAsync(
|
||||
this.State.Data.ConversationHistory.SelectMany(e => e.Messages).Select(m => m.ToChatMessage()),
|
||||
await agentWrapper.GetNewThreadAsync(cancellationToken).ConfigureAwait(false),
|
||||
options: null,
|
||||
this._cancellationToken);
|
||||
|
||||
AgentRunResponse response;
|
||||
AgentResponse response;
|
||||
if (this._messageHandler is null)
|
||||
{
|
||||
// If no message handler is provided, we can just get the full response at once.
|
||||
// This is expected to be the common case for non-interactive agents.
|
||||
response = await responseStream.ToAgentRunResponseAsync(this._cancellationToken);
|
||||
response = await responseStream.ToAgentResponseAsync(this._cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<AgentRunResponseUpdate> responseUpdates = [];
|
||||
List<AgentResponseUpdate> responseUpdates = [];
|
||||
|
||||
// To support interactive chat agents, we need to stream the responses to an IAgentMessageHandler.
|
||||
// The user-provided message handler can be implemented to send the responses to the user.
|
||||
// We assume that only non-empty text updates are useful for the user.
|
||||
async IAsyncEnumerable<AgentRunResponseUpdate> StreamResultsAsync()
|
||||
async IAsyncEnumerable<AgentResponseUpdate> StreamResultsAsync()
|
||||
{
|
||||
await foreach (AgentRunResponseUpdate update in responseStream)
|
||||
await foreach (AgentResponseUpdate update in responseStream)
|
||||
{
|
||||
// We need the full response further down, so we piece it together as we go.
|
||||
responseUpdates.Add(update);
|
||||
@@ -98,12 +98,12 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
}
|
||||
|
||||
await this._messageHandler.OnStreamingResponseUpdateAsync(StreamResultsAsync(), this._cancellationToken);
|
||||
response = responseUpdates.ToAgentRunResponse();
|
||||
response = responseUpdates.ToAgentResponse();
|
||||
}
|
||||
|
||||
// Persist the agent response to the entity state for client polling
|
||||
this.State.Data.ConversationHistory.Add(
|
||||
DurableAgentStateResponse.FromRunResponse(request.CorrelationId, response));
|
||||
DurableAgentStateResponse.FromResponse(request.CorrelationId, response));
|
||||
|
||||
string responseText = response.Text;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ internal sealed class AgentRunHandle
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The agent response corresponding to this request.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the response is not found after polling.</exception>
|
||||
public async Task<AgentRunResponse> ReadAgentResponseAsync(CancellationToken cancellationToken = default)
|
||||
public async Task<AgentResponse> ReadAgentResponseAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
TimeSpan pollInterval = TimeSpan.FromMilliseconds(50); // Start with 50ms
|
||||
TimeSpan maxPollInterval = TimeSpan.FromSeconds(3); // Maximum 3 seconds
|
||||
@@ -69,7 +69,7 @@ internal sealed class AgentRunHandle
|
||||
if (response is not null)
|
||||
{
|
||||
this._logger.LogDonePollingForResponse(this.SessionId, this.CorrelationId);
|
||||
return response.ToRunResponse();
|
||||
return response.ToResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// <exception cref="AgentNotRegisteredException">Thrown when the agent has not been registered.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the provided thread is not valid for a durable agent.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown when cancellation is requested (cancellation is not supported for durable agents).</exception>
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override async Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -107,7 +107,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
|
||||
try
|
||||
{
|
||||
return await this._context.Entities.CallEntityAsync<AgentRunResponse>(
|
||||
return await this._context.Entities.CallEntityAsync<AgentResponse>(
|
||||
durableThread.SessionId,
|
||||
nameof(AgentEntity.Run),
|
||||
request);
|
||||
@@ -130,7 +130,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// <param name="options">Optional run options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A streaming response enumerable.</returns>
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -138,8 +138,8 @@ public sealed class DurableAIAgent : AIAgent
|
||||
{
|
||||
// Streaming is not supported for durable agents, so we just return the full response
|
||||
// as a single update.
|
||||
AgentRunResponse response = await this.RunAsync(messages, thread, options, cancellationToken);
|
||||
foreach (AgentRunResponseUpdate update in response.ToAgentRunResponseUpdates())
|
||||
AgentResponse response = await this.RunAsync(messages, thread, options, cancellationToken);
|
||||
foreach (AgentResponseUpdate update in response.ToAgentResponseUpdates())
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
@@ -162,7 +162,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// Thrown when the agent response is empty or cannot be deserialized.
|
||||
/// </exception>
|
||||
/// <returns>The output from the agent.</returns>
|
||||
public async Task<AgentRunResponse<T>> RunAsync<T>(
|
||||
public async Task<AgentResponse<T>> RunAsync<T>(
|
||||
string message,
|
||||
AgentThread? thread = null,
|
||||
JsonSerializerOptions? serializerOptions = null,
|
||||
@@ -196,7 +196,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// <returns>The output from the agent.</returns>
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Fallback to reflection-based deserialization is intentional for library flexibility with user-defined types.")]
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "Fallback to reflection-based deserialization is intentional for library flexibility with user-defined types.")]
|
||||
public async Task<AgentRunResponse<T>> RunAsync<T>(
|
||||
public async Task<AgentResponse<T>> RunAsync<T>(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
JsonSerializerOptions? serializerOptions = null,
|
||||
@@ -223,7 +223,7 @@ public sealed class DurableAIAgent : AIAgent
|
||||
// Create the JSON schema for the response type
|
||||
durableOptions.ResponseFormat = ChatResponseFormat.ForJsonSchema<T>();
|
||||
|
||||
AgentRunResponse response = await this.RunAsync(messages, thread, durableOptions, cancellationToken);
|
||||
AgentResponse response = await this.RunAsync(messages, thread, durableOptions, cancellationToken);
|
||||
|
||||
// Deserialize the response text to the requested type
|
||||
if (string.IsNullOrEmpty(response.Text))
|
||||
@@ -242,11 +242,11 @@ public sealed class DurableAIAgent : AIAgent
|
||||
: JsonSerializer.Deserialize<T>(response.Text, serializerOptions))
|
||||
?? throw new InvalidOperationException($"Failed to deserialize agent response to type {typeof(T).Name}.");
|
||||
|
||||
return new DurableAIAgentRunResponse<T>(response, result);
|
||||
return new DurableAIAgentResponse<T>(response, result);
|
||||
}
|
||||
|
||||
private sealed class DurableAIAgentRunResponse<T>(AgentRunResponse response, T result)
|
||||
: AgentRunResponse<T>(response.AsChatResponse())
|
||||
private sealed class DurableAIAgentResponse<T>(AgentResponse response, T result)
|
||||
: AgentResponse<T>(response.AsChatResponse())
|
||||
{
|
||||
public override T Result { get; } = result;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient)
|
||||
return ValueTask.FromResult<AgentThread>(new DurableAgentThread(AgentSessionId.WithRandomKey(this.Name!)));
|
||||
}
|
||||
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override async Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
@@ -64,13 +64,13 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient)
|
||||
if (isFireAndForget)
|
||||
{
|
||||
// If the request is fire and forget, return an empty response.
|
||||
return new AgentRunResponse();
|
||||
return new AgentResponse();
|
||||
}
|
||||
|
||||
return await agentRunHandle.ReadAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.Agents.AI.DurableTask;
|
||||
/// <item><description><see cref="JsonSerializerDefaults.Web"/> baseline defaults.</description></item>
|
||||
/// <item><description><see cref="JsonIgnoreCondition.WhenWritingNull"/> for default null-value suppression.</description></item>
|
||||
/// <item><description><see cref="JsonNumberHandling.AllowReadingFromString"/> to tolerate numbers encoded as strings.</description></item>
|
||||
/// <item><description>Chained type info resolvers from shared agent abstractions to cover cross-package types (e.g. <see cref="ChatMessage"/>, <see cref="AgentRunResponse"/>).</description></item>
|
||||
/// <item><description>Chained type info resolvers from shared agent abstractions to cover cross-package types (e.g. <see cref="ChatMessage"/>, <see cref="AgentResponse"/>).</description></item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Keep the list of <c>[JsonSerializable]</c> types in sync with the Durable Agent data model anytime new state or request/response
|
||||
|
||||
@@ -21,13 +21,13 @@ internal sealed class EntityAgentWrapper(
|
||||
// The ID of the agent is always the entity ID.
|
||||
protected override string? IdCore => this._entityContext.Id.ToString();
|
||||
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
protected override async Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentRunResponse response = await base.RunCoreAsync(
|
||||
AgentResponse response = await base.RunCoreAsync(
|
||||
messages,
|
||||
thread,
|
||||
this.GetAgentEntityRunOptions(options),
|
||||
@@ -37,13 +37,13 @@ internal sealed class EntityAgentWrapper(
|
||||
return response;
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await foreach (AgentRunResponseUpdate update in base.RunCoreStreamingAsync(
|
||||
await foreach (AgentResponseUpdate update in base.RunCoreStreamingAsync(
|
||||
messages,
|
||||
thread,
|
||||
this.GetAgentEntityRunOptions(options),
|
||||
|
||||
@@ -17,7 +17,7 @@ public interface IAgentResponseHandler
|
||||
/// Signals that the operation should be cancelled.
|
||||
/// </param>
|
||||
ValueTask OnStreamingResponseUpdateAsync(
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> messageStream,
|
||||
IAsyncEnumerable<AgentResponseUpdate> messageStream,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -30,6 +30,6 @@ public interface IAgentResponseHandler
|
||||
/// Signals that the operation should be cancelled.
|
||||
/// </param>
|
||||
ValueTask OnAgentResponseAsync(
|
||||
AgentRunResponse message,
|
||||
AgentResponse message,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- CA2007: This rule should generally be suppressed in Durable Task libraries -->
|
||||
<!-- MEAI001: UserInputRequestContent is experimental but used in source-generated code for AgentRunResponse -->
|
||||
<!-- MEAI001: UserInputRequestContent is experimental but used in source-generated code for AgentResponse -->
|
||||
<NoWarn>$(NoWarn);CA2007;MEAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ internal sealed class DurableAgentStateResponse : DurableAgentStateEntry
|
||||
public DurableAgentStateUsage? Usage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="DurableAgentStateResponse"/> from an <see cref="AgentRunResponse"/>.
|
||||
/// Creates a <see cref="DurableAgentStateResponse"/> from an <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="correlationId">The correlation ID linking this response to its request.</param>
|
||||
/// <param name="response">The <see cref="AgentRunResponse"/> to convert.</param>
|
||||
/// <param name="response">The <see cref="AgentResponse"/> to convert.</param>
|
||||
/// <returns>A <see cref="DurableAgentStateResponse"/> representing the original response.</returns>
|
||||
public static DurableAgentStateResponse FromRunResponse(string correlationId, AgentRunResponse response)
|
||||
public static DurableAgentStateResponse FromResponse(string correlationId, AgentResponse response)
|
||||
{
|
||||
return new DurableAgentStateResponse()
|
||||
{
|
||||
@@ -34,12 +34,12 @@ internal sealed class DurableAgentStateResponse : DurableAgentStateEntry
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="DurableAgentStateResponse"/> back to an <see cref="AgentRunResponse"/>.
|
||||
/// Converts this <see cref="DurableAgentStateResponse"/> back to an <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="AgentRunResponse"/> representing this response.</returns>
|
||||
public AgentRunResponse ToRunResponse()
|
||||
/// <returns>A <see cref="AgentResponse"/> representing this response.</returns>
|
||||
public AgentResponse ToResponse()
|
||||
{
|
||||
return new AgentRunResponse()
|
||||
return new AgentResponse()
|
||||
{
|
||||
CreatedAt = this.CreatedAt,
|
||||
Messages = this.Messages.Select(m => m.ToChatMessage()).ToList(),
|
||||
|
||||
@@ -119,7 +119,7 @@ internal static class BuiltInFunctions
|
||||
|
||||
if (waitForResponse)
|
||||
{
|
||||
AgentRunResponse agentResponse = await agentProxy.RunAsync(
|
||||
AgentResponse agentResponse = await agentProxy.RunAsync(
|
||||
message: new ChatMessage(ChatRole.User, message),
|
||||
thread: new DurableAgentThread(sessionId),
|
||||
options: options,
|
||||
@@ -170,7 +170,7 @@ internal static class BuiltInFunctions
|
||||
|
||||
AIAgent agentProxy = client.AsDurableAgentProxy(functionContext, agentName);
|
||||
|
||||
AgentRunResponse agentResponse = await agentProxy.RunAsync(
|
||||
AgentResponse agentResponse = await agentProxy.RunAsync(
|
||||
message: new ChatMessage(ChatRole.User, query),
|
||||
thread: new DurableAgentThread(sessionId),
|
||||
options: null);
|
||||
@@ -224,7 +224,7 @@ internal static class BuiltInFunctions
|
||||
FunctionContext context,
|
||||
HttpStatusCode statusCode,
|
||||
string threadId,
|
||||
AgentRunResponse agentResponse)
|
||||
AgentResponse agentResponse)
|
||||
{
|
||||
HttpResponseData response = req.CreateResponse(statusCode);
|
||||
response.Headers.Add("x-ms-thread-id", threadId);
|
||||
@@ -321,7 +321,7 @@ internal static class BuiltInFunctions
|
||||
private sealed record AgentRunSuccessResponse(
|
||||
[property: JsonPropertyName("status")] int Status,
|
||||
[property: JsonPropertyName("thread_id")] string ThreadId,
|
||||
[property: JsonPropertyName("response")] AgentRunResponse Response);
|
||||
[property: JsonPropertyName("response")] AgentResponse Response);
|
||||
|
||||
/// <summary>
|
||||
/// Represents an accepted (fire-and-forget) agent run response.
|
||||
|
||||
@@ -77,7 +77,7 @@ public static async Task<string> SpamDetectionOrchestration(
|
||||
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
|
||||
|
||||
// Step 1: Check if the email is spam
|
||||
AgentRunResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
message:
|
||||
$"""
|
||||
Analyze this email for spam content and return a JSON response with 'is_spam' (boolean) and 'reason' (string) fields:
|
||||
@@ -99,7 +99,7 @@ public static async Task<string> SpamDetectionOrchestration(
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
|
||||
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
|
||||
|
||||
AgentRunResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
message:
|
||||
$"""
|
||||
Draft a professional response to this email. Return a JSON response with a 'response' field containing the reply:
|
||||
|
||||
+5
-5
@@ -70,18 +70,18 @@ internal static class AIAgentChatCompletionsProcessor
|
||||
DateTimeOffset? createdAt = null;
|
||||
var chunkId = IdGenerator.NewId(prefix: "chatcmpl", delimiter: "-", stringLength: 13);
|
||||
|
||||
await foreach (var agentRunResponseUpdate in agent.RunStreamingAsync(chatMessages, options: options, cancellationToken: cancellationToken).WithCancellation(cancellationToken))
|
||||
await foreach (var agentResponseUpdate in agent.RunStreamingAsync(chatMessages, options: options, cancellationToken: cancellationToken).WithCancellation(cancellationToken))
|
||||
{
|
||||
var finishReason = (agentRunResponseUpdate.RawRepresentation is ChatResponseUpdate { FinishReason: not null } chatResponseUpdate)
|
||||
var finishReason = (agentResponseUpdate.RawRepresentation is ChatResponseUpdate { FinishReason: not null } chatResponseUpdate)
|
||||
? chatResponseUpdate.FinishReason.ToString()
|
||||
: "stop";
|
||||
|
||||
var choiceChunks = new List<ChatCompletionChoiceChunk>();
|
||||
CompletionUsage? usageDetails = null;
|
||||
|
||||
createdAt ??= agentRunResponseUpdate.CreatedAt;
|
||||
createdAt ??= agentResponseUpdate.CreatedAt;
|
||||
|
||||
foreach (var content in agentRunResponseUpdate.Contents)
|
||||
foreach (var content in agentResponseUpdate.Contents)
|
||||
{
|
||||
// usage content is handled separately
|
||||
if (content is UsageContent usageContent && usageContent.Details != null)
|
||||
@@ -124,7 +124,7 @@ internal static class AIAgentChatCompletionsProcessor
|
||||
continue;
|
||||
}
|
||||
|
||||
delta.Role = agentRunResponseUpdate.Role?.Value ?? "user";
|
||||
delta.Role = agentResponseUpdate.Role?.Value ?? "user";
|
||||
|
||||
var choiceChunk = new ChatCompletionChoiceChunk
|
||||
{
|
||||
|
||||
+8
-8
@@ -12,33 +12,33 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions;
|
||||
/// <summary>
|
||||
/// Extension methods for converting agent responses to ChatCompletion models.
|
||||
/// </summary>
|
||||
internal static class AgentRunResponseExtensions
|
||||
internal static class AgentResponseExtensions
|
||||
{
|
||||
public static ChatCompletion ToChatCompletion(this AgentRunResponse agentRunResponse, CreateChatCompletion request)
|
||||
public static ChatCompletion ToChatCompletion(this AgentResponse agentResponse, CreateChatCompletion request)
|
||||
{
|
||||
IList<ChatCompletionChoice> choices = agentRunResponse.ToChoices();
|
||||
IList<ChatCompletionChoice> choices = agentResponse.ToChoices();
|
||||
|
||||
return new ChatCompletion
|
||||
{
|
||||
Id = IdGenerator.NewId(prefix: "chatcmpl", delimiter: "-", stringLength: 13),
|
||||
Choices = choices,
|
||||
Created = (agentRunResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(),
|
||||
Created = (agentResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(),
|
||||
Model = request.Model,
|
||||
Usage = agentRunResponse.Usage.ToCompletionUsage(),
|
||||
Usage = agentResponse.Usage.ToCompletionUsage(),
|
||||
ServiceTier = request.ServiceTier ?? "default"
|
||||
};
|
||||
}
|
||||
|
||||
public static List<ChatCompletionChoice> ToChoices(this AgentRunResponse agentRunResponse)
|
||||
public static List<ChatCompletionChoice> ToChoices(this AgentResponse agentResponse)
|
||||
{
|
||||
var chatCompletionChoices = new List<ChatCompletionChoice>();
|
||||
var index = 0;
|
||||
|
||||
var finishReason = (agentRunResponse.RawRepresentation is ChatResponse { FinishReason: not null } chatResponse)
|
||||
var finishReason = (agentResponse.RawRepresentation is ChatResponse { FinishReason: not null } chatResponse)
|
||||
? chatResponse.FinishReason.ToString()
|
||||
: "stop"; // "stop" is a natural stop point; returning this by-default
|
||||
|
||||
foreach (var message in agentRunResponse.Messages)
|
||||
foreach (var message in agentResponse.Messages)
|
||||
{
|
||||
foreach (var content in message.Contents)
|
||||
{
|
||||
+7
-7
@@ -13,19 +13,19 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
|
||||
/// <summary>
|
||||
/// Extension methods for converting agent responses to Response models.
|
||||
/// </summary>
|
||||
internal static class AgentRunResponseExtensions
|
||||
internal static class AgentResponseExtensions
|
||||
{
|
||||
private static ChatRole s_DeveloperRole => new("developer");
|
||||
|
||||
/// <summary>
|
||||
/// Converts an AgentRunResponse to a Response model.
|
||||
/// Converts an AgentResponse to a Response model.
|
||||
/// </summary>
|
||||
/// <param name="agentRunResponse">The agent run response to convert.</param>
|
||||
/// <param name="agentResponse">The agent response to convert.</param>
|
||||
/// <param name="request">The original create response request.</param>
|
||||
/// <param name="context">The agent invocation context.</param>
|
||||
/// <returns>A Response model.</returns>
|
||||
public static Response ToResponse(
|
||||
this AgentRunResponse agentRunResponse,
|
||||
this AgentResponse agentResponse,
|
||||
CreateResponse request,
|
||||
AgentInvocationContext context)
|
||||
{
|
||||
@@ -41,7 +41,7 @@ internal static class AgentRunResponseExtensions
|
||||
});
|
||||
}
|
||||
|
||||
output.AddRange(agentRunResponse.Messages
|
||||
output.AddRange(agentResponse.Messages
|
||||
.SelectMany(msg => msg.ToItemResource(context.IdGenerator, context.JsonSerializerOptions)));
|
||||
|
||||
return new Response
|
||||
@@ -49,7 +49,7 @@ internal static class AgentRunResponseExtensions
|
||||
Agent = request.Agent?.ToAgentId(),
|
||||
Background = request.Background,
|
||||
Conversation = request.Conversation ?? (context.ConversationId != null ? new ConversationReference { Id = context.ConversationId } : null),
|
||||
CreatedAt = (agentRunResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(),
|
||||
CreatedAt = (agentResponse.CreatedAt ?? DateTimeOffset.UtcNow).ToUnixTimeSeconds(),
|
||||
Error = null,
|
||||
Id = context.ResponseId,
|
||||
Instructions = request.Instructions,
|
||||
@@ -74,7 +74,7 @@ internal static class AgentRunResponseExtensions
|
||||
TopLogprobs = request.TopLogprobs,
|
||||
TopP = request.TopP ?? 1.0,
|
||||
Truncation = request.Truncation,
|
||||
Usage = agentRunResponse.Usage.ToResponseUsage(),
|
||||
Usage = agentResponse.Usage.ToResponseUsage(),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
User = request.User,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
+6
-6
@@ -16,12 +16,12 @@ using Microsoft.Extensions.AI;
|
||||
namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="AgentRunResponseUpdate"/>.
|
||||
/// Extension methods for <see cref="AgentResponseUpdate"/>.
|
||||
/// </summary>
|
||||
internal static class AgentRunResponseUpdateExtensions
|
||||
internal static class AgentResponseUpdateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a stream of <see cref="AgentRunResponseUpdate"/> to stream of <see cref="StreamingResponseEvent"/>.
|
||||
/// Converts a stream of <see cref="AgentResponseUpdate"/> to stream of <see cref="StreamingResponseEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="updates">The agent run response updates.</param>
|
||||
/// <param name="request">The create response request.</param>
|
||||
@@ -29,7 +29,7 @@ internal static class AgentRunResponseUpdateExtensions
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A stream of response events.</returns>
|
||||
public static async IAsyncEnumerable<StreamingResponseEvent> ToStreamingResponseAsync(
|
||||
this IAsyncEnumerable<AgentRunResponseUpdate> updates,
|
||||
this IAsyncEnumerable<AgentResponseUpdate> updates,
|
||||
CreateResponse request,
|
||||
AgentInvocationContext context,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
@@ -48,7 +48,7 @@ internal static class AgentRunResponseUpdateExtensions
|
||||
// Track active item IDs by executor ID to pair invoked/completed/failed events
|
||||
Dictionary<string, string> executorItemIds = [];
|
||||
|
||||
AgentRunResponseUpdate? previousUpdate = null;
|
||||
AgentResponseUpdate? previousUpdate = null;
|
||||
StreamingEventGenerator? generator = null;
|
||||
while (await updateEnumerator.MoveNextAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -279,7 +279,7 @@ internal static class AgentRunResponseUpdateExtensions
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSameMessage(AgentRunResponseUpdate? first, AgentRunResponseUpdate? second)
|
||||
private static bool IsSameMessage(AgentResponseUpdate? first, AgentResponseUpdate? second)
|
||||
{
|
||||
return IsSameValue(first?.MessageId, second?.MessageId)
|
||||
&& IsSameValue(first?.AuthorName, second?.AuthorName)
|
||||
+3
-3
@@ -7,9 +7,9 @@ namespace Microsoft.Agents.AI.OpenAI;
|
||||
|
||||
internal sealed class AsyncStreamingChatCompletionUpdateCollectionResult : AsyncCollectionResult<StreamingChatCompletionUpdate>
|
||||
{
|
||||
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
|
||||
private readonly IAsyncEnumerable<AgentResponseUpdate> _updates;
|
||||
|
||||
internal AsyncStreamingChatCompletionUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
|
||||
internal AsyncStreamingChatCompletionUpdateCollectionResult(IAsyncEnumerable<AgentResponseUpdate> updates)
|
||||
{
|
||||
this._updates = updates;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal sealed class AsyncStreamingChatCompletionUpdateCollectionResult : Async
|
||||
|
||||
protected override IAsyncEnumerable<StreamingChatCompletionUpdate> GetValuesFromPageAsync(ClientResult page)
|
||||
{
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentRunResponseUpdate>>)page).Value;
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentResponseUpdate>>)page).Value;
|
||||
|
||||
return updates.AsChatResponseUpdatesAsync().AsOpenAIStreamingChatCompletionUpdatesAsync();
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ namespace Microsoft.Agents.AI.OpenAI;
|
||||
|
||||
internal sealed class AsyncStreamingResponseUpdateCollectionResult : AsyncCollectionResult<StreamingResponseUpdate>
|
||||
{
|
||||
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
|
||||
private readonly IAsyncEnumerable<AgentResponseUpdate> _updates;
|
||||
|
||||
internal AsyncStreamingResponseUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
|
||||
internal AsyncStreamingResponseUpdateCollectionResult(IAsyncEnumerable<AgentResponseUpdate> updates)
|
||||
{
|
||||
this._updates = updates;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal sealed class AsyncStreamingResponseUpdateCollectionResult : AsyncCollec
|
||||
|
||||
protected async override IAsyncEnumerable<StreamingResponseUpdate> GetValuesFromPageAsync(ClientResult page)
|
||||
{
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentRunResponseUpdate>>)page).Value;
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentResponseUpdate>>)page).Value;
|
||||
|
||||
await foreach (var update in updates.ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed class StreamingUpdatePipelineResponse : PipelineResponse
|
||||
// No resources to dispose.
|
||||
}
|
||||
|
||||
internal StreamingUpdatePipelineResponse(IAsyncEnumerable<AgentRunResponseUpdate> updates)
|
||||
internal StreamingUpdatePipelineResponse(IAsyncEnumerable<AgentResponseUpdate> updates)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Microsoft.Agents.AI;
|
||||
/// These extensions bridge the gap between the Microsoft Extensions AI framework and the OpenAI SDK,
|
||||
/// allowing developers to work with native OpenAI types while leveraging the AI Agent framework.
|
||||
/// The methods handle the conversion between OpenAI chat message types and Microsoft Extensions AI types,
|
||||
/// and return OpenAI <see cref="ChatCompletion"/> objects directly from the agent's <see cref="AgentRunResponse"/>.
|
||||
/// and return OpenAI <see cref="ChatCompletion"/> objects directly from the agent's <see cref="AgentResponse"/>.
|
||||
/// </remarks>
|
||||
public static class AIAgentWithOpenAIExtensions
|
||||
{
|
||||
@@ -34,7 +34,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
/// <exception cref="NotSupportedException">Thrown when any message in <paramref name="messages"/> has a type that is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI chat messages to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static async Task<ChatCompletion> RunAsync(this AIAgent agent, IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -60,14 +60,14 @@ public static class AIAgentWithOpenAIExtensions
|
||||
/// <exception cref="NotSupportedException">Thrown when the <paramref name="messages"/> type is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI chat messages to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static AsyncCollectionResult<StreamingChatCompletionUpdate> RunStreamingAsync(this AIAgent agent, IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(agent);
|
||||
Throw.IfNull(messages);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
|
||||
IAsyncEnumerable<AgentResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
|
||||
|
||||
return new AsyncStreamingChatCompletionUpdateCollectionResult(response);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
/// <exception cref="NotSupportedException">Thrown when any message in <paramref name="messages"/> has a type that is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI response items to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="ResponseResult"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIResponse"/>.
|
||||
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="ResponseResult"/> from the response using <see cref="AgentResponseExtensions.AsOpenAIResponse"/>.
|
||||
/// </remarks>
|
||||
public static async Task<ResponseResult> RunAsync(this AIAgent agent, IEnumerable<ResponseItem> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -121,7 +121,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
Throw.IfNull(agent);
|
||||
Throw.IfNull(messages);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
|
||||
IAsyncEnumerable<AgentResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
|
||||
|
||||
return new AsyncStreamingResponseUpdateCollectionResult(response);
|
||||
}
|
||||
|
||||
+6
-6
@@ -8,18 +8,18 @@ using OpenAI.Responses;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> instances to
|
||||
/// Provides extension methods for <see cref="AgentResponse"/> and <see cref="AgentResponseUpdate"/> instances to
|
||||
/// create or extract native OpenAI response objects from the Microsoft Agent Framework responses.
|
||||
/// </summary>
|
||||
public static class AgentRunResponseExtensions
|
||||
public static class AgentResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates or extracts a native OpenAI <see cref="ChatCompletion"/> object from an <see cref="AgentRunResponse"/>.
|
||||
/// Creates or extracts a native OpenAI <see cref="ChatCompletion"/> object from an <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The agent response.</param>
|
||||
/// <returns>The OpenAI <see cref="ChatCompletion"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
public static ChatCompletion AsOpenAIChatCompletion(this AgentRunResponse response)
|
||||
public static ChatCompletion AsOpenAIChatCompletion(this AgentResponse response)
|
||||
{
|
||||
Throw.IfNull(response);
|
||||
|
||||
@@ -29,12 +29,12 @@ public static class AgentRunResponseExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or extracts a native OpenAI <see cref="ResponseResult"/> object from an <see cref="AgentRunResponse"/>.
|
||||
/// Creates or extracts a native OpenAI <see cref="ResponseResult"/> object from an <see cref="AgentResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The agent response.</param>
|
||||
/// <returns>The OpenAI <see cref="ResponseResult"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
public static ResponseResult AsOpenAIResponse(this AgentRunResponse response)
|
||||
public static ResponseResult AsOpenAIResponse(this AgentResponse response)
|
||||
{
|
||||
Throw.IfNull(response);
|
||||
|
||||
@@ -42,16 +42,16 @@ internal class PurviewAgent : AIAgent, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this._purviewWrapper.ProcessAgentContentAsync(messages, thread, options, this._innerAgent, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await this._purviewWrapper.ProcessAgentContentAsync(messages, thread, options, this._innerAgent, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var update in response.ToAgentRunResponseUpdates())
|
||||
foreach (var update in response.ToAgentResponseUpdates())
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
/// <param name="innerAgent">The wrapped agent.</param>
|
||||
/// <param name="cancellationToken">The cancellation token used to interrupt async operations.</param>
|
||||
/// <returns>The agent's response. This could be the response from the agent or a message indicating that Purview has blocked the prompt or response.</returns>
|
||||
public async Task<AgentRunResponse> ProcessAgentContentAsync(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
public async Task<AgentResponse> ProcessAgentContentAsync(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
string threadId = GetThreadIdFromAgentThread(thread, messages);
|
||||
|
||||
@@ -151,7 +151,7 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
this._logger.LogInformation("Prompt blocked by policy. Sending message: {Message}", this._purviewSettings.BlockedPromptMessage);
|
||||
}
|
||||
|
||||
return new AgentRunResponse(new ChatMessage(ChatRole.System, this._purviewSettings.BlockedPromptMessage));
|
||||
return new AgentResponse(new ChatMessage(ChatRole.System, this._purviewSettings.BlockedPromptMessage));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -167,7 +167,7 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
AgentRunResponse response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false);
|
||||
AgentResponse response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -180,7 +180,7 @@ internal sealed class PurviewWrapper : IDisposable
|
||||
this._logger.LogInformation("Response blocked by policy. Sending message: {Message}", this._purviewSettings.BlockedResponseMessage);
|
||||
}
|
||||
|
||||
return new AgentRunResponse(new ChatMessage(ChatRole.System, this._purviewSettings.BlockedResponseMessage));
|
||||
return new AgentResponse(new ChatMessage(ChatRole.System, this._purviewSettings.BlockedResponseMessage));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -211,7 +211,7 @@ The policy logic is identical; the only difference is the hook point in the pipe
|
||||
|
||||
## Middleware Lifecycle
|
||||
1. Before sending the prompt to the agent, the middleware checks the app and user metadata against Purview's protection scopes and evaluates all the `ChatMessage`s in the prompt.
|
||||
2. If the content was blocked, the middleware returns a `ChatResponse` or `AgentRunResponse` containing the `BlockedPromptMessage` text. The blocked content does not get passed to the agent.
|
||||
2. If the content was blocked, the middleware returns a `ChatResponse` or `AgentResponse` containing the `BlockedPromptMessage` text. The blocked content does not get passed to the agent.
|
||||
3. If the evaluation did not block the content, the middleware passes the prompt data to the agent and waits for a response.
|
||||
4. After receiving a response from the agent, the middleware calls Purview again to evaluate the response content.
|
||||
5. If the content was blocked, the middleware returns a response containing the `BlockedResponseMessage`.
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> InvokeAgentAsync(
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> InvokeAgentAsync(
|
||||
string agentId,
|
||||
string? agentVersion,
|
||||
string? conversationId,
|
||||
@@ -120,12 +120,12 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
|
||||
ChatClientAgentRunOptions runOptions = new(chatOptions);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentResponse =
|
||||
IAsyncEnumerable<AgentResponseUpdate> agentResponse =
|
||||
messages is not null ?
|
||||
agent.RunStreamingAsync([.. messages], null, runOptions, cancellationToken) :
|
||||
agent.RunStreamingAsync([new ChatMessage(ChatRole.User, string.Empty)], null, runOptions, cancellationToken);
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agentResponse.ConfigureAwait(false))
|
||||
await foreach (AgentResponseUpdate update in agentResponse.ConfigureAwait(false))
|
||||
{
|
||||
update.AuthorName = agentVersionResult.Name;
|
||||
yield return update;
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen
|
||||
EvaluateListExpression<ChatMessage>(this.Model.Input?.Messages, "inputMessages");
|
||||
this.Write(@"
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
AgentResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
context,
|
||||
agentName,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user