定制 cjmellor/fal-ai-laravel 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

cjmellor/fal-ai-laravel

最新稳定版本:v1.2.0

Composer 安装命令:

composer require cjmellor/fal-ai-laravel

包简介

A Laravel SDK for Fal.ai

README 文档

README

Fal AI Laravel SDK Banner

Fal.ai Laravel Package

Latest Version on Packagist GitHub Tests Action Status Total Downloads Packagist PHP Version Laravel Version

A Laravel package for integrating with the Fal.ai API, providing a fluent interface for AI model interactions with built-in webhook support.

✨ Features

  • 🚀 Fluent API - Chainable methods for easy request building
  • 🔗 Webhook Support - Secure webhook handling with ED25519 signature verification
  • Queue & Sync Modes - Support for both immediate and queued requests
  • 📡 Real-time Streaming - Server-Sent Events (SSE) support for progressive AI model responses
  • 💰 Platform API - Access pricing, cost estimation, usage tracking, and analytics for models
  • 🛡️ Security - Built-in webhook verification middleware
  • 🧪 Well Tested - Comprehensive test suite
  • 📝 Laravel Integration - Native Laravel middleware and service provider
  • 🛣️ Built-in Routes - Pre-configured webhook endpoints ready to use

📦 Installation

Install the package via Composer:

composer require fal-ai/laravel

Publish the configuration file:

php artisan vendor:publish --provider="FalAi\FalAiServiceProvider"

Add your Fal.ai API key to your .env file:

FAL_API_KEY=your_fal_api_key_here

🚀 Basic Usage

🎯 Simple Request

use FalAi\FalAi;

$falAi = new FalAi();

$response = $falAi->model('fal-ai/flux/schnell')
    ->prompt('A beautiful sunset over mountains')
    ->imageSize('landscape_4_3')
    ->run();

⚡ Queue vs Sync Modes

Tip

Queue mode is the default and recommended for most use cases. It's perfect for complex generations that take time to process.

📋 Queue Mode (Default)

$response = $falAi->model('fal-ai/flux/dev')
    ->prompt('A futuristic cityscape')
    ->queue() // Explicit queue mode (optional, it's the default)
    ->run();

// Returns: ['request_id' => 'req_123...', 'status' => 'IN_QUEUE']

Use queue mode when:

  • Generating high-quality images with many inference steps
  • Processing multiple images in batch
  • You don't need immediate results
  • Working with complex prompts or large image sizes

⚡ Sync Mode

$response = $falAi->model('fal-ai/flux/schnell')
    ->prompt('A beautiful landscape')
    ->sync() // Switch to sync mode
    ->run();

// Returns the complete result immediately

Use sync mode when:

  • You need immediate results
  • Generating simple images with few inference steps
  • Building interactive applications
  • Testing and development

Warning

Sync mode may timeout for complex requests. Use queue mode for production applications.

🛰️ Polling Request Status

You can poll the status of a queued request (useful when not using webhooks). Set includeLogs to true to retrieve execution logs.

use Cjmellor\FalAi\Facades\FalAi;

$status = FalAi::status('req_123456789', includeLogs: true);

if ($status->isInProgress()) {
    $logs = $status->getLogs();
}

📦 Fetching Results by ID

Fetch the final result payload for a completed request and use convenient accessors.

use Cjmellor\FalAi\Facades\FalAi;

$result = FalAi::result('req_123456789');

$firstImageUrl = $result->firstImageUrl; // Convenience accessor
$all = $result->json();                  // Raw payload if you prefer

⛔ Cancelling a Queued Request

Cancel a queued request that hasn’t started processing yet.

use Cjmellor\FalAi\Facades\FalAi;

FalAi::cancel('req_123456789', modelId: 'fal-ai/flux/schnell');

🧭 Submit Response Helpers

Access useful URLs directly from the initial submit response.

use Cjmellor\FalAi\Facades\FalAi;

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A photorealistic fox in a forest')
    ->queue()
    ->run();

