|
|
""" |
|
|
Configuration management for model pricing and settings. |
|
|
""" |
|
|
import os |
|
|
import json |
|
|
import logging |
|
|
from pathlib import Path |
|
|
from typing import Dict, Optional |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class LangFuseConfig: |
|
|
"""Manage LangFuse observability configuration.""" |
|
|
|
|
|
def __init__(self): |
|
|
"""Initialize LangFuse configuration from environment variables.""" |
|
|
self.enabled = os.getenv("LANGFUSE_ENABLED", "true").lower() == "true" |
|
|
self.public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "") |
|
|
self.secret_key = os.getenv("LANGFUSE_SECRET_KEY", "") |
|
|
self.host = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com") |
|
|
|
|
|
|
|
|
self.trace_all_llm_calls = os.getenv("LANGFUSE_TRACE_ALL_LLM", "true").lower() == "true" |
|
|
self.trace_rag = os.getenv("LANGFUSE_TRACE_RAG", "true").lower() == "true" |
|
|
self.flush_at = int(os.getenv("LANGFUSE_FLUSH_AT", "15")) |
|
|
self.flush_interval = int(os.getenv("LANGFUSE_FLUSH_INTERVAL", "10")) |
|
|
|
|
|
def is_configured(self) -> bool: |
|
|
"""Check if LangFuse is properly configured.""" |
|
|
if not self.enabled: |
|
|
return False |
|
|
|
|
|
if not self.public_key or not self.secret_key: |
|
|
logger.warning("LangFuse is enabled but API keys are missing") |
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
def get_init_params(self) -> Dict: |
|
|
"""Get initialization parameters for LangFuse client.""" |
|
|
return { |
|
|
"public_key": self.public_key, |
|
|
"secret_key": self.secret_key, |
|
|
"host": self.host, |
|
|
"flush_at": self.flush_at, |
|
|
"flush_interval": self.flush_interval, |
|
|
} |
|
|
|
|
|
|
|
|
class PricingConfig: |
|
|
"""Manage model pricing configuration with JSON + env override support.""" |
|
|
|
|
|
def __init__(self, config_path: Optional[str] = None): |
|
|
""" |
|
|
Initialize pricing configuration. |
|
|
|
|
|
Args: |
|
|
config_path: Path to pricing JSON file (optional) |
|
|
""" |
|
|
if config_path is None: |
|
|
|
|
|
project_root = Path(__file__).parent.parent |
|
|
config_path = project_root / "config" / "pricing.json" |
|
|
|
|
|
self.config_path = Path(config_path) |
|
|
self.pricing_data = self._load_pricing_config() |
|
|
|
|
|
def _load_pricing_config(self) -> Dict: |
|
|
"""Load pricing configuration from JSON file.""" |
|
|
try: |
|
|
if not self.config_path.exists(): |
|
|
logger.warning(f"Pricing config not found at {self.config_path}, using defaults") |
|
|
return self._get_default_pricing() |
|
|
|
|
|
with open(self.config_path, 'r') as f: |
|
|
data = json.load(f) |
|
|
logger.info(f"Loaded pricing config from {self.config_path}") |
|
|
return data |
|
|
except Exception as e: |
|
|
logger.error(f"Error loading pricing config: {e}, using defaults") |
|
|
return self._get_default_pricing() |
|
|
|
|
|
def _get_default_pricing(self) -> Dict: |
|
|
"""Return default pricing if config file not found.""" |
|
|
return { |
|
|
"models": { |
|
|
"gpt-4o-mini": { |
|
|
"input_price_per_1m": 0.15, |
|
|
"output_price_per_1m": 0.60 |
|
|
}, |
|
|
"phi-4-multimodal-instruct": { |
|
|
"input_price_per_1m": 0.08, |
|
|
"output_price_per_1m": 0.32 |
|
|
} |
|
|
}, |
|
|
"embeddings": { |
|
|
"text-embedding-3-small": { |
|
|
"price_per_1m": 0.02 |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
def get_model_pricing(self, model_name: str) -> Dict[str, float]: |
|
|
""" |
|
|
Get pricing for a specific model. |
|
|
|
|
|
Args: |
|
|
model_name: Model deployment name |
|
|
|
|
|
Returns: |
|
|
Dict with input_price_per_1m, output_price_per_1m |
|
|
""" |
|
|
|
|
|
env_input = os.getenv("PRICING_INPUT_PER_1M") |
|
|
env_output = os.getenv("PRICING_OUTPUT_PER_1M") |
|
|
|
|
|
if env_input and env_output: |
|
|
logger.info(f"Using pricing from environment variables: " |
|
|
f"input=${env_input}, output=${env_output}") |
|
|
return { |
|
|
"input_price_per_1m": float(env_input), |
|
|
"output_price_per_1m": float(env_output) |
|
|
} |
|
|
|
|
|
|
|
|
model_pricing = self.pricing_data.get("models", {}).get(model_name) |
|
|
|
|
|
if model_pricing: |
|
|
logger.info(f"Using pricing for {model_name} from config: " |
|
|
f"input=${model_pricing['input_price_per_1m']}, " |
|
|
f"output=${model_pricing['output_price_per_1m']}") |
|
|
return { |
|
|
"input_price_per_1m": model_pricing["input_price_per_1m"], |
|
|
"output_price_per_1m": model_pricing["output_price_per_1m"] |
|
|
} |
|
|
|
|
|
|
|
|
logger.warning(f"Model {model_name} not found in config, using gpt-4o-mini defaults") |
|
|
return { |
|
|
"input_price_per_1m": 0.15, |
|
|
"output_price_per_1m": 0.60 |
|
|
} |
|
|
|
|
|
def get_embedding_pricing(self, embedding_model: str) -> float: |
|
|
""" |
|
|
Get pricing for embedding model. |
|
|
|
|
|
Args: |
|
|
embedding_model: Embedding model name |
|
|
|
|
|
Returns: |
|
|
Price per 1M tokens |
|
|
""" |
|
|
|
|
|
env_embedding = os.getenv("PRICING_EMBEDDING_PER_1M") |
|
|
|
|
|
if env_embedding: |
|
|
logger.info(f"Using embedding pricing from env: ${env_embedding}") |
|
|
return float(env_embedding) |
|
|
|
|
|
|
|
|
embedding_pricing = self.pricing_data.get("embeddings", {}).get(embedding_model) |
|
|
|
|
|
if embedding_pricing: |
|
|
price = embedding_pricing["price_per_1m"] |
|
|
logger.info(f"Using embedding pricing for {embedding_model}: ${price}") |
|
|
return price |
|
|
|
|
|
|
|
|
logger.warning(f"Embedding model {embedding_model} not found, using default $0.02") |
|
|
return 0.02 |
|
|
|
|
|
|
|
|
|
|
|
_pricing_config = None |
|
|
_langfuse_config = None |
|
|
|
|
|
|
|
|
def get_pricing_config() -> PricingConfig: |
|
|
"""Get or create global pricing config instance.""" |
|
|
global _pricing_config |
|
|
if _pricing_config is None: |
|
|
_pricing_config = PricingConfig() |
|
|
return _pricing_config |
|
|
|
|
|
|
|
|
def get_langfuse_config() -> LangFuseConfig: |
|
|
"""Get or create global LangFuse config instance.""" |
|
|
global _langfuse_config |
|
|
if _langfuse_config is None: |
|
|
_langfuse_config = LangFuseConfig() |
|
|
return _langfuse_config |
|
|
|