Validering av strukturerad output från LLM:er i Python som håller
Sluta tolka stämningar. Validera kontrakt.
De flesta tutorials om “strukturerad utdata” från stora språkmodeller (LLM) är oseriösa. De lägger upp det som att du ska be artigt om JSON och sedan hoppas att modellen beter sig. Det är inte validering. Det är optimisme med klammermärken.
OpenAI:s egen dokumentation gör skillnaden tydlig. JSON-läget ger dig giltig JSON, medan Strukturerad Utdata (Structured Outputs) säkerställer att schemat följs, och OpenAI rekommenderar att man använder Strukturerad Utdata istället för JSON-läget när det är möjligt.

Det gör dock inte automatiskt att utdata är tillförlitlig. JSON Schema definierar struktur och tillåtna värden, Pydantic ger typvalidering i Python, och OpenAI påpekar explicit att ett svalgiltigt svar fortfarande kan innehålla felaktiga värden. Dessutom kan avslag och ofullständiga utdata kringgå den form du förväntade dig. I produktion är validering av strukturerad utdata en pipeline, inte en enkel på/av-knapp. Denna gräns måste också leva inom den större kontexten av genomströmning, återförsök och gränser för schemaläggaren på LLM-performance engineering-hubben.
Validering av strukturerad utdata är ett avtal
Validering av strukturerad utdata för LLM:er innebär att du definierar formen på svaret i förväg, begränsar modellen att producera den formen när det är möjligt, och sedan validerar resultatet igen innan din applikation litar på den. I praktiska termer innebär det att du kontrollerar obligatoriska fält, typer, enumereringar (enums), stängda objektstrukturer och domänregler innan utdata når din databas, gränssnitt, kö eller efterföljande tjänst. JSON Schema finns för exakt denna typ av strukturell validering, Pydantic är byggd för att validera otillitlig data mot Python-typantydningar, och Pythons jsonschema-bibliotek ger dig ett direkt sätt att validera en instans mot ett schema.
Det finns också en tydlig uppdelning mellan två vanliga användningsområden. Om modellen ska svara användaren i ett strukturerat format, använd ett strukturerat svarformat. Om modellen ska anropa din applikations verktyg eller funktioner, använd “function calling” (funktionanrop). OpenAI:s dokumentation uttrycker denna skillnad tydligt, och för funktionanrop rekommenderar de att aktivera strict: true så att argumenten pålitligt följer funktionens schema.
Min starka åsikt är enkel. Behandla varje strukturerad LLM-svar som en API-gräns. När du börjar tänka i termer av avtal istället för prompts, blir arkitekturen renare, buggarna billigare, och hela problemet med “varför uppfunn modellen ett nytt fält i produktion” försvinner mestadels. Det är det verkliga svaret på “vad är validering av strukturerad utdata för LLM:er” och det är ett mycket bättre svar än “be modellen artigt om JSON.”
JSON-läge är inte validering
Om du ska minnas en sak från denna artikel, låt det vara denna. JSON-läge är inte schemavalidering. OpenAI:s Help Center säger att JSON-läge inte garanterar att utdatan matchar något specifikt schema, bara att det är giltig JSON och parsas utan fel. Guiden för Strukturerad Utdata säger samma sak på ett renare sätt. Både JSON-läge och Strukturerad Utdata kan producera giltig JSON, men endast Strukturerad Utdata säkerställer att schemat följs.
Den skillnaden betyder mer än vad människor medger. I sitt lanseringsinlägg för Strukturerad Utdata rapporterade OpenAI att gpt-4o-2024-08-06 med Strukturerad Utdata fick 100 procent på sina komplexa JSON-schema-evalueringsuppgifter, medan gpt-4-0613 fick under 40 procent. Du behöver inte behandla dessa siffror som universell sanning för att se den bredare punkten. Schema-tvingande ändrar misslyckandeytan från “vad som helst kan hända” till “avtalet är mycket tightare.”
Det finns fortfarande randfall, och att låtsas det motsatta är hur leksaksdemonstrationer blir till “pager duty” (akutlarm). OpenAI dokumenterar att modellen kan vägra en osäker förfrågan, och dessa avslag visas utanför din normala schemapath. Det dokumenterar också ofullständiga svar, inklusive fall som att träffa max_output_tokens eller en avbrott i innehållsfiltrering. Så FAQ-frågan “räcker JSON-läge för pålitlig LLM-utdata” har ett kort svar och ett längre. Det korta svaret är nej. Det längre svaret är att även strikt strukturerad utdata fortfarande behöver explicit hantering av misslyckanden.
Var strukturerad utdata fortfarande brister
Schema-tvingande minskar problemet. Det raderar det inte. I verklig trafik ser du fortfarande trasiga eller överraskande utdata av skäl som har lite med din prompt-formulering att göra.
Misslyckandemönster som är värda att designa för
Modeller och klienter är oense om detaljer. Du kan få extra prosa före eller efter JSON, Markdown-avgränsade block runt utdatan, eller ett verktygsanrop vars namn är giltigt men vars argument är JSON som inte matchar ditt Pydantic-modell. Streaming förvärrar det eftersom du kanske validerar en halvfärdig buffert. Defensiv kod bör anta “sträng in, kanske JSON inuti” snarare än “bytes på tråden matchar redan min modell.”
Leverantörs- och API-skillnader
Inte varje värd exponerar samma yta för strukturerad utdata. En stack kan ge dig en förstaklass schema-bunden komplettering, en annan kan endast garantera JSON-syntax, och lokala runtime-miljöer kan hänga efter värd-API:er. Det är en anledning till att FAQ-frågan “hur validerar du LLM JSON i Python” börjar med leverantörstvingande när det finns och fortfarande slutar med validering på Python-sidan. För en bredare vy av hur leverantörer jämförs, se jämförelse av strukturerad utdata över populära LLM-leverantörer. Om du kör modeller lokalt, gäller samma valideringspipeline efter att du normaliserat trådformatet, till exempel efter extraktion med Ollama som i strukturerad LLM-utdata med Ollama i Python och Go. När en runtime fortfarande omsluter JSON med udda prefix eller resonemangsspår, förvänta dig samma klass av parser-fel som beskrivs i Ollama GPT-OSS problem med strukturerad utdata.
Den Python-stack som faktiskt fungerar
Min rekommendation är avsiktligt tråkig. Först, låt modellleverantören tvinga fram det strukturella avtalet när det går. Först, validera den returnerade utdatan i Python med Pydantic. Tredje, använd explicit affärsregelvalidering för fakta som ett schema ensamt inte kan bevisa. Fjärde, testa avtalet med fixtures och motstridiga exempel istället för att vinka mot en skärmdump från en playground och kalla det färdigt. OpenAI:s dokumentation för Strukturerad Utdata, Pydantics valideringsmodell, Pythons jsonschema-verktyg och OpenAI:s egna exempel på evalueringsuppgifter för strukturerad utdata pekar alla i den riktningen.
Pydantic är rätt tyngdpunkt för Python. Det låter dig modellera utdatan som vanliga Python-typer, generera JSON Schema med model_json_schema(), och validera rå JSON med model_validate_json(). Pydantics dokumentation noterar också att model_validate_json() generellt sett är den bättre vägen än att först göra json.loads(...) och sedan validera, eftersom den tvåstegs-ruttan lägger till extra parserarbete i Python.
Om du har fristående schemafiler i ditt repo, eller om du vill att CI ska validera fixture-utdata oberoende av modellkod, ger Pythons jsonschema-paket den enklaste möjliga avtalskontrollen med jsonschema.validate(...). Om du vill ha det i pre-commit, finns check-jsonschema specifikt som ett CLI- och pre-commit-hook-byggd på jsonschema. Det är en mycket bra match för team som vill att schemaändningar ska granskas som kodändningar.
Ramverk kan minska piperörsarbete, men de tar inte bort behovet av faktisk validering. LangChain väljer nu automatiskt utleverantörens inbyggda strukturerad utdata när leverantören stöder det och faller tillbaka till en verktygsstrategi annars. Instructor lager Pydantic-svarmodeller, validering, återförsök och multi-leverantörsstöd ovanpå modellanrop. Guardrails fokuserar på validerare och input/output-vaktposter. Användbara verktyg, alla. Men schemat och affärsreglarna tillhör fortfarande dig. Om du väljer mellan högnivåbibliotek, är jämförelsen BAML vs Instructor för Python en användbar följeslagare till denna artikel.
Ett minimalt exempel med OpenAI och Pydantic
Det minsta produktionsvärda exemplet har några icke-förhandlingsbara punkter. Använd en stängd uppsättning enum-liknande värden där det är möjligt. Förbjud extra nycklar. Lägg till fältdescriptioner så att schemat är begripligt för människor och mer läsbart för modellen. Håll root-objektet explicit och tråkigt. OpenAI rekommenderar tydliga namn plus titlar och beskrivningar för viktiga nycklar, JSON Schema använder enum för att begränsa värden, och Pydantic kan stänga objektstrukturen med extra="forbid".
from typing import Literal
from openai import OpenAI
from pydantic import BaseModel, ConfigDict, Field
class TicketClassification(BaseModel):
model_config = ConfigDict(extra="forbid")
category: Literal["billing", "bug", "how_to", "abuse"] = Field(
description="Support ticket category."
)
priority: Literal["low", "medium", "high"] = Field(
description="Operational urgency."
)
needs_human: bool = Field(
description="Whether a human should review the case."
)
summary: str = Field(
description="A one sentence summary of the issue."
)
client = OpenAI()
response = client.responses.parse(
model="gpt-4o-2024-08-06",
input=[
{
"role": "system",
"content": "Classify support tickets. Return only the structured result.",
},
{
"role": "user",
"content": "Customer reports duplicate charges after refreshing checkout.",
},
],
text_format=TicketClassification,
)
result = response.output_parsed
print(result.model_dump())
Två detaljer i det exemplet är lätta att missa och absolut värda att bry sig om. extra="forbid" på Pydantic-sidan speglar JSON Schema-idén om additionalProperties: false, vilket också är ett krav för strikta verktygsscheman i OpenAI:s dokumentation för funktionanrop. Och enumereringar är inte kosmetiska. De är ett av de enklaste sätten att stoppa modellen från att uppfinna ett värde din kod inte förstår.
OpenAI:s Python SDK stöder client.responses.parse(...) med en Pydantic-modell tillhandahållen som text_format, och det parsade objektet returneras på response.output_parsed. Samma SDK stöder också client.chat.completions.parse(...), där det parsade objektet finns på message.parsed. Om du vill ha direkt extraktion av strukturerad data med minimalt lim, är dessa hjälpfunktioner den renaste startpunkten.
Parsa, normalisera, validera sedan
Strukturerad Utdata och model_validate_json tar bort mycket parser-smärta när stacken är justerad ända ut. Ögonblicket du stöder en leverantör som returnerar vanlig chatttext, en modell som omsluter JSON i staket, eller en loggningsväg som sparar den råa kompletteringssträngen, vill du ha en flaskhals som omvandlar text till en dict innan Pydantic kör igång.
import json
def parse_json_from_llm_text(text: str) -> dict:
cleaned = text.strip()
if cleaned.startswith("```"):
cleaned = cleaned.split("\n", 1)[1]
cleaned = cleaned.rsplit("```", 1)[0].strip()
# Vanligt prefix "Självklart, här är JSON:" före objektet.
if not cleaned.startswith("{") and "{" in cleaned and "}" in cleaned:
start = cleaned.find("{")
end = cleaned.rfind("}")
if end > start:
cleaned = cleaned[start : end + 1]
return json.loads(cleaned)
ticket_dict = parse_json_from_llm_text(raw_completion_text)
ticket = TicketClassification.model_validate(ticket_dict)
Den hjälpen är avsiktligt tråkig. Den hanterar inramade “json ... "-block och ett ledande naturlig-språk-preamble när utdatan fortfarande är ett enda toppnivå-objekt. Den är inte en fullständig JSON-extraktor. Om modellen nestar klammermärken inuti strängvärden kan naiv skivning brytas, och den rätta fixen är oftast striktare prompting, schema-bundna kompletteringar, eller en dedikerad parser-bibliotek.
Strömskompletteringar
Om du streamar chatttokens, kör inte json.loads eller model_validate_json på varje delta. Buffra tills API:n rapporterar ett avslutat meddelande (kontrollera din klient för strömavslut eller finish_reason), konkatera texten, och pars sedan en gång. Samma regel gäller när verktygsanropsargument kommer i bitar. Du validerar först efter att argumentsträngen är komplett.
chunks: list[str] = []
for chunk in completion_stream:
delta = chunk.choices[0].delta.content or ""
chunks.append(delta)
raw_completion_text = "".join(chunks)
ticket = TicketClassification.model_validate_json(raw_completion_text)
Du kan fortfarande skicka raw_completion_text genom parse_json_from_llm_text först när du förväntar dig staket eller prat kring JSON:en.
När du äger plain-string-parsing, är nästa begränsning ofta inte Python utan leverantörens JSON Schema-dialekt och vad det fjärr-API:n faktiskt accepterar.
Leverantörens schemabegränsningar (innan du blir smart i Python)
Dumpa inte blindt vilken schemagenerator-utdata som helst i ett API och anta att varje JSON Schema-funktion stöds. OpenAI stöder en delmängd av JSON Schema, kräver att alla fält är obligatoriska för Strukturerad Utdata, kräver att roten är ett objekt snarare än ett toppnivå-anyOf, och dokumenterar begränsningar på nestingdjup och totalt antal egenskaper. Håll leverantörsförsjda schemat enkelt. Det är inte en kompromiss. Det är bra engineering.
Om du behöver en leverantörsoberoende valideringsväg, eller om du vill validera sparade fixtures och mockar, är Pydantic plus jsonschema fortfarande en bra kombination.
from jsonschema import validate as validate_json
schema = TicketClassification.model_json_schema()
payload = {
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Checkout duplicates charges after refresh.",
}
validate_json(instance=payload, schema=schema)
ticket = TicketClassification.model_validate(payload)
print(ticket)
Detta mönster är särskilt användbart i tester, avtalsfixtures och integrationer där modellleverantören inte erbjuder inbyggd tvingande av strukturerad utdata. Kom bara ihåg att ett lokalt genererat schema kan vara bredare än en given leverantörs stödda delmängd, så “giltigt lokalt” betyder inte automatiskt “accepterat av varje LLM API”. Notera också att vissa leverantörer förbearbetar och cachar schema-artefakter, så den första förfrågan för ett nytt schema kan vara långsammare än varma förfrågningar.
Verktygsanrop är ett andra avtal
Funktion- eller verktygsanrop är den andra stora strukturerade utdataformen. Modellen väljer ett namn och skickar argument som bör matcha ett JSON Schema du kontrollerar. OpenAI rekommenderar strict: true på verktygsdefinitioner så att argumenten hålls i linje med schemat. I agent-tunga stackar blir dåligt sampling till ogiltig verktygs-JSON snabbt; håll sampler-inställningarna i linje med flerstegsarbete med referensen för agensinference-parametrar för Qwen och Gemma.
Avsnitten nedantill antar att du redan har kartlagt leverantörens verktygsanropsobjekt till en name-sträng och en arguments-dict, till exempel genom att pars tool_calls[].function på chattkompletteringar (JSON-strängargument blir json.loads först). dispatch_tool är steget efter den normaliseringen.
Två praktiska regler hjälper i Python. Först, validera verktygsnamnet mot en explicit tillåtlist innan du ruttar exekveringen. Först, validera argumentdicten med samma Pydantic-modell du använder i tester, inte med ad hoc-nyckelåtkomst. Misslyckandesläget du undviker är “giltiga JSON-argument, fel form för det verktyg som avlossades”, vilket glider förbi strängkontroller.
from typing import Any, Callable
from pydantic import BaseModel
ToolHandler = Callable[[dict[str, Any]], str]
def dispatch_tool(
*,
name: str,
arguments: dict[str, Any],
handlers: dict[str, tuple[type[BaseModel], ToolHandler]],
) -> str:
if name not in handlers:
raise ValueError(f"unsupported tool {name}")
model_cls, handler = handlers[name]
validated = model_cls.model_validate(arguments)
return handler(validated.model_dump())
handlers: dict[str, tuple[type[BaseModel], ToolHandler]] = {
"classify_ticket": (
TicketClassification,
lambda data: f"queued as {data['category']}",
),
}
Detta mönster håller ruttning och validering på ett ställe. Dina verkliga handläggare kommer att vara rikare, men uppdelningen bör vara densamma: tillåtna namn, typerade argument, sedan sidoeffekter.
Schemavalidering behöver fortfarande affärsregler
Ett giltigt objekt är inte samma sak som ett korrekt objekt. OpenAI säger detta direkt. Strukturerad Utdata förhindrar inte misstag inuti värdena i JSON-objektet. Det är varför FAQ-frågan “varför är både schemavalidering och affärsregelvalidering viktigt” har ett rakryggat svar. För att ett svar kan matcha schemat perfekt och ändå vara felaktigt på ett sätt som skadar affären.
Här är ett realistiskt exempel. Strukturen kan vara giltig, men prissättningslogiken kan fortfarande vara nonsens.
from decimal import Decimal
from typing import Literal
from typing_extensions import Self
from pydantic import BaseModel, ConfigDict, Field, model_validator
class Offer(BaseModel):
model_config = ConfigDict(extra="forbid")
currency: Literal["USD", "EUR", "GBP"]
amount: Decimal = Field(gt=0)
original_amount: Decimal | None
discounted: bool
@model_validator(mode="after")
def check_discount_logic(self) -> Self:
if self.discounted:
if self.original_amount is None:
raise ValueError(
"original_amount is required when discounted is true"
)
if self.original_amount <= self.amount:
raise ValueError(
"original_amount must be greater than amount"
)
return self
Den valideraren gör något som scheman ensamt ofta gör dåligt i verkliga system. Den kontrollerar korsfältsemantik efter att hela modellen har parsats. Pydantics model_validator finns exakt för denna typ av helhetsobjektvalidering. Notera Decimal | None-fältet utan standardvärde. Det håller fältet närvarande samtidigt som det tillåter null, vilket matchar OpenAI:s dokumenterade mönster för valfria-värden under strikt Strukturerad Utdata.
Om du vill att valideringsfel automatiskt ska matas tillbaka till modellen, är Instructor ett praktiskt lager ovanpå Pydantic. Dess dokumentation beskriver en återförsöksloop där valideringsfel fångas, formateras som feedback, och används för att be modellen försöka igen.
import instructor
retrying_client = instructor.from_provider("openai/gpt-4o", max_retries=2)
offer = retrying_client.create(
response_model=Offer,
messages=[
{
"role": "user",
"content": (
"Extract the offer from this text. "
"Was 49.00 USD, now 19.00 USD."
),
}
],
)
Detta är en av de få bekvämligheter jag gärna rekommenderar. Automatiska återförsök kopplade till verkliga valideringsfel är användbara. Tyst tvingande är det inte. Instructors modellager, återförsök-dokumentation och valideringsdokumentation lutar alla in i samma idé, och de har rätt att göra det.
Du kan implementera samma idé utan ett ramverk. Loopen är liten. Be modellen, validera med Pydantic, och om valideringen misslyckas, skicka tillbaka feldetaljerna i ett uppföljande användarmeddelande och be om korrigerad JSON endast. Begränsa försök, logga det slutliga misslyckandet, och visa upp ett kontrollerat fel för anropande. När du redan förlitar dig på responses.parse eller andra schema-bundna hjälpfunktioner, kommer du sällan att öva denna väg. Det är fortfarande viktigt för JSON-läge, äldre chattändpunkter, eller någon gateway som ger dig en rå sträng.
from openai import OpenAI
from pydantic import ValidationError
client = OpenAI()
messages = [
{"role": "system", "content": "Return only JSON that matches the ticket schema."},
{"role": "user", "content": "Customer reports duplicate charges after refreshing checkout."},
]
ticket: TicketClassification | None = None
for attempt in range(2):
completion = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=messages,
response_format={"type": "json_object"},
)
raw_text = completion.choices[0].message.content or ""
try:
ticket = TicketClassification.model_validate_json(raw_text)
break
except ValidationError as exc:
messages.append(
{
"role": "user",
"content": f"Validation failed with {exc.errors()}. Return corrected JSON only.",
}
)
else:
raise RuntimeError("exhausted structured output retries")
assert ticket is not None
I verkliga tjänster skulle du bifoga spårnings-ID:n, redigera kundtext i loggar, och skilja på återhämtningsbara valideringsfel från avslag eller ofullständiga svar. Den viktiga delen är att återförsöket drivs av verklig valideringsutdata, inte av ett generiskt “försök igen”-meddelande.
Testa, återförsök, och misslyckas stängt
Vad ska hända när LLM-validering misslyckas? Inte axelryckning. Avvisa utdatan, logga misslyckandet, återförsök med begränsade försök om uppgiften är värd ett nytt försök, och misslyckas stängt istället för att normalisera skräp till något som bara ser acceptabelt ut. Det är också där många team glömmer att hantera avslag och ofullständiga utdata explicit, trots att leverantörsdokumentationen berättar att dessa vägar finns.
För OpenAI:s Responses API bör misslyckandehantering vara förstaklasskod, inte ett eftertanke. Variabeln är response från client.responses.create eller parse, inte completion från chattströmning annars i denna artikel.
if response.status == "incomplete":
raise RuntimeError(response.incomplete_details.reason)
content = response.output[0].content[0]
if content.type == "refusal":
raise RuntimeError(content.refusal)
Det är inte defensiv över-engineering. Det är direkt i linje med de dokumenterade misslyckandeslägena. Om modellen vägrar, håller du inte på ett svalgiltigt utdata. Om svaret är ofullständigt, håller du inte på ett svalgiltigt utdata. Behandla båda som explicita grenar i din kontrollflöde.
Du bör också testa avtalet utanför själva modellanropet.
import pytest
from jsonschema import validate as validate_json
from pydantic import ValidationError
def test_ticket_fixture_matches_schema():
payload = {
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Checkout duplicates charges after refresh.",
}
validate_json(instance=payload, schema=TicketClassification.model_json_schema())
def test_discount_logic_rejects_broken_offer():
with pytest.raises(ValidationError):
Offer.model_validate(
{
"currency": "USD",
"amount": "19.00",
"original_amount": "10.00",
"discounted": True,
}
)
def test_ticket_rejects_unknown_category_string():
with pytest.raises(ValidationError):
TicketClassification.model_validate(
{
"category": "refund",
"priority": "high",
"needs_human": True,
"summary": "Customer wants a refund.",
}
)
def test_ticket_rejects_extra_keys():
with pytest.raises(ValidationError):
TicketClassification.model_validate(
{
"category": "bug",
"priority": "high",
"needs_human": True,
"summary": "Broken flow.",
"severity": "critical",
}
)
Detta är rätt form av teststrategi för LLM-utdatavalidering i Python. Validera gyllene fixtures med jsonschema så att varje fält i avtalet övas. Validera semantik med Pydantic, och lägg sedan till motstridiga fall som olagliga enum-strängar, förbjudna extra nycklar, och korsfältsmotstridigheter du bryr dig om. Om du snapshotar verkliga modellutdata, rensa PII och behandla dem som regressionsfixtures.
Om ditt team lever i OpenAI-stacken, inkluderar Evals API också evalueringsrecept för strukturerad utdata specifikt för att testa och iterera på uppgifter som beror på maskinläsbara format. Och om du behåller råa schemafiler i repot, koppla in check-jsonschema i CI eller pre-commit. Skicka avtal, inte vibes.
Produktionskontroller som räddar dig senare
När validering misslyckas, är FAQ-svaret rakt. Avvisa utdatan, logga varför, återförsök med målinriktad feedback när uppgiften är värd ett nytt försök, och misslyckas stängt istället för att tvinga dålig data in i en kö.
En kort operationschecklista hjälper team att undvika upprepade incidenter.
- Logga schemaversion eller en hash av JSON Schema du skickade till leverantören så att du kan spela upp misslyckanden exakt.
- Redigera modellinputs och outputs i loggar. Strukturerade loggar är användlösa om de läcker kundtext.
- Emittera räkningar eller metrik för avslagshastighet, ofullständigt svarshastighet, valideringsfelhastighet och reparationsframgångshastighet. Spikar där slår gissningar när en modell eller prompt-ändring levererades.
Bredare observability för LLM-system-riktlinjer hjälper till att koppla in dessa signaler i dashboards, spår och SLO-granskningar när räkningarna finns.
Best practice är inte komplicerad. Använd leverantörs-sida Strukturerad Utdata eller strikta verktygsscheman när du kan. Normalisera rå text när du måste. Speglar avtalet i Python med Pydantic. Lägg till affärsregelvalidering för det schemat inte kan bevisa. Hantera avslag och ofullständiga svar som normala grenar. Testa avtalet tills det slutar vara en demo och börjar vara mjukvara. Mindre än det är bara prompt engineering cosplay.