rmi-backend/app/caching_shield/langfuse_sampler.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

72 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""
Langfuse Smart Sampling - Never exceed free tier, keep local as fallback.
Strategy:
- Sample 20% of normal traces → cloud
- 100% of errors (score < 0.5) → cloud
- 100% of user-facing requests → cloud
- Everything → local ClickHouse (full archive)
- Target: 800-1,200 observations/day (free tier: 1,600/day)
Free tier: 50,000 observations/month
Our target: 30,000/month (60% headroom)
"""
import logging
import os
import random
logger = logging.getLogger("langfuse_sampler")
SAMPLING_RATE = float(os.getenv("LANGFUSE_SAMPLING_RATE", "0.20")) # 20% normal
ERROR_RATE = 1.0 # 100% of errors
USER_RATE = 1.0 # 100% of user-facing
_counters = {
"total": 0,
"sampled": 0,
"errors_captured": 0,
"local_only": 0,
}
def should_send_to_cloud(
is_error: bool = False,
is_user_facing: bool = False,
force: bool = False,
) -> bool:
"""Decide whether to send this trace to Langfuse cloud."""
_counters["total"] += 1
if force:
_counters["sampled"] += 1
return True
# Always capture errors (failed calls, hallucinations, etc.)
if is_error:
_counters["errors_captured"] += 1
_counters["sampled"] += 1
return True
# Always capture user-facing interactions
if is_user_facing:
_counters["sampled"] += 1
return True
# Sample normal background traces
if random.random() < SAMPLING_RATE:
_counters["sampled"] += 1
return True
_counters["local_only"] += 1
return False
def stats() -> dict:
total = max(_counters["total"], 1)
return {
**_counters,
"sampling_rate_pct": round(_counters["sampled"] / total * 100, 1),
"estimated_daily": _counters["sampled"],
}