The first version of an AI feature is almost always a direct API call. You send a prompt, get a response, parse it with a regex or a fragile JSON decoder, and move on. This works well enough for a demo. It falls apart at scale.
The Prototype Trap
Raw LLM calls have a seductive simplicity. There is nothing to configure, nothing to learn, and they get you to a working output in minutes. But as soon as you try to build on top of that output — to pass it to another step, validate it, or expose it through an API — the cracks appear:
- The output format is implicit. You know what you expect, but the model does not always agree.
- Error handling is brittle. A slightly different phrasing produces a completely different structure.
- Composition is manual. Passing the output of one call as the input to another requires ad-hoc parsing code in every integration.
- Testing is impossible. Without a defined schema, you cannot write deterministic assertions.
The Case for Typed Output Schemas
The single most impactful change you can make to an LLM-based feature is to define its output schema explicitly. In Waiflo, every step has a ws_output_schema — a JSON Schema definition of exactly what the step produces:
"ws_output_schema": {
"properties": {
"priority": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
"category": { "type": "string" },
"summary": { "type": "string", "maxLength": 280 }
},
"required": ["priority", "category"]
}This schema serves three purposes simultaneously. It instructs the model on the exact format to produce (injected into the prompt at runtime). It validates the output before it reaches downstream steps. And it serves as documentation for anyone integrating with the step's output.
Step Composition as First-Class Design
Once outputs are typed, composition becomes straightforward. A downstream step declares which inputs it needs, and the runtime can verify that the outputs of upstream steps satisfy those requirements. Dependencies become explicit:
{ "step_id": "draft_response", "ws_ref": "draft_response",
"depends_on": ["classify_ticket", "enrich_customer"] }This dependency graph is not just documentation — it is the execution plan. The runtime resolves the order, handles concurrency where possible, and propagates outputs automatically. You define the intent; the engine handles the sequencing.
Reusability Across Projects
When steps are self-contained units with explicit input and output contracts, they become reusable assets. A classify_sentiment step written for a customer support pipeline can be lifted directly into a content moderation pipeline with no changes. The step does not know or care about its context — only about its inputs and outputs.
This composability is what transforms a collection of one-off LLM calls into an engineering discipline. Steps become building blocks. Pipelines become compositions of proven components. The cognitive overhead of building a new AI feature drops dramatically when you can start from a library of tested, typed steps.
The Discipline Pays Off
Adding explicit schemas and step boundaries feels like overhead when you are writing your first prompt. But it is the same discipline that makes any software system maintainable: define interfaces clearly, hide implementation details, make dependencies explicit. The patterns that have worked in software engineering for decades work just as well for AI pipelines — because AI pipelines are software.