Add support for Anthropic, Ollama, and Watsonx config

Introduces fields and validation for Anthropic API key, Ollama endpoint, and IBM watsonx.ai API key, endpoint, and project ID in environment management and configuration screens. Updates validation utilities and config UI to support these providers, allowing users to set and validate credentials and endpoints for additional AI services.
This commit is contained in:
Edwin Jose 2025-11-21 14:00:55 -05:00
parent e23a7d0f59
commit 933e600e9d
3 changed files with 237 additions and 4 deletions

View file

@ -35,6 +35,13 @@ class EnvConfig:
langflow_ingest_flow_id: str = "5488df7c-b93f-4f87-a446-b67028bc0813"
langflow_url_ingest_flow_id: str = "72c3d17c-2dac-4a73-b48a-6518473d7830"
# Provider API keys and endpoints
anthropic_api_key: str = ""
ollama_endpoint: str = ""
watsonx_api_key: str = ""
watsonx_endpoint: str = ""
watsonx_project_id: str = ""
# OAuth settings
google_oauth_client_id: str = ""
google_oauth_client_secret: str = ""
@ -128,6 +135,11 @@ class EnvManager:
# Map env vars to config attributes
attr_map = {
"OPENAI_API_KEY": "openai_api_key",
"ANTHROPIC_API_KEY": "anthropic_api_key",
"OLLAMA_ENDPOINT": "ollama_endpoint",
"WATSONX_API_KEY": "watsonx_api_key",
"WATSONX_ENDPOINT": "watsonx_endpoint",
"WATSONX_PROJECT_ID": "watsonx_project_id",
"OPENSEARCH_PASSWORD": "opensearch_password",
"LANGFLOW_SECRET_KEY": "langflow_secret_key",
"LANGFLOW_SUPERUSER": "langflow_superuser",
@ -197,6 +209,30 @@ class EnvManager:
"Invalid OpenAI API key format (should start with sk-)"
)
# Import validation functions for new provider fields
from ..utils.validation import validate_anthropic_api_key
# Validate Anthropic API key format if provided
if self.config.anthropic_api_key:
if not validate_anthropic_api_key(self.config.anthropic_api_key):
self.config.validation_errors["anthropic_api_key"] = (
"Invalid Anthropic API key format (should start with sk-ant-)"
)
# Validate Ollama endpoint if provided
if self.config.ollama_endpoint:
if not validate_url(self.config.ollama_endpoint):
self.config.validation_errors["ollama_endpoint"] = (
"Invalid Ollama endpoint URL format"
)
# Validate IBM watsonx.ai endpoint if provided
if self.config.watsonx_endpoint:
if not validate_url(self.config.watsonx_endpoint):
self.config.validation_errors["watsonx_endpoint"] = (
"Invalid IBM watsonx.ai endpoint URL format"
)
# Validate documents paths only if provided (optional)
if self.config.openrag_documents_paths:
is_valid, error_msg, _ = validate_documents_paths(
@ -289,9 +325,6 @@ class EnvManager:
f.write(f"LANGFLOW_URL_INGEST_FLOW_ID={self._quote_env_value(self.config.langflow_url_ingest_flow_id)}\n")
f.write(f"NUDGES_FLOW_ID={self._quote_env_value(self.config.nudges_flow_id)}\n")
f.write(f"OPENSEARCH_PASSWORD={self._quote_env_value(self.config.opensearch_password)}\n")
# Only write OpenAI API key if provided (can be set during onboarding instead)
if self.config.openai_api_key:
f.write(f"OPENAI_API_KEY={self._quote_env_value(self.config.openai_api_key)}\n")
f.write(
f"OPENRAG_DOCUMENTS_PATHS={self._quote_env_value(self.config.openrag_documents_paths)}\n"
)
@ -300,6 +333,27 @@ class EnvManager:
)
f.write("\n")
# Provider API keys and endpoints (optional - can be set during onboarding)
provider_vars = []
if self.config.openai_api_key:
provider_vars.append(("OPENAI_API_KEY", self.config.openai_api_key))
if self.config.anthropic_api_key:
provider_vars.append(("ANTHROPIC_API_KEY", self.config.anthropic_api_key))
if self.config.ollama_endpoint:
provider_vars.append(("OLLAMA_ENDPOINT", self.config.ollama_endpoint))
if self.config.watsonx_api_key:
provider_vars.append(("WATSONX_API_KEY", self.config.watsonx_api_key))
if self.config.watsonx_endpoint:
provider_vars.append(("WATSONX_ENDPOINT", self.config.watsonx_endpoint))
if self.config.watsonx_project_id:
provider_vars.append(("WATSONX_PROJECT_ID", self.config.watsonx_project_id))
if provider_vars:
f.write("# AI Provider API Keys and Endpoints\n")
for var_name, var_value in provider_vars:
f.write(f"{var_name}={self._quote_env_value(var_value)}\n")
f.write("\n")
# Ingestion settings
f.write("# Ingestion settings\n")
f.write(f"DISABLE_INGEST_WITH_LANGFLOW={self._quote_env_value(self.config.disable_ingest_with_langflow)}\n")

View file

