From 5284b611c2639136023a1bdfd375913a9cac5478 Mon Sep 17 00:00:00 2001 From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:04:56 +0100 Subject: [PATCH] .NET: API specification for Foundry SDK alignment (#359) * API specification for Foundry SDK alignment * Add descriptions to the samples * Add descriptions to the samples * Address some review feedback * Remove sample * Remove sample * Update docs/specs/001-foundry-sdk-alignment.md Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> * Update docs/specs/001-foundry-sdk-alignment.md Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> * Update docs/specs/001-foundry-sdk-alignment.md Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> * Update docs/specs/001-foundry-sdk-alignment.md Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> * Address code review feedback --------- Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> --- docs/specs/001-foundry-sdk-alignment.md | 291 ++++++++++++++++++ docs/specs/spec-template.md | 75 +++++ .../GettingStarted/GettingStarted.csproj | 2 +- ...9_ChatClientAgent_3rdPartyThreadStorage.cs | 8 +- 4 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 docs/specs/001-foundry-sdk-alignment.md create mode 100644 docs/specs/spec-template.md diff --git a/docs/specs/001-foundry-sdk-alignment.md b/docs/specs/001-foundry-sdk-alignment.md new file mode 100644 index 0000000000..1bbe879be8 --- /dev/null +++ b/docs/specs/001-foundry-sdk-alignment.md @@ -0,0 +1,291 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: accepted +contact: markwallace +date: 2025-08-06 +deciders: markwallace-microsoft, westey-m, quibitron +consulted: shawnhenry, elijahstraight +informed: +--- + +# Agent Framework / Foundry SDK Alignment + +Agent Framework and Foundry SDK have overlapping functionality but serve different audiences & scenarios. +This specification clarifies the positioning of these SDKs to customers, what goes in each and when to use what. + +- **Foundry SDK** is a thin-client SDK for accessing everything available in the agent service and is autogenerated from REST APIs in multiple languages +- **Agent Framework SDK** is general-purpose framework for agentic application development, where common agent abstractions enable creating and orchestrating heterogenous agent systems (across local & cloud) + +## What is the goal of this feature? + +Goals: +- Developers can seamlessly combine Foundry and Agent Framework SDK's and there is no friction when using both SDKs at the same time +- Developers can take advantage of the full capabilities supported by the Foundry SDK +- Developers can create multi-agent orchestrations using Foundry and other agent types + +Success Metrics: +- Complexity of basic samples is comparable to other agent frameworks +- Developers can easily discover how to use Foundry Agents in Agent Framework multi-agent orchestrations + +## What is the problem being solved? + +- In Semantic Kernel the Foundry Agent support isn't integrated into the Foundry SDK so there is a disjointed developer UX +- Customers are confused as to when they should use Foundry SDK versus Semantic Kernel + + +## API Changes + +The proposed solution is to add helper methods which allow developers to either retrieve or create an `AIAgent` using a `PersistentAgentsClient` + +- Retrieve an `AIAgent` + ```csharp + /// + /// Retrieves an existing server side agent, wrapped as a using the provided . + /// + /// The to create the with. + /// A for the persistent agent. + /// The ID of the server side agent to create a for. + /// Options that should apply to all runs of the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the persistent agent. + public static async Task GetAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string agentId, + ChatOptions? chatOptions = null, + CancellationToken cancellationToken = default) + ``` +- Create an `AIAgent` + ```csharp + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// The name of the agent. + /// The description of the agent. + /// The instructions for the agent. + /// The tools to be used by the agent. + /// The resources for the tools. + /// The temperature setting for the agent. + /// The top-p setting for the agent. + /// The response format for the agent. + /// The metadata for the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + public static async Task CreateAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string model, + string? name = null, + string? description = null, + string? instructions = null, + IEnumerable? tools = null, + ToolResources? toolResources = null, + float? temperature = null, + float? topP = null, + BinaryData? responseFormat = null, + IReadOnlyDictionary? metadata = null, + CancellationToken cancellationToken = default) + ``` +- Additional overload using the M.E.AI types: + ```csharp + /// + /// Creates a new server side agent using the provided . + /// + /// The to create the agent with. + /// The model to be used by the agent. + /// The name of the agent. + /// The description of the agent. + /// The instructions for the agent. + /// The tools to be used by the agent. + /// The temperature setting for the agent. + /// The top-p setting for the agent. + /// The response format for the agent. + /// The metadata for the agent. + /// The to monitor for cancellation requests. The default is . + /// A instance that can be used to perform operations on the newly created agent. + public static async Task CreateAIAgentAsync( + this PersistentAgentsClient persistentAgentsClient, + string model, + string? name = null, + string? description = null, + string? instructions = null, + IEnumerable? tools = null, + float? temperature = null, + float? topP = null, + BinaryData? responseFormat = null, + IReadOnlyDictionary? metadata = null, + CancellationToken cancellationToken = default) + ``` + + +## E2E Code Samples + +### 1. Create and retrieve with Foundry SDK, run with Agent Framework + +- [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` +- [Foundry SDK] Clean up the agent + + +```csharp +// Get a client to create server side agents with. +var persistentAgentsClient = new PersistentAgentsClient( + TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); + +// Create a persistent agent. +var persistentAgentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName!, + name: JokerName, + instructions: JokerInstructions); + +// Get the persistent agent we created in the previous step and expose it as an Agent Framework agent. +AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(persistentAgent.Value.Id); + +// Respond to user input. +var input = "Tell me a joke about a pirate."; +Console.WriteLine(input); +Console.WriteLine(await agent.RunAsync(input)); + +// Delete the persistent agent. +await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id); +``` + +### 2. Create directly with Foundry SDK, run with Agent Framework + +- [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` +- [Foundry SDK] Clean up the agent + +```csharp +// Get a client to create server side agents with. +var persistentAgentsClient = new PersistentAgentsClient( + TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); + +// Create a persistent agent and expose it as an Agent Framework agent. +AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName!, + name: JokerName, + instructions: JokerInstructions); + +// Respond to user input. +var input = "Tell me a joke about a pirate."; +Console.WriteLine(input); +Console.WriteLine(await agent.RunAsync(input)); + +// Delete the persistent agent. +await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id); +``` + +### 3. Create directly with Foundry SDK, run with conversation state using Agent Framework + +- [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` +- [Foundry SDK] Clean up the agent and the agent thread + +```csharp +// Get a client to create server side agents with. +var persistentAgentsClient = new PersistentAgentsClient( + TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); + +// Create an Agent Framework agent. +AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync( + model: TestConfiguration.AzureAI.DeploymentName!, + name: JokerName, + instructions: JokerInstructions); + +// Start a new thread for the agent conversation. +AgentThread thread = agent.GetNewThread(); + +// Respond to user input. +await RunAgentAsync("Tell me a joke about a pirate."); +await RunAgentAsync("Now add some emojis to the joke."); + +// Local function to run agent and display the conversation messages for the thread. +async Task RunAgentAsync(string input) +{ + Console.WriteLine( + $""" + User: {input} + Assistant: + {await agent.RunAsync(input, thread)} + + """); +} + +// Cleanup +await persistentAgentsClient.Threads.DeleteThreadAsync(thread.ConversationId); +await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id); +``` + +### 4. Create directly with Foundry SDK, orchestrate with Agent Framework + +- [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` +- [Foundry SDK] Clean up the agents + +```csharp +// Get a client to create server side agents with. +var persistentAgentsClient = new PersistentAgentsClient( + TestConfiguration.AzureAI.Endpoint, new AzureCliCredential()); +var model = TestConfiguration.OpenAI.ChatModelId; + +// Define the agents +AIAgent analystAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "Analyst", + instructions: + """ + You are a marketing analyst. Given a product description, identify: + - Key features + - Target audience + - Unique selling points + """, + description: "An agent that extracts key concepts from a product description."); +AIAgent writerAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "copywriter", + instructions: + """ + You are a marketing copywriter. Given a block of text describing features, audience, and USPs, + compose a compelling marketing copy (like a newsletter section) that highlights these points. + Output should be short (around 150 words), output just the copy as a single text block. + """, + description: "An agent that writes a marketing copy based on the extracted concepts."); +AIAgent editorAgent = + await persistentAgentsClient.CreateAIAgentAsync( + model, + name: "editor", + instructions: + """ + You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone, + give format and make it polished. Output the final improved copy as a single text block. + """, + description: "An agent that formats and proofreads the marketing copy."); + +// Define the orchestration +SequentialOrchestration orchestration = + new(analystAgent, writerAgent, editorAgent) + { + LoggerFactory = this.LoggerFactory, + }; + +// 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); +Console.WriteLine($"\n# RESULT: {result}"); + +// Cleanup +await persistentAgentsClient.Administration.DeleteAgentAsync(analystAgent.Id); +await persistentAgentsClient.Administration.DeleteAgentAsync(writerAgent.Id); +await persistentAgentsClient.Administration.DeleteAgentAsync(editorAgent.Id); +``` \ No newline at end of file diff --git a/docs/specs/spec-template.md b/docs/specs/spec-template.md new file mode 100644 index 0000000000..827ba044b5 --- /dev/null +++ b/docs/specs/spec-template.md @@ -0,0 +1,75 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: {proposed | rejected | accepted | deprecated | … | superseded by [SPEC-0001](0001-spec.md)} +contact: {person proposing the ADR} +date: {YYYY-MM-DD when the decision was last updated} +deciders: {list everyone involved in the decision} +consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication} +informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication} +--- + +# {short title of solved problem and solution} + +## What is the goal of this feature? + +Make sure to cover: +1. What is the value we are providing to users +1. Include one success metric +1. Implementation free description of outcome + +Consult PM on this. + +For example: + +We want users to be able to refer to external Azure resources easily when consuming them in other features like indexes, agents, +and evaluations. We know we're successful when 40% of project client users are using connections. + +## What is the problem being solved? + +Make sure to cover: +1. Why is this hard today? +1. Customer pain points? +1. Reducing system complexity (maintenance costs, latency, etc)? + +Consult PM on this. + +For example: + +Today, users have to understand control plane vs data plane endpoints and use multiple packages to stitch their application +code together. This makes using our product confusing and also increases the number of dependencies a customer will have +in their code. + +## API Changes + +List all new API changes + +## E2E Code Samples + +Include python or C# examples of how you expect this feature to be used with other things in our system. + +For example: + +This connection name is unique across the resource. Given a resource name, system should be able to unambiguously resolve a +connection name. A connection name can be used to pass along connection details to individual features. Services will be able to parse this ID and use it to access the underlying resource. The below example shows how a connection can be used to create a dataset. + +```python +client.datasets.create_dataset( + name="evaluation_dataset", + file="myblob/product1.pdf", + connection = "my-azure-blob-connection" +) +``` + +How to use a connection when creating an `AzureAISearchIndex` + +```python +from azure.ai.projects.models import AzureAISearchIndex + +azure_ai_search_index = AzureAISearchIndex( + name="azure-search-index", + connection="my-ai-search-connection", + index_name="my-index-in-azure-search", +) + +created_index = client.indexes.create_index(azure_ai_search_index) +``` diff --git a/dotnet/samples/GettingStarted/GettingStarted.csproj b/dotnet/samples/GettingStarted/GettingStarted.csproj index ba192b9f86..9b5ff872ee 100644 --- a/dotnet/samples/GettingStarted/GettingStarted.csproj +++ b/dotnet/samples/GettingStarted/GettingStarted.csproj @@ -14,7 +14,7 @@ $(ProjectsTargetFrameworks) $(ProjectsDebugTargetFrameworks) - + diff --git a/dotnet/samples/GettingStarted/Steps/Step09_ChatClientAgent_3rdPartyThreadStorage.cs b/dotnet/samples/GettingStarted/Steps/Step09_ChatClientAgent_3rdPartyThreadStorage.cs index db58f6f862..d411f4fcb6 100644 --- a/dotnet/samples/GettingStarted/Steps/Step09_ChatClientAgent_3rdPartyThreadStorage.cs +++ b/dotnet/samples/GettingStarted/Steps/Step09_ChatClientAgent_3rdPartyThreadStorage.cs @@ -27,23 +27,19 @@ public sealed class Step09_ChatClientAgent_3rdPartyThreadStorage(ITestOutputHelp [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)] public async Task ThirdPartyStorageThread(ChatClientProviders provider) { - var inMemoryVectorStore = new InMemoryVectorStore(); + VectorStore vectorStore = new InMemoryVectorStore(); // Define the options for the chat client agent. var agentOptions = new ChatClientAgentOptions { Name = JokerName, Instructions = JokerInstructions, - - // Get chat options based on the store type, if needed. - ChatOptions = base.GetChatOptions(provider), - ChatMessageStoreFactory = () => { // Create a new chat message store for this agent that stores the messages in a vector store. // Each thread must get its own copy of the VectorChatMessageStore, since the store // also contains the id that the thread is stored under. - return new VectorChatMessageStore(inMemoryVectorStore); + return new VectorChatMessageStore(vectorStore); } };