Zum Inhalt springen
CASOON

Erste Anwendung mit Mistral bauen: Chat, Kontext und einfache Tools

Minimal funktionierendes Setup ohne Overengineering – schneller erster Erfolg

10 Minuten
Erste Anwendung mit Mistral bauen: Chat, Kontext und einfache Tools
#Mistral #API #Tools #Entwicklung
SerieMistral & Vibe CLI
Teil 12 von 16

Der Einstieg mit Mistral ist bewusst simpel gehalten. Genau das ist der Vorteil: Du kannst innerhalb von Minuten etwas bauen, das wirklich funktioniert.

Dieser Guide zeigt dir den kleinsten sinnvollen Aufbau:

  • einfacher Chat
  • sauber gesetzter Kontext
  • ein erstes Tool (Function Calling)

Ohne RAG, ohne Infrastruktur, ohne unnötige Abstraktionen.

Setup: Minimale Abhängigkeiten

Alles, was du brauchst:

  • API-Key von Mistral AI
  • Node.js oder Python
  • einen simplen HTTP-Client

Mehr nicht.

Python Setup

pip install mistralai requests python-dotenv
import os
from mistralai import Mistral

client = Mistral(api_key=os.environ.get("MISTRAL_API_KEY"))

Node.js Setup

npm install @mistralai/mistralai dotenv
import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({
  apiKey: process.env.MISTRAL_API_KEY,
});

Erster Call: Minimaler Chat

Der erste funktionierende Request sollte so klein wie möglich sein.

response = client.chat.complete(
    model="mistral-large-latest",
    messages=[
        {"role": "user", "content": "Wer war Albert Einstein?"}
    ]
)

print(response.choices[0].message.content)

Das Ziel hier ist nicht „schöne Architektur”, sondern:

Proof, dass alles funktioniert.

Wenn das läuft, kannst du anfangen auszubauen.

Wichtiger Punkt: Messages sind dein Zustand

Viele unterschätzen das am Anfang:

👉 Das Modell hat kein Gedächtnis
👉 Alles, was es „weiß”, kommt aus messages

Das bedeutet:

  • Du musst den Verlauf selbst speichern
  • Jeder neue Request enthält den kompletten Kontext

Minimal:

messages = []

messages.append({"role": "user", "content": "Hallo"})
messages.append({"role": "assistant", "content": "Hi!"})

Das ist bereits dein „Memory”.

Kontext richtig setzen (System-Prompt)

Ein guter System-Prompt macht oft mehr aus als jede technische Erweiterung.

messages = [
    {
        "role": "system",
        "content": "Du bist ein erfahrener Backend-Entwickler. Antworte präzise, ohne Marketing-Sprache."
    },
    {
        "role": "user",
        "content": "Wie strukturiere ich eine REST API?"
    }
]

Praxisregel:

  • lieber kurz und klar
  • keine Romane
  • konkrete Rolle + Erwartung

Dokumente einbinden (ohne RAG)

Für kleine Use-Cases reicht es völlig, Inhalte direkt einzubauen.

with open("documentation.txt", "r") as f:
    documentation = f.read()

messages = [
    {
        "role": "system",
        "content": f"""Nutze ausschließlich diese Dokumentation:

{documentation}

Wenn etwas fehlt: sag es klar."""
    },
    {
        "role": "user",
        "content": "Wie konfiguriere ich das Admin-Dashboard?"
    }
]

Typischer Fehler: Einfach alles reinwerfen → funktioniert schlecht.

Besser:

  • relevante Abschnitte extrahieren
  • Kontext klein halten
  • gezielt füttern

Tool-Calling: Der erste echte Mehrwert

Jetzt wird es interessant: Das Modell kann Dinge tun, nicht nur antworten.

Tool definieren

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Holt Wetterdaten für eine Stadt",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    }
]

Modell entscheiden lassen

response = client.chat.complete(
    model="mistral-large-latest",
    tools=tools,
    messages=[
        {"role": "user", "content": "Wie ist das Wetter in Berlin?"}
    ]
)

Wichtig:

👉 Das Modell führt nichts selbst aus
👉 Es sagt dir nur: „Bitte Tool X mit Parametern Y ausführen”

Tool wirklich ausführen

import json

def get_weather(city):
    return f"Wetter in {city}: 15°C, bewölkt"

tool_call = response.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)

result = get_weather(args["city"])

Ergebnis zurück ins Modell geben

Das wird oft falsch gemacht.

messages = [
    {"role": "user", "content": "Wie ist das Wetter in Berlin?"},
    response.choices[0].message,
    {
        "role": "tool",
        "content": result,
        "tool_call_id": tool_call.id
    }
]

final = client.chat.complete(
    model="mistral-large-latest",
    tools=tools,
    messages=messages
)