$requestId = $response->getRequestId();
$statusUrl = $response->getStatusUrl();
$cancelUrl = $response->getCancelUrl();

🔗 Webhook Support

📤 Making Requests with Webhooks

When you add a webhook URL to your request, it automatically switches to queue mode:

$response = $falAi->model('fal-ai/flux/schnell')
    ->withWebhook('https://myapp.com/webhooks/fal')
    ->prompt('A beautiful sunset over mountains')
    ->imageSize('landscape_4_3')
    ->run();

// Returns: ['request_id' => 'req_123...', 'status' => 'IN_QUEUE']

📋 Webhook URL Requirements

  • Must be a valid HTTPS URL
  • Must be publicly accessible
  • Should respond with 2xx status codes

🛠️ Setting Up Webhook Endpoints

You have two options for handling webhooks: use the built-in route or create your own custom endpoint.

🎯 Option 1: Built-in Webhook Route (Easiest)

The package includes a pre-configured webhook route at /webhooks/fal that handles basic webhook processing:

// This route is automatically registered by the package
// POST /webhooks/fal

// Use it in your requests:
$response = $falAi->model('fal-ai/flux/schnell')
    ->withWebhook(url('/webhooks/fal')) // Uses the built-in route
    ->prompt('Your prompt here')
    ->run();

Tip

The built-in route automatically verifies webhooks and returns appropriate responses. Perfect for getting started quickly!

🏭 Option 2: Custom Webhook Endpoint (Recommended for Production)

use FalAi\Middleware\VerifyFalWebhook;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/webhooks/fal', function (Request $request) {
    $payload = $request->json()->all();
    
    if ($payload['status'] === 'OK') {
        $images = $payload['data']['images'];
        // Process successful results
        foreach ($images as $image) {
            // Save image URL: $image['url']
        }
    } elseif ($payload['status'] === 'ERROR') {
        $error = $payload['error'];
        // Handle error
    }
    
    return response()->json(['status' => 'processed']);
})->middleware(VerifyFalWebhook::class);

For production applications, create a custom webhook endpoint with your own processing logic:

use FalAi\Middleware\VerifyFalWebhook;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/webhooks/fal-custom', function (Request $request) {
    $payload = $request->json()->all();
    
    if ($payload['status'] === 'OK') {
        $images = $payload['data']['images'];
        // Process successful results
        foreach ($images as $image) {
            // Save image URL: $image['url']
            // Custom processing logic here
        }
    } elseif ($payload['status'] === 'ERROR') {
        $error = $payload['error'];
        // Handle error with custom logic
    }
    
    return response()->json(['status' => 'processed']);
})->middleware(VerifyFalWebhook::class);

🔧 Option 3: Manual Verification (Advanced)

For complete control over the verification process:

use FalAi\Services\WebhookVerifier;
use FalAi\Exceptions\WebhookVerificationException;

Route::post('/webhooks/fal-manual', function (Request $request) {
    $verifier = new WebhookVerifier();
    
    try {
        $verifier->verify($request);
        
        // Webhook is valid, process payload
        $payload = $request->json()->all();
        
        return response()->json(['status' => 'verified']);
        
    } catch (WebhookVerificationException $e) {
        return response()->json([
            'error' => 'Unauthorized',
            'message' => 'Webhook verification failed'
        ], 401);
    }
});

📄 Webhook Payload Examples

✅ Successful Completion

{
    "request_id": "req_123456789",
    "status": "OK",
    "data": {
        "images": [
            {
                "url": "https://fal.media/files/generated-image.jpg",
                "width": 1024,
                "height": 768,
                "content_type": "image/jpeg"
            }
        ],
        "seed": 12345,
        "has_nsfw_concepts": [false],
        "prompt": "A beautiful sunset over mountains"
    }
}

❌ Error

{
    "request_id": "req_123456789",
    "status": "ERROR",
    "error": {
        "type": "ValidationError",
        "message": "Invalid prompt provided"
    }
}

