Typed Inputs and Structured Outputs¶
Miért fontos a typed contract egy skillnél?¶
Ha egy skillt több workflow, agent vagy application service használ, akkor nem elég az, hogy „nagyjából tudjuk”, milyen adatot vár és mit ad vissza.
A reusable capability akkor lesz stabil komponens, ha a hívó fél és a skill között van egy explicit contract:
Caller
↓
Typed Skill Input
↓
Skill Runtime
↓
Structured Skill Result
↓
Application mapping / validation
Ez ugyanaz az elv, mint egy API-nál vagy service interface-nél.
A cél nem az, hogy az LLM-et „objektumorientálttá” tegyük, hanem hogy a probabilistic model körül legyen egy deterministic interface boundary.
Free text input vs typed input¶
Egy skill természetesen kaphat free textet:
Review this PR and focus on architecture.
De reusable runtime-ban érdemes a task inputot strukturáltabbá tenni:
{
"repository": "org/payment-service",
"pull_request_number": 184,
"focus": ["architecture", "correctness"],
"include_tests": true
}
A typed input előnye:
- már modellhívás előtt validálható,
- egyértelmű required/optional mezők vannak,
- könnyebb verziózni,
- könnyebb logolni és evaluálni,
- könnyebb más skilllel vagy workflow-val komponálni,
- kisebb az implicit jelentések száma.
A skill ettől még kaphat natural language mezőt is:
{
"task": "Review this change for backwards compatibility",
"repository": "org/api-service",
"pull_request_number": 42
}
A struktúra és a természetes nyelv nem egymás ellentétei.
Input validation a modell előtt¶
Ha egy input determinisztikusan hibás, ne az LLM próbálja megjavítani.
Például:
{
"environment": "moon",
"service": "payment-service"
}
Ha az environment csak:
dev | staging | production
lehet, akkor:
input
↓
schema validation
↓
INVALID_INPUT
jobb, mint:
input
↓
LLM guesses that "moon" probably means staging
Általános szabály:
Amit a runtime biztosan tud validálni, azt ne bízzuk semantic model reasoningre.
Structured output mint skill API¶
Ha az eredményt ember olvassa, free text teljesen megfelelő lehet.
Ha viszont egy másik komponens fogyasztja, strukturált output kell.
Például ticket triage skill:
{
"category": "BILLING",
"priority": "HIGH",
"confidence": 0.91,
"reason": "The customer reports a duplicate charge."
}
A workflow így nem regexeli a választ:
if "HIGH" in model_output:
hanem typed mezőt használ:
result.priority == HIGH
Schema correctness vs semantic correctness¶
Ez kritikus különbség.
A structured output garantálhatja például, hogy:
priority ∈ {LOW, MEDIUM, HIGH}
De nem garantálja, hogy a modell helyesen választotta a HIGH értéket.
schema correctness
≠
semantic correctness
A schema ezt biztosítja:
{
"priority": "HIGH"
}
A semantic evaluation pedig azt vizsgálja:
Valóban HIGH volt ez az eset?
Ezért a typed contract csak az egyik reliability layer.
LLM-facing DTO vs domain object¶
Nagyon hasznos különválasztás:
LLM result
↓
SkillResult DTO
↓
validation / mapping
↓
Application / Domain model
Például egy refund skill outputja:
{
"recommendation": "REQUIRES_APPROVAL",
"amount": 140,
"currency": "EUR",
"reason": "Amount exceeds direct-approval threshold."
}
Az alkalmazás domain modellje közben lehet sokkal komplexebb:
RefundRequest aggregate
├── Money
├── CustomerAccount
├── ApprovalPolicy
├── PaymentTransaction
└── AuditMetadata
Nem kell az LLM-mel közvetlenül domain aggregate-et konstruáltatni.
Jobb:
simple LLM-facing DTO
↓
deterministic mapper
↓
rich domain model
Ez csökkenti a couplingot és megakadályozza, hogy a provider-specific schema szétfolyjon a domain layerbe.
Input DTO sem feltétlenül domain object¶
Ugyanez input oldalon is igaz.
A domainben lehet:
PullRequestReviewContext
komplex objektum több adattal.
A skillnek viszont lehet, hogy csak ez kell:
{
"repository": "org/service",
"pr_number": 184,
"focus": ["correctness", "tests"]
}
A skill contract legyen minimal capability contract, ne az egész alkalmazás belső állapotának tükrözése.
Optional mezők: missing, null és unknown¶
AI outputnál gyakori probléma, hogy összemosódik:
not provided
not applicable
unknown
failed to retrieve
Pedig ezek külön állapotok.
Gyenge schema:
{
"owner": null
}
Nem tudjuk, mit jelent.
Jobb lehet explicit state:
{
"owner_status": "UNKNOWN",
"owner": null
}
vagy result envelope:
{
"status": "PARTIAL_RESULT",
"data": {
"service": "payment-service"
},
"missing": ["service_owner"]
}
Ez különösen fontos agentic workflow-kban, mert a következő lépés ezek alapján dönthet.
Result envelope pattern¶
Egy általános skill result lehet:
{
"status": "SUCCESS",
"data": {},
"warnings": [],
"errors": [],
"confidence": 0.93
}
Lehetséges statusok:
SUCCESS
PARTIAL_RESULT
INVALID_INPUT
INSUFFICIENT_CONTEXT
NOT_AUTHORIZED
TOOL_UNAVAILABLE
FAILED
Ez jobb, mint amikor minden failure külön exceptionként vagy szabad szövegben jelenik meg.
Fontos azonban, hogy ne gyártsunk egy túl generikus, mindent elrejtő envelope-ot. A domain-specifikus result továbbra is legyen explicit.
Discriminated result type¶
Sok esetben még tisztább:
SkillResult
├── SuccessResult
├── InsufficientContextResult
├── NotAuthorizedResult
└── ToolFailureResult
Konceptuális JSON:
{
"type": "INSUFFICIENT_CONTEXT",
"missing": ["current deployment state"]
}
vagy:
{
"type": "SUCCESS",
"health": "DEGRADED",
"evidence": ["5xx rate increased to 18%"]
}
Ez nagyon jól komponálható workflow-kban.
Enums ott, ahol tényleg zárt a domain¶
Például:
severity = SEV1 | SEV2 | SEV3 | SEV4
jó enum.
Viszont ezt:
root_cause = DATABASE | NETWORK | CODE | OTHER
csak akkor érdemes lezárni, ha valóban ez a domain modell.
Túl szűk enum forcingot okozhat:
actual cause = expired certificate
model forced to choose NETWORK
Ilyenkor lehet jobb:
root_cause_category
root_cause_description
vagy explicit:
UNKNOWN
OTHER
Confidence mező: óvatosan¶
A modell által generált:
{
"confidence": 0.92
}
nem automatikusan kalibrált valószínűség.
Nem szabad úgy kezelni, mintha matematikailag azt jelentené:
92% chance that the answer is correct
A confidence lehet hasznos routing signal, de csak akkor, ha evaluation alapján tudjuk, hogyan viselkedik.
Például:
model confidence < 0.6 → human review
csak akkor valid policy, ha mérés igazolja.
Typed contracts skill compositionnél¶
Tegyük fel:
Incident Triage Skill
↓
Diagnostic Recommendation Skill
Ha az első free textet ad:
"Looks like a database issue, maybe severe."
akkor a másodiknak újra interpretálnia kell.
Jobb:
{
"severity": "SEV2",
"suspected_component": "database",
"evidence": ["connection timeout spike"],
"confidence": 0.81
}
Ezután a következő skill typed inputként kapja.
Skill A typed output
↓
validation
↓
Skill B typed input
Ez csökkenti a semantic driftet a láncban.
Contract compatibility¶
Ha v1 output:
{
"severity": "SEV2",
"summary": "..."
}
és v2:
{
"severity": "SEV2",
"summary": "...",
"evidence": []
}
akkor ez lehet backwards-compatible, ha evidence optional.
De ha:
severity enum változik
vagy:
summary string → structured object
az downstream caller számára breaking change lehet.
Ezért skill contractot ugyanúgy érdemes compatibility szempontból kezelni, mint API schema-t.
Példa: Deployment Decision Skill¶
Input:
{
"service": "payment-service",
"environment": "production",
"candidate_version": "7.5.0",
"current_health": "HEALTHY",
"test_status": "PASS"
}
Output:
{
"recommendation": "PROCEED",
"risk": "MEDIUM",
"reasons": [
"All required tests passed",
"Production is currently healthy"
],
"required_approvals": ["production-owner"]
}
Fontos: a skill recommendationt ad.
A tényleges deploy policy továbbra is deterministic application logic lehet:
recommendation == PROCEED
+
approvals satisfied
+
change window open
+
authorization
↓
allow deployment
Anti-pattern: JSON mint domain guarantee¶
Attól, hogy ezt kapjuk:
{
"authorized": true
}
még senki nincs authorizationnel felruházva.
Az authorizationt nem a modell állapítja meg végső autoritásként.
Ugyanez:
{
"price": 19.99
}
nem jelenti, hogy ez a valós aktuális ár, ha nem megbízható source-ból jött.
Anti-pattern: regex és parser recovery lánc¶
Gyenge flow:
"Return JSON please"
↓
free-form string
↓
strip markdown fences
↓
regex repair
↓
JSON parse
↓
random defaults
Ha a platform támogat schema-constrained structured outputot, használjuk azt.
explicit schema
↓
structured output
↓
typed DTO
↓
semantic/business validation
Anti-pattern: domain aggregate közvetlen model-outputként¶
Ha a domain object tele van:
- invariánsokkal,
- persistence metadata-val,
- service dependencykkel,
- authorization state-tel,
- belső lifecycle mezőkkel,
akkor ne kényszerítsük az LLM-et ennek teljes reprezentációjára.
Az LLM-facing schema legyen egyszerű, stabil és task-specific.
Takeaways¶
- A typed input/output contract deterministic boundaryt ad a skill köré.
- A schema strukturális correctnesset ad, nem semantic truthot.
- Validáljuk az inputot modellhívás előtt, ahol lehet.
- Használjunk LLM-facing DTO-t, majd determinisztikusan mapeljük domain objectre.
- Különítsük el a
missing,unknown,not applicableés failure állapotokat. - A result envelope vagy discriminated result type jól kezelhető workflow-ban.
- Enum csak valóban zárt domainnél legyen szűk.
- A modell confidence értékét ne tekintsük automatikusan kalibrált valószínűségnek.
- Typed skill composition csökkenti a láncban fellépő semantic driftet.
- Skill contractot compatibility és versioning szempontból is API-ként érdemes kezelni.