Direkt zum Inhalt

DeepSeek-V3.2-Speciale Tutorial: Erstell einen Datenanalysten-Agenten

Lerne, wie du mit DeepSeek V3.2 Speciale und Streamlit jede CSV-Datei in einen interaktiven Analyse-Assistenten verwandeln kannst.
Aktualisierte 5. Dez. 2025  · 11 Min. Lesezeit

DeepSeek V3.2 Speciale ist ein hochleistungsfähiges Schlussfolgerungsmodell, das für komplexe, durch Tools unterstützte Arbeitsabläufe optimiert ist. Anstatt Fragen einfach direkt zu beantworten, ist es so gemacht, dass man Tools für den Aufbau von agentenbasierten Analysesystemen planen, begründen und koordinieren kann.

In diesem Tutorial nutzen wir DeepSeek V3.2 Speciale, um einen KI-Datenanalysten-Agenten zu erstellen, der auf jeder CSV-Datei läuft. Der Ablauf sieht so aus:

  1. Der Nutzer lädt eine CSV-Datei hoch und stellt eine Frage in natürlicher Sprache. 
  2. Ein Planer-Agent macht aus der Frage einen strukturierten JSON-Analyseplan.
  3. Dann machen ein paar Python-Tools den Plan fertig und erstellen Tabellen/Diagramme.
  4. Endlich ein Erklärer die reinen Zahlen in eine Zusammenfassung mit Erkenntnissen.

Am Ende hast du eine Streamlit-App mit klaren Plänen und reproduzierbaren Analyseschritten. Wenn du mehr über KI-Agenten erfahren möchtest, empfehle ich dir, dir den Skill Track „Grundlagen von KI-Agenten” anzuschauen.  

Was ist DeepSeek V3.2 Speciale?

DeepSeek V3.2 Speciale ist die Variante der DeepSeek V3.2-Familie mit hoher Rechenleistung und verstärktem Reinforcement Learning, die speziell für tiefgreifende Schlussfolgerungen und nicht für zwanglose Unterhaltungen entwickelt wurde. 

Es bringt die Leistung so weit, dass es bei komplizierten Aufgaben mit Modellen wie Gemini 3 Pro mithalten kann, und hat sogar Goldmedaillen bei Benchmarks wie IMO, CMO, ICPC World Finals und IOI 2025 geholt.

DeepSeek V3.2 Speciale ist optimiert für:

  • Schwierige Matheaufgaben, Logik und mehrstufige Argumentation
  • Es unterstützt auch agentenbasierte Workflows, bei denen das Modell Aktionen plant, anstatt sofort zu antworten.
  • Genau wie seine Konkurrenten unterstützt es auch strukturierte Ausgaben mit langem Kontext und Argumentationen im Stil einer Gedankenkette.

V3.2 Speciale unterstützt nur den Denkmodus und kann im Moment keine Tools über die offizielle API aufrufen. Du kannst nicht einfach die Datei „ temperature “ anpassen und erwarten, dass du beim ersten Versuch sauberes JSON bekommst. Das Speciale-Modell wird lange nachdenken und seine Überlegungen oft mit der endgültigen Antwort vermischen.

DeepSeek-V3.2-Speciale Beispielprojekt: KI-Datenanalyst

In diesem Abschnitt machen wir einen KI-Datenanalysten-Agenten mit einem DeepSeek V3.2 Speciale-Modell, das in eine Streamlit-App eingebettet ist. Auf hoher Ebene macht die fertige App Folgendes:

  • Lade eine beliebige CSV-Datei hoch. Auf Kaggle findest du coole Beispiele (wie die Umsatzprognose, die ich in dieser Demo benutzt habe).
  • Die App erkennt Datums-Spalten automatisch und fügt Hilfsfelder für Jahr, Monat und Jahr_Monat hinzu.
  • Du gibst eine Frage ein, und DeepSeek V3.2 Speciale macht als Planer-Agent mit einem JSON-Plan weiter:
    • Welche Spalten sollen gruppiert werden?
    • Welche Metriken sollen zusammengefasst werden?
    • Optionale Filter (z. B. „Jahr == 2019“)
    • Ob man ein Balkendiagramm, ein Liniendiagramm oder ein Kreisdiagramm zeichnet
  • Ein reiner Python-Datenarbeiter macht den Plan mit Pandas klar.
  • Ein Diagrammgenerator erstellt mit matplotlib und seaborn Balken-, Linien- und Kreisdiagramme.
  • Ein Explainer Agent fasst das Ergebnis in einfachen Worten zusammen und hebt die wichtigsten Erkenntnisse hervor.

DeepSeek v3.2 Speciale Final Demo-Ausgabe

Lass es uns Schritt für Schritt aufbauen.

Schritt 1: Was du brauchen solltest

Zuerst installierst du die wichtigsten Abhängigkeiten und holst dir einen API-Schlüssel von der API-Plattform von DeepSeek:

pip install streamlit pandas matplotlib seaborn python-dotenv openai

Wir werden ein paar Kernbibliotheken wie Streamlit für die interaktive Web-App-Benutzeroberfläche, Pandas zum Laden und Bearbeiten von CSV-Daten sowie Matplotlib und Seaborn zum Erstellen von Diagrammen und Visualisierungen nutzen. Die Bibliothek python-dotenv wird benutzt, um Umgebungsvariablen aus einer .env-Datei zu laden, und OpenAI als Client für die Kommunikation mit der OpenAI-kompatiblen API von DeepSeek.

