Building a Weather Forecast Agent in CSharp
Learn how to build a Weather Forecast Agent using the Microsoft 365 Agents SDK with .NET and C#. This tutorial will guide you through creating a simple bot that provides weather forecasts, providing a foundation for more complex bot development.
Prerequisites
- .NET SDK 8.0+
- Visual Studio 2022
- Microsoft 365 Agents Toolkit for Visual Studio
- A Microsoft 365 account (for testing)
- Basic knowledge of C#
1. Create a New C# Project
Open Visual Studio and click on Create a new project.
Search for 365 and select Microsoft 365 Agent template. Click Next.
Name your project (e.g., WeatherForecastAgentBot
), choose a location, and click Create.
Scroll down to select the Weather Agent template, also choose the .NET version (e.g., .NET 8.0 or later) then click Create.
Choose the LLM Service. You can either use Azure OpenAI or OpenAI. I will go ahead and choose Azure OpenAI. Fill out the values such as API Key, Endpoint, and Deployment Name.
You can go through our blog here for more information about creating Azure OpenAI Service and deploying a model.
This will create a new Microsoft 365 Agent project with the necessary files and dependencies.
2. Understand the Project Structure
Your solution will have the following structure:
Solution 'WeatherForecastAgentBot' (2 of 2 projects)
├── WeatherForecastAgentBot/
│ ├── Connected Services
│ ├── Dependencies
│ ├── Properties
│ ├── Bot/
│ │ └── Agents/
│ │ ├── WeatherForecastAgent.cs
│ │ └── WeatherForecastAgentResponse.cs
│ │ └── Plugins/
│ │ ├── AdaptiveCardPlugin.cs
│ │ ├── DateTimePlugin.cs
│ │ ├── WeatherForecast.cs
│ │ └── WeatherForecastPlugin.cs
│ ├── WeatherAgentBot.cs
│ ├── appsettings.json
│ ├── appsettings.Development.json
│ ├── appsettings.Playground.json
│ ├── AspNetExtensions.cs
│ ├── Config.cs
│ └── Program.cs
└── M365Agent/
├── appPackage/
│ ├── color.png
│ ├── manifest.json
│ └── outline.png
├── env/
│ ├── .env.dev
│ ├── .env.dev.user
│ ├── .env.local
│ └── .env.local.user
└── infra/
├── botRegistration/
│ ├── azurebot.bicep
│ └── readme.md
├── azure.bicep
├── azure.parameters.json
├── launchSettings.json
├── m365agents.local.yml
├── m365agents.yml
└── README.md
Key Components:
WeatherForecastAgentBot Project:
Bot/Agents/
: Contains the main weather agent logicWeatherForecastAgent.cs
: Main weather forecast agent implementationWeatherForecastAgentResponse.cs
: Response models for weather data
Bot/Plugins/
: Contains plugin implementations for weather functionalityAdaptiveCardPlugin.cs
: Handles adaptive card generation for weather displayDateTimePlugin.cs
: Manages date and time operationsWeatherForecast.cs
: Weather forecast data modelsWeatherForecastPlugin.cs
: Core weather forecast plugin logic
WeatherAgentBot.cs
: Main bot class that orchestrates the weather agentProgram.cs
: Entry point and bot configuration setupConfig.cs
: Configuration management for the applicationappsettings.json
: Application configuration settingsappsettings.Development.json
: Development-specific configurationappsettings.Playground.json
: Testing/playground configurationAspNetExtensions.cs
: Custom ASP.NET Core extensions
M365Agent Project:
appPackage/
: Contains the Microsoft 365 app manifest and iconsmanifest.json
: Defines the bot's capabilities and configurationcolor.png
&outline.png
: Bot icons for different display contexts
env/
: Environment configuration files for different deployment stagesinfra/
: Infrastructure as Code (Bicep) files for Azure deploymentbotRegistration/
: Azure Bot Service registration templatesazure.bicep
: Main infrastructure templatem365agents.yml
: Microsoft 365 Agents configuration
3. Understanding the Weather Agent Code
The starting point of the bot logic is implemented in the WeatherAgentBot.cs
file. Let's examine the key components:
using WeatherForecastAgentBot.Bot.Agents;
using Microsoft.Agents.Builder;
using Microsoft.Agents.Builder.App;
using Microsoft.Agents.Builder.State;
using Microsoft.Agents.Core.Models;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace WeatherForecastAgentBot.Bot;
public class WeatherAgentBot : AgentApplication
{
private WeatherForecastAgent _weatherAgent;
private Kernel _kernel;
public WeatherAgentBot(AgentApplicationOptions options, Kernel kernel) : base(options)
{
_kernel = kernel ?? throw new ArgumentNullException(nameof(kernel));
OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync);
OnActivity(ActivityTypes.Message, MessageActivityAsync, rank: RouteRank.Last);
}
protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
// Setup local service connection
ServiceCollection serviceCollection = [
new ServiceDescriptor(typeof(ITurnState), turnState),
new ServiceDescriptor(typeof(ITurnContext), turnContext),
new ServiceDescriptor(typeof(Kernel), _kernel),
];
// Start a Streaming Process
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on a response for you");
ChatHistory chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory());
_weatherAgent = new WeatherForecastAgent(_kernel, serviceCollection.BuildServiceProvider());
// Invoke the WeatherForecastAgent to process the message
WeatherForecastAgentResponse forecastResponse = await _weatherAgent.InvokeAgentAsync(turnContext.Activity.Text, chatHistory);
if (forecastResponse == null)
{
turnContext.StreamingResponse.QueueTextChunk("Sorry, I couldn't get the weather forecast at the moment.");
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);
return;
}
// Create a response message based on the response content type from the WeatherForecastAgent
// Send the response message back to the user.
switch (forecastResponse.ContentType)
{
case WeatherForecastAgentResponseContentType.Text:
turnContext.StreamingResponse.QueueTextChunk(forecastResponse.Content);
break;
case WeatherForecastAgentResponseContentType.AdaptiveCard:
turnContext.StreamingResponse.FinalMessage = MessageFactory.Attachment(new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = forecastResponse.Content,
});
break;
default:
break;
}
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken); // End the streaming response
}
protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
foreach (ChannelAccount member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome! I'm here to help with all your weather forecast needs!"), cancellationToken);
}
}
}
}
This is how the Weather Forecast Agent is initialized and invoked.
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using System.Text;
using System.Text.Json.Nodes;
using WeatherForecastAgentBot.Bot.Plugins;
namespace WeatherForecastAgentBot.Bot.Agents;
public class WeatherForecastAgent
{
private readonly Kernel _kernel;
private readonly ChatCompletionAgent _agent;
private const string AgentName = "WeatherForecastAgent";
private const string AgentInstructions = """
You are a friendly assistant that helps people find a weather forecast for a given time and place.
You may ask follow up questions until you have enough information to answer the customers question,
but once you have a forecast forecast, make sure to format it nicely using an adaptive card.
You should use adaptive JSON format to display the information in a visually appealing way and include a button for more details that points at https://www.msn.com/en-us/weather/forecast/in-{location}
You should use adaptive cards version 1.5 or later.
Respond in JSON format with the following JSON schema:
{
"contentType": "'Text' or 'AdaptiveCard' only",
"content": "{The content of the response, may be plain text, or JSON based adaptive card}"
}
""";
/// <summary>
/// Initializes a new instance of the <see cref="WeatherForecastAgent"/> class.
/// </summary>
/// <param name="kernel">An instance of <see cref="Kernel"/> for interacting with an LLM.</param>
public WeatherForecastAgent(Kernel kernel, IServiceProvider service)
{
_kernel = kernel;
// Define the agent
_agent =
new()
{
Instructions = AgentInstructions,
Name = AgentName,
Kernel = _kernel,
Arguments = new KernelArguments(new OpenAIPromptExecutionSettings()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
ResponseFormat = "json_object"
}),
};
// Give the agent some tools to work with
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<DateTimePlugin>(serviceProvider: service));
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<WeatherForecastPlugin>(serviceProvider: service));
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<AdaptiveCardPlugin>(serviceProvider: service));
}
/// <summary>
/// Invokes the agent with the given input and returns the response.
/// </summary>
/// <param name="input">A message to process.</param>
/// <returns>An instance of <see cref="WeatherForecastAgentResponse"/></returns>
public async Task<WeatherForecastAgentResponse> InvokeAgentAsync(string input, ChatHistory chatHistory)
{
ArgumentNullException.ThrowIfNull(chatHistory);
AgentThread thread = new ChatHistoryAgentThread();
ChatMessageContent message = new(AuthorRole.User, input);
chatHistory.Add(message);
StringBuilder sb = new();
await foreach (ChatMessageContent response in this._agent.InvokeAsync(chatHistory, thread: thread))
{
chatHistory.Add(response);
sb.Append(response.Content);
}
// Make sure the response is in the correct format and retry if necessary
try
{
string resultContent = sb.ToString();
var jsonNode = JsonNode.Parse(resultContent);
WeatherForecastAgentResponse result = new WeatherForecastAgentResponse()
{
Content = jsonNode["content"].ToString(),
ContentType = Enum.Parse<WeatherForecastAgentResponseContentType>(jsonNode["contentType"].ToString(), true)
};
return result;
}
catch (Exception je)
{
return await InvokeAgentAsync($"That response did not match the expected format. Please try again. Error: {je.Message}", chatHistory);
}
}
}
The Adaptive Card Plugin used in this agent allows creating rich interactive content in the chat interface in realtime.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using System.Threading.Tasks;
namespace WeatherForecastAgentBot.Bot.Plugins;
public class AdaptiveCardPlugin
{
private const string Instructions = """
When given data about the weather forecast for a given time and place, please generate an adaptive card
that displays the information in a visually appealing way. Make sure to only return the valid adaptive card
JSON string in the response.
""";
[KernelFunction]
public async Task<string> GetAdaptiveCardForData(Kernel kernel, string data)
{
// Create a chat history with the instructions as a system message and the data as a user message
ChatHistory chat = new(Instructions);
chat.Add(new ChatMessageContent(AuthorRole.User, data));
// Invoke the model to get a response
var chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
var response = await chatCompletion.GetChatMessageContentAsync(chat);
return response.ToString();
}
}
The Date Time Plugin just gets the current date and time.
using Microsoft.SemanticKernel;
using System.ComponentModel;
using System;
namespace WeatherForecastAgentBot.Bot.Plugins;
/// <summary>
/// Semantic Kernel plugins for date and time.
/// </summary>
public class DateTimePlugin
{
/// <summary>
/// Get the current date
/// </summary>
/// <example>
/// {{time.date}} => Sunday, 12 January, 2031
/// </example>
/// <returns> The current date </returns>
[KernelFunction, Description("Get the current date")]
public string Date(IFormatProvider formatProvider = null)
{
// Example: Sunday, 12 January, 2025
var date = DateTimeOffset.Now.ToString("D", formatProvider);
return date;
}
/// <summary>
/// Get the current date
/// </summary>
/// <example>
/// {{time.today}} => Sunday, 12 January, 2031
/// </example>
/// <returns> The current date </returns>
[KernelFunction, Description("Get the current date")]
public string Today(IFormatProvider formatProvider = null) =>
// Example: Sunday, 12 January, 2025
Date(formatProvider);
/// <summary>
/// Get the current date and time in the local time zone"
/// </summary>
/// <example>
/// {{time.now}} => Sunday, January 12, 2025 9:15 PM
/// </example>
/// <returns> The current date and time in the local time zone </returns>
[KernelFunction, Description("Get the current date and time in the local time zone")]
public string Now(IFormatProvider formatProvider = null) =>
// Sunday, January 12, 2025 9:15 PM
DateTimeOffset.Now.ToString("f", formatProvider);
}
And the Weather Forecast Plugin provides weather information. Currently, in the template, it just shows a random temperature. But, here it can call any weather API to get real-time data.
using Microsoft.Agents.Builder;
using Microsoft.SemanticKernel;
using System;
using System.Threading.Tasks;
namespace WeatherForecastAgentBot.Bot.Plugins;
public class WeatherForecastPlugin(ITurnContext turnContext)
{
/// <summary>
/// Retrieve the weather forecast for a specific date. This is a placeholder for a real implementation
/// and currently only returns a random temperature. This would typically call a weather service API.
/// </summary>
/// <param name="date">The date as a parsable string</param>
/// <param name="location">The location to get the weather for</param>
/// <returns></returns>
[KernelFunction]
public Task<WeatherForecast> GetForecastForDate(string date, string location)
{
string searchingForDate = date;
if (DateTime.TryParse(date, out DateTime searchingDate))
{
searchingForDate = searchingDate.ToLongDateString();
}
turnContext.StreamingResponse.QueueInformativeUpdateAsync($"Looking up the Weather in {location} for {searchingForDate}");
return Task.FromResult(new WeatherForecast
{
Date = date,
TemperatureC = Random.Shared.Next(-20, 55)
});
}
}
How the Weather Forecast Agent Works:
The Weather Forecast Agent is a sophisticated AI-powered bot that leverages the Microsoft 365 Agents SDK with Semantic Kernel for orchestration and Azure AI services for natural language understanding. Unlike a simple echo bot, this agent provides intelligent weather forecasting capabilities with rich adaptive card displays.
Agent Architecture:
The Weather Forecast Agent follows a layered architecture:
- WeatherAgentBot.cs - Main orchestrator that handles message routing and streaming responses
- WeatherForecastAgent.cs - Core AI agent using Semantic Kernel's ChatCompletionAgent
- Plugins - Specialized plugins for weather data, date/time, and adaptive card generation
Welcome Message Handler:
protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
foreach (ChannelAccount member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome! I'm here to help with all your weather forecast needs!"), cancellationToken);
}
}
}
When someone joins the chat, the bot welcomes them with a personalized message about weather forecasting assistance.
Intelligent Message Processing:
protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
{
// Start streaming response to show the bot is working
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on a response for you");
// Maintain conversation history for context-aware responses
ChatHistory chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory());
// Initialize the Weather Forecast Agent with Semantic Kernel
_weatherAgent = new WeatherForecastAgent(_kernel, serviceCollection.BuildServiceProvider());
// Process the user's message through the AI agent
WeatherForecastAgentResponse forecastResponse = await _weatherAgent.InvokeAgentAsync(turnContext.Activity.Text, chatHistory);
}
AI-Powered Response Generation:
The WeatherForecastAgent
uses Semantic Kernel's ChatCompletionAgent
with sophisticated instructions:
private const string AgentInstructions = """
You are a friendly assistant that helps people find a weather forecast for a given time and place.
You may ask follow up questions until you have enough information to answer the customers question,
but once you have a forecast, make sure to format it nicely using an adaptive card.
You should use adaptive JSON format to display the information in a visually appealing way.
""";
Plugin-Based Functionality:
The agent uses three specialized plugins:
- WeatherForecastPlugin - Retrieves weather data (currently mock data, can be extended to call real APIs)
- DateTimePlugin - Provides current date/time context for weather queries
- AdaptiveCardPlugin - Generates rich visual cards for weather display
Adaptive Response Rendering:
switch (forecastResponse.ContentType)
{
case WeatherForecastAgentResponseContentType.Text:
turnContext.StreamingResponse.QueueTextChunk(forecastResponse.Content);
break;
case WeatherForecastAgentResponseContentType.AdaptiveCard:
turnContext.StreamingResponse.FinalMessage = MessageFactory.Attachment(new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = forecastResponse.Content,
});
break;
}
Key Differentiators from Simple Bots:
- Context Awareness: Maintains conversation history for follow-up questions
- Streaming Responses: Shows real-time progress with informative updates
- Multi-modal Output: Supports both text and rich adaptive card responses
- Function Calling: Uses Semantic Kernel's function calling to access weather data
- Error Handling: Gracefully handles API failures and retry logic
- Structured Output: Enforces JSON schema for consistent response formatting
4. Run and Test Your Bot
Run your bot locally using Visual Studio:
- In Visual Studio, click Debug menu → Start Without Debugging (or press
Ctrl+F5
) - Alternatively, you can use the command line:
dotnet run
Recommended is to test the bot using Microsoft 365 Agents Playground (browser). It is already selected by default unless you want to test on Microsoft Teams client.
Test your bot using the Microsoft 365 Agents Playground. The Agent collects the location and date time, based on the collected details, it then generates an Adaptive Card showing the weather forecast.
🎥 Detailed Video
Next Steps
- Explore more features in the Microsoft 365 Agents SDK documentation
- Add authentication, adaptive cards, or integrate with other Microsoft 365 services
Happy coding! If you have questions, leave a comment below or check out the official docs for more advanced scenarios