📡 Streaming

The Fal.ai Laravel package supports real-time streaming responses using Server-Sent Events (SSE). This is particularly useful for AI models that generate content progressively, such as text generation or image creation with intermediate steps.

🎯 Basic Streaming Usage

To use streaming, call the stream() method instead of run() or queue():

use Cjmellor\FalAi\Facades\FalAi;

$streamResponse = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A beautiful sunset over mountains')
    ->imageSize('landscape_4_3')
    ->stream();

    $streamResponse->getResponse();
}

📊 Streaming vs Regular Requests

Feature Regular Request Streaming Request
Response Time Wait for completion Real-time updates
User Experience Loading spinner Progress indicators
Resource Usage Lower Slightly higher
Complexity Simple Moderate
Best For Simple workflows Interactive applications

📝 Important Notes

  • Streaming requests always use the https://fal.run endpoint regardless of configuration
  • Not all Fal.ai models support streaming - check the model documentation
  • Streaming responses cannot be cached like regular responses
  • Consider implementing proper error handling for network interruptions
  • Use streaming for models that benefit from progressive updates (text generation, multi-step image creation)

⚙️ Configuration

Note

You can customise the package behaviour by publishing and modifying the configuration file.

The configuration file config/fal-ai.php contains the following options:

return [
    'api_key' => env('FAL_API_KEY'),
    'base_url' => 'https://queue.fal.run',
    'default_model' => '',
    
    'webhook' => [
        // JWKS cache TTL in seconds (max 24 hours)
        'jwks_cache_ttl' => env('FAL_WEBHOOK_JWKS_CACHE_TTL', 86400),
        
        // Timestamp tolerance in seconds (prevents replay attacks)
        'timestamp_tolerance' => env('FAL_WEBHOOK_TIMESTAMP_TOLERANCE', 300),
        
        // HTTP timeout for JWKS fetching
        'verification_timeout' => env('FAL_WEBHOOK_VERIFICATION_TIMEOUT', 10),
    ],
];

Environment Variables

# Required
FAL_API_KEY=your_fal_api_key_here

# Optional webhook configuration
FAL_WEBHOOK_JWKS_CACHE_TTL=86400
FAL_WEBHOOK_TIMESTAMP_TOLERANCE=300
FAL_WEBHOOK_VERIFICATION_TIMEOUT=10

🎯 Using a Default Model

Set a default model in config and omit the model ID in your calls.

// config/fal-ai.php
// 'default_model' => 'fal-ai/flux/schnell'

use Cjmellor\FalAi\Facades\FalAi;

$response = FalAi::model()
    ->prompt('A cozy cabin in the woods')
    ->run();

🌐 Overriding the Base URL

If you need to direct a request to a specific Fal endpoint manually, you can override the base URL for a single call. The fluent builder already switches between queue and sync automatically in most cases.

use Cjmellor\FalAi\Facades\FalAi;

$response = FalAi::runWithBaseUrl(
    ['prompt' => 'A watercolor skyline at dawn'],
    modelId: 'fal-ai/flux/schnell',
    baseUrlOverride: 'https://queue.fal.run',
    webhookUrl: 'https://example.com/webhooks/fal'
);

💰 Platform API

The Platform API provides access to pricing information and cost estimation for Fal.ai models. This is separate from the model execution APIs and uses a different endpoint (api.fal.ai).

📊 Get Model Pricing

Retrieve pricing information for one or more model endpoints. Supports up to 50 endpoints per request.

use Cjmellor\FalAi\Facades\FalAi;

// Get pricing for multiple endpoints
$pricing = FalAi::platform()
    ->pricing()
    ->forEndpoints(['fal-ai/flux/dev', 'fal-ai/flux/schnell'])
    ->get();

// Or add endpoints individually
$pricing = FalAi::platform()
    ->pricing()
    ->forEndpoint('fal-ai/flux/dev')
    ->forEndpoint('fal-ai/flux/schnell')
    ->get();

