Strukturerade utdata och funktionsanrop: produktionsmönstren
Avancerad13 min läsningAI för företag

Strukturerade utdata och funktionsanrop: produktionsmönstren

Strukturerade utdata och funktionsanrop är bron från ”en LLM som genererar text” till ”ett system som utför arbete”. I produktion handlar de viktiga mönstren om scheman, felhantering, idempotens och kontrollerad degradering – inte bara JSON-läge.

Vad du bör kunna göra

Produktionsmässiga strukturerade utdata och funktionsanrop kräver mer än JSON-läge. Mönstren är strikta scheman, uttrycklig felsemantik, idempotens, kontrollerad felhantering och reflektionsloopar för verktygsresultat som fångar modellfel innan de sprids.

Sparas endast i denna webbläsare.
I denna artikel

Övergången från ”LLM-chattbot” till ”LLM-drivet system som utför arbete” sker i lagret för strukturerade utdata och funktionsanrop. Här slutar LLM:en att bara producera prosa och börjar producera data, välja åtgärder och integrera med resten av infrastrukturen.

I produktion betyder ”strukturerade utdata” inte att ”JSON-läget fungerade en gång i mitt test”. Det betyder en robust pipeline som hanterar modellvariation, felaktiga utdata, partiella fel, schemautveckling och den stökiga verkligheten att LLM:er inte alltid följer instruktioner.

Artikeln beskriver de kontroller en produktionsimplementation behöver. Påståendena om API:erna och deras begränsningar kontrollerades 4 augusti 2026 mot den officiella referensen för OpenAI:s strukturerade utdata, Claudes dokumentation om strukturerade utdata och Geminis dokumentation om strukturerade utdata. Exemplen är designmönster, inte en benchmarkkörning från AIExpert.

De två lägena

Två närbesläktade men skilda förmågor:

Strukturerade utdata: LLM:en producerar utdata som följer ett schema, vanligen JSON. Används när svaret behövs i ett format som kan bearbetas programmatiskt.

Funktions-/verktygsanrop: LLM:en får en uppsättning funktioner, väljer vilken som eventuellt ska anropas och producerar parametrarna. Värdsystemet kör funktionen och returnerar resultatet. LLM:en kan anropa fler funktioner eller ge ett slutsvar.

Modell-API:er exponerar dem vanligen via:

  • En parameter för svarsformat som tar en delmängd av JSON Schema för vanliga strukturerade utdata – OpenAI använder svarsformat med JSON-schema, Claude använder output_config.format och Gemini exponerar schemastyrda strukturerade utdata.
  • En matris tools som beskriver anropbara operationer, plus ett verktygsanropssvar när en operation används. Aktuella Claude-modeller stöder även strict: true i klientens verktygsdefinitioner – att tvinga fram ett konstruerat verktyg enbart för att få JSON är alltså inte längre den enda vägen i Claude.

Båda fungerar och hänger ihop. Ett ”funktionsanrop” är i praktiken strukturerade utdata där schemat är funktionssignaturen.

Mönster 1: strikta, explicita scheman

Den största enskilda tillförlitlighetsvinsten finns i schemana.

Ett löst schema:

{
  "type": "object",
  "properties": {
    "category": { "type": "string" },
    "priority": { "type": "string" }
  }
}

Ett strikt schema:

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "technical", "account", "feature_request", "complaint"],
      "description": "Ärendets kategori. Använd 'technical' för produktfel och 'account' för problem med inloggning eller lösenord."
    },
    "priority": {
      "type": "string",
      "enum": ["low", "medium", "high", "urgent"],
      "description": "Använd 'urgent' endast för avbrott eller verksamhetskritisk påverkan. 'High' för blockerande problem hos viktiga kunder. 'Medium' för normal påverkan. 'Low' för önskemål."
    }
  },
  "required": ["category", "priority"],
  "additionalProperties": false
}

