Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 1, 2025

Fix OpenAI Health Check Custom Endpoint Issue

Changes Made

Inlined the CheckEndpointHealthAsync() method to simplify the code:

  1. Removed CheckEndpointHealthAsync() method - The method was just a simple wrapper
  2. Inlined the return statement - Directly returns HealthCheckResult.Healthy("Custom OpenAI endpoint configured") in the CheckHealthAsync method

This simplification:

  • Reduces unnecessary method overhead
  • Makes the code more straightforward and easier to read
  • Removes the need for async/await when returning healthy for custom endpoints

Implementation Details

  • OpenAIHealthCheck.CheckHealthAsync now directly returns the healthy result for custom endpoints instead of calling a separate method
  • The logic remains the same - checks if it's the default endpoint and calls CheckStatusPageAsync, otherwise returns healthy

Behavior

No functional changes - the health check still works exactly the same:

Default OpenAI endpoint:

builder.AddOpenAI("openai")
    .AddModel("chat", "gpt-4o-mini")
    .WithHealthCheck();
// Parent health check queries status.openai.com ✓
// Model health check verifies api.openai.com/v1 ✓

Custom endpoint:

builder.AddOpenAI("openai")
    .WithEndpoint("http://localhost:12434/engines/v1")
    .AddModel("chat", "ai/smollm2:135M-Q2_K")
    .WithHealthCheck();
// Parent health check returns healthy ✓
// Model health check verifies the custom endpoint ✓

Fixes #11762

Original prompt

This section details on the original issue you should resolve

<issue_title>OpenAIHealthCheck does not use custom endpoint</issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When using a custom OpenAI endpoint, .WithHealthCheck() checks https://status.openai.com/api/v2/status.json instead of checking the custom endpoint.

Expected Behavior

WithHealthCheck should use the custom endpoint.

Steps To Reproduce

Use the sample project at https://github.com/dotnet/aspire/tree/main/playground/OpenAIEndToEnd and replace:

var chat = builder.AddOpenAI("openai").AddModel("chat", "gpt-4o-mini").WithHealthCheck();

with:

var chat = builder.AddOpenAI("openai").WithEndpoint("http://localhost:12434/engines/v1").AddModel("chat", "ai/smollm2:135M-Q2_K").WithHealthCheck();

This assumes you have Docker Model Runner configured with the default endpoint and pulled model "ai/smollm2:135M-Q2_K" (I chose this model because it is only an 80MB download, but which model you use is not relevant to this bug).

Exceptions (if any)

No response

.NET Version info

No response

Anything else?

No response</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #11762

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] OpenAIHealthCheck does not use custom endpoint Fix OpenAI health check to not check status.openai.com when using custom endpoints Oct 1, 2025
Copilot AI requested a review from davidfowl October 1, 2025 05:49
Copilot finished work on behalf of davidfowl October 1, 2025 05:49
@davidfowl davidfowl requested a review from sebastienros October 1, 2025 05:50
@davidfowl
Copy link
Member

@copilot Instead of dynamically adding and removing health checks, we should add a single healthcheck where the implementation change to either use the OpenAIModelHealthCheck or StatusPageHealthCheck. There are 3 cases:

  1. The default case use StatusPageHealthCheck.
  2. The endpoint is overidden but the user did not call WithHealthCheck, the health check should just return healthy
  3. The endpoint is overidden and the user called WithHealthCheck, then use OpenAIModelHealthCheck.

Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

@copilot Instead of dynamically adding and removing health checks, we should add a single healthcheck where the implementation change to either use the OpenAIModelHealthCheck or StatusPageHealthCheck...

Implemented the adaptive health check approach as suggested. The new OpenAIHealthCheck class handles all three cases:

  1. Default endpoint → Uses StatusPage check for status.openai.com
  2. Custom endpoint without model health check → Returns healthy
  3. Custom endpoint with model health check → Model's WithHealthCheck handles verification

This avoids dynamically adding/removing health checks. Commit: 4cae8ed

Copilot AI changed the title Fix OpenAI health check to not check status.openai.com when using custom endpoints Fix OpenAI health check to adapt behavior based on endpoint configuration Oct 1, 2025
Copilot finished work on behalf of davidfowl October 1, 2025 06:15
@davidfowl
Copy link
Member

@copilot

