Skip to content

Commit 2869435

Browse files
github-actions[bot]CopilotdavidfowlCopilotsebastienros
authored
[release/9.5] Adapt OpenAI health check based on endpoint configuration (#11792)
* Initial plan * Fix OpenAI health check to not use status.openai.com for custom endpoints Co-authored-by: davidfowl <[email protected]> * Implement adaptive OpenAI health check based on endpoint configuration Co-authored-by: davidfowl <[email protected]> * Refactor to use adaptive health check with state tracking on OpenAIResource Co-authored-by: davidfowl <[email protected]> * Remove StatusPageHealthCheck.cs as it's been merged into OpenAIHealthCheck Co-authored-by: davidfowl <[email protected]> * Simplify to use adaptive OpenAIHealthCheck with 2 cases Co-authored-by: davidfowl <[email protected]> * Remove StatusPage health check logic, return healthy for all cases Co-authored-by: davidfowl <[email protected]> * Restore OpenAIHealthCheck with CheckStatusPageAsync and CheckEndpointHealthAsync methods Co-authored-by: davidfowl <[email protected]> * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Make DefaultEndpoint and StatusPage Uri instances static readonly fields Co-authored-by: sebastienros <[email protected]> * Inline CheckEndpointHealthAsync method Co-authored-by: davidfowl <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: davidfowl <[email protected]> Co-authored-by: David Fowler <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: sebastienros <[email protected]>
1 parent a9d9aea commit 2869435

File tree

2 files changed

+40
-52
lines changed

2 files changed

+40
-52
lines changed

src/Aspire.Hosting.OpenAI/OpenAIExtensions.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ You can obtain an API key from the [OpenAI API Keys page](https://platform.opena
4747
// Register the health check
4848
var healthCheckKey = $"{name}_check";
4949

50-
builder.AddStatusPageCheck(
51-
healthCheckKey,
52-
statusJsonUrl: "https://status.openai.com/api/v2/status.json",
53-
httpClientName: "OpenAIHealthCheck",
54-
timeout: TimeSpan.FromSeconds(5),
50+
// Ensure IHttpClientFactory is available by registering HTTP client services
51+
builder.Services.AddHttpClient();
52+
53+
builder.Services.AddHealthChecks().Add(new HealthCheckRegistration(
54+
name: healthCheckKey,
55+
factory: sp =>
56+
{
57+
var httpFactory = sp.GetRequiredService<IHttpClientFactory>();
58+
return new OpenAIHealthCheck(httpFactory, resource, "OpenAIHealthCheck", TimeSpan.FromSeconds(5));
59+
},
5560
failureStatus: HealthStatus.Unhealthy,
56-
tags: ["openai", "healthcheck"]);
61+
tags: ["openai", "healthcheck"]));
5762

5863
return builder.AddResource(resource)
5964
.WithInitialState(new()

src/Aspire.Hosting.OpenAI/StatusPageHealthCheck.cs renamed to src/Aspire.Hosting.OpenAI/OpenAIHealthCheck.cs

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,58 @@
33

44
using System.Text.Json;
55
using Microsoft.Extensions.Diagnostics.HealthChecks;
6-
using Microsoft.Extensions.DependencyInjection;
76

87
namespace Aspire.Hosting.OpenAI;
98

109
/// <summary>
11-
/// Checks a StatusPage "status.json" endpoint and maps indicator to ASP.NET Core health status.
10+
/// Health check for OpenAI resources that adapts based on endpoint configuration.
1211
/// </summary>
13-
internal sealed class StatusPageHealthCheck : IHealthCheck
12+
internal sealed class OpenAIHealthCheck : IHealthCheck
1413
{
14+
private static readonly Uri s_defaultEndpointUri = new("https://api.openai.com/v1");
15+
private static readonly Uri s_statusPageUri = new("https://status.openai.com/api/v2/status.json");
16+
1517
private readonly IHttpClientFactory _httpClientFactory;
16-
private readonly Uri _statusEndpoint;
18+
private readonly OpenAIResource _resource;
1719
private readonly string? _httpClientName;
1820
private readonly TimeSpan _timeout;
1921

2022
/// <summary>
21-
/// Initializes a new instance of the <see cref="StatusPageHealthCheck"/> class.
23+
/// Initializes a new instance of the <see cref="OpenAIHealthCheck"/> class.
2224
/// </summary>
2325
/// <param name="httpClientFactory">The factory to create HTTP clients.</param>
24-
/// <param name="statusEndpoint">The URI of the status.json endpoint.</param>
26+
/// <param name="resource">The OpenAI resource.</param>
2527
/// <param name="httpClientName">The optional name of the HTTP client to use.</param>
2628
/// <param name="timeout">The optional timeout for the HTTP request.</param>
27-
public StatusPageHealthCheck(
29+
public OpenAIHealthCheck(
2830
IHttpClientFactory httpClientFactory,
29-
Uri statusEndpoint,
31+
OpenAIResource resource,
3032
string? httpClientName = null,
3133
TimeSpan? timeout = null)
3234
{
3335
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
34-
_statusEndpoint = statusEndpoint ?? throw new ArgumentNullException(nameof(statusEndpoint));
36+
_resource = resource ?? throw new ArgumentNullException(nameof(resource));
3537
_httpClientName = httpClientName;
3638
_timeout = timeout ?? TimeSpan.FromSeconds(5);
3739
}
3840

3941
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
42+
{
43+
// Case 1: Default endpoint - check StatusPage
44+
if (Uri.TryCreate(_resource.Endpoint, UriKind.Absolute, out var endpointUri) &&
45+
Uri.Compare(endpointUri, s_defaultEndpointUri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
46+
{
47+
return await CheckStatusPageAsync(cancellationToken).ConfigureAwait(false);
48+
}
49+
50+
// Case 2: Custom endpoint - return healthy
51+
return HealthCheckResult.Healthy("Custom OpenAI endpoint configured");
52+
}
53+
54+
/// <summary>
55+
/// Checks the StatusPage endpoint for the default OpenAI service.
56+
/// </summary>
57+
private async Task<HealthCheckResult> CheckStatusPageAsync(CancellationToken cancellationToken)
4058
{
4159
var client = string.IsNullOrWhiteSpace(_httpClientName)
4260
? _httpClientFactory.CreateClient()
@@ -45,7 +63,7 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
4563
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
4664
cts.CancelAfter(_timeout);
4765

48-
using var req = new HttpRequestMessage(HttpMethod.Get, _statusEndpoint);
66+
using var req = new HttpRequestMessage(HttpMethod.Get, s_statusPageUri);
4967
req.Headers.Accept.ParseAdd("application/json");
5068

5169
HttpResponseMessage resp;
@@ -94,7 +112,7 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
94112
{
95113
["indicator"] = indicator,
96114
["description"] = description,
97-
["endpoint"] = _statusEndpoint.ToString()
115+
["endpoint"] = s_statusPageUri.ToString()
98116
};
99117

100118
// Map indicator -> HealthStatus
@@ -113,38 +131,3 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
113131
}
114132
}
115133
}
116-
117-
internal static class StatuspageHealthCheckExtensions
118-
{
119-
/// <summary>
120-
/// Registers a StatusPage health check for a given status.json URL.
121-
/// </summary>
122-
public static IDistributedApplicationBuilder AddStatusPageCheck(
123-
this IDistributedApplicationBuilder builder,
124-
string name,
125-
string statusJsonUrl,
126-
string? httpClientName = null,
127-
TimeSpan? timeout = null,
128-
HealthStatus? failureStatus = null,
129-
IEnumerable<string>? tags = null)
130-
{
131-
ArgumentNullException.ThrowIfNull(builder);
132-
ArgumentException.ThrowIfNullOrWhiteSpace(name);
133-
ArgumentException.ThrowIfNullOrWhiteSpace(statusJsonUrl);
134-
135-
// Ensure IHttpClientFactory is available by registering HTTP client services
136-
builder.Services.AddHttpClient();
137-
138-
builder.Services.AddHealthChecks().Add(new HealthCheckRegistration(
139-
name: name,
140-
factory: sp =>
141-
{
142-
var httpFactory = sp.GetRequiredService<IHttpClientFactory>();
143-
return new StatusPageHealthCheck(httpFactory, new Uri(statusJsonUrl), httpClientName, timeout);
144-
},
145-
failureStatus: failureStatus,
146-
tags: tags));
147-
148-
return builder;
149-
}
150-
}

0 commit comments

Comments
 (0)