Att begränsla LLM:er med strukturerad output: Ollama, Qwen3 och Python eller Go

Några sätt att få strukturerad output från Ollama

Sidinnehåll

Storspråkmodeller (LLM) är kraftfulla, men i produktionsmiljöer vill vi sällan ha fritt formulerade stycken. Istället vill vi ha förutsägbar data: attribut, fakta eller strukturerade objekt som kan matas in i en applikation. Det är vad Strukturerad utdata från LLM handlar om.

Schema-tvingande minskar hur ofta felaktiga logits leder till ogiltig JSON, men temperatur och straff är fortfarande viktiga för att undvika återförsökstormar; se parametrar för agentbaserad inferens för Qwen och Gemma när du kombinerar format-begränsningar med agenter.

För ett tag sedan införde Ollama stöd för strukturerad utdata (meddelande), vilket gör det möjligt att begränsa en modells svar så att de följer ett JSON-schema. Detta möjliggör konsekventa pipelines för dataextraktion för uppgifter som att katalogisera LLM-funktioner, benchmarka modeller eller automatisera systemintegration.

anordnade ankan

I detta inlägg ska vi täcka:

  • Vad strukturerad utdata är och varför det är viktigt
  • Ett enkelt sätt att få strukturerad utdata från LLM
  • Hur Ollamas nya funktion fungerar
  • Exempel på extrahering av LLM-funktioner:

Vad är strukturerad utdata?

Normalt genererar LLM fritt text:

“Modell X stöder resonemang med chain-of-thought, har ett kontextfönster på 200K och talar engelska, kinesiska och spanska.”

Det är läsbar, men svår att parsas.

Istället, med strukturerad utdata, ber vi om ett strikt schema:

{
  "name": "Model X",
  "supports_thinking": true,
  "max_context_tokens": 200000,
  "languages": ["English", "Chinese", "Spanish"]
}

Denna JSON är enkel att validera, lagra i en databas eller mata till ett användargränssnitt.


Enkel metod för att få strukturerad utdata från LLM

LLM förstår ibland vad schemat är och vi kan be LLM att returnera utdata i JSON med ett visst schema. Modellen Qwen3 från Alibaba är optimerad för resonemang och strukturerade svar. Du kan explicit instruera den att svara i JSON.

Exempel 1: Använda Qwen3 med ollama i Python, begäran om JSON med schema

import json
import ollama

prompt = """
Du är en strukturerad dataextraktor.
Returnera endast JSON.
Text: "Elon Musk är 53 år och bor i Austin."
Schema: { "name": string, "age": int, "city": string }
"""

response = ollama.chat(model="qwen3", messages=[{"role": "user", "content": prompt}])
output = response['message']['content']

# Parsa JSON
try:
    data = json.loads(output)
    print(data)
except Exception as e:
    print("Fel vid parsning av JSON:", e)

Utdata:

{"name": "Elon Musk", "age": 53, "city": "Austin"}

Tvinga schemavalidering med Pydantic

För att undvika felaktiga utdata kan du validera mot ett Pydantic-schema i Python.

from pydantic import BaseModel

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

# Antag att 'output' är JSON-strängen från Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)

Detta säkerställer att utdatan följ den förväntade strukturen.


Ollamas strukturerade utdata

Ollama låter dig nu skicka ett schema i format-parametern. Modellen begränsas då att svara endast i JSON som följer schemat (dokumentation).

I Python definierar du vanligtvis ditt schema med Pydantic och låter Ollama använda det som ett JSON-schema.


Exempel 2: Extrahera metadata om LLM-funktioner

Antag att du har en textbit som beskriver en LLMs förmågor:

“Qwen3 har starkt flerspråkligt stöd (engelska, kinesiska, franska, spanska, arabiska). Den tillåter resonemangsteg (chain-of-thought). Kontextfönstret är 128K tokens.”

Du vill ha strukturerad data:

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 = """
Analysera följande beskrivning och returnera modellens funktioner endast i JSON.
Modellbeskrivning:
'Qwen3 har starkt flerspråkligt stöd (engelska, kinesiska, franska, spanska, arabiska).
Den tillåter resonemangsteg (chain-of-thought).
Kontextfönstret är 128K tokens.'
"""

resp = chat(
    model="qwen3",
    messages=[{"role": "user", "content": prompt}],
    format=LLMFeatures.model_json_schema(),
    options={"temperature": 0},
)

print(resp.message.content)

Möjlig utdata:

{
  "name": "Qwen3",
  "supports_thinking": true,
  "max_context_tokens": 128000,
  "languages": ["English", "Chinese", "French", "Spanish", "Arabic"]
}

Exempel 3: Jämför flera modeller

Skicka in beskrivningar av flera modeller och extrahera dem i strukturerad form:

from typing import List

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

prompt = """
Extrahera funktioner för varje modell till JSON.

1. Llama 3.1 stöder resonemang. Kontextfönstret är 128K. Språk: endast engelska.
2. GPT-4 Turbo stöder resonemang. Kontextfönstret är 128K. Språk: engelska, japanska.
3. Qwen3 stöder resonemang. Kontextfönstret är 128K. Språk: engelska, kinesiska, franska, spanska, arabiska.
"""

