Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions pkg/model/provider/openai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/model/provider/openai/response_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions pkg/model/provider/openai/response_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Loading