Als Nächstes logg dich bei der DeepSeek-API-Plattform ein, geh zum Tab „API-Schlüssel” und klick auf „Neuen API-Schlüssel erstellen”.

API-Schlüssel

Gib deinem Schlüssel zum Schluss einen Namen und kopiere den generierten Schlüssel. 

Erstell einen neuen API-Schlüssel

Leg deinen DeepSeek-API-Schlüssel als Umgebungsvariable fest:

export DEEPSEEK_API_KEY="your_api_key_here"

Jetzt ist unsere Umgebung fertig.

Schritt 2: Importe und DeepSeek-Client

Nachdem die Abhängigkeiten installiert sind, müssen wir sie als Nächstes in unser Skript einbinden und den DeepSeek-Client anschließen, der unsere Planer- und Erklärer-Agenten antreibt.

import io
import json
import os
import re
from datetime import datetime
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import streamlit as st
from dotenv import load_dotenv
from openai import OpenAI
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
load_dotenv()
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
if not DEEPSEEK_API_KEY:
    raise RuntimeError("Please set DEEPSEEK_API_KEY env variable.")
DEEPSEEK_BASE_URL = os.getenv(
    "DEEPSEEK_BASE_URL",
    "https://api.deepseek.com/v3.2_speciale_expires_on_20251215",
)
client = OpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url=DEEPSEEK_BASE_URL,
)

Der obige Code-Block macht drei Sachen. Es importiert unsere Kernbibliotheken, wendet einen Plotstil an und lädt Umgebungsvariablen von .env. Dann liest es „ DEEPSEEK_API_KEY “, überprüft es und baut einen OpenAI-Client, der auf den temporären V3.2 Speciale-Endpunkt von DeepSeek zeigt, damit jeder nachfolgende LLM-Aufruf über das richtige Modell läuft. 

Hey, DeepSeek V3.2 Speciale läuft gerade über einen temporären Endpunkt, der am 15. Dezember 2025 abläuft. Danach kann sich die Bereitstellungs-URL ändern, aber der Modellname bleibt deepseek-reasoner. Die neuesten Endpunkte und Nutzungsdetails findest du in der offiziellen DeepSeek-Dokumentation.

Nachdem der Client eingerichtet ist, legen wir unsere beiden Agenten fest – einen für die Planung der Analyse und einen für die Erklärung der Ergebnisse.

Schritt 3: Planer Agent 

Sobald der Client eingerichtet ist, können wir den Planner Agent erstellen, der entscheidet, welche Analysen mit den Daten durchgeführt werden sollen. Anstatt das Modell direkt auf das DataFrame zugreifen zu lassen, bitten wir es, einen strukturierten JSON-Plan zu erstellen, den unser Python-„Datenarbeiter“ sicher ausführen kann. 

Der Planer sieht eine Textbeschreibung des Datensatzes zusammen mit der Frage des Benutzers und gibt ein JSON-Objekt zurück, das die Operation, Gruppierungsspalten, Filter, Metrik und den Diagrammtyp beschreibt.

def call_planner_llm(schema_text: str, question: str) -> tuple[dict, str]:
    system_prompt = """
    You are a data analysis planner. Convert user questions into JSON plans.
    JSON Structure (REQUIRED):
    {
      "operation": "group_by_summary",
      "group_by": ["column_name"],
      "filters": [],
      "target_column": "column_to_analyze",
      "metric": "sum",
      "need_chart": true,
      "chart_type": "bar"
    }
    Fields:
    - operation: Always use "group_by_summary" (most flexible)
    - group_by: Columns to group by (e.g., ["Product Name"] or ["Category", "Region"])
    - filters: Optional filters, e.g., [{"column": "Year", "op": "==", "value": "2023"}]
    - target_column: Column to aggregate (must exist in schema!)
    - metric: One of: sum, mean, count, max, min
    - need_chart: true (always show chart)
    - chart_type: bar, line, or pie
    Quick Rules:
    - Use ONLY columns from the provided schema
    - For "this year", look for year columns or use the latest year in data
    - For "top N", use appropriate group_by and metric
    - For comparisons, use filters to split groups 
    Think briefly, then output the complete JSON.
    """.strip()
    messages = [
        {"role": "system", "content": system_prompt},
        {
            "role": "user",
            "content": f"Dataset:\n{schema_text}\n\nQuestion:\n{question}\n\nReminder: After analyzing, output the complete JSON plan.",
        },
    ]
    resp = client.chat.completions.create(
        model="deepseek-reasoner",
        messages=messages,
        max_tokens=8192,
    )
    message = resp.choices[0].message
    content = message.content or ""
    content = content.strip() 
    reasoning_content = getattr(message, 'reasoning_content', None) or ""
    finish_reason = resp.choices[0].finish_reason
    def extract_json_from_text(text: str) -> dict | None:
        if not text:
            return None
        try:
            return json.loads(text)
        except json.JSONDecodeError:
            pass   
        code_block_match = re.search(r'```(?:json)?\s*(\{[\s\S]*?\})\s*```', text, re.DOTALL)
        if code_block_match:
            try:
                return json.loads(code_block_match.group(1))
            except json.JSONDecodeError:
                pass
        def find_all_json_objects(s):
            objects = []
            depth = 0
            start_idx = None
            in_string = False
            escape_next = False            
            for i, char in enumerate(s):
                if escape_next:
                    escape_next = False
                    continue                
                if char == '\\':
                    escape_next = True
                    continue                
                if char == '"' and not in_string:
                    in_string = True
                elif char == '"' and in_string:
                    in_string = False
                elif not in_string:
                    if char == '{':
                        if depth == 0:
                            start_idx = i
                        depth += 1
                    elif char == '}':
                        depth -= 1
                        if depth == 0 and start_idx is not None:
                            objects.append(s[start_idx:i+1])
                            start_idx = None            
            return objects 
        for candidate in find_all_json_objects(text):
            try:
                parsed = json.loads(candidate)
                if isinstance(parsed, dict) and any(k in parsed for k in ['operation', 'target_column', 'metric', 'group_by']):
                    return parsed
            except json.JSONDecodeError:
                continue
        for i in range(len(text)):
            if text[i] == '{':
                decoder = json.JSONDecoder()
                try:
                    parsed, _ = decoder.raw_decode(text[i:])
                    if isinstance(parsed, dict) and any(k in parsed for k in ['operation', 'target_column', 'metric', 'group_by']):
                        return parsed
                except json.JSONDecodeError:
                    continue        
        return None
    if reasoning_content:
        plan = extract_json_from_text(reasoning_content)
        if plan is not None:
            return plan, reasoning_content
    plan = extract_json_from_text(content)
    if plan is not None:
        return plan, reasoning_content
    error_msg = (
        f"Planner returned non-JSON content.\n"
        f"Finish reason: {finish_reason}\n"
        f"Content received: {repr(content[:500]) if content else '(empty)'}\n"
        f"Reasoning content length: {len(reasoning_content) if reasoning_content else 0}\n"
        f"Reasoning content sample: {reasoning_content[:500] if reasoning_content else '(none)'}"
    )
    if finish_reason == "length":
        error_msg += "\n\nNote: Response was truncated due to token limit. The model didn't finish generating the JSON."   
    raise ValueError(error_msg)

