fix: replace PostHog evaluate_flags with static config for OSS notices (#7185)

This commit is contained in:
Kartik
2026-09-02 18:00:01 +05:30
committed by GitHub
parent c33ca27f5e
commit 3cf41878ea
15 changed files with 643 additions and 468 deletions
+97 -12
View File
@@ -1,16 +1,101 @@
import asyncio
import hashlib
import json
import logging
import os
import re
import sys
import threading
import time
import urllib.error
import urllib.request
from datetime import date, datetime, timedelta, timezone
from typing import Any, Dict, Optional, Tuple
from mem0.memory import telemetry as telemetry_module
from mem0.memory.setup import _load_config, _write_config
_logger = logging.getLogger(__name__)
FLAG_KEY = "mem0-oss-notices"
_REMOTE_CONFIG_URL = (
"https://raw.githubusercontent.com/mem0ai/mem0/main/mem0/memory/oss_notices_config.json"
)
_BUNDLED_CONFIG_PATH = os.path.join(os.path.dirname(__file__), "oss_notices_config.json")
_CONFIG_TTL_SECONDS = 3600
_CONFIG_FETCH_TIMEOUT_SECONDS = telemetry_module.FEATURE_FLAGS_REQUEST_TIMEOUT_SECONDS
_cached_config = None
_cached_config_ts = 0.0
_config_fetch_lock = threading.Lock()
class StaticFlagResult:
"""Drop-in replacement for PostHog's evaluate_flags return value."""
def __init__(self, variant, payload):
self._variant = variant
self._payload = payload
def get_flag(self, key):
return self._variant
def get_flag_payload(self, key):
return self._payload
def _get_event_properties(self):
return {
f"$feature/{FLAG_KEY}": self._variant,
"$active_feature_flags": [FLAG_KEY],
}
def _load_bundled_config():
try:
with open(_BUNDLED_CONFIG_PATH) as f:
return json.load(f)
except Exception:
return {"version": 1, "variant_split": 0.5, "notices": {}}
def _fetch_remote_config():
try:
req = urllib.request.Request(_REMOTE_CONFIG_URL, headers={"User-Agent": "mem0-oss"})
with urllib.request.urlopen(req, timeout=_CONFIG_FETCH_TIMEOUT_SECONDS) as resp:
return json.loads(resp.read())
except Exception:
_logger.debug("Failed to fetch remote notice config, using bundled fallback")
return None
def _get_notice_config():
global _cached_config, _cached_config_ts
now = time.monotonic()
if _cached_config is not None and (now - _cached_config_ts) < _CONFIG_TTL_SECONDS:
return _cached_config
with _config_fetch_lock:
if _cached_config is not None and (now - _cached_config_ts) < _CONFIG_TTL_SECONDS:
return _cached_config
remote = _fetch_remote_config()
if remote is not None and isinstance(remote, dict) and "notices" in remote:
_cached_config = remote
else:
_cached_config = _load_bundled_config()
_cached_config_ts = time.monotonic()
return _cached_config
def _evaluate_notice_flags(user_id):
config = _get_notice_config()
split = config.get("variant_split", 0.5)
hash_key = f"{FLAG_KEY}.{user_id}variant"
bucket = int(hashlib.sha1(hash_key.encode()).hexdigest()[:15], 16) / float(0xFFFFFFFFFFFFFFF)
variant = DISPLAYED_VARIANT if bucket < split else HOLDOUT_VARIANT
payload = {"notices": config.get("notices", {})}
return StaticFlagResult(variant, payload)
NOTICE_ID = "first_run"
TEMPORAL_FEATURE_NOTICE_ID = "temporal_stub"
TEMPORAL_USAGE_NOTICE_ID = "temporal_usage"
@@ -88,10 +173,10 @@ def display_first_run_notice(memory_instance, sync_type: str, trigger_function:
variant = None
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
_update_first_run_variant(variant)
@@ -168,10 +253,10 @@ def display_temporal_usage_notice(
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
if variant in (None, False):
return
@@ -302,10 +387,10 @@ def display_decay_usage_notice(
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
if variant in (None, False):
return
@@ -480,10 +565,10 @@ def display_scale_threshold_notice(
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
if variant in (None, False):
return
@@ -596,10 +681,10 @@ def display_performance_slow_query_notice(
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
if variant in (None, False):
return
@@ -749,10 +834,10 @@ def _get_feature_error_message(
try:
telemetry = telemetry_module._get_oss_telemetry()
if telemetry is None or telemetry.posthog is None or not telemetry.user_id:
if telemetry is None or not telemetry.user_id:
return plain_error
flags = telemetry.posthog.evaluate_flags(telemetry.user_id, flag_keys=[FLAG_KEY])
flags = _evaluate_notice_flags(telemetry.user_id)
variant = flags.get_flag(FLAG_KEY)
if variant in (None, False):
return plain_error
+44
View File
@@ -0,0 +1,44 @@
{
"version": 1,
"variant_split": 0.5,
"notices": {
"first_run": {
"copy": "",
"enabled": false,
"notice_type": "log_line"
},
"temporal_stub": {
"copy": "",
"enabled": false,
"notice_type": "error"
},
"temporal_usage": {
"copy": "",
"enabled": false,
"notice_type": "log_line"
},
"decay_stub": {
"copy": "",
"enabled": false,
"notice_type": "error"
},
"decay_usage": {
"copy": "",
"enabled": false,
"notice_type": "log_line"
},
"scale_threshold": {
"copies": {
"memory_count": "",
"top_k": ""
},
"enabled": false,
"notice_type": "log_line"
},
"performance_slow_query": {
"copy": "",
"enabled": false,
"notice_type": "log_line"
}
}
}