Einschränkung von LLMs mit strukturierten Ausgaben: Ollama, Qwen3 & Python oder Go

Einige Möglichkeiten, um strukturierte Ausgaben von Ollama zu erhalten

Inhaltsverzeichnis

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.

Enten in einer Reihe

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)

llm-chart

  1. 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]
  1. 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
  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/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)
  1. (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.