// Access pricing data
$unitPrice = $pricing->getUnitPriceFor('fal-ai/flux/dev'); // 0.025
$priceInfo = $pricing->getPriceFor('fal-ai/flux/dev');
// ['endpoint_id' => 'fal-ai/flux/dev', 'unit_price' => 0.025, 'unit' => 'image', 'currency' => 'USD']

Example Response:

[
    'prices' => [
        [
            'endpoint_id' => 'fal-ai/flux/dev',
            'unit_price' => 0.025,
            'unit' => 'image',
            'currency' => 'USD'
        ],
        [
            'endpoint_id' => 'fal-ai/flux/schnell',
            'unit_price' => 0.003,
            'unit' => 'image',
            'currency' => 'USD'
        ]
    ],
    'has_more' => false,
    'next_cursor' => null
]

💵 Estimate Costs

Estimate the cost of API usage before making requests. Supports two estimation modes: historical API price and unit price.

Historical API Price Mode

Estimate costs based on the number of API calls you plan to make.

use Cjmellor\FalAi\Facades\FalAi;

$estimate = FalAi::platform()
    ->estimateCost()
    ->historicalApiPrice()
    ->endpoint('fal-ai/flux/dev', callQuantity: 100)
    ->endpoint('fal-ai/flux/schnell', callQuantity: 50)
    ->estimate();

echo "Total cost: $" . $estimate->totalCost; // 3.65

Example Response:

[
    'estimate_type' => 'historical_api_price',
    'total_cost' => 3.65,
    'currency' => 'USD'
]

Unit Price Mode

Estimate costs based on the number of billing units (e.g., images, megapixels).

use Cjmellor\FalAi\Facades\FalAi;

$estimate = FalAi::platform()
    ->estimateCost()
    ->unitPrice()
    ->endpoint('fal-ai/flux/dev', unitQuantity: 100)
    ->estimate();

echo "Total cost: $" . $estimate->totalCost; // 2.50

Example Response:

[
    'estimate_type' => 'unit_price',
    'total_cost' => 2.50,
    'currency' => 'USD'
]

Bulk Endpoint Estimation

Set multiple endpoints at once using the endpoints() method:

$estimate = FalAi::platform()
    ->estimateCost()
    ->historicalApiPrice()
    ->endpoints([
        'fal-ai/flux/dev' => ['call_quantity' => 100],
        'fal-ai/flux/schnell' => ['call_quantity' => 50],
    ])
    ->estimate();

📊 Get Usage Data

Access detailed usage line items with unit quantities, prices, and costs. Perfect for tracking your API consumption over time.

use Cjmellor\FalAi\Facades\FalAi;

// Get usage for specific endpoints
$usage = FalAi::platform()
    ->usage()
    ->forEndpoints(['fal-ai/flux/dev', 'fal-ai/flux/schnell'])
    ->get();

// Access usage data
$timeSeries = $usage->timeSeries;
$totalCost = $usage->getTotalCost();         // Total cost across all endpoints
$totalQuantity = $usage->getTotalQuantity(); // Total units consumed

Filter by Date Range

$usage = FalAi::platform()
    ->usage()
    ->forEndpoint('fal-ai/flux/dev')
    ->between('2025-01-01T00:00:00Z', '2025-01-15T00:00:00Z')
    ->timeframe('day')  // Aggregate by: 'minute', 'hour', 'day', 'week', 'month'
    ->timezone('America/New_York')
    ->get();

Include Additional Data

$usage = FalAi::platform()
    ->usage()
    ->forEndpoint('fal-ai/flux/dev')
    ->withTimeSeries()  // Include time-bucketed data (default)
    ->withSummary()     // Include aggregate summary
    ->withAuthMethod()  // Include auth method breakdown
    ->get();

// Access summary if included
if ($usage->summary) {
    echo "Total: $" . $usage->summary['total_cost'];
}

Get Usage for Specific Endpoint

