Skip to content

Commit 145e108

Browse files
committed
add anthropic base url
Signed-off-by: dongjiang <dongjiang1989@126.com>
1 parent 03ad0e6 commit 145e108

6 files changed

Lines changed: 147 additions & 2 deletions

File tree

go/core/internal/controller/translator/agent/adk_api_translator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,12 @@ func (a *adkApiTranslator) translateModel(ctx context.Context, namespace, modelC
434434

435435
if model.Spec.Anthropic != nil {
436436
anthropic.BaseUrl = model.Spec.Anthropic.BaseURL
437+
if model.Spec.Anthropic.BaseURL != "" {
438+
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
439+
Name: env.AnthropicAPIBaseURL.Name(),
440+
Value: model.Spec.Anthropic.BaseURL,
441+
})
442+
}
437443
}
438444
return anthropic, modelDeploymentData, secretHashBytes, nil
439445
case v1alpha2.ModelProviderAzureOpenAI:

go/core/internal/controller/translator/agent/adk_api_translator_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,3 +1413,102 @@ func Test_AdkApiTranslator_SandboxAgent_BYOEmitsSandbox(t *testing.T) {
14131413
require.False(t, sawDeploy)
14141414
require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it")
14151415
}
1416+
1417+
func Test_AdkApiTranslator_AnthropicBaseURLEnvVar(t *testing.T) {
1418+
scheme := schemev1.Scheme
1419+
require.NoError(t, v1alpha2.AddToScheme(scheme))
1420+
1421+
namespace := "test-ns"
1422+
1423+
tests := []struct {
1424+
name string
1425+
baseURL string
1426+
wantEnvVar bool
1427+
wantEnvVarValue string
1428+
}{
1429+
{
1430+
name: "empty baseURL does not inject env var",
1431+
baseURL: "",
1432+
wantEnvVar: false,
1433+
},
1434+
{
1435+
name: "custom baseURL injects ANTHROPIC_BASE_URL env var",
1436+
baseURL: "https://proxy.internal/anthropic/v1",
1437+
wantEnvVar: true,
1438+
wantEnvVarValue: "https://proxy.internal/anthropic/v1",
1439+
},
1440+
{
1441+
name: "default Anthropic URL still injects env var",
1442+
baseURL: "https://api.anthropic.com",
1443+
wantEnvVar: true,
1444+
wantEnvVarValue: "https://api.anthropic.com",
1445+
},
1446+
}
1447+
1448+
for _, tt := range tests {
1449+
t.Run(tt.name, func(t *testing.T) {
1450+
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
1451+
1452+
modelConfig := &v1alpha2.ModelConfig{
1453+
ObjectMeta: metav1.ObjectMeta{Name: "anthropic-model", Namespace: namespace},
1454+
Spec: v1alpha2.ModelConfigSpec{
1455+
Provider: v1alpha2.ModelProviderAnthropic,
1456+
Model: "claude-3-sonnet-20240229",
1457+
APIKeySecret: "anthropic-secret",
1458+
Anthropic: &v1alpha2.AnthropicConfig{
1459+
BaseURL: tt.baseURL,
1460+
},
1461+
},
1462+
}
1463+
1464+
agent := &v1alpha2.Agent{
1465+
ObjectMeta: metav1.ObjectMeta{Name: "test-agent", Namespace: namespace},
1466+
Spec: v1alpha2.AgentSpec{
1467+
Type: v1alpha2.AgentType_Declarative,
1468+
Description: "Test Agent",
1469+
Declarative: &v1alpha2.DeclarativeAgentSpec{
1470+
SystemMessage: "You are a test agent",
1471+
ModelConfig: "anthropic-model",
1472+
},
1473+
},
1474+
}
1475+
1476+
kubeClient := fake.NewClientBuilder().
1477+
WithScheme(scheme).
1478+
WithObjects(ns, modelConfig, agent).
1479+
Build()
1480+
1481+
defaultModel := types.NamespacedName{Namespace: namespace, Name: "anthropic-model"}
1482+
trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil)
1483+
1484+
outputs, err := translator.TranslateAgent(context.Background(), trans, agent)
1485+
require.NoError(t, err)
1486+
require.NotNil(t, outputs)
1487+
1488+
// Find the deployment and check env vars
1489+
var deployment *appsv1.Deployment
1490+
for _, obj := range outputs.Manifest {
1491+
if d, ok := obj.(*appsv1.Deployment); ok {
1492+
deployment = d
1493+
break
1494+
}
1495+
}
1496+
require.NotNil(t, deployment)
1497+
1498+
var found bool
1499+
for _, envVar := range deployment.Spec.Template.Spec.Containers[0].Env {
1500+
if envVar.Name == "ANTHROPIC_BASE_URL" {
1501+
found = true
1502+
assert.Equal(t, tt.wantEnvVarValue, envVar.Value)
1503+
break
1504+
}
1505+
}
1506+
1507+
if tt.wantEnvVar {
1508+
assert.True(t, found, "expected ANTHROPIC_BASE_URL env var to be injected")
1509+
} else {
1510+
assert.False(t, found, "did not expect ANTHROPIC_BASE_URL env var")
1511+
}
1512+
})
1513+
}
1514+
}

