Hi Friends,
Here is what nobody tells you when you first install the Laravel AI SDK.
The app will work. The agent will respond. The database will save the output. And you will look at what the AI produced and realize it is completely unusable for your actual use case.
That is exactly what happened to me this week.
I built a proposal generator for my freelance business, FabBuilds, using Laravel 13’s brand new AI SDK powered by Anthropic. Fresh Laravel 13 install. Clean agent class. The whole thing ran perfectly on the first try. Then I tried to generate a PDF, and it broke immediately.
Not because the SDK was wrong. Not because DomPDF was misconfigured. Because I had not been specific enough in the one place that controls everything: the instructions() method.
Here is what is inside this post:
What the instructions() method actually is and why most developers underestimate it
The exact mistake I made and what it broke downstream
The one line that fixed everything
The broader skill this teaches you about working with AI in production Laravel apps
You should subscribe to get the weekly deets!
What the Laravel AI SDK Looks Like
If you have not seen it yet, this is what a basic agent class looks like in Laravel 13:
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;
class ProposalAgent implements Agent, Conversational
{
use Promptable;
public function instructions(): Stringable|string
{
return 'Your system prompt goes here.';
}
public function messages(): iterable
{
return [];
}
}
Clean. Simple. Very Laravel. The instructions() method is your system prompt. It defines the agent’s role, behavior, constraints, and output format. Everything the agent produces flows from what you return here.
And that is exactly why it is so easy to get wrong
The Mistake I Made
My original instructions told Claude to write professional proposals with clear headings and proper formatting. That is exactly what it did.
It formatted everything beautifully in markdown.
## Project Overview
**Thank you for reaching out.** This proposal outlines...
### Scope of Work
- Discovery call
- Design and development
- Mobile responsive build
Raw symbols. In a browser rendered as plain text. And when DomPDF tried to generate a PDF from that output it had no idea what to do with it because DomPDF renders HTML not markdown.
The AI was not wrong. It made a completely reasonable assumption because markdown is a common formatting standard. I just had not told it what I actually needed.
The Fix
Here is the updated instructions() method that solved it:
public function instructions(): Stringable|string
{
return 'You are a professional freelance proposal writer for FabBuilds,
a boutique web development studio specializing in Laravel and Next.js
applications. You write clear, confident, and professional proposals
that communicate value without being salesy. Your proposals are warm
but authoritative. Always include these sections in order: Project
Overview, Scope of Work, Deliverables, Timeline, Investment, Payment
Terms, and Next Steps. Keep the tone professional but human. Never
use filler phrases. Get straight to the point. Output ONLY valid HTML
using these elements:
for section headings,
for paragraphs,
and - for lists. Do not use markdown. Do not include ,
, , or any wrapping tags — only the inner content. Do not
include any CSS or style attributes.';
}
That addition at the end changed everything:
Output ONLY valid HTML using these elements. Do not use markdown. Do not include any wrapping tags. Only the inner content. No CSS or style attributes.
The agent now outputs clean semantic HTML every single time. My Blade view renders it with {!! $proposal->generated_proposal !!}. DomPDF picks it up cleanly. The email client button strips tags for plain text. Everything downstream works without touching a single other file.
One change in one method. The whole pipeline fixed itself.
The Real Skill Here
When you write your instructions() method you are not writing a description of what you want. You are writing a contract that specifies exactly what the AI must produce. Not approximately. Not generally. Exactly. Down to the specific HTML elements. Down to what it must not include. Down to the tone of every sentence.
The more constraints you give a production AI agent the more predictable and useful its output becomes. That feels counterintuitive because we are used to thinking that constraints limit creativity. In production AI work constraints are the whole game.
For Laravel developers this matters more than it might for other frameworks because your AI output never lives in isolation. It gets stored in a database, rendered in a Blade view, passed to DomPDF, sent in an email, displayed in a dashboard. Every one of those contexts has specific format requirements that your upstream prompt needs to account for before the agent ever runs.
Most tutorials show you how to call the agent. Nobody talks about what happens when the output hits every other layer of your application. That is the skill worth developing.
What the Full Pipeline Looks Like Now
After fixing the instructions() method here is how the entire flow works:
User fills in the proposal form with client and project details

Controller validates the input and builds a detailed prompt string
ProposalAgent receives the prompt and generates clean HTML
HTML saves to the database in the generated_proposal column
Blade view renders it with {!! $proposal->generated_proposal !!}
DomPDF picks up the HTML directly for PDF export
Email client button strips tags for the plain text email body

One instructions() method. Every layer handled.
What I Am Building Next
The proposal generator is live, and I am using it in my actual freelance workflow now. Next up is a full email flow, so proposals are sent directly from inside the app.
And if you are a Laravel developer building with AI, subscribe!
Let’s Build It Beautifully,
Fab