Skip to content

Commit ab6f7b2

Browse files
authored
Merge pull request #125 from dgageot/fix-config-with-gateway
Using a gateway means no need for the specific api tokens
2 parents 470ebf5 + e59f8c5 commit ab6f7b2

11 files changed

Lines changed: 212 additions & 32 deletions

pkg/teamloader/teamloader.go

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -80,43 +80,51 @@ func FindAgentPaths(agentsPathOrDirectory string) ([]string, error) {
8080
// checkRequiredEnvVars checks which environment variables are required by the models and tools.
8181
// This allows exiting early with a proper error message instead of failing later when trying to use a model or tool.
8282
// TODO(dga): This code contains lots of duplication and ought to be refactored.
83-
func checkRequiredEnvVars(ctx context.Context, cfg *latest.Config, env environment.Provider) error {
83+
func checkRequiredEnvVars(ctx context.Context, cfg *latest.Config, env environment.Provider, runtimeConfig config.RuntimeConfig) error {
8484
requiredEnv := map[string]bool{}
8585

86-
for _, model := range cfg.Models {
87-
switch model.Provider {
88-
case "openai":
89-
requiredEnv["OPENAI_API_KEY"] = true
90-
case "anthropic":
91-
requiredEnv["ANTHROPIC_API_KEY"] = true
92-
case "google":
93-
requiredEnv["GOOGLE_API_KEY"] = true
86+
// Models
87+
if runtimeConfig.ModelsGateway == "" {
88+
for _, model := range cfg.Models {
89+
switch model.Provider {
90+
case "openai":
91+
requiredEnv["OPENAI_API_KEY"] = true
92+
case "anthropic":
93+
requiredEnv["ANTHROPIC_API_KEY"] = true
94+
case "google":
95+
requiredEnv["GOOGLE_API_KEY"] = true
96+
}
9497
}
95-
}
9698

97-
for _, agent := range cfg.Agents {
98-
model := agent.Model
99-
switch {
100-
case strings.HasPrefix(model, "openai/"):
101-
requiredEnv["OPENAI_API_KEY"] = true
102-
case strings.HasPrefix(model, "anthropic/"):
103-
requiredEnv["ANTHROPIC_API_KEY"] = true
104-
case strings.HasPrefix(model, "google/"):
105-
requiredEnv["GOOGLE_API_KEY"] = true
99+
for _, agent := range cfg.Agents {
100+
model := agent.Model
101+
switch {
102+
case strings.HasPrefix(model, "openai/"):
103+
requiredEnv["OPENAI_API_KEY"] = true
104+
case strings.HasPrefix(model, "anthropic/"):
105+
requiredEnv["ANTHROPIC_API_KEY"] = true
106+
case strings.HasPrefix(model, "google/"):
107+
requiredEnv["GOOGLE_API_KEY"] = true
108+
}
106109
}
110+
}
107111

108-
for i := range agent.Toolsets {
109-
toolSet := agent.Toolsets[i]
110-
111-
if toolSet.Type == "mcp" && toolSet.Ref != "" {
112-
mcpServerName := gateway.ParseServerRef(toolSet.Ref)
113-
114-
secrets, err := gateway.RequiredEnvVars(ctx, mcpServerName, gateway.DockerCatalogURL)
115-
if err != nil {
116-
return fmt.Errorf("reading which secrets the MCP server needs: %w", err)
117-
}
118-
for _, secret := range secrets {
119-
requiredEnv[secret.Env] = true
112+
// Tools
113+
if runtimeConfig.ToolsGateway == "" {
114+
for _, agent := range cfg.Agents {
115+
for i := range agent.Toolsets {
116+
toolSet := agent.Toolsets[i]
117+
118+
if toolSet.Type == "mcp" && toolSet.Ref != "" {
119+
mcpServerName := gateway.ParseServerRef(toolSet.Ref)
120+
121+
secrets, err := gateway.RequiredEnvVars(ctx, mcpServerName, gateway.DockerCatalogURL)
122+
if err != nil {
123+
return fmt.Errorf("reading which secrets the MCP server needs: %w", err)
124+
}
125+
for _, secret := range secrets {
126+
requiredEnv[secret.Env] = true
127+
}
120128
}
121129
}
122130
}
@@ -171,7 +179,7 @@ func Load(ctx context.Context, path string, runtimeConfig config.RuntimeConfig)
171179
}
172180

173181
// Early check for required env vars before loading models and tools.
174-
if err := checkRequiredEnvVars(ctx, cfg, env); err != nil {
182+
if err := checkRequiredEnvVars(ctx, cfg, env, runtimeConfig); err != nil {
175183
return nil, err
176184
}
177185

pkg/teamloader/teamloader_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package teamloader
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/docker/cagent/pkg/config"
8+
"github.com/docker/cagent/pkg/environment"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
type noEnvProvider struct{}
14+
15+
func (p *noEnvProvider) Get(context.Context, string) string { return "" }
16+
17+
func TestCheckRequiredEnvVars(t *testing.T) {
18+
tests := []struct {
19+
yaml string
20+
expectedMissing []string
21+
}{
22+
{
23+
yaml: "openai_inline.yaml",
24+
expectedMissing: []string{"OPENAI_API_KEY"},
25+
},
26+
{
27+
yaml: "anthropic_inline.yaml",
28+
expectedMissing: []string{"ANTHROPIC_API_KEY"},
29+
},
30+
{
31+
yaml: "google_inline.yaml",
32+
expectedMissing: []string{"GOOGLE_API_KEY"},
33+
},
34+
{
35+
yaml: "dmr_inline.yaml",
36+
expectedMissing: []string{},
37+
},
38+
{
39+
yaml: "openai_model.yaml",
40+
expectedMissing: []string{"OPENAI_API_KEY"},
41+
},
42+
{
43+
yaml: "anthropic_model.yaml",
44+
expectedMissing: []string{"ANTHROPIC_API_KEY"},
45+
},
46+
{
47+
yaml: "google_model.yaml",
48+
expectedMissing: []string{"GOOGLE_API_KEY"},
49+
},
50+
{
51+
yaml: "dmr_model.yaml",
52+
expectedMissing: []string{},
53+
},
54+
{
55+
yaml: "all.yaml",
56+
expectedMissing: []string{"ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "OPENAI_API_KEY"},
57+
},
58+
}
59+
for _, test := range tests {
60+
t.Run(test.yaml, func(t *testing.T) {
61+
t.Parallel()
62+
63+
cfg, err := config.LoadConfigSecure(test.yaml, "testdata")
64+
require.NoError(t, err)
65+
66+
err = checkRequiredEnvVars(t.Context(), cfg, &noEnvProvider{}, config.RuntimeConfig{})
67+
68+
if len(test.expectedMissing) == 0 {
69+
require.NoError(t, err)
70+
} else {
71+
require.Error(t, err)
72+
assert.Equal(t, test.expectedMissing, err.(*environment.RequiredEnvError).Missing)
73+
}
74+
})
75+
}
76+
}
77+
78+
func TestCheckRequiredEnvVarsWithModelGateway(t *testing.T) {
79+
t.Parallel()
80+
81+
cfg, err := config.LoadConfigSecure("all.yaml", "testdata")
82+
require.NoError(t, err)
83+
84+
err = checkRequiredEnvVars(t.Context(), cfg, &noEnvProvider{}, config.RuntimeConfig{
85+
ModelsGateway: "gateway:8080",
86+
})
87+
88+
require.NoError(t, err)
89+
}

pkg/teamloader/testdata/all.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: "2"
2+
3+
agents:
4+
anthropic:
5+
model: anthropic/claude-sonnet-4-0
6+
instruction: Always answer by talking like a pirate.
7+
dmr:
8+
model: dmr/ai/gemma3-qat:12B
9+
instruction: Always answer by talking like a pirate.
10+
openai:
11+
model: openai/gpt-4o
12+
instruction: Always answer by talking like a pirate.
13+
gemini:
14+
model: google/gemini-2.0-flash
15+
instruction: Always answer by talking like a pirate.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: anthropic/claude-sonnet-4-0
6+
instruction: Always answer by talking like a pirate.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: claude
6+
instruction: Always answer by talking like a pirate.
7+
8+
models:
9+
claude:
10+
provider: anthropic
11+
model: claude-sonnet-4-0
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: dmr/ai/gemma3-qat:12B
6+
instruction: Always answer by talking like a pirate.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: dmr
6+
instruction: Always answer by talking like a pirate.
7+
8+
models:
9+
dmr:
10+
provider: dmr
11+
model: ai/gemma3-qat:12B
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: google/gemini-2.0-flash
6+
instruction: Always answer by talking like a pirate.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: gemini
6+
instruction: Always answer by talking like a pirate.
7+
8+
models:
9+
gemini:
10+
provider: google
11+
model: gemini-2-0
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
instruction: Always answer by talking like a pirate.

0 commit comments

Comments
 (0)