Stop writing prompt spaghetti.

THIS WEEK: Part 2 of 3. DSPy, Instructor and Outlines: turning output that usually works into output that cannot break.

Dear Reader…

 RAG system that dazzles in the demo and falls over in production is almost never a model problem. It is an output problem. The model returns something that is almost structured, the JSON is subtly malformed, and the dashboard or database behind it chokes. Or it runs beautifully for a month, you switch to a cheaper model to save on the bill, and every carefully hand-tuned prompt quietly stops behaving. That is the tax teams pay for treating prompts as clever strings instead of as engineered, testable output, and it is the single biggest reason promising prototypes never ship.

Last time we built a foundation worth querying. This time we close that reliability gap, turning retrieved context into answers you can depend on every time, and not just the time you happened to be watching.

Hand-tuning a prompt until it works is the mechanic's move. Engineering the output so it cannot break is the architect's, and it is the more valuable one. Three open-source tools get you there.

Treat prompts as code: DSPy

The root of "prompt spaghetti" is that most pipelines hardcode their instructions as long, brittle strings. They work until something changes, a new model, a new edge case, and then they fail in ways nobody can quite trace. DSPy ends this by refusing to treat prompts as strings at all. It treats them as trainable parameters of a program.

You declare what you want with a signature, for example context, question -> answer, and DSPy handles the wording. Modules add reasoning techniques like Chain-of-Thought or ReAct as composable layers. You describe the shape of the task, not the exact phrasing, which is already a large step towards maintainability.

The real power is the compiler. Given a small validation set, DSPy's MIPROv2 optimiser bootstraps good worked examples from your own data, uses a teacher model to propose candidate instructions, and then runs a Bayesian search to find the combination that scores highest for your specific target model. One rule matters above all here: this is a build-time step. You compile offline, serialise the result to a file, and simply load it at run-time. Running the optimiser inside a live request is the anti-pattern that hands you non-deterministic latency and a runaway bill. Wire the compile step into an orchestrator such as Dagster and your prompts become versioned software assets with lineage and tests, not sticky notes scattered through the codebase.

The payoff is portability. When a better or cheaper model arrives, you recompile against it rather than rewriting dozens of prompts by hand. That alone changes the economics of maintaining a pipeline.

Make the output valid: Instructor

A well-shaped prompt still has to return data your downstream systems can actually consume. Instructor enforces that at the client, using Pydantic. You define the target schema once as a Python class. If the model returns invalid or incomplete JSON, Instructor catches the Pydantic validation error, appends the specific failure back into the conversation, and asks the model to correct its own output.

Its strengths are simplicity and reach. It works with any API that exposes JSON or function-calling, and it drops straight into an ordinary Python application. The cost is in the mechanism: every self-correction is another round trip, so it adds tokens and latency, and it leans on the model being capable enough to fix itself. Smaller open-weight models often are not, which is exactly where the next tool comes in.

Query Billions of Rows in Milliseconds.

FlightAware cut a core query from 6.4 seconds to 30 milliseconds, on Postgres.

TimescaleDB adds hypertables, up to 95% compression, and continuous aggregates so queries stay fast as data grows. Same SQL, no pipeline, no second database.

Make the output impossible to break: Outlines

Outlines removes the retry loop entirely by making invalid output impossible in the first place. It compiles your schema or regex into a finite state machine and, during generation, masks the model's vocabulary so that structurally invalid tokens simply cannot be selected. The result is a 100% schema guarantee with zero retries and zero validation latency. The model is not asked nicely to comply; it is physically prevented from doing anything else.

The catch is where it can run. That logit-level masking requires direct access to the inference engine, which means self-hosted, open-weight runtimes such as vLLM, llama.cpp or MLX. Point Outlines at a closed API like OpenAI, Anthropic or Gemini and it can only wrap the provider's own structured-output endpoint, so you are back to trusting the provider rather than holding the guarantee yourself.

So the choice between the two is not about quality, it is about where you run. Calling a hosted API, Instructor's self-correction is the pragmatic path, and you pay for reliability in retries. Running your own open-weight models, Outlines gives you a mathematical guarantee the API route cannot offer. Plenty of serious pipelines use DSPy to shape the program and Outlines underneath to enforce the shape.

Why a guarantee beats a good mood

Here is the fair objection. Is this not over-engineering, when modern models return clean JSON almost every time? Almost is the whole problem. At a thousand calls a day, a one per cent failure rate is ten broken records daily, each able to poison a downstream table or halt a workflow, and not one of them caught until a human notices something is off. Then add the second failure mode: the day you switch models to cut costs, all that hand-tuned prompt behaviour drifts, silently. A guarantee costs a little effort up front. An unguaranteed pipeline costs you at two in the morning, which is a far worse exchange rate.

This is the layer that separates people who can make a model talk from people who can make a system depend on it. Anyone can coax a good answer once. Engineering output that is correct by construction and portable across models is the architect's work, and it is what lets a business build on top of what you shipped.

Tool

What it gives you

The trade-off

DSPy

Prompts as compilable, portable code (signatures, modules, MIPROv2)

Compile offline; validation runs cost compute

Instructor

Schema-valid output via client-side Pydantic and self-correction

Works with any API, but retries cost tokens and latency

Outlines

A 100% schema guarantee via logit-level constrained decoding

Needs self-hosted, open-weight models for the real guarantee

Next time, the final stage. The pipeline is built and its output is trustworthy; now it has to survive real traffic. We will look at serving it under load, keeping its cost from running away, and seeing inside it when it starts to drift: Ollama, LiteLLM and Langfuse.

For now, one line to take with you. If your pipeline's reliability depends on the model being in a good mood, you do not have a product yet. You have a demo with good manners.

That’s a wrap for this week
Happy Engineering Data Pro’s