🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 6 Min Lesezeit
0

How to parse lots of PDFs and more into markdown, with Laravel

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

You want to ask questions about your own documents from a Laravel app. The AI SDK that shipped with Laravel 13 handles most of it: embeddings, vector queries, agents. What it doesn't handle is the part that comes first, where a user hands you a 40-page scanned PDF and you need clean text out of it.



That's the step this tutorial covers properly. The rest of the pipeline is here too, end to end: upload, parse, chunk, embed, search, answer.






What we're building



Users upload documents. We convert each one to Markdown, split it into chunks, embed the chunks into a pgvector column, and answer questions with an agent that searches those chunks. Postgres only, no separate vector database.



You'll need Laravel 13, Postgres with the pgvector extension available, and two packages:




CODE
composer require laravel/ai parseforartisans/laravel






Full disclosure: parseforartisans/laravel is my product, Parse for Artisans. It's a hosted parsing API with a Laravel-native SDK. If all your documents are digital PDFs with a real text layer, you can swap that piece for spatie/pdf-to-text and pay nothing; I'll point out where. The hosted API earns its keep when uploads are scans, .doc files, emails, or all of the above, which in my experience is what "users upload documents" actually means.






Migrations



Two tables: the document, and its chunks with an embedding each.




CODE
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::ensureVectorExtensionExists();
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('source_path');
$table->string('status')->default('pending');
$table->timestamps();
});
Schema::create('chunks', function (Blueprint $table) {
$table->id();
$table->foreignId('document_id')->constrained()->cascadeOnDelete();
$table->text('content');
$table->vector('embedding', dimensions: 1536)->index();
$table->timestamps();
});






The models are small. Chunk casts the vector to an array, and Document gets the relation the listener will use:




CODE
class Chunk extends Model
{
protected $guarded = [];
protected function casts(): array

{
return ['embedding' => 'array'];
}
}
class Document extends Model
{
protected $guarded = [];
public function chunks(): HasMany

{
return $this->hasMany(Chunk::class);
}
}









Upload and parse



Store the upload on your disk, then point the parser at the stored path. The SDK resolves paths like Storage does, so there's no temp-file juggling:




CODE
use App\Models\Document;
use Illuminate\Http\Request;
use ParseForArtisans\Facades\Parse;
public function store(Request $request)
{
$request->validate([
'document' => ['required', 'file', 'mimes:pdf,doc,docx,xlsx,pptx,msg,eml', 'max:51200'],
]);
$path = $request->file('document')->store('uploads', 's3');
$document = Document::create(['source_path' => $path]);
Parse::disk('s3')->file($path)->for($document)->parse();
return back()->with('status', 'Processing.');
}






Parsing is async. ->for($document) ties the job to your model, so when the result comes back you get your record handed to you instead of matching ids. Scanned PDFs are OCR'd automatically; you don't tell it the file type.



The spatie alternative: replace the Parse:: line with Pdf::getText(Storage::disk('s3')->path($path)) and do the chunking inline. Works well until the first scan or .docx shows up, because pdftotext reads text layers and nothing else.






Chunk and embed when parsing completes



The Markdown arrives in a ParseCompleted event. Listen for it, split the text, and embed all chunks in one call:




CODE
use Laravel\Ai\Embeddings;
use ParseForArtisans\Events\ParseCompleted;
public function handle(ParseCompleted $event): void
{
$document = $event->request->parsable; // your Document model, typed
$markdown = $event->request->markdown();
$chunks = $this->split($markdown);
$embeddings = Embeddings::for($chunks)->generate()->embeddings;
foreach ($chunks as $i => $content) {
$document->chunks()->create([
'content' => $content,
'embedding' => $embeddings[$i],
]);
}
$document->update(['status' => 'ready']);
}






For splitting, start dumb. Markdown gives you natural seams, so split on headings, then cap the size:




CODE
/** @return string[] */
private function split(string $markdown, int $maxLength = 2000): array
{
$sections = preg_split('/^(?=#{1,3} )/m', $markdown);
return collect($sections)
->flatMap(fn ($s) => str_split(trim($s), $maxLength))
->filter(fn ($s) => strlen($s) > 50)
->values()
->all();
}






This is the crudest chunker that works, and clean Markdown input is what makes it viable. When your parser preserves headings and table structure, heading-based splits keep related content together. When your input is the word soup that OCR tools produce, no chunking strategy saves you. Garbage in, garbage embedded.






Ask questions



The AI SDK's agents can search your chunks with the built-in similarity search tool. Make an agent:




CODE
php artisan make:agent DocumentAssistant

namespace App\Ai\Agents;
use App\Models\Chunk;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Tools\SimilaritySearch;
class DocumentAssistant implements Agent, HasTools
{
use Promptable;
public function instructions(): string

{
return 'Answer using the document excerpts you retrieve. '
.'If the excerpts do not contain the answer, say so.';
}
public function tools(): iterable

{
return [
SimilaritySearch::usingModel(Chunk::class, 'embedding', limit: 8),
];
}
}

$response = (new DocumentAssistant)->prompt(
'What termination notice period does the supplier contract require?'
);
echo $response;






The agent embeds the question, pulls the nearest chunks, and answers from them. If you'd rather control retrieval yourself, the query builder does it directly:




CODE
$chunks = Chunk::query()
->whereVectorSimilarTo('embedding', 'termination notice period')
->limit(8)
->get();






Passing a string makes the SDK generate the query embedding for you.






Scaling notes



Ten thousand files work the same way as one. Parse::files($paths) submits a batch, and each file fires its own ParseCompleted event, so the listener you already wrote is the whole pipeline. Queue the listener (implements ShouldQueue) so embedding runs off the request path.



One alternative before you build any of this: if you don't want to own chunking and a vector column at all, the AI SDK also supports provider vector stores. Stores::create(), add files, and give an agent the FileSearch tool. You give up control over chunking and your data lives with the provider, but it's less code. I like owning the Postgres side; on a quick prototype I'd take the store.



That's the pipeline: a controller, a listener, an agent. The reason it stays this short is that every step downstream of parsing gets to assume clean Markdown. That assumption is the entire game in document RAG, and it's decided at the step most tutorials skip.



Parse for Artisans is a document parsing API for Laravel that converts PDF, DOCX, scans, and 20+ formats to clean Markdown. There's a free tier of 15,000 pages a month.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
The Gemini desktop app is now available for Windows
1 Quelle
Windows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC
1 Quelle
Vorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to parse lots of PDFs and more into markdown, with Laravel

Thematisch verwandte Begriffe: parse, lots, PDFs, more · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...