""" Integration test for Hindsight server with context manager. Tests the full workflow: 1. Starting server using context manager 2. Creating a memory bank 3. Storing memories (retain) 4. Recalling memories 5. Reflecting on memories Note: These tests use embedded PostgreSQL (pg0) with a shared server instance across all tests. Each test uses random bank_ids to avoid conflicts, allowing safe parallel execution. """ import os import uuid import pytest from hindsight import HindsightServer, HindsightClient @pytest.fixture(scope="session") def llm_config(): """Get LLM configuration from environment (session-scoped).""" provider = os.getenv("HINDSIGHT_LLM_PROVIDER", "groq") api_key = os.getenv("HINDSIGHT_LLM_API_KEY", "") model = os.getenv("HINDSIGHT_LLM_MODEL", "openai/gpt-oss-120b") # vertexai uses GCP service account credentials (HINDSIGHT_API_LLM_VERTEXAI_*), # not a traditional API key providers_without_api_key = ("vertexai", "ollama") if not api_key and provider not in providers_without_api_key: raise Exception("LLM API key not configured. Set HINDSIGHT_LLM_API_KEY environment variable.") return { "llm_provider": provider, "llm_api_key": api_key, "llm_model": model, } @pytest.fixture(scope="session") def shared_server(llm_config): """ Shared server instance for all tests (session-scoped). This allows tests to run in parallel by sharing the same pg0 instance, while using different bank_ids to avoid data conflicts. """ server = HindsightServer(db_url="pg0", **llm_config) server.start() yield server server.stop() @pytest.fixture def client(shared_server): """Create a client connected to the shared server.""" return HindsightClient(base_url=shared_server.url) def test_server_context_manager_basic_workflow(client): """ Test complete workflow using shared server. This test: 1. Uses a shared server instance 2. Creates a memory bank with unique ID 3. Stores multiple memories 4. Recalls memories based on a query 5. Reflects (generates contextual answers) based on stored memories """ # Use random bank_id to allow parallel test execution bank_id = f"test_assistant_{uuid.uuid4().hex[:8]}" # Step 1: Create a memory bank with background information print(f"\n1. Creating memory bank: {bank_id}") bank_response = client.create_bank( bank_id=bank_id, name="Test Assistant", mission="An AI assistant that helps with programming and data analysis tasks." ) assert bank_response.bank_id == bank_id # Step 2: Store some memories about user preferences print("\n2. Storing memories...") # Store first memory retain_response1 = client.retain( bank_id=bank_id, content="User prefers Python over JavaScript for data analysis projects.", context="User conversation about programming languages" ) assert retain_response1.success is True # Store second memory retain_response2 = client.retain( bank_id=bank_id, content="User is working on a machine learning project using scikit-learn.", context="Discussion about ML frameworks" ) assert retain_response2.success is True # Store third memory retain_response3 = client.retain( bank_id=bank_id, content="User likes visualizing data with matplotlib and seaborn.", context="Conversation about data visualization" ) assert retain_response3.success is True # Store batch memories batch_response = client.retain_batch( bank_id=bank_id, items=[ {"content": "User is interested in neural networks and deep learning."}, {"content": "User asked about best practices for training models."}, ] ) # Check if the batch was submitted successfully (items_count shows how many were submitted) assert batch_response.items_count >= 2 # Step 3: Recall memories based on a query print("\n3. Recalling memories about programming preferences...") recall_results = client.recall( bank_id=bank_id, query="What programming languages and tools does the user prefer?", max_tokens=4096 ) # Verify recall results assert isinstance(recall_results.results, list) assert len(recall_results.results) > 0 print(f" Found {len(recall_results.results)} relevant memories") # Check that results have expected structure for result in recall_results.results: print(f" - {result.text[:100]}") # Step 4: Recall memories about machine learning print("\n4. Recalling memories about machine learning...") ml_recall_results = client.recall( bank_id=bank_id, query="machine learning and neural networks", max_tokens=4096 ) # Verify recall results assert isinstance(ml_recall_results.results, list) assert len(ml_recall_results.results) > 0 print(f" Found {len(ml_recall_results.results)} ML-related memories") for result in ml_recall_results.results[:3]: # Show first 3 print(f" - {result.text[:100]}") # Step 5: Reflect (generate contextual answer based on memories) print("\n5. Reflecting on query about recommendations...") reflect_response = client.reflect( bank_id=bank_id, query="What tools and libraries should I recommend for this user's data analysis work?", budget="mid" ) # Verify reflection response answer = reflect_response.text assert len(answer) > 0 print(f" Answer: {answer[:200]}...") # Verify the answer mentions relevant tools/libraries answer_lower = answer.lower() assert any(term in answer_lower for term in ["python", "scikit-learn", "matplotlib", "seaborn", "data"]) # Step 6: Another reflection with different context print("\n6. Reflecting with additional context...") reflect_with_context = client.reflect( bank_id=bank_id, query="Should I use TensorFlow or PyTorch?", budget="low", context="The user is starting a new deep learning project" ) context_answer = reflect_with_context.text assert len(context_answer) > 0 print(f" Context-aware answer: {context_answer[:150]}...") def test_server_manual_start_stop(client): """ Test basic operations on shared server. Verifies that basic bank operations work correctly. """ # Use random bank_id to allow parallel test execution bank_id = f"test_manual_{uuid.uuid4().hex[:8]}" # Create bank bank_response = client.create_bank( bank_id=bank_id, name="Manual Test" ) assert bank_response.bank_id == bank_id # Store a memory retain_response = client.retain( bank_id=bank_id, content="Testing manual server lifecycle." ) assert retain_response.success is True # Recall the memory recall_results = client.recall( bank_id=bank_id, query="server testing" ) assert len(recall_results.results) >= 0 # May or may not find results immediately def test_server_with_client_context_manager(client): """ Test client context manager with shared server. """ # Use random bank_id to allow parallel test execution bank_id = f"test_nested_context_{uuid.uuid4().hex[:8]}" # Use client context manager (client fixture already provides this) # Create bank client.create_bank(bank_id=bank_id, name="Nested Context Test") # Store memory response = client.retain( bank_id=bank_id, content="Testing nested context managers." ) assert response.success is True # Verify we can recall results = client.recall(bank_id=bank_id, query="context") assert isinstance(results.results, list) def test_list_banks(client, shared_server): """ Test listing banks to verify bank_id field mapping. This test verifies that the list_banks endpoint correctly returns bank_id (not agent_id) in the response. """ # Create a couple of banks with random IDs to allow parallel test execution test_suffix = uuid.uuid4().hex[:8] bank1_id = f"test_bank_1_{test_suffix}" bank2_id = f"test_bank_2_{test_suffix}" client.create_bank(bank_id=bank1_id, name="Test Bank 1", mission="First test bank") client.create_bank(bank_id=bank2_id, name="Test Bank 2", mission="Second test bank") # List all banks using the namespace API response = client.banks.list() # Verify response structure assert hasattr(response, 'banks'), "Response should have 'banks' attribute" assert len(response.banks) >= 2, f"Should have at least 2 banks, got {len(response.banks)}" # Verify each bank has bank_id (not agent_id) for bank in response.banks: assert hasattr(bank, 'bank_id'), f"Bank should have 'bank_id' attribute" assert bank.bank_id is not None, "Bank ID should not be None" # Find our test banks bank_ids = [b.bank_id if hasattr(b, 'bank_id') else b['bank_id'] for b in response.banks] assert bank1_id in bank_ids, f"Should find {bank1_id} in bank list" assert bank2_id in bank_ids, f"Should find {bank2_id} in bank list" print(f"✓ Successfully listed {len(response.banks)} banks with correct bank_id field")