suna/backend/core/services/langfuse.py

154 lines
6.3 KiB
Python

import os
import atexit
from langfuse import Langfuse
from core.utils.logger import logger
# Get configuration from environment
public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
secret_key = os.getenv("LANGFUSE_SECRET_KEY")
host = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
# Determine if Langfuse should be enabled
enabled = bool(public_key and secret_key)
logger.debug(f"🔍 Langfuse Environment Check:")
logger.debug(f" - Public Key: {'✅ Set' if public_key else '❌ Missing'}")
logger.debug(f" - Secret Key: {'✅ Set' if secret_key else '❌ Missing'}")
logger.debug(f" - Host: {host}")
logger.debug(f" - Enabled: {enabled}")
# Initialize client using singleton pattern
if enabled:
logger.debug(f"🔍 Initializing Langfuse with host: {host}")
try:
# Initialize with constructor arguments (recommended approach)
# Disable SSL verification for local/development environments if needed
import httpx
# Use SSL verification by default, but disable if SSL issues are detected
try:
httpx_client = httpx.Client(verify=True)
# Test SSL connection
httpx_client.get("https://httpbin.org/get", timeout=2)
except Exception as ssl_error:
logger.debug(f"SSL verification failed, disabling: {ssl_error}")
httpx_client = httpx.Client(verify=False)
langfuse = Langfuse(
public_key=public_key,
secret_key=secret_key,
host=host,
httpx_client=httpx_client
)
logger.info(f"✅ Langfuse initialized successfully - Host: {host}")
logger.info(f"🔍 Langfuse Public Key: {public_key[:8]}...{public_key[-4:] if len(public_key) > 12 else public_key}")
# Test the connection
try:
logger.debug(f"🔍 Testing authentication with {host}...")
auth_result = langfuse.auth_check()
if auth_result:
logger.info(f"🔗 Langfuse authentication successful with {host}")
else:
logger.warning(f"❌ Langfuse authentication failed with {host}")
except Exception as auth_error:
logger.warning(f"⚠️ Langfuse auth check failed: {auth_error}")
# Try alternative host if authentication fails
if "us.cloud.langfuse.com" in host:
logger.info("🔄 Trying EU host as fallback...")
fallback_host = "https://cloud.langfuse.com"
elif "cloud.langfuse.com" in host and "us." not in host:
logger.info("🔄 Trying US host as fallback...")
fallback_host = "https://us.cloud.langfuse.com"
else:
fallback_host = None
if fallback_host:
try:
logger.info(f"🔄 Testing fallback host: {fallback_host}")
langfuse_fallback = Langfuse(
public_key=public_key,
secret_key=secret_key,
host=fallback_host,
httpx_client=httpx_client
)
auth_result_fallback = langfuse_fallback.auth_check()
if auth_result_fallback:
logger.info(f"✅ Fallback host authentication successful! Switching to {fallback_host}")
langfuse = langfuse_fallback
host = fallback_host
# Update the global host for URL generation
globals()['host'] = fallback_host
else:
logger.warning(f"❌ Fallback host {fallback_host} authentication also failed")
except Exception as fallback_error:
logger.warning(f"⚠️ Fallback host {fallback_host} also failed: {fallback_error}")
# Register shutdown hook for clean exit
atexit.register(langfuse.shutdown)
except Exception as e:
logger.error(f"❌ Failed to initialize Langfuse: {e}")
# Create disabled instance as fallback with SSL handling
try:
import httpx
httpx_client = httpx.Client(verify=False)
langfuse = Langfuse(enabled=False, httpx_client=httpx_client)
except Exception:
# Ultimate fallback - create a mock client
class MockLangfuse:
def __init__(self):
self.enabled = False
def trace(self, **kwargs): return MockTrace()
def generation(self, **kwargs): return MockGeneration()
def span(self, **kwargs): return MockSpan()
def event(self, **kwargs): pass
def flush(self): pass
def shutdown(self): pass
def auth_check(self): return False
class MockTrace:
def __init__(self): self.id = "mock-trace-id"
class MockGeneration:
def __init__(self): self.id = "mock-generation-id"
def update(self, **kwargs): pass
def end(self, **kwargs): pass
class MockSpan:
def __init__(self): self.id = "mock-span-id"
def end(self, **kwargs): pass
langfuse = MockLangfuse()
enabled = False
else:
logger.debug("⚠️ Langfuse disabled - missing LANGFUSE_PUBLIC_KEY or LANGFUSE_SECRET_KEY")
# Create mock client for disabled state
class MockLangfuse:
def __init__(self):
self.enabled = False
def trace(self, **kwargs): return MockTrace()
def generation(self, **kwargs): return MockGeneration()
def span(self, **kwargs): return MockSpan()
def event(self, **kwargs): pass
def flush(self): pass
def shutdown(self): pass
def auth_check(self): return False
class MockTrace:
def __init__(self): self.id = "mock-trace-id"
class MockGeneration:
def __init__(self): self.id = "mock-generation-id"
def update(self, **kwargs): pass
def end(self, **kwargs): pass
class MockSpan:
def __init__(self): self.id = "mock-span-id"
def end(self, **kwargs): pass
langfuse = MockLangfuse()
def get_langfuse_client():
"""
Get the Langfuse client instance.
In langfuse v2.x, we return the global instance.
"""
return langfuse