TradingAgents/PR_MEMORY_IMPROVEMENTS.md

326 lines
9.3 KiB
Markdown

# Pull Request: Add Chunking and Persistent Storage to FinancialSituationMemory
## Overview
This PR adds two critical improvements to the `FinancialSituationMemory` class:
1. **Text Chunking for Long Inputs** - Handles texts exceeding embedding model limits using `RecursiveCharacterTextSplitter`
2. **Persistent ChromaDB Storage** - Enables disk-based storage for memory persistence across sessions
## Motivation
### Problem 1: Long Text Handling
The OpenAI embedding model `text-embedding-3-small` has a maximum context length of **8,192 tokens**. When financial analysis texts exceed this limit (e.g., comprehensive market analyses, long research reports), the embedding API fails with an error.
**Before:**
```python
# Would fail with texts > 24,000 characters (~8000 tokens)
embedding = get_embedding(very_long_market_analysis) # ❌ API Error
```
**After:**
```python
# Automatically chunks and processes long texts
embeddings = get_embedding(very_long_market_analysis) # ✅ Returns list of embeddings
```
### Problem 2: Memory Persistence
The original implementation used ChromaDB's in-memory client, meaning all memories were lost when the process ended. For production trading systems, persisting historical knowledge is essential.
**Before:**
```python
# In-memory only - lost on restart
memory = FinancialSituationMemory("trading", config)
```
**After:**
```python
# Persistent storage - survives restarts
memory = FinancialSituationMemory("trading", config, persistent_dir="./data/chromadb")
```
## Changes Made
### 1. Updated `requirements.txt`
Added `langchain` for `RecursiveCharacterTextSplitter`:
```diff
typing-extensions
+langchain
langchain-openai
langchain-experimental
```
### 2. Enhanced `FinancialSituationMemory.__init__()`
**Added Parameters:**
- `symbol` (optional): For symbol-specific collection naming
- `persistent_dir` (optional): Path for persistent storage
**Key Improvements:**
- Persistent ChromaDB client when `persistent_dir` is provided
- Backward-compatible in-memory client when not provided
- Symbol-based collection naming for multi-symbol support
- Collection name sanitization for ChromaDB compatibility
- Automatic directory creation for persistent storage
- Fallback error handling for ChromaDB compatibility issues
```python
def __init__(self, name, config, symbol=None, persistent_dir=None):
# ... (see full implementation in memory.py)
```
### 3. Reimplemented `get_embedding()`
**Returns Changed:**
- **Old:** Single embedding (float list)
- **New:** List of embeddings (list of float lists)
**Key Features:**
- Automatically detects long texts (> 24,000 characters)
- Uses `RecursiveCharacterTextSplitter` for intelligent chunking
- Chunks at natural boundaries (paragraphs, sentences, words)
- 500-character overlap to preserve context between chunks
- Robust error handling with per-chunk try-catch
- Returns single-item list for short texts (backward compatible)
**Algorithm:**
```
if text_length <= 24,000 chars:
return [single_embedding]
else:
1. Split text into ~23,000 char chunks with 500 char overlap
2. Get embedding for each chunk
3. Return list of all chunk embeddings
```
### 4. Updated `add_situations()`
**Changed to handle chunked embeddings:**
- Processes list of embeddings instead of single embedding
- Creates separate document for each chunk
- Associates full situation text with each chunk
- Maintains unique IDs for all chunks
**Benefit:** Even if query matches only one chunk of a long situation, the full situation is returned.
### 5. Enhanced `get_memories()`
**Added embedding averaging for multi-chunk queries:**
```python
if len(query_embeddings) > 1:
query_embedding = np.mean(query_embeddings, axis=0).tolist()
else:
query_embedding = query_embeddings[0]
```
**Benefit:** Long queries are represented by their average embedding, improving semantic search accuracy.
## Backward Compatibility
**Fully backward compatible** - existing code continues to work without changes:
```python
# Old usage - still works!
memory = FinancialSituationMemory("trading", config)
```
New features are opt-in via optional parameters:
```python
# New usage - persistent storage
memory = FinancialSituationMemory(
"trading",
config,
symbol="AAPL",
persistent_dir="./chromadb_storage"
)
```
## Testing
Created comprehensive test suite in `test_memory_chunking.py`:
1. **Short Text Backward Compatibility** - Verifies existing functionality
2. **Long Text Chunking** - Tests 24,000+ character texts
3. **Persistent Storage** - Verifies data survives process restart
4. **Symbol Collection Naming** - Tests multi-symbol support
**To run tests:**
```bash
cd TradingAgents
export OPENAI_API_KEY="your-key"
python test_memory_chunking.py
```
## Benefits
### For Users
1. **No More Embedding Errors** - Handles texts of any length
2. **Persistent Memory** - Trading insights survive restarts
3. **Better Organization** - Symbol-specific memory collections
4. **No Breaking Changes** - Existing code works as-is
### For Developers
1. **Cleaner API** - Consistent return type (list of embeddings)
2. **Better Error Handling** - Graceful fallbacks
3. **Extensible** - Easy to add more chunking strategies
4. **Well-Documented** - Clear docstrings and comments
## Performance Impact
### Memory Usage
- **In-memory mode:** Same as before
- **Persistent mode:** Minimal overhead (ChromaDB uses SQLite)
### Processing Time
- **Short texts (<24K chars):** No change
- **Long texts:** Linear increase with text length
- ~1-2 seconds per 24K chars chunk (API latency)
- Parallel processing possible (future optimization)
### Storage
- **Disk usage:** ~1KB per embedding (persistent mode)
- **Query speed:** Same as before (ChromaDB vector search)
## Migration Guide
### For Existing Users
**No changes required!** Your existing code continues to work:
```python
memory = FinancialSituationMemory("my_memory", config)
```
### For New Features
**Add persistent storage:**
```python
memory = FinancialSituationMemory(
"my_memory",
config,
persistent_dir="./chromadb_data"
)
```
**Add symbol-specific collections:**
```python
memory = FinancialSituationMemory(
"stock_analysis",
config,
symbol="AAPL",
persistent_dir="./chromadb_data"
)
```
## Example Usage
### Before (Old API)
```python
config = {"backend_url": "https://api.openai.com/v1"}
memory = FinancialSituationMemory("trading", config)
# Short text only
memory.add_situations([
("Tech stocks volatile", "Reduce exposure")
])
results = memory.get_memories("Tech volatility", n_matches=1)
```
### After (New API with Long Texts)
```python
config = {"backend_url": "https://api.openai.com/v1"}
memory = FinancialSituationMemory(
"trading",
config,
symbol="AAPL",
persistent_dir="./chromadb_storage"
)
# Long comprehensive analysis (would have failed before)
long_analysis = """
[5000+ words of detailed market analysis covering:
- Macroeconomic conditions
- Sector rotation trends
- Technical analysis
- Fundamental metrics
- Risk factors
... etc]
"""
memory.add_situations([
(long_analysis, "Maintain position with trailing stop")
])
# Works with long queries too
long_query = """[Another long market situation...]"""
results = memory.get_memories(long_query, n_matches=3)
```
## Files Changed
1. **requirements.txt** - Added `langchain` dependency
2. **tradingagents/agents/utils/memory.py** - Enhanced with chunking and persistence
3. **test_memory_chunking.py** (new) - Comprehensive test suite
## Code Quality
- **Type hints preserved** where applicable
- **Docstrings updated** with new behavior
- **Error handling** added for robustness
- **Comments added** for complex logic
- **Minimal code changes** for easy review
- **No breaking changes** to existing API
## Checklist
- [x] Code follows project style guidelines
- [x] Self-review completed
- [x] Comments added for complex areas
- [x] Documentation updated (this PR description)
- [x] Backward compatibility maintained
- [x] Tests added/updated
- [x] All tests pass locally
- [x] No breaking changes
- [x] Dependencies documented in requirements.txt
## Future Enhancements
Potential follow-ups (not in this PR):
1. Parallel chunk embedding for faster processing
2. Configurable chunk size and overlap
3. Alternative chunking strategies (semantic, sentence-based)
4. Embedding caching for repeated texts
5. Compression for large persistent collections
## Questions & Answers
**Q: Why return a list instead of single embedding?**
A: Consistency - both short and long texts now return lists. Makes API more predictable and easier to handle.
**Q: Why average embeddings for multi-chunk queries?**
A: Common approach in semantic search - represents overall meaning while avoiding bias toward any single chunk.
**Q: Is persistent_dir required?**
A: No - optional parameter. Defaults to in-memory storage for backward compatibility.
**Q: Can I migrate existing in-memory data to persistent storage?**
A: Not directly - would need to re-add situations with persistent_dir specified.
**Q: Performance impact?**
A: Negligible for short texts. Linear increase for long texts based on chunk count.
## Acknowledgments
This implementation is based on patterns from:
- BA2TradePlatform integration testing
- LangChain best practices for text chunking
- ChromaDB documentation for persistent storage
---
**Ready for Review**
Please let me know if you have any questions or suggestions!