Erst jetzt entsteht eine saubere, kontextbasierte Antwort.

Minimal sinnvoller Chat-Loop

from mistralai import Mistral
import json

client = Mistral(api_key="your-api-key")

messages = []
max_history = 20  # Speicher begrenzen

while True:
    user_input = input("Du: ")
    if user_input.lower() in ["exit", "quit"]:
        break
    
    # Message hinzufügen
    messages.append({"role": "user", "content": user_input})
    
    # API aufrufen
    response = client.chat.complete(
        model="mistral-large-latest",
        messages=messages
    )
    
    # Assistant-Response speichern
    assistant_message = response.choices[0].message
    messages.append(assistant_message)
    
    # History begrenzen
    if len(messages) > max_history:
        messages = messages[-max_history:]
    
    # Antwort anzeigen
    print(f"Mistral: {assistant_message.content}\n")

Was du am Anfang NICHT brauchst

Das ist der wichtigste Teil:

  • ❌ keine Vektor-Datenbank
  • ❌ kein RAG
  • ❌ kein Fine-Tuning
  • ❌ kein komplexes Prompt-System
  • ❌ keine Microservices

Wenn du das zu früh einbaust, verkomplizierst du alles unnötig.

Was du stattdessen tun solltest

Baue in dieser Reihenfolge:

  1. funktionierender API-Call
  2. stabiler Chat (mit Messages)
  3. sauberer System-Prompt
  4. EIN Tool
  5. Logging + Debugging

Danach erst erweitern.

Praktische Debug-Tipps

Wenn etwas „komisch” ist, logge immer:

  • messages (komplette History)
  • tool_calls (falls vorhanden)
  • komplette response

Sehr oft liegt das Problem nicht im Modell, sondern im Kontext.

Performance / Kosten im Blick behalten

Ein paar einfache Hebel:

  • temperature=0 → stabilere Antworten
  • max_tokens setzen → Kosten kontrollieren
  • kleinere Modelle testen (mistral-8b-latest)

Nächste sinnvolle Schritte

Wenn das hier läuft, kannst du gezielt erweitern:

  • mehrere Tools kombinieren
  • Chat speichern (DB)
  • UI davor bauen
  • Kontext dynamisch erzeugen

Was ich aus echter Produktion gelernt habe

1. System-Prompts sind dein geheimes Tuning

Du brauchst kein Fine-Tuning. Stattdessen:

# Schlecht: zu generisch
"Du bist ein hilfsbereiter Assistent."

# Besser: konkret, aber nicht zu lang
"""Du bist ein Kundensupport-Agent für ein SaaS-Produkt.
- Antworte nur auf Basis unserer Dokumentation
- Bei Unsicherheit: frag nach
- Sei direkt, keine Floskeln"""

Ein 3-Zeiler macht oft mehr aus als 10 Prompts mit unterschiedlichen Modellen.

2. Messages limitieren ist nicht optional

In Produktion wächst die Message-History schnell. Das führt zu:

  • höheren Kosten
  • langsameren Responses
  • Quality-Problemen (ältere Messages verwirren das Modell)

Praktische Regel:

  • last 10-15 Messages behalten
  • noch älter → archive (wenn nötig)
  • bei Speicher: letzte N Messages + System-Prompt + relevanten Kontext
def trim_messages(messages, max_recent=15, system_prefix=1):
    """Behält System-Prompt + letzte N Messages"""
    system_msgs = [m for m in messages if m.get("role") == "system"]
    other_msgs = [m for m in messages if m.get("role") != "system"]
    
    return system_msgs[:system_prefix] + other_msgs[-max_recent:]

3. Tool-Errors sind deine Schuld, nicht des Modells

Wenn ein Tool fehlschlägt, wird das Modell verwirrt. Es weiß nicht, was es tun soll.

Immer Error-Handling im Tool:

