Strukturierte Ausgabe zur Einschränkung von LLMs: Ollama, Qwen3 und Python oder Go

Einige Möglichkeiten, strukturierte Ausgaben von Ollama zu erhalten

Inhaltsverzeichnis

Large Language Models (LLMs) sind leistungsstark, aber im Produktivbetrieb benötigen wir selten freie Textabsätze. Stattdessen wünschen wir uns vorhersagbare Daten: Attribute, Fakten oder strukturierte Objekte, die in eine Anwendung eingespeist werden können. Das ist Strukturierte Ausgabe von LLMs.

Die Durchsetzung von Schemata reduziert die Häufigkeit, mit der schlechte Logits in ungültiges JSON umgewandelt werden, aber Temperatur und Strafen sind dennoch relevant für Wiederholungsstürme; siehe Parameter für agentisches Inferenz bei Qwen und Gemma, wenn Sie format-Einschränkungen mit Agents kombinieren.

Vor einiger Zeit stellte Ollama die Unterstützung für strukturierte Ausgaben vor (Ankündigung), wodurch es möglich wurde, die Antworten eines Modells so einzuschränken, dass sie einem JSON-Schema entsprechen. Dies ermöglicht konsistente Datenextraktionspipelines für Aufgaben wie die Katalogisierung von LLM-Funktionen, das Benchmarking von Modellen oder die Automatisierung der Systemintegration.

Enten in einer Reihe

In diesem Beitrag behandeln wir:

  • Was strukturierte Ausgabe ist und warum sie wichtig ist
  • Einfache Möglichkeit, strukturierte Ausgabe von LLMs zu erhalten
  • Wie die neue Funktion von Ollama funktioniert
  • Beispiele zur Extraktion von LLM-Fähigkeiten:

Was ist strukturierte Ausgabe?

Normalerweise generieren LLMs freien Text:

„Modell X unterstützt Reasoning mit Chain-of-Thought, hat ein Kontextfenster von 200K Token und spricht Englisch, Chinesisch und Spanisch.“

Das ist lesbar, aber schwer zu parsen.

Stattdessen fordern wir mit strukturierter Ausgabe ein striktes Schema an:

{
  "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 an eine UI weiterzuleiten.


Einfache Methode, um strukturierte Ausgabe von LLMs zu erhalten

LLMs verstehen manchmal, was das Schema ist, und wir können das LLM bitten, die Ausgabe in JSON gemäß einem bestimmten Schema zurückzugeben. Das Qwen3-Modell von Alibaba ist auf Reasoning 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 strukturierter Datenextraktor.
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"}

Durchsetzung der Schemavalidierung mit Pydantic

Um fehlerhafte Ausgaben zu vermeiden, können Sie die Validierung gegen ein Pydantic-Schema in Python durchführen.

from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int
    city: str

# Nehmen wir an, 'output' ist der JSON-String von Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)

Dies stellt sicher, dass die Ausgabe der erwarteten Struktur entspricht.


Ollamas strukturierte Ausgabe

Ollama ermöglicht es Ihnen jetzt, ein Schema im format-Parameter zu übergeben. Das Modell ist dann gezwungen, nur in JSON zu antworten, das dem Schema entspricht (Dokumentation).

In Python definieren Sie Ihr Schema typischerweise mit Pydantic und lassen Ollama dieses als JSON-Schema verwenden.


Beispiel 2: Extraktion von LLM-Metadaten

Angenommen, Sie haben einen Textausschnitt, der die Fähigkeiten eines LLM beschreibt:

„Qwen3 bietet starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch). Es ermöglicht Reasoning-Schritte (Chain-of-Thought). 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 Funktionen des Modells nur in JSON zurück.
Modellbeschreibung:
'Qwen3 bietet starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch).
Es ermöglicht Reasoning-Schritte (Chain-of-Thought).
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

Speisen Sie Beschreibungen mehrerer Modelle ein und extrahieren Sie diese in strukturierte Form:

from typing import List

class ModelComparison(BaseModel):
    models: List[LLMFeatures]