@ -20,7 +20,13 @@ from rich.text import Text
from pathlib import Path
from ..managers.env_manager import EnvManager
from ..utils.validation import validate_openai_api_key, validate_documents_paths
from ..utils.validation import (
validate_openai_api_key,
validate_anthropic_api_key,
validate_ollama_endpoint,
validate_watsonx_endpoint,
validate_documents_paths,
)
from pathlib import Path
@ -37,6 +43,45 @@ class OpenAIKeyValidator(Validator):
return self.failure("Invalid OpenAI API key format (should start with sk-)")
class AnthropicKeyValidator(Validator):
"""Validator for Anthropic API keys."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_anthropic_api_key(value):
return self.success()
else:
return self.failure("Invalid Anthropic API key format (should start with sk-ant-)")
class OllamaEndpointValidator(Validator):
"""Validator for Ollama endpoint URLs."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_ollama_endpoint(value):
return self.success()
else:
return self.failure("Invalid Ollama endpoint URL format")
class WatsonxEndpointValidator(Validator):
"""Validator for IBM watsonx.ai endpoint URLs."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_watsonx_endpoint(value):
return self.success()
else:
return self.failure("Invalid watsonx.ai endpoint URL format")
class DocumentsPathValidator(Validator):
"""Validator for documents paths."""
@ -227,6 +272,107 @@ class ConfigScreen(Screen):
yield Button("Show", id="toggle-openai-key", variant="default")
yield Static(" ")
# Anthropic API Key
yield Label("Anthropic API Key")
yield Static(
Text("Get a key: https://console.anthropic.com/settings/keys", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can also be provided during onboarding", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "anthropic_api_key", "")
with Horizontal(id="anthropic-key-row"):
input_widget = Input(
placeholder="sk-ant-...",
value=current_value,
password=True,
validators=[AnthropicKeyValidator()],
id="input-anthropic_api_key",
)
yield input_widget
self.inputs["anthropic_api_key"] = input_widget
yield Button("Show", id="toggle-anthropic-key", variant="default")
yield Static(" ")
# Ollama Endpoint
yield Label("Ollama Base URL")
yield Static(
Text("Endpoint of your Ollama server", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can also be provided during onboarding", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "ollama_endpoint", "")
input_widget = Input(
placeholder="http://localhost:11434",
value=current_value,
validators=[OllamaEndpointValidator()],
id="input-ollama_endpoint",
)
yield input_widget
self.inputs["ollama_endpoint"] = input_widget
yield Static(" ")
# IBM watsonx.ai API Key
yield Label("IBM watsonx.ai API Key")
yield Static(
Text("Get a key: https://cloud.ibm.com/iam/apikeys", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can also be provided during onboarding", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_api_key", "")
with Horizontal(id="watsonx-key-row"):
input_widget = Input(
placeholder="",
value=current_value,
password=True,
id="input-watsonx_api_key",
)
yield input_widget
self.inputs["watsonx_api_key"] = input_widget
yield Button("Show", id="toggle-watsonx-key", variant="default")
yield Static(" ")
# IBM watsonx.ai Endpoint
yield Label("IBM watsonx.ai Endpoint")
yield Static(
Text("Example: https://us-south.ml.cloud.ibm.com", style="dim"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_endpoint", "")
input_widget = Input(
placeholder="https://us-south.ml.cloud.ibm.com",
value=current_value,
validators=[WatsonxEndpointValidator()],
id="input-watsonx_endpoint",
)
yield input_widget
self.inputs["watsonx_endpoint"] = input_widget
yield Static(" ")
# IBM watsonx.ai Project ID
yield Label("IBM watsonx.ai Project ID")
yield Static(
Text("Find in your IBM Cloud project settings", style="dim"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_project_id", "")
input_widget = Input(
placeholder="",
value=current_value,
id="input-watsonx_project_id",
)
yield input_widget
self.inputs["watsonx_project_id"] = input_widget
yield Static(" ")
# Add OAuth fields only in full mode
if self.mode == "full":
# Google OAuth Client ID
@ -550,6 +696,18 @@ class ConfigScreen(Screen):
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "🙈" if not input_widget.password else "👁"
elif event.button.id == "toggle-anthropic-key":
# Toggle Anthropic API key visibility
input_widget = self.inputs.get("anthropic_api_key")
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "Hide" if not input_widget.password else "Show"
elif event.button.id == "toggle-watsonx-key":
# Toggle watsonx API key visibility
input_widget = self.inputs.get("watsonx_api_key")
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "Hide" if not input_widget.password else "Show"
def action_generate(self) -> None:
"""Generate secure passwords for admin accounts."""

View file

@ -63,6 +63,27 @@ def validate_openai_api_key(key: str) -> bool:
return key.startswith("sk-") and len(key) > 20
def validate_anthropic_api_key(key: str) -> bool:
"""Validate Anthropic API key format."""
if not key:
return False
return key.startswith("sk-ant-") and len(key) > 20
def validate_ollama_endpoint(endpoint: str) -> bool:
"""Validate Ollama endpoint URL format."""
if not endpoint:
return False
return validate_url(endpoint)
def validate_watsonx_endpoint(endpoint: str) -> bool:
"""Validate IBM watsonx.ai endpoint URL format."""
if not endpoint:
return False
return validate_url(endpoint)
def validate_google_oauth_client_id(client_id: str) -> bool:
"""Validate Google OAuth client ID format."""
if not client_id: