Merge pull request #4773 from oyobbeb/fix/openai-base-url-env

fix(openai): treat OPENAI_BASE_URL as a custom endpoint
This commit is contained in:
Arnaud Héritier authored and GitHub committed 2026-10-31 19:40:05 +00:00
commit c3c6e56008
3 files changed
+28 -4

No files matched your search

+11 -3
View File
@@ -212,6 +212,14 @@ func (c *Client) replaysReasoning() bool {
return oaistream.ReplayReasoning(c.ModelConfig.ProviderOpts)
}
// endpointConfig returns ModelConfig with BaseURL set to the endpoint requests
// reach, so endpoint checks also see OPENAI_BASE_URL.
func (c *Client) endpointConfig() *latest.ModelConfig {
cfg := c.ModelConfig
cfg.BaseURL = cmp.Or(cfg.BaseURL, c.BaseURL)
return &cfg
}
// convertMessages converts chat.Message to openai.ChatCompletionMessageParamUnion
// using the shared oaistream implementation.
func (c *Client) convertMessages(ctx context.Context, messages []chat.Message) []openai.ChatCompletionMessageParamUnion {
@@ -230,7 +238,7 @@ func (c *Client) convertMessages(ctx context.Context, messages []chat.Message) [
// merging rewrites the stable message boundaries the markers pin. The
// self-hosted case the merge exists for (openai provider + custom
// base_url) is already excluded by the gate and still merges below.
if sendsExplicitCacheBreakpoints(&c.ModelConfig, c.ModelOptions.OpenAIVendor()) {
if sendsExplicitCacheBreakpoints(c.endpointConfig(), c.ModelOptions.OpenAIVendor()) {
return c.convertMessagesWithCacheBreakpoints(ctx, messages)
}
// Coalesce consecutive same-role (system/user) messages into one for generic
@@ -247,7 +255,7 @@ func (c *Client) convertMessages(ctx context.Context, messages []chat.Message) [
// prompt-cache breakpoints never reach this path: such backends don't
// accept the OpenAI-only field, and the merge rewrites message boundaries
// the markers would pin.
if shouldMergeConsecutiveMessages(&c.ModelConfig) {
if shouldMergeConsecutiveMessages(c.endpointConfig()) {
converted := oaistream.ConvertMessages(ctx, messages, c.ID(), c.ModelOptions.ModelsDevStore(), c.CapsOverride())
return oaistream.MergeConsecutiveMessages(converted)
}
@@ -906,7 +914,7 @@ func getTransport(cfg *latest.ModelConfig) string {
}
func (c *Client) convertMessagesToResponseInput(ctx context.Context, messages []chat.Message) []responses.ResponseInputItemUnionParam {
markBreakpoints := sendsExplicitCacheBreakpoints(&c.ModelConfig, c.ModelOptions.OpenAIVendor())
markBreakpoints := sendsExplicitCacheBreakpoints(c.endpointConfig(), c.ModelOptions.OpenAIVendor())
var input []responses.ResponseInputItemUnionParam
for _, msg := range messages {
if replay := c.replayResponse(msg); len(replay) > 0 {
+1 -1
View File
@@ -23,7 +23,7 @@ func (c *Client) firstPartyResponses() bool {
if c.ModelConfig.Provider == chatgpt.ProviderName {
return c.ModelConfig.BaseURL == "" || strings.TrimRight(c.ModelConfig.BaseURL, "/") == chatgpt.BaseURL
}
return c.ModelConfig.Provider == "openai" && catalog.IsDefaultEndpoint(c.ModelConfig.Provider, c.ModelConfig.BaseURL)
return c.ModelConfig.Provider == "openai" && catalog.IsDefaultEndpoint(c.ModelConfig.Provider, c.endpointConfig().BaseURL)
}
// Native search needs ordered output replay to retain its loaded tools.
@@ -22,6 +22,7 @@ import (
"github.com/docker/docker-agent/pkg/chat"
"github.com/docker/docker-agent/pkg/chatgpt"
"github.com/docker/docker-agent/pkg/config/latest"
"github.com/docker/docker-agent/pkg/environment"
"github.com/docker/docker-agent/pkg/httpclient"
"github.com/docker/docker-agent/pkg/model/provider/base"
"github.com/docker/docker-agent/pkg/tools"
@@ -309,3 +310,18 @@ func TestResponseStateChatGPTDefaultEndpoint(t *testing.T) {
client.ModelConfig.BaseURL = "https://other.example/codex"
assert.Nil(t, client.replayResponse(message))
}
func TestResponseStateOpenAIBaseURLEnv(t *testing.T) {
t.Setenv("OPENAI_BASE_URL", "http://localhost:8080/v1")
env := environment.NewMapEnvProvider(map[string]string{"OPENAI_API_KEY": "secret"})
client, err := NewClient(t.Context(), &responseStateClient().ModelConfig, env)
require.NoError(t, err)
defer client.Close()
assert.Nil(t, client.replayResponse(responseStateMessage()))
// Self-hosted: no cache breakpoints, so the system messages merge.
assert.Len(t, client.convertMessages(t.Context(), []chat.Message{
{Role: chat.MessageRoleSystem, Content: "a"},
{Role: chat.MessageRoleSystem, Content: "b"},
}), 1)
}