mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
c974b01721
Program.cs#CreateOpenAiClient previously read the OpenAI endpoint solely from the OPENAI_BASE_URL environment variable and fell back to ApiKeyResolver.DefaultOpenAiEndpoint. The secondary tool-calling HTTP client (A2uiSecondaryToolCaller) instead routes through ApiKeyResolver.ResolveEndpoint, which checks env, then configuration[OPENAI_BASE_URL] (appsettings.json / user-secrets), then the default. When OPENAI_BASE_URL was supplied only via configuration (e.g. user-secrets in local dev), the primary client silently dialed the public Azure-hosted models endpoint while the secondary client dialed the configured aimock. The two endpoints diverged with no visible signal, breaking aimock-routed flows and masking misconfigurations. Switch CreateOpenAiClient to call ApiKeyResolver.ResolveEndpoint so both clients share a single source of truth. Logging is preserved and now reports which source supplied the endpoint (env, configuration, or default).
102 lines
3.6 KiB
C#
102 lines
3.6 KiB
C#
using System.ClientModel;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
|
using Microsoft.AspNetCore.Http.Json;
|
|
using Microsoft.Extensions.Options;
|
|
using OpenAI;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
{
|
|
options.SerializerOptions.TypeInfoResolverChain.Add(BeautifulChatSerializerContext.Default);
|
|
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
|
});
|
|
builder.Services.AddAGUI();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Forward D5/aimock x-* headers from incoming AG-UI requests to outgoing
|
|
// OpenAI calls until the .NET SDK owns this propagation centrally.
|
|
app.UseMiddleware<AimockHeaderMiddleware>();
|
|
|
|
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
|
var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>();
|
|
var openAiClient = CreateOpenAiClient(builder.Configuration, loggerFactory.CreateLogger("Program"));
|
|
|
|
var beautifulChatFactory = new BeautifulChatAgentFactory(
|
|
builder.Configuration,
|
|
openAiClient,
|
|
jsonOptions.Value.SerializerOptions,
|
|
loggerFactory.CreateLogger<BeautifulChatAgentFactory>());
|
|
|
|
app.MapAGUI("/beautiful-chat", beautifulChatFactory.Create());
|
|
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
|
|
|
|
await app.RunAsync();
|
|
|
|
static OpenAIClient CreateOpenAiClient(IConfiguration configuration, ILogger logger)
|
|
{
|
|
// Use the shared resolver so the primary OpenAI client and the secondary
|
|
// tool-calling HTTP client (A2uiSecondaryToolCaller) agree on which
|
|
// upstream endpoint to hit. Previously this method only consulted the
|
|
// OPENAI_BASE_URL env var, while ApiKeyResolver.ResolveEndpoint also
|
|
// checks configuration[OPENAI_BASE_URL] (appsettings.json / user-secrets)
|
|
// — that divergence let the primary client silently fall back to the
|
|
// public Azure endpoint while the secondary client used aimock.
|
|
var endpoint = ApiKeyResolver.ResolveEndpoint(configuration);
|
|
var endpointEnv = Environment.GetEnvironmentVariable("OPENAI_BASE_URL");
|
|
var endpointConfig = configuration["OPENAI_BASE_URL"];
|
|
|
|
if (!string.IsNullOrEmpty(endpointEnv))
|
|
{
|
|
logger.LogInformation("Using OpenAI endpoint from OPENAI_BASE_URL env: {Endpoint}", endpoint);
|
|
}
|
|
else if (!string.IsNullOrEmpty(endpointConfig))
|
|
{
|
|
logger.LogInformation("Using OpenAI endpoint from configuration OPENAI_BASE_URL: {Endpoint}", endpoint);
|
|
}
|
|
else
|
|
{
|
|
logger.LogInformation("OPENAI_BASE_URL not set; using default OpenAI endpoint: {Endpoint}", endpoint);
|
|
}
|
|
|
|
var apiKey = ApiKeyResolver.ResolveApiKey(configuration, logger);
|
|
|
|
return new OpenAIClient(
|
|
new ApiKeyCredential(apiKey),
|
|
AimockHeaderPolicy.CreateOpenAIClientOptions(endpoint));
|
|
}
|
|
|
|
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("feels_like")]
|
|
public int FeelsLike { get; init; }
|
|
|
|
[JsonPropertyName("city")]
|
|
public string City { get; init; } = string.Empty;
|
|
}
|
|
|
|
public partial class Program { }
|
|
|
|
[JsonSerializable(typeof(WeatherInfo))]
|
|
[JsonSerializable(typeof(BeautifulChatTodo))]
|
|
[JsonSerializable(typeof(List<BeautifulChatTodo>))]
|
|
[JsonSerializable(typeof(BeautifulChatFlight))]
|
|
[JsonSerializable(typeof(List<BeautifulChatFlight>))]
|
|
internal partial class BeautifulChatSerializerContext : JsonSerializerContext
|
|
{
|
|
}
|