# LangChain Integration Tests

This directory contains tests for custom LangChain components integration with Local Deep Research v1.0+.

## Overview

These tests ensure that users can integrate their own LangChain components (LLMs and retrievers) with LDR's research pipeline, enabling fully customized AI research workflows.

## Test Files

### 1. `test_custom_langchain_llm.py`
Tests for integrating custom LangChain LLMs:
- Basic custom LLM usage with research functions
- Custom LLM factory patterns
- Error handling for failing LLMs
- Streaming support
- Integration with real database sessions

### 2. `test_custom_langchain_retriever.py`
Tests for integrating custom LangChain retrievers:
- Single retriever as search engine
- Multiple retrievers for different domains
- Hybrid search (custom retriever + web search)
- Empty results handling
- Metadata preservation
- Integration with custom LLMs

### 3. `test_combined_llm_retriever.py`
Real-world scenarios combining custom LLMs and retrievers:
- Company knowledge base with custom LLM
- Multi-domain research
- Performance monitoring
- Fallback handling
- Factory patterns for component creation

## Running These Tests

```bash
# Run all LangChain integration tests
pytest tests/langchain_integration/

# Run specific test file
pytest tests/langchain_integration/test_custom_langchain_llm.py -v

# Run specific test
pytest tests/langchain_integration/test_custom_langchain_llm.py::TestCustomLangChainLLM::test_custom_llm_basic_usage -v

# Run with coverage
pytest tests/langchain_integration/ --cov=local_deep_research.api --cov=local_deep_research.config
```

## Example: Using Custom LangChain Components

### Custom LLM Example

```python
from langchain.llms.base import LLM
from local_deep_research.api.research_functions import quick_summary
from local_deep_research.settings import SettingsManager
from local_deep_research.database.session_context import get_user_db_session

class MyCustomLLM(LLM):
    @property
    def _llm_type(self) -> str:
        return "my_custom_llm"

    def _call(self, prompt: str, **kwargs) -> str:
        # Your custom LLM logic here
        return f"Custom response to: {prompt}"

# Use with LDR
with get_user_db_session(username="user", password="pass") as session:
    settings_manager = SettingsManager(session)
    settings_snapshot = settings_manager.get_all_settings()

    custom_llm = MyCustomLLM()
    result = quick_summary(
        query="Your research query",
        llm=custom_llm,
        settings_snapshot=settings_snapshot
    )
```

### Custom Retriever Example

```python
from langchain.schema import BaseRetriever, Document
from local_deep_research.api.research_functions import quick_summary

class MyKnowledgeBase(BaseRetriever):
    def _get_relevant_documents(self, query: str, **kwargs) -> List[Document]:
        # Your retrieval logic here
        return [
            Document(
                page_content="Relevant content",
                metadata={"source": "my_kb", "id": "1"}
            )
        ]

# Use with LDR
with get_user_db_session(username="user", password="pass") as session:
    settings_manager = SettingsManager(session)
    settings_snapshot = settings_manager.get_all_settings()

    retriever = MyKnowledgeBase()
    result = quick_summary(
        query="Your research query",
        retrievers={"my_kb": retriever},
        search_tool="my_kb",
        settings_snapshot=settings_snapshot
    )
```

## Test Patterns

### 1. Settings Snapshot Pattern
All tests use the settings snapshot pattern for thread safety:
```python
@pytest.fixture
def settings_snapshot(self):
    return {
        "llm.provider": {"value": "custom", "type": "str"},
        "llm.model": {"value": "test_model", "type": "str"},
        # ... other settings
    }
```

### 2. Mock LLM Responses
Tests mock LLM responses for deterministic results:
```python
mock_llm.invoke.return_value = Mock(content="Expected response")
mock_llm.batch.return_value = [
    Mock(content="Question 1"),
    Mock(content="Question 2")
]
```

### 3. Factory Pattern Testing
Tests verify factory functions work correctly:
```python
def create_custom_llm(settings_snapshot):
    api_key = settings_snapshot.get("llm.custom.api_key", {}).get("value")
    return CustomLLM(api_key=api_key)
```

## Common Test Scenarios

### 1. Basic Integration
- Custom component works with LDR research functions
- Settings are properly passed through
- Results contain expected structure

### 2. Error Handling
- Component failures are handled gracefully
- Clear error messages are provided
- System doesn't crash on component errors

### 3. Advanced Features
- Streaming support for LLMs
- Metadata preservation for retrievers
- Performance monitoring capabilities

### 4. Real-World Usage
- Multi-domain knowledge bases
- Company-specific LLMs
- Hybrid search scenarios

## Debugging Tips

1. **Component Not Called**: Verify the search_tool parameter matches your retriever key
2. **Settings Not Available**: Ensure settings_snapshot is passed to all functions
3. **Import Errors**: Check that LangChain is installed: `pip install langchain`
4. **Mock Issues**: Use patch decorators in the correct order (outermost first)

## Adding New Tests

When adding tests for new LangChain features:

1. Create appropriate fixtures for settings and mocks
2. Test both success and failure paths
3. Verify integration with LDR's research pipeline
4. Include examples in docstrings
5. Update this README with new patterns
