From aafdf770d34b57101a01c2f38696c501416da27f Mon Sep 17 00:00:00 2001 From: Asankhaya Sharma Date: Tue, 30 Sep 2025 12:01:36 +0800 Subject: [PATCH 1/4] Add configurable SSL certificate verification support Introduces SSL verification and custom CA bundle configuration via CLI and environment variables. Updates server, plugins, and clients to propagate SSL settings, adds documentation (README.md, SSL_CONFIGURATION.md), and provides a test suite for SSL configuration. Security warnings are logged when SSL verification is disabled. --- README.md | 26 ++ SSL_CONFIGURATION.md | 88 ++++++ optillm/plugins/deep_research_plugin.py | 9 +- optillm/plugins/readurls_plugin.py | 24 +- optillm/server.py | 47 +++- tests/test_ssl_config.py | 354 ++++++++++++++++++++++++ 6 files changed, 534 insertions(+), 14 deletions(-) create mode 100644 SSL_CONFIGURATION.md create mode 100644 tests/test_ssl_config.py diff --git a/README.md b/README.md index 7b580b06..c9538e8c 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,32 @@ source .venv/bin/activate pip install -r requirements.txt ``` +## 🔒 SSL Configuration + +OptILLM supports SSL certificate verification configuration for working with self-signed certificates or corporate proxies. + +**Disable SSL verification (development only):** +```bash +# Command line +optillm --no-ssl-verify + +# Environment variable +export OPTILLM_SSL_VERIFY=false +optillm +``` + +**Use custom CA certificate:** +```bash +# Command line +optillm --ssl-cert-path /path/to/ca-bundle.crt + +# Environment variable +export OPTILLM_SSL_CERT_PATH=/path/to/ca-bundle.crt +optillm +``` + +⚠️ **Security Note**: Disabling SSL verification is insecure and should only be used in development. For production environments with custom CAs, use `--ssl-cert-path` instead. See [SSL_CONFIGURATION.md](SSL_CONFIGURATION.md) for details. + ## Implemented techniques | Approach | Slug | Description | diff --git a/SSL_CONFIGURATION.md b/SSL_CONFIGURATION.md new file mode 100644 index 00000000..8f091f09 --- /dev/null +++ b/SSL_CONFIGURATION.md @@ -0,0 +1,88 @@ +# SSL Certificate Configuration + +OptILLM now supports SSL certificate verification configuration to work with self-signed certificates or corporate proxies. + +## Usage + +### Disable SSL Verification (Development Only) + +**⚠️ WARNING: Only use this in development environments. Disabling SSL verification is insecure.** + +#### Via Command Line +```bash +python optillm.py --no-ssl-verify +``` + +#### Via Environment Variable +```bash +export OPTILLM_SSL_VERIFY=false +python optillm.py +``` + +### Use Custom CA Certificate Bundle + +For corporate environments with custom Certificate Authorities: + +#### Via Command Line +```bash +python optillm.py --ssl-cert-path /path/to/ca-bundle.crt +``` + +#### Via Environment Variable +```bash +export OPTILLM_SSL_CERT_PATH=/path/to/ca-bundle.crt +python optillm.py +``` + +## Configuration Options + +| Option | Environment Variable | Default | Description | +|--------|---------------------|---------|-------------| +| `--ssl-verify` / `--no-ssl-verify` | `OPTILLM_SSL_VERIFY` | `true` | Enable/disable SSL certificate verification | +| `--ssl-cert-path` | `OPTILLM_SSL_CERT_PATH` | `""` | Path to custom CA certificate bundle | + +## Affected Components + +SSL configuration applies to: +- **OpenAI API clients** (OpenAI, Azure, Cerebras) +- **HTTP plugins** (readurls, deep_research) +- **All external HTTPS connections** + +## Examples + +### Development with Self-Signed Certificate +```bash +# Disable SSL verification temporarily +python optillm.py --no-ssl-verify --base-url https://localhost:8443/v1 +``` + +### Production with Corporate CA +```bash +# Use corporate certificate bundle +python optillm.py --ssl-cert-path /etc/ssl/certs/corporate-ca-bundle.crt +``` + +### Docker Environment +```bash +docker run -e OPTILLM_SSL_VERIFY=false optillm +``` + +## Security Notes + +1. **Never disable SSL verification in production** - This makes your application vulnerable to man-in-the-middle attacks +2. **Use custom CA bundles instead** - For corporate environments, provide the proper CA certificate path +3. **Warning messages** - When SSL verification is disabled, OptILLM will log a warning message for security awareness + +## Testing + +Run the SSL configuration test suite: +```bash +python -m unittest tests.test_ssl_config -v +``` + +This validates: +- CLI argument parsing +- Environment variable configuration +- HTTP client SSL settings +- Plugin SSL propagation +- Warning messages \ No newline at end of file diff --git a/optillm/plugins/deep_research_plugin.py b/optillm/plugins/deep_research_plugin.py index 62717d9c..75a58f0f 100644 --- a/optillm/plugins/deep_research_plugin.py +++ b/optillm/plugins/deep_research_plugin.py @@ -66,6 +66,9 @@ def create(self, **kwargs): ) else: # OpenAI or AzureOpenAI + # Get existing http_client to preserve SSL settings + existing_http_client = getattr(self.parent.client, '_client', None) + if 'Azure' in self.parent.client.__class__.__name__: from openai import AzureOpenAI # AzureOpenAI has different parameters @@ -75,7 +78,8 @@ def create(self, **kwargs): azure_endpoint=getattr(self.parent.client, 'azure_endpoint', None), azure_ad_token_provider=getattr(self.parent.client, 'azure_ad_token_provider', None), timeout=self.parent.timeout, - max_retries=self.parent.max_retries + max_retries=self.parent.max_retries, + http_client=existing_http_client ) else: from openai import OpenAI @@ -83,7 +87,8 @@ def create(self, **kwargs): api_key=self.parent.client.api_key, base_url=getattr(self.parent.client, 'base_url', None), timeout=self.parent.timeout, - max_retries=self.parent.max_retries + max_retries=self.parent.max_retries, + http_client=existing_http_client ) return custom_client.chat.completions.create(**kwargs) except Exception as e: diff --git a/optillm/plugins/readurls_plugin.py b/optillm/plugins/readurls_plugin.py index cf9024ba..b799bca6 100644 --- a/optillm/plugins/readurls_plugin.py +++ b/optillm/plugins/readurls_plugin.py @@ -1,10 +1,10 @@ import re -from typing import Tuple, List +from typing import Tuple, List, Optional import requests import os from bs4 import BeautifulSoup from urllib.parse import urlparse -from optillm import __version__ +from optillm import __version__, server_config SLUG = "readurls" @@ -24,13 +24,27 @@ def extract_urls(text: str) -> List[str]: return cleaned_urls -def fetch_webpage_content(url: str, max_length: int = 100000) -> str: +def fetch_webpage_content(url: str, max_length: int = 100000, verify_ssl: Optional[bool] = None, cert_path: Optional[str] = None) -> str: try: headers = { 'User-Agent': f'optillm/{__version__} (https://github.com/codelion/optillm)' } - - response = requests.get(url, headers=headers, timeout=10) + + # Use SSL configuration from server_config if not explicitly provided + if verify_ssl is None: + verify_ssl = server_config.get('ssl_verify', True) + if cert_path is None: + cert_path = server_config.get('ssl_cert_path', '') + + # Determine verify parameter for requests + if not verify_ssl: + verify = False + elif cert_path: + verify = cert_path + else: + verify = True + + response = requests.get(url, headers=headers, timeout=10, verify=verify) response.raise_for_status() # Make a soup diff --git a/optillm/server.py b/optillm/server.py index c8237f48..29a0313d 100644 --- a/optillm/server.py +++ b/optillm/server.py @@ -58,7 +58,24 @@ conversation_logger = None def get_config(): + import httpx + API_KEY = None + + # Create httpx client with SSL configuration + ssl_verify = server_config.get('ssl_verify', True) + ssl_cert_path = server_config.get('ssl_cert_path', '') + + # Determine SSL verification setting + if not ssl_verify: + logger.warning("SSL certificate verification is DISABLED. This is insecure and should only be used for development.") + http_client = httpx.Client(verify=False) + elif ssl_cert_path: + logger.info(f"Using custom CA certificate bundle: {ssl_cert_path}") + http_client = httpx.Client(verify=ssl_cert_path) + else: + http_client = httpx.Client(verify=True) + if os.environ.get("OPTILLM_API_KEY"): # Use local inference engine from optillm.inference import create_inference_client @@ -69,16 +86,16 @@ def get_config(): API_KEY = os.environ.get("CEREBRAS_API_KEY") base_url = server_config['base_url'] if base_url != "": - default_client = Cerebras(api_key=API_KEY, base_url=base_url) + default_client = Cerebras(api_key=API_KEY, base_url=base_url, http_client=http_client) else: - default_client = Cerebras(api_key=API_KEY) + default_client = Cerebras(api_key=API_KEY, http_client=http_client) elif os.environ.get("OPENAI_API_KEY"): API_KEY = os.environ.get("OPENAI_API_KEY") base_url = server_config['base_url'] if base_url != "": - default_client = OpenAI(api_key=API_KEY, base_url=base_url) + default_client = OpenAI(api_key=API_KEY, base_url=base_url, http_client=http_client) else: - default_client = OpenAI(api_key=API_KEY) + default_client = OpenAI(api_key=API_KEY, http_client=http_client) elif os.environ.get("AZURE_OPENAI_API_KEY"): API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") API_VERSION = os.environ.get("AZURE_API_VERSION") @@ -88,6 +105,7 @@ def get_config(): api_key=API_KEY, api_version=API_VERSION, azure_endpoint=AZURE_ENDPOINT, + http_client=http_client ) else: from azure.identity import DefaultAzureCredential, get_bearer_token_provider @@ -96,7 +114,8 @@ def get_config(): default_client = AzureOpenAI( api_version=API_VERSION, azure_endpoint=AZURE_ENDPOINT, - azure_ad_token_provider=token_provider + azure_ad_token_provider=token_provider, + http_client=http_client ) else: # Import the LiteLLM wrapper @@ -152,7 +171,7 @@ def count_reasoning_tokens(text: str, tokenizer=None) -> int: # Server configuration server_config = { - 'approach': 'none', + 'approach': 'none', 'mcts_simulations': 2, 'mcts_exploration': 0.2, 'mcts_depth': 1, @@ -167,6 +186,8 @@ def count_reasoning_tokens(text: str, tokenizer=None) -> int: 'return_full_response': False, 'port': 8000, 'log': 'info', + 'ssl_verify': True, + 'ssl_cert_path': '', } # List of known approaches @@ -977,7 +998,19 @@ def parse_args(): base_url_default = os.environ.get("OPTILLM_BASE_URL", "") parser.add_argument("--base-url", "--base_url", dest="base_url", type=str, default=base_url_default, help="Base url for OpenAI compatible endpoint") - + + # SSL configuration arguments + ssl_verify_default = os.environ.get("OPTILLM_SSL_VERIFY", "true").lower() in ("true", "1", "yes") + parser.add_argument("--ssl-verify", dest="ssl_verify", action="store_true" if ssl_verify_default else "store_false", + default=ssl_verify_default, + help="Enable SSL certificate verification (default: True)") + parser.add_argument("--no-ssl-verify", dest="ssl_verify", action="store_false", + help="Disable SSL certificate verification") + + ssl_cert_path_default = os.environ.get("OPTILLM_SSL_CERT_PATH", "") + parser.add_argument("--ssl-cert-path", dest="ssl_cert_path", type=str, default=ssl_cert_path_default, + help="Path to custom CA certificate bundle for SSL verification") + # Use the function to get the default path default_config_path = get_config_path() diff --git a/tests/test_ssl_config.py b/tests/test_ssl_config.py new file mode 100644 index 00000000..8f58c6ac --- /dev/null +++ b/tests/test_ssl_config.py @@ -0,0 +1,354 @@ +""" +Unit tests for SSL configuration support in optillm. + +Tests verify that SSL certificate verification can be configured via: +- Command-line arguments (--ssl-verify, --no-ssl-verify, --ssl-cert-path) +- Environment variables (OPTILLM_SSL_VERIFY, OPTILLM_SSL_CERT_PATH) +- And that SSL settings are properly propagated to HTTP clients +""" + +import unittest +from unittest.mock import Mock, patch, MagicMock, call +import sys +import os +import tempfile +import httpx + +# Add parent directory to path to import optillm modules +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from optillm import server_config, parse_args + + +class TestSSLConfiguration(unittest.TestCase): + """Test SSL configuration via CLI arguments and environment variables.""" + + def setUp(self): + """Reset server_config before each test.""" + # Save original config + self.original_config = server_config.copy() + + # Clear SSL-related environment variables + for key in ['OPTILLM_SSL_VERIFY', 'OPTILLM_SSL_CERT_PATH']: + if key in os.environ: + del os.environ[key] + + def tearDown(self): + """Restore original server_config after each test.""" + server_config.clear() + server_config.update(self.original_config) + + def test_default_ssl_verify_enabled(self): + """Test that SSL verification is enabled by default.""" + self.assertTrue(server_config.get('ssl_verify', True)) + self.assertEqual(server_config.get('ssl_cert_path', ''), '') + + def test_cli_no_ssl_verify_flag(self): + """Test --no-ssl-verify CLI flag disables SSL verification.""" + with patch('sys.argv', ['optillm', '--no-ssl-verify']): + args = parse_args() + self.assertFalse(args.ssl_verify) + + def test_cli_ssl_cert_path(self): + """Test --ssl-cert-path CLI argument.""" + test_cert_path = '/path/to/ca-bundle.crt' + with patch('sys.argv', ['optillm', '--ssl-cert-path', test_cert_path]): + args = parse_args() + self.assertEqual(args.ssl_cert_path, test_cert_path) + + def test_env_ssl_verify_false(self): + """Test OPTILLM_SSL_VERIFY=false environment variable.""" + os.environ['OPTILLM_SSL_VERIFY'] = 'false' + with patch('sys.argv', ['optillm']): + args = parse_args() + self.assertFalse(args.ssl_verify) + + def test_env_ssl_verify_true(self): + """Test OPTILLM_SSL_VERIFY=true environment variable.""" + os.environ['OPTILLM_SSL_VERIFY'] = 'true' + with patch('sys.argv', ['optillm']): + args = parse_args() + self.assertTrue(args.ssl_verify) + + def test_env_ssl_cert_path(self): + """Test OPTILLM_SSL_CERT_PATH environment variable.""" + test_cert_path = '/etc/ssl/certs/custom-ca.pem' + os.environ['OPTILLM_SSL_CERT_PATH'] = test_cert_path + with patch('sys.argv', ['optillm']): + args = parse_args() + self.assertEqual(args.ssl_cert_path, test_cert_path) + + def test_cli_overrides_env(self): + """Test that CLI arguments override environment variables.""" + os.environ['OPTILLM_SSL_VERIFY'] = 'true' + with patch('sys.argv', ['optillm', '--no-ssl-verify']): + args = parse_args() + self.assertFalse(args.ssl_verify) + + +class TestHTTPClientSSLConfiguration(unittest.TestCase): + """Test that SSL configuration is properly applied to HTTP clients.""" + + def setUp(self): + """Set up test environment.""" + self.original_config = server_config.copy() + + def tearDown(self): + """Restore original server_config.""" + server_config.clear() + server_config.update(self.original_config) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_httpx_client_ssl_verify_disabled(self): + """Test httpx.Client created with verify=False when SSL disabled.""" + from optillm.server import get_config + + # Configure to disable SSL verification + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + + # Create client + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + # Verify httpx.Client was called with verify=False + mock_httpx_client.assert_called_once_with(verify=False) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_httpx_client_ssl_verify_enabled(self): + """Test httpx.Client created with verify=True by default.""" + from optillm.server import get_config + + # Configure to enable SSL verification (default) + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + + # Create client + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + # Verify httpx.Client was called with verify=True + mock_httpx_client.assert_called_once_with(verify=True) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_httpx_client_custom_cert_path(self): + """Test httpx.Client created with custom certificate path.""" + from optillm.server import get_config + + # Configure custom certificate path + test_cert_path = '/path/to/custom-ca.pem' + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = test_cert_path + + # Create client + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + # Verify httpx.Client was called with custom cert path + mock_httpx_client.assert_called_once_with(verify=test_cert_path) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_openai_client_receives_http_client(self): + """Test that OpenAI client receives the configured httpx client.""" + from optillm.server import get_config + + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + mock_http_client_instance = MagicMock() + + with patch('httpx.Client', return_value=mock_http_client_instance) as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + + # Verify OpenAI was called with http_client parameter + mock_openai.assert_called_once() + call_kwargs = mock_openai.call_args[1] + self.assertIn('http_client', call_kwargs) + self.assertEqual(call_kwargs['http_client'], mock_http_client_instance) + + @patch.dict(os.environ, {'CEREBRAS_API_KEY': 'test-key'}) + def test_cerebras_client_receives_http_client(self): + """Test that Cerebras client receives the configured httpx client.""" + from optillm.server import get_config + + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + mock_http_client_instance = MagicMock() + + with patch('httpx.Client', return_value=mock_http_client_instance) as mock_httpx_client, \ + patch('optillm.server.Cerebras') as mock_cerebras: + get_config() + + # Verify Cerebras was called with http_client parameter + mock_cerebras.assert_called_once() + call_kwargs = mock_cerebras.call_args[1] + self.assertIn('http_client', call_kwargs) + self.assertEqual(call_kwargs['http_client'], mock_http_client_instance) + + @patch.dict(os.environ, {'AZURE_OPENAI_API_KEY': 'test-key', 'AZURE_API_VERSION': '2024-02-15-preview', 'AZURE_API_BASE': 'https://test.openai.azure.com'}) + def test_azure_client_receives_http_client(self): + """Test that AzureOpenAI client receives the configured httpx client.""" + from optillm.server import get_config + + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + + mock_http_client_instance = MagicMock() + + with patch('httpx.Client', return_value=mock_http_client_instance) as mock_httpx_client, \ + patch('optillm.server.AzureOpenAI') as mock_azure: + get_config() + + # Verify AzureOpenAI was called with http_client parameter + mock_azure.assert_called_once() + call_kwargs = mock_azure.call_args[1] + self.assertIn('http_client', call_kwargs) + self.assertEqual(call_kwargs['http_client'], mock_http_client_instance) + + +class TestPluginSSLConfiguration(unittest.TestCase): + """Test that plugins properly use SSL configuration.""" + + def setUp(self): + """Set up test environment.""" + self.original_config = server_config.copy() + + def tearDown(self): + """Restore original server_config.""" + server_config.clear() + server_config.update(self.original_config) + + @patch('optillm.plugins.readurls_plugin.requests.get') + def test_readurls_plugin_ssl_verify_disabled(self, mock_requests_get): + """Test readurls plugin respects SSL verification disabled.""" + from optillm.plugins.readurls_plugin import fetch_webpage_content + + # Configure to disable SSL verification + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + + # Mock response + mock_response = MagicMock() + mock_response.content = b'

Test content

' + mock_response.raise_for_status = MagicMock() + mock_requests_get.return_value = mock_response + + # Fetch webpage + fetch_webpage_content('https://example.com') + + # Verify requests.get was called with verify=False + mock_requests_get.assert_called_once() + call_kwargs = mock_requests_get.call_args[1] + self.assertIn('verify', call_kwargs) + self.assertFalse(call_kwargs['verify']) + + @patch('optillm.plugins.readurls_plugin.requests.get') + def test_readurls_plugin_ssl_verify_enabled(self, mock_requests_get): + """Test readurls plugin respects SSL verification enabled.""" + from optillm.plugins.readurls_plugin import fetch_webpage_content + + # Configure to enable SSL verification + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + + # Mock response + mock_response = MagicMock() + mock_response.content = b'

Test content

' + mock_response.raise_for_status = MagicMock() + mock_requests_get.return_value = mock_response + + # Fetch webpage + fetch_webpage_content('https://example.com') + + # Verify requests.get was called with verify=True + mock_requests_get.assert_called_once() + call_kwargs = mock_requests_get.call_args[1] + self.assertIn('verify', call_kwargs) + self.assertTrue(call_kwargs['verify']) + + @patch('optillm.plugins.readurls_plugin.requests.get') + def test_readurls_plugin_custom_cert_path(self, mock_requests_get): + """Test readurls plugin uses custom certificate path.""" + from optillm.plugins.readurls_plugin import fetch_webpage_content + + # Configure custom certificate path + test_cert_path = '/path/to/custom-ca.pem' + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = test_cert_path + + # Mock response + mock_response = MagicMock() + mock_response.content = b'