Den strikta versionen:

  • Begränsar värdena till kända enumvärden och hindrar fri drift.
  • Har beskrivningar som fungerar som inbäddade prompter och används av modellen.
  • Kräver fält så att svaren inte blir ofullständiga.
  • Förbjuder extra fält och därmed slumpmässigt hallucinerade nycklar.

I produktion bör varje schemafält ha en beskrivning, varje enum vara explicit och varje obligatoriskt fält markeras. Detta är ”schema som prompt”: schemat utför promptutformning.

Mönster 2: begränsad generering

De stora leverantörerna stöder nu begränsad generering: modellen begränsas vid avkodningen till att endast producera giltiga utdata.

  • OpenAI: response_format: { type: "json_schema", json_schema: { ..., strict: true } }
  • Anthropic: JSON-utdata via output_config.format och strikta verktygsindata med strict: true.
  • Servering av öppna vikter: grammatik- eller schemastyrd avkodning där den valda inferensservern och modellen stöder det.

Föredra begränsade utdata när leverantören, modellen och den delmängd av schemat du behöver stöder det. Det förhindrar fel i syntax och schemaform, men det gör inte de extraherade värdena faktamässigt korrekta och det ger ingen behörighet att utlösa en sidoeffekt. Leverantörerna dokumenterar vilka schemanyckelord som saknar stöd och vilka komplexitetsgränser som gäller. Grammatikkompilering kan lägga latens på det första anropet, och vissa leverantörer cachelagrar kompilerade grammatiker – mät därför både kallt och varmt beteende i stället för att kalla omkostnaden försumbar.

När begränsad generering saknas, för vissa öppna modeller eller konfigurationer, är validering och omförsök reservlösningen (se mönster 4).

Mönster 3: schemaversionering

Scheman utvecklas. Du lägger till och fasar ut fält samt ändrar enumvärden.

En schemaändring är en kodändring. Den ska vara:

  • Versionshanterad. Märk varje schema med versionsnummer.
  • Testad. Kör utvärderingssviten mot det nya schemat före driftsättning.
  • Kommunicerad. Nedströmskonsumenter känner till ändringen.
  • Bakåtkompatibel när det går. Lägg till nya valfria fält, ta inte bort obligatoriska.

Ett välfungerande mönster är att lagra scheman som TypeScript-typer eller Pydantic-modeller, versionshantera dem och generera JSON Schema från dem. Typerna används både av modell-API:et och applikationskoden.

class TicketClassificationV2(BaseModel):
    category: Literal["billing", "technical", "account", "feature_request", "complaint"]
    priority: Literal["low", "medium", "high", "urgent"]
    confidence: float = Field(ge=0, le=1, description="Tilltro till klassificeringen, 0–1")
    needs_human_review: bool = Field(description="Sant om något fält har låg tilltro eller en ovanlig signal")
    reasoning: str = Field(description="Kort motivering till klassificeringen, särskilt i svårbedömda fall")

En Pydantic-modell definierar schemat, validerar utdata och fungerar som typ i Python-koden. En enda sanningskälla.

Mönster 4: validering och omförsök

Validera utdata före användning även med begränsad generering:

from pydantic import ValidationError

def call_with_validation(prompt, schema, max_retries=2):
    for attempt in range(max_retries + 1):
        response = llm_call(prompt, response_format=schema)
        try:
            parsed = schema.model_validate_json(response.content)
            return parsed
        except ValidationError as e:
            if attempt < max_retries:
                prompt = build_retry_prompt(prompt, response.content, e)
                continue
            raise

Omförsöksprompten ska innehålla de ursprungliga instruktionerna, modellens föregående utdata och en specifik felbeskrivning:

Ditt föregående svar hade ett valideringsfel:
{error message}

Dina föregående utdata:
{previous output}

Rätta felet och producera ett giltigt svar.

Om ett omförsök hjälper beror på modellen, felkategorin och återkopplingen. Mät hur ofta ett omförsök faktiskt räddar anropet, uppdelat per typ av valideringsfel – och gör aldrig om en misslyckad säkerhets- eller affärsregelkontroll till ett automatiskt omförsök mot modellen.