prompt = """
Extrahieren Sie die Funktionen jedes Modells in JSON.

1. Llama 3.1 unterstützt Reasoning. Kontextfenster ist 128K. Sprachen: Nur Englisch.
2. GPT-4 Turbo unterstützt Reasoning. Kontextfenster ist 128K. Sprachen: Englisch, Japanisch.
3. Qwen3 unterstützt Reasoning. Kontextfenster ist 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"]
    }
  ]
}

Dies macht es trivial, Modelle nach ihren Funktionen zu benchmarken, zu visualisieren oder 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]]

Dies stellt sicher, dass Ihr Schema gültig bleibt, selbst wenn einige Informationen unbekannt sind.


Vorteile, Fallstricke & Best Practices

Die Verwendung strukturierter Ausgabe über Ollama (oder ein anderes System, das dies unterstützt) bietet viele Vorteile – hat aber auch einige Fallstricke.

Vorteile

  • Stärkere Garantien: Das Modell wird aufgefordert, einem JSON-Schema zu folgen, anstatt freien Text zu generieren.
  • Einfacheres Parsen: 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 hinzufügen (mit Standardwerten) und Abwärtskompatibilität aufrechterhalten.
  • Interoperabilität: Nachgelagerte Systeme erwarten strukturierte Daten.
  • Determinismus (besser bei niedriger Temperatur): Wenn die Temperatur niedrig ist (z. B. 0), hält sich das Modell eher strikt an das Schema. Ollamas Dokumentation empfiehlt dies.

Fallstricke & Risiken

  • Schema-Mismatch: Das Modell könnte dennoch abweichen – z. B. eine erforderliche Eigenschaft übersehen, Schlüssel neu anordnen oder zusätzliche Felder enthalten. Sie benötigen Validierung.
  • Komplexe Schemata: Sehr tiefe oder rekursive JSON-Schemata könnten das Modell verwirren oder zu Fehlern führen.
  • Ambiguität im Prompt: Wenn Ihr Prompt vage ist, könnte das Modell Felder oder Einheiten falsch erraten.
  • Inkonsistenz zwischen Modellen: Einige Modelle könnten besser oder schlechter darin sein, strukturierte Einschränkungen einzuhalten.
  • Token-Limits: Das Schema selbst fügt dem Prompt oder API-Aufruf Token-Kosten hinzu.

Best Practices & Tipps (basierend auf Ollamas Blog + 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 „nur in JSON antworten“ oder „keine Kommentare oder zusätzlichen Text einfügen“ in Ihren Prompt ein.
  • Verwenden Sie Temperatur = 0 (oder sehr niedrig), um Zufälligkeit zu minimieren und die Schemakonformität zu maximieren. Ollama empfiehlt Determinismus.
  • Validieren Sie und fallen Sie ggf. zurück (z. B. Wiederholung oder Bereinigung), wenn das Parsen von JSON fehlschlägt oder die Schemavalidierung fehlschlägt.
  • Beginnen Sie mit einem einfacheren Schema und erweitern Sie es schrittweise. Überkomplizieren Sie es 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 zu omitieren (wenn Ihr Schema dies erlaubt).

Go-Beispiel 1: Extraktion von LLM-Funktionen

Hier ist ein einfaches Go-Programm, das Qwen3 nach strukturierter Ausgabe über die Funktionen eines LLM fragt.

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 Funktionen des Modells nur in JSON zurück.
  Beschreibung:
  "Qwen3 bietet starke mehrsprachige Unterstützung (Englisch, Chinesisch, Französisch, Spanisch, Arabisch).
  Es ermöglicht Reasoning-Schritte (Chain-of-Thought).
  Das Kontextfenster beträgt 128K Token."
  `

	// Definieren Sie das JSON-Schema für 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"},
	}

	// Konvertieren Sie das Schema in JSON
	formatJSON, err := json.Marshal(formatSchema)
	if err != nil {
		log.Fatal("Fehler beim Marshalling 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 {
		// Akkumulieren Sie den Inhalt während des Streamings
		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("Geparter Struct: %+v\n", features)
}

Um dieses Beispiel-Go-Programm zu kompilieren und auszuführen – nehmen wir an, wir haben diese main.go-Datei in einem Ordner ollama-struct, Müssen wir innerhalb dieses Ordners ausführen:

# Modul initialisieren
go mod init ollama-struct
# Alle Abhängigkeiten holen
go mod tidy
# Bauen & ausführen
go build -o ollama-struct main.go
./ollama-struct

Beispielausgabe

Geparter Struct: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[Englisch Chinesisch Französisch Spanisch Arabisch]}

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 Funktionen aus den folgenden Modellbeschreibungen und geben Sie sie als JSON zurück:

	1. PaLM 2: Dieses Modell hat begrenzte Reasoning-Fähigkeiten und konzentriert sich auf grundlegendes Sprachverständnis. Es unterstützt ein Kontextfenster von 8.000 Token. Es unterstützt hauptsächlich nur die englische Sprache.
	2. LLaMA 2: Dieses Modell hat moderate Reasoning-Fähigkeiten und kann einige logische Aufgaben bearbeiten. Es kann bis zu 4.000 Token in seinem Kontext verarbeiten. Es unterstützt Englisch, Spanisch und Italienisch.
	3. Codex: Dieses Modell hat starke Reasoning-Fähigkeiten speziell für Programmierung und Codeanalyse. Es hat ein Kontextfenster von 16.000 Token. 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 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"},
	}

	// Konvertieren Sie das Schema in JSON
	comparisonFormatJSON, err := json.Marshal(comparisonSchema)
	if err != nil {
		log.Fatal("Fehler beim Marshalling 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 {
		// Akkumulieren Sie den Inhalt während des Streamings
		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: Kontext=%d, Sprachen=%v\n", m.Name, m.MaxContextTokens, m.Languages)
	}

Beispielausgabe

PaLM 2: Kontext=8000, Sprachen=[Englisch]
LLaMA 2: Kontext=4000, Sprachen=[Englisch Spanisch Italienisch]
Codex: Kontext=16000, Sprachen=[Englisch Python JavaScript Java]

Übrigens funktioniert qwen3:4b bei diesen Beispielen genauso gut wie qwen3:8b.

Best Practices für Go-Entwickler

  • Setzen Sie die Temperatur auf 0, um maximale Schemakonformität zu gewährleisten.
  • Validieren Sie mit json.Unmarshal und fallen Sie zurück, falls das Parsen fehlschlägt.
  • Halten Sie Schemata einfach – tief verschachtelte oder rekursive JSON-Strukturen können Probleme verursachen.
  • Erlauben Sie optionale Felder (verwenden Sie omitempty in Go-Struct-Tags), wenn Sie fehlende Daten erwarten.
  • Fügen Sie Wiederholungen hinzu, falls das Modell gelegentlich ungültiges JSON ausgibt.

Vollständiges Beispiel - Zeichnen eines Diagramms mit LLM-Spezifikationen (Schritt-für-Schritt: von strukturiertem JSON zu Vergleichstabellen)

llm-chart

  1. Definieren Sie ein Schema für die gewünschten Daten

Verwenden Sie Pydantic, damit Sie sowohl (a) ein JSON-Schema für Ollama generieren als auch (b) die Antwort des Modells validieren können.

from pydantic import BaseModel
from typing import List, Optional

class LLMFeatures(BaseModel):
    name: str
    supports_thinking: bool
    max_context_tokens: int
    languages: List[str]
  1. Bitten Sie Ollama, nur JSON in dieser Form zurückzugeben

Übergeben Sie das Schema in format= und senken Sie die Temperatur für Determinismus.

from ollama import chat

prompt = """
Extrahieren Sie Funktionen 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 von LLMFeatures
  1. 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]
  1. Erstellen Sie eine Vergleichstabelle (pandas)