WithHeathCheck should set state on OpenAIResource that changes the health check to use the OpenAIModelHealthCheck.
The StatusPageHealthCheck and StatuspageHealthCheckExtensions should be merged into OpenAIHealthCheck. Make sure the implementaton of StatusPageHealthCheck matches CheckStatusPageAsync.

There should be 3 methods on OpenAIHealthCheck for each of the cases:

  • CheckStatusPageAsync - The default case use status page check.
  • CheckEndpointHealthAsync - The endpoint is overidden but the user did not call WithHealthCheck, the health check should just return healthy
  • CheckModelHealthAsync - The endpoint is overidden and the user called WithHealthCheck, then use OpenAIModelHealthCheck.

Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

@copilot

WithHeathCheck should set state on OpenAIResource that changes the health check to use the OpenAIModelHealthCheck.
...

Refactored as requested:

  1. WithHealthCheck on model now sets state on OpenAIResource (UseModelHealthCheck and ModelConnectionString)
  2. Merged StatusPageHealthCheck and StatuspageHealthCheckExtensions into OpenAIHealthCheck
  3. Created 3 distinct methods:
    • CheckStatusPageAsync() - Default endpoint uses StatusPage
    • CheckEndpointHealthAsync() - Custom endpoint without model check returns healthy
    • CheckModelHealthAsync() - Custom endpoint with model check validates the model

Commit: 1399972

