Compare commits
10 commits
main
...
container-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc779ad404 | ||
|
|
9fd77debe4 | ||
|
|
f7c2561760 | ||
|
|
d018a96923 | ||
|
|
d6b100459f | ||
|
|
622eb422b2 | ||
|
|
4f3bad9a9d | ||
|
|
66134703e7 | ||
|
|
67e0a53363 | ||
|
|
adbd94b176 |
8 changed files with 152 additions and 8 deletions
|
|
@ -35,6 +35,7 @@ export interface Settings {
|
||||||
separator?: string;
|
separator?: string;
|
||||||
embeddingModel?: string;
|
embeddingModel?: string;
|
||||||
};
|
};
|
||||||
|
localhost_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGetSettingsQuery = (
|
export const useGetSettingsQuery = (
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export function OllamaOnboarding({
|
||||||
sampleDataset: boolean;
|
sampleDataset: boolean;
|
||||||
setSampleDataset: (dataset: boolean) => void;
|
setSampleDataset: (dataset: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const [endpoint, setEndpoint] = useState("http://localhost:11434");
|
const [endpoint, setEndpoint] = useState(`http://localhost:11434`);
|
||||||
const [showConnecting, setShowConnecting] = useState(false);
|
const [showConnecting, setShowConnecting] = useState(false);
|
||||||
const debouncedEndpoint = useDebouncedValue(endpoint, 500);
|
const debouncedEndpoint = useDebouncedValue(endpoint, 500);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import platform
|
import platform
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
|
from utils.container_utils import transform_localhost_url
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
from config.settings import (
|
from config.settings import (
|
||||||
LANGFLOW_URL,
|
LANGFLOW_URL,
|
||||||
|
|
@ -8,6 +9,7 @@ from config.settings import (
|
||||||
LANGFLOW_INGEST_FLOW_ID,
|
LANGFLOW_INGEST_FLOW_ID,
|
||||||
LANGFLOW_PUBLIC_URL,
|
LANGFLOW_PUBLIC_URL,
|
||||||
DOCLING_COMPONENT_ID,
|
DOCLING_COMPONENT_ID,
|
||||||
|
LOCALHOST_URL,
|
||||||
clients,
|
clients,
|
||||||
get_openrag_config,
|
get_openrag_config,
|
||||||
config_manager,
|
config_manager,
|
||||||
|
|
@ -77,6 +79,7 @@ async def get_settings(request, session_manager):
|
||||||
"llm_model": agent_config.llm_model,
|
"llm_model": agent_config.llm_model,
|
||||||
"system_prompt": agent_config.system_prompt,
|
"system_prompt": agent_config.system_prompt,
|
||||||
},
|
},
|
||||||
|
"localhost_url": LOCALHOST_URL,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only expose edit URLs when a public URL is configured
|
# Only expose edit URLs when a public URL is configured
|
||||||
|
|
@ -535,7 +538,8 @@ async def onboarding(request, flows_service):
|
||||||
|
|
||||||
# Set base URL for Ollama provider
|
# Set base URL for Ollama provider
|
||||||
if provider == "ollama" and "endpoint" in body:
|
if provider == "ollama" and "endpoint" in body:
|
||||||
endpoint = body["endpoint"]
|
endpoint = transform_localhost_url(body["endpoint"])
|
||||||
|
|
||||||
await clients._create_langflow_global_variable(
|
await clients._create_langflow_global_variable(
|
||||||
"OLLAMA_BASE_URL", endpoint, modify=True
|
"OLLAMA_BASE_URL", endpoint, modify=True
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from openai import AsyncOpenAI
|
||||||
from opensearchpy import AsyncOpenSearch
|
from opensearchpy import AsyncOpenSearch
|
||||||
from opensearchpy._async.http_aiohttp import AIOHttpConnection
|
from opensearchpy._async.http_aiohttp import AIOHttpConnection
|
||||||
|
|
||||||
|
from utils.container_utils import get_container_host
|
||||||
from utils.document_processing import create_document_converter
|
from utils.document_processing import create_document_converter
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
|
|
@ -575,6 +576,8 @@ OLLAMA_LLM_TEXT_COMPONENT_ID = os.getenv(
|
||||||
# Docling component ID for ingest flow
|
# Docling component ID for ingest flow
|
||||||
DOCLING_COMPONENT_ID = os.getenv("DOCLING_COMPONENT_ID", "DoclingRemote-78KoX")
|
DOCLING_COMPONENT_ID = os.getenv("DOCLING_COMPONENT_ID", "DoclingRemote-78KoX")
|
||||||
|
|
||||||
|
LOCALHOST_URL = get_container_host() or "localhost"
|
||||||
|
|
||||||
# Global clients instance
|
# Global clients instance
|
||||||
clients = AppClients()
|
clients = AppClients()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -836,10 +836,5 @@ class FlowsService:
|
||||||
template["url"]["value"] = endpoint
|
template["url"]["value"] = endpoint
|
||||||
template["url"]["options"] = [endpoint]
|
template["url"]["options"] = [endpoint]
|
||||||
updated = True
|
updated = True
|
||||||
elif provider == "ollama" and "base_url" in template:
|
|
||||||
# Ollama uses "base_url" field
|
|
||||||
template["base_url"]["value"] = endpoint
|
|
||||||
# Note: base_url is typically a MessageTextInput, not dropdown, so no options field
|
|
||||||
updated = True
|
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import httpx
|
import httpx
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
from utils.container_utils import transform_localhost_url
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
@ -95,7 +96,7 @@ class ModelsService:
|
||||||
"""Fetch available models from Ollama API with tool calling capabilities for language models"""
|
"""Fetch available models from Ollama API with tool calling capabilities for language models"""
|
||||||
try:
|
try:
|
||||||
# Use provided endpoint or default
|
# Use provided endpoint or default
|
||||||
ollama_url = endpoint
|
ollama_url = transform_localhost_url(endpoint)
|
||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
tags_url = f"{ollama_url}/api/tags"
|
tags_url = f"{ollama_url}/api/tags"
|
||||||
|
|
|
||||||
138
src/utils/container_utils.py
Normal file
138
src/utils/container_utils.py
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
"""Utilities for detecting and working with container environments."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def detect_container_environment() -> str | None:
|
||||||
|
"""Detect if running in a container and return the appropriate container type.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
'docker' if running in Docker, 'podman' if running in Podman, None otherwise.
|
||||||
|
"""
|
||||||
|
# Check for .dockerenv file (Docker)
|
||||||
|
if Path("/.dockerenv").exists():
|
||||||
|
return "docker"
|
||||||
|
|
||||||
|
# Check cgroup for container indicators
|
||||||
|
try:
|
||||||
|
with Path("/proc/self/cgroup").open() as f:
|
||||||
|
content = f.read()
|
||||||
|
if "docker" in content:
|
||||||
|
return "docker"
|
||||||
|
if "podman" in content:
|
||||||
|
return "podman"
|
||||||
|
except (FileNotFoundError, PermissionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check environment variables (lowercase 'container' is the standard for Podman)
|
||||||
|
if os.getenv("container") == "podman": # noqa: SIM112
|
||||||
|
return "podman"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_container_host() -> str | None:
|
||||||
|
"""Get the hostname to access host services from within a container.
|
||||||
|
|
||||||
|
Tries multiple methods to find the correct hostname:
|
||||||
|
1. host.containers.internal (Podman) or host.docker.internal (Docker)
|
||||||
|
2. Gateway IP from routing table (fallback for Linux)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hostname or IP to use, or None if not in a container.
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# Check if we're in a container first
|
||||||
|
container_type = detect_container_environment()
|
||||||
|
if not container_type:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Try container-specific hostnames first based on detected type
|
||||||
|
if container_type == "podman":
|
||||||
|
# Podman: try host.containers.internal first
|
||||||
|
try:
|
||||||
|
socket.getaddrinfo("host.containers.internal", None)
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return "host.containers.internal"
|
||||||
|
|
||||||
|
# Fallback to host.docker.internal (for Podman Desktop on macOS)
|
||||||
|
try:
|
||||||
|
socket.getaddrinfo("host.docker.internal", None)
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return "host.docker.internal"
|
||||||
|
else:
|
||||||
|
# Docker: try host.docker.internal first
|
||||||
|
try:
|
||||||
|
socket.getaddrinfo("host.docker.internal", None)
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return "host.docker.internal"
|
||||||
|
|
||||||
|
# Fallback to host.containers.internal (unlikely but possible)
|
||||||
|
try:
|
||||||
|
socket.getaddrinfo("host.containers.internal", None)
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return "host.containers.internal"
|
||||||
|
|
||||||
|
# Fallback: try to get gateway IP from routing table (Linux containers)
|
||||||
|
try:
|
||||||
|
with Path("/proc/net/route").open() as f:
|
||||||
|
for line in f:
|
||||||
|
fields = line.strip().split()
|
||||||
|
min_field_count = 3 # Minimum fields needed: interface, destination, gateway
|
||||||
|
if len(fields) >= min_field_count and fields[1] == "00000000": # Default route
|
||||||
|
# Gateway is in hex format (little-endian)
|
||||||
|
gateway_hex = fields[2]
|
||||||
|
# Convert hex to IP address
|
||||||
|
# The hex is in little-endian format, so we read it backwards in pairs
|
||||||
|
octets = [gateway_hex[i : i + 2] for i in range(0, 8, 2)]
|
||||||
|
return ".".join(str(int(octet, 16)) for octet in reversed(octets))
|
||||||
|
except (FileNotFoundError, PermissionError, IndexError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def transform_localhost_url(url: str) -> str:
|
||||||
|
"""Transform localhost URLs to container-accessible hosts when running in a container.
|
||||||
|
|
||||||
|
Automatically detects if running inside a container and finds the appropriate host
|
||||||
|
address to replace localhost/127.0.0.1. Tries in order:
|
||||||
|
- host.docker.internal (if resolvable)
|
||||||
|
- host.containers.internal (if resolvable)
|
||||||
|
- Gateway IP from routing table (fallback)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The original URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Transformed URL with container-accessible host if applicable, otherwise the original URL.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> transform_localhost_url("http://localhost:5001")
|
||||||
|
# Returns "http://host.docker.internal:5001" if running in Docker and hostname resolves
|
||||||
|
# Returns "http://172.17.0.1:5001" if running in Docker on Linux (gateway IP fallback)
|
||||||
|
# Returns "http://localhost:5001" if not in a container
|
||||||
|
"""
|
||||||
|
container_host = get_container_host()
|
||||||
|
|
||||||
|
if not container_host:
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Replace localhost and 127.0.0.1 with the container host
|
||||||
|
localhost_patterns = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
|
for pattern in localhost_patterns:
|
||||||
|
if pattern in url:
|
||||||
|
return url.replace(pattern, container_host)
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
@ -10,6 +10,8 @@ def get_embedding_dimensions(model_name: str) -> int:
|
||||||
# Check all model dictionaries
|
# Check all model dictionaries
|
||||||
all_models = {**OPENAI_EMBEDDING_DIMENSIONS, **OLLAMA_EMBEDDING_DIMENSIONS, **WATSONX_EMBEDDING_DIMENSIONS}
|
all_models = {**OPENAI_EMBEDDING_DIMENSIONS, **OLLAMA_EMBEDDING_DIMENSIONS, **WATSONX_EMBEDDING_DIMENSIONS}
|
||||||
|
|
||||||
|
model_name = model_name.lower().strip().split(":")[0]
|
||||||
|
|
||||||
if model_name in all_models:
|
if model_name in all_models:
|
||||||
dimensions = all_models[model_name]
|
dimensions = all_models[model_name]
|
||||||
logger.info(f"Found dimensions for model '{model_name}': {dimensions}")
|
logger.info(f"Found dimensions for model '{model_name}': {dimensions}")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue