Skip to content

Commit 12bd6ae

Browse files
authored
Merge pull request #478 from krissetto/no-structured-outputs-title-and-summarization
2 parents fdbea33 + 50f9c2f commit 12bd6ae

9 files changed

Lines changed: 93 additions & 5 deletions

File tree

pkg/model/provider/anthropic/client.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
// Client represents an Anthropic client wrapper implementing provider.Provider
2222
// It holds the anthropic client and model config
2323
type Client struct {
24-
client anthropic.Client
25-
config *latest.ModelConfig
24+
client anthropic.Client
25+
config *latest.ModelConfig
26+
modelOptions options.ModelOptions
2627
// When using the Docker AI Gateway, tokens are short-lived. We rebuild
2728
// the client per request when in gateway mode.
2829
useGateway bool
@@ -114,6 +115,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
114115
return &Client{
115116
client: client,
116117
config: cfg,
118+
modelOptions: globalOptions,
117119
useGateway: useGateway,
118120
gatewayBaseURL: gatewayBaseURL,
119121
}, nil
@@ -402,3 +404,8 @@ func ConvertParametersToSchema(params tools.FunctionParameters) anthropic.ToolIn
402404
func (c *Client) ID() string {
403405
return c.config.Provider + "/" + c.config.Model
404406
}
407+
408+
// Options returns the effective model options used by this client.
409+
func (c *Client) Options() options.ModelOptions {
410+
return c.modelOptions
411+
}

pkg/model/provider/clone.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"strings"
7+
8+
latest "github.com/docker/cagent/pkg/config/v2"
9+
"github.com/docker/cagent/pkg/environment"
10+
"github.com/docker/cagent/pkg/model/provider/options"
11+
)
12+
13+
// CloneWithOptions returns a new Provider instance using the same provider/model
14+
// as the base provider, applying the provided options. If cloning fails, the
15+
// original base provider is returned.
16+
func CloneWithOptions(ctx context.Context, base Provider, env environment.Provider, opts ...options.Opt) Provider {
17+
if base == nil {
18+
return nil
19+
}
20+
21+
id := strings.TrimSpace(base.ID())
22+
parts := strings.SplitN(id, "/", 2)
23+
if len(parts) != 2 {
24+
return base
25+
}
26+
27+
cfg := &latest.ModelConfig{Provider: parts[0], Model: parts[1]}
28+
if env == nil {
29+
env = environment.NewDefaultProvider(ctx)
30+
}
31+
32+
// Preserve existing options, then apply overrides. Later opts take precedence.
33+
baseOpts := options.FromModelOptions(base.Options())
34+
mergedOpts := append(baseOpts, opts...)
35+
36+
cloned, err := New(ctx, cfg, env, mergedOpts...)
37+
if err != nil {
38+
slog.Debug("Failed to clone provider; using base provider", "error", err, "id", id)
39+
return base
40+
}
41+
return cloned
42+
}

pkg/model/provider/dmr/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,11 @@ func (c *Client) ID() string {
442442
return c.config.Provider + "/" + c.config.Model
443443
}
444444