def call_external_api(endpoint, params):
    try:
        response = requests.get(endpoint, params=params, timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.Timeout:
        return {"error": "API antwortet nicht – bitte später versuchen"}
    except requests.HTTPError as e:
        return {"error": f"Anfrage fehlgeschlagen: {e.status_code}"}
    except Exception as e:
        return {"error": "Interner Fehler beim Datenabruf"}

Das Modell kann damit umgehen. Ohne Error-Handling zerbricht der Flow.

4. Temperature ist nicht nur für Kreativität

Die meisten denken: temperature=0 für Fakten, temperature=0.8 für Kreativität.

Stimmt, aber es gibt noch einen anderen Use-Case:

  • Bei Tool-Calling: temperature=0 oder max 0.3

    • Das Modell soll Entscheidungen klar treffen, nicht spekulativ werden
    • Höhere Temperaturen = mehr Halluzinationen bei Parametern
  • Bei Fragen über Dokumentation: temperature=0.2

    • Stabil, deterministic, aber noch minimale Varianz für natürlichere Antworten
  • Bei Text-Generierung / Brainstorming: temperature=0.7

    • Variativ genug, aber noch fokussiert

5. Streaming ist unterschätzt

Für User Experience macht Streaming oft den größeren Unterschied als Modell-Wahl.

# Ohne Streaming: User wartet 3 Sekunden auf komplette Antwort
response = client.chat.complete(...)

# Mit Streaming: erste Wörter nach 500ms
for chunk in client.chat.stream(...):
    print(chunk.choices[0].delta.content, end="", flush=True)

User fühlen sich nicht abgehängt, auch wenn die Antwort lang ist.

6. Tool-Calls sind nicht deterministisch

Das Modell entscheidet opportunistisch, ob es ein Tool nutzt.

Das bedeutet: Manchmal nutzt es ein Tool, manchmal nicht – auch bei gleicher Eingabe.

Praktische Konsequenz:

Baue deine Anwendung so, dass sie auch ohne Tool-Calls sinnvoll funktioniert. Tools sind Optimierungen, nicht Voraussetzungen.

# Nicht so (Tool ist Pflicht):
if not tool_call:
    return "Fehler: konnte Tool nicht aufrufen"

# Sondern so (Tool ist optional):
if tool_call:
    result = execute_tool(...)
    # Zweiten Call mit Ergebnis
else:
    # Modell antwortet auch ohne Tool – auch gut
    pass

7. Kontext-Länge managen ist ein Skill

Du hast 8k Tokens. Das klingt viel, ist aber schnell voll:

  • System-Prompt: 200-500 Tokens
  • Chat-History (10 Messages): ~2k Tokens
  • Kontext-Dokument: 3-4k Tokens

Praktisches Pattern für Dokumentation:

Statt alles reinzuwerfen → einen Smart Filter bauen:

def get_relevant_docs(user_question, all_docs):
    """Filtert nur relevante Abschnitte"""
    # Beispiel: Keyword-Match (oder später: embedding-basiert)
    keywords = extract_keywords(user_question)
    relevant = [doc for doc in all_docs if any(kw in doc.lower() for kw in keywords)]
    return "\n---\n".join(relevant[:3])  # Top 3

Das spart Tokens und macht Antworten präziser.

8. Retry-Logik braucht Intelligenz

Blind retrying funktioniert nicht:

# Schlecht: immer gleich machen
for i in range(3):
    try:
        response = client.chat.complete(...)
    except:
        time.sleep(1)  # immer gleich lange warten

# Besser: unterschied zwischen Fehlertypen
try:
    response = client.chat.complete(...)
except RateLimitError:
    time.sleep(30)  # API-Rate-Limit: länger warten
except TimeoutError:
    time.sleep(5)   # Timeout: kurz warten, dann neu
except Exception:
    # Unerwarteter Fehler: nicht wiederholen, loggen
    logger.error("Unexpected error", exc_info=True)

9. Kosten sind nicht proportional zu Quality

Ein häufiger Fehler: „Ich nutze immer das größte Modell für alles.”

In der Praxis:

  • mistral-large für komplexes Reasoning und Tool-Orchestration
  • mistral-8b für einfache Fragen, Klassifizierung, Summarization
  • Kosten sparen → einfachere Tasks mit 8b testen

Ein guter Filter spart 80% Kosten bei gleicher Quality.

10. Monitoring ohne Overhead

Du brauchst keine komplexe Telemetrie, aber:

import time

def logged_chat_complete(messages, **kwargs):
    start = time.time()
    try:
        response = client.chat.complete(messages=messages, **kwargs)
        duration = time.time() - start
        
        # Minimal loggen
        print(f"Success | duration={duration:.1f}s | tokens_in={response.usage.input_tokens}")
        return response
    except Exception as e:
        duration = time.time() - start
        print(f"Error | {type(e).__name__} | duration={duration:.1f}s")
        raise

Das hilft dir später, Bottlenecks zu finden.

Kurz zusammengefasst

Der schnellste Weg zu einer funktionierenden Anwendung ist:

  • Chat API nutzen
  • Kontext sauber setzen
  • Tool-Calling minimal einbauen

Aber der Weg zu einer guten Anwendung:

  • System-Prompts perfektionieren (nicht Modelle wechseln)
  • Messages aktiv begrenzen (Kosten + Quality)
  • Tools mit Error-Handling bauen (nicht: Tool muss immer funktionieren)
  • Streaming für UX, richtige Temperature für Usecase
  • Monitoring von Anfang an, auch minimal

Das ist nicht fancy, aber es funktioniert in Produktion.