$usage = FalAi::platform()
    ->usage()
    ->forEndpoints(['fal-ai/flux/dev', 'fal-ai/flux/schnell'])
    ->get();

// Get cost for specific endpoint
$devCost = $usage->getTotalCostFor('fal-ai/flux/dev');
$schnellCost = $usage->getTotalCostFor('fal-ai/flux/schnell');

// Get quantity for specific endpoint
$devQuantity = $usage->getTotalQuantityFor('fal-ai/flux/dev');

Example Response:

[
    'time_series' => [
        [
            'bucket' => '2025-01-15T00:00:00-05:00',
            'results' => [
                [
                    'endpoint_id' => 'fal-ai/flux/dev',
                    'unit' => 'image',
                    'quantity' => 10,
                    'unit_price' => 0.025,
                    'cost' => 0.25,
                    'currency' => 'USD',
                    'auth_method' => 'Production Key'
                ]
            ]
        ]
    ],
    'has_more' => false,
    'next_cursor' => null
]

📈 Get Analytics Data

Query time-bucketed metrics for request counts, success/error rates, and latency percentiles. Essential for monitoring API performance.

use Cjmellor\FalAi\Facades\FalAi;

// Get analytics for an endpoint (endpoint_id is required)
$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->get();

// Access metrics
$totalRequests = $analytics->getTotalRequests();
$successRate = $analytics->getSuccessRate(); // Returns percentage (e.g., 95.0)

Include Specific Metrics

$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->withRequestCount()      // Total requests
    ->withSuccessCount()      // Successful responses (2xx)
    ->withErrorCount()        // Server errors (5xx)
    ->withUserErrorCount()    // User errors (4xx)
    ->withP50Duration()       // 50th percentile latency
    ->withP90Duration()       // 90th percentile latency
    ->get();

Convenience Methods

// Include all error metrics
$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->withAllErrors()          // user_error_count + error_count
    ->get();

// Include all latency metrics
$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->withAllLatencyMetrics()  // P50, P75, P90 for both prepare and execution
    ->get();

// Include everything
$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->withAllMetrics()
    ->get();

Filter by Date Range and Timeframe

$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->between('2025-01-01T00:00:00Z', '2025-01-15T00:00:00Z')
    ->timeframe('hour')  // Aggregate by: 'minute', 'hour', 'day', 'week', 'month'
    ->timezone('UTC')
    ->get();

Calculate Success Rates

$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoints(['fal-ai/flux/dev', 'fal-ai/flux/schnell'])
    ->withSuccessCount()
    ->get();

// Overall success rate
$overallRate = $analytics->getSuccessRate(); // e.g., 97.5

// Per-endpoint success rate
$devRate = $analytics->getSuccessRateFor('fal-ai/flux/dev');
$schnellRate = $analytics->getSuccessRateFor('fal-ai/flux/schnell');

// Get totals
$totalErrors = $analytics->getTotalErrors();
$totalUserErrors = $analytics->getTotalUserErrors();

Example Response:

[
    'time_series' => [
        [
            'bucket' => '2025-01-15T12:00:00-05:00',
            'results' => [
                [
                    'endpoint_id' => 'fal-ai/flux/dev',
                    'request_count' => 1500,
                    'success_count' => 1450,
                    'user_error_count' => 40,
                    'error_count' => 10,
                    'p50_duration' => 2.5,
                    'p90_duration' => 4.8
                ]
            ]
        ]
    ],
    'has_more' => false,
    'next_cursor' => null
]

🗑️ Delete Request Payloads

Delete IO payloads and associated CDN output files for a specific request. This is useful for cleaning up generated content and freeing storage.

Warning

This action is irreversible. Only output CDN files are deleted; input files may be used by other requests and are preserved.

use Cjmellor\FalAi\Facades\FalAi;

$response = FalAi::platform()
    ->deleteRequestPayloads('req_123456789')
    ->delete();

// Check if all deletions succeeded
if (!$response->hasErrors()) {
    echo "All files deleted successfully";
}