go/core/pkg/env/providers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ var (
3535
"API key for Anthropic.",
3636
ComponentAgentRuntime,
3737
)
38+
39+
AnthropicAPIBaseURL = RegisterStringVar(
40+
"ANTHROPIC_BASE_URL",
41+
"",
42+
"Custom base URL for the Anthropic API.",
43+
ComponentAgentRuntime,
44+
)
3845
)
3946

4047
// Azure OpenAI

python/packages/kagent-adk/src/kagent/adk/_a2a.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ def thread_dump(request: Request) -> PlainTextResponse:
5252
kagent_url_override = os.getenv("KAGENT_URL")
5353

5454

55+
def _configure_anthropic_client() -> None:
56+
"""
57+
Configure the Anthropic client to use ANTHROPIC_BASE_URL.
58+
59+
Reads ANTHROPIC_BASE_URL from the environment (matching the Go-side
60+
env.AnthropicAPIBaseURL registration) and logs its presence. The Anthropic
61+
SDK natively reads ANTHROPIC_BASE_URL, so no explicit remapping is needed.
62+
"""
63+
anthropic_api_base = os.getenv("ANTHROPIC_BASE_URL")
64+
if anthropic_api_base:
65+
logger.info(f"Configured Anthropic client with base URL: {anthropic_api_base}")
66+
67+
5568
class KAgentApp:
5669
def __init__(
5770
self,
@@ -86,6 +99,8 @@ def __init__(
8699
self.agent_config = agent_config
87100

88101
def build(self, local=False) -> FastAPI:
102+
_configure_anthropic_client()
103+
89104
session_service = InMemorySessionService()
90105
token_service = None
91106
http_client: Optional[httpx.AsyncClient] = None

python/packages/kagent-adk/src/kagent/adk/models/_anthropic.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ def _create_http_client(self):
4444
@cached_property
4545
def _anthropic_client(self) -> AsyncAnthropic:
4646
api_key = self._api_key or os.environ.get("ANTHROPIC_API_KEY")
47+
base_url = self.base_url or os.environ.get("ANTHROPIC_BASE_URL")
4748
kwargs = {}
4849
if api_key:
4950
kwargs["api_key"] = api_key
50-
if self.base_url:
51-
kwargs["base_url"] = self.base_url
51+
if base_url:
52+
kwargs["base_url"] = base_url
5253
if self.extra_headers:
5354
kwargs["default_headers"] = self.extra_headers
5455

python/packages/kagent-adk/tests/unittests/models/test_anthropic.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ def test_client_uses_base_url(self):
3535
_ = llm._anthropic_client
3636
assert mock_anthropic.call_args.kwargs["base_url"] == "https://proxy.internal/anthropic"
3737

38+
def test_client_falls_back_to_env_base_url(self):
39+
llm = KAgentAnthropicLlm(model="claude-3-sonnet-20240229")
40+
with mock.patch.dict("os.environ", {"ANTHROPIC_BASE_URL": "https://env-proxy.internal/anthropic"}):
41+
with mock.patch("kagent.adk.models._anthropic.AsyncAnthropic") as mock_anthropic:
42+
mock_anthropic.return_value = mock.MagicMock(spec=AsyncAnthropic)
43+
_ = llm._anthropic_client
44+
assert mock_anthropic.call_args.kwargs["base_url"] == "https://env-proxy.internal/anthropic"
45+
46+
def test_explicit_base_url_takes_precedence_over_env(self):
47+
llm = KAgentAnthropicLlm(model="claude-3-sonnet-20240229", base_url="https://explicit.internal/anthropic")
48+
with mock.patch.dict("os.environ", {"ANTHROPIC_BASE_URL": "https://env.internal/anthropic"}):
49+
with mock.patch("kagent.adk.models._anthropic.AsyncAnthropic") as mock_anthropic:
50+
mock_anthropic.return_value = mock.MagicMock(spec=AsyncAnthropic)
51+
_ = llm._anthropic_client
52+
# Explicit base_url wins over env
53+
assert mock_anthropic.call_args.kwargs["base_url"] == "https://explicit.internal/anthropic"
54+
3855
def test_client_uses_extra_headers(self):
3956
llm = KAgentAnthropicLlm(model="claude-3-sonnet-20240229", extra_headers={"X-Org": "test-org"})
4057
with mock.patch("kagent.adk.models._anthropic.AsyncAnthropic") as mock_anthropic:

0 commit comments

Comments
 (0)