Blog

Reddit-Threads mit Sprach- und Vision-Modellen analysieren

LLMRedditZusammenfassungOpenRouter

Verbringen Sie viel Zeit auf Reddit? Ich tue es (Nein, nicht mit Memes). Ich nutze Reddit als Lernwerkzeug, und glauben Sie es oder nicht, einige Subreddits sind aufschlussreicher als viele kostenpflichtige Kurse. Nach ein paar Monaten des Herumlungerns in verschiedenen Subreddits bemerkte ich jedoch, dass ich viel Zeit damit verbrachte, lange Threads zu lesen und oft zu tief verschachtelten Kommentaren in den Blättern des Kommentarbaums zu scrollen. Es fühlte sich ineffizient an. Also fragte ich mich: Kann ich etwas dagegen tun?

Ich habe Reddit genutzt, um über LLMs zu lernen. Also dachte ich, warum nicht das Spiel umdrehen und LLMs nutzen, um von Reddit zu lernen? Angetrieben von einem plötzlichen Motivationsschub kam ich auf einen Plan, ein Tool zu bauen, das theoretisch jeden Tag wertvolle Zeit sparen könnte! Ich werde Sie durch den Code in diesem Beitrag führen. Das GitHub-Repository ist hier verfügbar, und Sie können die Live-Demo hier ausprobieren.

Hinweis: Die Demo-Site wechselt manchmal in den Ruhemodus, wenn sie längere Zeit nicht verwendet wurde. Ein kleiner Preis, den ich zahlen musste, um sie kostenlos auf Streamlit Community Cloud zu hosten. Wenn Sie aufgefordert werden, sie aufzuwecken, tun Sie dies und warten Sie etwa 1 Minute, bis alles geladen ist.

Die 7 Nützlichen Tricks

Obwohl dies ein kleines Projekt ist, ist die Codebasis umfangreich genug, um einen Blog-Post mit vollständiger Code-Durchgängung zu rechtfertigen. Stattdessen werde ich mich auf 7 nützliche Tricks konzentrieren, die ich während der Entwicklung entdeckt oder verwendet habe. Übrigens enthält der Frontend-Ordner im Stammverzeichnis den Streamlit-Code, aber da dieser Beitrag nicht über Streamlit handelt, überspringe ich diesen Teil und tauche direkt in die Reddit- und LLM-bezogenen Komponenten ein.

  1. Einheitliche LLM-API-Aufruffunktion für alle Modellanfragen (einschließlich visueller Modelle)

Trotz verschiedener API-Aufrufe an verschiedene Modelle, von denen einige Bilder beinhalten, handhaben wir sie alle über eine einzige Funktion namens async_chat_completion in der Datei llm_interact.py. Durch die Isolierung dieser Logik in einer eigenen Datei und die Trennung von der Hauptprojektlogik (wie Reddit-Handling) wird der Code viel sauberer und einfacher zu verwalten.

llm_interact.py

async def async_chat_completion(
    chat_history: List[Dict[str, str]],
    temperature: float = 0.9,
    is_image: bool = False
) -> str:
    """
    Asynchrone Chat-Vervollständigungsfunktion mit OpenAI-kompatibler API.
    """
    base_url = os.getenv("LLM_BASE_URL").rstrip('/')
    api_key = os.getenv("LLM_API_KEY").rstrip('/')
    model = os.getenv("VLM_NAME" if is_image else "MODEL_NAME")
    
    # Erstelle den asynchronen OpenAI-Client
    client = AsyncOpenAI(
        api_key=api_key,
        base_url=base_url,
    )
    
    # Bereite Request-Parameter vor
    request_params = {
        "model": model,
        "messages": chat_history.copy(),
        "temperature": temperature,
    }

Das Schöne an diesem Ansatz ist seine Flexibilität. Egal ob wir GPT-4, Claude oder sogar ein Vision-Modell aufrufen, die Schnittstelle bleibt konsistent. Der is_image-Parameter weist die Funktion einfach an, ein anderes Modell aus unseren Umgebungsvariablen zu verwenden.

  1. Umgang mit Reddit-API-Ratenbegrenzungen mit exponentieller Backoff-Strategie

Reddit's API kann temperamentvoll sein, besonders wenn Sie zu schnell zu viele Anfragen stellen. Anstatt Ihren Code zum Absturz zu bringen, implementiert das Projekt eine elegante Retry-Logik:

reddit_utils.py

async def fetch_with_retry(url: str, headers: dict, max_retries: int = 3) -> dict:
    """
    Führt HTTP-Anfrage mit exponentieller Backoff-Retry-Logik aus.
    """
    for attempt in range(max_retries):
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(url, headers=headers) as response:
                    if response.status == 200:
                        return await response.json()
                    elif response.status == 429:  # Rate Limited
                        wait_time = (2 ** attempt) + random.uniform(0, 1)
                        await asyncio.sleep(wait_time)
                        continue
                    else:
                        response.raise_for_status()
        except Exception as e:
            if attempt == max_retries - 1:
                raise e
            await asyncio.sleep(2 ** attempt)
    
    raise Exception("Max retries exceeded")

