add container utils (#151)
* add container utils * added localhost url to settings * added localhost_url as a constant * added localhost_url to get settings query * make ollama onboarding have localhost url by default * make endpoint be changed in models service and in onboarding backend instead of onboarding screen * fixed embedding dimensions to get stripped model * make config come as localhost but global variable be set as the transformed endpoint * remove setting ollama url since it comes from the global variable * use localhost again on ollama --------- Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
This commit is contained in:
parent
f54479cf48
commit
d015ed1b0c
8 changed files with 152 additions and 8 deletions
|
|
@ -37,6 +37,7 @@ export interface Settings {
|
|||
separator?: string;
|
||||
embeddingModel?: string;
|
||||
};
|
||||
localhost_url?: string;
|
||||
}
|
||||
|
||||
export const useGetSettingsQuery = (
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function OllamaOnboarding({
|
|||
sampleDataset: boolean;
|
||||
setSampleDataset: (dataset: boolean) => void;
|
||||
}) {
|
||||
const [endpoint, setEndpoint] = useState("http://localhost:11434");
|
||||
const [endpoint, setEndpoint] = useState(`http://localhost:11434`);
|
||||
const [showConnecting, setShowConnecting] = useState(false);
|
||||
const debouncedEndpoint = useDebouncedValue(endpoint, 500);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import platform
|
||||
from starlette.responses import JSONResponse
|
||||
from utils.container_utils import transform_localhost_url
|
||||
from utils.logging_config import get_logger
|
||||
from config.settings import (
|
||||
LANGFLOW_URL,
|
||||
|
|
@ -8,6 +9,7 @@ from config.settings import (
|
|||
LANGFLOW_INGEST_FLOW_ID,
|
||||
LANGFLOW_PUBLIC_URL,
|
||||
DOCLING_COMPONENT_ID,
|
||||
LOCALHOST_URL,
|
||||
clients,
|
||||
get_openrag_config,
|
||||
config_manager,
|
||||
|
|
@ -74,6 +76,7 @@ async def get_settings(request, session_manager):
|
|||
"llm_model": agent_config.llm_model,
|
||||
"system_prompt": agent_config.system_prompt,
|
||||
},
|
||||
"localhost_url": LOCALHOST_URL,
|
||||
}
|
||||
|
||||
# Only expose edit URLs when a public URL is configured
|
||||
|
|
@ -570,7 +573,8 @@ async def onboarding(request, flows_service):
|
|||
|
||||
# Set base URL for Ollama provider
|
||||
if provider == "ollama" and "endpoint" in body:
|
||||
endpoint = body["endpoint"]
|
||||
endpoint = transform_localhost_url(body["endpoint"])
|
||||
|
||||
await clients._create_langflow_global_variable(
|
||||
"OLLAMA_BASE_URL", endpoint, modify=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from openai import AsyncOpenAI
|
|||
from opensearchpy import AsyncOpenSearch
|
||||
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.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 = os.getenv("DOCLING_COMPONENT_ID", "DoclingRemote-78KoX")
|
||||
|
||||
LOCALHOST_URL = get_container_host() or "localhost"
|
||||
|
||||
# Global clients instance
|
||||
clients = AppClients()
|
||||
|
||||
|
|
|
|||
|
|
@ -836,10 +836,5 @@ class FlowsService:
|
|||
template["url"]["value"] = endpoint
|
||||
template["url"]["options"] = [endpoint]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import httpx
|
||||
from typing import Dict, List
|
||||
from utils.container_utils import transform_localhost_url
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -95,7 +96,7 @@ class ModelsService:
|
|||
"""Fetch available models from Ollama API with tool calling capabilities for language models"""
|
||||
try:
|
||||
# Use provided endpoint or default
|
||||
ollama_url = endpoint
|
||||
ollama_url = transform_localhost_url(endpoint)
|
||||
|
||||
# API endpoints
|
||||
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
|
||||
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:
|
||||
dimensions = all_models[model_name]
|
||||
logger.info(f"Found dimensions for model '{model_name}': {dimensions}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue