From b14bcd01c538d7eb3b8e5d8c0e0b7c81ac1037e7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:40:33 +0000 Subject: [PATCH] fix: standardize Watson environment variables to WATSONX_ prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update WatsonProvider to use WATSONX_ prefixed environment variables - Maintain backward compatibility for existing WATSON_ variables - Add comprehensive tests for both naming conventions - Fixes inconsistency between CLI constants and embeddings provider Fixes #3595 Co-Authored-By: João --- .../rag/embeddings/providers/ibm/watson.py | 19 ++- .../providers/ibm/test_watson_env_vars.py | 108 ++++++++++++++++++ 2 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 tests/rag/embeddings/providers/ibm/test_watson_env_vars.py diff --git a/src/crewai/rag/embeddings/providers/ibm/watson.py b/src/crewai/rag/embeddings/providers/ibm/watson.py index ccc63be74..7e9de13f0 100644 --- a/src/crewai/rag/embeddings/providers/ibm/watson.py +++ b/src/crewai/rag/embeddings/providers/ibm/watson.py @@ -2,7 +2,7 @@ from typing import Any -from pydantic import Field, model_validator +from pydantic import AliasChoices, Field, model_validator from typing_extensions import Self from crewai.rag.core.base_embeddings_provider import BaseEmbeddingsProvider @@ -21,7 +21,8 @@ class WatsonProvider(BaseEmbeddingsProvider[WatsonEmbeddingFunction]): default=WatsonEmbeddingFunction, description="Watson embedding function class" ) model_id: str = Field( - description="Watson model ID", validation_alias="WATSON_MODEL_ID" + description="Watson model ID", + validation_alias=AliasChoices("WATSONX_MODEL_ID", "WATSON_MODEL_ID"), ) params: dict[str, str | dict[str, str]] | None = Field( default=None, description="Additional parameters" @@ -30,7 +31,7 @@ class WatsonProvider(BaseEmbeddingsProvider[WatsonEmbeddingFunction]): project_id: str | None = Field( default=None, description="Watson project ID", - validation_alias="WATSON_PROJECT_ID", + validation_alias=AliasChoices("WATSONX_PROJECT_ID", "WATSON_PROJECT_ID"), ) space_id: str | None = Field( default=None, description="Watson space ID", validation_alias="WATSON_SPACE_ID" @@ -67,9 +68,13 @@ class WatsonProvider(BaseEmbeddingsProvider[WatsonEmbeddingFunction]): retry_status_codes: list[int] | None = Field( default=None, description="HTTP status codes to retry on" ) - url: str = Field(description="Watson API URL", validation_alias="WATSON_URL") + url: str = Field( + description="Watson API URL", + validation_alias=AliasChoices("WATSONX_URL", "WATSON_URL"), + ) api_key: str = Field( - description="Watson API key", validation_alias="WATSON_API_KEY" + description="Watson API key", + validation_alias=AliasChoices("WATSONX_APIKEY", "WATSON_API_KEY"), ) name: str | None = Field( default=None, description="Service name", validation_alias="WATSON_NAME" @@ -85,7 +90,9 @@ class WatsonProvider(BaseEmbeddingsProvider[WatsonEmbeddingFunction]): validation_alias="WATSON_TRUSTED_PROFILE_ID", ) token: str | None = Field( - default=None, description="Bearer token", validation_alias="WATSON_TOKEN" + default=None, + description="Bearer token", + validation_alias=AliasChoices("WATSONX_TOKEN", "WATSON_TOKEN"), ) projects_token: str | None = Field( default=None, diff --git a/tests/rag/embeddings/providers/ibm/test_watson_env_vars.py b/tests/rag/embeddings/providers/ibm/test_watson_env_vars.py new file mode 100644 index 000000000..f9c2c5d02 --- /dev/null +++ b/tests/rag/embeddings/providers/ibm/test_watson_env_vars.py @@ -0,0 +1,108 @@ +"""Tests for Watson provider environment variable handling.""" + +import os +from unittest.mock import patch +import pytest +from crewai.rag.embeddings.providers.ibm.watson import WatsonProvider + + +class TestWatsonEnvironmentVariables: + """Test Watson provider environment variable compatibility.""" + + def test_watsonx_prefix_variables(self): + """Test that WATSONX_ prefixed variables work correctly.""" + with patch.dict(os.environ, { + "WATSONX_URL": "https://us-south.ml.cloud.ibm.com", + "WATSONX_APIKEY": "test-api-key", + "WATSONX_PROJECT_ID": "test-project-id", + "WATSONX_MODEL_ID": "ibm/slate-125m-english-rtrvr" + }, clear=True): + provider = WatsonProvider() + assert provider.url == "https://us-south.ml.cloud.ibm.com" + assert provider.api_key == "test-api-key" + assert provider.project_id == "test-project-id" + assert provider.model_id == "ibm/slate-125m-english-rtrvr" + + def test_watson_prefix_backward_compatibility(self): + """Test that legacy WATSON_ prefixed variables still work.""" + with patch.dict(os.environ, { + "WATSON_URL": "https://us-south.ml.cloud.ibm.com", + "WATSON_API_KEY": "test-api-key", + "WATSON_PROJECT_ID": "test-project-id", + "WATSON_MODEL_ID": "ibm/slate-125m-english-rtrvr" + }, clear=True): + provider = WatsonProvider() + assert provider.url == "https://us-south.ml.cloud.ibm.com" + assert provider.api_key == "test-api-key" + assert provider.project_id == "test-project-id" + assert provider.model_id == "ibm/slate-125m-english-rtrvr" + + def test_watsonx_takes_precedence_over_watson(self): + """Test that WATSONX_ variables take precedence over WATSON_ when both are set.""" + with patch.dict(os.environ, { + "WATSONX_URL": "https://new-url.com", + "WATSON_URL": "https://old-url.com", + "WATSONX_APIKEY": "new-key", + "WATSON_API_KEY": "old-key", + "WATSONX_PROJECT_ID": "new-project", + "WATSON_PROJECT_ID": "old-project", + "WATSONX_MODEL_ID": "new-model", + "WATSON_MODEL_ID": "old-model" + }, clear=True): + provider = WatsonProvider() + assert provider.url == "https://new-url.com" + assert provider.api_key == "new-key" + assert provider.project_id == "new-project" + assert provider.model_id == "new-model" + + def test_mixed_environment_variables(self): + """Test that mixing WATSONX_ and WATSON_ variables works correctly.""" + with patch.dict(os.environ, { + "WATSONX_URL": "https://us-south.ml.cloud.ibm.com", + "WATSON_API_KEY": "test-api-key", + "WATSONX_PROJECT_ID": "test-project-id", + "WATSON_MODEL_ID": "ibm/slate-125m-english-rtrvr" + }, clear=True): + provider = WatsonProvider() + assert provider.url == "https://us-south.ml.cloud.ibm.com" + assert provider.api_key == "test-api-key" + assert provider.project_id == "test-project-id" + assert provider.model_id == "ibm/slate-125m-english-rtrvr" + + def test_token_environment_variables(self): + """Test that token environment variables work with both prefixes.""" + with patch.dict(os.environ, { + "WATSONX_URL": "https://us-south.ml.cloud.ibm.com", + "WATSONX_APIKEY": "test-api-key", + "WATSONX_PROJECT_ID": "test-project-id", + "WATSONX_MODEL_ID": "ibm/slate-125m-english-rtrvr", + "WATSONX_TOKEN": "test-token" + }, clear=True): + provider = WatsonProvider() + assert provider.token == "test-token" + + with patch.dict(os.environ, { + "WATSON_URL": "https://us-south.ml.cloud.ibm.com", + "WATSON_API_KEY": "test-api-key", + "WATSON_PROJECT_ID": "test-project-id", + "WATSON_MODEL_ID": "ibm/slate-125m-english-rtrvr", + "WATSON_TOKEN": "legacy-token" + }, clear=True): + provider = WatsonProvider() + assert provider.token == "legacy-token" + + def test_validation_error_when_required_fields_missing(self): + """Test that validation errors are raised when required fields are missing.""" + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(Exception): + WatsonProvider() + + def test_space_or_project_validation(self): + """Test that either space_id or project_id must be provided.""" + with patch.dict(os.environ, { + "WATSONX_URL": "https://us-south.ml.cloud.ibm.com", + "WATSONX_APIKEY": "test-api-key", + "WATSONX_MODEL_ID": "ibm/slate-125m-english-rtrvr" + }, clear=True): + with pytest.raises(ValueError, match="One of 'space_id' or 'project_id' must be provided"): + WatsonProvider()