Begränsningar: försök inte för alltid, högst 2–3 gånger. Försök inte igen vid andra fel än valideringsfel, exempelvis hastighetsgränser eller innehållsfilter. Logga försöken. En ökande andel signalerar modelldrift eller promptproblem.

Mönster 5: reflektion över resultat

Vid komplexa funktionsanrop kan ett separat modellsteg tolka ett verktygsresultat innan svaret formuleras. Det är ett resonemangsstöd – inte en säkerhetskontroll.

En enkel loop:

1. Anropa LLM:en med tillgängliga verktyg.
2. Modellen väljer att anropa verktyg X.
3. Kör X.
4. Skicka resultatet till modellen.
5. Modellen producerar slutsvaret.

En reflektionsloop:

1. Anropa LLM:en med tillgängliga verktyg.
2. Modellen väljer att anropa verktyg X.
3. Kör X.
4. Skicka resultatet till modellen.
5. Modellen bedömer: motsvarar resultatet förväntningarna? Bör jag agera på det?
6. Om ja ger modellen slutsvaret. Om nej anropar den ett annat verktyg eller ber om ett förtydligande.

Det fångar bland annat:

  • Verktyget returnerade 0 resultat när data förväntades – nästa steg behandlar tomfallet uttryckligen.
  • Verktyget returnerade ett fel – nästa steg hanterar det i stället för att ignorera det.
  • Verktyget returnerade oväntade data – nästa steg flaggar det och anpassar sig.

Implementering: be modellen utvärdera verktygsresultat explicit, exempelvis med ett strukturerat mönster ”utvärdera, sedan agera”.

Det kostar latens och token. För åtgärder med konsekvenser ska du tillämpa deterministiska policykontroller och mänskligt godkännande där det krävs – modellens ”reflektion” får aldrig auktorisera betalning, radering, inskickning eller kontoändring. Utvärdera om det extra modellsteget förbättrar resultatet på en märkt uppsättning uppgifter innan du behåller det.

Mönster 6: idempotens

LLM:er anropar ibland samma verktyg två gånger eller upprepar anrop som redan lyckats. Utan idempotens uppstår dubletter: två återbetalningar, två e-postmeddelanden eller två poster.

Mönster för idempotens:

Idempotensnycklar. Varje verktygsanrop får en unik nyckel som genereras på klientsidan och ingår i anropet. Nedströms-API:et eller verktygsomslaget identifierar dubletter och returnerar befintligt resultat.

Atomisk hämta-eller-skapa-semantik. Lägg ett unikhetsvillkor i databasen på affärsnyckeln och gör infogningen och uppslaget i samma transaktion. En separat sekvens av ”kontrollera, sedan skapa” hamnar i kapplöpning vid samtidighet och är inte idempotens.

Idempotensposter. Gör anspråk på idempotensnyckeln atomiskt i beständig lagring, spara operationens tillstånd och slutliga svar, och returnera samma svar vid omförsök. En logg som bara läggs till, utan atomiskt anspråk, hamnar fortfarande i kapplöpning.

Konservativ verktygsdesign. Verktyg med betydande konsekvenser utformas så att de kräver uttrycklig bekräftelse eller mänskligt godkännande. LLM:en kan inte utlösa dem av misstag i en tät loop.

Alla verktyg med sidoeffekter ska utformas för idempotens. Att utelämna detta är en viktig källa till produktionsfel.

Mönster 7: observerbarhet för verktygsanrop

Du måste veta vad som händer. Logga för varje anrop:

  • Tidsstämpel.
  • Verktygsnamn och argument.
  • Resultat eller fel.
  • Varaktighet.
  • Tillhörande användare/session.
  • Anropskedjan i turen – ingick anropet i en längre kedja?

