diff --git a/litellm/entrypoint.py b/litellm/entrypoint.py index 1b987365..20d4baf0 100644 --- a/litellm/entrypoint.py +++ b/litellm/entrypoint.py @@ -98,6 +98,26 @@ def strptime(cls, date_str: str, fmt: str) -> original_datetime: datetime.datetime = RobustDatetime sys.stdout.flush() +# Register both RobustDatetime AND the original datetime with Prisma's +# singledispatch serializer. When entrypoint.py replaces datetime.datetime +# with RobustDatetime before Prisma loads, Prisma's own +# @serializer.register(datetime.datetime) ends up registering RobustDatetime. +# But database drivers (psycopg2) return the *original* C-level datetime +# instances, which no longer match. We must register both classes. +try: + from prisma.builder import serializer + def _serialize_dt(dt): + """Serialize datetime to ISO8601 with timezone (UTC if naive).""" + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + return dt.isoformat() + serializer.register(original_datetime, _serialize_dt) + serializer.register(RobustDatetime, _serialize_dt) + print("🩹 Registered original_datetime + RobustDatetime with Prisma serializer") +except Exception: + pass +sys.stdout.flush() + # Start LiteLLM Proxy import litellm from litellm.proxy.proxy_cli import run_server diff --git a/pod.yaml b/pod.yaml index d8ae82b7..359e9429 100644 --- a/pod.yaml +++ b/pod.yaml @@ -11,7 +11,7 @@ spec: - 'no' - --loglevel - warning - image: valkey/valkey:9.1.0-alpine + image: docker.io/valkey/valkey:9.1.0-alpine livenessProbe: tcpSocket: port: 6379 @@ -34,7 +34,7 @@ spec: - /app/entrypoint.py env: - name: DATABASE_URL - value: postgresql://postgres:***@127.0.0.1:5432/postgres + value: postgresql://postgres:POSTGRES_PASSWORD_ENCODED_PLACEHOLDER@127.0.0.1:5432/postgres - name: STORE_MODEL_IN_DB value: 'True' - name: LITELLM_LOG @@ -42,9 +42,9 @@ spec: - name: LANGFUSE_HOST value: http://127.0.0.1:3001 - name: LITELLM_MASTER_KEY - value: sk-lit...33bf + value: LITELLM_MASTER_KEY_PLACEHOLDER - name: OLLAMA_API_KEY - value: 3cd542f8fca14fd08bedfd4f2ab36f9f.6G7Iukbvu1Keyi9x8eKckNEO + value: OLLAMA_API_KEY_PLACEHOLDER image: ghcr.io/berriai/litellm:v1.90.1 livenessProbe: exec: @@ -89,19 +89,19 @@ spec: - name: LITELLM_CONFIG_PATH value: /config/litellm_dir/config.yaml - name: DATABASE_URL - value: postgresql://postgres:***@127.0.0.1:5432/postgres + value: postgresql://postgres:POSTGRES_PASSWORD_ENCODED_PLACEHOLDER@127.0.0.1:5432/postgres - name: DBUS_SESSION_BUS_ADDRESS value: unix:path=/run/user/1000/bus - name: LITELLM_MASTER_KEY - value: sk-lit...33bf + value: LITELLM_MASTER_KEY_PLACEHOLDER - name: LANGFUSE_PUBLIC_KEY - value: pk-lf-200bbf48-1d23-447d-a79a-8614106497e6 + value: LANGFUSE_PUBLIC_KEY_PLACEHOLDER - name: LANGFUSE_SECRET_KEY - value: sk-lf-...3b49 + value: LANGFUSE_SECRET_KEY_PLACEHOLDER - name: LANGFUSE_HOST value: http://127.0.0.1:3001 - name: OLLAMA_API_KEY - value: 3cd542f8fca14fd08bedfd4f2ab36f9f.6G7Iukbvu1Keyi9x8eKckNEO + value: OLLAMA_API_KEY_PLACEHOLDER - name: LOG_LEVEL value: info image: localhost/llm-triage-router:latest @@ -154,10 +154,10 @@ spec: - name: POSTGRES_USER value: postgres - name: POSTGRES_PASSWORD - value: postgres-password-*** + value: POSTGRES_PASSWORD_RAW_PLACEHOLDER - name: POSTGRES_DB value: langfuse - image: pgvector/pgvector:0.8.3-pg18 + image: docker.io/pgvector/pgvector:0.8.3-pg18 livenessProbe: exec: command: @@ -188,7 +188,7 @@ spec: value: clickhouse - name: CLICKHOUSE_PASSWORD value: clickhouse - image: clickhouse/clickhouse-server:26.6.1.1193 + image: docker.io/clickhouse/clickhouse-server:26.6.1.1193 livenessProbe: exec: command: @@ -227,7 +227,7 @@ spec: - noeviction - --loglevel - warning - image: valkey/valkey:9.1.0-alpine + image: docker.io/valkey/valkey:9.1.0-alpine livenessProbe: tcpSocket: port: 6380 @@ -253,7 +253,7 @@ spec: # Langfuse Web — observability dashboard - env: - name: DATABASE_URL - value: postgresql://postgres:***@127.0.0.1:5432/langfuse + value: postgresql://postgres:POSTGRES_PASSWORD_ENCODED_PLACEHOLDER@127.0.0.1:5432/langfuse - name: NEXTAUTH_SECRET value: NEXTAUTH_SECRET_PLACEHOLDER - name: NEXTAUTH_URL @@ -310,7 +310,7 @@ spec: value: admin-local-pw-2026 - name: LANGFUSE_LOG_LEVEL value: warn - image: langfuse/langfuse:3.202.1 + image: docker.io/langfuse/langfuse:3.202.1 livenessProbe: exec: command: @@ -337,7 +337,7 @@ spec: # Langfuse Worker — background job processor - env: - name: DATABASE_URL - value: postgresql://postgres:***@127.0.0.1:5432/langfuse + value: postgresql://postgres:POSTGRES_PASSWORD_ENCODED_PLACEHOLDER@127.0.0.1:5432/langfuse - name: CLICKHOUSE_URL value: http://127.0.0.1:8123 - name: CLICKHOUSE_USER @@ -372,7 +372,7 @@ spec: value: minioadmin - name: LANGFUSE_LOG_LEVEL value: warn - image: langfuse/langfuse-worker:3.202.1 + image: docker.io/langfuse/langfuse-worker:3.202.1 livenessProbe: exec: command: @@ -396,7 +396,7 @@ spec: value: minioadmin - name: MINIO_ROOT_PASSWORD value: minioadmin - image: minio/minio:RELEASE.2025-09-07T16-13-09Z + image: docker.io/minio/minio:RELEASE.2025-09-07T16-13-09Z livenessProbe: httpGet: path: /minio/health/live diff --git a/start-stack.sh b/start-stack.sh index ee181ee8..d59746de 100755 --- a/start-stack.sh +++ b/start-stack.sh @@ -31,7 +31,7 @@ if [ -f "$ENV_FILE" ]; then fi # Ensure openssl is installed if we need to generate passwords/keys -if [ -z "$POSTGRES_PASSWORD" ] || [ -z "$NEXTAUTH_SECRET" ] || [ -z "$SALT" ] || [ -z "$ENCRYPTION_KEY" ] || [ -z "$LITELLM_MASTER_KEY" ]; then +if [ -z "$POSTGRES_PASSWORD" ] || [ -z "$NEXTAUTH_SECRET" ] || [ -z "$SALT" ] || [ -z "$ENCRYPTION_KEY" ] || [ -z "$LITELLM_MASTER_KEY" ] || [ -z "$ROUTER_API_KEY" ] || [ -z "$LANGFUSE_PUBLIC_KEY" ] || [ -z "$LANGFUSE_SECRET_KEY" ]; then if ! command -v openssl &>/dev/null; then echo "❌ Error: 'openssl' is required to generate secure random keys but was not found in PATH." exit 1 @@ -105,7 +105,7 @@ else echo "⚠️ Warning: Host agy daemon not responding on port 5005" fi -if [ -z "$NEXTAUTH_SECRET" ] || [ -z "$SALT" ] || [ -z "$ENCRYPTION_KEY" ] || [ -z "$LITELLM_MASTER_KEY" ] || [ -z "$ROUTER_API_KEY" ]; then +if [ -z "$NEXTAUTH_SECRET" ] || [ -z "$SALT" ] || [ -z "$ENCRYPTION_KEY" ] || [ -z "$LITELLM_MASTER_KEY" ] || [ -z "$ROUTER_API_KEY" ] || [ -z "$LANGFUSE_PUBLIC_KEY" ] || [ -z "$LANGFUSE_SECRET_KEY" ]; then if ! command -v openssl &>/dev/null; then echo "❌ Error: 'openssl' is required to generate secure random keys but was not found in PATH." exit 1 @@ -116,6 +116,17 @@ fi touch "$ENV_FILE" chmod 600 "$ENV_FILE" +generate_uuid() { + local val + val=$(openssl rand -hex 16 2>/dev/null) + local status=$? + if [ $status -ne 0 ] || [ ${#val} -ne 32 ]; then + echo "❌ Error: Failed to generate secure random UUID (openssl rand returned exit status $status, length ${#val})." >&2 + return 1 + fi + echo "${val:0:8}-${val:8:4}-${val:12:4}-${val:16:4}-${val:20:12}" +} + if [ -z "$NEXTAUTH_SECRET" ]; then NEXTAUTH_SECRET="$(openssl rand -base64 32)" echo "NEXTAUTH_SECRET=\"$NEXTAUTH_SECRET\"" >> "$ENV_FILE" @@ -151,6 +162,48 @@ if [ -z "$ROUTER_API_KEY" ]; then echo "✓ Generated new ROUTER_API_KEY and saved to $ENV_FILE" fi +if [ -z "$LANGFUSE_PUBLIC_KEY" ]; then + uuid=$(generate_uuid) + if [ $? -ne 0 ] || [ -z "$uuid" ]; then + echo "❌ Error: Failed to generate LANGFUSE_PUBLIC_KEY." >&2 + exit 1 + fi + LANGFUSE_PUBLIC_KEY="pk-lf-$uuid" + echo "LANGFUSE_PUBLIC_KEY=\"$LANGFUSE_PUBLIC_KEY\"" >> "$ENV_FILE" + chmod 600 "$ENV_FILE" + echo "✓ Generated new LANGFUSE_PUBLIC_KEY and saved to $ENV_FILE" +fi + +if [ -z "$LANGFUSE_SECRET_KEY" ]; then + uuid=$(generate_uuid) + if [ $? -ne 0 ] || [ -z "$uuid" ]; then + echo "❌ Error: Failed to generate LANGFUSE_SECRET_KEY." >&2 + exit 1 + fi + LANGFUSE_SECRET_KEY="sk-lf-$uuid" + echo "LANGFUSE_SECRET_KEY=\"$LANGFUSE_SECRET_KEY\"" >> "$ENV_FILE" + chmod 600 "$ENV_FILE" + echo "✓ Generated new LANGFUSE_SECRET_KEY and saved to $ENV_FILE" +fi + +if [ -z "$OLLAMA_API_KEY" ]; then + if [ -t 0 ]; then + echo "🔑 OLLAMA_API_KEY not found." + echo -n "Please enter your Ollama API Key (input will be hidden): " + read -rs OLLAMA_API_KEY + echo "" + echo "OLLAMA_API_KEY=\"$OLLAMA_API_KEY\"" >> "$ENV_FILE" + chmod 600 "$ENV_FILE" + echo "✓ Ollama API key saved securely to $ENV_FILE" + else + echo "❌ Error: OLLAMA_API_KEY is not set in your environment or in $ENV_FILE." + echo "Please run this script interactively first, or create the file manually:" + echo " echo 'OLLAMA_API_KEY=your_key_here' >> $ENV_FILE" + echo " chmod 600 $ENV_FILE" + exit 1 + fi +fi + # DYNAMIC_LITELLM_MASTER_KEY_PLACEHOLDER in router config is resolved at runtime from env @@ -360,7 +413,7 @@ if podman pod exists agent-router-pod 2>/dev/null; then fi render_pod_yaml() { - export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY + export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY OLLAMA_API_KEY LANGFUSE_PUBLIC_KEY LANGFUSE_SECRET_KEY python3 - "$WORKDIR/pod.yaml" <<'PY' import os, sys, urllib.parse uid = os.getuid() @@ -370,12 +423,15 @@ placeholders = [ "/home/gpav/Vrac/LAB/AI/LLM-Routing", "/home/gpav/", "/run/user/1000", - "sk-lit...33bf", - "postgres:***", + "LITELLM_MASTER_KEY_PLACEHOLDER", + "POSTGRES_PASSWORD_RAW_PLACEHOLDER", + "POSTGRES_PASSWORD_ENCODED_PLACEHOLDER", "NEXTAUTH_SECRET_PLACEHOLDER", "SALT_PLACEHOLDER", "ENCRYPTION_KEY_PLACEHOLDER", - "postgres-password-***" + "OLLAMA_API_KEY_PLACEHOLDER", + "LANGFUSE_PUBLIC_KEY_PLACEHOLDER", + "LANGFUSE_SECRET_KEY_PLACEHOLDER" ] for ph in placeholders: if ph not in text: @@ -384,14 +440,17 @@ for ph in placeholders: text = text.replace("/home/gpav/Vrac/LAB/AI/LLM-Routing", os.environ["WORKDIR"]) text = text.replace("/home/gpav/", os.environ["HOME"] + "/") text = text.replace("/run/user/1000", f"/run/user/{uid}") -text = text.replace("sk-lit...33bf", os.environ["LITELLM_MASTER_KEY"]) +text = text.replace("LITELLM_MASTER_KEY_PLACEHOLDER", os.environ["LITELLM_MASTER_KEY"]) +text = text.replace("POSTGRES_PASSWORD_RAW_PLACEHOLDER", os.environ["POSTGRES_PASSWORD"]) # URL-encode the postgres password for DSN insertion -encoded_password = urllib.parse.quote_plus(os.environ['POSTGRES_PASSWORD']) -text = text.replace("postgres:***", f"postgres:{encoded_password}") -text = text.replace("postgres-password-***", os.environ["POSTGRES_PASSWORD"]) +encoded_password = urllib.parse.quote(os.environ['POSTGRES_PASSWORD'], safe='') +text = text.replace("POSTGRES_PASSWORD_ENCODED_PLACEHOLDER", encoded_password) text = text.replace("NEXTAUTH_SECRET_PLACEHOLDER", os.environ["NEXTAUTH_SECRET"]) text = text.replace("SALT_PLACEHOLDER", os.environ["SALT"]) text = text.replace("ENCRYPTION_KEY_PLACEHOLDER", os.environ["ENCRYPTION_KEY"]) +text = text.replace("OLLAMA_API_KEY_PLACEHOLDER", os.environ["OLLAMA_API_KEY"]) +text = text.replace("LANGFUSE_PUBLIC_KEY_PLACEHOLDER", os.environ["LANGFUSE_PUBLIC_KEY"]) +text = text.replace("LANGFUSE_SECRET_KEY_PLACEHOLDER", os.environ["LANGFUSE_SECRET_KEY"]) sys.stdout.write(text) PY }