Test content

' + mock_response.raise_for_status = MagicMock() + mock_requests_get.return_value = mock_response + + # Fetch webpage + fetch_webpage_content('https://example.com') + + # Verify requests.get was called with custom cert path + mock_requests_get.assert_called_once() + call_kwargs = mock_requests_get.call_args[1] + self.assertIn('verify', call_kwargs) + self.assertEqual(call_kwargs['verify'], test_cert_path) + + +class TestSSLWarnings(unittest.TestCase): + """Test that appropriate warnings are shown when SSL is disabled.""" + + def setUp(self): + """Set up test environment.""" + self.original_config = server_config.copy() + + def tearDown(self): + """Restore original server_config.""" + server_config.clear() + server_config.update(self.original_config) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_warning_when_ssl_disabled(self): + """Test that a warning is logged when SSL verification is disabled.""" + from optillm.server import get_config + + # Configure to disable SSL verification + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai, \ + patch('optillm.server.logger.warning') as mock_logger_warning: + get_config() + + # Verify warning was logged + mock_logger_warning.assert_called() + warning_message = mock_logger_warning.call_args[0][0] + self.assertIn('SSL certificate verification is DISABLED', warning_message) + self.assertIn('insecure', warning_message.lower()) + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'test-key'}) + def test_info_when_custom_cert_used(self): + """Test that an info message is logged when using custom certificate.""" + from optillm.server import get_config + + # Configure custom certificate path + test_cert_path = '/path/to/custom-ca.pem' + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = test_cert_path + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai, \ + patch('optillm.server.logger.info') as mock_logger_info: + get_config() + + # Verify info message was logged + mock_logger_info.assert_called() + info_message = mock_logger_info.call_args[0][0] + self.assertIn('custom CA certificate bundle', info_message) + self.assertIn(test_cert_path, info_message) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From c4f5031507233829bab8b859e1eb6d8467d56f21 Mon Sep 17 00:00:00 2001 From: Asankhaya Sharma Date: Tue, 30 Sep 2025 12:17:56 +0800 Subject: [PATCH 2/4] Add disk space cleanup to Docker publish workflows Introduced a step to free up disk space in both AMD64 and ARM64 Docker offline publish workflows by removing unused directories and pruning Docker system. This helps prevent build failures due to insufficient disk space on GitHub Actions runners. --- .github/workflows/publish-docker-offline-amd64.yml | 8 ++++++++ .github/workflows/publish-docker-offline-arm64.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/publish-docker-offline-amd64.yml b/.github/workflows/publish-docker-offline-amd64.yml index e8f078f7..8cabc303 100644 --- a/.github/workflows/publish-docker-offline-amd64.yml +++ b/.github/workflows/publish-docker-offline-amd64.yml @@ -13,6 +13,14 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + docker system prune -af + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/publish-docker-offline-arm64.yml b/.github/workflows/publish-docker-offline-arm64.yml index 70068662..9f727165 100644 --- a/.github/workflows/publish-docker-offline-arm64.yml +++ b/.github/workflows/publish-docker-offline-arm64.yml @@ -13,6 +13,14 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + docker system prune -af + - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 2c0f163b54ecf752fd4563eaa3c8245bc4befa07 Mon Sep 17 00:00:00 2001 From: Asankhaya Sharma Date: Tue, 30 Sep 2025 12:19:09 +0800 Subject: [PATCH 3/4] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9538e8c..17f06230 100644 --- a/README.md +++ b/README.md @@ -102,10 +102,18 @@ docker run -p 8000:8000 ghcr.io/codelion/optillm:latest 2024-10-22 07:45:06,293 - INFO - Starting server with approach: auto ``` -To use optillm without local inference and only as a proxy you can add the `-proxy` suffix. +**Available Docker image variants:** + +- **Full image** (`latest`): Includes all dependencies for local inference and plugins +- **Proxy-only** (`latest-proxy`): Lightweight image without local inference capabilities +- **Offline** (`latest-offline`): Self-contained image with pre-downloaded models (spaCy) for fully offline operation ```bash +# Proxy-only (smallest) docker pull ghcr.io/codelion/optillm:latest-proxy + +# Offline (largest, includes pre-downloaded models) +docker pull ghcr.io/codelion/optillm:latest-offline ``` ### Install from source From f82443b8f363b7933e1f13c8e130b3f5a538243b Mon Sep 17 00:00:00 2001 From: Asankhaya Sharma Date: Tue, 30 Sep 2025 12:26:01 +0800 Subject: [PATCH 4/4] Bump version to 0.3.2 Update __init__.py and pyproject.toml to reflect new version 0.3.2. No other changes included. --- optillm/__init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/optillm/__init__.py b/optillm/__init__.py index f22e9889..dee0c8b6 100644 --- a/optillm/__init__.py +++ b/optillm/__init__.py @@ -1,5 +1,5 @@ # Version information -__version__ = "0.3.1" +__version__ = "0.3.2" # Import from server module from .server import ( diff --git a/pyproject.toml b/pyproject.toml index 02dd0824..f822c077 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "optillm" -version = "0.3.1" +version = "0.3.2" description = "An optimizing inference proxy for LLMs." readme = "README.md" license = "Apache-2.0"