Bygg instrumentpaneler för dessa data. Vanliga vyer:

  • Antal verktygsanrop per verktyg.
  • Felfrekvens per verktyg.
  • Genomsnittlig varaktighet per verktyg.
  • Mönster i verktygssekvenser – vilka verktyg brukar anropas tillsammans?
  • Hallucinerade verktygsanrop där LLM:en försökte anropa ett verktyg som inte finns.

De visar var systemet brister och var det kostar pengar.

Mönster 8: hallucinerade argument

LLM:er hittar ibland på parametervärden. De anropar search_customers(email="...") med en adress som inte motsvarar frågan eller book_meeting(date="...") med ett datum som aldrig nämnts.

Motåtgärder:

Strikt schema med beskrivningar. ”user_id måste ha nämnts tidigare i konversationen. Hitta inte på ID:n.”

Validering i verktygsomslaget. Om värdet är orimligt, exempelvis ett okänt användar-ID eller ett passerat datum, returnerar verktyget ett strukturerat fel så att modellen omprövar.

Reflektion. ”Bekräfta före anropet att värdena har stöd i konversationen.”

Begränsade verktygsbeskrivningar. Verktyg för specifika entiteter exponerar bara entitets-ID:n som redan hämtats. Exponera inte rå sökning.

Revisionsloggar. Fånga mönster av hallucinerade argument och justera prompter eller scheman.

Mönster 9: kontrollerad degradering

Verktyg och API:er fallerar, och hastighetsgränser nås. Rätt svar är sällan att säga att ingenting fungerar.

Mönster:

Cachelagrade eller inaktuella data. Returnera cachedata med uppgift om att de är inaktuella när livekällan är otillgänglig.

Partiellt slutförande. Om tre av fem deluppgifter lyckas, redovisa vad som utfördes och inte.

Reservvägar. Om huvudverktyget fallerar ska ett alternativ finnas dokumenterat i prompten eller verktygsuppsättningen. Om search_documents fallerar kan modellen exempelvis falla tillbaka på search_web med lämpliga förbehåll.

Användarsynliga feltillstånd. Om verktyget verkligen inte kan slutföra uppgiften ger modellen ett tydligt felmeddelande, inte ett hallucinerat lyckat resultat.

Modellen behöver känna till de här mönstren. Dokumentera mönstren i systemprompten:

Om ett verktyg returnerar ett fel:
- Prova det alternativa verktyget om ett sådant finns.
- Redovisa partiella resultat tydligt om användaren redan har lämnat information.
- Påstå aldrig att åtgärden lyckades när verktyget returnerade ett fel.

Mönster 10: strömmande strukturerade utdata

Att strömma partiella strukturerade utdata ger bra UX: användaren ser resultatet ta form i realtid.

Implementering:

  • De flesta moderna modell-API:er strömmar JSON token för token.
  • Tolka partiell JSON inkrementellt med exempelvis partial-json-parser eller en liten egen strömtolk.
  • Uppdatera gränssnittet när fälten anländer.

Det fungerar särskilt bra för utdata med flera avsnitt, såsom en lång produktbeskrivning, en analys med flera insikter eller en kodgranskning med flera fynd – de upplevs som betydligt mer responsiva när de strömmas.

Förbehåll: fatta inga beslut utifrån partiella utdata. Strömma för visning, men invänta slutförandet innan det strukturerade resultatet används.

Mönster 11: funktionsanrop kontra explicita beslutsanrop

Inbyggda funktionsanrop är praktiska: modellen ”bestämmer” när ett verktyg ska användas. I vissa arbetsflöden är ett explicit beslutsanrop mer tillförlitligt.

Exempel: ett supportflöde där modellen väljer mellan flera åtgärder.

Inbyggt funktionsanrop: Ge modellen fem verktyg (refund, send_article, escalate_to_human, ask_clarifying_question, close_ticket) och låt den välja.

Explicit beslutsanrop: Anropa först modellen med ett enda verktyg, decide_action, vars schema namnger de tillåtna åtgärderna. Värdsystemet prövar beslutet mot en deterministisk policy och exponerar sedan enbart den operation som är tillåten härnäst.