resp = chat(
    model="qwen3",
    messages=[{"role": "user", "content": prompt}],
    format=ModelComparison.model_json_schema(),
    options={"temperature": 0},
)

print(resp.message.content)

Utdata:

{
  "models": [
    {
      "name": "Llama 3.1",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["English"]
    },
    {
      "name": "GPT-4 Turbo",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["English", "Japanese"]
    },
    {
      "name": "Qwen3",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["English", "Chinese", "French", "Spanish", "Arabic"]
    }
  ]
}

Detta gör det trivialt att benchmarka, visualisera eller filtrera modeller baserat på deras funktioner.


Exempel 4: Detektera luckor automatiskt

Du kan till och med tillåta null-värden när ett fält saknas:

from typing import Optional

class FlexibleLLMFeatures(BaseModel):
    name: str
    supports_thinking: Optional[bool]
    max_context_tokens: Optional[int]
    languages: Optional[List[str]]

Detta säkerställer att ditt schema förblir giltigt även om viss information är okänd.


Fördelar, fallgropar och bästa praxis

Att använda strukturerad utdata genom Ollama (eller något system som stöder det) erbjuder många fördelar – men har också vissa fallgropar.

Fördelar

  • Starkare garantier: Modellen be om att följa ett JSON-schema snarare än fritt formulerad text.
  • Enklare parsning: Du kan direkt använda json.loads eller validera med Pydantic / Zod, istället för regex eller heuristik.
  • Schema-baserad evolution: Du kan versionera ditt schema, lägga till fält (med standardvärden) och bibehålla bakåtkompatibilitet.
  • Interoperabilitet: Nedströmsystem förväntar sig strukturerad data.
  • Determinism (bättre med låg temperatur): När temperaturen är låg (t.ex. 0) är modellen mer benägen att strikt hålla sig till schemat. Ollamas dokumentation rekommenderar detta.

Fallgropar och risker

  • Schema-missmatch: Modellen kan ändå avvika – t.ex. missa en obligatorisk egenskap, reordna nycklar eller inkludera extra fält. Du behöver validering.
  • Komplexa scheman: Mycket djupa eller rekursiva JSON-scheman kan förvirra modellen eller leda till fel.
  • Tvetydighet i prompt: Om din prompt är vag kan modellen gissa fält eller enheter felaktigt.
  • Inkonsekvens mellan modeller: Vissa modeller kan vara bättre eller sämre på att respektera strukturerade begränsningar.
  • Token-gränser: Själva schemat lägger till tokenkostnad till prompten eller API-anropet.

Bästa praxis och tips (från Ollamas blogg + erfarenhet)

  • Använd Pydantic (Python) eller Zod (JavaScript) för att definiera dina scheman och auto-generera JSON-scheman. Detta undviker manuella fel.
  • Inkludera alltid instruktioner som “svara endast i JSON” eller “inkludera inte kommentarer eller extra text” i din prompt.
  • Använd temperature = 0 (eller mycket lågt) för att minimera slumpmässighet och maximera schemalydnad. Ollama rekommenderar determinism.
  • Validera och potentiellt fallback (t.ex. försök igen eller rensa upp) när JSON-parsning eller schemavalidering misslyckas.
  • Börja med ett enklare schema och utvidga sedan gradvis. Komplikera inte allt från början.
  • Inkludera hjälpsamma men begränsade felinstruktioner: t.ex. om modellen inte kan fylla ett obligatoriskt fält, svara med null istället för att utelämna det (om ditt schema tillåter det).

Go-exempel 1: Extrahera LLM-funktioner

