Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

conversation.py

20_multi_turn_conversations/conversation.py  ·  156 lines  ·  view on GitHub

"""
Multi-Turn Conversations & Long Context
Interview question: "How to design multi-turn conversations?"
"""
import numpy as np

class ConversationManager:
    """
    Manages multi-turn conversation history
    """
    
    def __init__(self, max_history: int = 10):
        self.max_history = max_history
        self.conversation_history = []
    
    def add_message(self, role: str, content: str):
        """
        Add message to conversation
        
        Args:
            role: 'user' or 'assistant'
            content: Message content
        """
        self.conversation_history.append({
            'role': role,
            'content': content
        })
        
        # Keep only recent history
        if len(self.conversation_history) > self.max_history * 2:
            self.conversation_history = self.conversation_history[-self.max_history * 2:]
    
    def get_context(self) -> str:
        """Format conversation for model input"""
        context = ""
        for msg in self.conversation_history:
            context += f"{msg['role']}: {msg['content']}\n"
        return context
    
    def clear(self):
        """Clear conversation history"""
        self.conversation_history = []


class LongContextHandler:
    """
    Handle long context (millions of tokens)
    Strategies: Chunking, RAG, Summarization
    """
    
    def chunk_text(self, text: str, chunk_size: int = 1000) -> list:
        """Split text into chunks"""
        return [text[i:i+chunk_size] 
                for i in range(0, len(text), chunk_size)]
    
    def retrieval_based(self, query: str, documents: list, 
                       top_k: int = 5) -> list:
        """
        RAG: Retrieve relevant chunks
        
        For millions of tokens:
        1. Split into chunks
        2. Embed chunks
        3. Retrieve top-k relevant
        4. Use only retrieved for generation
        """
        # Simplified: Just return top-k by length similarity
        # In practice, use embeddings and cosine similarity
        doc_scores = []
        for doc in documents:
            # Simple scoring (in practice, use embeddings)
            score = len(set(query.split()) & set(doc.split()))
            doc_scores.append((score, doc))
        
        doc_scores.sort(reverse=True)
        return [doc for _, doc in doc_scores[:top_k]]
    
    def summarize_context(self, text: str, max_length: int = 500) -> str:
        """
        Summarize long context
        
        Keep only important parts
        """
        # Simplified: Just truncate
        # In practice, use summarization model
        if len(text) <= max_length:
            return text
        
        # Keep beginning and end
        half = max_length // 2
        return text[:half] + "...[truncated]..." + text[-half:]


def how_to_increase_context_length():
    """
    Techniques to handle millions of tokens:
    
    1. RAG (Retrieval-Augmented Generation)
       - Store chunks in vector DB
       - Retrieve only relevant chunks
       - Can handle unlimited tokens
    
    2. Summarization
       - Summarize old context
       - Keep summaries + recent
       - Compress history
    
    3. Hierarchical Processing
       - Process at multiple levels
       - Coarse summaries, then details
    
    4. Sparse Attention
       - Only attend to important parts
       - Longformer, BigBird attention
    
    5. External Memory
       - Store context externally
       - Retrieve when needed
    """
    strategies = [
        "RAG: Retrieve only relevant chunks",
        "Summarization: Compress old context",
        "Hierarchical: Multi-level processing",
        "Sparse Attention: Efficient attention",
        "External Memory: Store externally"
    ]
    
    print("Strategies for Long Context (Millions of Tokens):")
    for i, strategy in enumerate(strategies, 1):
        print(f"  {i}. {strategy}")


# Usage Example
if __name__ == "__main__":
    print("Multi-Turn Conversations & Long Context")
    print("=" * 60)
    
    # Multi-turn conversation
    conv = ConversationManager(max_history=5)
    conv.add_message("user", "Hello, what's the weather?")
    conv.add_message("assistant", "I don't have access to weather data.")
    conv.add_message("user", "Can you help me with Python?")
    
    print("Conversation Context:")
    print(conv.get_context())
    print()
    
    # Long context handling
    long_text = "This is a very long text. " * 1000
    handler = LongContextHandler()
    
    chunks = handler.chunk_text(long_text, chunk_size=100)
    print(f"Long text split into {len(chunks)} chunks")
    
    how_to_increase_context_length()