"""Global configuration for Hindsight-LiteLLM integration. This module provides a clean API for configuring Hindsight integration: 1. configure() - Connection settings + default per-call settings - API URL, authentication, and default values for all per-call settings 2. set_defaults() - Update default values for per-call settings - Convenience function to update defaults without reconfiguring connection 3. Per-call kwargs (hindsight_* prefix) - Override any setting per-call - hindsight_bank_id, hindsight_budget, hindsight_inject_memories, etc. 4. set_bank_mission() - Set the mission for a memory bank (for mental models) """ import os from dataclasses import asdict, dataclass, field, fields from enum import Enum from typing import Any, Dict, List, Optional # Default Hindsight API URL (production) DEFAULT_HINDSIGHT_API_URL = "https://api.hindsight.vectorize.io" DEFAULT_BANK_ID = "default" HINDSIGHT_API_KEY_ENV = "HINDSIGHT_API_KEY" class MemoryInjectionMode(str, Enum): """How memories should be injected into the prompt. Use inject_memories=False if you don't want memory injection. """ SYSTEM_MESSAGE = "system_message" # Add to/create system message PREPEND_USER = "prepend_user" # Prepend to last user message @dataclass class HindsightCallSettings: """Unified settings for Hindsight memory operations. All fields here can be: - Set as defaults via configure() or set_defaults() - Overridden per-call via hindsight_* kwargs (e.g., hindsight_bank_id="other") To add a new setting, just add a field here - it automatically works everywhere. Attributes: bank_id: Memory bank ID for operations. Use different bank_ids per user for multi-user support (e.g., f"user-{user_id}") session_id: Session ID for grouping conversations (maps to Hindsight's document_id). Use this to group related messages in a conversation. When set, Hindsight uses upsert behavior (same session = replace). document_id: DEPRECATED - Use session_id instead. Kept for backward compatibility. If both are set, session_id takes precedence. store_conversations: Whether to store conversations to Hindsight inject_memories: Whether to inject relevant memories into prompts injection_mode: How to inject memories (system_message or prepend_user) budget: Budget level for memory recall (low, mid, high) fact_types: Filter by fact types (world, experience, opinion, observation) max_memories: Maximum memories to inject (None = no limit) max_memory_tokens: Maximum tokens for memory context include_entities: Include entity observations in recall results trace: Enable trace info for recall debugging tags: Tags to apply when storing conversations. Use for visibility scoping (e.g., ["user:alice", "session:123"]). Stored memories will have these tags. recall_tags: Tags to filter by when recalling/reflecting memories. Only memories matching these tags (based on recall_tags_match mode) will be retrieved. recall_tags_match: How to match recall_tags. Options: - "any": OR matching, includes untagged memories (default) - "all": AND matching, includes untagged memories - "any_strict": OR matching, excludes untagged memories - "all_strict": AND matching, excludes untagged memories use_reflect: Use reflect API instead of recall for memory injection reflect_context: Context for reflect reasoning (shapes response, not retrieval) reflect_response_schema: JSON Schema for structured reflect output reflect_include_facts: Include facts used by reflect in debug info query: Custom query for memory recall (if not set, extracts from user message) verbose: Enable verbose logging """ # Memory bank settings bank_id: Optional[str] = None session_id: Optional[str] = None # Primary - maps to Hindsight's document_id document_id: Optional[str] = None # Deprecated - use session_id instead # Feature toggles store_conversations: bool = True inject_memories: bool = True injection_mode: MemoryInjectionMode = MemoryInjectionMode.SYSTEM_MESSAGE # Recall settings budget: str = "mid" # low, mid, high fact_types: Optional[List[str]] = None # world, experience, opinion, observation max_memories: Optional[int] = None # None = no limit max_memory_tokens: int = 4096 include_entities: bool = True trace: bool = False # Tags for visibility scoping tags: Optional[List[str]] = None # Tags applied when storing conversations recall_tags: Optional[List[str]] = None # Tags to filter recall/reflect recall_tags_match: str = "any" # any, all, any_strict, all_strict # Reflect settings (alternative to recall) use_reflect: bool = False reflect_context: Optional[str] = None reflect_response_schema: Optional[Dict[str, Any]] = None reflect_include_facts: bool = False # Query override (if not set, extracts from last user message) query: Optional[str] = None # Logging verbose: bool = False @property def effective_document_id(self) -> Optional[str]: """Get the effective document_id for Hindsight API calls. Returns session_id if set, otherwise falls back to document_id. This maps to Hindsight's document_id parameter for retain operations. """ return self.session_id if self.session_id is not None else self.document_id def _merge_call_settings( defaults: HindsightCallSettings, kwargs: Dict[str, Any] ) -> HindsightCallSettings: """Merge per-call kwargs (hindsight_*) with defaults. This automatically handles all fields in HindsightCallSettings. When a new field is added to the dataclass, it works here automatically. Args: defaults: The default settings kwargs: The kwargs passed to the call, may contain hindsight_* overrides Returns: Merged settings with per-call values overriding defaults """ # Start with defaults as dict merged = asdict(defaults) # Get valid field names from the dataclass valid_fields = {f.name for f in fields(HindsightCallSettings)} # Override with hindsight_* kwargs for key, value in kwargs.items(): if key.startswith("hindsight_"): setting_name = key[len("hindsight_") :] if setting_name in valid_fields: merged[setting_name] = value return HindsightCallSettings(**merged) # Backward compatibility alias HindsightDefaults = HindsightCallSettings @dataclass class HindsightConfig: """Connection-level configuration for Hindsight integration. These are settings that require a new client connection to change: - API URL and authentication - Session-level settings (excluded_models, sync_storage) Per-call settings (bank_id, budget, etc.) are in default_settings. Attributes: hindsight_api_url: URL of the Hindsight API server api_key: API key for Hindsight authentication excluded_models: List of model patterns to exclude from interception sync_storage: If True, storage runs synchronously and raises errors immediately default_settings: Default values for all per-call settings """ hindsight_api_url: str = DEFAULT_HINDSIGHT_API_URL api_key: Optional[str] = None excluded_models: List[str] = field(default_factory=list) sync_storage: bool = False default_settings: HindsightCallSettings = field( default_factory=HindsightCallSettings ) # Backward compatibility properties - delegate to default_settings @property def bank_id(self) -> Optional[str]: return self.default_settings.bank_id @property def store_conversations(self) -> bool: return self.default_settings.store_conversations @property def inject_memories(self) -> bool: return self.default_settings.inject_memories @property def injection_mode(self) -> MemoryInjectionMode: return self.default_settings.injection_mode @property def verbose(self) -> bool: return self.default_settings.verbose # Global instances _global_config: Optional[HindsightConfig] = None def configure( hindsight_api_url: Optional[str] = None, api_key: Optional[str] = None, excluded_models: Optional[List[str]] = None, sync_storage: bool = False, # Bank setup (one-time) mission: Optional[str] = None, bank_name: Optional[str] = None, # Per-call defaults (all HindsightCallSettings fields) bank_id: Optional[str] = None, session_id: Optional[str] = None, document_id: Optional[str] = None, # Deprecated - use session_id store_conversations: bool = True, inject_memories: bool = True, injection_mode: MemoryInjectionMode = MemoryInjectionMode.SYSTEM_MESSAGE, budget: str = "mid", fact_types: Optional[List[str]] = None, max_memories: Optional[int] = None, max_memory_tokens: int = 4096, include_entities: bool = True, trace: bool = False, tags: Optional[List[str]] = None, recall_tags: Optional[List[str]] = None, recall_tags_match: str = "any", use_reflect: bool = False, reflect_context: Optional[str] = None, reflect_response_schema: Optional[Dict[str, Any]] = None, reflect_include_facts: bool = False, verbose: bool = False, ) -> HindsightConfig: """Configure Hindsight integration settings. Sets up connection settings and default values for per-call settings. All per-call settings can be overridden using hindsight_* kwargs. Args: hindsight_api_url: URL of the Hindsight API server (default: https://api.hindsight.vectorize.io) api_key: API key for Hindsight authentication. If not provided, reads from HINDSIGHT_API_KEY environment variable. excluded_models: List of model patterns to exclude from interception sync_storage: If True, storage runs synchronously and raises errors immediately. If False (default), storage runs in background for better performance. mission: Instructions guiding what Hindsight should learn and remember (used for mental model generation). bank_name: Optional display name for the bank. # Per-call defaults (can be overridden with hindsight_* kwargs): bank_id: Memory bank ID (default: "default"). For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") session_id: Session ID for grouping conversations. Maps to Hindsight's document_id. When set, enables upsert behavior (same session = replace). document_id: DEPRECATED - Use session_id instead. store_conversations: Whether to store conversations (default: True) inject_memories: Whether to inject memories (default: True) injection_mode: How to inject memories (system_message or prepend_user) budget: Recall budget level - low/mid/high (default: "mid") fact_types: Filter by fact types (world/experience/opinion/observation) max_memories: Max memories to inject (None = no limit) max_memory_tokens: Max tokens for memory context (default: 4096) include_entities: Include entity observations in recall (default: True) trace: Enable trace info for debugging (default: False) tags: Tags to apply when storing conversations (e.g., ["user:alice"]) recall_tags: Tags to filter by when recalling/reflecting memories recall_tags_match: Tag matching mode - any/all/any_strict/all_strict (default: "any") use_reflect: Use reflect API instead of recall (default: False) reflect_context: Context for reflect reasoning reflect_response_schema: JSON Schema for structured reflect output reflect_include_facts: Include facts in reflect debug info (default: False) verbose: Enable verbose logging (default: False) Returns: The configured HindsightConfig instance Example: >>> from hindsight_litellm import configure, enable >>> >>> # Minimal usage - just set HINDSIGHT_API_KEY env var >>> configure() >>> enable() >>> >>> # With per-call defaults >>> configure( ... bank_id="user-123", ... budget="high", ... background="Remember user preferences.", ... ) >>> enable() >>> >>> # Override per-call: >>> response = litellm.completion( ... model="gpt-4", ... messages=[...], ... hindsight_bank_id="other-user", # Override default ... ) """ global _global_config # Apply connection-level defaults resolved_api_url = hindsight_api_url or DEFAULT_HINDSIGHT_API_URL resolved_api_key = api_key or os.environ.get(HINDSIGHT_API_KEY_ENV) resolved_bank_id = bank_id or DEFAULT_BANK_ID # Build default settings default_settings = HindsightCallSettings( bank_id=resolved_bank_id, document_id=document_id, session_id=session_id, store_conversations=store_conversations, inject_memories=inject_memories, injection_mode=injection_mode, budget=budget, fact_types=fact_types, max_memories=max_memories, max_memory_tokens=max_memory_tokens, include_entities=include_entities, trace=trace, tags=tags, recall_tags=recall_tags, recall_tags_match=recall_tags_match, use_reflect=use_reflect, reflect_context=reflect_context, reflect_response_schema=reflect_response_schema, reflect_include_facts=reflect_include_facts, verbose=verbose, ) _global_config = HindsightConfig( hindsight_api_url=resolved_api_url, api_key=resolved_api_key, excluded_models=excluded_models or [], sync_storage=sync_storage, default_settings=default_settings, ) # If mission or bank_name is provided, create/update the bank if mission or bank_name: _create_or_update_bank( hindsight_api_url=resolved_api_url, bank_id=resolved_bank_id, name=bank_name, mission=mission, verbose=verbose, api_key=resolved_api_key, ) return _global_config def set_defaults( bank_id: Optional[str] = None, session_id: Optional[str] = None, document_id: Optional[str] = None, # Deprecated - use session_id store_conversations: Optional[bool] = None, inject_memories: Optional[bool] = None, injection_mode: Optional[MemoryInjectionMode] = None, budget: Optional[str] = None, fact_types: Optional[List[str]] = None, max_memories: Optional[int] = None, max_memory_tokens: Optional[int] = None, include_entities: Optional[bool] = None, trace: Optional[bool] = None, tags: Optional[List[str]] = None, recall_tags: Optional[List[str]] = None, recall_tags_match: Optional[str] = None, use_reflect: Optional[bool] = None, reflect_context: Optional[str] = None, reflect_response_schema: Optional[Dict[str, Any]] = None, reflect_include_facts: Optional[bool] = None, verbose: Optional[bool] = None, ) -> HindsightCallSettings: """Update default values for per-call settings. Updates only the specified fields, preserving other defaults. Any of these can be overridden on individual calls using hindsight_* kwargs (e.g., hindsight_bank_id="other-bank"). Args: bank_id: Memory bank ID for memory operations session_id: Session ID for grouping conversations. Maps to Hindsight's document_id. When set, enables upsert behavior (same session = replace). document_id: DEPRECATED - Use session_id instead. store_conversations: Whether to store conversations inject_memories: Whether to inject memories injection_mode: How to inject memories (system_message or prepend_user) budget: Budget level for memory recall (low, mid, high) fact_types: Fact types to filter (world, experience, opinion, observation) max_memories: Max number of memories to inject max_memory_tokens: Max tokens for memory context include_entities: Include entity observations in recall trace: Enable trace info for debugging tags: Tags to apply when storing conversations recall_tags: Tags to filter by when recalling/reflecting memories recall_tags_match: Tag matching mode - any/all/any_strict/all_strict use_reflect: Use reflect API instead of recall reflect_context: Context for reflect reasoning reflect_response_schema: JSON Schema for structured reflect output reflect_include_facts: Include facts in reflect debug info verbose: Enable verbose logging Returns: The updated HindsightCallSettings instance Example: >>> from hindsight_litellm import configure, set_defaults >>> configure() >>> set_defaults(bank_id="my-agent", budget="high") """ global _global_config # Ensure configure() was called if _global_config is None: # Auto-configure with defaults if not configured configure() # Get current defaults current = _global_config.default_settings # Update only provided values using dataclass fields updated_settings = HindsightCallSettings( bank_id=bank_id if bank_id is not None else current.bank_id, document_id=document_id if document_id is not None else current.document_id, session_id=session_id if session_id is not None else current.session_id, store_conversations=store_conversations if store_conversations is not None else current.store_conversations, inject_memories=inject_memories if inject_memories is not None else current.inject_memories, injection_mode=injection_mode if injection_mode is not None else current.injection_mode, budget=budget if budget is not None else current.budget, fact_types=fact_types if fact_types is not None else current.fact_types, max_memories=max_memories if max_memories is not None else current.max_memories, max_memory_tokens=max_memory_tokens if max_memory_tokens is not None else current.max_memory_tokens, include_entities=include_entities if include_entities is not None else current.include_entities, trace=trace if trace is not None else current.trace, tags=tags if tags is not None else current.tags, recall_tags=recall_tags if recall_tags is not None else current.recall_tags, recall_tags_match=recall_tags_match if recall_tags_match is not None else current.recall_tags_match, use_reflect=use_reflect if use_reflect is not None else current.use_reflect, reflect_context=reflect_context if reflect_context is not None else current.reflect_context, reflect_response_schema=reflect_response_schema if reflect_response_schema is not None else current.reflect_response_schema, reflect_include_facts=reflect_include_facts if reflect_include_facts is not None else current.reflect_include_facts, verbose=verbose if verbose is not None else current.verbose, ) # Update the config's default settings _global_config = HindsightConfig( hindsight_api_url=_global_config.hindsight_api_url, api_key=_global_config.api_key, excluded_models=_global_config.excluded_models, sync_storage=_global_config.sync_storage, default_settings=updated_settings, ) return updated_settings def _create_or_update_bank( hindsight_api_url: str, bank_id: str, name: Optional[str] = None, mission: Optional[str] = None, verbose: bool = False, api_key: Optional[str] = None, ) -> None: """Create or update a memory bank with the given configuration. Args: hindsight_api_url: URL of the Hindsight API server bank_id: The bank ID to create/update name: Optional display name for the bank mission: Instructions guiding what Hindsight should learn and remember verbose: Enable verbose logging """ try: from hindsight_client import Hindsight client = Hindsight(base_url=hindsight_api_url, api_key=api_key) client.create_bank( bank_id=bank_id, name=name, mission=mission, ) if verbose: import logging logging.getLogger("hindsight_litellm").info( f"Created/updated bank '{bank_id}' with mission" ) except ImportError: if verbose: import logging logging.getLogger("hindsight_litellm").warning( "hindsight_client not installed. Cannot create bank. " "Install with: pip install hindsight-client" ) except Exception as e: if verbose: import logging logging.getLogger("hindsight_litellm").warning( f"Failed to create/update bank: {e}" ) def get_config() -> Optional[HindsightConfig]: """Get the current global configuration. Returns: The current HindsightConfig instance, or None if not configured """ return _global_config def get_defaults() -> Optional[HindsightCallSettings]: """Get the current global defaults for per-call settings. Returns: The current HindsightCallSettings instance, or None if not configured """ if _global_config is not None: return _global_config.default_settings return None def is_configured() -> bool: """Check if Hindsight has been configured with a valid bank_id. Returns: True if configure() has been called and a bank_id is set """ if _global_config is not None and _global_config.bank_id: return True return False def reset_config() -> None: """Reset all global configuration to None.""" global _global_config _global_config = None