// Access individual results
foreach ($response->cdnDeleteResults as $result) {
    echo $result['link'];        // CDN file URL
    echo $result['exception'];   // null or error message
}

With Idempotency Key

Use an idempotency key for safe retries. Responses are cached for 10 minutes per unique key.

$response = FalAi::platform()
    ->deleteRequestPayloads('req_123456789')
    ->withIdempotencyKey('unique-operation-key')
    ->delete();

Filtering Results

// Get only successful deletions
$successful = $response->getSuccessfulDeletions();

// Get only failed deletions
$failed = $response->getFailedDeletions();

// Check if any deletions failed
if ($response->hasErrors()) {
    foreach ($failed as $result) {
        Log::error("Failed to delete: {$result['link']} - {$result['exception']}");
    }
}

Example Response:

[
    'cdn_delete_results' => [
        [
            'link' => 'https://v3.fal.media/files/abc123/output.png',
            'exception' => null
        ],
        [
            'link' => 'https://v3.fal.media/files/def456/output.jpg',
            'exception' => 'File not found'
        ]
    ]
]

🔗 Fluent API Methods

🛠️ Common Methods

$request = $falAi->model('fal-ai/flux/schnell')
    ->prompt('Your prompt here')           // Set the text prompt
    ->imageSize('landscape_4_3')           // Set image dimensions
    ->numImages(2)                         // Number of images to generate
    ->seed(12345)                          // Set random seed
    ->withWebhook('https://...')           // Add webhook URL
    ->queue()                              // Use queue mode
    ->sync();                              // Use sync mode

🧰 Adding and Inspecting Payload Data

Enrich the request body with with([...]) and dynamic setters. You can also inspect what will be sent.

use Cjmellor\FalAi\Facades\FalAi;

$request = FalAi::model('fal-ai/flux/schnell')
    ->with(['num_images' => 2])
    ->imageSize('square_hd')
    ->prompt('An astronaut riding a horse');

$payload = $request->toArray();
$response = $request->run();

⚠️ Error Handling

Important

Always implement proper error handling in production applications to gracefully handle API failures and webhook verification issues.

use FalAi\Exceptions\WebhookVerificationException;
use InvalidArgumentException;

try {
    $response = $falAi->model('fal-ai/flux/schnell')
        ->withWebhook('https://myapp.com/webhook')
        ->prompt('Test prompt')
        ->run();
        
    if (!$response->successful()) {
        throw new Exception('API request failed: ' . $response->body());
    }
    
} catch (InvalidArgumentException $e) {
    // Invalid webhook URL or other validation errors
    echo "Validation error: " . $e->getMessage();
} catch (WebhookVerificationException $e) {
    // Webhook verification failed (in webhook endpoints)
    echo "Webhook error: " . $e->getMessage();
} catch (Exception $e) {
    // Other errors (network, API, etc.)
    echo "Error: " . $e->getMessage();
}

🧪 Testing

Run the test suite:

composer test

🔒 Security

Caution

Webhook security is critical for protecting your application from malicious requests. Always use the provided verification mechanisms.

🔐 Webhook Security

This package implements Fal.ai's webhook verification using:

  • ED25519 signature verification using Fal.ai's public keys
  • Timestamp validation to prevent replay attacks
  • JWKS caching for performance
  • Automatic header extraction and validation

💡 Best Practices

Tip

Follow these security practices to ensure your webhook endpoints are secure:

  1. Always use HTTPS for webhook URLs
  2. Use the provided middleware for automatic verification
  3. Validate webhook payloads in your application logic
  4. Implement proper error handling and logging
  5. Monitor webhook endpoints for suspicious activity
  6. Use rate limiting on webhook routes
  7. Keep your API keys secure and rotate them regularly

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This package is open-sourced software licensed under the MIT license.

💬 Support

For support, please open an issue on GitHub or contact the maintainers.

统计信息

  • 总下载量: 330
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 5
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 5
  • Watchers: 0
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-07-26