Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/upgrades/prompts/SemanticKernelToAgentFramework.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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`)
Expand Down Expand Up @@ -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>
Expand All @@ -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.

Expand Down Expand Up @@ -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);
```

Expand All @@ -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);
}
Expand All @@ -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>
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}");
```

Expand All @@ -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)
{
Expand All @@ -734,10 +734,10 @@ await foreach (var content in agent.InvokeAsync(userInput, thread))

**With this Agent Framework breaking glass pattern:**
```csharp
var agentRunResponse = await agent.RunAsync(userInput, thread);
var agentResponse = await agent.RunAsync(userInput, thread);

// If the agent uses a ChatClient the first breaking glass probably will be a Microsoft.Extensions.AI.ChatResponse
ChatResponse? chatResponse = agentRunResponse.RawRepresentation as ChatResponse;
ChatResponse? chatResponse = agentResponse.RawRepresentation as ChatResponse;

// If thats the case, to access the underlying SDK types you will need to break glass again.
UnderlyingSdkType? underlyingChatMessage = chatResponse?.RawRepresentation as UnderlyingSdkType;
Expand Down
36 changes: 18 additions & 18 deletions docs/decisions/0001-agent-run-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
{
}
```
Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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; }
Expand Down Expand Up @@ -360,15 +360,15 @@ 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
...
}


public class AgentRunResponseUpdate
public class AgentResponseUpdate
{
...
public AgentRun RunReference { get; set; } // Reference to long running process
Expand Down Expand Up @@ -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.

Expand All @@ -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[]>();
```

Expand All @@ -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)

Expand Down
12 changes: 6 additions & 6 deletions docs/decisions/0006-userapproval.md
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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; }

Expand Down Expand Up @@ -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; }

Expand Down
16 changes: 8 additions & 8 deletions docs/decisions/0007-agent-filtering-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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}");
Expand All @@ -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
{
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading