From 80b22232adac46858a966249655f3b7556702339 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 1 Nov 2025 17:21:54 -0400 Subject: [PATCH] docs: add integration tests and documentation for config override system --- API_REFERENCE.md | 23 ++++ QUICK_START.md | 39 ++++++- tests/integration/test_config_override.py | 121 ++++++++++++++++++++++ 3 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 tests/integration/test_config_override.py diff --git a/API_REFERENCE.md b/API_REFERENCE.md index 4fe5eec..9dd5287 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -729,6 +729,29 @@ Server loads model definitions from configuration file (default: `configs/defaul - `openai_base_url` - Optional custom API endpoint - `openai_api_key` - Optional model-specific API key +### Configuration Override System + +**Default config:** `/app/configs/default_config.json` (baked into image) + +**Custom config:** `/app/user-configs/config.json` (optional, via volume mount) + +**Merge behavior:** +- Custom config sections completely replace default sections (root-level merge) +- If no custom config exists, defaults are used +- Validation occurs at container startup (before API starts) +- Invalid config causes immediate exit with detailed error message + +**Example custom config** (overrides models only): +```json +{ + "models": [ + {"name": "gpt-5", "basemodel": "openai/gpt-5", "signature": "gpt-5", "enabled": true} + ] +} +``` + +All other sections (`agent_config`, `log_config`, etc.) inherited from default. + --- ## OpenAPI / Swagger Documentation diff --git a/QUICK_START.md b/QUICK_START.md index 300f32e..3aa735c 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -54,7 +54,36 @@ JINA_API_KEY=your-jina-key-here --- -## Step 3: Start the API Server +## Step 3: (Optional) Custom Model Configuration + +To use different AI models than the defaults, create a custom config: + +1. Create config directory: + ```bash + mkdir -p configs + ``` + +2. Create `configs/config.json`: + ```json + { + "models": [ + { + "name": "my-gpt-4", + "basemodel": "openai/gpt-4", + "signature": "my-gpt-4", + "enabled": true + } + ] + } + ``` + +3. The Docker container will automatically merge this with default settings. + +Your custom config only needs to include sections you want to override. + +--- + +## Step 4: Start the API Server ```bash docker-compose up -d @@ -79,7 +108,7 @@ docker logs -f ai-trader-server --- -## Step 4: Verify Service is Running +## Step 5: Verify Service is Running ```bash curl http://localhost:8080/health @@ -99,7 +128,7 @@ If you see `"status": "healthy"`, you're ready! --- -## Step 5: Run Your First Simulation +## Step 6: Run Your First Simulation Trigger a simulation for a single day with GPT-4: @@ -130,7 +159,7 @@ curl -X POST http://localhost:8080/simulate/trigger \ --- -## Step 6: Monitor Progress +## Step 7: Monitor Progress ```bash # Replace with your job_id from Step 5 @@ -175,7 +204,7 @@ curl http://localhost:8080/simulate/status/$JOB_ID --- -## Step 7: View Results +## Step 8: View Results ```bash curl "http://localhost:8080/results?job_id=$JOB_ID" | jq '.' diff --git a/tests/integration/test_config_override.py b/tests/integration/test_config_override.py new file mode 100644 index 0000000..54db87a --- /dev/null +++ b/tests/integration/test_config_override.py @@ -0,0 +1,121 @@ +"""Integration tests for config override system.""" + +import pytest +import json +import subprocess +import tempfile +from pathlib import Path + + +@pytest.fixture +def test_configs(tmp_path): + """Create test config files.""" + # Default config + default_config = { + "agent_type": "BaseAgent", + "date_range": {"init_date": "2025-10-01", "end_date": "2025-10-21"}, + "models": [ + {"name": "default-model", "basemodel": "openai/gpt-4", "signature": "default", "enabled": True} + ], + "agent_config": {"max_steps": 30, "max_retries": 3, "base_delay": 1.0, "initial_cash": 10000.0}, + "log_config": {"log_path": "./data/agent_data"} + } + + configs_dir = tmp_path / "configs" + configs_dir.mkdir() + + default_path = configs_dir / "default_config.json" + with open(default_path, 'w') as f: + json.dump(default_config, f, indent=2) + + return configs_dir, default_config + + +def test_config_override_models_only(test_configs): + """Test overriding only the models section.""" + configs_dir, default_config = test_configs + + # Custom config - only override models + custom_config = { + "models": [ + {"name": "gpt-5", "basemodel": "openai/gpt-5", "signature": "gpt-5", "enabled": True} + ] + } + + user_configs_dir = configs_dir.parent / "user-configs" + user_configs_dir.mkdir() + + custom_path = user_configs_dir / "config.json" + with open(custom_path, 'w') as f: + json.dump(custom_config, f, indent=2) + + # Run merge + result = subprocess.run( + [ + "python", "-c", + f"import sys; sys.path.insert(0, '.'); " + f"from tools.config_merger import DEFAULT_CONFIG_PATH, CUSTOM_CONFIG_PATH, OUTPUT_CONFIG_PATH, merge_and_validate; " + f"import tools.config_merger; " + f"tools.config_merger.DEFAULT_CONFIG_PATH = '{configs_dir}/default_config.json'; " + f"tools.config_merger.CUSTOM_CONFIG_PATH = '{custom_path}'; " + f"tools.config_merger.OUTPUT_CONFIG_PATH = '{configs_dir.parent}/runtime.json'; " + f"merge_and_validate()" + ], + capture_output=True, + text=True, + cwd="/home/bballou/AI-Trader/.worktrees/config-override-system" + ) + + assert result.returncode == 0, f"Merge failed: {result.stderr}" + + # Verify merged config + runtime_path = configs_dir.parent / "runtime.json" + with open(runtime_path, 'r') as f: + merged = json.load(f) + + # Models should be overridden + assert merged["models"] == custom_config["models"] + + # Other sections should be from default + assert merged["agent_config"] == default_config["agent_config"] + assert merged["date_range"] == default_config["date_range"] + + +def test_config_validation_fails_gracefully(test_configs): + """Test that invalid config causes exit with clear error.""" + configs_dir, _ = test_configs + + # Invalid custom config (no enabled models) + custom_config = { + "models": [ + {"name": "test", "basemodel": "openai/gpt-4", "signature": "test", "enabled": False} + ] + } + + user_configs_dir = configs_dir.parent / "user-configs" + user_configs_dir.mkdir() + + custom_path = user_configs_dir / "config.json" + with open(custom_path, 'w') as f: + json.dump(custom_config, f, indent=2) + + # Run merge (should fail) + result = subprocess.run( + [ + "python", "-c", + f"import sys; sys.path.insert(0, '.'); " + f"from tools.config_merger import merge_and_validate; " + f"import tools.config_merger; " + f"tools.config_merger.DEFAULT_CONFIG_PATH = '{configs_dir}/default_config.json'; " + f"tools.config_merger.CUSTOM_CONFIG_PATH = '{custom_path}'; " + f"tools.config_merger.OUTPUT_CONFIG_PATH = '{configs_dir.parent}/runtime.json'; " + f"merge_and_validate()" + ], + capture_output=True, + text=True, + cwd="/home/bballou/AI-Trader/.worktrees/config-override-system" + ) + + assert result.returncode == 1 + assert "CONFIG VALIDATION FAILED" in result.stderr + assert "At least one model must be enabled" in result.stderr