test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled

This commit is contained in:
2026-01-24 03:05:12 +11:00
parent f78f2388b3
commit 539852f81c
2584 changed files with 287471 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using AdaptiveCards;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace M365Agent.Agents;
/// <summary>
/// An <see cref="AIContent"/> type allows an <see cref="AIAgent"/> to return adaptive cards as part of its response messages.
/// </summary>
internal sealed class AdaptiveCardAIContent : AIContent
{
public AdaptiveCardAIContent(AdaptiveCard adaptiveCard)
{
this.AdaptiveCard = adaptiveCard ?? throw new ArgumentNullException(nameof(adaptiveCard));
}
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
[JsonConstructor]
public AdaptiveCardAIContent(string adaptiveCardJson)
{
this.AdaptiveCardJson = adaptiveCardJson;
}
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
[JsonIgnore]
public AdaptiveCard AdaptiveCard { get; private set; }
public string AdaptiveCardJson
{
get => this.AdaptiveCard.ToJson();
set => this.AdaptiveCard = AdaptiveCard.FromJson(value).Card;
}
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json;
using AdaptiveCards;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace M365Agent.Agents;
/// <summary>
/// A weather forecasting agent. This agent wraps a <see cref="ChatClientAgent"/> and adds custom logic
/// to generate adaptive cards for weather forecasts and add these to the agent's response.
/// </summary>
public class WeatherForecastAgent : DelegatingAIAgent
{
private const string AgentName = "WeatherForecastAgent";
private const string AgentInstructions = """
You are a friendly assistant that helps people find a weather forecast for a given location.
You may ask follow up questions until you have enough information to answer the customers question.
When answering with a weather forecast, fill out the weatherCard property with an adaptive card containing the weather information and
add some emojis to indicate the type of weather.
When answering with just text, fill out the context property with a friendly response.
""";
/// <summary>
/// Initializes a new instance of the <see cref="WeatherForecastAgent"/> class.
/// </summary>
/// <param name="chatClient">An instance of <see cref="IChatClient"/> for interacting with an LLM.</param>
public WeatherForecastAgent(IChatClient chatClient)
: base(new ChatClientAgent(
chatClient: chatClient,
new ChatClientAgentOptions()
{
Name = AgentName,
ChatOptions = new ChatOptions()
{
Instructions = AgentInstructions,
Tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))],
// We want the agent to return structured output in a known format
// so that we can easily create adaptive cards from the response.
ResponseFormat = ChatResponseFormat.ForJsonSchema(
schema: AIJsonUtilities.CreateJsonSchema(typeof(WeatherForecastAgentResponse)),
schemaName: "WeatherForecastAgentResponse",
schemaDescription: "Response to a query about the weather in a specified location"),
}
}))
{
}
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
var response = await base.RunCoreAsync(messages, thread, options, cancellationToken);
// If the agent returned a valid structured output response
// we might be able to enhance the response with an adaptive card.
if (response.TryDeserialize<WeatherForecastAgentResponse>(JsonSerializerOptions.Web, out var structuredOutput))
{
var textContentMessage = response.Messages.FirstOrDefault(x => x.Contents.OfType<TextContent>().Any());
if (textContentMessage is not null)
{
// If the response contains weather information, create an adaptive card.
if (structuredOutput.ContentType == WeatherForecastAgentResponseContentType.WeatherForecastAgentResponse)
{
var card = CreateWeatherCard(structuredOutput.Location, structuredOutput.MeteorologicalCondition, structuredOutput.TemperatureInCelsius);
textContentMessage.Contents.Add(new AdaptiveCardAIContent(card));
}
// If the response is just text, replace the structured output with the text response.
if (structuredOutput.ContentType == WeatherForecastAgentResponseContentType.OtherAgentResponse)
{
var textContent = textContentMessage.Contents.OfType<TextContent>().First();
textContent.Text = structuredOutput.OtherResponse;
}
}
}
return response;
}
/// <summary>
/// A mock weather tool, to get weather information for a given location.
/// </summary>
[Description("Get the weather for a given location.")]
private static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
/// <summary>
/// Create an adaptive card to display weather information.
/// </summary>
private static AdaptiveCard CreateWeatherCard(string? location, string? condition, string? temperature)
{
var card = new AdaptiveCard("1.5");
card.Body.Add(new AdaptiveTextBlock
{
Text = "🌤️ Weather Forecast 🌤️",
Size = AdaptiveTextSize.Large,
Weight = AdaptiveTextWeight.Bolder,
HorizontalAlignment = AdaptiveHorizontalAlignment.Center
});
card.Body.Add(new AdaptiveTextBlock
{
Text = "Location: " + location,
});
card.Body.Add(new AdaptiveTextBlock
{
Text = "Condition: " + condition,
});
card.Body.Add(new AdaptiveTextBlock
{
Text = "Temperature: " + temperature,
});
return card;
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json.Serialization;
namespace M365Agent.Agents;
/// <summary>
/// The structured output type for the <see cref="WeatherForecastAgent"/>.
/// </summary>
internal sealed class WeatherForecastAgentResponse
{
/// <summary>
/// A value indicating whether the response contains a weather forecast or some other type of response.
/// </summary>
[JsonPropertyName("contentType")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public WeatherForecastAgentResponseContentType ContentType { get; set; }
/// <summary>
/// If the agent could not provide a weather forecast this should contain a textual response.
/// </summary>
[Description("If the answer is other agent response, contains the textual agent response.")]
[JsonPropertyName("otherResponse")]
public string? OtherResponse { get; set; }
/// <summary>
/// The location for which the weather forecast is given.
/// </summary>
[Description("If the answer is a weather forecast, contains the location for which the forecast is given.")]
[JsonPropertyName("location")]
public string? Location { get; set; }
/// <summary>
/// The temperature in Celsius for the given location.
/// </summary>
[Description("If the answer is a weather forecast, contains the temperature in Celsius.")]
[JsonPropertyName("temperatureInCelsius")]
public string? TemperatureInCelsius { get; set; }
/// <summary>
/// The meteorological condition for the given location.
/// </summary>
[Description("If the answer is a weather forecast, contains the meteorological condition (e.g., Sunny, Rainy).")]
[JsonPropertyName("meteorologicalCondition")]
public string? MeteorologicalCondition { get; set; }
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace M365Agent.Agents;
/// <summary>
/// The type of content contained in a <see cref="WeatherForecastAgentResponse"/>.
/// </summary>
internal enum WeatherForecastAgentResponseContentType
{
[JsonPropertyName("otherAgentResponse")]
OtherAgentResponse,
[JsonPropertyName("weatherForecastAgentResponse")]
WeatherForecastAgentResponse
}