Zuerst definiert der „ system_prompt ” den Vertrag für den Planner Agent, der die Benutzerfrage in einen JSON-Plan umwandelt, und legt genau fest, welche Felder wir erwarten, wie zum Beispiel eine Liste von „ group_by ”-Spalten, optionale „ filters ”, eine „ target_column ”, eine „ metric ” und eine „ chart_type ”. 

Wir packen auch ein paar Heuristiken rein (wie zum Beispiel, wie man mit „dieses Jahr“ oder „Top N“ umgeht), damit sich das Modell eher wie ein junger Datenanalyst als wie ein allgemeiner Chatbot verhält.

Als Nächstes erstellen wir die Systemmeldung, die die Anweisungen enthält, und packen zwei Sachen rein: eine Textbeschreibung des Datensatzschemas (Spaltennamen, Datentypen, Beispielzeilen) und die Frage des Benutzers. Dadurch hat das Modell genug Kontext, um passende Spaltennamen zu finden und eine sinnvolle Aggregation für die gestellte Frage zu wählen.

DeepSeek V3.2 Speciale wird dann über client.chat.completions.create() aufgerufen. Da es sich um ein Argumentationsmodell handelt, kann es zwei Arten von Ergebnissen liefern: einen sichtbaren Inhaltsstrang und einen internen reasoning_content Strang, in dem oft das meiste „Denken” und die endgültige Antwort stecken. Wir holen beides aus der Antwort raus und behalten die finish_reason im Auge, damit wir Kürzungen erkennen können.

Die Hauptarbeit läuft in der Funktion „ extract_json_from_text() “ ab. Weil Argumentationsmodelle nicht immer sauberes JSON liefern, packen wir es in ein Markdown. Um den Planer stabil zu machen, probiert diese Hilfsfunktion mehrere Strategien nacheinander aus:

  • Es versucht zuerst, die ganze Zeichenfolge mit json.loads
  • Dann sucht es nach JSON-Code-Fences und analysiert den Inhalt darin.
  • Schließlich sucht es mit einer kleinen Routine zum Abgleichen von Klammern nach ausgeglichenen Blöcken und nutzt einen rohen JSON-Decoder.

Für jeden Kandidaten werden nur Objekte akzeptiert, die wie ein gültiger Plan aussehen, d. h. sie müssen mindestens eines der folgenden Felder enthalten: operation, target_column, metric oder group_by

Zum Schluss verbinden wir alles miteinander, indem wir die Funktion „ extract_json_from_text() “ auf „ reasoning_content “ anwenden.

Zusammen machen diese Teile aus jeder Frage in natürlicher Sprache und jedem Schema-Snapshot einen strukturierten Analyseplan. 

Schritt 4: Erklärer Agent

Sobald der Datenverarbeiter die Analyse mit Pandas durchgeführt hat, schicken wir die Ergebnis-Tabelle zusammen mit dem JSON-Plan zurück an DeepSeek und machen daraus eine Zusammenfassung mit konkreten Erkenntnissen.

