From 8fa0cc5699590152149ffd464e16cbfe47006363 Mon Sep 17 00:00:00 2001 From: matano Date: Tue, 20 Jan 2026 15:40:13 +0200 Subject: [PATCH 01/22] support a custom openai api base --- docker-compose.yml | 4 +++- src/api/provider_validation.py | 17 +++++++++++++---- src/services/models_service.py | 6 +++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 305745061..c88b59bac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,7 @@ services: - OPENSEARCH_USERNAME=admin - OPENSEARCH_PASSWORD=${OPENSEARCH_PASSWORD} - OPENAI_API_KEY=${OPENAI_API_KEY} + - OPENAI_API_BASE=${OPENAI_API_BASE:-None} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - WATSONX_API_KEY=${WATSONX_API_KEY} - WATSONX_ENDPOINT=${WATSONX_ENDPOINT} @@ -114,6 +115,7 @@ services: - LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY:-} - LANGFUSE_HOST=${LANGFUSE_HOST:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-None} + - OPENAI_API_BASE=${OPENAI_API_BASE:-None} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-None} - WATSONX_API_KEY=${WATSONX_API_KEY:-None} - WATSONX_ENDPOINT=${WATSONX_ENDPOINT:-None} @@ -133,7 +135,7 @@ services: - MIMETYPE=None - FILESIZE=0 - SELECTED_EMBEDDING_MODEL=${SELECTED_EMBEDDING_MODEL:-} - - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL + - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,OPENAI_API_BASE,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL - LANGFLOW_LOG_LEVEL=DEBUG - LANGFLOW_AUTO_LOGIN=${LANGFLOW_AUTO_LOGIN} - LANGFLOW_SUPERUSER=${LANGFLOW_SUPERUSER} diff --git a/src/api/provider_validation.py b/src/api/provider_validation.py index 813826a18..2f587fb49 100644 --- a/src/api/provider_validation.py +++ b/src/api/provider_validation.py @@ -1,6 +1,8 @@ """Provider validation utilities for testing API keys and models during onboarding.""" import json +import os + import httpx from utils.container_utils import transform_localhost_url from utils.logging_config import get_logger @@ -107,6 +109,9 @@ def _extract_error_details(response: httpx.Response) -> str: return parsed return response_text +def get_openai_url(endpoint: str) -> str: + api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") + return f"{api_base}{endpoint}" async def validate_provider_setup( provider: str, @@ -248,10 +253,11 @@ async def _test_openai_lightweight_health(api_key: str) -> None: "Content-Type": "application/json", } + url = get_openai_url(endpoint="/v1/models") async with httpx.AsyncClient() as client: # Use /v1/models endpoint which validates the key without consuming credits response = await client.get( - "https://api.openai.com/v1/models", + url=url, headers=headers, timeout=10.0, # Short timeout for lightweight check ) @@ -309,8 +315,9 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> No async with httpx.AsyncClient() as client: # Try with max_tokens first payload = {**base_payload, "max_tokens": 50} + url = get_openai_url(endpoint="/v1/chat/completions") response = await client.post( - "https://api.openai.com/v1/chat/completions", + url=url, headers=headers, json=payload, timeout=30.0, @@ -320,8 +327,9 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> No if response.status_code != 200: logger.info("max_tokens parameter failed, trying max_completion_tokens instead") payload = {**base_payload, "max_completion_tokens": 50} + url = get_openai_url(endpoint="/v1/chat/completions") response = await client.post( - "https://api.openai.com/v1/chat/completions", + url=url, headers=headers, json=payload, timeout=30.0, @@ -356,8 +364,9 @@ async def _test_openai_embedding(api_key: str, embedding_model: str) -> None: } async with httpx.AsyncClient() as client: + url = get_openai_url(endpoint="/v1/embeddings") response = await client.post( - "https://api.openai.com/v1/embeddings", + url=url, headers=headers, json=payload, timeout=30.0, diff --git a/src/services/models_service.py b/src/services/models_service.py index cd08b7085..318dfecea 100644 --- a/src/services/models_service.py +++ b/src/services/models_service.py @@ -1,3 +1,5 @@ +import os + import httpx from typing import Dict, List from utils.container_utils import transform_localhost_url @@ -54,8 +56,10 @@ async def get_openai_models(self, api_key: str) -> Dict[str, List[Dict[str, str] async with httpx.AsyncClient() as client: # Lightweight validation: just check if API key is valid # This doesn't consume credits, only validates the key + from api.provider_validation import get_openai_url + url = get_openai_url(endpoint="/v1/models") response = await client.get( - "https://api.openai.com/v1/models", headers=headers, timeout=10.0 + url, headers=headers, timeout=10.0 ) if response.status_code == 200: From cc2187b3eccac473837f10f182b1790f145c2f7a Mon Sep 17 00:00:00 2001 From: matano Date: Thu, 29 Jan 2026 13:17:30 +0200 Subject: [PATCH 02/22] fix merge --- docker-compose.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8a5e8d41e..f28e682a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,11 +139,7 @@ services: - MIMETYPE=None - FILESIZE=0 - SELECTED_EMBEDDING_MODEL=${SELECTED_EMBEDDING_MODEL:-} -<<<<<<< HEAD - - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,OPENAI_API_BASE,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL -======= - - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL ->>>>>>> remotes/upstream/main + - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,OPENAI_API_BASE,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL>>>>>>> remotes/upstream/main - LANGFLOW_LOG_LEVEL=DEBUG - LANGFLOW_AUTO_LOGIN=${LANGFLOW_AUTO_LOGIN} - LANGFLOW_SUPERUSER=${LANGFLOW_SUPERUSER} From 1794b0df13deb8c6002c17f9f39e084a9a1260c4 Mon Sep 17 00:00:00 2001 From: matano Date: Thu, 29 Jan 2026 14:43:03 +0200 Subject: [PATCH 03/22] further changes to support custom endpoints --- src/api/models.py | 18 ++++++++++++- src/api/provider_health.py | 2 +- src/api/provider_validation.py | 49 +++++++++++++++++----------------- src/services/models_service.py | 6 ++--- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/src/api/models.py b/src/api/models.py index 118126029..961e1d8c9 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,3 +1,5 @@ +import os + from starlette.responses import JSONResponse from utils.logging_config import get_logger from config.settings import get_openrag_config @@ -10,9 +12,11 @@ async def get_openai_models(request, models_service, session_manager): try: # Get API key from request body api_key = None + api_base = None try: body = await request.json() api_key = body.get("api_key") if body else None + api_base = body.get("api_base") if body else None except Exception: # Body might be empty or invalid JSON, continue to fallback pass @@ -36,7 +40,19 @@ async def get_openai_models(request, models_service, session_manager): status_code=400, ) - models = await models_service.get_openai_models(api_key=api_key) + if not api_base: + try: + config = get_openrag_config() + api_base = config.providers.openai.endpoint + logger.info( + f"Retrieved OpenAI API base from config: {'yes' if api_base else 'no'}" + ) + except Exception as e: + logger.error(f"Failed to get config: {e}") + if not api_base: + api_base = os.environ.get("OPENAI_BASE_API", "https://api.openai.com") + + models = await models_service.get_openai_models(api_key=api_key, api_base=api_base) return JSONResponse(models) except Exception as e: logger.error(f"Failed to get OpenAI models: {str(e)}") diff --git a/src/api/provider_health.py b/src/api/provider_health.py index 3802d8dcb..36238e279 100644 --- a/src/api/provider_health.py +++ b/src/api/provider_health.py @@ -42,7 +42,7 @@ async def check_provider_health(request): provider = current_config.agent.llm_provider # Validate provider name - valid_providers = ["openai", "ollama", "watsonx", "anthropic"] + valid_providers = ["openai_ete", "openai", "ollama", "watsonx", "anthropic"] if provider not in valid_providers: return JSONResponse( { diff --git a/src/api/provider_validation.py b/src/api/provider_validation.py index 2f587fb49..358264998 100644 --- a/src/api/provider_validation.py +++ b/src/api/provider_validation.py @@ -109,10 +109,6 @@ def _extract_error_details(response: httpx.Response) -> str: return parsed return response_text -def get_openai_url(endpoint: str) -> str: - api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") - return f"{api_base}{endpoint}" - async def validate_provider_setup( provider: str, api_key: str = None, @@ -130,7 +126,7 @@ async def validate_provider_setup( api_key: API key for the provider (optional for ollama) embedding_model: Embedding model to test llm_model: LLM model to test - endpoint: Provider endpoint (required for ollama and watsonx) + endpoint: Provider endpoint (required for ollama and watsonx, optional for openai) project_id: Project ID (required for watsonx) test_completion: If True, performs full validation with completion/embedding tests (consumes credits). If False, performs lightweight validation (no credits consumed). Default: False. @@ -143,11 +139,14 @@ async def validate_provider_setup( try: logger.info(f"Starting validation for provider: {provider_lower} (test_completion={test_completion})") + if provider == "openai" and not endpoint: + endpoint = os.environ.get("OPENAI_BASE_API", "https://api.openai.com") + if test_completion: # Full validation with completion/embedding tests (consumes credits) if embedding_model: # Test embedding - await test_embedding( + await _test_embedding( provider=provider_lower, api_key=api_key, embedding_model=embedding_model, @@ -156,7 +155,7 @@ async def validate_provider_setup( ) elif llm_model: # Test completion with tool calling - await test_completion_with_tools( + await _test_completion_with_tools( provider=provider_lower, api_key=api_key, llm_model=llm_model, @@ -165,7 +164,7 @@ async def validate_provider_setup( ) else: # Lightweight validation (no credits consumed) - await test_lightweight_health( + await _test_lightweight_health( provider=provider_lower, api_key=api_key, endpoint=endpoint, @@ -180,7 +179,7 @@ async def validate_provider_setup( raise -async def test_lightweight_health( +async def _test_lightweight_health( provider: str, api_key: str = None, endpoint: str = None, @@ -188,8 +187,8 @@ async def test_lightweight_health( ) -> None: """Test provider health with lightweight check (no credits consumed).""" - if provider == "openai": - await _test_openai_lightweight_health(api_key) + if provider.startswith("openai"): + await _test_openai_lightweight_health(api_key, endpoint) elif provider == "watsonx": await _test_watsonx_lightweight_health(api_key, endpoint, project_id) elif provider == "ollama": @@ -200,7 +199,7 @@ async def test_lightweight_health( raise ValueError(f"Unknown provider: {provider}") -async def test_completion_with_tools( +async def _test_completion_with_tools( provider: str, api_key: str = None, llm_model: str = None, @@ -209,8 +208,8 @@ async def test_completion_with_tools( ) -> None: """Test completion with tool calling for the provider.""" - if provider == "openai": - await _test_openai_completion_with_tools(api_key, llm_model) + if provider.startswith("openai"): + await _test_openai_completion_with_tools(api_key, llm_model, endpoint) elif provider == "watsonx": await _test_watsonx_completion_with_tools(api_key, llm_model, endpoint, project_id) elif provider == "ollama": @@ -221,7 +220,7 @@ async def test_completion_with_tools( raise ValueError(f"Unknown provider: {provider}") -async def test_embedding( +async def _test_embedding( provider: str, api_key: str = None, embedding_model: str = None, @@ -230,8 +229,8 @@ async def test_embedding( ) -> None: """Test embedding generation for the provider.""" - if provider == "openai": - await _test_openai_embedding(api_key, embedding_model) + if provider.startswith("openai"): + await _test_openai_embedding(api_key, embedding_model, endpoint) elif provider == "watsonx": await _test_watsonx_embedding(api_key, embedding_model, endpoint, project_id) elif provider == "ollama": @@ -241,7 +240,7 @@ async def test_embedding( # OpenAI validation functions -async def _test_openai_lightweight_health(api_key: str) -> None: +async def _test_openai_lightweight_health(api_key: str, endpoint: str) -> None: """Test OpenAI API key validity with lightweight check. Only checks if the API key is valid without consuming credits. @@ -253,7 +252,8 @@ async def _test_openai_lightweight_health(api_key: str) -> None: "Content-Type": "application/json", } - url = get_openai_url(endpoint="/v1/models") + url = f"{endpoint}/v1/models" + logger.debug("Testing openai lightweight health", url=url) async with httpx.AsyncClient() as client: # Use /v1/models endpoint which validates the key without consuming credits response = await client.get( @@ -277,7 +277,7 @@ async def _test_openai_lightweight_health(api_key: str) -> None: raise -async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> None: +async def _test_openai_completion_with_tools(api_key: str, llm_model: str, endpoint: str) -> None: """Test OpenAI completion with tool calling.""" try: headers = { @@ -315,7 +315,8 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> No async with httpx.AsyncClient() as client: # Try with max_tokens first payload = {**base_payload, "max_tokens": 50} - url = get_openai_url(endpoint="/v1/chat/completions") + url = f"{endpoint}/v1/chat/completions" + logger.debug("Test openai completion tools", url=url) response = await client.post( url=url, headers=headers, @@ -327,7 +328,7 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> No if response.status_code != 200: logger.info("max_tokens parameter failed, trying max_completion_tokens instead") payload = {**base_payload, "max_completion_tokens": 50} - url = get_openai_url(endpoint="/v1/chat/completions") + logger.debug("Test openai completion tools", url=url) response = await client.post( url=url, headers=headers, @@ -350,7 +351,7 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str) -> No raise -async def _test_openai_embedding(api_key: str, embedding_model: str) -> None: +async def _test_openai_embedding(api_key: str, embedding_model: str, endpoint: str) -> None: """Test OpenAI embedding generation.""" try: headers = { @@ -364,7 +365,7 @@ async def _test_openai_embedding(api_key: str, embedding_model: str) -> None: } async with httpx.AsyncClient() as client: - url = get_openai_url(endpoint="/v1/embeddings") + url = f"{endpoint}/v1/embeddings" response = await client.post( url=url, headers=headers, diff --git a/src/services/models_service.py b/src/services/models_service.py index 318dfecea..61fc574f1 100644 --- a/src/services/models_service.py +++ b/src/services/models_service.py @@ -45,7 +45,7 @@ class ModelsService: def __init__(self): self.session_manager = None - async def get_openai_models(self, api_key: str) -> Dict[str, List[Dict[str, str]]]: + async def get_openai_models(self, api_key: str, api_base: str) -> Dict[str, List[Dict[str, str]]]: """Fetch available models from OpenAI API with lightweight validation""" try: headers = { @@ -56,8 +56,8 @@ async def get_openai_models(self, api_key: str) -> Dict[str, List[Dict[str, str] async with httpx.AsyncClient() as client: # Lightweight validation: just check if API key is valid # This doesn't consume credits, only validates the key - from api.provider_validation import get_openai_url - url = get_openai_url(endpoint="/v1/models") + url = f"{api_base}/v1/models" + logger.debug("Getting openai models.", url=url) response = await client.get( url, headers=headers, timeout=10.0 ) From 16a1ad4a4c17cd10d99453afaf76f007fd111974 Mon Sep 17 00:00:00 2001 From: matano Date: Thu, 29 Jan 2026 15:14:28 +0200 Subject: [PATCH 04/22] add missing field --- src/config/config_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/config_manager.py b/src/config/config_manager.py index 6af07db4d..46caad0c2 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -14,6 +14,7 @@ class OpenAIConfig: """OpenAI provider configuration.""" api_key: str = "" + endpoint: str = "" configured: bool = False @@ -223,6 +224,8 @@ def _load_env_overrides( # OpenAI provider settings if os.getenv("OPENAI_API_KEY"): config_data["providers"]["openai"]["api_key"] = os.getenv("OPENAI_API_KEY") + if os.getenv("OPENAI_API_BASE"): + config_data["providers"]["openai"]["endpoint"] = os.getenv("OPENAI_API_BASE") # Anthropic provider settings if os.getenv("ANTHROPIC_API_KEY"): From c72358e8facd4f2d20cf0dbfada842b63d19ce47 Mon Sep 17 00:00:00 2001 From: matano Date: Thu, 29 Jan 2026 15:14:41 +0200 Subject: [PATCH 05/22] fix env var name --- src/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/models.py b/src/api/models.py index 961e1d8c9..bc43d1bb2 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -50,7 +50,7 @@ async def get_openai_models(request, models_service, session_manager): except Exception as e: logger.error(f"Failed to get config: {e}") if not api_base: - api_base = os.environ.get("OPENAI_BASE_API", "https://api.openai.com") + api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") models = await models_service.get_openai_models(api_key=api_key, api_base=api_base) return JSONResponse(models) From e200f6b38e60e26d0f0c83ebb71160b83134c80a Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:05:12 +0200 Subject: [PATCH 06/22] openai providers startwith 'openai' --- src/api/provider_validation.py | 10 +++++----- src/config/config_manager.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/provider_validation.py b/src/api/provider_validation.py index 358264998..ed051ad88 100644 --- a/src/api/provider_validation.py +++ b/src/api/provider_validation.py @@ -139,8 +139,8 @@ async def validate_provider_setup( try: logger.info(f"Starting validation for provider: {provider_lower} (test_completion={test_completion})") - if provider == "openai" and not endpoint: - endpoint = os.environ.get("OPENAI_BASE_API", "https://api.openai.com") + if provider.startswith("openai") and not endpoint: + endpoint = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") if test_completion: # Full validation with completion/embedding tests (consumes credits) @@ -253,7 +253,7 @@ async def _test_openai_lightweight_health(api_key: str, endpoint: str) -> None: } url = f"{endpoint}/v1/models" - logger.debug("Testing openai lightweight health", url=url) + logger.info("Testing openai lightweight health", url=url) async with httpx.AsyncClient() as client: # Use /v1/models endpoint which validates the key without consuming credits response = await client.get( @@ -316,7 +316,7 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str, endpo # Try with max_tokens first payload = {**base_payload, "max_tokens": 50} url = f"{endpoint}/v1/chat/completions" - logger.debug("Test openai completion tools", url=url) + logger.info("Test openai completion tools", url=url) response = await client.post( url=url, headers=headers, @@ -328,7 +328,7 @@ async def _test_openai_completion_with_tools(api_key: str, llm_model: str, endpo if response.status_code != 200: logger.info("max_tokens parameter failed, trying max_completion_tokens instead") payload = {**base_payload, "max_completion_tokens": 50} - logger.debug("Test openai completion tools", url=url) + logger.info("Test openai completion tools", url=url) response = await client.post( url=url, headers=headers, diff --git a/src/config/config_manager.py b/src/config/config_manager.py index 46caad0c2..902552f07 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -52,7 +52,7 @@ class ProvidersConfig: def get_provider_config(self, provider: str): """Get configuration for a specific provider.""" provider_lower = provider.lower() - if provider_lower == "openai": + if provider_lower.startswith("openai"): return self.openai elif provider_lower == "anthropic": return self.anthropic From 830271f195bec462fbdffe57d6a052f2be555abd Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:35:04 +0200 Subject: [PATCH 07/22] revert to a single openai provider --- src/api/provider_validation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/provider_validation.py b/src/api/provider_validation.py index ed051ad88..458190193 100644 --- a/src/api/provider_validation.py +++ b/src/api/provider_validation.py @@ -139,7 +139,7 @@ async def validate_provider_setup( try: logger.info(f"Starting validation for provider: {provider_lower} (test_completion={test_completion})") - if provider.startswith("openai") and not endpoint: + if provider == "openai" and not endpoint: endpoint = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") if test_completion: @@ -187,7 +187,7 @@ async def _test_lightweight_health( ) -> None: """Test provider health with lightweight check (no credits consumed).""" - if provider.startswith("openai"): + if provider == "openai": await _test_openai_lightweight_health(api_key, endpoint) elif provider == "watsonx": await _test_watsonx_lightweight_health(api_key, endpoint, project_id) @@ -208,7 +208,7 @@ async def _test_completion_with_tools( ) -> None: """Test completion with tool calling for the provider.""" - if provider.startswith("openai"): + if provider == "openai": await _test_openai_completion_with_tools(api_key, llm_model, endpoint) elif provider == "watsonx": await _test_watsonx_completion_with_tools(api_key, llm_model, endpoint, project_id) @@ -229,7 +229,7 @@ async def _test_embedding( ) -> None: """Test embedding generation for the provider.""" - if provider.startswith("openai"): + if provider == "openai": await _test_openai_embedding(api_key, embedding_model, endpoint) elif provider == "watsonx": await _test_watsonx_embedding(api_key, embedding_model, endpoint, project_id) From fe5562220062753279bdc4f2054332fe10f7a709 Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:36:00 +0200 Subject: [PATCH 08/22] revert to a single openai provider --- src/api/provider_health.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/provider_health.py b/src/api/provider_health.py index 36238e279..3802d8dcb 100644 --- a/src/api/provider_health.py +++ b/src/api/provider_health.py @@ -42,7 +42,7 @@ async def check_provider_health(request): provider = current_config.agent.llm_provider # Validate provider name - valid_providers = ["openai_ete", "openai", "ollama", "watsonx", "anthropic"] + valid_providers = ["openai", "ollama", "watsonx", "anthropic"] if provider not in valid_providers: return JSONResponse( { From 7f6ebe6992a7027b0bddbb967afd40d1a73c1874 Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:39:17 +0200 Subject: [PATCH 09/22] revert to a single openai provider --- src/config/config_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config_manager.py b/src/config/config_manager.py index 902552f07..46caad0c2 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -52,7 +52,7 @@ class ProvidersConfig: def get_provider_config(self, provider: str): """Get configuration for a specific provider.""" provider_lower = provider.lower() - if provider_lower.startswith("openai"): + if provider_lower == "openai": return self.openai elif provider_lower == "anthropic": return self.anthropic From 787e5da6c15eadb66c1d8fb0364c59bd5277c1ed Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:42:44 +0200 Subject: [PATCH 10/22] fix LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f28e682a6..d5466c8ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,7 +139,7 @@ services: - MIMETYPE=None - FILESIZE=0 - SELECTED_EMBEDDING_MODEL=${SELECTED_EMBEDDING_MODEL:-} - - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,OPENAI_API_BASE,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL>>>>>>> remotes/upstream/main + - LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=JWT,OPENRAG-QUERY-FILTER,OPENSEARCH_PASSWORD,OPENSEARCH_URL,DOCLING_SERVE_URL,OWNER,OWNER_NAME,OWNER_EMAIL,CONNECTOR_TYPE,FILENAME,MIMETYPE,FILESIZE,SELECTED_EMBEDDING_MODEL,OPENAI_API_KEY,OPENAI_API_BASE,ANTHROPIC_API_KEY,WATSONX_API_KEY,WATSONX_ENDPOINT,WATSONX_PROJECT_ID,OLLAMA_BASE_URL - LANGFLOW_LOG_LEVEL=DEBUG - LANGFLOW_AUTO_LOGIN=${LANGFLOW_AUTO_LOGIN} - LANGFLOW_SUPERUSER=${LANGFLOW_SUPERUSER} From 371029f88e14ef161902b5d9f100a1e33111e596 Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 1 Feb 2026 10:47:55 +0200 Subject: [PATCH 11/22] throw if api_base is not in request or in configuration (as done for the api_key) --- src/api/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/models.py b/src/api/models.py index bc43d1bb2..8d4ceb281 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -50,7 +50,12 @@ async def get_openai_models(request, models_service, session_manager): except Exception as e: logger.error(f"Failed to get config: {e}") if not api_base: - api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") + return JSONResponse( + { + "error": "OpenAI API base is required either in request body or in configuration" + }, + status_code=400, + ) models = await models_service.get_openai_models(api_key=api_key, api_base=api_base) return JSONResponse(models) From 4241f31f2c9d449e815e41be0e15134a4dbbfe8d Mon Sep 17 00:00:00 2001 From: matano Date: Sun, 8 Feb 2026 17:35:52 +0200 Subject: [PATCH 12/22] add OPENAI_API_BASE to add_provider_credentials_to_headers() and build_mcp_global_vars_from_config() --- src/utils/langflow_headers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/langflow_headers.py b/src/utils/langflow_headers.py index e3447e611..bfc5f1271 100644 --- a/src/utils/langflow_headers.py +++ b/src/utils/langflow_headers.py @@ -14,7 +14,10 @@ def add_provider_credentials_to_headers(headers: Dict[str, str], config) -> None # Add OpenAI credentials if config.providers.openai.api_key: headers["X-LANGFLOW-GLOBAL-VAR-OPENAI_API_KEY"] = str(config.providers.openai.api_key) - + + if config.providers.openai.endpoint: + headers["X-LANGFLOW-GLOBAL-VAR-OPENAI_API_BASE"] = str(config.providers.openai.endpoint) + # Add Anthropic credentials if config.providers.anthropic.api_key: headers["X-LANGFLOW-GLOBAL-VAR-ANTHROPIC_API_KEY"] = str(config.providers.anthropic.api_key) @@ -47,6 +50,9 @@ def build_mcp_global_vars_from_config(config) -> Dict[str, str]: if config.providers.openai.api_key: global_vars["OPENAI_API_KEY"] = config.providers.openai.api_key + if config.providers.openai.endpoint: + global_vars["OPENAI_API_BASE"] = config.providers.openai.endpoint + # Add Anthropic credentials if config.providers.anthropic.api_key: global_vars["ANTHROPIC_API_KEY"] = config.providers.anthropic.api_key From 6d2e809f2d17cc0d4f03336a1e0a326e1648a61c Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Feb 2026 12:51:22 +0200 Subject: [PATCH 13/22] add OPENAI_API_BASE as default for the api_base field of the OPENAI embedder --- flows/ingestion_flow.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flows/ingestion_flow.json b/flows/ingestion_flow.json index 6f65c04e0..78a4ddad9 100644 --- a/flows/ingestion_flow.json +++ b/flows/ingestion_flow.json @@ -5317,7 +5317,7 @@ "_type": "Component", "api_base": { "_input_type": "MessageTextInput", - "advanced": true, + "advanced": false, "display_name": "OpenAI API Base URL", "dynamic": false, "info": "Base URL for the API. Leave empty for default.", @@ -5326,7 +5326,7 @@ ], "list": false, "list_add_label": "Add More", - "load_from_db": false, + "load_from_db": true, "name": "api_base", "override_skip": false, "placeholder": "", @@ -5338,7 +5338,7 @@ "trace_as_metadata": true, "track_in_telemetry": false, "type": "str", - "value": "" + "value": "OPENAI_API_BASE" }, "api_key": { "_input_type": "SecretStrInput", From 73ab1b3a735c78eb375024fbfa4fdce9b1bb29ff Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Feb 2026 14:07:50 +0200 Subject: [PATCH 14/22] add OPENAI_API_BASE to embedder and agent --- flows/openrag_agent.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flows/openrag_agent.json b/flows/openrag_agent.json index f04405ebe..d8541c598 100644 --- a/flows/openrag_agent.json +++ b/flows/openrag_agent.json @@ -1808,14 +1808,14 @@ }, "openai_api_base": { "_input_type": "StrInput", - "advanced": true, + "advanced": false, "display_name": "OpenAI API Base", "dynamic": false, "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", "input_types": [], "list": false, "list_add_label": "Add More", - "load_from_db": false, + "load_from_db": true, "name": "openai_api_base", "override_skip": false, "placeholder": "", @@ -1826,7 +1826,7 @@ "trace_as_metadata": true, "track_in_telemetry": false, "type": "str", - "value": "" + "value": "OPENAI_API_BASE" }, "output_schema": { "_input_type": "TableInput", @@ -2344,7 +2344,7 @@ ], "list": false, "list_add_label": "Add More", - "load_from_db": false, + "load_from_db": true, "name": "api_base", "override_skip": false, "placeholder": "", @@ -2356,7 +2356,7 @@ "trace_as_metadata": true, "track_in_telemetry": false, "type": "str", - "value": "" + "value": "OPENAI_API_BASE" }, "api_key": { "_input_type": "SecretStrInput", From 6624c4c2440854523ad05c2576535d11801d4467 Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Feb 2026 14:09:18 +0200 Subject: [PATCH 15/22] when updating an openai api_base, use the OPENAI_API_BASE --- src/services/flows_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/flows_service.py b/src/services/flows_service.py index e97ac2d3a..2d147353d 100644 --- a/src/services/flows_service.py +++ b/src/services/flows_service.py @@ -1393,8 +1393,8 @@ async def _update_component_fields( template["api_key"]["advanced"] = False updated = True if provider == "openai" and "api_base" in template: - template["api_base"]["value"] = "" - template["api_base"]["load_from_db"] = False + template["api_base"]["value"] = "OPENAI_API_BASE" + template["api_base"]["load_from_db"] = True template["api_base"]["show"] = True template["api_base"]["advanced"] = False updated = True From 9fbb7cc206e1b811f9c8055fd8da43578e9a72ae Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Feb 2026 14:15:17 +0200 Subject: [PATCH 16/22] add claude-opus to OpenAI allowed LLMs --- src/services/models_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/models_service.py b/src/services/models_service.py index 61fc574f1..d99e91e33 100644 --- a/src/services/models_service.py +++ b/src/services/models_service.py @@ -30,6 +30,7 @@ class ModelsService: "o3-pro", "o4-mini", "o4-mini-high", + "claude-opus-4-5-20251101", ] ANTHROPIC_MODELS = [ From 4a1a39af4ec319856cba7d5ce5d583a7f39af12d Mon Sep 17 00:00:00 2001 From: matano Date: Tue, 10 Feb 2026 10:31:39 +0200 Subject: [PATCH 17/22] update openai_api_base when updating the template --- src/services/flows_service.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/flows_service.py b/src/services/flows_service.py index 2d147353d..9a0614b69 100644 --- a/src/services/flows_service.py +++ b/src/services/flows_service.py @@ -1398,6 +1398,12 @@ async def _update_component_fields( template["api_base"]["show"] = True template["api_base"]["advanced"] = False updated = True + if provider == "openai" and "openai_api_base" in template: + template["openai_api_base"]["value"] = "OPENAI_API_BASE" + template["openai_api_base"]["load_from_db"] = True + template["openai_api_base"]["show"] = True + template["openai_api_base"]["advanced"] = False + updated = True if provider == "anthropic" and "api_key" in template: template["api_key"]["value"] = "ANTHROPIC_API_KEY" From a1beeb210ce8611a912d781025029cee5a4cef56 Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 23 Feb 2026 10:58:49 +0200 Subject: [PATCH 18/22] fix for redundant /v1 extension --- src/api/provider_validation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/provider_validation.py b/src/api/provider_validation.py index 458190193..c2eb1794c 100644 --- a/src/api/provider_validation.py +++ b/src/api/provider_validation.py @@ -141,6 +141,11 @@ async def validate_provider_setup( if provider == "openai" and not endpoint: endpoint = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") + + # Strip /v1 suffix from OpenAI endpoint if present to avoid double /v1 paths + if provider == "openai" and endpoint and endpoint.endswith("/v1"): + endpoint = endpoint.rstrip("/v1") + logger.info(f"Stripped /v1 suffix from OpenAI endpoint: {endpoint}") if test_completion: # Full validation with completion/embedding tests (consumes credits) From b49315f5dbb91c546f37819306c6439e595ea6b6 Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Mar 2026 14:46:21 +0200 Subject: [PATCH 19/22] update OPEN_API_BASE env variable when the endpoint is loaded from the config --- src/config/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/settings.py b/src/config/settings.py index 78cf03a65..e82db0d20 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -415,6 +415,10 @@ def patched_async_client(self): if config.providers.openai.api_key: os.environ["OPENAI_API_KEY"] = config.providers.openai.api_key logger.debug("Loaded OpenAI API key from config") + + if config.providers.openai.endpoint: + os.environ["OPENAI_API_BASE"] = config.providers.openai.endpoint + logger.debug(f"Loaded OpenAI endpoint from config: '{config.providers.openai.endpoint}'") # Set Anthropic credentials if config.providers.anthropic.api_key: From 8fc4ce3c8dbc73508dcdea641a3ebe9d184c157e Mon Sep 17 00:00:00 2001 From: matano Date: Mon, 9 Mar 2026 15:16:02 +0200 Subject: [PATCH 20/22] update api_base setup in get_openai_models --- src/api/models.py | 28 ++++++++++++---------------- src/services/models_service.py | 2 -- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/api/models.py b/src/api/models.py index ce6865796..be3a084b4 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,3 +1,4 @@ +import os from typing import Optional from fastapi import Depends @@ -45,23 +46,18 @@ async def get_openai_models( {"error": "OpenAI API key is required either in request body or in configuration"}, status_code=400, ) - - if not api_base: - try: - config = get_openrag_config() - api_base = config.providers.openai.endpoint - logger.info( - f"Retrieved OpenAI API base from config: {'yes' if api_base else 'no'}" - ) - except Exception as e: - logger.error(f"Failed to get config: {e}") - if not api_base: - return JSONResponse( - { - "error": "OpenAI API base is required either in request body or in configuration" - }, - status_code=400, + + api_base = None + try: + config = get_openrag_config() + api_base = config.providers.openai.endpoint + logger.info( + f"Retrieved OpenAI API base from config: {'yes' if api_base else 'no'}" ) + except Exception as e: + logger.error(f"Failed to get config: {e}") + if not api_base: + api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com") models = await models_service.get_openai_models(api_key=api_key, api_base=api_base) return JSONResponse(models) diff --git a/src/services/models_service.py b/src/services/models_service.py index 218275eb2..95271ae0b 100644 --- a/src/services/models_service.py +++ b/src/services/models_service.py @@ -1,5 +1,3 @@ -import os - import httpx from typing import Dict, List from config.model_constants import ( From 0a16084cdf46967f4ae21b2aea9e8abaadb463b3 Mon Sep 17 00:00:00 2001 From: matano Date: Wed, 11 Mar 2026 14:49:03 +0200 Subject: [PATCH 21/22] remove default of None --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index fd62e89bb..92193b132 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,7 +68,7 @@ services: - OPENSEARCH_USERNAME=${OPENSEARCH_USERNAME:-admin} - OPENSEARCH_PASSWORD=${OPENSEARCH_PASSWORD} - OPENAI_API_KEY=${OPENAI_API_KEY} - - OPENAI_API_BASE=${OPENAI_API_BASE:-None} + - OPENAI_API_BASE=${OPENAI_API_BASE} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - WATSONX_API_KEY=${WATSONX_API_KEY} - WATSONX_ENDPOINT=${WATSONX_ENDPOINT} From d3a454308aff3562b8de958d5ee74e47e58de099 Mon Sep 17 00:00:00 2001 From: matano Date: Thu, 12 Mar 2026 17:31:31 +0200 Subject: [PATCH 22/22] revert flow changes back to main --- flows/ingestion_flow.json | 8 ++++---- flows/openrag_agent.json | 26 ++------------------------ 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/flows/ingestion_flow.json b/flows/ingestion_flow.json index 4555f40a3..6cfcb289f 100644 --- a/flows/ingestion_flow.json +++ b/flows/ingestion_flow.json @@ -5564,8 +5564,8 @@ "_type": "Component", "api_base": { "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "OpenAI API Base URL", + "advanced": true, + "display_name": "API Base URL", "dynamic": false, "info": "Base URL for the API. Leave empty for default.", "input_types": [ @@ -5573,7 +5573,7 @@ ], "list": false, "list_add_label": "Add More", - "load_from_db": true, + "load_from_db": false, "name": "api_base", "override_skip": false, "placeholder": "", @@ -5585,7 +5585,7 @@ "trace_as_metadata": true, "track_in_telemetry": false, "type": "str", - "value": "OPENAI_API_BASE" + "value": "" }, "api_key": { "_input_type": "SecretStrInput", diff --git a/flows/openrag_agent.json b/flows/openrag_agent.json index 1e6309032..2c81cd51e 100644 --- a/flows/openrag_agent.json +++ b/flows/openrag_agent.json @@ -1806,28 +1806,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "input_types": [], - "list": false, - "list_add_label": "Add More", - "load_from_db": true, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "OPENAI_API_BASE" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2344,7 +2322,7 @@ ], "list": false, "list_add_label": "Add More", - "load_from_db": true, + "load_from_db": false, "name": "api_base", "override_skip": false, "placeholder": "", @@ -2356,7 +2334,7 @@ "trace_as_metadata": true, "track_in_telemetry": false, "type": "str", - "value": "OPENAI_API_BASE" + "value": "" }, "api_key": { "_input_type": "SecretStrInput",