Laravel OpenRouter

repository·master·Indexed 41 days ago

https://github.com/moe-mizrak/laravel-openrouter

A unified interface for interacting with various Large Language Models (LLMs) via the OpenRouter API within Laravel applications. It provides a LaravelOpenRouter facade, a ChatData DTO for structured requests, and support for streaming responses and tool-calling. Requires PHP 8.2+ for v2.x.x or PHP 8.1 for v1.0.9.

Tokens
9.1K
Snippets
19
Records
23
Agent score
50%

What's inside Laravel OpenRouter

  1. Implement Prompt Caching for reduced cost and latency

    master

    This package supports two methods for OpenRouter prompt caching:

    1. Top-level caching (Recommended for multi-turn conversations): Set the cache_control property on the ChatData object. This applies caching to the entire request.
    2. Explicit cache breakpoint (Fine-grained control): Set the cache_control property on specific TextContentData objects within a message's content array. This is ideal for caching large, static blocks like RAG data or long instructions.

    Use CacheControlData::ALLOWED_TYPE (e.g., "ephemeral") and an optional ttl (e.g., '1h').

    // Top-level example (automatic caching)
    $chatData = new ChatData(
        messages: [
            new MessageData(
                role: RoleType::USER,
                content: 'What triggered the collapse?',
            ),
        ],
        model: 'anthropic/claude-sonnet-4.6',
        cache_control: new CacheControlData(
            type: CacheControlData::ALLOWED_TYPE, // "ephemeral"
            ttl: '1h', // optional
        ),
    );
    
    // Explicit breakpoint example (cache a large text block)
    $chatData = new ChatData(
        messages: [
            new MessageData(
                role: RoleType::USER,
                content: [
                    new TextContentData(text: 'Given the book below:'),
                    new TextContentData(
                        text: 'HUGE TEXT BODY',
                        cache_control: new CacheControlData(
                            type: CacheControlData::ALLOWED_TYPE,
                            ttl: '1h',
                        ),
                    ),
                    new TextContentData(text: 'What triggered the collapse?'),
                ],
            ),
        ],
        model: 'anthropic/claude-sonnet-4.6',
    );
  2. Understanding the ChatData DTO

    master

    The ChatData class encapsulates all data required for chat requests. It uses several XOR-gated fields (you must provide one or the other, but not both):

    • messages (array of MessageData) OR prompt (string).
    • model (string) OR models (array of strings).

    Key Properties

    • response_format: Instance of ResponseFormatData for desired output format.
    • usage: Boolean to include usage info (default false).
    • stop: Stop sequence (array or string).
    • stream: Boolean to enable streaming.
    • reasoning: Instance of ReasoningData for model reasoning configuration.
    • cache_control: Controls prompt caching via CacheControlData.
    • session_id: String for maintaining conversation continuity.
    • debug: Instance of DebugData for troubleshooting (e.g., echo_upstream_body).

    LLM Parameters

    • max_tokens: Max tokens to generate (default 1024).
    • temperature: Randomness (0 to 2).
    • top_p: Nucleus sampling (0 to 1).
    • top_k: Top-k sampling (1 to infinity).
    • frequency_penalty: Penalize new tokens based on frequency (-2 to 2).
    • presence_penalty: Penalize new tokens based on appearance (-2 to 2).
    • repetition_penalty: Penalize repetitive tokens (0 to 2).
    • seed: Deterministic sampling (OpenAI models only).

    Tool-calling (OpenAI models only)

    • tool_choice: Specifies tool choice.
    • tools: Array of ToolCallData objects.

    OpenRouter-specific Parameters

    • transforms: Array for prompt transforms.
    • plugins: Array of PluginData (e.g., web search).
    • web_search_options: Configuration for web search.
    • route: Route type (e.g., RouteType::FALLBACK).
    • provider: Instance of ProviderPreferencesData for routing control.
  3. Understand the structure of filtered streaming responses

    master

    When using filterStreamingResponse, the resulting array contains ResponseData objects.

    Key characteristics of the stream:

    • Chunks: Most chunks contain a delta field within choices representing the new content.
    • Usage: The final chunk in the stream typically contains the usage data (prompt tokens, completion tokens, total tokens, and cost).
    • Termination: The stream ends when the server returns data: [DONE].
  4. How to interact with the OpenRouter API

    master

    The package provides two primary ways to interact with the OpenRouter API:

    1. Using the LaravelOpenRouter facade: A convenient, high-level way to send requests.
    2. Instantiating the OpenRouterRequest class directly: For more manual control.

    Both methods require you to use the ChatData DTO to structure your request data.

  5. Install and setup Laravel OpenRouter

    master

    To integrate OpenRouter into your Laravel application, install the package via Composer and publish the configuration file.

    Requirements:

    • PHP 8.2+ for version v2.x.x (uses spatie/laravel-data).
    • PHP 8.1 for version v1.0.9 (uses spatie/data-transfer-object).
    # Install the package
    composer require moe-mizrak/laravel-openrouter
    
    # Publish the configuration file
    php artisan vendor:publish --tag=laravel-openrouter
  6. Maintain conversation continuity

    master

    To make the LLM 'remember' previous parts of a conversation, you must include the historical messages in the messages array of your ChatData instance.

    When adding historical responses from the assistant, set their role to RoleType::ASSISTANT. The sequence should typically follow: USER -> ASSISTANT -> USER.

    // 1. Historical message from the assistant (e.g., retrieved from DB)
    $historicalMessage = new MessageData(
        role: RoleType::ASSISTANT,
        content: 'Previous assistant response content',
    );
    
    // 2. The new user message
    $newMessage = new MessageData(
        role: RoleType::USER,
        content: 'Who am I?',
    );
    
    // 3. Send both to maintain context
    $chatData = new ChatData(
        messages: [
            $historicalMessage,
            $newMessage,
        ],
        model: 'mistralai/mistral-7b-instruct:free',
    );
    
    $response = LaravelOpenRouter::chatRequest($chatData);
  7. Provide File/Document Inputs

    master

    You can send files (like PDFs) to multimodal models by using FileContentData within a MessageData content array.

    Supported Methods

    • Public URLs: Use FileUrlData with a file_data URL.
    • Base64 Encoded: Provide a data URI (e.g., data:application/pdf;base64,...) in the file_data field.

    File Parsing Plugins

    You can configure how files are parsed using the file-parser plugin. Supported engines include pdf-text, mistral-ocr, and native (the model's default). If no engine is specified, OpenRouter defaults to the model's native capability or falls back to mistral-ocr.

    $plugins = [
        new PluginData(
            id: 'file-parser',
            pdf: ['engine' => 'pdf-text'],
        ),
    ];
    
    $fileContentData = new FileContentData(
        type: FileContentData::ALLOWED_TYPE,
        file: new FileUrlData(
            file_data: 'https://example.com/report.pdf',
            filename: 'quarterly-report.pdf',
        ),
    );
    
    $messageData = new MessageData(
        role: RoleType::USER,
        content: [
            new TextContentData(type: TextContentData::ALLOWED_TYPE, text: 'Please summarize this.'),
            $fileContentData,
        ],
    );
    
    $chatData = new ChatData(
        messages: [$messageData],
        model: 'anthropic/claude-3.5-sonnet',
        plugins: $plugins,
    );
  8. Use Web Search via OpenRouter

    master

    Web search is available for any model on OpenRouter. You can enable it in two ways:

    1. Model Slug Shortcut: Append :online to the model name (e.g., openai/gpt-4o:online).
    2. Plugin Configuration: Explicitly add a PluginData object with id: 'web' to the plugins array in ChatData.

    Customization

    • search_context_size: Use SearchContextSizeType to control context depth (LOW, MEDIUM, or HIGH).
    • engine: In PluginData, specify native (provider's built-in), exa (Exa API), or undefined (fallback logic).

    Accessing Citations

    When using web search, annotations (URLs and citations) are included in the response. You can access them via the message.annotations path in the response array.

    // Using the :online shortcut
    $chatData = new ChatData(
        messages: [new MessageData(role: RoleType::USER, content: 'What are the latest developments in AI?')],
        model: 'openai/gpt-4o:online',
        web_search_options: new WebSearchOptionsData(
            search_context_size: SearchContextSizeType::HIGH,
        ),
    );
    
    $response = LaravelOpenRouter::chatRequest($chatData);
    $annotations = Arr::get($response->choices[0], 'message.annotations', []);
  9. Use the OpenRouterRequest class via Dependency Injection

    master

    Instead of using the LaravelOpenRouter facade, you can inject the OpenRouterRequest class into your class constructor. This allows you to call the following methods directly on the injected instance:

    • chatRequest(ChatData $chatData)
    • chatStreamRequest(ChatData $chatData)
    • costRequest(string $generationId)
    • limitRequest()
    public function __construct(protected OpenRouterRequest $openRouterRequest) {}
    
    /*
     * Usage:
     */
    $response = $this->openRouterRequest->chatRequest($chatData);
    $streamResponse = $this->openRouterRequest->chatStreamRequest($chatData);
    $costResponse = $this->openRouterRequest->costRequest($generationId);
    $limitResponse = $this->openRouterRequest->limitRequest();
  10. Stream chat responses using chatStreamRequest

    master

    To implement real-time streaming (e.g., for a typewriter effect in a UI), use LaravelOpenRouter::chatStreamRequest($chatData). This returns a promise that resolves to a GuzzleHttp\ \nPsr7\Stream object.

    You can process the stream in two ways:

    1. Wait for the whole response: Use $stream->getContents() to get the entire raw response at once.
    2. Process chunks as they arrive: Use a while (!$stream->eof()) loop with $stream->read(size) to process data incrementally.

    To convert the raw OpenRouter stream chunks into structured ResponseData DTOs, pass the raw string to LaravelOpenRouter::filterStreamingResponse($rawResponse).

    $chatData = new ChatData(
        messages: [new MessageData(content: 'Hello', role: RoleType::USER)],
        model: 'deepseek/deepseek-chat-v3.1',
        max_tokens: 1000,
    );
    
    $promise = LaravelOpenRouter::chatStreamRequest($chatData);
    $stream = $promise->wait();
    
    while (!$stream->eof()) {
        $rawResponse = $stream->read(1024);
        
        // Option 1: Print raw text
        echo $rawResponse;
    
        // Option 2: Map to ResponseData DTOs
        $responses = LaravelOpenRouter::filterStreamingResponse($rawResponse);
        foreach ($responses as $responseData) {
            echo json_encode($responseData->toArray()) . PHP_EOL;
        }
        flush();
    }
  11. Implement Tool Calling (Function Calling)

    master

    To use tools with an LLM, follow these steps:

    1. Define Tools: Create an array of ToolCallData objects. Each tool uses FunctionData to define its name, description, and JSON schema for parameters.
    2. Initial Request: Send a ChatData object containing your messages, the model, and the tools array. You can also set tool_choice (e.g., 'auto').
    3. Handle Tool Call: If the LLM response contains tool_calls, extract the id, name, and arguments.
    4. Execute Tool: Run your local function using the provided arguments.
    5. Provide Result: Create a new ChatData request that includes the full conversation history:
      • The original USER message.
      • An ASSISTANT message containing the ToolCallData (using the same id returned by the LLM).
      • A TOOL role message containing the tool_call_id and the content (the tool result, typically JSON encoded).
    6. Final Response: Send this updated history to get the LLM's final response based on the tool output.
    // 1. Define tools
    $tools = [
        new ToolCallData(
            type: 'function',
            function: new FunctionData(
                name: 'getWeather',
                description: 'Get the current weather for a location',
                parameters: [
                    'type' => 'object',
                    'properties' => [
                        'location' => ['type' => 'string', 'description' => 'The city name'],
                    ],
                    'required' => ['location'],
                ],
            ),
        ),
    ];
    
    // 2. Initial request
    $chatData = new ChatData(
        messages: [new MessageData(role: RoleType::USER, content: 'What is the weather like in Tokyo?')],
        model: 'openai/gpt-4o-mini',
        tools: $tools,
        tool_choice: 'auto',
    );
    $response = LaravelOpenRouter::chatRequest($chatData);
    
    // 3. Extract tool call
    $toolCall = $response['choices'][0]['message']['tool_calls'][0] ?? null;
    $toolCallId = $toolCall['id'];
    
    // 4. Execute tool (manual step)
    $toolResult = ['temperature' => '22°C', 'condition' => 'Sunny'];
    
    // 5. Provide result back to LLM
    $chatDataWithToolResult = new ChatData(
        messages: [
            new MessageData(role: RoleType::USER, content: 'What is the weather like in Tokyo?'),
            new MessageData(
                role: RoleType::ASSISTANT,
                tool_calls: [
                    new ToolCallData(
                        id: $toolCallId,
                        type: 'function',
                        function: new FunctionCallData(
                            name: $toolCall['function']['name'],
                            arguments: $toolCall['function']['arguments'],
                        ),
                    ),
                ],
            ),
            new MessageData(
                role: RoleType::TOOL,
                tool_call_id: $toolCallId,
                content: json_encode($toolResult),
            ),
        ],
        model: 'openai/gpt-4o-mini',
    );
    
    // 6. Final response
    $finalResponse = LaravelOpenRouter::chatRequest($chatDataWithToolResult);
  12. Request Structured Output (JSON)

    master

    You can force the LLM to return data in a specific format using response_format in the ChatData object.

    JSON Object Mode

    Set the type to json_object. It is highly recommended to set require_parameters: true in ProviderPreferencesData to ensure the provider supports this.

    JSON Schema Mode

    Set the type to json_schema and provide a schema definition. For strict adherence, set 'strict' => true within the json_schema array.

    Caution: If you encounter errors, try removing the require_parameters property from the provider configuration.

    // Example: JSON Schema
    $chatData = new ChatData(
        messages: [
            new MessageData(role: RoleType::USER, content: 'Tell me a story...')
        ],
        model: 'mistralai/mistral-7b-instruct:free',
        response_format: new ResponseFormatData(
            type: 'json_schema',
            json_schema: [
                'name' => 'article',
                'strict' => true,
                'schema' => [
                    'type' => 'object',
                    'properties' => [
                        'title' => ['type' => 'string'],
                        'details' => ['type' => 'string'],
                        'keywords' => ['type' => 'string'],
                    ],
                    'required' => ['title', 'details', 'keywords'],
                    'additionalProperties' => false
                ]
            ],
        ),
        provider: new ProviderPreferencesData(require_parameters: true),
    );