Här är ett enkelt Go-program som frågar Qwen3 om strukturerad utdata om en LLMs funktioner.

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 := `
  Analysera följande beskrivning och returnera modellens funktioner endast i JSON.
  Beskrivning:
  "Qwen3 har starkt flerspråkligt stöd (engelska, kinesiska, franska, spanska, arabiska).
  Den tillåter resonemangsteg (chain-of-thought).
  Kontextfönstret är 128K tokens."
  `

	// Definiera JSON-schemat för strukturerad utdata
	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"},
	}

	// Konvertera schema till JSON
	formatJSON, err := json.Marshal(formatSchema)
	if err != nil {
		log.Fatal("Misslyckades med att marshalera formatschemat:", 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 {
		// Ackumulera innehåll när det strömmas
		rawResponse += response.Response

		// Parsa endast när svaret är komplett
		if response.Done {
			if err := json.Unmarshal([]byte(rawResponse), &features); err != nil {
				return fmt.Errorf("JSON-parsfel: %v", err)
			}
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Parsad struct: %+v\n", features)
}

För att kompilera och köra detta exempel Go-program – låt oss anta att vi har denna main.go-fil i en mapp ollama-struct, Vi behöver exekvera inuti denna mapp:

# initiera modul
go mod init ollama-struct
# hämta alla beroenden
go mod tidy
# bygga & exekvera
go build -o ollama-struct main.go
./ollama-struct

Exempel på utdata

Parsed struct: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[English Chinese French Spanish Arabic]}

Go-exempel 2: Jämför flera modeller

Du kan utöka detta för att extrahera en lista av modeller för jämförelse.

  type ModelComparison struct {
		Models []LLMFeatures `json:"models"`
	}

	prompt = `
	Extrahera funktioner från följande modellbeskrivningar och returnera som JSON:

	1. PaLM 2: Denna modell har begränsade resonemangsförmågor och fokuserar på grundläggande språkförståelse. Den stöder ett kontextfönster på 8 000 tokens. Den stöder främst endast engelska.
	2. LLaMA 2: Denna modell har måttliga resonemangsförmågor och kan hantera vissa logiska uppgifter. Den kan processa upp till 4 000 tokens i sitt kontext. Den stöder engelska, spanska och italienska.
	3. Codex: Denna modell har starka resonemangsförmågor specifikt för programmering och kodanalys. Den har ett kontextfönster på 16 000 tokens. Den stöder engelska, Python, JavaScript och Java.

	Returnera ett JSON-objekt med en "models"-array som innehåller alla modeller.
	`

	// Definiera JSON-schemat för modelljämförelse
	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"},
	}

	// Konvertera schema till JSON
	comparisonFormatJSON, err := json.Marshal(comparisonSchema)
	if err != nil {
		log.Fatal("Misslyckades med att marshalera jämförelseschemat:", 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 {
		// Ackumulera innehåll när det strömmas
		comparisonResponse += response.Response

		// Parsa endast när svaret är komplett
		if response.Done {
			if err := json.Unmarshal([]byte(comparisonResponse), &comp); err != nil {
				return fmt.Errorf("JSON-parsfel: %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)
	}

Exempel på utdata

PaLM 2: Context=8000, Languages=[English]
LLaMA 2: Context=4000, Languages=[English Spanish Italian]
Codex: Context=16000, Languages=[English Python JavaScript Java]

Förresten, qwen3:4b fungerar bra på dessa exempel, precis som qwen3:8b.

Bästa praxis för Go-utvecklare

  • Sätt temperaturen till 0 för maximal schemalydnad.
  • Validera med json.Unmarshal och fallback om parsning misslyckas.
  • Håll scheman enkla – djupt nästlade eller rekursiva JSON-strukturer kan orsaka problem.
  • Tillåt valfria fält (använd omitempty i Go-struct-taggar) om du förväntar dig saknad data.
  • Lägg till försök om modellen ibland genererar ogiltig JSON.

Fullständigt exempel – Rita en diagram med LLM-specifikationer (Steg-för-steg: från strukturerad JSON till jämförelsetabeller)

llm-chart

  1. Definiera ett schema för den data du vill ha

Använd Pydantic så att du både (a) kan generera ett JSON-schema för Ollama och (b) validera modellens svar.

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. Be Ollama att returnera endast JSON i den formen

Skicka schemat i format= och sänk temperaturen för determinism.

from ollama import chat

prompt = """
Extrahera funktioner för varje modell. Returnera endast JSON som matchar schemat.
1) Qwen3 stöder chain-of-thought; 128K kontext; engelska, kinesiska, franska, spanska, arabiska.
2) Llama 3.1 stöder chain-of-thought; 128K kontext; engelska.
3) GPT-4 Turbo stöder chain-of-thought; 128K kontext; engelska, japanska.
"""

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-lista av LLMFeatures
  1. Validera & normalisera

Validera alltid innan användning i produktion.

from pydantic import TypeAdapter

adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json)  # -> list[LLMFeatures]
  1. Bygg en jämförelsetabell (pandas)

Omändla dina validerade objekt till en DataFrame som du kan sortera/filtera och exportera.

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))

# Reordna kolumner för läsbarhet
df = df[["name", "supports_thinking", "max_context_tokens", "languages_count", "languages"]]

# Spara som CSV för vidare användning
df.to_csv("llm_feature_comparison.csv", index=False)
  1. (Valfritt) Snabba visualiseringar

Enkla diagram hjälper dig att snabbt se skillnader mellan modeller.

import matplotlib.pyplot as plt

plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Max Kontextfönster per Modell (tokens)")
plt.xlabel("Modell")
plt.ylabel("Max Kontext Tokens")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")

TL;DR

Med Ollamas nya stöd för strukturerad utdata kan du behandla LLM inte bara som chattbotar utan som dataextraktionsmotorer.

Exemplen ovan visade hur man automatiskt kan extrahera strukturerad metadata om LLM-funktioner som stöd för tänkande, storlek på kontextfönster och språk – uppgifter som annars skulle kräva bräcklig parsning.

Oavsett om du bygger en LLM-modellkatalog, ett utvärderingsdashboard eller en AI-driven forskningsassistent, gör strukturerade utdata integrationen smidig, pålitlig och produktionsklar.

Användbara länkar

Prenumerera

Få nya inlägg om system, infrastruktur och AI-ingenjörskonst.