PHP Classes

File: docs/tracking.md

Recommend this page to a friend!
  Packages of Victor John Ukam   Prompt Deck   docs/tracking.md   Download  
File: docs/tracking.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: Prompt Deck
Organize versioned artificial intelligence promts
Author: By
Last change:
Date: 2 months ago
Size: 14,024 bytes
 

Contents

Class file image Download

Tracking & Performance

<a name="introduction"></a>

Introduction

Prompt Deck includes an optional database tracking system that logs prompt versions and execution data. This enables:

  • Performance monitoring ? Track token usage, latency, and cost per prompt version.
  • A/B testing ? Compare performance metrics across different prompt versions.
  • Audit trails ? Record what was sent to and received from AI providers.
  • Cost analysis ? Monitor API spending per prompt, model, and provider.
  • User feedback ? Attach ratings and comments to individual executions.

Tracking is entirely optional. Prompt Deck works fully without it.

<a name="setup"></a>

Setup

<a name="enable-tracking"></a>

Enable Tracking

Set the tracking configuration in config/prompt-deck.php:

'tracking' => [
    'enabled'    => env('PROMPTDECK_TRACKING_ENABLED', true),
    'connection' => env('PROMPTDECK_DB_CONNECTION'),
],

Or via environment variable:

PROMPTDECK_TRACKING_ENABLED=true

By default, tracking is disabled when APP_DEBUG=true and enabled in production.

<a name="publish-migrations"></a>

Publish Migrations

Publish and run the migrations to create the required tables:

php artisan vendor:publish --tag=prompt-deck-migrations
php artisan migrate

<a name="database-connection"></a>

Database Connection

By default, tracking uses your application's default database connection. To store tracking data on a separate database:

PROMPTDECK_DB_CONNECTION=analytics

The connection name must match a connection defined in config/database.php.

<a name="database-schema"></a>

Database Schema

<a name="prompt-versions-table"></a>

prompt_versions Table

Stores prompt version records and tracks which version is active.

| Column | Type | Nullable | Description | |---|---|---|---| | id | bigint | No | Auto-incrementing primary key. | | name | string | No | The prompt name (e.g. order-summary). | | version | unsigned int | No | The version number (e.g. 1, 2, 3). | | system_prompt | text | Yes | The system prompt content. | | user_prompt | text | No | The user prompt content. | | metadata | json | Yes | Version metadata as JSON. | | is_active | boolean | No | Whether this version is the active version. Default: false. | | created_at | timestamp | Yes | Creation timestamp. | | updated_at | timestamp | Yes | Last update timestamp. |

Indexes: - Unique index on (name, version) ? ensures no duplicate versions per prompt. - Index on (name, is_active) ? fast lookups for the active version.

<a name="prompt-executions-table"></a>

prompt_executions Table

Logs individual prompt executions with performance data.

| Column | Type | Nullable | Description | |---|---|---|---| | id | bigint | No | Auto-incrementing primary key. | | prompt_name | string | No | The prompt name. | | prompt_version | unsigned int | No | The prompt version used. | | input | json | Yes | The input sent to the AI provider. | | output | text | Yes | The response received from the AI provider. | | tokens | unsigned int | Yes | Total token usage (input + output). | | latency_ms | unsigned int | Yes | Round-trip time in milliseconds. | | cost | decimal(8,6) | Yes | Estimated cost in your currency. | | model | string | Yes | The AI model used (e.g. gpt-4o). | | provider | string | Yes | The AI provider (e.g. openai, anthropic). | | feedback | json | Yes | User feedback (ratings, comments, etc.). | | created_at | timestamp | Yes | Execution timestamp. |

Indexes: - Index on (prompt_name, prompt_version, created_at) ? fast filtering by prompt and time range.

> Note > The prompt_executions table does not have an updated_at column. Records are insert-only.

<a name="recording-executions"></a>

Recording Executions

<a name="manual-tracking"></a>

Manual Tracking

Use the PromptDeck::track() method to record an execution manually:

use Veeqtoh\PromptDeck\Facades\PromptDeck;

