mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3b2fa09a | ||
|
|
5687e13221 | ||
|
|
a151f10cc2 | ||
|
|
b773830e4b | ||
|
|
975884f32d | ||
|
|
b5ca0c8eda | ||
|
|
dd3e2b6e53 | ||
|
|
48d124efbe | ||
|
|
6e9420f614 | ||
|
|
2ab859dd94 | ||
|
|
e192af93a7 | ||
|
|
3dbdecedda | ||
|
|
15d0c34d9f | ||
|
|
620da7a829 | ||
|
|
80b25a782b | ||
|
|
ffe2e787ba | ||
|
|
cb2862d4c3 | ||
|
|
9b9a0f178c | ||
|
|
6c956ec596 | ||
|
|
99c5718696 | ||
|
|
f56808b279 | ||
|
|
c70e594e6c | ||
|
|
8b1449024e | ||
|
|
d8cf8361bd | ||
|
|
1ae0b09e42 | ||
|
|
c063fc77e6 | ||
|
|
04657c207a | ||
|
|
655a59a75f | ||
|
|
7d2d34511c | ||
|
|
0b152418b6 | ||
|
|
3e97425245 | ||
|
|
5faa2851bb | ||
|
|
9c094573e8 | ||
|
|
b2893fbc00 | ||
|
|
203fb7b1c4 | ||
|
|
ef44fb4960 | ||
|
|
e63c148fc7 | ||
|
|
c7cb5be231 | ||
|
|
3e13909e59 | ||
|
|
3a5fe31263 | ||
|
|
bb6ecd9c71 | ||
|
|
6e3bc219e0 | ||
|
|
551c2c3abe | ||
|
|
6445b6b3a6 | ||
|
|
d28ad2d7df | ||
|
|
88968da0bd | ||
|
|
50d34aec91 | ||
|
|
13a5b70703 | ||
|
|
9c04196491 | ||
|
|
01c5aabda5 | ||
|
|
3f7ea350dc | ||
|
|
92435c6ab5 | ||
|
|
f6086e4ccd | ||
|
|
99fac4ca56 | ||
|
|
7aa72f6fdb | ||
|
|
49cecf324c | ||
|
|
b88b2c3190 | ||
|
|
ab493af110 | ||
|
|
33888641ec | ||
|
|
299a5110ed | ||
|
|
f508f1d6da | ||
|
|
e9d97ce6b7 | ||
|
|
f4ab586f11 | ||
|
|
a118fd5c07 | ||
|
|
521f04632d | ||
|
|
dd69cabc67 | ||
|
|
2e1189ca65 | ||
|
|
db283cd396 | ||
|
|
f49e537721 | ||
|
|
202f557c71 |
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Documentation
|
||||
url: https://aka.ms/agent-framework
|
||||
about: Check out the official documentation for guides and API reference.
|
||||
- name: Discussions
|
||||
url: https://github.com/microsoft/agent-framework/discussions
|
||||
about: Ask questions and share ideas in GitHub Discussions.
|
||||
about: Ask questions about Agent Framework.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
name: .NET Bug Report
|
||||
description: Report a bug in the Agent Framework .NET SDK
|
||||
title: ".NET: [Bug]: "
|
||||
labels: ["bug", ".NET"]
|
||||
type: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a clear and detailed description of the bug.
|
||||
placeholder: |
|
||||
- What happened?
|
||||
- What did you expect to happen?
|
||||
- Steps to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Code Sample
|
||||
description: If applicable, provide a minimal code sample that demonstrates the issue.
|
||||
placeholder: |
|
||||
```csharp
|
||||
// Your code here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: error-messages
|
||||
attributes:
|
||||
label: Error Messages / Stack Traces
|
||||
description: Include any error messages or stack traces you received.
|
||||
placeholder: |
|
||||
```
|
||||
Paste error messages or stack traces here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-packages
|
||||
attributes:
|
||||
label: Package Versions
|
||||
description: List the Microsoft.Agents.* packages and versions you are using
|
||||
placeholder: "e.g., Microsoft.Agents.AI.Abstractions: 1.0.0, Microsoft.Agents.AI.OpenAI: 1.0.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: dotnet-version
|
||||
attributes:
|
||||
label: .NET Version
|
||||
description: What version of .NET are you using?
|
||||
placeholder: "e.g., .NET 8.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context or screenshots that might be helpful.
|
||||
placeholder: "Any additional information..."
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Feature Request
|
||||
description: Request a new feature for Microsoft Agent Framework
|
||||
title: "[Feature]: "
|
||||
type: feature
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please describe the feature you'd like and why it would be useful.
|
||||
placeholder: |
|
||||
Describe the feature you're requesting:
|
||||
- What problem does it solve?
|
||||
- What would the expected behavior be?
|
||||
- Are there any alternatives you've considered?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Code Sample
|
||||
description: If applicable, provide a code sample showing how you'd like to use this feature.
|
||||
placeholder: |
|
||||
```python
|
||||
# Your code here
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```csharp
|
||||
// Your code here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: language
|
||||
attributes:
|
||||
label: Language/SDK
|
||||
description: Which language/SDK does this feature apply to?
|
||||
options:
|
||||
- Both
|
||||
- .NET
|
||||
- Python
|
||||
- Other / Not Applicable
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,203 +0,0 @@
|
||||
name: Issue Report
|
||||
description: Report a bug, request a feature, or ask a question about Microsoft Agent Framework
|
||||
title: "[Issue]: "
|
||||
labels: ["triage"]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: language
|
||||
attributes:
|
||||
label: Language
|
||||
description: Which language/SDK are you using?
|
||||
options:
|
||||
- .NET
|
||||
- Python
|
||||
- None / Not Applicable
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: issue-type
|
||||
attributes:
|
||||
label: Type of Issue
|
||||
description: What type of issue is this?
|
||||
options:
|
||||
- Bug
|
||||
- Feature Request
|
||||
- Question
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Version Information
|
||||
Please provide the version of the package(s) you are using. Select the relevant packages below.
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### .NET Packages"
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI
|
||||
description: Version of Microsoft.Agents.AI (e.g., 1.0.0)
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-abstractions
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.Abstractions
|
||||
description: Version of Microsoft.Agents.AI.Abstractions
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-openai
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.OpenAI
|
||||
description: Version of Microsoft.Agents.AI.OpenAI
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-azureai
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.AzureAI
|
||||
description: Version of Microsoft.Agents.AI.AzureAI
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-anthropic
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.Anthropic
|
||||
description: Version of Microsoft.Agents.AI.Anthropic
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-hosting
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.Hosting
|
||||
description: Version of Microsoft.Agents.AI.Hosting
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-agents-ai-workflows
|
||||
attributes:
|
||||
label: Microsoft.Agents.AI.Workflows
|
||||
description: Version of Microsoft.Agents.AI.Workflows
|
||||
placeholder: "e.g., 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: dotnet-other-packages
|
||||
attributes:
|
||||
label: Other .NET Packages
|
||||
description: List any other Microsoft.Agents.* packages and versions you are using
|
||||
placeholder: "e.g., Microsoft.Agents.AI.CopilotStudio: 1.0.0, Microsoft.Agents.AI.Purview: 1.0.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Python Packages"
|
||||
|
||||
- type: input
|
||||
id: python-core
|
||||
attributes:
|
||||
label: agent-framework-core
|
||||
description: Version of agent-framework-core
|
||||
placeholder: "e.g., 1.0.0b1"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: python-azure-ai
|
||||
attributes:
|
||||
label: agent-framework-azure-ai
|
||||
description: Version of agent-framework-azure-ai
|
||||
placeholder: "e.g., 1.0.0b1"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: python-anthropic
|
||||
attributes:
|
||||
label: agent-framework-anthropic
|
||||
description: Version of agent-framework-anthropic
|
||||
placeholder: "e.g., 1.0.0b1"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: python-azurefunctions
|
||||
attributes:
|
||||
label: agent-framework-azurefunctions
|
||||
description: Version of agent-framework-azurefunctions
|
||||
placeholder: "e.g., 1.0.0b1"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: python-other-packages
|
||||
attributes:
|
||||
label: Other Python Packages
|
||||
description: List any other agent-framework-* packages and versions you are using
|
||||
placeholder: "e.g., agent-framework-mem0: 1.0.0b1, agent-framework-redis: 1.0.0b1"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "---"
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a clear and detailed description of the issue, feature request, or question.
|
||||
placeholder: |
|
||||
For bugs: Describe what happened, what you expected to happen, and steps to reproduce.
|
||||
For features: Describe the feature you'd like and why it would be useful.
|
||||
For questions: Describe what you're trying to accomplish.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Code Sample
|
||||
description: If applicable, provide a minimal code sample that demonstrates the issue or your use case.
|
||||
placeholder: |
|
||||
```python
|
||||
# Your code here
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```csharp
|
||||
// Your code here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context, screenshots, error messages, or stack traces that might be helpful.
|
||||
placeholder: "Any additional information..."
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Python Bug Report
|
||||
description: Report a bug in the Agent Framework Python SDK
|
||||
title: "Python: [Bug]: "
|
||||
labels: ["bug", "Python"]
|
||||
type: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a clear and detailed description of the bug.
|
||||
placeholder: |
|
||||
- What happened?
|
||||
- What did you expect to happen?
|
||||
- Steps to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Code Sample
|
||||
description: If applicable, provide a minimal code sample that demonstrates the issue.
|
||||
placeholder: |
|
||||
```python
|
||||
# Your code here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: error-messages
|
||||
attributes:
|
||||
label: Error Messages / Stack Traces
|
||||
description: Include any error messages or stack traces you received.
|
||||
placeholder: |
|
||||
```
|
||||
Paste error messages or stack traces here
|
||||
```
|
||||
render: markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: python-packages
|
||||
attributes:
|
||||
label: Package Versions
|
||||
description: List the agent-framework-* packages and versions you are using
|
||||
placeholder: "e.g., agent-framework-core: 1.0.0, agent-framework-azure-ai: 1.0.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: python-version
|
||||
attributes:
|
||||
label: Python Version
|
||||
description: What version of Python are you using?
|
||||
placeholder: "e.g., Python 3.11"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context or screenshots that might be helpful.
|
||||
placeholder: "Any additional information..."
|
||||
validations:
|
||||
required: false
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -29,3 +29,4 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout: 3600
|
||||
interval: 30
|
||||
ignored: CodeQL
|
||||
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
id: azure-functions-setup
|
||||
- name: Test with pytest
|
||||
timeout-minutes: 10
|
||||
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 300 --retries 3 --retry-delay 10
|
||||
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 600 --retries 3 --retry-delay 10
|
||||
working-directory: ./python
|
||||
- name: Test core samples
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -206,6 +206,7 @@ agents.md
|
||||
WARP.md
|
||||
**/memory-bank/
|
||||
**/projectBrief.md
|
||||
**/tmpclaude*
|
||||
|
||||
# Azurite storage emulator files
|
||||
*/__azurite_db_blob__.json
|
||||
@@ -226,3 +227,4 @@ local.settings.json
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
python/dotnet-ref
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
---
|
||||
status: proposed
|
||||
contact: dmytrostruk
|
||||
date: 2025-12-12
|
||||
deciders: dmytrostruk, markwallace-microsoft, eavanvalkenburg, giles17
|
||||
---
|
||||
|
||||
# Create/Get Agent API
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
There is a misalignment between the create/get agent API in the .NET and Python implementations.
|
||||
|
||||
In .NET, the `CreateAIAgent` method can create either a local instance of an agent or a remote instance if the backend provider supports it. For remote agents, once the agent is created, you can retrieve an existing remote agent by using the `GetAIAgent` method. If a backend provider doesn't support remote agents, `CreateAIAgent` just initializes a new local agent instance and `GetAIAgent` is not available. There is also a `BuildAIAgent` method, which is an extension for the `ChatClientBuilder` class from `Microsoft.Extensions.AI`. It builds pipelines of `IChatClient` instances with an `IServiceProvider`. This functionality does not exist in Python, so `BuildAIAgent` is out of scope.
|
||||
|
||||
In Python, there is only one `create_agent` method, which always creates a local instance of the agent. If the backend provider supports remote agents, the remote agent is created only on the first `agent.run()` invocation.
|
||||
|
||||
Below is a short summary of different providers and their APIs in .NET:
|
||||
|
||||
| Package | Method | Behavior | Python support |
|
||||
|---|---|---|---|
|
||||
| Microsoft.Agents.AI | `CreateAIAgent` (based on `IChatClient`) | Creates a local instance of `ChatClientAgent`. | Yes (`create_agent` in `BaseChatClient`). |
|
||||
| Microsoft.Agents.AI.Anthropic | `CreateAIAgent` (based on `IBetaService` and `IAnthropicClient`) | Creates a local instance of `ChatClientAgent`. | Yes (`AnthropicClient` inherits `BaseChatClient`, which exposes `create_agent`). |
|
||||
| Microsoft.Agents.AI.AzureAI (V2) | `GetAIAgent` (based on `AIProjectClient` with `AgentReference`) | Creates a local instance of `ChatClientAgent`. | Partial (Python uses `create_agent` from `BaseChatClient`). |
|
||||
| Microsoft.Agents.AI.AzureAI (V2) | `GetAIAgent`/`GetAIAgentAsync` (with `Name`/`ChatClientAgentOptions`) | Fetches `AgentRecord` via HTTP, then creates a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.AzureAI (V2) | `CreateAIAgent`/`CreateAIAgentAsync` (based on `AIProjectClient`) | Creates a remote agent first, then wraps it into a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.AzureAI.Persistent (V1) | `GetAIAgent` (based on `PersistentAgentsClient` with `PersistentAgent`) | Creates a local instance of `ChatClientAgent`. | Partial (Python uses `create_agent` from `BaseChatClient`). |
|
||||
| Microsoft.Agents.AI.AzureAI.Persistent (V1) | `GetAIAgent`/`GetAIAgentAsync` (with `AgentId`) | Fetches `PersistentAgent` via HTTP, then creates a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.AzureAI.Persistent (V1) | `CreateAIAgent`/`CreateAIAgentAsync` | Creates a remote agent first, then wraps it into a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.OpenAI | `GetAIAgent` (based on `AssistantClient` with `Assistant`) | Creates a local instance of `ChatClientAgent`. | Partial (Python uses `create_agent` from `BaseChatClient`). |
|
||||
| Microsoft.Agents.AI.OpenAI | `GetAIAgent`/`GetAIAgentAsync` (with `AgentId`) | Fetches `Assistant` via HTTP, then creates a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.OpenAI | `CreateAIAgent`/`CreateAIAgentAsync` (based on `AssistantClient`) | Creates a remote agent first, then wraps it into a local `ChatClientAgent` instance. | No |
|
||||
| Microsoft.Agents.AI.OpenAI | `CreateAIAgent` (based on `ChatClient`) | Creates a local instance of `ChatClientAgent`. | Yes (`create_agent` in `BaseChatClient`). |
|
||||
| Microsoft.Agents.AI.OpenAI | `CreateAIAgent` (based on `OpenAIResponseClient`) | Creates a local instance of `ChatClientAgent`. | Yes (`create_agent` in `BaseChatClient`). |
|
||||
|
||||
Another difference between Python and .NET implementation is that in .NET `CreateAIAgent`/`GetAIAgent` methods are implemented as extension methods based on underlying SDK client, like `AIProjectClient` from Azure AI or `AssistantClient` from OpenAI:
|
||||
|
||||
```csharp
|
||||
// Definition
|
||||
public static ChatClientAgent CreateAIAgent(
|
||||
this AIProjectClient aiProjectClient,
|
||||
string name,
|
||||
string model,
|
||||
string instructions,
|
||||
string? description = null,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{ }
|
||||
|
||||
// Usage
|
||||
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); // Initialization of underlying SDK client
|
||||
|
||||
var newAgent = await aiProjectClient.CreateAIAgentAsync(name: AgentName, model: deploymentName, instructions: AgentInstructions, tools: [tool]); // ChatClientAgent creation from underlying SDK client
|
||||
|
||||
// Alternative usage (same as extension method, just explicit syntax)
|
||||
var newAgent = await AzureAIProjectChatClientExtensions.CreateAIAgentAsync(
|
||||
aiProjectClient,
|
||||
name: AgentName,
|
||||
model: deploymentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [tool]);
|
||||
```
|
||||
|
||||
Python doesn't support extension methods. Currently `create_agent` method is defined on `BaseChatClient`, but this method only creates a local instance of `ChatAgent` and it can't create remote agents for providers that support it for a couple of reasons:
|
||||
|
||||
- It's defined as non-async.
|
||||
- `BaseChatClient` implementation is stateful for providers like Azure AI or OpenAI Assistants. The implementation stores agent/assistant metadata like `AgentId` and `AgentName`, so currently it's not possible to create different instances of `ChatAgent` from a single `BaseChatClient` in case if the implementation is stateful.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- API should be aligned between .NET and Python.
|
||||
- API should be intuitive and consistent between backend providers in .NET and Python.
|
||||
|
||||
## Considered Options
|
||||
|
||||
Add missing implementations on the Python side. This should include the following:
|
||||
|
||||
### agent-framework-azure-ai (both V1 and V2)
|
||||
|
||||
- Add a `get_agent` method that accepts an underlying SDK agent instance and creates a local instance of `ChatAgent`.
|
||||
- Add a `get_agent` method that accepts an agent identifier, performs an additional HTTP request to fetch agent data, and then creates a local instance of `ChatAgent`.
|
||||
- Override the `create_agent` method from `BaseChatClient` to create a remote agent instance and wrap it into a local `ChatAgent`.
|
||||
|
||||
.NET:
|
||||
|
||||
```csharp
|
||||
var agent1 = new AIProjectClient(...).GetAIAgent(agentInstanceFromSdkType); // Creates a local ChatClientAgent instance from Azure.AI.Projects.OpenAI.AgentReference
|
||||
var agent2 = new AIProjectClient(...).GetAIAgent(agentName); // Fetches agent data, creates a local ChatClientAgent instance
|
||||
var agent3 = new AIProjectClient(...).CreateAIAgent(...); // Creates a remote agent, returns a local ChatClientAgent instance
|
||||
```
|
||||
|
||||
### agent-framework-core (OpenAI Assistants)
|
||||
|
||||
- Add a `get_agent` method that accepts an underlying SDK agent instance and creates a local instance of `ChatAgent`.
|
||||
- Add a `get_agent` method that accepts an agent name, performs an additional HTTP request to fetch agent data, and then creates a local instance of `ChatAgent`.
|
||||
- Override the `create_agent` method from `BaseChatClient` to create a remote agent instance and wrap it into a local `ChatAgent`.
|
||||
|
||||
.NET:
|
||||
|
||||
```csharp
|
||||
var agent1 = new AssistantClient(...).GetAIAgent(agentInstanceFromSdkType); // Creates a local ChatClientAgent instance from OpenAI.Assistants.Assistant
|
||||
var agent2 = new AssistantClient(...).GetAIAgent(agentId); // Fetches agent data, creates a local ChatClientAgent instance
|
||||
var agent3 = new AssistantClient(...).CreateAIAgent(...); // Creates a remote agent, returns a local ChatClientAgent instance
|
||||
```
|
||||
|
||||
### Possible Python implementations
|
||||
|
||||
Methods like `create_agent` and `get_agent` should be implemented separately or defined on some stateless component that will allow to create multiple agents from the same instance/place.
|
||||
|
||||
Possible options:
|
||||
|
||||
#### Option 1: Module-level functions
|
||||
|
||||
Implement free functions in the provider package that accept the underlying SDK client as the first argument (similar to .NET extension methods, but expressed in Python).
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
from agent_framework.azure import create_agent, get_agent
|
||||
|
||||
ai_project_client = AIProjectClient(...)
|
||||
|
||||
# Creates a remote agent first, then returns a local ChatAgent wrapper
|
||||
created_agent = await create_agent(
|
||||
ai_project_client,
|
||||
name="",
|
||||
instructions="",
|
||||
tools=[tool],
|
||||
)
|
||||
|
||||
# Gets an existing remote agent and returns a local ChatAgent wrapper
|
||||
first_agent = await get_agent(ai_project_client, agent_id=agent_id)
|
||||
|
||||
# Wraps an SDK agent instance (no extra HTTP call)
|
||||
second_agent = get_agent(ai_project_client, agent_reference)
|
||||
```
|
||||
|
||||
Pros:
|
||||
|
||||
- Naturally supports async `create_agent` / `get_agent`.
|
||||
- Supports multiple agents per SDK client.
|
||||
- Closest conceptual match to .NET extension methods while staying Pythonic.
|
||||
|
||||
Cons:
|
||||
|
||||
- Discoverability is lower (users need to know where the functions live).
|
||||
- Verbose when creating multiple agents (client must be passed every time):
|
||||
|
||||
```python
|
||||
agent1 = await azure_agents.create_agent(client, name="Agent1", ...)
|
||||
agent2 = await azure_agents.create_agent(client, name="Agent2", ...)
|
||||
```
|
||||
|
||||
#### Option 2: Provider object
|
||||
|
||||
Introduce a dedicated provider type that is constructed from the underlying SDK client, and exposes async `create_agent` / `get_agent` methods.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
from agent_framework.azure import AzureAIAgentProvider
|
||||
|
||||
ai_project_client = AIProjectClient(...)
|
||||
provider = AzureAIAgentProvider(ai_project_client)
|
||||
|
||||
agent = await provider.create_agent(
|
||||
name="",
|
||||
instructions="",
|
||||
tools=[tool],
|
||||
)
|
||||
|
||||
agent = await provider.get_agent(agent_id=agent_id)
|
||||
agent = provider.get_agent(agent_reference=agent_reference)
|
||||
```
|
||||
|
||||
Pros:
|
||||
|
||||
- High discoverability and clear grouping of related behavior.
|
||||
- Keeps SDK clients unchanged and supports multiple agents per SDK client.
|
||||
- Concise when creating multiple agents (client passed once):
|
||||
|
||||
```python
|
||||
provider = AzureAIAgentProvider(ai_project_client)
|
||||
agent1 = await provider.create_agent(name="Agent1", ...)
|
||||
agent2 = await provider.create_agent(name="Agent2", ...)
|
||||
```
|
||||
|
||||
Cons:
|
||||
|
||||
- Adds a new public concept/type for users to learn.
|
||||
|
||||
#### Option 3: Inheritance (SDK client subclass)
|
||||
|
||||
Create a subclass of the underlying SDK client and add `create_agent` / `get_agent` methods.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
class ExtendedAIProjectClient(AIProjectClient):
|
||||
async def create_agent(self, *, name: str, model: str, instructions: str, **kwargs) -> ChatAgent:
|
||||
...
|
||||
|
||||
async def get_agent(self, *, agent_id: str | None = None, sdk_agent=None, **kwargs) -> ChatAgent:
|
||||
...
|
||||
|
||||
client = ExtendedAIProjectClient(...)
|
||||
agent = await client.create_agent(name="", instructions="")
|
||||
```
|
||||
|
||||
Pros:
|
||||
|
||||
- Discoverable and ergonomic call sites.
|
||||
- Mirrors the .NET “methods on the client” feeling.
|
||||
|
||||
Cons:
|
||||
|
||||
- Many SDK clients are not designed for inheritance; SDK upgrades can break subclasses.
|
||||
- Users must opt into subclass everywhere.
|
||||
- Typing/initialization can be tricky if the SDK client has non-trivial constructors.
|
||||
|
||||
#### Option 4: Monkey patching
|
||||
|
||||
Attach `create_agent` / `get_agent` methods to an SDK client class (or instance) at runtime.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
def _create_agent(self, *, name: str, model: str, instructions: str, **kwargs) -> ChatAgent:
|
||||
...
|
||||
|
||||
AIProjectClient.create_agent = _create_agent # monkey patch
|
||||
```
|
||||
|
||||
Pros:
|
||||
|
||||
- Produces “extension method-like” call sites without wrappers or subclasses.
|
||||
|
||||
Cons:
|
||||
|
||||
- Fragile across SDK updates and difficult to type-check.
|
||||
- Surprising behavior (global side effects), potential conflicts across packages.
|
||||
- Harder to support/debug, especially in larger apps and test suites.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Implement `create_agent`/`get_agent`/`as_agent` API via **Option 2: Provider object**.
|
||||
|
||||
### Rationale
|
||||
|
||||
| Aspect | Option 1 (Functions) | Option 2 (Provider) |
|
||||
|--------|----------------------|---------------------|
|
||||
| Multiple implementations | One package may contain V1, V2, and other agent types. Function names like `create_agent` become ambiguous - which agent type does it create? | Each provider class is explicit: `AzureAIAgentsProvider` vs `AzureAIProjectAgentProvider` |
|
||||
| Discoverability | Users must know to import specific functions from the package | IDE autocomplete on provider instance shows all available methods |
|
||||
| Client reuse | SDK client must be passed to every function call: `create_agent(client, ...)`, `get_agent(client, ...)` | SDK client passed once at construction: `provider = Provider(client)` |
|
||||
|
||||
**Option 1 example:**
|
||||
```python
|
||||
from agent_framework.azure import create_agent, get_agent
|
||||
agent1 = await create_agent(client, name="Agent1", ...) # Which agent type, V1 or V2?
|
||||
agent2 = await create_agent(client, name="Agent2", ...) # Repetitive client passing
|
||||
```
|
||||
|
||||
**Option 2 example:**
|
||||
```python
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
provider = AzureAIProjectAgentProvider(client) # Clear which service, client passed once
|
||||
agent1 = await provider.create_agent(name="Agent1", ...)
|
||||
agent2 = await provider.create_agent(name="Agent2", ...)
|
||||
```
|
||||
|
||||
### Method Naming
|
||||
|
||||
| Operation | Python | .NET | Async |
|
||||
|-----------|--------|------|-------|
|
||||
| Create on service | `create_agent()` | `CreateAIAgent()` | Yes |
|
||||
| Get from service | `get_agent(id=...)` | `GetAIAgent(agentId)` | Yes |
|
||||
| Wrap SDK object | `as_agent(reference)` | `AsAIAgent(agentInstance)` | No |
|
||||
|
||||
The method names (`create_agent`, `get_agent`) do not explicitly mention "service" or "remote" because:
|
||||
- In Python, the provider class name explicitly identifies the service (`AzureAIAgentsProvider`, `OpenAIAssistantProvider`), making additional qualifiers in method names redundant.
|
||||
- In .NET, these are extension methods on `AIProjectClient` or `AssistantClient`, which already imply service operations.
|
||||
|
||||
### Provider Class Naming
|
||||
|
||||
| Package | Provider Class | SDK Client | Service |
|
||||
|---------|---------------|------------|---------|
|
||||
| `agent_framework.azure` | `AzureAIProjectAgentProvider` | `AIProjectClient` | Azure AI Agent Service, based on Responses API (V2) |
|
||||
| `agent_framework.azure` | `AzureAIAgentsProvider` | `AgentsClient` | Azure AI Agent Service (V1) |
|
||||
| `agent_framework.openai` | `OpenAIAssistantProvider` | `AsyncOpenAI` | OpenAI Assistants API |
|
||||
|
||||
> **Note:** Azure AI naming is temporary. Final naming will be updated according to Azure AI / Microsoft Foundry renaming decisions.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Azure AI Agent Service V2 (based on Responses API)
|
||||
|
||||
```python
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.ai.projects import AIProjectClient
|
||||
|
||||
client = AIProjectClient(endpoint, credential)
|
||||
provider = AzureAIProjectAgentProvider(client)
|
||||
|
||||
# Create new agent on service
|
||||
agent = await provider.create_agent(name="MyAgent", model="gpt-4", instructions="...")
|
||||
|
||||
# Get existing agent by name
|
||||
agent = await provider.get_agent(agent_name="MyAgent")
|
||||
|
||||
# Wrap already-fetched SDK object (no HTTP calls)
|
||||
agent_ref = await client.agents.get("MyAgent")
|
||||
agent = provider.as_agent(agent_ref)
|
||||
```
|
||||
|
||||
#### Azure AI Persistent Agents V1
|
||||
|
||||
```python
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.ai.agents import AgentsClient
|
||||
|
||||
client = AgentsClient(endpoint, credential)
|
||||
provider = AzureAIAgentsProvider(client)
|
||||
|
||||
agent = await provider.create_agent(name="MyAgent", model="gpt-4", instructions="...")
|
||||
agent = await provider.get_agent(agent_id="persistent-agent-456")
|
||||
agent = provider.as_agent(persistent_agent)
|
||||
```
|
||||
|
||||
#### OpenAI Assistants
|
||||
|
||||
```python
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI()
|
||||
provider = OpenAIAssistantProvider(client)
|
||||
|
||||
agent = await provider.create_agent(name="MyAssistant", model="gpt-4", instructions="...")
|
||||
agent = await provider.get_agent(assistant_id="asst_123")
|
||||
agent = provider.as_agent(assistant)
|
||||
```
|
||||
|
||||
#### Local-Only Agents (No Provider)
|
||||
|
||||
Current method `create_agent` (python) / `CreateAIAgent` (.NET) can be renamed to `as_agent` (python) / `AsAIAgent` (.NET) to emphasize the conversion logic rather than creation/initialization logic and to avoid collision with `create_agent` method for remote calls.
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
# Convert chat client to ChatAgent (no remote service involved)
|
||||
client = OpenAIChatClient(model="gpt-4")
|
||||
agent = client.as_agent(name="LocalAgent", instructions="...") # instead of create_agent
|
||||
```
|
||||
|
||||
### Adding New Agent Types
|
||||
|
||||
Python:
|
||||
|
||||
1. Create provider class in appropriate package.
|
||||
2. Implement `create_agent`, `get_agent`, `as_agent` as applicable.
|
||||
|
||||
.NET:
|
||||
|
||||
1. Create static class for extension methods.
|
||||
2. Implement `CreateAIAgentAsync`, `GetAIAgentAsync`, `AsAIAgent` as applicable.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
# These are optional elements. Feel free to remove any of them.
|
||||
status: proposed
|
||||
contact: eavanvalkenburg
|
||||
date: 2026-01-08
|
||||
deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, johanst, brettcannon
|
||||
consulted: taochenosu, moonbox3, dmytrostruk, giles17
|
||||
---
|
||||
|
||||
# Leveraging TypedDict and Generic Options in Python Chat Clients
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The Agent Framework Python SDK provides multiple chat client implementations for different providers (OpenAI, Anthropic, Azure AI, Bedrock, Ollama, etc.). Each provider has unique configuration options beyond the common parameters defined in `ChatOptions`. Currently, developers using these clients lack type safety and IDE autocompletion for provider-specific options, leading to runtime errors and a poor developer experience.
|
||||
|
||||
How can we provide type-safe, discoverable options for each chat client while maintaining a consistent API across all implementations?
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- **Type Safety**: Developers should get compile-time/static analysis errors when using invalid options
|
||||
- **IDE Support**: Full autocompletion and inline documentation for all available options
|
||||
- **Extensibility**: Users should be able to define custom options that extend provider-specific options
|
||||
- **Consistency**: All chat clients should follow the same pattern for options handling
|
||||
- **Provider Flexibility**: Each provider can expose its unique options without affecting the common interface
|
||||
|
||||
## Considered Options
|
||||
|
||||
- **Option 1: Status Quo - Class `ChatOptions` with `**kwargs`**
|
||||
- **Option 2: TypedDict with Generic Type Parameters**
|
||||
|
||||
### Option 1: Status Quo - Class `ChatOptions` with `**kwargs`
|
||||
|
||||
The current approach uses a base `ChatOptions` Class with common parameters, and provider-specific options are passed via `**kwargs` or loosely typed dictionaries.
|
||||
|
||||
```python
|
||||
# Current usage - no type safety for provider-specific options
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
top_k=40,
|
||||
random=42, # No validation
|
||||
)
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Simple implementation
|
||||
- Maximum flexibility
|
||||
|
||||
**Cons:**
|
||||
- No type checking for provider-specific options
|
||||
- No IDE autocompletion for available options
|
||||
- Runtime errors for typos or invalid options
|
||||
- Documentation must be consulted for each provider
|
||||
|
||||
### Option 2: TypedDict with Generic Type Parameters (Chosen)
|
||||
|
||||
Each chat client is parameterized with a TypeVar bound to a provider-specific `TypedDict` that extends `ChatOptions`. This enables full type safety and IDE support.
|
||||
|
||||
```python
|
||||
# Provider-specific TypedDict
|
||||
class AnthropicChatOptions(ChatOptions, total=False):
|
||||
"""Anthropic-specific chat options."""
|
||||
top_k: int
|
||||
thinking: ThinkingConfig
|
||||
# ... other Anthropic-specific options
|
||||
|
||||
# Generic chat client
|
||||
class AnthropicChatClient(ChatClientBase[TAnthropicChatOptions]):
|
||||
...
|
||||
|
||||
client = AnthropicChatClient(...)
|
||||
|
||||
# Usage with full type safety
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
options={
|
||||
"temperature": 0.7,
|
||||
"top_k": 40,
|
||||
"random": 42, # fails type checking and IDE would flag this
|
||||
}
|
||||
)
|
||||
|
||||
# Users can extend for custom options
|
||||
class MyAnthropicOptions(AnthropicChatOptions, total=False):
|
||||
custom_field: str
|
||||
|
||||
|
||||
client = AnthropicChatClient[MyAnthropicOptions](...)
|
||||
|
||||
# Usage of custom options with full type safety
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
options={
|
||||
"temperature": 0.7,
|
||||
"top_k": 40,
|
||||
"custom_field": "value",
|
||||
}
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Full type safety with static analysis
|
||||
- IDE autocompletion for all options
|
||||
- Compile-time error detection
|
||||
- Self-documenting through type hints
|
||||
- Users can extend options for their specific needs or advances in models
|
||||
|
||||
**Cons:**
|
||||
- More complex implementation
|
||||
- Some type: ignore comments needed for TypedDict field overrides
|
||||
- Minor: Requires TypeVar with default (Python 3.13+ or typing_extensions)
|
||||
|
||||
> [NOTE!]
|
||||
> In .NET this is already achieved through overloads on the `GetResponseAsync` method for each provider-specific options class, e.g., `AnthropicChatOptions`, `OpenAIChatOptions`, etc. So this does not apply to .NET.
|
||||
|
||||
### Implementation Details
|
||||
|
||||
1. **Base Protocol**: `ChatClientProtocol[TOptions]` is generic over options type, with default set to `ChatOptions` (the new TypedDict)
|
||||
2. **Provider TypedDicts**: Each provider defines its options extending `ChatOptions`
|
||||
They can even override fields with type=None to indicate they are not supported.
|
||||
3. **TypeVar Pattern**: `TProviderOptions = TypeVar("TProviderOptions", bound=TypedDict, default=ProviderChatOptions, contravariant=True)`
|
||||
4. **Option Translation**: Common options are kept in place,and explicitly documented in the Options class how they are used. (e.g., `user` → `metadata.user_id`) in `_prepare_options` (for Anthropic) to preserve easy use of common options.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: **"Option 2: TypedDict with Generic Type Parameters"**, because it provides full type safety, excellent IDE support with autocompletion, and allows users to extend provider-specific options for their use cases. Extended this Generic to ChatAgents in order to also properly type the options used in agent construction and run methods.
|
||||
|
||||
See [typed_options.py](../../python/samples/getting_started/chat_client/typed_options.py) for a complete example demonstrating the usage of typed options with custom extensions.
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
status: Accepted
|
||||
contact: eavanvalkenburg
|
||||
date: 2026-01-06
|
||||
deciders: markwallace-microsoft, dmytrostruk, taochenosu, alliscode, moonbox3, sphenry
|
||||
consulted: sergeymenshykh, rbarreto, dmytrostruk, westey-m
|
||||
informed:
|
||||
---
|
||||
|
||||
# Simplify Python Get Response API into a single method
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Currently chat clients must implement two separate methods to get responses, one for streaming and one for non-streaming. This adds complexity to the client implementations and increases the maintenance burden. This was likely done because the .NET version cannot do proper typing with a single method, in Python this is possible and this for instance is also how the OpenAI python client works, this would then also make it simpler to work with the Python version because there is only one method to learn about instead of two.
|
||||
|
||||
## Implications of this change
|
||||
|
||||
### Current Architecture Overview
|
||||
|
||||
The current design has **two separate methods** at each layer:
|
||||
|
||||
| Layer | Non-streaming | Streaming |
|
||||
|-------|---------------|-----------|
|
||||
| **Protocol** | `get_response()` → `ChatResponse` | `get_streaming_response()` → `AsyncIterable[ChatResponseUpdate]` |
|
||||
| **BaseChatClient** | `get_response()` (public) | `get_streaming_response()` (public) |
|
||||
| **Implementation** | `_inner_get_response()` (private) | `_inner_get_streaming_response()` (private) |
|
||||
|
||||
### Key Usage Areas Identified
|
||||
|
||||
#### 1. **ChatAgent** (_agents.py)
|
||||
- `run()` → calls `self.chat_client.get_response()`
|
||||
- `run_stream()` → calls `self.chat_client.get_streaming_response()`
|
||||
|
||||
These are parallel methods on the agent, so consolidating the client methods would **not break** the agent API. You could keep `agent.run()` and `agent.run_stream()` unchanged while internally calling `get_response(stream=True/False)`.
|
||||
|
||||
#### 2. **Function Invocation Decorator** (_tools.py)
|
||||
This is **the most impacted area**. Currently:
|
||||
- `_handle_function_calls_response()` decorates `get_response`
|
||||
- `_handle_function_calls_streaming_response()` decorates `get_streaming_response`
|
||||
- The `use_function_invocation` class decorator wraps **both methods separately**
|
||||
|
||||
**Impact**: The decorator logic is almost identical (~200 lines each) with small differences:
|
||||
- Non-streaming collects response, returns it
|
||||
- Streaming yields updates, returns async iterable
|
||||
|
||||
With a unified method, you'd need **one decorator** that:
|
||||
- Checks the `stream` parameter
|
||||
- Uses `@overload` to determine return type
|
||||
- Handles both paths with conditional logic
|
||||
- The new decorator could be applied just on the method, instead of the whole class.
|
||||
|
||||
This would **reduce code duplication** but add complexity to a single function.
|
||||
|
||||
#### 3. **Observability/Instrumentation** (observability.py)
|
||||
Same pattern as function invocation:
|
||||
- `_trace_get_response()` wraps `get_response`
|
||||
- `_trace_get_streaming_response()` wraps `get_streaming_response`
|
||||
- `use_instrumentation` decorator applies both
|
||||
|
||||
**Impact**: Would need consolidation into a single tracing wrapper.
|
||||
|
||||
#### 4. **Chat Middleware** (_middleware.py)
|
||||
The `use_chat_middleware` decorator also wraps both methods separately with similar logic.
|
||||
|
||||
#### 5. **AG-UI Client** (_client.py)
|
||||
Wraps both methods to unwrap server function calls:
|
||||
```python
|
||||
original_get_streaming_response = chat_client.get_streaming_response
|
||||
original_get_response = chat_client.get_response
|
||||
```
|
||||
|
||||
#### 6. **Provider Implementations** (all subpackages)
|
||||
All subclasses implement both `_inner_*` methods, except:
|
||||
- OpenAI Assistants Client (and similar clients, such as Foundry Agents V1) - it implements `_inner_get_response` by calling `_inner_get_streaming_response`
|
||||
|
||||
### Implications of Consolidation
|
||||
|
||||
| Aspect | Impact |
|
||||
|--------|--------|
|
||||
| **Type Safety** | Overloads work well: `@overload` with `Literal[True]` → `AsyncIterable`, `Literal[False]` → `ChatResponse`. Runtime return type based on `stream` param. |
|
||||
| **Breaking Change** | **Major breaking change** for anyone implementing custom chat clients. They'd need to update from 2 methods to 1 (or 2 inner methods to 1). |
|
||||
| **Decorator Complexity** | All 3 decorator systems (function invocation, middleware, observability) would need refactoring to handle both paths in one wrapper. |
|
||||
| **Code Reduction** | Significant reduction in _tools.py (~200 lines of near-duplicate code) and other decorators. |
|
||||
| **Samples/Tests** | Many samples call `get_streaming_response()` directly - would need updates. |
|
||||
| **Protocol Simplification** | `ChatClientProtocol` goes from 2 methods + 1 property to 1 method + 1 property. |
|
||||
|
||||
### Recommendation
|
||||
|
||||
The consolidation makes sense architecturally, but consider:
|
||||
|
||||
1. **The overload pattern with `stream: bool`** works well in Python typing:
|
||||
```python
|
||||
@overload
|
||||
async def get_response(self, messages, *, stream: Literal[True] = True, ...) -> AsyncIterable[ChatResponseUpdate]: ...
|
||||
@overload
|
||||
async def get_response(self, messages, *, stream: Literal[False] = False, ...) -> ChatResponse: ...
|
||||
```
|
||||
|
||||
2. **The decorator complexity** is the biggest concern. The current approach of separate decorators for separate methods is cleaner than conditional logic inside one wrapper.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Reduce code needed to implement a Chat Client, simplify the public API for chat clients
|
||||
- Reduce code duplication in decorators and middleware
|
||||
- Maintain type safety and clarity in method signatures
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. Status quo: Keep separate methods for streaming and non-streaming
|
||||
2. Consolidate into a single `get_response` method with a `stream` parameter
|
||||
3. Option 2 plus merging `agent.run` and `agent.run_stream` into a single method with a `stream` parameter as well
|
||||
|
||||
## Option 1: Status Quo
|
||||
- Good: Clear separation of streaming vs non-streaming logic
|
||||
- Good: Aligned with .NET design, although it is already `run` for Python and `RunAsync` for .NET
|
||||
- Bad: Code duplication in decorators and middleware
|
||||
- Bad: More complex client implementations
|
||||
|
||||
## Option 2: Consolidate into Single Method
|
||||
- Good: Simplified public API for chat clients
|
||||
- Good: Reduced code duplication in decorators
|
||||
- Good: Smaller API footprint for users to get familiar with
|
||||
- Good: People using OpenAI directly already expect this pattern
|
||||
- Bad: Increased complexity in decorators and middleware
|
||||
- Bad: Less alignment with .NET design (`get_response(stream=True)` vs `GetStreamingResponseAsync`)
|
||||
|
||||
## Option 3: Consolidate + Merge Agent and Workflow Methods
|
||||
- Good: Further simplifies agent and workflow implementation
|
||||
- Good: Single method for all chat interactions
|
||||
- Good: Smaller API footprint for users to get familiar with
|
||||
- Good: People using OpenAI directly already expect this pattern
|
||||
- Good: Workflows internally already use a single method (_run_workflow_with_tracing), so would eliminate public API duplication as well, with hardly any code changes
|
||||
- Bad: More breaking changes for agent users
|
||||
- Bad: Increased complexity in agent implementation
|
||||
- Bad: More extensive misalignment with .NET design (`run(stream=True)` vs `RunStreamingAsync` in addition to `get_response` change)
|
||||
|
||||
## Misc
|
||||
|
||||
Smaller questions to consider:
|
||||
- Should default be `stream=False` or `stream=True`? (Current is False)
|
||||
- Default to `False` makes it simpler for new users, as non-streaming is easier to handle.
|
||||
- Default to `False` aligns with existing behavior.
|
||||
- Streaming tends to be faster, so defaulting to `True` could improve performance for common use cases.
|
||||
- Should this differ between ChatClient, Agent and Workflows? (e.g., Agent and Workflow defaults to streaming, ChatClient to non-streaming)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen Option: **Option 3: Consolidate + Merge Agent and Workflow Methods**
|
||||
|
||||
Since this is the most pythonic option and it reduces the API surface and code duplication the most, we will go with this option.
|
||||
We will keep the default of `stream=False` for all methods to maintain backward compatibility and simplicity for new users.
|
||||
|
||||
# Appendix
|
||||
## Code Samples for Consolidated Method
|
||||
|
||||
### Python - Option 3: Direct ChatClient + Agent with Single Method
|
||||
|
||||
```python
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Example 1: Direct ChatClient usage with single method
|
||||
client = OpenAIChatClient()
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
|
||||
# Non-streaming usage
|
||||
print(f"User: {message}")
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response.text}")
|
||||
|
||||
# Streaming usage - same method, different parameter
|
||||
print(f"\nUser: {message}")
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="")
|
||||
print("")
|
||||
|
||||
# Example 2: Agent usage with single method
|
||||
agent = ChatAgent(
|
||||
chat_client=client,
|
||||
tools=get_weather,
|
||||
name="WeatherAgent",
|
||||
instructions="You are a weather assistant.",
|
||||
)
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
# Non-streaming agent
|
||||
print(f"\nUser: {message}")
|
||||
result = await agent.run(message, thread=thread) # default would be stream=False
|
||||
print(f"{agent.name}: {result.text}")
|
||||
|
||||
# Streaming agent - same method, different parameter
|
||||
print(f"\nUser: {message}")
|
||||
print(f"{agent.name}: ", end="")
|
||||
async for update in agent.run(message, thread=thread, stream=True):
|
||||
if update.text:
|
||||
print(update.text, end="")
|
||||
print("")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### .NET - Current pattern for comparison
|
||||
|
||||
```csharp
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
instructions: "You are good at telling jokes about pirates.",
|
||||
name: "PirateJoker");
|
||||
|
||||
// Non-streaming: Returns a string directly
|
||||
Console.WriteLine("=== Non-streaming ===");
|
||||
string result = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Streaming: Returns IAsyncEnumerable<AgentUpdate>
|
||||
Console.WriteLine("\n=== Streaming ===");
|
||||
await foreach (AgentUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Anthropic" Version="12.0.0" />
|
||||
<PackageVersion Include="Anthropic" Version="12.0.1" />
|
||||
<PackageVersion Include="Anthropic.Foundry" Version="0.1.0" />
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
|
||||
@@ -26,7 +26,7 @@
|
||||
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
<!-- Google Gemini -->
|
||||
<PackageVersion Include="Google.GenAI" Version="0.6.0" />
|
||||
<PackageVersion Include="Google.GenAI" Version="0.11.0" />
|
||||
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
|
||||
<!-- Microsoft.Azure.* -->
|
||||
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
|
||||
@@ -100,7 +100,7 @@
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.3" />
|
||||
<!-- Inference SDKs -->
|
||||
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.5" />
|
||||
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.5.1" />
|
||||
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
|
||||
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
|
||||
<PackageVersion Include="OpenAI" Version="2.8.0" />
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
|
||||
|
||||
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetOpenAIResponseClient(deploymentName)
|
||||
.CreateAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
.AsAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251219.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251219.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251219.1</GitTag>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260108.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260108.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.260108.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -29,7 +29,7 @@ internal sealed class HostClientAgent
|
||||
// Create the agent that uses the remote agents as tools
|
||||
this.Agent = new OpenAIClient(new ApiKeyCredential(apiKey))
|
||||
.GetChatClient(modelId)
|
||||
.CreateAIAgent(instructions: "You specialize in handling queries for users and using your tools to provide answers.", name: "HostClient", tools: tools);
|
||||
.AsAIAgent(instructions: "You specialize in handling queries for users and using your tools to provide answers.", name: "HostClient", tools: tools);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ public static class Program
|
||||
// Create the Host agent
|
||||
var hostAgent = new HostClientAgent(loggerFactory);
|
||||
await hostAgent.InitializeAgentAsync(modelId, apiKey, agentUrls!.Split(";"));
|
||||
AgentThread thread = hostAgent.Agent!.GetNewThread();
|
||||
AgentThread thread = await hostAgent.Agent!.GetNewThreadAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
|
||||
@@ -35,7 +35,7 @@ internal static class HostAgentFactory
|
||||
{
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(instructions, name, tools: tools);
|
||||
.AsAIAgent(instructions, name, tools: tools);
|
||||
|
||||
AgentCard agentCard = agentType.ToUpperInvariant() switch
|
||||
{
|
||||
|
||||
@@ -83,12 +83,12 @@ public static class Program
|
||||
serverUrl,
|
||||
jsonSerializerOptions: AGUIClientSerializerContext.Default.Options);
|
||||
|
||||
AIAgent agent = chatClient.CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent",
|
||||
tools: [changeBackground, readClientClimateSensors]);
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync(cancellationToken);
|
||||
List<ChatMessage> messages = [new(ChatRole.System, "You are a helpful assistant.")];
|
||||
try
|
||||
{
|
||||
@@ -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"),
|
||||
|
||||
@@ -33,7 +33,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
return chatClient.AsIChatClient().CreateAIAgent(
|
||||
return chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "AgenticChat",
|
||||
description: "A simple chat agent using Azure OpenAI");
|
||||
}
|
||||
@@ -42,7 +42,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
return chatClient.AsIChatClient().CreateAIAgent(
|
||||
return chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "BackendToolRenderer",
|
||||
description: "An agent that can render backend tools using Azure OpenAI",
|
||||
tools: [AIFunctionFactory.Create(
|
||||
@@ -56,7 +56,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
return chatClient.AsIChatClient().CreateAIAgent(
|
||||
return chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "HumanInTheLoopAgent",
|
||||
description: "An agent that involves human feedback in its decision-making process using Azure OpenAI");
|
||||
}
|
||||
@@ -65,7 +65,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
return chatClient.AsIChatClient().CreateAIAgent(
|
||||
return chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "ToolBasedGenerativeUIAgent",
|
||||
description: "An agent that uses tools to generate user interfaces using Azure OpenAI");
|
||||
}
|
||||
@@ -73,7 +73,7 @@ internal static class ChatClientAgentFactory
|
||||
public static AIAgent CreateAgenticUI(JsonSerializerOptions options)
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(new ChatClientAgentOptions
|
||||
var baseAgent = chatClient.AsIChatClient().AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "AgenticUIAgent",
|
||||
Description = "An agent that generates agentic user interfaces using Azure OpenAI",
|
||||
@@ -116,7 +116,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
var baseAgent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "SharedStateAgent",
|
||||
description: "An agent that demonstrates shared state patterns using Azure OpenAI");
|
||||
|
||||
@@ -127,7 +127,7 @@ internal static class ChatClientAgentFactory
|
||||
{
|
||||
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
|
||||
|
||||
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(new ChatClientAgentOptions
|
||||
var baseAgent = chatClient.AsIChatClient().AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = "PredictiveStateUpdatesAgent",
|
||||
Description = "An agent that demonstrates predictive state updates using Azure OpenAI",
|
||||
|
||||
+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")]
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ var agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
tools: [
|
||||
AIFunctionFactory.Create(
|
||||
|
||||
@@ -119,7 +119,7 @@ The `AGUIServer` uses the `MapAGUI` extension method to expose an agent through
|
||||
```csharp
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(
|
||||
.AsAIAgent(
|
||||
instructions: "You are a helpful assistant.",
|
||||
name: "AGUIAssistant");
|
||||
|
||||
@@ -144,16 +144,16 @@ var chatClient = new AGUIChatClient(
|
||||
modelId: "agui-client",
|
||||
jsonSerializerOptions: null);
|
||||
|
||||
AIAgent agent = chatClient.CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
instructions: null,
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent",
|
||||
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`
|
||||
@@ -74,7 +74,7 @@ AzureOpenAIClient azureOpenAIClient = new AzureOpenAIClient(
|
||||
ChatClient chatClient = azureOpenAIClient.GetChatClient(deploymentName);
|
||||
|
||||
// Create AI agent
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "ChatAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
@@ -162,7 +162,7 @@ dotnet run
|
||||
Edit the instructions in `Server/Program.cs`:
|
||||
|
||||
```csharp
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "ChatAssistant",
|
||||
instructions: "You are a helpful coding assistant specializing in C# and .NET.");
|
||||
```
|
||||
|
||||
@@ -25,7 +25,7 @@ AzureOpenAIClient azureOpenAIClient = new(
|
||||
|
||||
ChatClient chatClient = azureOpenAIClient.GetChatClient(deploymentName);
|
||||
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "ChatAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName);
|
||||
AIAgent agent = client.GetChatClient(deploymentName).AsAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
// Configure the function app to host the AI agent.
|
||||
// This will automatically generate HTTP API endpoints for the agent.
|
||||
|
||||
@@ -19,13 +19,13 @@ public static class FunctionTriggers
|
||||
public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] TaskOrchestrationContext context)
|
||||
{
|
||||
DurableAIAgent writer = context.GetAgent("WriterAgent");
|
||||
AgentThread writerThread = writer.GetNewThread();
|
||||
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);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const string WriterInstructions =
|
||||
when given an improved sentence you polish it further.
|
||||
""";
|
||||
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).CreateAIAgent(WriterInstructions, WriterName);
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).AsAIAgent(WriterInstructions, WriterName);
|
||||
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -28,8 +28,8 @@ const string PhysicistInstructions = "You are an expert in physics. You answer q
|
||||
const string ChemistName = "ChemistAgent";
|
||||
const string ChemistInstructions = "You are an expert in chemistry. You answer questions from a chemistry perspective.";
|
||||
|
||||
AIAgent physicistAgent = client.GetChatClient(deploymentName).CreateAIAgent(PhysicistInstructions, PhysicistName);
|
||||
AIAgent chemistAgent = client.GetChatClient(deploymentName).CreateAIAgent(ChemistInstructions, ChemistName);
|
||||
AIAgent physicistAgent = client.GetChatClient(deploymentName).AsAIAgent(PhysicistInstructions, PhysicistName);
|
||||
AIAgent chemistAgent = client.GetChatClient(deploymentName).AsAIAgent(ChemistInstructions, ChemistName);
|
||||
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
|
||||
+4
-4
@@ -21,10 +21,10 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the spam detection agent
|
||||
DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent");
|
||||
AgentThread spamThread = spamDetectionAgent.GetNewThread();
|
||||
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:
|
||||
@@ -43,9 +43,9 @@ public static class FunctionTriggers
|
||||
|
||||
// Generate and send response for legitimate email
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
|
||||
AgentThread emailThread = emailAssistantAgent.GetNewThread();
|
||||
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:
|
||||
|
||||
@@ -29,10 +29,10 @@ const string EmailAssistantName = "EmailAssistantAgent";
|
||||
const string EmailAssistantInstructions = "You are an email assistant that helps users draft responses to emails with professionalism.";
|
||||
|
||||
AIAgent spamDetectionAgent = client.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(SpamDetectionInstructions, SpamDetectionName);
|
||||
.AsAIAgent(SpamDetectionInstructions, SpamDetectionName);
|
||||
|
||||
AIAgent emailAssistantAgent = client.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(EmailAssistantInstructions, EmailAssistantName);
|
||||
.AsAIAgent(EmailAssistantInstructions, EmailAssistantName);
|
||||
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
|
||||
@@ -24,13 +24,13 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent("WriterAgent");
|
||||
AgentThread writerThread = writerAgent.GetNewThread();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
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;
|
||||
|
||||
@@ -29,7 +29,7 @@ const string WriterInstructions =
|
||||
You write engaging, informative, and well-structured content that follows best practices for readability and accuracy.
|
||||
""";
|
||||
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).CreateAIAgent(WriterInstructions, WriterName);
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).AsAIAgent(WriterInstructions, WriterName);
|
||||
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
|
||||
@@ -20,13 +20,13 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent("Writer");
|
||||
AgentThread writerThread = writerAgent.GetNewThread();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
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;
|
||||
|
||||
@@ -33,7 +33,7 @@ const string WriterAgentInstructions =
|
||||
You write engaging, informative, and well-structured content that follows best practices for readability and accuracy.
|
||||
""";
|
||||
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).CreateAIAgent(WriterAgentInstructions, WriterAgentName);
|
||||
AIAgent writerAgent = client.GetChatClient(deploymentName).AsAIAgent(WriterAgentInstructions, WriterAgentName);
|
||||
|
||||
// Agent that can start content generation workflows using tools
|
||||
const string PublisherAgentName = "Publisher";
|
||||
@@ -57,7 +57,7 @@ using IHost app = FunctionsApplication
|
||||
// Initialize the tools to be used by the agent.
|
||||
Tools publisherTools = new(sp.GetRequiredService<ILogger<Tools>>());
|
||||
|
||||
return client.GetChatClient(deploymentName).CreateAIAgent(
|
||||
return client.GetChatClient(deploymentName).AsAIAgent(
|
||||
instructions: PublisherAgentInstructions,
|
||||
name: PublisherAgentName,
|
||||
services: sp,
|
||||
|
||||
@@ -28,13 +28,13 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Define three AI agents we are going to use in this application.
|
||||
AIAgent agent1 = client.GetChatClient(deploymentName).CreateAIAgent("You are good at telling jokes.", "Joker");
|
||||
AIAgent agent1 = client.GetChatClient(deploymentName).AsAIAgent("You are good at telling jokes.", "Joker");
|
||||
|
||||
AIAgent agent2 = client.GetChatClient(deploymentName)
|
||||
.CreateAIAgent("Check stock prices.", "StockAdvisor");
|
||||
.AsAIAgent("Check stock prices.", "StockAdvisor");
|
||||
|
||||
AIAgent agent3 = client.GetChatClient(deploymentName)
|
||||
.CreateAIAgent("Recommend plants.", "PlantAdvisor", description: "Get plant recommendations.");
|
||||
.AsAIAgent("Recommend plants.", "PlantAdvisor", description: "Get plant recommendations.");
|
||||
|
||||
using IHost app = FunctionsApplication
|
||||
.CreateBuilder(args)
|
||||
|
||||
@@ -95,7 +95,7 @@ public sealed class FunctionTriggers
|
||||
AIAgent agentProxy = durableClient.AsDurableAgentProxy(context, "TravelPlanner");
|
||||
|
||||
// Create a new agent thread
|
||||
AgentThread thread = agentProxy.GetNewThread();
|
||||
AgentThread thread = await agentProxy.GetNewThreadAsync(cancellationToken);
|
||||
string agentSessionId = thread.GetService<AgentSessionId>().ToString();
|
||||
|
||||
this._logger.LogInformation("Creating new agent session: {AgentSessionId}", agentSessionId);
|
||||
|
||||
@@ -70,7 +70,7 @@ FunctionsApplicationBuilder builder = FunctionsApplication
|
||||
// Define the Travel Planner agent with tools for weather and events
|
||||
options.AddAIAgentFactory(TravelPlannerName, sp =>
|
||||
{
|
||||
return client.GetChatClient(deploymentName).CreateAIAgent(
|
||||
return client.GetChatClient(deploymentName).AsAIAgent(
|
||||
instructions: TravelPlannerInstructions,
|
||||
name: TravelPlannerName,
|
||||
services: sp,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,14 +23,14 @@ A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent a2aAgent = agentCard.GetAIAgent();
|
||||
AIAgent a2aAgent = agentCard.AsAIAgent();
|
||||
|
||||
// Create the main agent, and provide the a2a agent skills as a function tools.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
.AsAIAgent(
|
||||
instructions: "You are a helpful assistant that helps people with travel planning.",
|
||||
tools: [.. CreateFunctionTools(a2aAgent, agentCard)]
|
||||
);
|
||||
|
||||
@@ -14,12 +14,12 @@ A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent agent = agentCard.GetAIAgent();
|
||||
AIAgent agent = agentCard.AsAIAgent();
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
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
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ using HttpClient httpClient = new()
|
||||
|
||||
AGUIChatClient chatClient = new(httpClient, serverUrl);
|
||||
|
||||
AIAgent agent = chatClient.CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
AIAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ using HttpClient httpClient = new()
|
||||
|
||||
AGUIChatClient chatClient = new(httpClient, serverUrl);
|
||||
|
||||
AIAgent agent = chatClient.CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
ChatClientAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant with access to restaurant information.",
|
||||
tools: tools);
|
||||
|
||||
@@ -28,12 +28,12 @@ using HttpClient httpClient = new()
|
||||
|
||||
AGUIChatClient chatClient = new(httpClient, serverUrl);
|
||||
|
||||
AIAgent agent = chatClient.CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent",
|
||||
tools: frontendTools);
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
AIAgent agent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
AIAgent agent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ using HttpClient httpClient = new()
|
||||
AGUIChatClient chatClient = new(httpClient, serverUrl);
|
||||
|
||||
// Create agent
|
||||
ChatClientAgent baseAgent = chatClient.CreateAIAgent(
|
||||
ChatClientAgent baseAgent = chatClient.AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant.");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -57,7 +57,7 @@ ChatClient openAIChatClient = new AzureOpenAIClient(
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
ChatClientAgent baseAgent = openAIChatClient.AsIChatClient().CreateAIAgent(
|
||||
ChatClientAgent baseAgent = openAIChatClient.AsIChatClient().AsAIAgent(
|
||||
name: "AGUIAssistant",
|
||||
instructions: "You are a helpful assistant in charge of approving expenses",
|
||||
tools: tools);
|
||||
|
||||
+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,
|
||||
|
||||
@@ -19,7 +19,7 @@ using HttpClient httpClient = new()
|
||||
|
||||
AGUIChatClient chatClient = new(httpClient, serverUrl);
|
||||
|
||||
AIAgent baseAgent = chatClient.CreateAIAgent(
|
||||
AIAgent baseAgent = chatClient.AsAIAgent(
|
||||
name: "recipe-client",
|
||||
description: "AG-UI Recipe Client Agent");
|
||||
|
||||
@@ -30,7 +30,7 @@ JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web)
|
||||
};
|
||||
StatefulAgent<AgentState> agent = new(baseAgent, jsonOptions, new AgentState());
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful recipe assistant.")
|
||||
@@ -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)
|
||||
|
||||
@@ -34,7 +34,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
new DefaultAzureCredential())
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
AIAgent baseAgent = chatClient.AsIChatClient().CreateAIAgent(
|
||||
AIAgent baseAgent = chatClient.AsIChatClient().AsAIAgent(
|
||||
name: "RecipeAgent",
|
||||
instructions: """
|
||||
You are a helpful recipe assistant. When users ask you to create or suggest a recipe,
|
||||
|
||||
+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")]
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ var agent = new ChatClientAgent(instrumentedChatClient,
|
||||
.UseOpenTelemetry(SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
|
||||
.Build();
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
var thread = await agent.GetNewThreadAsync();
|
||||
|
||||
appLogger.LogInformation("Agent created successfully with ID: {AgentId}", agent.Id);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -26,9 +26,9 @@ using Microsoft.Agents.AI.A2A;
|
||||
A2AClient a2aClient = new(new Uri("https://your-a2a-agent-host/echo"));
|
||||
|
||||
// Create an AIAgent from the A2AClient
|
||||
AIAgent agent = a2aClient.GetAIAgent();
|
||||
AIAgent agent = a2aClient.AsAIAgent();
|
||||
|
||||
// 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);
|
||||
```
|
||||
@@ -26,7 +26,7 @@ AnthropicClient? client = (resource is null)
|
||||
? new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(apiKey, resource)) // If an apiKey is provided, use Foundry with ApiKey authentication
|
||||
: new AnthropicFoundryClient(new AnthropicAzureTokenCredential(new AzureCliCredential(), resource)); // Otherwise, use Foundry with Azure Client authentication
|
||||
|
||||
AIAgent agent = client.CreateAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName);
|
||||
AIAgent agent = client.AsAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ AIAgent agent2 = await persistentAgentsClient.CreateAIAgentAsync(
|
||||
instructions: JokerInstructions);
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
AgentThread thread = agent1.GetNewThread();
|
||||
AgentThread thread = await agent1.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
|
||||
@@ -27,7 +27,7 @@ var createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: J
|
||||
// agentVersion.Name = <agentName>
|
||||
|
||||
// You can retrieve an AIAgent for an already created server side agent version.
|
||||
AIAgent existingJokerAgent = aiProjectClient.GetAIAgent(createdAgentVersion);
|
||||
AIAgent existingJokerAgent = aiProjectClient.AsAIAgent(createdAgentVersion);
|
||||
|
||||
// You can also create another AIAgent version by providing the same name with a different definition.
|
||||
AIAgent newJokerAgent = aiProjectClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: "You are extremely hilarious at telling jokes.");
|
||||
@@ -40,7 +40,7 @@ var latestAgentVersion = jokerAgentLatest.GetService<AgentVersion>()!;
|
||||
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
|
||||
|
||||
// Once you have the AIAgent, you can invoke it like any other AIAgent.
|
||||
AgentThread thread = jokerAgentLatest.GetNewThread();
|
||||
AgentThread thread = await jokerAgentLatest.GetNewThreadAsync();
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// This will use the same thread to continue the conversation.
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ OpenAIClient client = string.IsNullOrWhiteSpace(apiKey)
|
||||
|
||||
AIAgent agent = client
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetResponsesClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+10
-10
@@ -28,16 +28,16 @@ namespace SampleApp
|
||||
{
|
||||
public override string? Name => "UpperCaseParrotAgent";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
=> new CustomAgentThread();
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentThread());
|
||||
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> new CustomAgentThread(serializedThread, jsonSerializerOptions);
|
||||
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 ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
if (thread is not CustomAgentThread typedThread)
|
||||
{
|
||||
@@ -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,10 +66,10 @@ 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 ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
if (thread is not CustomAgentThread typedThread)
|
||||
{
|
||||
@@ -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,
|
||||
|
||||
-558
@@ -1,558 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Google.Apis.Util;
|
||||
using Google.GenAI;
|
||||
using Google.GenAI.Types;
|
||||
|
||||
namespace Microsoft.Extensions.AI;
|
||||
|
||||
/// <summary>Provides an <see cref="IChatClient"/> implementation based on <see cref="Client"/>.</summary>
|
||||
internal sealed class GoogleGenAIChatClient : IChatClient
|
||||
{
|
||||
/// <summary>The wrapped <see cref="Client"/> instance (optional).</summary>
|
||||
private readonly Client? _client;
|
||||
|
||||
/// <summary>The wrapped <see cref="Models"/> instance.</summary>
|
||||
private readonly Models _models;
|
||||
|
||||
/// <summary>The default model that should be used when no override is specified.</summary>
|
||||
private readonly string? _defaultModelId;
|
||||
|
||||
/// <summary>Lazily-initialized metadata describing the implementation.</summary>
|
||||
private ChatClientMetadata? _metadata;
|
||||
|
||||
/// <summary>Initializes a new <see cref="GoogleGenAIChatClient"/> instance.</summary>
|
||||
public GoogleGenAIChatClient(Client client, string? defaultModelId)
|
||||
{
|
||||
this._client = client;
|
||||
this._models = client.Models;
|
||||
this._defaultModelId = defaultModelId;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new <see cref="GoogleGenAIChatClient"/> instance.</summary>
|
||||
public GoogleGenAIChatClient(Models client, string? defaultModelId)
|
||||
{
|
||||
this._models = client;
|
||||
this._defaultModelId = defaultModelId;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Utilities.ThrowIfNull(messages, nameof(messages));
|
||||
|
||||
// Create the request.
|
||||
(string? modelId, List<Content> contents, GenerateContentConfig config) = this.CreateRequest(messages, options);
|
||||
|
||||
// Send it.
|
||||
GenerateContentResponse generateResult = await this._models.GenerateContentAsync(modelId!, contents, config).ConfigureAwait(false);
|
||||
|
||||
// Create the response.
|
||||
ChatResponse chatResponse = new(new ChatMessage(ChatRole.Assistant, []))
|
||||
{
|
||||
CreatedAt = generateResult.CreateTime is { } dt ? new DateTimeOffset(dt) : null,
|
||||
ModelId = !string.IsNullOrWhiteSpace(generateResult.ModelVersion) ? generateResult.ModelVersion : modelId,
|
||||
RawRepresentation = generateResult,
|
||||
ResponseId = generateResult.ResponseId,
|
||||
};
|
||||
|
||||
// Populate the response messages.
|
||||
chatResponse.FinishReason = PopulateResponseContents(generateResult, chatResponse.Messages[0].Contents);
|
||||
|
||||
// Populate usage information if there is any.
|
||||
if (generateResult.UsageMetadata is { } usageMetadata)
|
||||
{
|
||||
chatResponse.Usage = ExtractUsageDetails(usageMetadata);
|
||||
}
|
||||
|
||||
// Return the response.
|
||||
return chatResponse;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
Utilities.ThrowIfNull(messages, nameof(messages));
|
||||
|
||||
// Create the request.
|
||||
(string? modelId, List<Content> contents, GenerateContentConfig config) = this.CreateRequest(messages, options);
|
||||
|
||||
// Send it, and process the results.
|
||||
await foreach (GenerateContentResponse generateResult in this._models.GenerateContentStreamAsync(modelId!, contents, config).WithCancellation(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Create a response update for each result in the stream.
|
||||
ChatResponseUpdate responseUpdate = new(ChatRole.Assistant, [])
|
||||
{
|
||||
CreatedAt = generateResult.CreateTime is { } dt ? new DateTimeOffset(dt) : null,
|
||||
ModelId = !string.IsNullOrWhiteSpace(generateResult.ModelVersion) ? generateResult.ModelVersion : modelId,
|
||||
RawRepresentation = generateResult,
|
||||
ResponseId = generateResult.ResponseId,
|
||||
};
|
||||
|
||||
// Populate the response update contents.
|
||||
responseUpdate.FinishReason = PopulateResponseContents(generateResult, responseUpdate.Contents);
|
||||
|
||||
// Populate usage information if there is any.
|
||||
if (generateResult.UsageMetadata is { } usageMetadata)
|
||||
{
|
||||
responseUpdate.Contents.Add(new UsageContent(ExtractUsageDetails(usageMetadata)));
|
||||
}
|
||||
|
||||
// Yield the update.
|
||||
yield return responseUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? GetService(System.Type serviceType, object? serviceKey = null)
|
||||
{
|
||||
Utilities.ThrowIfNull(serviceType, nameof(serviceType));
|
||||
|
||||
if (serviceKey is null)
|
||||
{
|
||||
// If there's a request for metadata, lazily-initialize it and return it. We don't need to worry about race conditions,
|
||||
// as there's no requirement that the same instance be returned each time, and creation is idempotent.
|
||||
if (serviceType == typeof(ChatClientMetadata))
|
||||
{
|
||||
return this._metadata ??= new("gcp.gen_ai", new("https://generativelanguage.googleapis.com/"), defaultModelId: this._defaultModelId);
|
||||
}
|
||||
|
||||
// Allow a consumer to "break glass" and access the underlying client if they need it.
|
||||
if (serviceType.IsInstanceOfType(this._models))
|
||||
{
|
||||
return this._models;
|
||||
}
|
||||
|
||||
if (this._client is not null && serviceType.IsInstanceOfType(this._client))
|
||||
{
|
||||
return this._client;
|
||||
}
|
||||
|
||||
if (serviceType.IsInstanceOfType(this))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose() { /* nop */ }
|
||||
|
||||
/// <summary>Creates the message parameters for <see cref="Models.GenerateContentAsync(string, List{Content}, GenerateContentConfig?)"/> from <paramref name="messages"/> and <paramref name="options"/>.</summary>
|
||||
private (string? ModelId, List<Content> Contents, GenerateContentConfig Config) CreateRequest(IEnumerable<ChatMessage> messages, ChatOptions? options)
|
||||
{
|
||||
// Create the GenerateContentConfig object. If the options contains a RawRepresentationFactory, try to use it to
|
||||
// create the request instance, allowing the caller to populate it with GenAI-specific options. Otherwise, create
|
||||
// a new instance directly.
|
||||
string? model = this._defaultModelId;
|
||||
List<Content> contents = [];
|
||||
GenerateContentConfig config = options?.RawRepresentationFactory?.Invoke(this) as GenerateContentConfig ?? new();
|
||||
|
||||
if (options is not null)
|
||||
{
|
||||
if (options.FrequencyPenalty is { } frequencyPenalty)
|
||||
{
|
||||
config.FrequencyPenalty ??= frequencyPenalty;
|
||||
}
|
||||
|
||||
if (options.Instructions is { } instructions)
|
||||
{
|
||||
((config.SystemInstruction ??= new()).Parts ??= []).Add(new() { Text = instructions });
|
||||
}
|
||||
|
||||
if (options.MaxOutputTokens is { } maxOutputTokens)
|
||||
{
|
||||
config.MaxOutputTokens ??= maxOutputTokens;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.ModelId))
|
||||
{
|
||||
model = options.ModelId;
|
||||
}
|
||||
|
||||
if (options.PresencePenalty is { } presencePenalty)
|
||||
{
|
||||
config.PresencePenalty ??= presencePenalty;
|
||||
}
|
||||
|
||||
if (options.Seed is { } seed)
|
||||
{
|
||||
config.Seed ??= (int)seed;
|
||||
}
|
||||
|
||||
if (options.StopSequences is { } stopSequences)
|
||||
{
|
||||
(config.StopSequences ??= []).AddRange(stopSequences);
|
||||
}
|
||||
|
||||
if (options.Temperature is { } temperature)
|
||||
{
|
||||
config.Temperature ??= temperature;
|
||||
}
|
||||
|
||||
if (options.TopP is { } topP)
|
||||
{
|
||||
config.TopP ??= topP;
|
||||
}
|
||||
|
||||
if (options.TopK is { } topK)
|
||||
{
|
||||
config.TopK ??= topK;
|
||||
}
|
||||
|
||||
// Populate tools. Each kind of tool is added on its own, except for function declarations,
|
||||
// which are grouped into a single FunctionDeclaration.
|
||||
List<FunctionDeclaration>? functionDeclarations = null;
|
||||
if (options.Tools is { } tools)
|
||||
{
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
switch (tool)
|
||||
{
|
||||
case AIFunctionDeclaration af:
|
||||
functionDeclarations ??= [];
|
||||
functionDeclarations.Add(new()
|
||||
{
|
||||
Name = af.Name,
|
||||
Description = af.Description ?? "",
|
||||
ParametersJsonSchema = af.JsonSchema,
|
||||
});
|
||||
break;
|
||||
|
||||
case HostedCodeInterpreterTool:
|
||||
(config.Tools ??= []).Add(new() { CodeExecution = new() });
|
||||
break;
|
||||
|
||||
case HostedFileSearchTool:
|
||||
(config.Tools ??= []).Add(new() { Retrieval = new() });
|
||||
break;
|
||||
|
||||
case HostedWebSearchTool:
|
||||
(config.Tools ??= []).Add(new() { GoogleSearch = new() });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (functionDeclarations is { Count: > 0 })
|
||||
{
|
||||
Tool functionTools = new();
|
||||
(functionTools.FunctionDeclarations ??= []).AddRange(functionDeclarations);
|
||||
(config.Tools ??= []).Add(functionTools);
|
||||
}
|
||||
|
||||
// Transfer over the tool mode if there are any tools.
|
||||
if (options.ToolMode is { } toolMode && config.Tools?.Count > 0)
|
||||
{
|
||||
switch (toolMode)
|
||||
{
|
||||
case NoneChatToolMode:
|
||||
config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.NONE } };
|
||||
break;
|
||||
|
||||
case AutoChatToolMode:
|
||||
config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.AUTO } };
|
||||
break;
|
||||
|
||||
case RequiredChatToolMode required:
|
||||
config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.ANY } };
|
||||
if (required.RequiredFunctionName is not null)
|
||||
{
|
||||
((config.ToolConfig.FunctionCallingConfig ??= new()).AllowedFunctionNames ??= []).Add(required.RequiredFunctionName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the response format if specified.
|
||||
if (options.ResponseFormat is ChatResponseFormatJson responseFormat)
|
||||
{
|
||||
config.ResponseMimeType = "application/json";
|
||||
if (responseFormat.Schema is { } schema)
|
||||
{
|
||||
config.ResponseJsonSchema = schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer messages to request, handling system messages specially
|
||||
Dictionary<string, string>? callIdToFunctionNames = null;
|
||||
foreach (var message in messages)
|
||||
{
|
||||
if (message.Role == ChatRole.System)
|
||||
{
|
||||
string instruction = message.Text;
|
||||
if (!string.IsNullOrWhiteSpace(instruction))
|
||||
{
|
||||
((config.SystemInstruction ??= new()).Parts ??= []).Add(new() { Text = instruction });
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Content content = new() { Role = message.Role == ChatRole.Assistant ? "model" : "user" };
|
||||
content.Parts ??= [];
|
||||
AddPartsForAIContents(ref callIdToFunctionNames, message.Contents, content.Parts);
|
||||
|
||||
contents.Add(content);
|
||||
}
|
||||
|
||||
// Make sure the request contains at least one content part (the request would always fail if empty).
|
||||
if (!contents.SelectMany(c => c.Parts ?? Enumerable.Empty<Part>()).Any())
|
||||
{
|
||||
contents.Add(new() { Role = "user", Parts = new() { { new() { Text = "" } } } });
|
||||
}
|
||||
|
||||
return (model, contents, config);
|
||||
}
|
||||
|
||||
/// <summary>Creates <see cref="Part"/>s for <paramref name="contents"/> and adds them to <paramref name="parts"/>.</summary>
|
||||
private static void AddPartsForAIContents(ref Dictionary<string, string>? callIdToFunctionNames, IList<AIContent> contents, List<Part> parts)
|
||||
{
|
||||
for (int i = 0; i < contents.Count; i++)
|
||||
{
|
||||
var content = contents[i];
|
||||
|
||||
byte[]? thoughtSignature = null;
|
||||
if (content is not TextReasoningContent { ProtectedData: not null } &&
|
||||
i + 1 < contents.Count &&
|
||||
contents[i + 1] is TextReasoningContent nextReasoning &&
|
||||
string.IsNullOrWhiteSpace(nextReasoning.Text) &&
|
||||
nextReasoning.ProtectedData is { } protectedData)
|
||||
{
|
||||
i++;
|
||||
thoughtSignature = Convert.FromBase64String(protectedData);
|
||||
}
|
||||
|
||||
Part? part = null;
|
||||
switch (content)
|
||||
{
|
||||
case TextContent textContent:
|
||||
part = new() { Text = textContent.Text };
|
||||
break;
|
||||
|
||||
case TextReasoningContent reasoningContent:
|
||||
part = new()
|
||||
{
|
||||
Thought = true,
|
||||
Text = !string.IsNullOrWhiteSpace(reasoningContent.Text) ? reasoningContent.Text : null,
|
||||
ThoughtSignature = reasoningContent.ProtectedData is not null ? Convert.FromBase64String(reasoningContent.ProtectedData) : null,
|
||||
};
|
||||
break;
|
||||
|
||||
case DataContent dataContent:
|
||||
part = new()
|
||||
{
|
||||
InlineData = new()
|
||||
{
|
||||
MimeType = dataContent.MediaType,
|
||||
Data = dataContent.Data.ToArray(),
|
||||
DisplayName = dataContent.Name,
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case UriContent uriContent:
|
||||
part = new()
|
||||
{
|
||||
FileData = new()
|
||||
{
|
||||
FileUri = uriContent.Uri.AbsoluteUri,
|
||||
MimeType = uriContent.MediaType,
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case FunctionCallContent functionCallContent:
|
||||
(callIdToFunctionNames ??= [])[functionCallContent.CallId] = functionCallContent.Name;
|
||||
callIdToFunctionNames[""] = functionCallContent.Name; // track last function name in case calls don't have IDs
|
||||
|
||||
part = new()
|
||||
{
|
||||
FunctionCall = new()
|
||||
{
|
||||
Id = functionCallContent.CallId,
|
||||
Name = functionCallContent.Name,
|
||||
Args = functionCallContent.Arguments is null ? null : functionCallContent.Arguments as Dictionary<string, object> ?? new(functionCallContent.Arguments!),
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case FunctionResultContent functionResultContent:
|
||||
part = new()
|
||||
{
|
||||
FunctionResponse = new()
|
||||
{
|
||||
Id = functionResultContent.CallId,
|
||||
Name = callIdToFunctionNames?.TryGetValue(functionResultContent.CallId, out string? functionName) is true || callIdToFunctionNames?.TryGetValue("", out functionName) is true ?
|
||||
functionName :
|
||||
null,
|
||||
Response = functionResultContent.Result is null ? null : new() { ["result"] = functionResultContent.Result },
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (part is not null)
|
||||
{
|
||||
part.ThoughtSignature ??= thoughtSignature;
|
||||
parts.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates <see cref="AIContent"/>s for <paramref name="parts"/> and adds them to <paramref name="contents"/>.</summary>
|
||||
private static void AddAIContentsForParts(List<Part> parts, IList<AIContent> contents)
|
||||
{
|
||||
foreach (var part in parts)
|
||||
{
|
||||
AIContent? content = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(part.Text))
|
||||
{
|
||||
content = part.Thought is true ?
|
||||
new TextReasoningContent(part.Text) :
|
||||
new TextContent(part.Text);
|
||||
}
|
||||
else if (part.InlineData is { } inlineData)
|
||||
{
|
||||
content = new DataContent(inlineData.Data, inlineData.MimeType ?? "application/octet-stream")
|
||||
{
|
||||
Name = inlineData.DisplayName,
|
||||
};
|
||||
}
|
||||
else if (part.FileData is { FileUri: not null } fileData)
|
||||
{
|
||||
content = new UriContent(new Uri(fileData.FileUri), fileData.MimeType ?? "application/octet-stream");
|
||||
}
|
||||
else if (part.FunctionCall is { Name: not null } functionCall)
|
||||
{
|
||||
content = new FunctionCallContent(functionCall.Id ?? "", functionCall.Name, functionCall.Args!);
|
||||
}
|
||||
else if (part.FunctionResponse is { } functionResponse)
|
||||
{
|
||||
content = new FunctionResultContent(
|
||||
functionResponse.Id ?? "",
|
||||
functionResponse.Response?.TryGetValue("output", out var output) is true ? output :
|
||||
functionResponse.Response?.TryGetValue("error", out var error) is true ? error :
|
||||
null);
|
||||
}
|
||||
|
||||
if (content is not null)
|
||||
{
|
||||
content.RawRepresentation = part;
|
||||
contents.Add(content);
|
||||
|
||||
if (part.ThoughtSignature is { } thoughtSignature)
|
||||
{
|
||||
contents.Add(new TextReasoningContent(null)
|
||||
{
|
||||
ProtectedData = Convert.ToBase64String(thoughtSignature),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ChatFinishReason? PopulateResponseContents(GenerateContentResponse generateResult, IList<AIContent> responseContents)
|
||||
{
|
||||
ChatFinishReason? finishReason = null;
|
||||
|
||||
// Populate the response messages. There should only be at most one candidate, but if there are more, ignore all but the first.
|
||||
if (generateResult.Candidates is { Count: > 0 } &&
|
||||
generateResult.Candidates[0] is { Content: { } candidateContent } candidate)
|
||||
{
|
||||
// Grab the finish reason if one exists.
|
||||
finishReason = ConvertFinishReason(candidate.FinishReason);
|
||||
|
||||
// Add all of the response content parts as AIContents.
|
||||
if (candidateContent.Parts is { } parts)
|
||||
{
|
||||
AddAIContentsForParts(parts, responseContents);
|
||||
}
|
||||
|
||||
// Add any citation metadata.
|
||||
if (candidate.CitationMetadata is { Citations: { Count: > 0 } citations } &&
|
||||
responseContents.OfType<TextContent>().FirstOrDefault() is TextContent textContent)
|
||||
{
|
||||
foreach (var citation in citations)
|
||||
{
|
||||
textContent.Annotations =
|
||||
[
|
||||
new CitationAnnotation()
|
||||
{
|
||||
Title = citation.Title,
|
||||
Url = Uri.TryCreate(citation.Uri, UriKind.Absolute, out Uri? uri) ? uri : null,
|
||||
AnnotatedRegions =
|
||||
[
|
||||
new TextSpanAnnotatedRegion()
|
||||
{
|
||||
StartIndex = citation.StartIndex,
|
||||
EndIndex = citation.EndIndex,
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate error information if there is any.
|
||||
if (generateResult.PromptFeedback is { } promptFeedback)
|
||||
{
|
||||
responseContents.Add(new ErrorContent(promptFeedback.BlockReasonMessage));
|
||||
}
|
||||
|
||||
return finishReason;
|
||||
}
|
||||
|
||||
/// <summary>Creates an M.E.AI <see cref="ChatFinishReason"/> from a Google <see cref="FinishReason"/>.</summary>
|
||||
private static ChatFinishReason? ConvertFinishReason(FinishReason? finishReason)
|
||||
{
|
||||
return finishReason switch
|
||||
{
|
||||
null => null,
|
||||
|
||||
FinishReason.MAX_TOKENS =>
|
||||
ChatFinishReason.Length,
|
||||
|
||||
FinishReason.MALFORMED_FUNCTION_CALL or
|
||||
FinishReason.UNEXPECTED_TOOL_CALL =>
|
||||
ChatFinishReason.ToolCalls,
|
||||
|
||||
FinishReason.FINISH_REASON_UNSPECIFIED or
|
||||
FinishReason.STOP =>
|
||||
ChatFinishReason.Stop,
|
||||
|
||||
_ => ChatFinishReason.ContentFilter,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="UsageDetails"/> populated from the supplied <paramref name="usageMetadata"/>.</summary>
|
||||
private static UsageDetails ExtractUsageDetails(GenerateContentResponseUsageMetadata usageMetadata)
|
||||
{
|
||||
UsageDetails details = new()
|
||||
{
|
||||
InputTokenCount = usageMetadata.PromptTokenCount,
|
||||
OutputTokenCount = usageMetadata.CandidatesTokenCount,
|
||||
TotalTokenCount = usageMetadata.TotalTokenCount,
|
||||
};
|
||||
|
||||
AddIfPresent(nameof(usageMetadata.CachedContentTokenCount), usageMetadata.CachedContentTokenCount);
|
||||
AddIfPresent(nameof(usageMetadata.ThoughtsTokenCount), usageMetadata.ThoughtsTokenCount);
|
||||
AddIfPresent(nameof(usageMetadata.ToolUsePromptTokenCount), usageMetadata.ToolUsePromptTokenCount);
|
||||
|
||||
return details;
|
||||
|
||||
void AddIfPresent(string key, int? value)
|
||||
{
|
||||
if (value is int i)
|
||||
{
|
||||
(details.AdditionalCounts ??= [])[key] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Google.Apis.Util;
|
||||
using Google.GenAI;
|
||||
|
||||
namespace Microsoft.Extensions.AI;
|
||||
|
||||
/// <summary>Provides implementations of Microsoft.Extensions.AI abstractions based on <see cref="Client"/>.</summary>
|
||||
public static class GoogleGenAIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IChatClient"/> wrapper around the specified <see cref="Client"/>.
|
||||
/// </summary>
|
||||
/// <param name="client">The <see cref="Client"/> to wrap.</param>
|
||||
/// <param name="defaultModelId">The default model ID to use for chat requests if not specified in <see cref="ChatOptions.ModelId"/>.</param>
|
||||
/// <returns>An <see cref="IChatClient"/> that wraps the specified client.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
|
||||
public static IChatClient AsIChatClient(this Client client, string? defaultModelId = null)
|
||||
{
|
||||
Utilities.ThrowIfNull(client, nameof(client));
|
||||
return new GoogleGenAIChatClient(client, defaultModelId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IChatClient"/> wrapper around the specified <see cref="Models"/>.
|
||||
/// </summary>
|
||||
/// <param name="models">The <see cref="Models"/> client to wrap.</param>
|
||||
/// <param name="defaultModelId">The default model ID to use for chat requests if not specified in <see cref="ChatOptions.ModelId"/>.</param>
|
||||
/// <returns>An <see cref="IChatClient"/> that wraps the specified <see cref="Models"/> client.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="models"/> is <see langword="null"/>.</exception>
|
||||
public static IChatClient AsIChatClient(this Models models, string? defaultModelId = null)
|
||||
{
|
||||
Utilities.ThrowIfNull(models, nameof(models));
|
||||
return new GoogleGenAIChatClient(models, defaultModelId);
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,13 @@ string apiKey = Environment.GetEnvironmentVariable("GOOGLE_GENAI_API_KEY") ?? th
|
||||
string model = Environment.GetEnvironmentVariable("GOOGLE_GENAI_MODEL") ?? "gemini-2.5-flash";
|
||||
|
||||
// Using a Google GenAI IChatClient implementation
|
||||
// Until the PR https://github.com/googleapis/dotnet-genai/pull/81 is not merged this option
|
||||
// requires usage of also both GeminiChatClient.cs and GoogleGenAIExtensions.cs polyfills to work.
|
||||
|
||||
ChatClientAgent agentGenAI = new(
|
||||
new Client(vertexAI: false, apiKey: apiKey).AsIChatClient(model),
|
||||
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
|
||||
|
||||
@@ -25,12 +25,7 @@ $env:GOOGLE_GENAI_MODEL="gemini-2.5-fast" # Optional, defaults to gemini-2.5-fa
|
||||
|
||||
### Google GenAI (Official)
|
||||
|
||||
The official Google GenAI package provides direct access to Google's Generative AI models. This sample uses an extension method to convert the Google client to an `IChatClient`.
|
||||
|
||||
> [!NOTE]
|
||||
> Until PR [googleapis/dotnet-genai#81](https://github.com/googleapis/dotnet-genai/pull/81) is merged, this option requires the additional `GeminiChatClient.cs` and `GoogleGenAIExtensions.cs` files included in this sample.
|
||||
>
|
||||
> We appreciate any community push by liking and commenting in the above PR to get it merged and release as part of official Google GenAI package.
|
||||
The official Google GenAI package provides direct access to Google's Generative AI models. This sample uses the `AsIChatClient()` extension method to convert the Google client to an `IChatClient`.
|
||||
|
||||
### Mscc.GenerativeAI.Microsoft (Community)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ var modelPath = Environment.GetEnvironmentVariable("ONNX_MODEL_PATH") ?? throw n
|
||||
|
||||
// Get a chat client for ONNX and use it to construct an AIAgent.
|
||||
using OnnxRuntimeGenAIChatClient chatClient = new(modelPath);
|
||||
AIAgent agent = chatClient.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
AIAgent agent = chatClient.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -11,7 +11,7 @@ var modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") ?? throw
|
||||
|
||||
// Get a chat client for Ollama and use it to construct an AIAgent.
|
||||
AIAgent agent = new OllamaApiClient(new Uri(endpoint), modelName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -33,7 +33,7 @@ AIAgent agent2 = await assistantClient.CreateAIAgentAsync(
|
||||
instructions: JokerInstructions);
|
||||
|
||||
// You can invoke the agent like any other AIAgent.
|
||||
AgentThread thread = agent1.GetNewThread();
|
||||
AgentThread thread = await agent1.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
AIAgent agent = new OpenAIClient(
|
||||
apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -12,7 +12,7 @@ var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
AIAgent agent = new OpenAIClient(
|
||||
apiKey)
|
||||
.GetResponsesClient(model)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw ne
|
||||
var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
|
||||
|
||||
AIAgent agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
|
||||
.CreateAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
var response = await agent.RunAsync("Tell me a joke about a pirate.");
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ var maxTokens = 4096;
|
||||
var thinkingTokens = 2048;
|
||||
|
||||
var agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
|
||||
.CreateAIAgent(
|
||||
.AsAIAgent(
|
||||
model: model,
|
||||
clientFactory: (chatClient) => chatClient
|
||||
.AsBuilder()
|
||||
|
||||
+4
-4
@@ -23,15 +23,15 @@ AITool tool = AIFunctionFactory.Create(GetWeather);
|
||||
|
||||
// Get anthropic client to create agents.
|
||||
AIAgent agent = new AnthropicClient { APIKey = apiKey }
|
||||
.CreateAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
|
||||
.AsAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
thread = agent.GetNewThread();
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+5
-5
@@ -30,11 +30,11 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are good at telling jokes." },
|
||||
Name = "Joker",
|
||||
AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider(
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new ChatHistoryMemoryProvider(
|
||||
vectorStore,
|
||||
collectionName: "chathistory",
|
||||
vectorDimensions: 3072,
|
||||
@@ -43,18 +43,18 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
storageScope: new() { UserId = "UID1", ThreadId = new Guid().ToString() },
|
||||
// Configure the scope which would be used to search for relevant prior messages.
|
||||
// In this case, we are searching for any messages for the user across all threads.
|
||||
searchScope: new() { UserId = "UID1" })
|
||||
searchScope: new() { UserId = "UID1" }))
|
||||
});
|
||||
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with the thread that stores conversation history in the vector store.
|
||||
Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Start a second thread. Since we configured the search scope to be across all threads for the user,
|
||||
// the agent should remember that the user likes pirate jokes.
|
||||
AgentThread thread2 = agent.GetNewThread();
|
||||
AgentThread thread2 = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with the second thread.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", thread2));
|
||||
|
||||
+6
-6
@@ -28,19 +28,19 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions()
|
||||
.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||
AIContextProviderFactory = ctx => ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
|
||||
// If each thread should have its own Mem0 scope, you can create a new id per thread here:
|
||||
// ? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ThreadId = Guid.NewGuid().ToString() })
|
||||
// In this case we are storing memories scoped by application and user instead so that memories are retained across threads.
|
||||
? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ApplicationId = "getting-started-agents", UserId = "sample-user" })
|
||||
// For cases where we are restoring from serialized state:
|
||||
: new Mem0Provider(mem0HttpClient, ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
: new Mem0Provider(mem0HttpClient, ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Clear any existing memories for this scope to demonstrate fresh behavior.
|
||||
Mem0Provider mem0Provider = thread.GetService<Mem0Provider>()!;
|
||||
@@ -56,9 +56,9 @@ Console.WriteLine(await agent.RunAsync("What do you already know about my upcomi
|
||||
|
||||
Console.WriteLine("\n>> Serialize and deserialize the thread to demonstrate persisted state\n");
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
AgentThread restoredThread = agent.DeserializeThread(serializedThread);
|
||||
AgentThread restoredThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredThread));
|
||||
|
||||
Console.WriteLine("\n>> Start a new thread that shares the same Mem0 scope\n");
|
||||
AgentThread newThread = agent.GetNewThread();
|
||||
AgentThread newThread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Summarize what you already know about me.", newThread));
|
||||
|
||||
+5
-5
@@ -30,14 +30,14 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
// and preferably shared between multiple threads used by the same user, ensure that the
|
||||
// factory reads the user id from the current context and scopes the memory component
|
||||
// and its storage to that user id.
|
||||
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
|
||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
|
||||
AIContextProviderFactory = ctx => new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
// Create a new thread for the conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Use thread with blank memory\n");
|
||||
|
||||
@@ -52,7 +52,7 @@ var threadElement = thread.Serialize();
|
||||
Console.WriteLine("\n>> Use deserialized thread with previously created memories\n");
|
||||
|
||||
// Later we can deserialize the thread and continue the conversation with the previous memory component state.
|
||||
var deserializedThread = agent.DeserializeThread(threadElement);
|
||||
var deserializedThread = await agent.DeserializeThreadAsync(threadElement);
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedThread));
|
||||
|
||||
Console.WriteLine("\n>> Read memories from memory component\n");
|
||||
@@ -68,7 +68,7 @@ Console.WriteLine("\n>> Use new thread with previously created memories\n");
|
||||
|
||||
// It is also possible to set the memories in a memory component on an individual thread.
|
||||
// This is useful if we want to start a new thread, but have it share the same memories as a previous thread.
|
||||
var newThread = agent.GetNewThread();
|
||||
var newThread = await agent.GetNewThreadAsync();
|
||||
if (userInfo is not null && newThread.GetService<UserInfoMemory>() is UserInfoMemory newThreadMemory)
|
||||
{
|
||||
newThreadMemory.UserInfo = userInfo;
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
UserChatMessage chatMessage = new("Tell me a joke about a pirate.");
|
||||
|
||||
|
||||
+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
@@ -30,7 +30,7 @@ using JsonDocument createConversationResultAsJson = JsonDocument.Parse(createCon
|
||||
string conversationId = createConversationResultAsJson.RootElement.GetProperty("id"u8)!.GetString()!;
|
||||
|
||||
// Create a thread for the conversation - this enables conversation state management for subsequent turns
|
||||
AgentThread thread = agent.GetNewThread(conversationId);
|
||||
AgentThread thread = await agent.GetNewThreadAsync(conversationId);
|
||||
|
||||
Console.WriteLine("=== Multi-turn Conversation Demo ===\n");
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user