Diese Retry-Logik folgt bewährten Praktiken: Sie wartet exponentiell länger zwischen Versuchen und fügt etwas Zufälligkeit hinzu, um den "Thundering Herd"-Effekt zu vermeiden.

  1. Rekursive Kommentar-Thread-Parsing für verschachtelte Diskussionen

Reddit-Kommentare sind wie russische Puppen - Kommentare in Kommentaren in Kommentaren. Hier ist, wie wir sie alle erfassen:

def parse_comment_tree(comment_data: dict, depth: int = 0, max_depth: int = 10) -> List[dict]:
    """
    Parsed Reddit-Kommentare rekursiv in eine flache Liste.
    """
    comments = []
    
    if depth > max_depth:
        return comments
    
    # Verarbeite aktuellen Kommentar
    if 'body' in comment_data.get('data', {}):
        comment_info = {
            'body': comment_data['data']['body'],
            'author': comment_data['data'].get('author', '[deleted]'),
            'score': comment_data['data'].get('score', 0),
            'depth': depth
        }
        comments.append(comment_info)
    
    # Verarbeite Antworten rekursiv
    replies = comment_data.get('data', {}).get('replies', {})
    if isinstance(replies, dict) and 'data' in replies:
        children = replies['data'].get('children', [])
        for child in children:
            if child.get('kind') == 't1':  # Kommentar-Typ
                comments.extend(parse_comment_tree(child, depth + 1, max_depth))
    
    return comments

Die max_depth-Begrenzung verhindert, dass wir uns in absurd tiefen Thread-Löchern verlieren - niemand braucht eine 50-Level-tiefe Diskussion über Pineapple auf Pizza.

  1. Intelligente Inhaltsfilterung und Bereinigung

Reddit kann chaotisch sein. Gelöschte Kommentare, Bot-Posts, reine Link-Drops - nicht alles ist es wert, analysiert zu werden:

def clean_and_filter_content(comments: List[dict], min_score: int = 1) -> List[dict]:
    """
    Filtert und bereinigt Reddit-Kommentare für die LLM-Analyse.
    """
    cleaned_comments = []
    
    for comment in comments:
        body = comment.get('body', '').strip()
        
        # Filter unwanted content
        if (body in ['[deleted]', '[removed]', ''] or
            len(body) < 10 or
            comment.get('score', 0) < min_score or
            body.startswith('http') and len(body.split()) < 3):
            continue
        
        # Entferne Reddit-spezifische Markdown
        body = re.sub(r'/u/\w+', '[User]', body)  # Anonymize usernames
        body = re.sub(r'/r/\w+', '[Subreddit]', body)  # Generalize subreddit references
        body = re.sub(r'&gt;', '>', body)  # Fix HTML entities
        
        comment['body'] = body
        cleaned_comments.append(comment)
    
    return cleaned_comments

Diese Bereinigung sorgt dafür, dass das LLM hochwertige, relevante Inhalte zum Arbeiten bekommt, anstatt durch Spam und Low-Effort-Posts zu waten.

  1. Kontextbewusste Prompt-Gestaltung für verschiedene Analysestile

Verschiedene Threads erfordern verschiedene Analyseansätze. Ein technisches How-to-Thread unterscheidet sich stark von einer hitzigen politischen Debatte:

def build_analysis_prompt(thread_title: str, comments: List[dict], tone: str = "neutral") -> str:
    """
    Erstellt kontextbewusste Prompts basierend auf Thread-Inhalt und gewünschtem Ton.
    """
    
    tone_instructions = {
        "neutral": "Bieten Sie eine ausgewogene, sachliche Analyse.",
        "casual": "Schreiben Sie in einem entspannten, konversationellen Stil.",
        "technical": "Konzentrieren Sie sich auf technische Details und Implementierung.",
        "humorous": "Fügen Sie Humor hinzu, bleiben Sie aber respektvoll und informativ."
    }
    
    # Analysiere Thread-Typ basierend auf Titel und Top-Kommentaren
    thread_context = analyze_thread_context(thread_title, comments[:5])
    
    prompt = f"""
    Analysieren Sie diese Reddit-Diskussion mit dem Titel: "{thread_title}"
    
    Thread-Kontext: {thread_context}
    Stil: {tone_instructions.get(tone, tone_instructions["neutral"])}
    
    Kommentare:
    {format_comments_for_analysis(comments)}
    
    Bieten Sie:
    1. Eine prägnante Zusammenfassung (2-3 Sätze)
    2. Wichtige Erkenntnisse oder Punkte
    3. Bemerkenswerte Meinungsverschiedenheiten oder Konsens
    4. Handlungsorientierte Takeaways (falls zutreffend)
    """
    
    return prompt

