From c0ca9b027e762965b2686a8ed9ae22699cdc6d8c Mon Sep 17 00:00:00 2001 From: Chris Bartholomew Date: Mon, 26 Jan 2026 04:49:06 -0500 Subject: [PATCH] Fix: Pass api_key to Hindsight client in litellm integration (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Pass api_key to Hindsight client in litellm integration The recall(), reflect(), and retain() wrapper functions were creating Hindsight client instances without passing the api_key from the config. This caused 401 Unauthorized errors when using hindsight-litellm with authenticated Hindsight API servers. Also added api_key parameter to: - HindsightOpenAI and HindsightAnthropic wrapper classes - wrap_openai() and wrap_anthropic() functions * Add sensible defaults for simpler API usage Make it easier to get started with hindsight-litellm by providing sensible defaults: - Default API URL: https://api.hindsight.vectorize.io (production) - Default bank_id: "default" - Read api_key from HINDSIGHT_API_KEY environment variable Now users can simply do: client = wrap_openai(OpenAI()) With just the HINDSIGHT_API_KEY env var set, and it works. Also adds comprehensive unit tests for the new defaults behavior. * Fix test using non-existent 'enabled' parameter in configure() The test was calling configure(enabled=False) but configure() doesn't have an enabled parameter. Changed to test is_configured() returns False when reset_config() has been called. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Fix: rename 'background' parameter to 'mission' in Python client create_bank() The parameter was named 'background' but the internal code used 'mission', causing undefined variable errors. The tests also expected 'mission'. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Nicolò Boschi Co-authored-by: Claude Opus 4.5 --- .../hindsight_client/hindsight_client.py | 2 +- .../litellm/hindsight_litellm/config.py | 77 +++++-- .../litellm/hindsight_litellm/wrappers.py | 113 +++++++--- hindsight-integrations/litellm/pyproject.toml | 5 + .../litellm/tests/test_config.py | 202 ++++++++++++++++++ .../litellm/tests/test_integration.py | 17 +- hindsight-integrations/litellm/uv.lock | 8 + 7 files changed, 370 insertions(+), 54 deletions(-) create mode 100644 hindsight-integrations/litellm/tests/test_config.py diff --git a/hindsight-clients/python/hindsight_client/hindsight_client.py b/hindsight-clients/python/hindsight_client/hindsight_client.py index c428901a..52e64e54 100644 --- a/hindsight-clients/python/hindsight_client/hindsight_client.py +++ b/hindsight-clients/python/hindsight_client/hindsight_client.py @@ -324,7 +324,7 @@ class Hindsight: self, bank_id: str, name: str | None = None, - background: str | None = None, + mission: str | None = None, disposition: dict[str, float] | None = None, ) -> BankProfileResponse: """Create or update a memory bank. diff --git a/hindsight-integrations/litellm/hindsight_litellm/config.py b/hindsight-integrations/litellm/hindsight_litellm/config.py index cf2b126f..599b1323 100644 --- a/hindsight-integrations/litellm/hindsight_litellm/config.py +++ b/hindsight-integrations/litellm/hindsight_litellm/config.py @@ -15,10 +15,16 @@ This module provides a clean API for configuring Hindsight integration: 4. set_bank_mission() - Set the mission for a memory bank (for mental models) """ +import os from typing import Optional, List, Any, Dict from dataclasses import dataclass, field from enum import Enum +# 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. @@ -37,7 +43,11 @@ class HindsightConfig: Attributes: hindsight_api_url: URL of the Hindsight API server - api_key: Optional API key for Hindsight authentication + (default: https://api.hindsight.vectorize.io) + bank_id: Memory bank ID for memory operations (default: "default"). + For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") + api_key: API key for Hindsight authentication. If not provided, + reads from HINDSIGHT_API_KEY environment variable. 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) @@ -47,7 +57,8 @@ class HindsightConfig: If False (default), storage runs in background thread for better performance. """ - hindsight_api_url: str = "http://localhost:8888" + hindsight_api_url: str = DEFAULT_HINDSIGHT_API_URL + bank_id: str = DEFAULT_BANK_ID api_key: Optional[str] = None store_conversations: bool = True inject_memories: bool = True @@ -102,8 +113,11 @@ _global_defaults: Optional[HindsightDefaults] = None def configure( - hindsight_api_url: str = "http://localhost:8888", + hindsight_api_url: Optional[str] = None, + bank_id: Optional[str] = None, api_key: Optional[str] = None, + background: Optional[str] = None, + bank_name: Optional[str] = None, store_conversations: bool = True, inject_memories: bool = True, injection_mode: MemoryInjectionMode = MemoryInjectionMode.SYSTEM_MESSAGE, @@ -116,9 +130,20 @@ def configure( This sets up settings that typically don't change during a session. For per-call settings like bank_id, use set_defaults() or per-call kwargs. + With sensible defaults, you can use minimal configuration: + + configure() # Just set HINDSIGHT_API_KEY env var + enable() + Args: hindsight_api_url: URL of the Hindsight API server - api_key: Optional API key for Hindsight authentication + (default: https://api.hindsight.vectorize.io) + bank_id: Memory bank ID for memory operations (default: "default"). + For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") + api_key: API key for Hindsight authentication. If not provided, + reads from HINDSIGHT_API_KEY environment variable. + background: Instructions guiding what Hindsight should learn and remember. + bank_name: Optional display name for the bank. store_conversations: Whether to store conversations to Hindsight inject_memories: Whether to inject relevant memories into prompts injection_mode: How to inject memories into the prompt @@ -132,20 +157,30 @@ def configure( The configured HindsightConfig instance Example: - >>> from hindsight_litellm import configure, set_defaults, enable + >>> from hindsight_litellm import configure, enable + >>> + >>> # Minimal usage - just set HINDSIGHT_API_KEY env var + >>> configure() + >>> enable() + >>> + >>> # Or with custom settings >>> configure( - ... hindsight_api_url="http://localhost:8888", - ... api_key="your-api-key", - ... verbose=True, + ... bank_id="user-123", # Per-user bank for multi-user support + ... background="Remember user preferences and past interactions.", ... ) - >>> set_defaults(bank_id="user-123") - >>> enable() # Start memory integration + >>> enable() """ global _global_config + # Apply defaults + resolved_api_url = hindsight_api_url or DEFAULT_HINDSIGHT_API_URL + resolved_bank_id = bank_id or DEFAULT_BANK_ID + resolved_api_key = api_key or os.environ.get(HINDSIGHT_API_KEY_ENV) + _global_config = HindsightConfig( - hindsight_api_url=hindsight_api_url, - api_key=api_key, + hindsight_api_url=resolved_api_url, + bank_id=resolved_bank_id, + api_key=resolved_api_key, store_conversations=store_conversations, inject_memories=inject_memories, injection_mode=injection_mode, @@ -154,6 +189,17 @@ def configure( sync_storage=sync_storage, ) + # If background or bank_name is provided, create/update the bank + if background or bank_name: + _create_or_update_bank( + hindsight_api_url=resolved_api_url, + bank_id=resolved_bank_id, + name=bank_name, + mission=background, + verbose=verbose, + api_key=resolved_api_key, + ) + return _global_config @@ -244,6 +290,7 @@ def _create_or_update_bank( 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. @@ -257,7 +304,7 @@ def _create_or_update_bank( try: from hindsight_client import Hindsight - client = Hindsight(hindsight_api_url) + client = Hindsight(base_url=hindsight_api_url, api_key=api_key) client.create_bank( bank_id=bank_id, name=name, @@ -305,8 +352,10 @@ 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 in defaults + 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 ( _global_config is not None and _global_defaults is not None diff --git a/hindsight-integrations/litellm/hindsight_litellm/wrappers.py b/hindsight-integrations/litellm/hindsight_litellm/wrappers.py index e0ef2502..3cdb63e6 100644 --- a/hindsight-integrations/litellm/hindsight_litellm/wrappers.py +++ b/hindsight-integrations/litellm/hindsight_litellm/wrappers.py @@ -8,10 +8,16 @@ integration with native client libraries. """ import logging +import os import threading from typing import Any, Dict, List, Optional, Union from dataclasses import dataclass +# 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" + from .config import get_config, get_defaults, is_configured, HindsightConfig @@ -22,7 +28,7 @@ _retain_errors_lock = threading.Lock() logger = logging.getLogger(__name__) -def _get_client(api_url: str): +def _get_client(api_url: str, api_key: Optional[str] = None): """Create a fresh Hindsight client for the given URL. Note: We create a fresh client each time because the hindsight_client @@ -30,7 +36,7 @@ def _get_client(api_url: str): calls causes asyncio context issues. """ from hindsight_client import Hindsight - return Hindsight(base_url=api_url, timeout=30.0) + return Hindsight(base_url=api_url, api_key=api_key, timeout=30.0) def _close_client(): @@ -145,7 +151,7 @@ def recall( client = None try: # Create fresh client for this operation - client = _get_client(api_url) + client = _get_client(api_url, config.api_key if config else None) # Call recall API results = client.recall( @@ -308,7 +314,7 @@ def reflect( client = None try: # Create fresh client for this operation - client = _get_client(api_url) + client = _get_client(api_url, config.api_key if config else None) # Call reflect API reflect_kwargs = { @@ -410,12 +416,13 @@ def _retain_sync( target_document_id: Optional[str], metadata: Optional[Dict[str, str]], verbose: bool, + api_key: Optional[str] = None, ) -> RetainResult: """Internal synchronous retain implementation.""" client = None try: # Create fresh client for this operation - client = _get_client(api_url) + client = _get_client(api_url, api_key) # Call retain API result = client.retain( @@ -467,6 +474,7 @@ def _retain_background( target_document_id: Optional[str], metadata: Optional[Dict[str, str]], verbose: bool, + api_key: Optional[str] = None, ) -> None: """Background thread worker for async retain.""" global _retain_errors @@ -479,6 +487,7 @@ def _retain_background( target_document_id=target_document_id, metadata=metadata, verbose=verbose, + api_key=api_key, ) except Exception as e: with _retain_errors_lock: @@ -571,6 +580,8 @@ def retain( "Hindsight not configured. Call configure() or provide bank_id and hindsight_api_url." ) + api_key = config.api_key if config else None + if sync: # Synchronous mode - block and return result return _retain_sync( @@ -581,6 +592,7 @@ def retain( target_document_id=target_document_id, metadata=metadata, verbose=verbose, + api_key=api_key, ) else: # Async mode - run in background thread @@ -594,6 +606,7 @@ def retain( target_document_id, metadata, verbose, + api_key, ), daemon=True, ) @@ -653,6 +666,7 @@ class HindsightOpenAI: client: Any, bank_id: str, hindsight_api_url: str = "http://localhost:8888", + api_key: Optional[str] = None, session_id: Optional[str] = None, store_conversations: bool = True, inject_memories: bool = True, @@ -667,6 +681,7 @@ class HindsightOpenAI: bank_id: Memory bank ID for memory operations. For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") hindsight_api_url: URL of the Hindsight API server + api_key: Optional API key for Hindsight authentication session_id: Session identifier for conversation grouping store_conversations: Whether to store conversations inject_memories: Whether to inject relevant memories @@ -677,6 +692,7 @@ class HindsightOpenAI: self._client = client self._bank_id = bank_id self._api_url = hindsight_api_url + self._api_key = api_key self._session_id = session_id self._store_conversations = store_conversations self._inject_memories = inject_memories @@ -694,6 +710,7 @@ class HindsightOpenAI: from hindsight_client import Hindsight self._hindsight_client = Hindsight( base_url=self._api_url, + api_key=self._api_key, timeout=30.0, ) return self._hindsight_client @@ -857,6 +874,7 @@ class HindsightAnthropic: client: Any, bank_id: str, hindsight_api_url: str = "http://localhost:8888", + api_key: Optional[str] = None, session_id: Optional[str] = None, store_conversations: bool = True, inject_memories: bool = True, @@ -871,6 +889,7 @@ class HindsightAnthropic: bank_id: Memory bank ID for memory operations. For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") hindsight_api_url: URL of the Hindsight API server + api_key: Optional API key for Hindsight authentication session_id: Session identifier for conversation grouping store_conversations: Whether to store conversations inject_memories: Whether to inject relevant memories @@ -881,6 +900,7 @@ class HindsightAnthropic: self._client = client self._bank_id = bank_id self._api_url = hindsight_api_url + self._api_key = api_key self._session_id = session_id self._store_conversations = store_conversations self._inject_memories = inject_memories @@ -898,6 +918,7 @@ class HindsightAnthropic: from hindsight_client import Hindsight self._hindsight_client = Hindsight( base_url=self._api_url, + api_key=self._api_key, timeout=30.0, ) return self._hindsight_client @@ -1031,8 +1052,9 @@ class _WrappedAnthropicMessages: def wrap_openai( client: Any, - bank_id: str, - hindsight_api_url: str = "http://localhost:8888", + bank_id: Optional[str] = None, + hindsight_api_url: Optional[str] = None, + api_key: Optional[str] = None, session_id: Optional[str] = None, store_conversations: bool = True, inject_memories: bool = True, @@ -1045,14 +1067,23 @@ def wrap_openai( This creates a wrapped client that automatically injects memories and stores conversations when making chat completion calls. + With sensible defaults, you can use it with minimal configuration: + + client = wrap_openai(OpenAI()) + + Just set the HINDSIGHT_API_KEY environment variable and you're ready to go. + Args: client: The OpenAI client instance to wrap - bank_id: Memory bank ID for memory operations. For multi-user support, + bank_id: Memory bank ID (default: "default"). For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") 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. session_id: Session identifier for conversation grouping - store_conversations: Whether to store conversations - inject_memories: Whether to inject relevant memories + store_conversations: Whether to store conversations (default: True) + inject_memories: Whether to inject relevant memories (default: True) max_memories: Maximum number of memories to inject (None = no limit) budget: Budget level for memory recall (low, mid, high) verbose: Enable verbose logging @@ -1064,21 +1095,24 @@ def wrap_openai( >>> from openai import OpenAI >>> from hindsight_litellm import wrap_openai >>> - >>> client = OpenAI() - >>> wrapped = wrap_openai( - ... client, - ... bank_id=f"user-{user_id}", # Multi-user support via separate banks - ... ) + >>> # Minimal usage - just set HINDSIGHT_API_KEY env var + >>> client = wrap_openai(OpenAI()) >>> - >>> response = wrapped.chat.completions.create( - ... model="gpt-4", + >>> response = client.chat.completions.create( + ... model="gpt-4o-mini", ... messages=[{"role": "user", "content": "What do you know about me?"}] ... ) """ + # Apply defaults + resolved_bank_id = bank_id or DEFAULT_BANK_ID + resolved_api_url = hindsight_api_url or DEFAULT_HINDSIGHT_API_URL + resolved_api_key = api_key or os.environ.get(HINDSIGHT_API_KEY_ENV) + return HindsightOpenAI( client=client, - bank_id=bank_id, - hindsight_api_url=hindsight_api_url, + bank_id=resolved_bank_id, + hindsight_api_url=resolved_api_url, + api_key=resolved_api_key, session_id=session_id, store_conversations=store_conversations, inject_memories=inject_memories, @@ -1090,8 +1124,9 @@ def wrap_openai( def wrap_anthropic( client: Any, - bank_id: str, - hindsight_api_url: str = "http://localhost:8888", + bank_id: Optional[str] = None, + hindsight_api_url: Optional[str] = None, + api_key: Optional[str] = None, session_id: Optional[str] = None, store_conversations: bool = True, inject_memories: bool = True, @@ -1104,14 +1139,23 @@ def wrap_anthropic( This creates a wrapped client that automatically injects memories and stores conversations when making message calls. + With sensible defaults, you can use it with minimal configuration: + + client = wrap_anthropic(Anthropic()) + + Just set the HINDSIGHT_API_KEY environment variable and you're ready to go. + Args: client: The Anthropic client instance to wrap - bank_id: Memory bank ID for memory operations. For multi-user support, + bank_id: Memory bank ID (default: "default"). For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") 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. session_id: Session identifier for conversation grouping - store_conversations: Whether to store conversations - inject_memories: Whether to inject relevant memories + store_conversations: Whether to store conversations (default: True) + inject_memories: Whether to inject relevant memories (default: True) max_memories: Maximum number of memories to inject (None = no limit) budget: Budget level for memory recall (low, mid, high) verbose: Enable verbose logging @@ -1123,22 +1167,25 @@ def wrap_anthropic( >>> from anthropic import Anthropic >>> from hindsight_litellm import wrap_anthropic >>> - >>> client = Anthropic() - >>> wrapped = wrap_anthropic( - ... client, - ... bank_id=f"user-{user_id}", # Multi-user support via separate banks - ... ) + >>> # Minimal usage - just set HINDSIGHT_API_KEY env var + >>> client = wrap_anthropic(Anthropic()) >>> - >>> response = wrapped.messages.create( - ... model="claude-3-5-sonnet-20241022", + >>> response = client.messages.create( + ... model="claude-sonnet-4-20250514", ... max_tokens=1024, ... messages=[{"role": "user", "content": "What do you know about me?"}] ... ) """ + # Apply defaults + resolved_bank_id = bank_id or DEFAULT_BANK_ID + resolved_api_url = hindsight_api_url or DEFAULT_HINDSIGHT_API_URL + resolved_api_key = api_key or os.environ.get(HINDSIGHT_API_KEY_ENV) + return HindsightAnthropic( client=client, - bank_id=bank_id, - hindsight_api_url=hindsight_api_url, + bank_id=resolved_bank_id, + hindsight_api_url=resolved_api_url, + api_key=resolved_api_key, session_id=session_id, store_conversations=store_conversations, inject_memories=inject_memories, diff --git a/hindsight-integrations/litellm/pyproject.toml b/hindsight-integrations/litellm/pyproject.toml index 6c95de70..3e87c0aa 100644 --- a/hindsight-integrations/litellm/pyproject.toml +++ b/hindsight-integrations/litellm/pyproject.toml @@ -61,3 +61,8 @@ packages = ["hindsight_litellm"] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] + +[dependency-groups] +dev = [ + "pytest>=9.0.2", +] diff --git a/hindsight-integrations/litellm/tests/test_config.py b/hindsight-integrations/litellm/tests/test_config.py new file mode 100644 index 00000000..38bf128e --- /dev/null +++ b/hindsight-integrations/litellm/tests/test_config.py @@ -0,0 +1,202 @@ +"""Unit tests for hindsight_litellm configuration and defaults.""" + +import os +from unittest.mock import MagicMock, patch + +import pytest + +from hindsight_litellm import configure, wrap_openai, wrap_anthropic, reset_config, is_configured +from hindsight_litellm.config import ( + DEFAULT_HINDSIGHT_API_URL, + DEFAULT_BANK_ID, + HINDSIGHT_API_KEY_ENV, + reset_config, + get_config, + is_configured, +) +from hindsight_litellm.wrappers import HindsightOpenAI, HindsightAnthropic + + +class TestDefaults: + """Test default configuration values.""" + + def test_default_api_url(self): + """Test default API URL is production.""" + assert DEFAULT_HINDSIGHT_API_URL == "https://api.hindsight.vectorize.io" + + def test_default_bank_id(self): + """Test default bank ID is 'default'.""" + assert DEFAULT_BANK_ID == "default" + + def test_env_var_name(self): + """Test environment variable name for API key.""" + assert HINDSIGHT_API_KEY_ENV == "HINDSIGHT_API_KEY" + + +class TestConfigure: + """Test configure() function.""" + + def setup_method(self): + """Reset config before each test.""" + reset_config() + + def teardown_method(self): + """Reset config after each test.""" + reset_config() + + def test_configure_with_no_arguments(self): + """Test configure() with no arguments uses defaults.""" + config = configure() + + assert config.hindsight_api_url == DEFAULT_HINDSIGHT_API_URL + assert config.bank_id == DEFAULT_BANK_ID + + def test_configure_reads_api_key_from_env(self): + """Test configure() reads API key from environment variable.""" + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "test-api-key-123"}): + config = configure() + + assert config.api_key == "test-api-key-123" + + def test_configure_explicit_api_key_overrides_env(self): + """Test explicit api_key parameter overrides environment variable.""" + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "env-key"}): + config = configure(api_key="explicit-key") + + assert config.api_key == "explicit-key" + + def test_configure_explicit_values_override_defaults(self): + """Test explicit values override defaults.""" + config = configure( + hindsight_api_url="http://custom-url:8888", + bank_id="custom-bank", + api_key="custom-key", + ) + + assert config.hindsight_api_url == "http://custom-url:8888" + assert config.bank_id == "custom-bank" + assert config.api_key == "custom-key" + + def test_is_configured_true_with_defaults(self): + """Test is_configured() returns True with default config.""" + configure() + assert is_configured() is True + + def test_is_configured_false_when_not_configured(self): + """Test is_configured() returns False when not configured.""" + reset_config() + assert is_configured() is False + + +class TestWrapOpenAI: + """Test wrap_openai() function.""" + + def setup_method(self): + """Reset config before each test.""" + reset_config() + + def teardown_method(self): + """Reset config after each test.""" + reset_config() + + def test_wrap_openai_with_only_client(self): + """Test wrap_openai() works with only the client argument.""" + mock_client = MagicMock() + + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "test-key"}): + wrapped = wrap_openai(mock_client) + + assert isinstance(wrapped, HindsightOpenAI) + assert wrapped._bank_id == DEFAULT_BANK_ID + assert wrapped._api_url == DEFAULT_HINDSIGHT_API_URL + assert wrapped._api_key == "test-key" + + def test_wrap_openai_uses_defaults(self): + """Test wrap_openai() uses default values.""" + mock_client = MagicMock() + + wrapped = wrap_openai(mock_client) + + assert wrapped._bank_id == DEFAULT_BANK_ID + assert wrapped._api_url == DEFAULT_HINDSIGHT_API_URL + + def test_wrap_openai_reads_api_key_from_env(self): + """Test wrap_openai() reads API key from environment.""" + mock_client = MagicMock() + + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "env-api-key"}): + wrapped = wrap_openai(mock_client) + + assert wrapped._api_key == "env-api-key" + + def test_wrap_openai_explicit_overrides_defaults(self): + """Test wrap_openai() explicit values override defaults.""" + mock_client = MagicMock() + + wrapped = wrap_openai( + mock_client, + bank_id="my-bank", + hindsight_api_url="http://localhost:9999", + api_key="my-key", + ) + + assert wrapped._bank_id == "my-bank" + assert wrapped._api_url == "http://localhost:9999" + assert wrapped._api_key == "my-key" + + +class TestWrapAnthropic: + """Test wrap_anthropic() function.""" + + def setup_method(self): + """Reset config before each test.""" + reset_config() + + def teardown_method(self): + """Reset config after each test.""" + reset_config() + + def test_wrap_anthropic_with_only_client(self): + """Test wrap_anthropic() works with only the client argument.""" + mock_client = MagicMock() + + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "test-key"}): + wrapped = wrap_anthropic(mock_client) + + assert isinstance(wrapped, HindsightAnthropic) + assert wrapped._bank_id == DEFAULT_BANK_ID + assert wrapped._api_url == DEFAULT_HINDSIGHT_API_URL + assert wrapped._api_key == "test-key" + + def test_wrap_anthropic_uses_defaults(self): + """Test wrap_anthropic() uses default values.""" + mock_client = MagicMock() + + wrapped = wrap_anthropic(mock_client) + + assert wrapped._bank_id == DEFAULT_BANK_ID + assert wrapped._api_url == DEFAULT_HINDSIGHT_API_URL + + def test_wrap_anthropic_reads_api_key_from_env(self): + """Test wrap_anthropic() reads API key from environment.""" + mock_client = MagicMock() + + with patch.dict(os.environ, {HINDSIGHT_API_KEY_ENV: "env-api-key"}): + wrapped = wrap_anthropic(mock_client) + + assert wrapped._api_key == "env-api-key" + + def test_wrap_anthropic_explicit_overrides_defaults(self): + """Test wrap_anthropic() explicit values override defaults.""" + mock_client = MagicMock() + + wrapped = wrap_anthropic( + mock_client, + bank_id="my-bank", + hindsight_api_url="http://localhost:9999", + api_key="my-key", + ) + + assert wrapped._bank_id == "my-bank" + assert wrapped._api_url == "http://localhost:9999" + assert wrapped._api_key == "my-key" diff --git a/hindsight-integrations/litellm/tests/test_integration.py b/hindsight-integrations/litellm/tests/test_integration.py index 71a5a181..058dced1 100644 --- a/hindsight-integrations/litellm/tests/test_integration.py +++ b/hindsight-integrations/litellm/tests/test_integration.py @@ -85,17 +85,22 @@ class TestConfiguration: assert defaults.fact_types == ["world", "opinion"] assert defaults.document_id == "doc-123" - def test_is_configured_without_bank_id(self): - """Test is_configured returns False without bank_id.""" - configure(hindsight_api_url="http://localhost:8888") - assert is_configured() is False + def test_is_configured_with_defaults(self): + """Test is_configured returns True with default bank_id.""" + configure() # Uses default bank_id="default" + assert is_configured() is True - def test_is_configured_with_bank_id(self): - """Test is_configured returns True with bank_id.""" + def test_is_configured_with_bank_id_in_defaults(self): + """Test is_configured returns True with bank_id in defaults.""" configure(hindsight_api_url="http://localhost:8888") set_defaults(bank_id="test-agent") assert is_configured() is True + def test_is_configured_with_explicit_bank_id(self): + """Test is_configured returns True with explicit bank_id.""" + configure(bank_id="test-agent") + assert is_configured() is True + def test_reset_config(self): """Test reset_config clears the configuration.""" configure(hindsight_api_url="http://localhost:8888") diff --git a/hindsight-integrations/litellm/uv.lock b/hindsight-integrations/litellm/uv.lock index 1ec20ae6..d21cdfcf 100644 --- a/hindsight-integrations/litellm/uv.lock +++ b/hindsight-integrations/litellm/uv.lock @@ -707,6 +707,11 @@ dev = [ { name = "pytest-mock" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.13.3" }, @@ -719,6 +724,9 @@ requires-dist = [ ] provides-extras = ["dev"] +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=9.0.2" }] + [[package]] name = "httpcore" version = "1.0.9"