Det explicita sättet är långsammare och utförligare, men det ger värdsystemet en policykontrollpunkt och en smalare verktygsuppsättning i nästa steg. Om det höjer uppgiftsframgången beror på arbetslasten – jämför de båda sätten på samma märkta fall.

Det explicita sättet vinner ofta i arbetsflöden med höga insatser. Inbyggda funktionsanrop räcker för utforskande eller enkla flöden.

Mönster 12: formatering av verktygsresultat

Hur resultatet returneras spelar roll. Modellen läser det.

Dåligt:

{"id": "cus_123", "n": "John", "p": "12345"}

Bättre:

{
  "customer_id": "cus_123",
  "name": "John Doe",
  "phone": "+1-555-0123",
  "tier": "premium",
  "open_tickets": 0
}

Bäst (i vissa fall):

Kund hittad:
- ID: cus_123
- Namn: John Doe
- Nivå: Premium
- Telefon: +1-555-0123
- Öppna ärenden: 0

Kunden tillhör premiumnivån och har inga öppna ärenden.

Den ”bästa” formen är människoläsbar, ger kontext och är enklare för modellen i efterföljande generering. Den ”bättre” är mer strukturerad och maskinläsbar. Testa vilken modellen hanterar bäst för nedströmsuppgiften.

För vissa verktyg fungerar både strukturerad text och berättande text bra: ”Här är resultatet: [berättande]. Rådata: [JSON].”

Mönster 13: schemamedvetna omförsök

Vissa valideringsfel går inte att reparera eftersom modellen i grunden missförstått uppgiften. Andra är enkla att rätta. Klassificera felet och agera därefter.

def handle_validation_error(error):
    # Pydantic exposes stable structured errors; do not branch on human text.
    issues = error.errors()
    feedback = []
    for issue in issues:
        location = ".".join(str(part) for part in issue["loc"])
        feedback.append({
            "field": location,
            "type": issue["type"],
            "message": issue["msg"],
        })
    return retry_with_json({"validation_errors": feedback})

Anpassade omförsök lyckas oftare än generella.

Mönster 14: komponerbarhet

Verktyg ska kunna kombineras. Små, fokuserade verktyg som gör en sak kan sättas ihop av modellen till komplexa arbetsflöden.

Ett monolitiskt verktyg process_customer_request(query) som gör allt är en svart låda. Modellen kan varken observera eller styra den interna logiken.

Fokuserade verktyg – search_customer(email), get_recent_orders(customer_id), check_subscription_status(customer_id), escalate_to_human(reason) – kan kombineras till rätt flöde för varje situation.

Välj rätt granularitet. Varje verktyg gör en sak. Verktygen sätts samman till arbetsflöden.

Mönster 15: schema för ”jag vet inte”

Modellera osäkerhet explicit i schemat:

class CustomerInfo(BaseModel):
    name: str
    name_confidence: Literal["high", "medium", "low"]
    needs_clarification: bool
    clarification_question: Optional[str] = None

Modellen kan returnera ”low confidence” och en förtydligande fråga i stället för att fabulera. Det är betydligt bättre än en modell som alltid fyller i fälten tvärsäkert, ibland med hallucinerade data.

Genomarbetat exempel: fakturahantering

Så här ser ett produktionsmässigt fakturahanteringssystem ut.

Indata: PDF-faktura bifogad till e-post. Mål: Extrahera strukturerade data och dirigera dem till ekonomisystemet.

Schema:

class LineItem(BaseModel):
    description: str
    quantity: float
    unit_price: float
    total: float
    confidence: Literal["high", "medium", "low"]

class Invoice(BaseModel):
    vendor_name: str
    vendor_id: Optional[str] = None  # null om leverantören saknas i våra register
    invoice_number: str
    invoice_date: date
    due_date: Optional[date] = None
    line_items: List[LineItem]
    subtotal: float
    tax: float
    total: float
    currency: str  # ISO 4217
    confidence: Literal["high", "medium", "low"]
    needs_review: bool
    review_reasons: List[str]  # Specifika skäl till att granskning behövs

Arbetsflöde:

  1. OCR-steg: En bildmodell extraherar text ur PDF-filen.
  2. Extraktionssteg: LLM-anrop med schemat ovan och begränsad generering.
  3. Valideringssteg: Pydantic validerar. Valideringsfel utlöser ett omförsök med felåterkoppling.
  4. Avstämningssteg: Ett verktyg söker leverantören i registren. Matchar vendor_name, lägg till vendor_id. Annars sätts needs_review=true.
  5. Matematikkontroll: Verifiera sum(line_items.total) ≈ subtotal och subtotal + tax ≈ total. Sätt annars needs_review=true.
  6. Tilltroskontroll: Sätt needs_review=true om confidence är low eller någon rad har låg tilltro.
  7. Dirigering: Skicka till mänsklig granskningskö om needs_review=true, annars till ekonomisystemet.
  8. Loggning: Logga varje stegs indata, utdata, varaktighet och fel.

Hanterade fellägen:

  • Felaktig JSON: begränsad generering förebygger, omförsök hanterar specialfall.
  • Hallucinerade fält: schemat är strikt.
  • Räknefel: valideras.
  • Okända leverantörer: flaggas.
  • Låg tilltro: flaggas.
  • Verktygsfel: hanteras explicit.

Låna inte en procentsats för helt automatisk hantering från en artikel. Bygg en märkt uppsättning av de fakturaformat, språk, valutor, skanningar och undantagstyper du faktiskt tar emot. Kom överens om fältnivåprecision, avstämning av belopp, tolerans för felaktiga automatgodkännanden och vilka leverantörer eller belopp som alltid ska granskas – innan något automatiseras. Kör i skuggläge, redovisa varje felkategori och släpp bara fram skrivningar till ekonomisystemet för den delmängd som klarar den överenskomna nivån.

Detta är produktionsmässiga strukturerade utdata. Inte bara ”JSON-läget fungerade en gång”, utan en pipeline för verkliga fellägen.

Vanliga misstag

Några mönster vi ser gång på gång:

Misstag 1: Ingen validering. Pydantic, zod eller något annat – validera bara. Lita inte på modellen.

Misstag 2: Vaga beskrivningar. ”category: string” hjälper inte modellen. ”category: ett av billing, technical, account_access, där billing omfattar …” gör det.

Misstag 3: Irrelevanta verktyg. En bred verktygskatalog ökar modellens urvalsbörda. Exponera bara verktyg som är relevanta och tillåtna i det aktuella tillståndet, och ta reda på vilket antal som fungerar med en utvärdering av verktygsval – inte med en universell regel om ”färre än tio”.

Misstag 4: Inget omförsök vid valideringsfel. Ett felaktigt resultat slår ut hela flödet. Försök en gång till med återkoppling.

Misstag 5: Ingen observerbarhet. Utan spår går det inte att diagnostisera produktionsfel.

Misstag 6: Ingen idempotens för verktyg med sidoeffekter. Dubbla återbetalningar och e-postmeddelanden är ett förutsägbart fel.

Misstag 7: Lita på LLM-valda argument utan validering. Hallucinerade användar-ID:n och datum. Validera argumenten före körning.

Misstag 8: Ingen schemaversionering. Schemaändringar bryter nedströmskonsumenter. Versionshantera.

Från demo till produktionssystem

Strukturerade utdata och funktionsanrop är bron från ”en LLM som talar” till ”en LLM som utför arbete”. Rätt utfört möjliggör de produktions-AI. Fel utfört går de sönder på intressanta och dyra sätt.

De viktiga mönstren är strikta scheman, begränsad generering, validering med omförsök, reflektion över verktygsresultat, idempotens, kontrollerad degradering, schemamedveten felhantering och heltäckande observerbarhet.

Varje sådant mönster skiljer en demo från ett produktionssystem. Bygg in dem från början.

Läs nästa

Fortsätt längs samma lärstig med nästa praktiska artikel.