PHPackages                             yannelli/laravel-prompt-pipeline - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. yannelli/laravel-prompt-pipeline

ActiveLibrary[Templating &amp; Views](/categories/templating)

yannelli/laravel-prompt-pipeline
================================

A Laravel package for managing LLM prompt templates with safe Twig rendering, composable fragments, and chainable processing

v0.1.1(4mo ago)00[2 PRs](https://github.com/yannelli/laravel-prompt-pipeline/pulls)MITPHPPHP ^8.3CI passing

Since Jan 1Pushed 1mo agoCompare

[ Source](https://github.com/yannelli/laravel-prompt-pipeline)[ Packagist](https://packagist.org/packages/yannelli/laravel-prompt-pipeline)[ Docs](https://github.com/yannelli/laravel-prompt-pipeline)[ GitHub Sponsors](https://github.com/:vendor_name)[ RSS](/packages/yannelli-laravel-prompt-pipeline/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (15)Versions (6)Used By (0)

Laravel Prompt Pipeline
=======================

[](#laravel-prompt-pipeline)

A Laravel 12 package for managing LLM prompt templates with safe Twig rendering, composable fragments, Claude-optimized structure helpers, chainable input/output processing, tag/fragment exclusions, and content deduplication.

Table of Contents
-----------------

[](#table-of-contents)

1. [Installation](#installation)
2. [Database Schema](#database-schema)
3. [Core Concepts](#core-concepts)
4. [Models &amp; Traits](#models--traits)
5. [XmlBuilder](#xmlbuilder)
6. [Twig Functions](#twig-functions)
7. [Fragments](#fragments)
8. [Processing Pipeline](#processing-pipeline)
9. [Exclusion System](#exclusion-system)
10. [Deduplication](#deduplication)
11. [Configuration](#configuration)
12. [Artisan Commands](#artisan-commands)
13. [Exceptions](#exceptions)
14. [Events](#events)
15. [Full Integration Example](#full-integration-example)
16. [Testing](#testing)
17. [License](#license)

---

Installation
------------

[](#installation)

```
composer require yannelli/laravel-prompt-pipeline
```

```
php artisan vendor:publish --provider="Yannelli\PromptPipeline\PromptPipelineServiceProvider"
php artisan migrate
```

---

Database Schema
---------------

[](#database-schema)

### `prompt_pipeline_templates` table

[](#prompt_pipeline_templates-table)

ColumnTypeNotesidulidPrimary keynamestring(255)Human readable identifierslugstring(255), nullableMachine-friendly lookupcontenttextTwig template contenttypestring(100), nullableCategorization (system, fragment, etc.)metadatajson, nullableProcessors, exclusions, etc.templateable\_typestring(255)Polymorphic morph typetemplateable\_idstring(36)Polymorphic morph IDis\_activebooleanDefault: truesort\_orderintegerDefault: 0created\_attimestampupdated\_attimestamp**Indexes:**

- Composite: `(templateable_type, templateable_id, type)`
- Composite unique: `(templateable_type, templateable_id, slug)`

---

Core Concepts
-------------

[](#core-concepts)

### Prompt Templates

[](#prompt-templates)

Database-stored Twig templates that can belong to any Eloquent model via polymorphic relationship. Templates support variable interpolation, composable fragments, and XML structure helpers.

### Fragments

[](#fragments)

Reusable template snippets (type = 'fragment') that can be embedded in other templates. Safe alternative to Twig's native `include` which is blocked for security.

### Processing Pipeline

[](#processing-pipeline)

Chainable processors that transform data before rendering (input processors) and after rendering (output processors). Follows Laravel's Pipeline pattern.

### Exclusions

[](#exclusions)

System for excluding specific fragments or XML tags from rendering. Supports global config, per-render overrides, and custom providers.

### Safe Twig Rendering

[](#safe-twig-rendering)

Twig templating in sandbox mode with explicitly whitelisted functions, filters, and tags. No arbitrary PHP execution.

---

Models &amp; Traits
-------------------

[](#models--traits)

### PromptTemplate Model

[](#prompttemplate-model)

```
namespace Yannelli\PromptPipeline\Models;

class PromptTemplate extends Model
{
    protected $table = 'prompt_pipeline_templates';

    // Relationships
    public function templateable(): MorphTo

    // Scopes
    public function scopeActive(Builder $query): Builder
    public function scopeOfType(Builder $query, string $type): Builder
    public function scopeBySlug(Builder $query, string $slug): Builder
    public function scopeFragments(Builder $query): Builder

    // Methods
    public function render(array $variables = []): string
    public function isFragment(): bool

    // Casts
    protected function casts(): array
    {
        return [
            'metadata' => 'array',
            'is_active' => 'boolean',
        ];
    }
}
```

### HasPromptTemplates Trait

[](#hasprompttemplates-trait)

Apply to any model that owns templates.

```
namespace Yannelli\PromptPipeline\Traits;

trait HasPromptTemplates
{
    // Relationships
    public function promptTemplates(): MorphMany
    public function activePromptTemplates(): MorphMany

    // Scoped Relationships
    public function promptTemplatesOfType(string $type): MorphMany
    public function fragments(): MorphMany

    // Finders
    public function findPromptTemplate(string $slugOrId): ?PromptTemplate
    public function findFragment(string $slugOrId): ?PromptTemplate

    // Creators
    public function createPromptTemplate(array $attributes): PromptTemplate
    public function createFragment(string $slug, string $content, array $attributes = []): PromptTemplate

    // Rendering
    public function renderPromptTemplate(string $slugOrId, array $variables = []): string

    // Override in model to provide automatic variables
    public function promptTemplateVariables(): array
}
```

**Usage:**

```
use Yannelli\PromptPipeline\Traits\HasPromptTemplates;

class Organization extends Model
{
    use HasPromptTemplates;

    public function promptTemplateVariables(): array
    {
        return [
            'organization_name' => $this->name,
            'organization_npi' => $this->npi,
        ];
    }
}
```

---

XmlBuilder
----------

[](#xmlbuilder)

Fluent builder for constructing XML-tagged prompt structures. Supports both predefined methods and dynamic tag creation via `__call()`.

### Basic Usage

[](#basic-usage)

```
use Yannelli\PromptPipeline\Structure\XmlBuilder;

$prompt = XmlBuilder::make()
    ->systemInstructions('You are a helpful assistant.')
    ->context($patientContext)
    ->task('Summarize the encounter.')
    ->build();
```

### Dynamic Tags

[](#dynamic-tags)

Any undefined method becomes an XML tag automatically. Method names convert from camelCase to snake\_case.

```
$prompt = XmlBuilder::make()
    ->patientDemographics('Name: John Doe')      //
    ->clinicalFindings($findings)                 //
    ->myCustomSection('content')                  //
    ->build();
```

### Method Signatures

[](#method-signatures)

All tag methods accept the same signature:

```
// Content only
->tagName('content')

// Content with attributes
->tagName('content', ['id' => '123'])

// Closure for nested content
->tagName(function (XmlBuilder $xml) {
    $xml->nested('content');
})

// Empty tag
->tagName()

// Attributes only
->tagName(null, ['status' => 'pending'])
```

### Core Methods

[](#core-methods)

```
class XmlBuilder
{
    // Factory
    public static function make(): static

    // Core tag method (all others delegate to this)
    public function tag(string $name, string|Closure|null $content = null, array $attributes = []): static

    // Raw content (no wrapping)
    public function raw(string $content): static

    // Blank line
    public function blank(): static

    // Conditional tag
    public function when(bool $condition, string $name, string|Closure|null $content = null, array $attributes = []): static

    // Manual open/close
    public function open(string $tag, array $attributes = []): static
    public function close(string $tag): static

    // Build final string
    public function build(): string

    // Get as array (for inspection)
    public function toArray(): array

    // Case transformation
    public function preserveCase(): static
    public function useSnakeCase(): static

    // Static helper
    public static function wrap(string $tag, string $content, array $attributes = []): string
}
```

### Predefined Claude Methods

[](#predefined-claude-methods)

These are explicitly defined for consistency and IDE autocomplete:

```
// System & Instructions
->systemInstructions(string $content): static
->instructions(string $content): static
->task(string $content): static
->context(string $content, ?string $label = null): static
->constraints(array $items): static
->rules(array $items): static

// Chain of Thought
->thinking(?string $content = null): static
->reasoning(?string $content = null): static
->answer(?string $content = null): static
->scratchpad(?string $content = null): static

// Documents
->document(string $content, array $attributes = []): static
->documents(array $docs, string $contentKey = 'content', ?string $nameKey = 'name'): static
->documentContent(string $content): static
->source(string $value): static

// Examples (Multishot)
->example(string|array $content, ?string $label = null): static
->examples(array $examples): static
->examplePair(string $input, string $output, ?string $label = null): static

// Output
->outputFormat(string|array $schema): static

// User Input
->userMessage(string $content): static
->query(string $content): static

// Utilities
->cdata(string $content): static
```

### Chain of Thought Helpers

[](#chain-of-thought-helpers)

```
// Add basic CoT instruction text
->cotBasic(): static
// Adds: "Think step-by-step before providing your answer."

// Add guided CoT with specific steps
->cotGuided(array $steps): static
// Adds numbered steps

// Add structured CoT instruction
->cotStructured(): static
// Adds instructions to use  and  tags
```

### Document Helpers

[](#document-helpers)

```
// Wrap multiple documents with proper structure
XmlBuilder::make()
    ->documents([
        ['name' => 'intake.pdf', 'content' => '...'],
        ['name' => 'notes.pdf', 'content' => '...'],
    ])
    ->build();
```

**Output:**

```

...

...

```

### Example (Multishot) Helpers

[](#example-multishot-helpers)

```
// Single example pair
XmlBuilder::make()
    ->examplePair(
        input: 'Patient reports sadness for 2 weeks',
        output: '{"symptom": "depressed_mood"}'
    )
    ->build();

// Multiple examples
XmlBuilder::make()
    ->examples([
        ['input' => '...', 'output' => '...'],
        ['input' => '...', 'output' => '...'],
    ])
    ->build();
```

**Output:**

```

Patient reports sadness for 2 weeks
{"symptom": "depressed_mood"}

```

### Long Context Pattern

[](#long-context-pattern)

For large document processing, Anthropic recommends documents at top, query at bottom.

```
XmlBuilder::make()
    ->documents($largeDocumentArray)     // TOP: Long content first
    ->instructions($taskInstructions)    // MIDDLE: Instructions
    ->scratchpad()                       // For recall assistance
    ->thinking()                         // CoT
    ->query($userQuery)                  // BOTTOM: Query last (30% improvement)
    ->build();
```

### Full XmlBuilder Example

[](#full-xmlbuilder-example)

```
$prompt = XmlBuilder::make()
    ->systemInstructions('You are a board-certified psychiatrist.')
    ->constraints([
        'Maintain HIPAA compliance',
        'Use clinical terminology',
        'Do not diagnose without sufficient evidence',
    ])
    ->patientContext(function ($xml) use ($patient) {
        $xml->name($patient->full_name);
        $xml->dob($patient->dob->format('Y-m-d'));
        $xml->mrn($patient->mrn);
    })
    ->documents($encounterDocuments)
    ->examples([
        [
            'input' => 'Patient reports feeling hopeless',
            'output' => '{"finding": "hopelessness", "category": "mood"}',
        ],
    ])
    ->context($clinicalHistory)
    ->cotStructured()
    ->thinking()
    ->task('Generate a clinical assessment.')
    ->outputFormat($schema)
    ->build();
```

---

Twig Functions
--------------

[](#twig-functions)

All functions are available in templates automatically.

### XML Tags

[](#xml-tags)

FunctionSignatureOutput`xml``xml(tag, content, attrs = {})``content``xml_open``xml_open(tag, attrs = {})````xml_close``xml_close(tag)````cdata``cdata(content)```### Claude-Specific Tags

[](#claude-specific-tags)

FunctionOutput`system_instructions(content)``...``instructions(content)``...``context(content, label = null)``...``task(content)``...``constraints(items)``- item1\n- item2``rules(items)``...``output_format(content)``...``user_message(content)``...``query(content)``...`### Chain of Thought

[](#chain-of-thought)

FunctionOutput`thinking(content = null)``content``thinking_open()````thinking_close()````reasoning(content = null)``...``answer(content = null)``...``answer_open()````answer_close()````scratchpad()````cot_basic()`"Think step-by-step..."`cot_guided(steps)`Numbered step instructions`cot_structured()`Instructions to use thinking/answer tags### Documents

[](#documents)

FunctionOutput`document(content, attrs = {})``...``documents_open()````documents_close()````document_content(content)``...``source(value)``value`### Examples (Multishot)

[](#examples-multishot)

FunctionOutput`examples_open()````examples_close()````example(content, label = null)``...``example_pair(input, output)``......`### Fragments

[](#fragments-1)

FunctionOutput`fragment(slug, vars = {})`Resolved fragment content### Utilities

[](#utilities)

FunctionOutput`json(value, pretty = false)`JSON encoded string`deduplicate(content)`Deduplicated content`deduplicate_whitespace(content)`Whitespace normalized`deduplicate_lines(content)`Duplicate lines removed---

Fragments
---------

[](#fragments-2)

Fragments are reusable template pieces stored with `type = 'fragment'`.

### Creating Fragments

[](#creating-fragments)

```
$organization->createFragment(
    slug: 'hipaa_reminder',
    content: 'Remember: Never include patient identifiers in your response.'
);

$organization->createFragment(
    slug: 'json_output',
    content:  'John'])
    ->render();

// From raw string
$rendered = PromptPipeline::fromString($templateString)
    ->withVariables($variables)
    ->render();

// Process LLM output (no template, just processors)
$cleaned = PromptPipeline::forOutput($llmResponse)
    ->outputProcessor(ExtractJsonBlock::class)
    ->run();
```

### Pipeline Methods

[](#pipeline-methods)

```
class Pipeline
{
    // Factory methods
    public static function make(PromptTemplate $template): static
    public static function fromString(string $template): static
    public static function processOutput(string $output): static

    // Variables
    public function withVariables(array $variables): static
    public function withModel(Model $model): static

    // Processors
    public function inputProcessor(string $class, array $config = []): static
    public function outputProcessor(string $class, array $config = []): static
    public function withoutDefaultProcessors(): static

    // Exclusions
    public function excludeFragments(array $slugs): static
    public function excludeTags(array $tags): static
    public function withExclusions(ExclusionSet $set): static
    public function withExclusionProvider(ExclusionProvider $provider): static

    // Execute
    public function render(): string
    public function run(): string  // Alias for output-only processing
    public function validate(): ValidationResult
}
```

### Execution Flow

[](#execution-flow)

**Full Render:**

1. Collect variables (system providers → model → user)
2. Run input processor chain on variables
3. Resolve fragments (respecting exclusions)
4. Render Twig template (respecting tag exclusions)
5. Run output processor chain on rendered string
6. Return final string

**Output-Only:**

1. Run output processor chain on provided string
2. Return processed string

### Built-in Input Processors

[](#built-in-input-processors)

ClassPurposeConfig`TrimWhitespace`Trims string values recursively`recursive: bool``SanitizeInput`Escapes potentially dangerous content`strict: bool``NormalizeLineBreaks`Converts \\r\\n to \\nnone`JsonEncodeArrays`JSON encodes array values`pretty: bool``EscapeXmlContent`Escapes XML special chars in specified keys`keys: array`### Built-in Output Processors

[](#built-in-output-processors)

ClassPurposeConfig`TrimOutput`Trims final outputnone`ExtractJsonBlock`Extracts JSON from markdown fences`fallback_raw: bool``StripMarkdownFences`Removes code fences`preserve_content: bool``NormalizeWhitespace`Collapses excessive whitespace`max_newlines: int``ExtractXmlTag`Extracts content from specific tag`tag: string``Deduplicate`Removes duplicate content`strategies: array`### Custom Processor

[](#custom-processor)

```
use Yannelli\PromptPipeline\Contracts\InputProcessor;

class InjectPatientContext implements InputProcessor
{
    public function __construct(
        protected array $config = []
    ) {}

    public function process(array $variables): array
    {
        if (!isset($variables['patient_id'])) {
            return $variables;
        }

        $patient = Patient::find($variables['patient_id']);
        $variables['patient_name'] = $patient->full_name;
        $variables['patient_dob'] = $patient->dob->format('m/d/Y');

        return $variables;
    }
}
```

```
use Yannelli\PromptPipeline\Contracts\OutputProcessor;

class ExtractClinicalJson implements OutputProcessor
{
    public function process(string $output): string
    {
        if (preg_match('/```(?:json)?\s*(\{[\s\S]*?\})\s*```/', $output, $matches)) {
            return $matches[1];
        }

        return $output;
    }
}
```

---

Exclusion System
----------------

[](#exclusion-system)

Allows excluding specific fragments and XML tags from rendering.

### ExclusionSet

[](#exclusionset)

Immutable set of exclusions for per-render use.

```
use Yannelli\PromptPipeline\Exclusions\ExclusionSet;

$exclusions = ExclusionSet::make()
    ->excludeFragments(['hipaa_reminder', 'legal_disclaimer'])
    ->excludeTags(['scratchpad', 'thinking']);
```

### ExclusionManager

[](#exclusionmanager)

Global exclusion management.

```
use Yannelli\PromptPipeline\Exclusions\ExclusionManager;

// Add global exclusions
ExclusionManager::excludeFragment('hipaa_reminder');
ExclusionManager::excludeTag('scratchpad');

// Check exclusions
ExclusionManager::isFragmentExcluded('hipaa_reminder');  // true
ExclusionManager::isTagExcluded('thinking');  // false

// Get all
ExclusionManager::excludedFragments();
ExclusionManager::excludedTags();

// Clear
ExclusionManager::clearAll();
```

### ExclusionProvider Contract

[](#exclusionprovider-contract)

Implement for dynamic exclusions (from database, user settings, etc.).

```
namespace Yannelli\PromptPipeline\Contracts;

interface ExclusionProvider
{
    public function excludedFragments(): array;
    public function excludedTags(): array;
}
```

**Implementation:**

```
class UserExclusionProvider implements ExclusionProvider
{
    public function __construct(
        protected User $user
    ) {}

    public function excludedFragments(): array
    {
        return $this->user->getSetting('excluded_fragments', []);
    }

    public function excludedTags(): array
    {
        return $this->user->getSetting('excluded_tags', []);
    }
}
```

### Pipeline Integration

[](#pipeline-integration)

```
// Using ExclusionSet
PromptPipeline::make($template)
    ->withExclusions($exclusions)
    ->render();

// Using provider
PromptPipeline::make($template)
    ->withExclusionProvider(new UserExclusionProvider($user))
    ->render();

// Inline
PromptPipeline::make($template)
    ->excludeFragments(['hipaa_reminder'])
    ->excludeTags(['thinking', 'scratchpad'])
    ->render();
```

### Exclusion Behavior

[](#exclusion-behavior)

**Fragments:** `{{ fragment('hipaa_reminder') }}` renders as empty string when excluded.

**Tags:** `{{ xml('thinking', content) }}` renders as empty string when excluded. The content is NOT rendered.

---

Deduplication
-------------

[](#deduplication)

Removes duplicate content, excessive whitespace, and repeated text from prompts.

### Output Processor

[](#output-processor)

```
use Yannelli\PromptPipeline\Processing\Output\Deduplicate;

PromptPipeline::make($template)
    ->outputProcessor(Deduplicate::class, [
        'strategies' => ['whitespace', 'blankLines', 'duplicateLines'],
    ])
    ->render();
```

### Available Strategies

[](#available-strategies)

#### `whitespace`

[](#whitespace)

Normalizes excessive whitespace.

```
[
    'normalize_spaces' => true,      // Multiple spaces to single
    'trim_lines' => true,            // Trim trailing whitespace per line
    'preserve_indentation' => false, // Keep leading whitespace
]
```

#### `blankLines`

[](#blanklines)

Removes excessive blank lines.

```
[
    'max_consecutive' => 2,  // Max blank lines in a row
    'trim_start' => true,    // Remove leading blank lines
    'trim_end' => true,      // Remove trailing blank lines
]
```

#### `duplicateLines`

[](#duplicatelines)

Removes duplicate consecutive lines.

```
[
    'case_sensitive' => false,
    'ignore_whitespace' => true,
]
```

#### `duplicateSentences`

[](#duplicatesentences)

Removes duplicate sentences anywhere in text.

```
[
    'case_sensitive' => false,
    'similarity_threshold' => 0.85,  // 1.0 = exact match only
    'keep_first' => true,
]
```

### Twig Filters

[](#twig-filters)

```
{{ transcript | deduplicate }}
{{ transcript | deduplicate(['whitespace', 'duplicateLines']) }}
{{ content | deduplicate_whitespace }}
{{ content | deduplicate_lines }}
```

### Standalone Usage

[](#standalone-usage)

```
use Yannelli\PromptPipeline\Processing\Output\Deduplicate;

$processor = new Deduplicate([
    'strategies' => ['whitespace', 'blankLines', 'duplicateLines'],
]);

$cleaned = $processor->process($dirtyText);
```

---

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag="prompt-pipeline-config"
```

`config/prompt-pipeline.php`

```
return [
    /*
    |--------------------------------------------------------------------------
    | Database
    |--------------------------------------------------------------------------
    */

    'table_name' => 'prompt_pipeline_templates',

    'model' => \Yannelli\PromptPipeline\Models\PromptTemplate::class,

    /*
    |--------------------------------------------------------------------------
    | Rendering
    |--------------------------------------------------------------------------
    */

    'cache' => [
        'enabled' => env('PROMPT_PIPELINE_CACHE', true),
        'path' => storage_path('framework/cache/prompt-pipeline'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Fragments
    |--------------------------------------------------------------------------
    */

    'fragments' => [
        'max_depth' => 3,
    ],

    /*
    |--------------------------------------------------------------------------
    | Sandbox Policy
    |--------------------------------------------------------------------------
    | Additional filters/functions to whitelist beyond defaults.
    | Blocked items cannot be overridden.
    */

    'sandbox' => [
        'allowed_filters' => [],
        'allowed_functions' => [],
    ],

    /*
    |--------------------------------------------------------------------------
    | Variable Providers
    |--------------------------------------------------------------------------
    */

    'providers' => [
        \Yannelli\PromptPipeline\Providers\DateTimeVariables::class,
        \Yannelli\PromptPipeline\Providers\EnvironmentVariables::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Default Processors
    |--------------------------------------------------------------------------
    */

    'processors' => [
        'input' => [],
        'output' => [],
    ],

    /*
    |--------------------------------------------------------------------------
    | Exclusions
    |--------------------------------------------------------------------------
    */

    'exclusions' => [
        'fragments' => [],
        'tags' => [],
        'provider' => null,
    ],

    /*
    |--------------------------------------------------------------------------
    | Deduplication Defaults
    |--------------------------------------------------------------------------
    */

    'deduplication' => [
        'default_strategies' => ['whitespace', 'blankLines'],

        'whitespace' => [
            'normalize_spaces' => true,
            'preserve_indentation' => false,
        ],

        'blankLines' => [
            'max_consecutive' => 2,
        ],

        'duplicateLines' => [
            'case_sensitive' => false,
        ],

        'duplicateSentences' => [
            'similarity_threshold' => 0.85,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Structure Defaults
    |--------------------------------------------------------------------------
    */

    'structure' => [
        'xml_method_case' => 'snake',  // snake, camel, preserve
        'xml_newlines' => true,
    ],

    /*
    |--------------------------------------------------------------------------
    | Missing Variable Behavior
    |--------------------------------------------------------------------------
    | How to handle undefined variables in templates.
    | Options: 'empty', 'error', 'keep'
    */

    'missing_variable_behavior' => 'empty',
];
```

---

Artisan Commands
----------------

[](#artisan-commands)

CommandPurpose`prompt-pipeline:validate {--file=} {--string=}`Validate template syntax and sandbox compliance`prompt-pipeline:render {template} {--variables=}`Test render a template`prompt-pipeline:variables`List all registered system variables`prompt-pipeline:fragments {--owner=}`List available fragments`prompt-pipeline:cache:clear`Clear compiled template cache**Examples:**

```
php artisan prompt-pipeline:validate --file=template.twig
php artisan prompt-pipeline:render my-template --variables='{"name":"value"}'
php artisan prompt-pipeline:variables
php artisan prompt-pipeline:fragments --owner="App\Models\Organization:1"
php artisan prompt-pipeline:cache:clear
```

---

Exceptions
----------

[](#exceptions)

ExceptionWhen Thrown`PromptRenderException`Twig syntax error or render failure`SandboxViolationException`Template uses blocked feature`FragmentNotFoundException`Referenced fragment doesn't exist`CircularFragmentException`Circular fragment reference detected`FragmentDepthExceededException`Fragment nesting exceeds max\_depth`MissingVariableException`Required variable not provided (when behavior = 'error')`ProcessorException`Processor fails during executionAll exceptions extend `PromptPipelineException`.

---

Events
------

[](#events)

EventPayload`PromptTemplateCreated``PromptTemplate $template``PromptTemplateUpdated``PromptTemplate $template``PromptTemplateDeleted``PromptTemplate $template``PromptRendering``PromptTemplate $template, array &$variables``PromptRendered``PromptTemplate $template, string $output``FragmentResolved``string $slug, PromptTemplate $fragment`---

Full Integration Example
------------------------

[](#full-integration-example)

### Setup

[](#setup)

```
// app/Models/Organization.php

use Yannelli\PromptPipeline\Traits\HasPromptTemplates;

class Organization extends Model
{
    use HasPromptTemplates;

    public function promptTemplateVariables(): array
    {
        return [
            'organization_name' => $this->name,
            'organization_npi' => $this->npi,
        ];
    }
}
```

### Create Templates

[](#create-templates)

```
// Create fragments
$org->createFragment('clinical_role', 'You are a clinical documentation specialist.');

$org->createFragment('hipaa_reminder', 'Never include patient identifiers in your response.');

$org->createFragment('json_schema',  'Clinical Assessment',
    'slug' => 'clinical_assessment',
    'type' => 'system',
    'content' => excludeFragments($user->getSetting('excluded_fragments', []))
            ->excludeTags($user->getSetting('excluded_tags', []));

        // Render prompt
        $prompt = PromptPipeline::make(
            $organization->findPromptTemplate('clinical_assessment')
        )
            ->withModel($patient)
            ->withVariables([
                'transcript' => $encounter->transcript,
                'encounter_type' => $encounter->type->label(),
                'output_schema' => ClinicalAssessmentSchema::toArray(),
            ])
            ->withExclusions($exclusions)
            ->inputProcessor(SanitizeInput::class)
            ->outputProcessor(Deduplicate::class)
            ->outputProcessor(TrimOutput::class)
            ->render();

        // Send to LLM
        $response = PrismAnthropicClient::structured(
            schema: ClinicalAssessmentSchema::forPrism(),
            userMessage: $prompt,
            temperature: 0.3,
        )->asStructured();

        // Process response
        $result = PromptPipeline::forOutput($response->text)
            ->outputProcessor(ExtractJsonBlock::class)
            ->run();

        return json_decode($result, true);
    }
}
```

### Using XmlBuilder Directly

[](#using-xmlbuilder-directly)

```
use Yannelli\PromptPipeline\Structure\XmlBuilder;

$prompt = XmlBuilder::make()
    ->systemInstructions('You are a clinical assistant.')
    ->constraints([
        'Maintain HIPAA compliance',
        'Use clinical terminology',
    ])
    ->patientInfo(function ($xml) use ($patient) {
        $xml->name($patient->full_name);
        $xml->dob($patient->dob->format('Y-m-d'));
        $xml->conditions($patient->conditions->pluck('name')->join(', '));
    })
    ->documents($encounter->documents->map(fn ($d) => [
        'name' => $d->filename,
        'content' => $d->content,
    ])->toArray())
    ->cotStructured()
    ->thinking()
    ->task('Summarize this clinical encounter.')
    ->outputFormat($schema)
    ->build();
```

---

Testing
-------

[](#testing)

```
composer test
```

---

Dependencies
------------

[](#dependencies)

### Required

[](#required)

- `php: ^8.3`
- `laravel/framework: ^12.0`
- `twig/twig: ^3.0`
- `spatie/laravel-package-tools: ^1.16`

### Development

[](#development)

- `pestphp/pest: ^3.0`
- `orchestra/testbench: ^10.0`

---

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

---

[Ryan Yannelli](https://ryanyannelli.com)

###  Health Score

35

—

LowBetter than 80% of packages

Maintenance85

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 91.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

132d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/53b64331d4de8c9cef47bc20707ed728c0e900cd2bda4ed7d95359ced8b9adf1?d=identicon)[yannelli](/maintainers/yannelli)

---

Top Contributors

[![yannelli](https://avatars.githubusercontent.com/u/59575788?v=4)](https://github.com/yannelli "yannelli (11 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

laraveltwigaitemplatesllmyannelliprompt-pipeline

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/yannelli-laravel-prompt-pipeline/health.svg)

```
[![Health](https://phpackages.com/badges/yannelli-laravel-prompt-pipeline/health.svg)](https://phpackages.com/packages/yannelli-laravel-prompt-pipeline)
```

###  Alternatives

[rcrowe/twigbridge

Adds the power of Twig to Laravel

9105.9M50](/packages/rcrowe-twigbridge)[ryangjchandler/blade-capture-directive

Create inline partials in your Blade templates with ease.

8222.2M12](/packages/ryangjchandler-blade-capture-directive)[spatie/laravel-blade-comments

Add debug comments to your rendered output

177325.5k](/packages/spatie-laravel-blade-comments)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[daikazu/laravel-glider

Start using Glide on-the-fly instantly in your Laravel blade templates.

882.3k](/packages/daikazu-laravel-glider)[combindma/dash-ui

A streamlined and stylish UI component library for Laravel Blade, crafted with TailwindCSS and AlpineJs for simplicity and elegance.

631.4k](/packages/combindma-dash-ui)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