Wandeln Sie Ihre validierten Objekte in einen DataFrame um, den Sie sortieren/filtern 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"]]

# Als CSV speichern für weitere Verwendung
df.to_csv("llm_feature_comparison.csv", index=False)
  1. (Optional) Schnelle Visualisierungen

Einfache Diagramme helfen Ihnen, Unterschiede zwischen Modellen schnell zu erkennen.

import matplotlib.pyplot as plt

plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Maximales Kontextfenster nach Modell (Token)")
plt.xlabel("Modell")
plt.ylabel("Maximale Kontext-Token")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")

TL;DR

Mit der neuen Unterstützung für strukturierte Ausgabe von Ollama können Sie LLMs nicht nur als Chatbots, sondern als Datenextraktionsmotoren behandeln.

Die obigen Beispiele zeigten, wie man strukturierte Metadaten über LLM-Funktionen wie Reasoning-Unterstützung, Kontextfenstergröße und unterstützte Sprachen automatisch extrahiert – Aufgaben, die sonst zerbrechliches Parsing erfordern würden.

Ob Sie einen LLM-Modellkatalog, ein Evaluierungs-Dashboard oder einen KI-gestützten Forschungsassistenten aufbauen, strukturierte Ausgaben machen die Integration reibungslos, zuverlässig und produktionsreif.

Abonnieren

Neue Beiträge zu Systemen, Infrastruktur und KI-Engineering.