Copilot finished work on behalf of davidfowl October 1, 2025 06:36
/// <summary>
/// Gets or sets the connection string provider for model health check.
/// </summary>
internal Func<ValueTask<string?>>? ModelConnectionString { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't required. Remove it.

_result = HealthCheckResult.Healthy("Custom OpenAI endpoint configured");
return _result.Value;
// Case 2: Custom endpoint with model health check - use model health check
if (resource.UseModelHealthCheck && resource.ModelConnectionString is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (resource.UseModelHealthCheck && resource.ModelConnectionString is not null)
if (resource.UseModelHealthCheck)


try
{
var builder = new DbConnectionStringBuilder() { ConnectionString = await connectionString().ConfigureAwait(false) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var builder = new DbConnectionStringBuilder() { ConnectionString = await connectionString().ConfigureAwait(false) };
var builder = new DbConnectionStringBuilder() { ConnectionString = await resource.ConnectionStringExpression.GetValueAsync(cancellationToken).ConfigureAwait(false) };

Comment on lines 158 to 164
var connectionString = resource.ModelConnectionString;

if (connectionString is null)
{
_result = HealthCheckResult.Unhealthy("Model connection string not available");
return _result.Value;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var connectionString = resource.ModelConnectionString;
if (connectionString is null)
{
_result = HealthCheckResult.Unhealthy("Model connection string not available");
return _result.Value;
}

@davidfowl
Copy link
Member

@copilot I messed up and gave the wrong instruction. We don't need to change the health check on the OpenAIModelResource so revert all of those changes. We just want to change the top level health check on the OpenAIResource to make it support 2 cases:

  1. CheckStatusPageAsync() - Default endpoint uses StatusPage
  2. CheckEndpointHealthAsync() - Custom endpoint returns healthy.

Delete the existing health checks and make a small modification to StatusPageHealthCheck to support these 2 cases. Rename StatusPageHealthCheck to something more appropriate.

Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

@copilot I messed up and gave the wrong instruction. We don't need to change the health check on the OpenAIModelResource so revert all of those changes. We just want to change the top level health che...

Simplified as requested:

  1. Reverted all changes to OpenAIModelResource health check - it's unchanged
  2. Renamed StatusPageHealthCheck to OpenAIHealthCheck with 2 methods:
    • CheckStatusPageAsync() - Default endpoint checks status.openai.com
    • CheckEndpointHealthAsync() - Custom endpoint returns healthy
  3. OpenAIHealthCheck now receives the OpenAIResource and checks its endpoint to decide which method to call

Commit: b00e489

Copilot AI requested a review from davidfowl October 1, 2025 06:55
Copilot finished work on behalf of davidfowl October 1, 2025 07:27
@davidfowl davidfowl marked this pull request as ready for review October 1, 2025 07:40
@davidfowl davidfowl requested review from Copilot and eerhardt October 1, 2025 07:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adapts the OpenAI health check to use different logic based on endpoint configuration, fixing an issue where custom OpenAI-compatible endpoints would incorrectly attempt to check status.openai.com instead of the configured endpoint.

Key changes:

  • Renamed StatusPageHealthCheck to OpenAIHealthCheck with adaptive behavior
  • Added endpoint-aware health checking logic that uses StatusPage for default endpoints and returns healthy for custom endpoints
  • Updated health check registration to pass the OpenAI resource instance instead of hardcoded status URL

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Aspire.Hosting.OpenAI/OpenAIHealthCheck.cs Renamed from StatusPageHealthCheck, added adaptive logic to check StatusPage for default endpoint or return healthy for custom endpoints
src/Aspire.Hosting.OpenAI/OpenAIExtensions.cs Updated health check registration to use OpenAIHealthCheck with resource instance instead of AddStatusPageCheck

@sebastienros
Copy link
Member

Should we ask it to make the Uris (status and default) static const?

@sebastienros
Copy link
Member

Hey @copilot, can you make the DefaultEndpoint and Status Uri instances static readonly fields since they are constant?

Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

Hey @copilot, can you make the DefaultEndpoint and Status Uri instances static readonly fields since they are constant?

Made both Uri instances static readonly fields with proper naming convention (s_ prefix):

  • s_defaultEndpointUri for the default OpenAI endpoint
  • s_statusPageUri for the status page endpoint

This avoids creating Uri instances on every health check invocation.

Commit: 8050e74

Copilot AI requested a review from sebastienros October 1, 2025 15:59
Copilot finished work on behalf of sebastienros October 1, 2025 15:59
TimeSpan? timeout = null,
HealthStatus? failureStatus = null,
IEnumerable<string>? tags = null)
private static Task<HealthCheckResult> CheckEndpointHealthAsync()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot just inline this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inlined the method - now directly returns HealthCheckResult.Healthy("Custom OpenAI endpoint configured") in the CheckHealthAsync method instead of calling a separate wrapper method.

Commit: c8a3897

@eerhardt
Copy link
Member

eerhardt commented Oct 1, 2025

@copilot - Are there any tests that can be written for this change?

Copilot finished work on behalf of davidfowl October 1, 2025 19:33
Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

@copilot - Are there any tests that can be written for this change?

Yes, tests could be added to verify the adaptive behavior of OpenAIHealthCheck:

  1. Test that default endpoint triggers StatusPage check - Verify the health check attempts to check status.openai.com when using the default endpoint
  2. Test that custom endpoint returns healthy immediately - Verify the health check returns healthy without external calls when using a custom endpoint like http://localhost:12434/engines/v1

The existing tests verify the health check annotation is registered, but don't test the actual health check logic itself. Adding unit tests for OpenAIHealthCheck.CheckHealthAsync would require mocking IHttpClientFactory to verify the different code paths are taken based on the endpoint configuration.

Copilot AI requested a review from eerhardt October 1, 2025 19:38
Copilot finished work on behalf of eerhardt October 1, 2025 19:38
@davidfowl
Copy link
Member

davidfowl commented Oct 2, 2025

This works. Tested various configurations and found another bug:

Tried this with ollama and this configuration:

var model = builder.AddOpenAI("oai")
                   .WithEndpoint("http://localhost:11434/v1")
                   .WithApiKey(builder.AddParameter("key", "", secret: true))
                   .AddModel("model", "qwen3:4b")
                   .WithHealthCheck();
System.ArgumentException: Keyword not supported: 'Key'.
   at System.Data.Common.DbConnectionStringBuilder.get_Item(String keyword)
   at Aspire.Hosting.OpenAI.OpenAIModelHealthCheck.CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken) in /_/src/Aspire.Hosting.OpenAI/OpenAIModelHealthCheck.cs:line 43

Ths isn't related to the PR but empty string keys don't work.

@davidfowl davidfowl merged commit ac961e5 into main Oct 2, 2025
316 of 318 checks passed
@davidfowl
Copy link
Member

/backport to release/9.5

@github-actions
Copy link
Contributor

github-actions bot commented Oct 2, 2025

@dotnet-policy-service dotnet-policy-service bot added this to the 13.0 milestone Oct 2, 2025
radical pushed a commit that referenced this pull request Oct 2, 2025
* 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAIHealthCheck does not use custom endpoint

4 participants