From b2a6b64d722ac0551dd51eca95847f94194d14f8 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:04:31 +0100 Subject: [PATCH] .NET: Add Ollama and custom agent samples, plus ONNX improvements (#639) * Add ollama and custom agent samples, plus onnx improvements * Update dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR comments. * Address pr comments. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dotnet/Directory.Packages.props | 1 + dotnet/agent-framework-dotnet.slnx | 3 + .../Agent_With_CustomImplementation.csproj | 16 +++ .../Program.cs | 102 ++++++++++++++++++ .../Agent_With_CustomImplementation/README.md | 3 + .../AgentProviders/Agent_With_ONNX/Program.cs | 1 + .../AgentProviders/Agent_With_ONNX/README.md | 8 ++ .../Agent_With_Ollama.csproj | 20 ++++ .../Agent_With_Ollama/Program.cs | 20 ++++ .../Agent_With_Ollama/README.md | 34 ++++++ .../GettingStarted/AgentProviders/README.md | 3 + dotnet/samples/GettingStarted/README.md | 4 +- 12 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/README.md create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Program.cs create mode 100644 dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 85769f892d..a4a699b780 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -79,6 +79,7 @@ + diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index e764f4c936..f46ab37c4f 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -25,6 +25,8 @@ + + @@ -43,6 +45,7 @@ + diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj new file mode 100644 index 0000000000..4fa7edf3fc --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + 12 + + enable + disable + + + + + + + diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs new file mode 100644 index 0000000000..df315dbfaa --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows all the required steps to create a fully custom agent implementation. +// In this case the agent doesn't use AI at all, and simply parrots back the user input in upper case. +// You can however, build a fully custom agent that uses AI in any way you want. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI.Agents; +using SampleApp; + +AIAgent agent = new UpperCaseParrotAgent(); + +// Invoke the agent and output the text result. +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.")); + +// Invoke the agent with streaming support. +await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.")) +{ + Console.WriteLine(update); +} + +namespace SampleApp +{ + // Custom agent that parrot's the user input back in upper case. + internal sealed class UpperCaseParrotAgent : AIAgent + { + public override async Task RunAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + // Create a thread if the user didn't supply one. + thread ??= this.GetNewThread(); + + // Clone the input messages and turn them into response messages with upper case text. + List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList(); + + // Notify the thread of the input and output messages. + await NotifyThreadOfNewMessagesAsync(thread, messages, cancellationToken); + await NotifyThreadOfNewMessagesAsync(thread, responseMessages, cancellationToken); + + return new AgentRunResponse + { + AgentId = this.Id, + ResponseId = Guid.NewGuid().ToString(), + Messages = responseMessages + }; + } + + public override async IAsyncEnumerable RunStreamingAsync(IReadOnlyCollection messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + // Create a thread if the user didn't supply one. + thread ??= this.GetNewThread(); + + // Clone the input messages and turn them into response messages with upper case text. + List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList(); + + // Notify the thread of the input and output messages. + await NotifyThreadOfNewMessagesAsync(thread, messages, cancellationToken); + await NotifyThreadOfNewMessagesAsync(thread, responseMessages, cancellationToken); + + foreach (var message in responseMessages) + { + yield return new AgentRunResponseUpdate + { + AgentId = this.Id, + AuthorName = this.DisplayName, + Role = ChatRole.Assistant, + Contents = message.Contents, + ResponseId = Guid.NewGuid().ToString(), + MessageId = Guid.NewGuid().ToString() + }; + } + } + + private static IEnumerable CloneAndToUpperCase(IReadOnlyCollection messages, string agentName) => messages.Select(x => + { + // Clone the message and update its author to be the agent. + var messageClone = x.Clone(); + messageClone.Role = ChatRole.Assistant; + messageClone.MessageId = Guid.NewGuid().ToString(); + messageClone.AuthorName = agentName; + + // Clone and convert any text content to upper case. + messageClone.Contents = x.Contents.Select(c => c switch + { + TextContent tc => new TextContent(tc.Text.ToUpperInvariant()) + { + AdditionalProperties = tc.AdditionalProperties, + Annotations = tc.Annotations, + RawRepresentation = tc.RawRepresentation + }, + _ => c + }).ToList(); + + return messageClone; + }); + } +} diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/README.md new file mode 100644 index 0000000000..97eb3e87d3 --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/README.md @@ -0,0 +1,3 @@ +# Prerequisites + +This sample has no prerequisites. \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Program.cs index 94994dd92d..8a2579b1ff 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Program.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // This sample shows how to create and use a simple AI agent with ONNX as the backend. +// WARNING: ONNX doesn't support function calling, so any function tools passed to the agent will be ignored. using System; using Microsoft.Extensions.AI.Agents; diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md index 7bfacf30e9..cb86e0d7c4 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md @@ -1,10 +1,18 @@ # Prerequisites +WARNING: ONNX doesn't support function calling, so any function tools passed to the agent will be ignored. + Before you begin, ensure you have the following prerequisites: - .NET 8.0 SDK or later - An ONNX model downloaded to your machine +You can download an ONNX model from hugging face, using git clone: + +```powershell +git clone https://huggingface.co/microsoft/Phi-4-mini-instruct-onnx +``` + Set the following environment variables: ```powershell diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj new file mode 100644 index 0000000000..44603cc24c --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + 12 + + enable + disable + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Program.cs new file mode 100644 index 0000000000..9a8b028f5f --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Program.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to create and use a simple AI agent with Ollama as the backend. + +using System; +using Microsoft.Extensions.AI.Agents; +using OllamaSharp; + +var endpoint = Environment.GetEnvironmentVariable("OLLAMA_ENDPOINT") ?? throw new InvalidOperationException("OLLAMA_ENDPOINT is not set."); +var modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set."); + +const string JokerName = "Joker"; +const string JokerInstructions = "You are good at telling jokes."; + +// Get a chat client for Ollama and use it to construct an AIAgent. +using OllamaApiClient chatClient = new(new Uri(endpoint), modelName); +AIAgent agent = new ChatClientAgent(chatClient, JokerInstructions, JokerName); + +// Invoke the agent and output the text result. +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.")); diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md new file mode 100644 index 0000000000..be76a75de0 --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md @@ -0,0 +1,34 @@ +# Prerequisites + +Before you begin, ensure you have the following prerequisites: + +- .NET 8.0 SDK or later +- Docker installed and running on your machine +- An Ollama model downloaded into Ollama + +To download and start Ollama on Docker using CPU, run the following command in your terminal. + +```powershell +docker run -d -v "c:\temp\ollama:/root/.ollama" -p 11434:11434 --name ollama ollama/ollama +``` + +To download and start Ollama on Docker using GPU, run the following command in your terminal. + +```powershell +docker run -d --gpus=all -v "c:\temp\ollama:/root/.ollama" -p 11434:11434 --name ollama ollama/ollama +``` + +After the container has started, launch a Terminal window for the docker container, e.g. if using docker desktop, choose Open in Terminal from actions. + +From this terminal download the required models, e.g. here we are downloading the phi3 model. + +```text +ollama pull gpt-oss +``` + +Set the following environment variables: + +```powershell +$env:OLLAMA_ENDPOINT="http://localhost:11434" +$env:OLLAMA_MODEL_NAME="gpt-oss" +``` diff --git a/dotnet/samples/GettingStarted/AgentProviders/README.md b/dotnet/samples/GettingStarted/AgentProviders/README.md index 981e60d9b8..0b5fbf1a74 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/README.md +++ b/dotnet/samples/GettingStarted/AgentProviders/README.md @@ -9,6 +9,7 @@ see the [Getting Started Steps](../GettingStartedSteps/README.md) samples. ## Prerequisites See the README.md for each sample for the prerequisites for that sample. + ## Samples |Sample|Description| @@ -17,6 +18,8 @@ See the README.md for each sample for the prerequisites for that sample. |[Creating an AIAgent with AzureFoundry](./Agent_With_AzureFoundry/)|This sample demonstrates how to create an Azure Foundry agent and expose it as an AIAgent| |[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service| |[Creating an AIAgent with Azure OpenAI Responses](./Agent_With_AzureOpenAIResponses/)|This sample demonstrates how to create an AIAgent using Azure OpenAI Responses as the underlying inference service| +|[Creating an AIAgent with a custom implementation](./Agent_With_CustomImplementation/)|This sample demonstrates how to create an AIAgent with a custom implementation| +|[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service| |[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service| |[Creating an AIAgent with OpenAI ChatCompletion](./Agent_With_OpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using OpenAI ChatCompletion as the underlying inference service| |[Creating an AIAgent with OpenAI Responses](./Agent_With_OpenAIResponses/)|This sample demonstrates how to create an AIAgent using OpenAI Responses as the underlying inference service| diff --git a/dotnet/samples/GettingStarted/README.md b/dotnet/samples/GettingStarted/README.md index 55cc7f7a97..e95b96c923 100644 --- a/dotnet/samples/GettingStarted/README.md +++ b/dotnet/samples/GettingStarted/README.md @@ -7,6 +7,8 @@ of the agent framework. |Sample|Description| |---|---| -|[Agents](./Agents/README.md)|Getting started with agents| +|[Agents](./Agents/README.md)|Step by step instructions for getting started with agents| |[Agent Providers](./AgentProviders/README.md)|Getting started with creating agents using various providers| |[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents| +|[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents| +|[Workflow](./Workflow/README.md)|Getting started with Workflow|