def call_explainer_llm(question: str, plan: dict, result_summary: list) -> tuple[str, str]:
    system_prompt = """
    You are a senior data analyst explaining insights to business stakeholders.
    The user asked a question about their CSV data.
    Another agent already designed an analysis plan and we executed it in Python.
    You will now explain the results in clear, engaging, non-technical language.
    Requirements:
    - Start with a direct 1-2 sentence answer to their question
    - Use bullet points (3-5 points) to highlight key insights, trends, or comparisons
    - Include specific numbers and percentages where relevant
    - Use business-friendly language (avoid technical jargon like "aggregation", "groupby")
    - If the data shows interesting patterns, point them out
    - End with a brief recommendation or next step if appropriate    
    Keep it concise and actionable.
    """.strip()
    limited_summary = result_summary[:20] if len(result_summary) > 20 else result_summary
    user_content = f"""
    User question:
    {question}
    Analysis performed:
    {json.dumps(plan, indent=2)}
    Results (top rows):
    {json.dumps(limited_summary, indent=2)}    
    Total rows in result: {len(result_summary)}
    """.strip()
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_content},
    ]
    resp = client.chat.completions.create(
        model="deepseek-reasoner",
        messages=messages,
        max_tokens=2048,
    )   
    message = resp.choices[0].message
    content = message.content or ""
    content = content.strip()
    reasoning_content = getattr(message, 'reasoning_content', None) or ""    
    if not content:
        if reasoning_content:
            content = reasoning_content.strip()  
    return (content if content else "Unable to generate explanation from the model response.", 
            reasoning_content)

Der Explainer-Agent fängt damit an, den „ system_prompt “ einzustellen, der DeepSeek sagt, dass es sich wie ein erfahrener Datenanalyst verhalten soll, der mit den Leuten aus dem Unternehmen redet. Wir sagen dir genau, wie die Antwort aussehen soll, und geben dir noch ein paar Tipps, wie du sie am besten formulierst.

Als Nächstes machen wir uns an „ user_content “, das dem Modell den ganzen Kontext gibt, den es braucht. Wir übergeben die ursprüngliche Frage, den JSON-Analyseplan (damit er weiß, welche Berechnungen durchgeführt wurden) und die Gesamtzahl der Zeilen.

Dann stellen wir die Nachrichtenliste zusammen und rufen DeepSeek V3.2 Speciale über client.chat.completions.create() auf. Genau wie im Planer kann das Modell Text sowohl in „ content “ als auch in „ reasoning_content “ zurückgeben. Deshalb fügen wir beide hinzu und greifen auf „ reasoning_content “ zurück, wenn „ content “ leer ist. 

Am Ende gibt die Funktion ein Tupel zurück, das sowohl die Erklärung als auch die Rohdaten-Begründungszeichenfolge enthält, die im Expander angezeigt wird.

Mit diesem Explainer Agent ist der Kreis geschlossen:

  • Der Planner Agent entscheidet, was berechnet wird,
  • Der Datenarbeiter macht den Plan mit Pandas und Matplotlib und
  • Der Explainer Agent macht aus den Zahlen nützliche Infos.

Schritt 5: Hilfsfunktionen

Bevor wir DeepSeek irgendwas planen lassen können, müssen wir die Daten klar beschreiben und sie für die Analyse aufbereiten. Das wird mit ein paar Hilfsfunktionen gemacht.

Schritt 5.1 Schemaplaner

Der Planner Agent kann dein DataFrame nicht direkt sehen, deshalb schicken wir ihm eine kompakte Textzusammenfassung des Datensatzes.

def get_schema_description(df: pd.DataFrame, max_rows: int = 3) -> str:
    schema_lines = ["Columns:"]
    for col, dtype in df.dtypes.items():
        if dtype == 'object' and df[col].nunique() < 20:
            unique_vals = df[col].dropna().unique()[:5]
            schema_lines.append(f"- {col} ({dtype}) - sample values: {', '.join(map(str, unique_vals))}")
        else:
            schema_lines.append(f"- {col} ({dtype})")
    sample = df.head(max_rows).to_markdown(index=False)
    return "\n".join(schema_lines) + "\n\nSample rows:\n" + sample

Dieser Helfer durchläuft alle Spalten. Es gibt auch ein paar Beispielwerte für Sachen wie Produktnamen, Regionen oder Kategorien. F

Schließlich fügt es eine kurze Markdown-Tabelle mit den ersten paar Zeilen hinzu, damit das Modell konkrete Beispiele zum Nachdenken hat.

Diese Schema-Zeichenkette ist die Sicht des Planers auf deine Daten, die es ihm ermöglicht, gültige Spaltennamen und Gruppierungen auszuwählen.

Schritt 5.2 Automatische Datenvorverarbeitung

Wir erkennen automatisch Datums-Spalten und fügen Hilfsfelder wie „ _year “ und „ _year_month “ hinzu, was es DeepSeek einfacher macht, Fragen wie „letztes Jahr“ oder „nach Monat“ zu beantworten.

