chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Build results
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# User-specific files
|
||||
*.user
|
||||
*.userosscache
|
||||
*.suo
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# NuGet
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
@@ -0,0 +1,163 @@
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using Microsoft.AspNetCore.Http.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenAI;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Add(ProverbsAgentSerializerContext.Default));
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
// Create the agent factory and map the AG-UI agent endpoint
|
||||
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
||||
var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>();
|
||||
var agentFactory = new ProverbsAgentFactory(builder.Configuration, loggerFactory, jsonOptions.Value.SerializerOptions);
|
||||
|
||||
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
|
||||
app.MapAGUI("/", agentFactory.CreateProverbsAgent());
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
// =================
|
||||
// State Management
|
||||
// =================
|
||||
public class ProverbsState
|
||||
{
|
||||
public List<string> Proverbs { get; set; } = [];
|
||||
}
|
||||
|
||||
// =================
|
||||
// Agent Factory
|
||||
// =================
|
||||
public class ProverbsAgentFactory
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ProverbsState _state;
|
||||
private readonly OpenAIClient _openAiClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly System.Text.Json.JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
public ProverbsAgentFactory(IConfiguration configuration, ILoggerFactory loggerFactory, System.Text.Json.JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_state = new();
|
||||
_logger = loggerFactory.CreateLogger<ProverbsAgentFactory>();
|
||||
_jsonSerializerOptions = jsonSerializerOptions;
|
||||
|
||||
// Get the GitHub token from configuration
|
||||
var githubToken = _configuration["GitHubToken"]
|
||||
?? throw new InvalidOperationException(
|
||||
"GitHubToken not found in configuration. " +
|
||||
"Please set it using: dotnet user-secrets set GitHubToken \"<your-token>\" " +
|
||||
"or get it using: gh auth token");
|
||||
|
||||
_openAiClient = new(
|
||||
new System.ClientModel.ApiKeyCredential(githubToken),
|
||||
new OpenAIClientOptions
|
||||
{
|
||||
Endpoint = new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL") ?? "https://models.inference.ai.azure.com")
|
||||
});
|
||||
}
|
||||
|
||||
public AIAgent CreateProverbsAgent()
|
||||
{
|
||||
var chatClient = _openAiClient.GetChatClient("gpt-4o-mini").AsIChatClient();
|
||||
|
||||
var chatClientAgent = new ChatClientAgent(
|
||||
chatClient,
|
||||
name: "ProverbsAgent",
|
||||
description: @"A helpful assistant that helps manage and discuss proverbs.
|
||||
You have tools available to add, set, or retrieve proverbs from the list.
|
||||
When discussing proverbs, ALWAYS use the get_proverbs tool to see the current list before mentioning, updating, or discussing proverbs with the user.",
|
||||
tools: [
|
||||
AIFunctionFactory.Create(GetProverbs, options: new() { Name = "get_proverbs", SerializerOptions = _jsonSerializerOptions }),
|
||||
AIFunctionFactory.Create(AddProverbs, options: new() { Name = "add_proverbs", SerializerOptions = _jsonSerializerOptions }),
|
||||
AIFunctionFactory.Create(SetProverbs, options: new() { Name = "set_proverbs", SerializerOptions = _jsonSerializerOptions }),
|
||||
AIFunctionFactory.Create(GetWeather, options: new() { Name = "get_weather", SerializerOptions = _jsonSerializerOptions })
|
||||
]);
|
||||
|
||||
return new SharedStateAgent(chatClientAgent, _jsonSerializerOptions);
|
||||
}
|
||||
|
||||
// =================
|
||||
// Tools
|
||||
// =================
|
||||
|
||||
[Description("Get the current list of proverbs.")]
|
||||
private List<string> GetProverbs()
|
||||
{
|
||||
_logger.LogInformation("📖 Getting proverbs: {Proverbs}", string.Join(", ", _state.Proverbs));
|
||||
return _state.Proverbs;
|
||||
}
|
||||
|
||||
[Description("Add new proverbs to the list.")]
|
||||
private void AddProverbs([Description("The proverbs to add")] List<string> proverbs)
|
||||
{
|
||||
_logger.LogInformation("➕ Adding proverbs: {Proverbs}", string.Join(", ", proverbs));
|
||||
_state.Proverbs.AddRange(proverbs);
|
||||
}
|
||||
|
||||
[Description("Replace the entire list of proverbs.")]
|
||||
private void SetProverbs([Description("The new list of proverbs")] List<string> proverbs)
|
||||
{
|
||||
_logger.LogInformation("📝 Setting proverbs: {Proverbs}", string.Join(", ", proverbs));
|
||||
_state.Proverbs = [.. proverbs];
|
||||
}
|
||||
|
||||
[Description("Get the weather for a given location. Ensure location is fully spelled out.")]
|
||||
private WeatherInfo GetWeather([Description("The location to get the weather for")] string location)
|
||||
{
|
||||
_logger.LogInformation("🌤️ Getting weather for: {Location}", location);
|
||||
return new()
|
||||
{
|
||||
Temperature = 20,
|
||||
Conditions = "sunny",
|
||||
Humidity = 50,
|
||||
WindSpeed = 10,
|
||||
FeelsLike = 25
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// Data Models
|
||||
// =================
|
||||
|
||||
public class ProverbsStateSnapshot
|
||||
{
|
||||
[JsonPropertyName("proverbs")]
|
||||
public List<string> Proverbs { get; set; } = [];
|
||||
}
|
||||
|
||||
public class WeatherInfo
|
||||
{
|
||||
[JsonPropertyName("temperature")]
|
||||
public int Temperature { get; init; }
|
||||
|
||||
[JsonPropertyName("conditions")]
|
||||
public string Conditions { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("humidity")]
|
||||
public int Humidity { get; init; }
|
||||
|
||||
[JsonPropertyName("wind_speed")]
|
||||
public int WindSpeed { get; init; }
|
||||
|
||||
[JsonPropertyName("feelsLike")]
|
||||
public int FeelsLike { get; init; }
|
||||
}
|
||||
|
||||
public partial class Program { }
|
||||
|
||||
// =================
|
||||
// Serializer Context
|
||||
// =================
|
||||
[JsonSerializable(typeof(ProverbsStateSnapshot))]
|
||||
[JsonSerializable(typeof(WeatherInfo))]
|
||||
internal sealed partial class ProverbsAgentSerializerContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:8000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>proverbs-agent-12345678-1234-1234-1234-123456789abc</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Agents.AI.Hosting.AGUI.AspNetCore" Version="1.0.0-preview.251110.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.10.2-preview.1.25552.1" />
|
||||
<PackageReference Include="OpenAI" Version="2.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ProverbsAgentFactory")]
|
||||
internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
public SharedStateAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
|
||||
: base(innerAgent)
|
||||
{
|
||||
_jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options is not ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } chatRunOptions ||
|
||||
!properties.TryGetValue("ag_ui_state", out JsonElement state))
|
||||
{
|
||||
await foreach (var update in InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
var firstRunOptions = new ChatClientAgentRunOptions
|
||||
{
|
||||
ChatOptions = chatRunOptions.ChatOptions.Clone(),
|
||||
AllowBackgroundResponses = chatRunOptions.AllowBackgroundResponses,
|
||||
ContinuationToken = chatRunOptions.ContinuationToken,
|
||||
ChatClientFactory = chatRunOptions.ChatClientFactory,
|
||||
};
|
||||
|
||||
// Configure JSON schema response format for structured state output
|
||||
firstRunOptions.ChatOptions.ResponseFormat = ChatResponseFormat.ForJsonSchema<ProverbsStateSnapshot>(
|
||||
schemaName: "ProverbsStateSnapshot",
|
||||
schemaDescription: "A response containing the current list of proverbs");
|
||||
|
||||
ChatMessage stateUpdateMessage = new(
|
||||
ChatRole.System,
|
||||
[
|
||||
new TextContent("Here is the current state in JSON format:"),
|
||||
new TextContent(state.GetRawText()),
|
||||
new TextContent("The new state is:")
|
||||
]);
|
||||
|
||||
var firstRunMessages = messages.Append(stateUpdateMessage);
|
||||
|
||||
var allUpdates = new List<AgentRunResponseUpdate>();
|
||||
await foreach (var update in InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
allUpdates.Add(update);
|
||||
|
||||
// Yield all non-text updates (tool calls, etc.)
|
||||
bool hasNonTextContent = update.Contents.Any(c => c is not TextContent);
|
||||
if (hasNonTextContent)
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
|
||||
var response = allUpdates.ToAgentRunResponse();
|
||||
|
||||
if (response.TryDeserialize(_jsonSerializerOptions, out JsonElement stateSnapshot))
|
||||
{
|
||||
byte[] stateBytes = JsonSerializer.SerializeToUtf8Bytes(
|
||||
stateSnapshot,
|
||||
_jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)));
|
||||
yield return new AgentRunResponseUpdate
|
||||
{
|
||||
Contents = [new DataContent(stateBytes, "application/json")]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var secondRunMessages = messages.Concat(response.Messages).Append(
|
||||
new ChatMessage(
|
||||
ChatRole.System,
|
||||
[new TextContent("Please provide a concise summary of the state changes in at most two sentences.")]));
|
||||
|
||||
await foreach (var update in InnerAgent.RunStreamingAsync(secondRunMessages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user