Diese Kontextbewusstheit macht die Analysen viel nützlicher. Ein Gamedev-Thread bekommt technische Erkenntnisse, während ein Lifestyle-Thread praktische Tipps erhält.

  1. Asynchrone Verarbeitung für bessere Performance

Wenn Sie mit mehreren API-Aufrufen und Netzwerkanfragen arbeiten, kann Synchronität Ihr Feind sein:

async def process_multiple_threads(thread_urls: List[str]) -> List[dict]:
    """
    Verarbeitet mehrere Reddit-Threads gleichzeitig.
    """
    
    async def process_single_thread(url: str) -> dict:
        try:
            # Hole Thread-Daten
            thread_data = await fetch_reddit_thread(url)
            
            # Verarbeite Kommentare
            comments = parse_comment_tree(thread_data)
            cleaned_comments = clean_and_filter_content(comments)
            
            # Generiere Analyse
            analysis = await async_chat_completion(
                build_analysis_prompt(thread_data['title'], cleaned_comments)
            )
            
            return {
                'url': url,
                'title': thread_data['title'],
                'analysis': analysis,
                'comment_count': len(cleaned_comments)
            }
        except Exception as e:
            return {'url': url, 'error': str(e)}
    
    # Verarbeite alle Threads gleichzeitig
    tasks = [process_single_thread(url) for url in thread_urls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    return [r for r in results if not isinstance(r, Exception)]

Diese asynchrone Architektur macht die App viel reaktionsfähiger. Anstatt darauf zu warten, dass ein Thread nach dem anderen verarbeitet wird, kann sie mehrere gleichzeitig bearbeiten.

  1. Elegante Fehlerbehandlung mit benutzerfreundlichen Fallbacks

Dinge gehen schief - APIs fallen aus, Netzwerke haben Schluckauf, Reddit ändert seine Struktur. Anstatt die App zum Absturz zu bringen, fallen Sie elegant zurück:

class RedditAnalysisError(Exception):
    """Benutzerdefinierte Exception für Reddit-Analysefehler."""
    pass

async def safe_analyze_thread(url: str) -> dict:
    """
    Analysiert einen Reddit-Thread mit umfassender Fehlerbehandlung.
    """
    try:
        # Validiere URL-Format
        if not is_valid_reddit_url(url):
            return {
                'error': 'Ungültiges Reddit-URL-Format',
                'suggestion': 'Stellen Sie sicher, dass die URL ein vollständiger Reddit-Thread-Link ist'
            }
        
        # Versuche Thread-Analyse
        result = await analyze_reddit_thread(url)
        
        if not result.get('comments'):
            return {
                'error': 'Keine analysierbaren Kommentare gefunden',
                'suggestion': 'Dieser Thread könnte zu neu sein oder keine öffentlichen Kommentare haben'
            }
        
        return result
        
    except aiohttp.ClientError:
        return {
            'error': 'Netzwerkproblem beim Zugriff auf Reddit',
            'suggestion': 'Überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut'
        }
    except RedditAnalysisError as e:
        return {
            'error': f'Reddit-Analysefehler: {str(e)}',
            'suggestion': 'Versuchen Sie einen anderen Thread oder versuchen Sie es später erneut'
        }
    except Exception as e:
        logger.error(f"Unerwarteter Fehler bei der Thread-Analyse: {str(e)}")
        return {
            'error': 'Unerwarteter Fehler aufgetreten',
            'suggestion': 'Bitte versuchen Sie es später erneut oder kontaktieren Sie den Support'
        }

Diese Fehlerbehandlung verwandelt kryptische Exceptions in hilfreiche Nachrichten, die Benutzer tatsächlich verstehen und darauf reagieren können.

Den Thread zusammenziehen

Dieses Projekt zeigt, wie man moderne AI-Tools nutzen kann, um ein reales Problem zu lösen - Informationsüberlastung bei der Verarbeitung langer Diskussionen. Durch die Kombination von Web-Scraping, natürlicher Sprachverarbeitung und durchdachtem UX-Design entsteht ein Tool, das tatsächlich Zeit spart.

Die wichtigsten Erkenntnisse? Halten Sie Ihre API-Schichten sauber, handhaben Sie Fehler elegant, und vergessen Sie nie, dass am anderen Ende ein Mensch sitzt, der nur versucht, Informationen schneller zu bekommen. Manchmal ist das beste AI-Tool dasjenige, das einfach aus dem Weg geht und seine Arbeit macht.

Haben Sie Fragen zum Code oder Ideen für Verbesserungen? Das Repository ist open source - Beiträge sind willkommen!