diff --git a/dotnet/SK-dotnet.slnx b/dotnet/SK-dotnet.slnx index 835c32f258a1..ea1e02fd7de6 100644 --- a/dotnet/SK-dotnet.slnx +++ b/dotnet/SK-dotnet.slnx @@ -27,7 +27,9 @@ - + + + @@ -37,6 +39,7 @@ + diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj new file mode 100644 index 000000000000..a500b427e031 --- /dev/null +++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj @@ -0,0 +1,30 @@ + + + + Exe + net9.0 + enable + enable + $(NoWarn);CA1707;CA2007;VSTHRD111 + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json new file mode 100644 index 000000000000..84715914da63 --- /dev/null +++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json @@ -0,0 +1,354 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Github Versions API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.github.com" + } + ], + "components": { + "schemas": { + "basic-error": { + "title": "Basic Error", + "description": "Basic Error", + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "documentation_url": { + "type": "string" + }, + "url": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "label": { + "title": "Label", + "description": "Color-coded labels help you categorize and filter your issues (just like labels in Gmail).", + "type": "object", + "properties": { + "id": { + "description": "Unique identifier for the label.", + "type": "integer", + "format": "int64", + "example": 208045946 + }, + "node_id": { + "type": "string", + "example": "MDU6TGFiZWwyMDgwNDU5NDY=" + }, + "url": { + "description": "URL for the label", + "example": "https://api.github.com/repositories/42/labels/bug", + "type": "string", + "format": "uri" + }, + "name": { + "description": "The name of the label.", + "example": "bug", + "type": "string" + }, + "description": { + "description": "Optional description of the label, such as its purpose.", + "type": "string", + "example": "Something isn't working", + "nullable": true + }, + "color": { + "description": "6-character hex code, without the leading #, identifying the color", + "example": "FFFFFF", + "type": "string" + }, + "default": { + "description": "Whether this label comes by default in a new repository.", + "type": "boolean", + "example": true + } + }, + "required": [ + "id", + "node_id", + "url", + "name", + "description", + "color", + "default" + ] + }, + "tag": { + "title": "Tag", + "description": "Tag", + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "v0.1" + }, + "commit": { + "type": "object", + "properties": { + "sha": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "sha", + "url" + ] + }, + "zipball_url": { + "type": "string", + "format": "uri", + "example": "https://github.com/octocat/Hello-World/zipball/v0.1" + }, + "tarball_url": { + "type": "string", + "format": "uri", + "example": "https://github.com/octocat/Hello-World/tarball/v0.1" + }, + "node_id": { + "type": "string" + } + }, + "required": [ + "name", + "node_id", + "commit", + "zipball_url", + "tarball_url" + ] + } + }, + "examples": { + "label-items": { + "value": [ + { + "id": 208045946, + "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", + "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", + "name": "bug", + "description": "Something isn't working", + "color": "f29513", + "default": true + }, + { + "id": 208045947, + "node_id": "MDU6TGFiZWwyMDgwNDU5NDc=", + "url": "https://api.github.com/repos/octocat/Hello-World/labels/enhancement", + "name": "enhancement", + "description": "New feature or request", + "color": "a2eeef", + "default": false + } + ] + }, + "tag-items": { + "value": [ + { + "name": "v0.1", + "commit": { + "sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc", + "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" + }, + "zipball_url": "https://github.com/octocat/Hello-World/zipball/v0.1", + "tarball_url": "https://github.com/octocat/Hello-World/tarball/v0.1", + "node_id": "MDQ6VXNlcjE=" + } + ] + } + }, + "parameters": { + "owner": { + "name": "owner", + "description": "The account owner of the repository. The name is not case sensitive.", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + "repo": { + "name": "repo", + "description": "The name of the repository without the `.git` extension. The name is not case sensitive.", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + "per-page": { + "name": "per_page", + "description": "The number of results per page (max 100). For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"", + "in": "query", + "schema": { + "type": "integer", + "default": 30 + } + }, + "page": { + "name": "page", + "description": "The page number of the results to fetch. For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + } + }, + "responses": { + "not_found": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/basic-error" + } + } + } + } + }, + "headers": { + "link": { + "example": "; rel=\"next\", ; rel=\"last\"", + "schema": { + "type": "string" + } + } + } + }, + "paths": { + "/repos/{owner}/{repo}/tags": { + "get": { + "summary": "List repository tags", + "description": "", + "tags": [ + "repos" + ], + "operationId": "repos/list-tags", + "externalDocs": { + "description": "API method documentation", + "url": "https://docs.github.com/rest/repos/repos#list-repository-tags" + }, + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo" + }, + { + "$ref": "#/components/parameters/per-page" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tag" + } + }, + "examples": { + "default": { + "$ref": "#/components/examples/tag-items" + } + } + } + }, + "headers": { + "Link": { + "$ref": "#/components/headers/link" + } + } + } + }, + "x-github": { + "githubCloudOnly": false, + "enabledForGitHubApps": true, + "category": "repos", + "subcategory": "repos" + } + } + }, + "/repos/{owner}/{repo}/labels": { + "get": { + "summary": "List labels for a repository", + "description": "Lists all labels for a repository.", + "tags": [ + "issues" + ], + "operationId": "issues/list-labels-for-repo", + "externalDocs": { + "description": "API method documentation", + "url": "https://docs.github.com/rest/issues/labels#list-labels-for-a-repository" + }, + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo" + }, + { + "$ref": "#/components/parameters/per-page" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/label" + } + }, + "examples": { + "default": { + "$ref": "#/components/examples/label-items" + } + } + } + }, + "headers": { + "Link": { + "$ref": "#/components/headers/link" + } + } + }, + "404": { + "$ref": "#/components/responses/not_found" + } + }, + "x-github": { + "githubCloudOnly": false, + "enabledForGitHubApps": true, + "category": "issues", + "subcategory": "labels" + } + } + } + } +} \ No newline at end of file diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs new file mode 100644 index 000000000000..5898050f5d7a --- /dev/null +++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample demonstrates how to use an Agent with function tools provided via an OpenAPI spec with both Semantic Kernel and Agent Framework. + +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Plugins.OpenApi; +using OpenAI; + +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"; + +await SKAgent(); +await AFAgent(); + +async Task SKAgent() +{ + Console.WriteLine("\n=== SK Agent ===\n"); + + // Create a kernel with an Azure OpenAI chat client. + var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential()).Build(); + + // Load the OpenAPI Spec from a file. + var plugin = await kernel.ImportPluginFromOpenApiAsync("github", "OpenAPISpec.json"); + + // Create the agent, and provide the kernel with the OpenAPI function tools to the agent. + var agent = new ChatCompletionAgent() + { + Kernel = kernel, + Instructions = "You are a helpful assistant", + Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), + }; + + // Run the agent with the OpenAPI function tools. + await foreach (var result in agent.InvokeAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github.")) + { + Console.WriteLine(result.Message); + } +} + +async Task AFAgent() +{ + Console.WriteLine("\n=== AF Agent ===\n"); + + // Load the OpenAPI Spec from a file. + KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("github", "OpenAPISpec.json"); + + // Convert the Semantic Kernel plugin to Agent Framework function tools. + // This requires a dummy Kernel instance, since KernelFunctions cannot execute without one. + Kernel kernel = new(); + List tools = plugin.Select(x => x.WithKernel(kernel)).Cast().ToList(); + + // Create the chat client and agent, and provide the OpenAPI function tools to the agent. + AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetChatClient(deploymentName) + .CreateAIAgent(instructions: "You are a helpful assistant", tools: tools); + + // Run the agent with the OpenAPI function tools. + Console.WriteLine(await agent.RunAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github.")); +}