Einschränkung von LLMs mit strukturierten Ausgaben: Ollama, Qwen3 & Python oder Go
Einige Möglichkeiten, um strukturierte Ausgaben von Ollama zu erhalten
Große Sprachmodelle (LLMs) sind leistungsfähig, aber in der Produktion wollen wir selten frei formulierte Absätze. Stattdessen wollen wir vorhersehbare Daten: Attribute, Fakten oder strukturierte Objekte, die Sie in eine Anwendung einspeisen können. Das ist LLM-Strukturierte Ausgabe.
Vor einiger Zeit hat Ollama die Unterstützung für strukturierte Ausgaben eingeführt (Ankündigung), was es möglich macht, die Antworten eines Modells auf ein JSON-Schema zu beschränken. Dies ermöglicht konsistente Datenextraktionspipelines für Aufgaben wie die Katalogisierung von LLM-Funktionen, das Benchmarking von Modellen oder die Automatisierung der Systemintegration.
In diesem Beitrag werden wir behandeln:
- Was strukturierte Ausgaben sind und warum sie wichtig sind
- Einfache Methode zur Erzeugung strukturierter Ausgaben von LLMs
- Wie die Ollama-Funktion funktioniert
- Beispiele zur Extraktion von LLM-Funktionen:
Was ist strukturierte Ausgabe?
Normalerweise generieren LLMs freien Text:
„Modell X unterstützt das Denken mit Kette-der-Gedanken, hat ein Kontextfenster von 200K und spricht Englisch, Chinesisch und Spanisch.“
Das ist lesbar, aber schwer zu parsen.
Stattdessen fragen wir mit strukturierter Ausgabe nach einem strengen Schema:
{
"name": "Modell X",
"supports_thinking": true,
"max_context_tokens": 200000,
"languages": ["Englisch", "Chinesisch", "Spanisch"]
}
Dieses JSON ist einfach zu validieren, in einer Datenbank zu speichern oder einer Benutzeroberfläche zuzuführen.
Einfache Methode zur Erzeugung strukturierter Ausgaben von LLM
Die LLMs verstehen manchmal, was das Schema ist, und wir können das LLM anweisen, die Ausgabe in JSON mit einem bestimmten Schema zurückzugeben. Das Qwen3-Modell von Alibaba ist für das Denken und strukturierte Antworten optimiert. Sie können es explizit anweisen, in JSON zu antworten.
Beispiel 1: Verwendung von Qwen3 mit ollama
in Python, Anforderung von JSON mit Schema
import json
import ollama
prompt = """
Sie sind ein Extrahierer strukturierter Daten.
Geben Sie nur JSON zurück.
Text: "Elon Musk ist 53 und lebt in Austin."
Schema: { "name": string, "age": int, "city": string }
"""
response = ollama.chat(model="qwen3", messages=[{"role": "user", "content": prompt}])
output = response['message']['content']
# JSON parsen
try:
data = json.loads(output)
print(data)
except Exception as e:
print("Fehler beim Parsen von JSON:", e)
Ausgabe:
{"name": "Elon Musk", "age": 53, "city": "Austin"}
Schema-Validierung mit Pydantic erzwingen
Um fehlerhafte Ausgaben zu vermeiden, können Sie gegen ein Pydantic-Schema in Python validieren.
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
city: str
# Angenommen, 'output' ist die JSON-Zeichenfolge von Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)
Dadurch wird sichergestellt, dass die Ausgabe der erwarteten Struktur entspricht.
Ollamas strukturierte Ausgabe
Ollama ermöglicht es Ihnen nun, ein Schema im Parameter format
zu übergeben. Das Modell wird dann darauf beschränkt, nur JSON zu antworten, das dem Schema entspricht (Dokumentation).
In Python definieren Sie Ihr Schema in der Regel mit Pydantic und lassen Ollama dieses als JSON-Schema verwenden.
Beispiel 2: Extraktion von LLM-Funktionsmetadaten
Angenommen, Sie haben eine Textstelle, die die Fähigkeiten eines LLM beschreibt:
„Qwen3 hat starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch). Es ermöglicht Denkschritte (Kette der Gedanken). Das Kontextfenster beträgt 128K Token.“
Sie möchten strukturierte Daten:
from pydantic import BaseModel
from typing import List
from ollama import chat
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
prompt = """
Analysieren Sie die folgende Beschreibung und geben Sie die Merkmale des Modells nur in JSON zurück.
Modellbeschreibung:
'Qwen3 hat starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch).
Es ermöglicht Denkschritte (Kette der Gedanken).
Das Kontextfenster beträgt 128K Token.'
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=LLMFeatures.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Mögliche Ausgabe:
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Englisch", "Chinesisch", "Französisch", "Spanisch", "Arabisch"]
}
Beispiel 3: Vergleich mehrerer Modelle
Geben Sie Beschreibungen mehrerer Modelle ein und extrahieren Sie sie in strukturierter Form:
from typing import List
class ModelComparison(BaseModel):
models: List[LLMFeatures]
prompt = """
Extrahieren Sie die Merkmale jedes Modells in JSON.
1. Llama 3.1 unterstützt das Denken. Kontextfenster beträgt 128K. Sprachen: Nur Englisch.
2. GPT-4 Turbo unterstützt das Denken. Kontextfenster beträgt 128K. Sprachen: Englisch, Japanisch.
3. Qwen3 unterstützt das Denken. Kontextfenster beträgt 128K. Sprachen: Englisch, Chinesisch, Französisch, Spanisch, Arabisch.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=ModelComparison.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Ausgabe:
{
"models": [
{
"name": "Llama 3.1",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Englisch"]
},
{
"name": "GPT-4 Turbo",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Englisch", "Japanisch"]
},
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Englisch", "Chinesisch", "Französisch", "Spanisch", "Arabisch"]
}
]
}
Dadurch wird es trivial, Modelle zu vergleichen, zu visualisieren oder nach ihren Merkmalen zu filtern.
Beispiel 4: Automatische Erkennung von Lücken
Sie können sogar null
-Werte zulassen, wenn ein Feld fehlt:
from typing import Optional
class FlexibleLLMFeatures(BaseModel):
name: str
supports_thinking: Optional[bool]
max_context_tokens: Optional[int]
languages: Optional[List[str]]
Dadurch bleibt Ihr Schema auch dann gültig, wenn einige Informationen unbekannt sind.
Vorteile, Einschränkungen und Best Practices
Die Verwendung strukturierter Ausgaben über Ollama (oder ein anderes System, das dies unterstützt) bietet viele Vorteile – hat aber auch einige Einschränkungen.
Vorteile
- Stärkere Garantien: Das Modell wird gebeten, einem JSON-Schema zu entsprechen, anstatt freien Text zu verwenden.
- Einfachere Analyse: Sie können direkt
json.loads
verwenden oder mit Pydantic / Zod validieren, anstatt Regex oder Heuristiken zu verwenden. - Schema-basierte Evolution: Sie können Ihr Schema versionieren, Felder (mit Standardwerten) hinzufügen und die Abwärtskompatibilität aufrechterhalten.
- Interoperabilität: Nachgelagerte Systeme erwarten strukturierte Daten.
- Determinismus (besser bei niedriger Temperatur): Wenn die Temperatur niedrig ist (z. B. 0), ist das Modell eher bereit, sich strikt an das Schema zu halten. Die Ollama-Dokumentation empfiehlt dies.
Einschränkungen und Fallstricke
- Schema-Inkompatibilität: Das Modell kann trotzdem abweichen – z. B. eine erforderliche Eigenschaft auslassen, Schlüssel neu anordnen oder zusätzliche Felder einfügen. Sie benötigen eine Validierung.
- Komplexe Schemata: Sehr tiefe oder rekursive JSON-Schemata können das Modell verwirren oder zu Fehlern führen.
- Mehrdeutigkeit in der Aufforderung: Wenn Ihre Aufforderung vage ist, kann das Modell Felder oder Einheiten falsch erraten.
- Inkonsistenz über Modelle hinweg: Einige Modelle können besser oder schlechter sein, wenn es darum geht, strukturierte Beschränkungen einzuhalten.
- Token-Grenzen: Das Schema selbst erhöht die Token-Kosten für die Aufforderung oder den API-Aufruf.
Best Practices und Tipps (basierend auf dem Ollama-Blog und Erfahrung)
- Verwenden Sie Pydantic (Python) oder Zod (JavaScript), um Ihre Schemata zu definieren und JSON-Schemata automatisch zu generieren. Dies vermeidet manuelle Fehler.
- Fügen Sie immer Anweisungen wie „Antworten Sie nur in JSON“ oder „Fügen Sie keine Kommentare oder zusätzlichen Texte hinzu“ in Ihre Aufforderung ein.
- Verwenden Sie Temperatur = 0 (oder sehr niedrig), um die Zufälligkeit zu minimieren und die Schema-Übereinstimmung zu maximieren. Ollama empfiehlt Determinismus.
- Validieren und potenziell ausweichen (z. B. wiederholen oder bereinigen), wenn das Parsen von JSON oder die Schema-Validierung fehlschlägt.
- Beginnen Sie mit einem einfachen Schema und erweitern Sie es dann schrittweise. Überkomplizieren Sie nicht von Anfang an.
- Fügen Sie hilfreiche, aber eingeschränkte Fehleranweisungen hinzu: z. B. wenn das Modell ein erforderliches Feld nicht ausfüllen kann, antworten Sie mit
null
, anstatt es wegzulassen (wenn Ihr Schema dies zulässt).
Go-Beispiel 1: Extraktion von LLM-Merkmalen
Hier ist ein einfaches Go-Programm, das Qwen3 um strukturierte Ausgaben zu den Merkmalen eines LLM bittet.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/ollama/ollama/api"
)
type LLMFeatures struct {
Name string `json:"name"`
SupportsThinking bool `json:"supports_thinking"`
MaxContextTokens int `json:"max_context_tokens"`
Languages []string `json:"languages"`
}
func main() {
client, err := api.ClientFromEnvironment()
if err != nil {
log.Fatal(err)
}
prompt := `
Analysieren Sie die folgende Beschreibung und geben Sie die Merkmale des Modells im JSON-Format zurück.
Beschreibung:
"Qwen3 hat eine starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch).
Es ermöglicht Denkschritte (chain-of-thought).
Das Kontextfenster beträgt 128K Tokens."
`
// Definieren Sie das JSON-Schema für die strukturierte Ausgabe
formatSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
}
// Schema in JSON umwandeln
formatJSON, err := json.Marshal(formatSchema)
if err != nil {
log.Fatal("Fehler beim Umwandeln des Format-Schemas:", err)
}
req := &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: formatJSON,
Options: map[string]any{"temperature": 0},
}
var features LLMFeatures
var rawResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Inhalt während des Streamings sammeln
rawResponse += response.Response
// Nur parsen, wenn die Antwort vollständig ist
if response.Done {
if err := json.Unmarshal([]byte(rawResponse), &features); err != nil {
return fmt.Errorf("JSON-Parsefehler: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Parsed struct: %+v\n", features)
}
Um dieses Beispiel-Programm in Go zu kompilieren und auszuführen - nehmen wir an, wir haben diese main.go-Datei in einem Ordner ollama-struct
,
müssen wir in diesem Ordner ausführen:
# Modul initialisieren
go mod init ollama-struct
# Alle Abhängigkeiten herunterladen
go mod tidy
# Bauen & ausführen
go build -o ollama-struct main.go
./ollama-struct
Beispielausgabe
Parsed struct: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[English Chinese French Spanish Arabic]}
Go-Beispiel 2: Vergleich mehrerer Modelle
Sie können dies erweitern, um eine Liste von Modellen zum Vergleich zu extrahieren.
type ModelComparison struct {
Models []LLMFeatures `json:"models"`
}
prompt = `
Extrahieren Sie die Merkmale aus den folgenden Modellbeschreibungen und geben Sie sie als JSON zurück:
1. PaLM 2: Dieses Modell hat begrenzte Denkfähigkeiten und konzentriert sich auf grundlegendes Sprachverständnis. Es unterstützt ein Kontextfenster von 8.000 Tokens. Es unterstützt hauptsächlich nur die englische Sprache.
2. LLaMA 2: Dieses Modell hat moderate Denkfähigkeiten und kann einige logische Aufgaben bewältigen. Es kann bis zu 4.000 Tokens in seinem Kontext verarbeiten. Es unterstützt Englisch, Spanisch und Italienisch.
3. Codex: Dieses Modell hat starke Denkfähigkeiten, die speziell für Programmierung und Code-Analyse geeignet sind. Es hat ein Kontextfenster von 16.000 Tokens. Es unterstützt Englisch, Python, JavaScript und Java.
Geben Sie ein JSON-Objekt mit einem "models"-Array zurück, das alle Modelle enthält.
`
// Definieren Sie das JSON-Schema für den Modellvergleich
comparisonSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"models": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
},
},
},
"required": []string{"models"},
}
// Schema in JSON umwandeln
comparisonFormatJSON, err := json.Marshal(comparisonSchema)
if err != nil {
log.Fatal("Fehler beim Umwandeln des Vergleichsschemas:", err)
}
req = &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: comparisonFormatJSON,
Options: map[string]any{"temperature": 0},
}
var comp ModelComparison
var comparisonResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Inhalt während des Streamings sammeln
comparisonResponse += response.Response
// Nur parsen, wenn die Antwort vollständig ist
if response.Done {
if err := json.Unmarshal([]byte(comparisonResponse), &comp); err != nil {
return fmt.Errorf("JSON-Parsefehler: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
for _, m := range comp.Models {
fmt.Printf("%s: Context=%d, Languages=%v\n", m.Name, m.MaxContextTokens, m.Languages)
}
Beispielausgabe
PaLM 2: Context=8000, Languages=[English]
LLaMA 2: Context=4000, Languages=[English Spanish Italian]
Codex: Context=16000, Languages=[English Python JavaScript Java]
Übrigens, qwen3:4b funktioniert bei diesen Beispielen genauso gut wie qwen3:8b.
Beste Praktiken für Go-Entwickler
- Setzen Sie die Temperatur auf 0, um die Schema-Einhaltung zu maximieren.
- Validieren Sie mit
json.Unmarshal
und greifen Sie auf eine Alternative zurück, wenn das Parsen fehlschlägt. - Halten Sie die Schemas einfach - tief verschachtelte oder rekursive JSON-Strukturen können Probleme verursachen.
- Erlauben Sie optionale Felder (verwenden Sie
omitempty
in den Go-Strukt-Tags), wenn Sie mit fehlenden Daten rechnen. - Fügen Sie Wiederholungen hinzu, wenn das Modell gelegentlich ungültiges JSON ausgibt.
Vollständiges Beispiel - Erstellen eines Diagramms mit LLM-Spezifikationen (Schritt-für-Schritt: von strukturiertem JSON zu Vergleichstabellen)
- Definieren Sie ein Schema für die Daten, die Sie möchten
Verwenden Sie Pydantic, um sowohl (a) ein JSON-Schema für Ollama zu generieren als auch (b) die Antwort des Modells zu validieren.
from pydantic import BaseModel
from typing import List, Optional
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
- Bitten Sie Ollama, nur JSON in dieser Form zurückzugeben
Übertragen Sie das Schema in format=
und senken Sie die Temperatur für Determinismus.
from ollama import chat
prompt = """
Extrahieren Sie die Merkmale für jedes Modell. Geben Sie nur JSON zurück, das dem Schema entspricht.
1) Qwen3 unterstützt chain-of-thought; 128K Kontext; Englisch, Chinesisch, Französisch, Spanisch, Arabisch.
2) Llama 3.1 unterstützt chain-of-thought; 128K Kontext; Englisch.
3) GPT-4 Turbo unterstützt chain-of-thought; 128K Kontext; Englisch, Japanisch.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format={"type": "array", "items": LLMFeatures.model_json_schema()},
options={"temperature": 0}
)
raw_json = resp.message.content # JSON-Liste der LLMFeatures
- Validieren & normalisieren
Validieren Sie immer, bevor Sie in der Produktion verwenden.
from pydantic import TypeAdapter
adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json) # -> list[LLMFeatures]
- Erstellen Sie eine Vergleichstabelle (pandas)
Wandeln Sie Ihre validierten Objekte in einen DataFrame um, den Sie sortieren/filtrieren und exportieren können.
import pandas as pd
df = pd.DataFrame([m.model_dump() for m in models])
df["languages_count"] = df["languages"].apply(len)
df["languages"] = df["languages"].apply(lambda xs: ", ".join(xs))
# Spalten für bessere Lesbarkeit neu anordnen
df = df[["name", "supports_thinking", "max_context_tokens", "languages_count", "languages"]]
# Speichern als CSV für weitere Verwendung
df.to_csv("llm_feature_comparison.csv", index=False)
- (Optional) Schnelle Visualisierungen
Einfache Diagramme helfen Ihnen, Unterschiede zwischen den Modellen schnell zu erkennen.
import matplotlib.pyplot as plt
plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Maximales Kontextfenster pro Modell (Tokens)")
plt.xlabel("Modell")
plt.ylabel("Maximale Kontext-Tokens")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")
TL;DR
Mit der neuen strukturierten Ausgabe-Unterstützung von Ollama können Sie LLMs nicht nur als Chatbots, sondern auch als Datenextraktionsmaschinen behandeln.
Die obigen Beispiele zeigten, wie man automatisch strukturierte Metadaten zu LLM-Merkmalen wie Denkunterstützung, Kontextfenstergröße und unterstützte Sprachen extrahieren kann - Aufgaben, die sonst eine brüchige Parsing erfordern würden.
Ob Sie einen LLM-Modellkatalog, ein Bewertungsdashboard oder einen KI-gestützten Forschungsassistenten erstellen - strukturierte Ausgaben machen die Integration reibungslos, zuverlässig und produktionsbereit.
Nützliche Links
- https://ollama.com/blog/structured-outputs
- Ollama Cheatsheet
- Python Cheatsheet
- AWS SAM + AWS SQS + Python PowerTools
- Golang Cheatsheet
- Vergleich von Go ORMs für PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Neuordnung von Textdokumenten mit Ollama und dem Qwen3-Embedding-Modell - in Go
- AWS Lambda-Leistung: JavaScript vs Python vs Golang