Spaces:
Sleeping
Sleeping
charSLee013
feat: complete Hugging Face Spaces deployment with production-ready CognitiveKernel-Launchpad
1ea26af
| """ | |
| Test cases for environment variable fallback in LLM configuration. | |
| Phase 1, Task 1.2: Design test cases for environment variable fallback scenarios | |
| """ | |
| import os | |
| import pytest | |
| from unittest.mock import patch | |
| from ck_pro.config.settings import Settings, LLMConfig | |
| class TestEnvironmentVariableFallback: | |
| """Test environment variable fallback behavior in _build_llm_config""" | |
| def setup_method(self): | |
| """Clean up environment variables before each test""" | |
| env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"] | |
| for var in env_vars: | |
| os.environ.pop(var, None) | |
| def teardown_method(self): | |
| """Clean up environment variables after each test""" | |
| env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"] | |
| for var in env_vars: | |
| os.environ.pop(var, None) | |
| # Test Case 1.1: Environment variables used when no config provided | |
| def test_env_vars_used_when_no_config(self): | |
| """Test that environment variables are used when no TOML config is provided""" | |
| # Setup environment variables | |
| os.environ["OPENAI_API_BASE"] = "https://test.openai.com/v1/chat/completions" | |
| os.environ["OPENAI_API_KEY"] = "test-key-123" | |
| os.environ["OPENAI_API_MODEL"] = "test-model-456" | |
| # Call with empty config | |
| result = Settings._build_llm_config({}, {"temperature": 0.5}) | |
| # Verify environment variables are used | |
| assert result.call_target == "https://test.openai.com/v1/chat/completions" | |
| assert result.api_key == "test-key-123" | |
| assert result.model == "test-model-456" | |
| assert result.extract_body == {"temperature": 0.5} | |
| # Test Case 1.2: Environment variables not used when config provided | |
| def test_env_vars_ignored_when_config_provided(self): | |
| """Test that environment variables are ignored when TOML config is provided""" | |
| # Setup environment variables (should be ignored) | |
| os.environ["OPENAI_API_BASE"] = "https://env.openai.com/v1/chat/completions" | |
| os.environ["OPENAI_API_KEY"] = "env-key-123" | |
| os.environ["OPENAI_API_MODEL"] = "env-model-456" | |
| # Provide TOML config (should take precedence) | |
| config = { | |
| "call_target": "https://toml.openai.com/v1/chat/completions", | |
| "api_key": "toml-key-789", | |
| "model": "toml-model-999" | |
| } | |
| result = Settings._build_llm_config(config, {"temperature": 0.5}) | |
| # Verify TOML config is used, not environment variables | |
| assert result.call_target == "https://toml.openai.com/v1/chat/completions" | |
| assert result.api_key == "toml-key-789" | |
| assert result.model == "toml-model-999" | |
| # Test Case 1.3: Partial environment variable usage | |
| def test_partial_env_var_usage(self): | |
| """Test mixing environment variables with some config values""" | |
| # Setup only some environment variables | |
| os.environ["OPENAI_API_KEY"] = "env-key-only" | |
| # Don't set OPENAI_API_BASE or OPENAI_API_MODEL | |
| # Provide partial TOML config | |
| config = { | |
| "call_target": "https://toml.openai.com/v1/chat/completions", | |
| "model": "toml-model" | |
| # api_key not provided in config | |
| } | |
| result = Settings._build_llm_config(config, {"temperature": 0.5}) | |
| # Verify mix of config and environment variables | |
| assert result.call_target == "https://toml.openai.com/v1/chat/completions" # From config | |
| assert result.api_key == "env-key-only" # From environment | |
| assert result.model == "toml-model" # From config | |
| # Test Case 1.4: No environment variables set (fallback to defaults) | |
| def test_no_env_vars_fallback_to_defaults(self): | |
| """Test fallback to hardcoded defaults when no environment variables are set""" | |
| # Don't set any environment variables | |
| # Call with empty config | |
| result = Settings._build_llm_config({}, {"temperature": 0.7}) | |
| # Verify hardcoded defaults are used | |
| assert result.call_target == "https://api.openai.com/v1/chat/completions" | |
| assert result.api_key == "your-api-key-here" | |
| assert result.model == "gpt-4o-mini" | |
| assert result.extract_body == {"temperature": 0.7} | |
| # Test Case 1.5: Environment variables with extract_body merging | |
| def test_env_vars_with_extract_body_merging(self): | |
| """Test environment variables work correctly with extract_body merging""" | |
| os.environ["OPENAI_API_BASE"] = "https://test.openai.com/v1/chat/completions" | |
| os.environ["OPENAI_API_KEY"] = "test-key" | |
| os.environ["OPENAI_API_MODEL"] = "test-model" | |
| # Provide config with extract_body | |
| config = { | |
| "extract_body": {"temperature": 0.8, "max_tokens": 2000} | |
| } | |
| result = Settings._build_llm_config(config, {"temperature": 0.5, "top_p": 0.9}) | |
| # Verify environment variables are used | |
| assert result.call_target == "https://test.openai.com/v1/chat/completions" | |
| assert result.api_key == "test-key" | |
| assert result.model == "test-model" | |
| # Verify extract_body merging: config overrides default | |
| assert result.extract_body == {"temperature": 0.8, "max_tokens": 2000, "top_p": 0.9} | |
| # Test Case 1.6: HTTP validation still works with environment variables | |
| def test_http_validation_with_env_vars(self): | |
| """Test that HTTP validation still works when using environment variables""" | |
| # Set invalid HTTP URL in environment | |
| os.environ["OPENAI_API_BASE"] = "invalid-url-without-http" | |
| config = {} # No config provided, should use env var | |
| # Should raise ValueError for invalid HTTP URL | |
| with pytest.raises(ValueError, match="call_target must be HTTP URL"): | |
| Settings._build_llm_config(config, {"temperature": 0.5}) | |
| # Test Case 1.7: Priority order: TOML > env vars > defaults | |
| def test_priority_order_comprehensive(self): | |
| """Comprehensive test of priority order: TOML > env vars > defaults""" | |
| # Setup environment variables | |
| os.environ["OPENAI_API_BASE"] = "https://env.openai.com/v1/chat/completions" | |
| os.environ["OPENAI_API_KEY"] = "env-key" | |
| os.environ["OPENAI_API_MODEL"] = "env-model" | |
| # Test 1: All from TOML config (highest priority) | |
| config1 = { | |
| "call_target": "https://toml.openai.com/v1/chat/completions", | |
| "api_key": "toml-key", | |
| "model": "toml-model" | |
| } | |
| result1 = Settings._build_llm_config(config1, {"temperature": 0.5}) | |
| assert result1.call_target == "https://toml.openai.com/v1/chat/completions" | |
| assert result1.api_key == "toml-key" | |
| assert result1.model == "toml-model" | |
| # Test 2: Mix of TOML and env vars | |
| config2 = { | |
| "call_target": "https://toml.openai.com/v1/chat/completions" | |
| # api_key and model not provided, should use env vars | |
| } | |
| result2 = Settings._build_llm_config(config2, {"temperature": 0.5}) | |
| assert result2.call_target == "https://toml.openai.com/v1/chat/completions" # TOML | |
| assert result2.api_key == "env-key" # Env var | |
| assert result2.model == "env-model" # Env var | |
| # Test 3: All from env vars | |
| result3 = Settings._build_llm_config({}, {"temperature": 0.5}) | |
| assert result3.call_target == "https://env.openai.com/v1/chat/completions" | |
| assert result3.api_key == "env-key" | |
| assert result3.model == "env-model" | |
| # Test 4: No env vars set, fallback to defaults | |
| # Clean up env vars | |
| os.environ.pop("OPENAI_API_BASE", None) | |
| os.environ.pop("OPENAI_API_KEY", None) | |
| os.environ.pop("OPENAI_API_MODEL", None) | |
| result4 = Settings._build_llm_config({}, {"temperature": 0.5}) | |
| assert result4.call_target == "https://api.openai.com/v1/chat/completions" # Default | |
| assert result4.api_key == "your-api-key-here" # Default | |
| assert result4.model == "gpt-4o-mini" # Default | |
| # Test Case 1.8: Backward compatibility with call_kwargs | |
| def test_backward_compatibility_call_kwargs(self): | |
| """Test that legacy call_kwargs still works with environment variables""" | |
| os.environ["OPENAI_API_KEY"] = "env-key" | |
| config = { | |
| "call_kwargs": {"temperature": 0.9, "max_tokens": 1500} | |
| } | |
| result = Settings._build_llm_config(config, {"temperature": 0.5}) | |
| # Verify environment variable is used | |
| assert result.api_key == "env-key" | |
| # Verify call_kwargs are merged with default extract_body | |
| assert result.extract_body["temperature"] == 0.9 # From call_kwargs | |
| assert result.extract_body["max_tokens"] == 1500 # From call_kwargs | |
| class TestInheritanceWithEnvironmentVariables: | |
| """Test environment variables work correctly with inheritance""" | |
| def setup_method(self): | |
| """Clean up environment variables""" | |
| env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"] | |
| for var in env_vars: | |
| os.environ.pop(var, None) | |
| def teardown_method(self): | |
| """Clean up environment variables""" | |
| env_vars = ["OPENAI_API_BASE", "OPENAI_API_KEY", "OPENAI_API_MODEL"] | |
| for var in env_vars: | |
| os.environ.pop(var, None) | |
| def test_inheritance_priority_over_env_vars(self): | |
| """Test that inheritance has priority over environment variables""" | |
| # This test verifies that the inheritance logic in to_ckagent_kwargs() | |
| # works correctly with the new environment variable fallback | |
| # Setup environment variables | |
| os.environ["OPENAI_API_KEY"] = "env-key" | |
| # Create settings with CK model having api_key, web model inheriting | |
| settings = Settings() | |
| settings.ck.model = LLMConfig( | |
| call_target="https://ck.openai.com/v1/chat/completions", | |
| api_key="ck-key", # This should be inherited by web model | |
| model="ck-model" | |
| ) | |
| # Web model should inherit from CK model, not use env var | |
| web_model_dict = { | |
| "call_target": "https://web.openai.com/v1/chat/completions", | |
| "model": "web-model" | |
| # api_key not specified, should inherit from ck.model | |
| } | |
| web_config = Settings._build_llm_config(web_model_dict, {"temperature": 0.0}) | |
| # The inheritance happens in to_ckagent_kwargs(), so this test | |
| # verifies that env vars don't interfere with inheritance logic | |
| assert web_config.call_target == "https://web.openai.com/v1/chat/completions" | |
| assert web_config.model == "web-model" | |
| # api_key should be inherited from ck.model, not from env var | |
| # (This test assumes inheritance logic is working correctly) | |
| def test_inheritance_with_model_field(self): | |
| """Test that model field is properly inherited from parent to child configs""" | |
| # Create settings with parent model | |
| settings = Settings() | |
| settings.ck.model = LLMConfig( | |
| call_target="https://parent.openai.com/v1/chat/completions", | |
| api_key="parent-key", | |
| model="parent-model" | |
| ) | |
| # Create child web model without model specified (should inherit) | |
| settings.web.model = LLMConfig( | |
| call_target="https://web.openai.com/v1/chat/completions", | |
| api_key="web-key", | |
| model="" # Empty model should trigger inheritance | |
| ) | |
| # Get kwargs and check inheritance | |
| kwargs = settings.to_ckagent_kwargs() | |
| web_agent_config = kwargs.get("web_agent", {}) | |
| web_model_config = web_agent_config.get("model", {}) | |
| # Verify that model was inherited from parent | |
| assert web_model_config.get("model") == "parent-model", f"Expected 'parent-model', got {web_model_config.get('model')}" | |
| # Verify other fields are preserved | |
| assert web_model_config.get("call_target") == "https://web.openai.com/v1/chat/completions" | |
| assert web_model_config.get("api_key") == "web-key" | |
| if __name__ == "__main__": | |
| pytest.main([__file__, "-v"]) |