PromptDeck::track('order-summary', 2, [
    'input'    => ['message' => 'Summarise order #1234'],
    'output'   => 'Your order contains 3 items totalling $150.00...',
    'tokens'   => 150,
    'latency'  => 234.5,
    'cost'     => 0.002,
    'model'    => 'gpt-4o',
    'provider' => 'openai',
    'feedback' => ['rating' => 5, 'comment' => 'Accurate summary'],
]);

All fields in the data array are optional. You can track as little or as much as you need:

// Minimal tracking ? just record that the prompt was used
PromptDeck::track('order-summary', 2, []);

// Track only tokens and cost
PromptDeck::track('order-summary', 2, [
    'tokens' => 150,
    'cost'   => 0.002,
]);

If tracking is disabled in configuration, the track() method is a safe no-op ? you can call it without checking configuration first.

<a name="automatic-tracking"></a>

Automatic Tracking via Middleware

When using the Laravel AI SDK integration, the TrackPromptMiddleware handles tracking automatically. See AI SDK ? Performance Tracking Middleware for setup details.

<a name="tracked-fields"></a>

Tracked Fields

| Field | Type | Description | |---|---|---| | input | array\|null | The input data sent to the AI provider. Stored as JSON. | | output | string\|null | The text response from the AI provider. | | tokens | int\|null | Total token count (input + output tokens). | | latency | float\|null | Round-trip time in milliseconds. Stored as latency_ms. | | cost | float\|null | Estimated cost. Stored with up to 6 decimal places. | | model | string\|null | AI model identifier (e.g. gpt-4o, claude-3-sonnet). | | provider | string\|null | AI provider name (e.g. openai, anthropic). | | feedback | array\|null | Arbitrary feedback data. Stored as JSON. |

<a name="eloquent-models"></a>

Eloquent Models

Prompt Deck provides two Eloquent models for interacting with tracking data.

<a name="prompt-version-model"></a>

PromptVersion

Veeqtoh\PromptDeck\Models\PromptVersion

Represents a prompt version record in the prompt_versions table.

use Veeqtoh\PromptDeck\Models\PromptVersion;

// Find the active version
$active = PromptVersion::where('name', 'order-summary')
    ->where('is_active', true)
    ->first();

// Get all versions for a prompt
$versions = PromptVersion::where('name', 'order-summary')
    ->orderBy('version')
    ->get();

Fillable attributes: name, version, system_prompt, user_prompt, metadata, is_active

Casts:

| Attribute | Cast | |---|---| | version | integer | | is_active | boolean | | metadata | array |

<a name="prompt-execution-model"></a>

PromptExecution

Veeqtoh\PromptDeck\Models\PromptExecution

Represents an execution record in the prompt_executions table.

use Veeqtoh\PromptDeck\Models\PromptExecution;

// Get recent executions
$recent = PromptExecution::where('prompt_name', 'order-summary')
    ->latest()
    ->limit(100)
    ->get();

// Get executions for a specific version
$v2Executions = PromptExecution::where('prompt_name', 'order-summary')
    ->where('prompt_version', 2)
    ->get();

Fillable attributes: prompt_name, prompt_version, input, output, tokens, latency_ms, cost, model, provider, feedback

Casts:

| Attribute | Cast | |---|---| | prompt_version | integer | | tokens | integer | | latency_ms | integer | | cost | decimal:6 | | input | array | | feedback | array |

> Note > PromptExecution sets UPDATED_AT = null since execution records are insert-only and never updated.

<a name="factories"></a>

Factories

Both models include factories for testing.

<a name="prompt-version-factory"></a>

PromptVersionFactory

use Veeqtoh\PromptDeck\Models\PromptVersion;

// Create a basic version
$version = PromptVersion::factory()->create();

// Create an active version
$version = PromptVersion::factory()->active()->create();

// Create with a specific name and version
$version = PromptVersion::factory()
    ->named('order-summary')
    ->version(2)
    ->active()
    ->create();

Available states:

| Method | Description | |---|---| | active() | Mark as the active version (is_active = true). | | version(int $v) | Set a specific version number. | | named(string $name) | Set a specific prompt name. |

<a name="prompt-execution-factory"></a>

PromptExecutionFactory

use Veeqtoh\PromptDeck\Models\PromptExecution;

// Create a basic execution
$execution = PromptExecution::factory()->create();

// Create with feedback
$execution = PromptExecution::factory()
    ->withFeedback(['rating' => 5, 'comment' => 'Great'])
    ->create();

// Create a minimal execution (only required fields)
$execution = PromptExecution::factory()->minimal()->create();

// Create for a specific prompt
$execution = PromptExecution::factory()
    ->forPrompt('order-summary', 2)
    ->create();

Available states:

| Method | Description | |---|---| | withFeedback(array $feedback = []) | Include user feedback. Generates random feedback if no argument given. | | minimal() | Set all optional fields to null. | | forPrompt(string $name, int $version) | Set a specific prompt name and version. |

<a name="querying-execution-data"></a>

Querying Execution Data

<a name="basic-queries"></a>

Basic Queries

use Veeqtoh\PromptDeck\Models\PromptExecution;

// Total executions for a prompt
$count = PromptExecution::where('prompt_name', 'order-summary')->count();

// Most recent execution
$latest = PromptExecution::where('prompt_name', 'order-summary')
    ->latest()
    ->first();

// Executions in the last 24 hours
$recent = PromptExecution::where('prompt_name', 'order-summary')
    ->where('created_at', '>=', now()->subDay())
    ->get();

<a name="performance-analysis"></a>

Performance Analysis

// Average latency for a prompt version
$avgLatency = PromptExecution::where('prompt_name', 'order-summary')
    ->where('prompt_version', 2)
    ->avg('latency_ms');

// Average token usage
$avgTokens = PromptExecution::where('prompt_name', 'order-summary')
    ->where('prompt_version', 2)
    ->avg('tokens');

// 95th percentile latency (approximate)
$p95 = PromptExecution::where('prompt_name', 'order-summary')
    ->orderBy('latency_ms')
    ->limit(1)
    ->offset((int) (PromptExecution::where('prompt_name', 'order-summary')->count() * 0.95))
    ->value('latency_ms');

<a name="ab-testing"></a>

A/B Testing

Compare metrics across prompt versions:

// Compare average latency between v1 and v2
$comparison = PromptExecution::where('prompt_name', 'order-summary')
    ->whereIn('prompt_version', [1, 2])
    ->groupBy('prompt_version')
    ->selectRaw('prompt_version, AVG(latency_ms) as avg_latency, AVG(tokens) as avg_tokens, COUNT(*) as executions')
    ->get();

// Compare feedback ratings
$ratings = PromptExecution::where('prompt_name', 'order-summary')
    ->whereNotNull('feedback')
    ->whereIn('prompt_version', [1, 2])
    ->groupBy('prompt_version')
    ->selectRaw("prompt_version, AVG(JSON_EXTRACT(feedback, '$.rating')) as avg_rating")
    ->get();

<a name="cost-analysis"></a>

Cost Analysis

// Total cost for a prompt
$totalCost = PromptExecution::where('prompt_name', 'order-summary')
    ->sum('cost');

// Cost by model
$costByModel = PromptExecution::where('prompt_name', 'order-summary')
    ->groupBy('model')
    ->selectRaw('model, SUM(cost) as total_cost, COUNT(*) as executions')
    ->get();

// Daily cost trend
$dailyCost = PromptExecution::where('prompt_name', 'order-summary')
    ->where('created_at', '>=', now()->subDays(30))
    ->groupByRaw('DATE(created_at)')
    ->selectRaw('DATE(created_at) as date, SUM(cost) as daily_cost')
    ->orderBy('date')
    ->get();

<a name="version-management-via-database"></a>

Version Management via Database

When tracking is enabled, version activation is managed through the prompt_versions table instead of metadata.json files. This provides a centralised, queryable record of version history.

The PromptDeck::activate() method and prompt:activate command automatically use the appropriate storage (database or file) based on your tracking configuration.

// Activate programmatically
PromptDeck::activate('order-summary', 2);

// Query active versions
$activeVersions = PromptVersion::where('is_active', true)->get();

// Version history
$history = PromptVersion::where('name', 'order-summary')
    ->orderBy('version')
    ->get()
    ->map(fn ($v) => [
        'version'  => $v->version,
        'active'   => $v->is_active,
        'metadata' => $v->metadata,
    ]);