def preprocess_dates(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    date_columns = []    
    for col in df.columns:
        if pd.api.types.is_datetime64_any_dtype(df[col]):
            date_columns.append(col)
            continue
        if any(x in col.lower() for x in ['date', 'time', 'day']):
            try:
                df[col] = pd.to_datetime(df[col], errors='coerce')
                if df[col].notna().sum() > 0: 
                    date_columns.append(col)
            except:
                pass
    for col in date_columns:
        if col in df.columns and pd.api.types.is_datetime64_any_dtype(df[col]):
            df[f'{col}_year'] = df[col].dt.year
            df[f'{col}_month'] = df[col].dt.month
            df[f'{col}_year_month'] = df[col].dt.to_period('M').astype(str) 
    return df

Zuerst checken wir die Spalten und wenn eine Spalte schon ein Datums- oder Zeitwert ist oder ihr Name auf ein Datum hindeutet (wie „ order_date “ oder „ day “), versuchen wir, sie mit „ pd.to_datetime “ zu analysieren. Für jede Datumsspalte erstellen wir Merkmale wie „ _year “, „ _month “ und „ _year_month “.

Durch diese Vorverarbeitung kann der Planer sicher auf die Spalten „ year “ und „ year_month “ zugreifen, ohne dass du für jeden Datensatz manuell Zeitmerkmale erstellen musst.

Schritt 5.3 Filter anwenden

Der Planer kann einfache Zeilenfilter zum Plan hinzufügen, und dieser Helfer macht daraus Pandas-Filter.

def apply_filters(df: pd.DataFrame, filters: list) -> pd.DataFrame:
    if not filters:
        return df
    filtered = df.copy()
    for f in filters:
        col = f.get("column")
        op = f.get("op")
        val = f.get("value")
        if col not in filtered.columns:
            continue
        try:
            val_cast = pd.to_numeric(val)
        except Exception:
            val_cast = val
        if op == "==":
            filtered = filtered[filtered[col] == val_cast]
        elif op == "!=":
            filtered = filtered[filtered[col] != val_cast]
        elif op == ">":
            filtered = filtered[filtered[col] > val_cast]
        elif op == "<":
            filtered = filtered[filtered[col] < val_cast]
        elif op == ">=":
            filtered = filtered[filtered[col] >= val_cast]
        elif op == "<=":
            filtered = filtered[filtered[col] <= val_cast]
        elif op == "contains":
            filtered = filtered[filtered[col].astype(str).str.contains(str(val_cast), case=False, na=False)]
    return filtered

Für jeden Filter checken wir, ob die Spalte da ist, und wandeln den Wert, wenn möglich, in eine Zahl um. Dann machen wir den passenden Vergleich. Alle Filter werden nacheinander angewendet, sodass du Bedingungen wie „Region enthält West“ und „Jahr >= 2021“ ganz einfach kombinieren kannst.

Diese Funktion verbindet die Beschränkungen der natürlichen Sprache aus dem Plan mit den Zeilenuntergruppen.

Schritt 5.4: Analyseplan 

Jetzt machen wir das Data Worker der eine dünne Schicht ist, die den JSON-Plan liest und die entsprechenden Pandas-Aggregationen ausführt.

def run_analysis_plan(df: pd.DataFrame, plan: dict) -> pd.DataFrame:
    op = plan.get("operation")
    target_col = plan.get("target_column")
    group_by = plan.get("group_by") or []
    filters = plan.get("filters") or []
    metric = plan.get("metric", "sum")
    if filters:
        df = apply_filters(df, filters)
    if not op or not target_col or target_col not in df.columns:
        return df.head(20)
    if op in ("aggregate_compare", "group_by_summary", "filter_then_aggregate"):
        if not group_by:
            if metric == "sum":
                value = df[target_col].sum()
            elif metric == "mean":
                value = df[target_col].mean()
            elif metric == "count":
                value = df[target_col].count()
            elif metric == "max":
                value = df[target_col].max()
            elif metric == "min":
                value = df[target_col].min()
            else:
                raise ValueError(f"Unsupported metric: {metric}")
            return pd.DataFrame({f"{metric}_{target_col}": [value]})
        if metric == "sum":
            agg_df = df.groupby(group_by)[target_col].sum().reset_index()
        elif metric == "mean":
            agg_df = df.groupby(group_by)[target_col].mean().reset_index()
        elif metric == "count":
            agg_df = df.groupby(group_by)[target_col].count().reset_index()
        elif metric == "max":
            agg_df = df.groupby(group_by)[target_col].max().reset_index()
        elif metric == "min":
            agg_df = df.groupby(group_by)[target_col].min().reset_index()
        else:
            raise ValueError(f"Unsupported metric: {metric}")
        agg_df = agg_df.sort_values(by=target_col, ascending=False)       
        return agg_df
    return df.head(20)

Zuerst schauen wir uns den Plan mit den Parametern „Operation“, „Zielspalte“, „Gruppierungsspalten“, „Filter“ und „Metrik“ genauer an. T

Dann werden Filter über die Funktion „ apply_filters() “ angewendet. Wenn der Plan auf eine ungültige Spalte verweist, zeigen wir stattdessen einfach eine einfache Vorschau an. 

Ansonsten machen wir bei den unterstützten Operationen entweder eine Aggregation über die ganze Tabelle oder wir führen eine Gruppierung durch, gefolgt von Operationen wie Summe, Mittelwert, Anzahl, Maximalwert oder Minimalwert, und sortieren das Ergebnis nach der Metrikspalte.

Das ist das Herzstück der Tool-Ebene: Das LLM entscheidet, was berechnet werden soll, und diese Funktion entscheidet, wie das mit Pandas sicher gemacht wird.

Schritt 5.5: Diagrammgenerator 

Zum Schluss machen wir aus der Tabelle ein Diagramm, das zum Plan „ chart_type “ passt, sodass wir sowohl Zahlen als auch Bilder in der Ausgabe haben.

def generate_chart(df: pd.DataFrame, plan: dict):
    if not plan.get("need_chart") or df.empty:
        return None
    chart_type = plan.get("chart_type", "bar")
    group_by = plan.get("group_by") or []
    target_col = plan.get("target_column")
    if not target_col or target_col not in df.columns:
        return None
    fig, ax = plt.subplots(figsize=(10, 6))    
    if group_by and len(group_by) > 0:
        x_col = group_by[0]
        if x_col not in df.columns:
            return None
        plot_df = df.head(15)        
        if chart_type == "bar":
            bars = ax.bar(range(len(plot_df)), plot_df[target_col], color=sns.color_palette("husl", len(plot_df)))
            ax.set_xticks(range(len(plot_df)))
            ax.set_xticklabels(plot_df[x_col], rotation=45, ha='right')
            ax.set_ylabel(target_col, fontsize=12)
            ax.set_xlabel(x_col, fontsize=12)
            for i, (bar, val) in enumerate(zip(bars, plot_df[target_col])):
                height = bar.get_height()
                ax.text(bar.get_x() + bar.get_width()/2., height,
                       f'{val:,.0f}' if abs(val) > 100 else f'{val:.2f}',
                       ha='center', va='bottom', fontsize=9)                 
        elif chart_type == "line":
            ax.plot(range(len(plot_df)), plot_df[target_col], marker='o', linewidth=2, markersize=8)
            ax.set_xticks(range(len(plot_df)))
            ax.set_xticklabels(plot_df[x_col], rotation=45, ha='right')
            ax.set_ylabel(target_col, fontsize=12)
            ax.set_xlabel(x_col, fontsize=12)
            ax.grid(True, alpha=0.3)    
        elif chart_type == "pie" and len(plot_df) <= 10:
            ax.pie(plot_df[target_col], labels=plot_df[x_col], autopct='%1.1f%%', startangle=90)
            ax.axis('equal')
        else:
            bars = ax.bar(range(len(plot_df)), plot_df[target_col])
            ax.set_xticks(range(len(plot_df)))
            ax.set_xticklabels(plot_df[x_col], rotation=45, ha='right')
        title = f"{target_col} by {x_col}"
    else:
        ax.bar([target_col], [df[target_col].iloc[0]])
        title = f"{target_col}"
    ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
    fig.tight_layout()
    buf = io.BytesIO()
    fig.savefig(buf, format="png", dpi=150, bbox_inches='tight')
    buf.seek(0)
    plt.close(fig)
    return buf

Wir checken die Seiten chart_type, group_by und target_column aus dem Plan und erstellen eine Matplotlib-Abbildung. Die Funktion „ generate_chart() “ oben gibt einen PNG-Puffer im Speicher zurück, den die Streamlit-App neben den wichtigsten Kennzahlen anzeigt.

Mit diesen Hilfsfunktionen wird der Rest des Systems viel einfacher. Der Planer nutzt „ get_schema_description “, um die Analyse zu gestalten, der Datenverarbeiter nutzt die Funktionen „ preprocess_dates() “, „ apply_filters() “ und „ run_analysis_plan() “, um die Zahlen zu berechnen, und die Funktion „ generate_chart() “ macht aus der endgültigen Tabelle ein Diagramm.

Schritt 6: Streamlit-Benutzeroberfläche

Jetzt, wo wir die Funktionen für Planung, Datenverarbeitung, Erläuterung und Unterstützung haben, können wir alles in eine Streamlit-App packen. 

st.set_page_config(
    page_title="AI Data Analyst Agent",
    page_icon="📊",
    layout="wide",
    initial_sidebar_state="expanded"
)
st.markdown("""
<style>
    .main-header {
        
        font-weight: 700;
        
        
        text-align: center;
    }
    .sub-header {
        
        
        
        text-align: center;
    }
    .metric-card {
        background-
        
        border-radius: 0.5rem;
        border-left: 4px solid #1f77b4;
    }
    .insight-box {
        background-
        
        border-radius: 0.5rem;
        border-left: 4px solid #2ecc71;
        
    }
</style>
""", unsafe_allow_html=True)
st.markdown("<h1 class='main-header'>AI Data Analyst Agent</h1>", unsafe_allow_html=True)
uploaded_file = st.file_uploader("Upload CSV file", type=["csv"], help="Upload any CSV file to get started")
if uploaded_file is not None:
    try:
        df = pd.read_csv(uploaded_file)
        df = preprocess_dates(df)
    except Exception as e:
        st.error(f"Error loading CSV: {e}")
        st.stop()
    with st.expander("Preview Data", expanded=False):
        col1, col2, col3 = st.columns(3)
        col1.metric("Total Rows", f"{len(df):,}")
        col2.metric("Total Columns", len(df.columns))
        col3.metric("Memory Usage", f"{df.memory_usage(deep=True).sum() / 1024**2:.1f} MB")  
        st.dataframe(df.head(100), width='stretch', height=300)
        st.markdown("**Numeric Columns Summary:**")
        numeric_cols = df.select_dtypes(include=['number']).columns
        if len(numeric_cols) > 0:
            st.dataframe(df[numeric_cols].describe(), width='stretch')
    st.markdown("---")
    col1, col2 = st.columns([4, 1])
    with col1:
        question = st.text_input(
            " Ask a question about your data",
            placeholder="e.g., Which product category had the highest sales last year?",
            label_visibility="collapsed"
        )
    with col2:
        analyze_button = st.button("Analyze", type="primary", width='stretch')
    if analyze_button and question.strip():
        with st.spinner("Analyzing data..."):
            schema_text = get_schema_description(df)
            try:
                plan, planner_reasoning = call_planner_llm(schema_text, question)
            except Exception as e:
                st.error(f" Planner agent failed: {e}")
                st.stop()
            with st.expander(" Analysis Plan", expanded=False):
                col1, col2 = st.columns(2)
                with col1:
                    st.json(plan)
                with col2:
                    if planner_reasoning:
                        st.markdown("**AI Reasoning Process:**")
                        st.text_area("Planner Reasoning", planner_reasoning[:1000] + "..." if len(planner_reasoning) > 1000 else planner_reasoning, 
                                   height=200, label_visibility="collapsed")
            try:
                result_df = run_analysis_plan(df, plan)
            except Exception as e:
                st.error(f" Error running analysis: {e}")
                st.info(" Tip: Try rephrasing your question or be more specific about the columns you want to analyze.")
                st.stop()
        st.markdown("---")
        st.markdown("## Results")
        chart_buf = generate_chart(result_df, plan) 
        if chart_buf is not None:
            col1, col2 = st.columns([2, 1])
            with col1:
                st.image(chart_buf)
            with col2:
                st.markdown("### Key Metrics")
                target_col = plan.get("target_column")
                if target_col in result_df.columns:
                    st.metric("Total", f"{result_df[target_col].sum():,.2f}")
                    st.metric("Average", f"{result_df[target_col].mean():,.2f}")
                    st.metric("Max", f"{result_df[target_col].max():,.2f}")
                    st.metric("Min", f"{result_df[target_col].min():,.2f}")
        else:
            st.info("Chart generation skipped (not applicable for this query)")
        with st.expander("Results Table", expanded=True):
            st.dataframe(result_df, width='stretch', height=400)
            csv_data = result_df.to_csv(index=False)
            st.download_button(
                label="Download Results as CSV",
                data=csv_data,
                file_name=f"analysis_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
                mime="text/csv"
            )
        with st.spinner("Generating insights..."):
            result_df_serializable = result_df.copy()
            for col in result_df_serializable.columns:
                if pd.api.types.is_datetime64_any_dtype(result_df_serializable[col]):
                    result_df_serializable[col] = result_df_serializable[col].astype(str)
                elif pd.api.types.is_period_dtype(result_df_serializable[col]):
                    result_df_serializable[col] = result_df_serializable[col].astype(str)
            result_summary = result_df_serializable.to_dict(orient="records")
            explanation, explainer_reasoning = call_explainer_llm(
                question=question,
                plan=plan,
                result_summary=result_summary,
            )
        st.markdown("---")
        st.markdown("## AI Insights")
        st.markdown(f'<div class="insight-box">{explanation}</div>', unsafe_allow_html=True)
        if explainer_reasoning:
            with st.expander(" AI Reasoning Process", expanded=False):
                st.text_area("Explainer Reasoning", explainer_reasoning[:1500] + "..." if len(explainer_reasoning) > 1500 else explainer_reasoning,
                           height=300, label_visibility="collapsed")

st.set_page_config Oben richten wir die Seite mit HTML und CSS ein, um das Dashboard zu gestalten. Der Benutzer lädt erst mal eine CSV-Datei hoch, die wir dann in einen DataFrame laden, durch „ preprocess_dates “ schicken und in einem “ mit Zeilen-/Spaltenanzahl, Speicherverbrauch und einer kurzen Zusammenfassung anzeigen.

Hinter den Kulissen erstellen wir mit der Funktion „ get_schema_description() “ ein Schema, rufen „ call_planner_llm “ auf, um einen JSON-Plan zu bekommen, und zeigen sowohl den Plan als auch die Argumentation des Modells in einem Expander an. Der Datenarbeiter macht dann den Plan mit Pandas klar, und im Abschnitt -Ergebnisse“ gibt's ein Diagramm von generate_chart zusammen mit den wichtigsten Kennzahlen.

Zum Schluss rufen wir „ call_explainer_llm “ mit der Frage, dem Plan und der Zusammenfassung der Ergebnisse auf, um eine Erklärung für die KI-Einblicke Box zu erstellen, mit einem optionalen AI-Begründungsprozess für alle, die einen Blick hinter die Kulissen werfen möchten.

Speicher alles als „ app.py ” und starte:

streamlit run app.py

Im folgenden Video kannst du eine gekürzte Version des Arbeitsablaufs in Aktion sehen:

Fazit

In diesem Tutorial haben wir eine Streamlit-App erstellt, die beliebige CSV-Dateien verarbeitet, sie dem Modell beschreibt, das DeepSeek V3.2 Speciale-Modell auffordert, einen JSON-Analyseplan zu erstellen, diesen Plan mit Pandas ausführt, die Ausgabe mit Matplotlib/Seaborn visualisiert und die Ergebnisse dann in benutzerfreundliche Erkenntnisse übersetzt.

Von hier aus kannst du dieselbe Architektur auf SQL-Warehouses, Parquet-Dateien oder sogar einen kompletten BI-Stack ausweiten. Tausch einfach den CSV-Loader gegen einen Datenbank-Connector aus, füge neue Operationen hinzu oder schließ andere Schlussfolgerungsmodelle an. 

Wenn du mehr über die Arbeit mit DeepSeek erfahren möchtest, empfehle ich dir den Kurs „Arbeiten mit DeepSeek in Python”. Du solltest auch unser Tutorial zu LangSmith Agent Builder und Google Antigravity lesen.

DeepSeek V3.2 Spezielle FAQs

Kann ich statt DeepSeek V3.2 Speciale auch ein anderes Modell nehmen?

Die Architektur ist modellunabhängig, solange das Modell Anweisungen befolgen und strukturierte JSON-Daten ausgeben kann. Du kannst jedes OpenAI-kompatible Modell einbinden, indem du einfach die Namen „ base_url “ und „ model “ im Client änderst.

Welche Fragen kann dieser KI-Datenanalyst-Agent am besten beantworten?

Die aktuelle Konfiguration eignet sich am besten für beschreibende Analysen wie Gruppierungszusammenfassungen, Vergleiche, einfache Filter und einfache Aggregationen. Es ist nicht für beliebige SQL-Abfragen gedacht, aber du kannst das JSON-Schema und die run_analysis_plan() erweitern, um komplexere Operationen zu unterstützen.

Wie groß kann meine CSV-Datei sein, bevor sie kaputt geht?

Der limitierende Faktor ist nicht nur das Modell, sondern auch Pandas und der Speicher. Die App kann je nach deiner Hardware kleine bis mittelgroße Datensätze verarbeiten. Wenn du mit echt großen Tabellen arbeitest, solltest du Downsampling, Voraggregierung oder den Austausch der CSV-/Pandas-Ebene gegen eine Datenbank in Betracht ziehen.

Ist es okay, das mit sensiblen Daten zu machen?

Standardmäßig werden alle Daten, die du hochlädst, an die API von DeepSeek geschickt, wenn du das Schema und die Ergebnisse für das Modell beschreibst. Bei sensiblen oder regulierten Datensätzen solltest du versuchen, Spalten zu anonymisieren, nicht mehr Rohwerte als nötig zu senden und ein kompatibles Schlussfolgerungsmodell lokal zu hosten.

Warum fragst du nicht einfach direkt beim Model nach, anstatt mehrere Agenten zu kontaktieren?

Du kannst die CSV-Datei direkt an das Modell schicken, aber dann geht die Reproduzierbarkeit und Kontrolle verloren. Durch die Aufteilung des Workflows in einen Planer (JSON-Plan), einen Datenverarbeiter (Pandas-Ausführung) und einen Erklärer (Erkenntnisse in natürlicher Sprache) wird jeder Schritt transparent, debugbar und überprüfbar.


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Ich bin ein Google Developers Expert in ML (Gen AI), ein Kaggle 3x Expert und ein Women Techmakers Ambassador mit mehr als 3 Jahren Erfahrung im Tech-Bereich. Ich habe 2020 ein Startup im Bereich Gesundheitstechnologie mitbegründet und mache einen Master in Informatik an der Georgia Tech, der sich auf maschinelles Lernen spezialisiert.

Themen

Die besten DataCamp-Kurse

Kurs

Agentensysteme mit LangChain entwerfen

3 Std.
8.2K
Mache dich mit den grundlegenden Komponenten der LangChain-Agenten vertraut und baue eigene Chat-Agenten.
Siehe DetailsRight Arrow
Kurs starten
Mehr anzeigenRight Arrow
Verwandt

Der Blog

Arten von KI-Agenten: Ihre Rollen, Strukturen und Anwendungen verstehen

Lerne die wichtigsten Arten von KI-Agenten kennen, wie sie mit ihrer Umgebung interagieren und wie sie in verschiedenen Branchen eingesetzt werden. Verstehe einfache reflexive, modellbasierte, zielbasierte, nutzenbasierte, lernende Agenten und mehr.
Vinod Chugani's photo

Vinod Chugani

14 Min.

Der Blog

Q2 2023 DataCamp Donates Digest

DataCamp Donates hat im zweiten Quartal 2023 über 20.000 Stipendien an unsere gemeinnützigen Partner vergeben. Erfahre, wie fleißige benachteiligte Lernende diese Chancen in lebensverändernde berufliche Erfolge verwandelt haben.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

Lernprogramm

Python Switch Case Statement: Ein Leitfaden für Anfänger

Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Matt Crabtree's photo

Matt Crabtree

Lernprogramm

Wie man Listen in Python aufteilt: Einfache Beispiele und fortgeschrittene Methoden

Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Allan Ouko's photo

Allan Ouko

Lernprogramm

Loop-Schleifen in Python-Tutorial

Lerne, wie du For-Schleifen in Python umsetzt, um eine Sequenz oder die Zeilen und Spalten eines Pandas-DataFrame zu durchlaufen.
Aditya Sharma's photo

Aditya Sharma

Lernprogramm

Python JSON-Daten: Ein Leitfaden mit Beispielen

Lerne, wie man mit JSON in Python arbeitet, einschließlich Serialisierung, Deserialisierung, Formatierung, Leistungsoptimierung, Umgang mit APIs und Verständnis der Einschränkungen und Alternativen von JSON.
Moez Ali's photo

Moez Ali

Mehr anzeigenMehr anzeigen