445+
// Options returns the effective model options used by this client.
446+
func (c *Client) Options() options.ModelOptions {
447+
return c.modelOptions
448+
}
449+
445450
func parseDMRProviderOpts(cfg *latest.ModelConfig) (contextSize int, runtimeFlags []string) {
446451
if cfg == nil {
447452
return 0, nil

pkg/model/provider/gemini/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,8 @@ func (c *Client) CreateChatCompletionStream(
414414
func (c *Client) ID() string {
415415
return c.config.Provider + "/" + c.config.Model
416416
}
417+
418+
// Options returns the effective model options used by this client.
419+
func (c *Client) Options() options.ModelOptions {
420+
return c.modelOptions
421+
}

pkg/model/provider/openai/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ func (c *Client) ID() string {
367367
return c.config.Provider + "/" + c.config.Model
368368
}
369369

370+
// Options returns the effective model options used by this client.
371+
func (c *Client) Options() options.ModelOptions {
372+
return c.modelOptions
373+
}
374+
370375
// getOpenAIReasoningEffort resolves the reasoning effort value from the
371376
// model configuration's ThinkingBudget. Returns the effort (minimal|low|medium|high) or an error
372377
func getOpenAIReasoningEffort(cfg *latest.ModelConfig) (effort string, err error) {

pkg/model/provider/options/options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,16 @@ func WithStructuredOutput(output *latest.StructuredOutput) Opt {
2626
cfg.StructuredOutput = output
2727
}
2828
}
29+
30+
// FromModelOptions converts a concrete ModelOptions value into a slice of
31+
// Opt configuration functions. Later Opts override earlier ones when applied.
32+
func FromModelOptions(m ModelOptions) []Opt {
33+
var out []Opt
34+
if g := m.Gateway(); g != "" {
35+
out = append(out, WithGateway(g))
36+
}
37+
if m.StructuredOutput != nil {
38+
out = append(out, WithStructuredOutput(m.StructuredOutput))
39+
}
40+
return out
41+
}

pkg/model/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ type Provider interface {
4747
messages []chat.Message,
4848
tools []tools.Tool,
4949
) (chat.MessageStream, error)
50+
// Options returns the effective model options used by this provider
51+
Options() options.ModelOptions
5052
}
5153

5254
func New(ctx context.Context, cfg *latest.ModelConfig, env environment.Provider, opts ...options.Opt) (Provider, error) {

pkg/runtime/runtime.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818

1919
"github.com/docker/cagent/pkg/agent"
2020
"github.com/docker/cagent/pkg/chat"
21+
"github.com/docker/cagent/pkg/model/provider"
22+
"github.com/docker/cagent/pkg/model/provider/options"
2123
"github.com/docker/cagent/pkg/modelsdev"
2224
"github.com/docker/cagent/pkg/session"
2325
"github.com/docker/cagent/pkg/team"
@@ -959,9 +961,11 @@ func (r *runtime) generateSessionTitle(ctx context.Context, sess *session.Sessio
959961
systemPrompt := "You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic."
960962
userPrompt := fmt.Sprintf("Based on the following conversation between a user and an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n\nConversation history:%s\n\nGenerate a title for this conversation:", conversationHistory.String())
961963

964+
titleModel := provider.CloneWithOptions(ctx, r.CurrentAgent().Model(), nil, options.WithStructuredOutput(nil))
965+
962966
newTeam := team.New(
963967
team.WithID("title-generator"),
964-
team.WithAgents(agent.New("root", systemPrompt, agent.WithModel(r.CurrentAgent().Model()))),
968+
team.WithAgents(agent.New("root", systemPrompt, agent.WithModel(titleModel))),
965969
)
966970

967971
titleSession := session.New(session.WithSystemMessage(systemPrompt))
@@ -1019,10 +1023,10 @@ func (r *runtime) Summarize(ctx context.Context, sess *session.Session, events c
10191023
// Create a new session for summary generation
10201024
systemPrompt := "You are a helpful AI assistant that creates comprehensive summaries of conversations. You will be given a conversation history and asked to create a concise yet thorough summary that captures the key points, decisions made, and outcomes."
10211025
userPrompt := fmt.Sprintf("Based on the following conversation between a user and an AI assistant, create a comprehensive summary that captures:\n- The main topics discussed\n- Key information exchanged\n- Decisions made or conclusions reached\n- Important outcomes or results\n\nProvide a well-structured summary (2-4 paragraphs) that someone could read to understand what happened in this conversation. Return ONLY the summary text, nothing else.\n\nConversation history:%s\n\nGenerate a summary for this conversation:", conversationHistory.String())
1022-
1026+
newModel := provider.CloneWithOptions(ctx, r.CurrentAgent().Model(), nil, options.WithStructuredOutput(nil))
10231027
newTeam := team.New(
10241028
team.WithID("summary-generator"),
1025-
team.WithAgents(agent.New("root", systemPrompt, agent.WithModel(r.CurrentAgent().Model()))),
1029+
team.WithAgents(agent.New("root", systemPrompt, agent.WithModel(newModel))),
10261030
)
10271031

10281032
summarySession := session.New(session.WithSystemMessage(systemPrompt))

pkg/runtime/runtime_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/docker/cagent/pkg/agent"
1313
"github.com/docker/cagent/pkg/chat"
14+
"github.com/docker/cagent/pkg/model/provider/options"
1415
"github.com/docker/cagent/pkg/modelsdev"
1516
"github.com/docker/cagent/pkg/session"
1617
"github.com/docker/cagent/pkg/team"
@@ -112,6 +113,8 @@ func (m *mockProvider) CreateChatCompletionStream(ctx context.Context, messages
112113
return m.stream, nil
113114
}
114115

116+
func (m *mockProvider) Options() options.ModelOptions { return options.ModelOptions{} }
117+
115118
type mockProviderWithError struct {
116119
id string
117120
}
@@ -122,6 +125,8 @@ func (m *mockProviderWithError) CreateChatCompletionStream(ctx context.Context,
122125
return nil, fmt.Errorf("simulated error creating chat completion stream")
123126
}
124127

128+
func (m *mockProviderWithError) Options() options.ModelOptions { return options.ModelOptions{} }
129+
125130
type mockModelStore struct{}
126131

127132
func (m mockModelStore) GetModel(ctx context.Context, id string) (*modelsdev.Model, error) {

0 commit comments

Comments
 (0)