{"id":49410579,"url":"https://github.com/bycerfrance/llmapilib","last_synced_at":"2026-05-11T10:03:40.225Z","repository":{"id":347976819,"uuid":"1165914852","full_name":"ByCerfrance/LlmApiLib","owner":"ByCerfrance","description":"PHP library for interacting with multiple LLM providers (Google, Mistral, OpenAI, OVH and any OpenAI-compatible endpoint) with failover, retry, and tool calling support.","archived":false,"fork":false,"pushed_at":"2026-04-28T23:01:27.000Z","size":353,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-29T00:11:24.405Z","etag":null,"topics":["api","chat","completion","google","llm","mistral","openai","ovh","php"],"latest_commit_sha":null,"homepage":"https://www.vigicorp.fr","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ByCerfrance.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-24T17:21:14.000Z","updated_at":"2026-04-28T23:01:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ByCerfrance/LlmApiLib","commit_stats":null,"previous_names":["bycerfrance/llmapilib"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/ByCerfrance/LlmApiLib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ByCerfrance%2FLlmApiLib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ByCerfrance%2FLlmApiLib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ByCerfrance%2FLlmApiLib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ByCerfrance%2FLlmApiLib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ByCerfrance","download_url":"https://codeload.github.com/ByCerfrance/LlmApiLib/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ByCerfrance%2FLlmApiLib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32889972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"online","status_checked_at":"2026-05-11T02:00:05.975Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","chat","completion","google","llm","mistral","openai","ovh","php"],"created_at":"2026-04-29T00:07:11.230Z","updated_at":"2026-05-11T10:03:40.219Z","avatar_url":"https://github.com/ByCerfrance.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LLM API Library\n\n[![Latest Version](https://img.shields.io/packagist/v/bycerfrance/llm-api-lib.svg?style=flat-square)](https://github.com/ByCerfrance/LlmApiLib/releases)\n![Packagist Dependency Version](https://img.shields.io/packagist/dependency-v/bycerfrance/llm-api-lib/php?version=dev-main\u0026style=flat-square)\n[![Software license](https://img.shields.io/github/license/ByCerfrance/LlmApiLib.svg?style=flat-square)](https://github.com/ByCerfrance/LlmApiLib/blob/main/LICENSE)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/ByCerfrance/LlmApiLib/tests.yml?branch=main\u0026style=flat-square\u0026label=tests)](https://github.com/ByCerfrance/LlmApiLib/actions/workflows/tests.yml?query=branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/bycerfrance/llm-api-lib.svg?style=flat-square)](https://packagist.org/packages/bycerfrance/llm-api-lib)\n\nPHP 8.3+ library for interacting with multiple LLM providers (Google, Mistral, OpenAI, OVH and any OpenAI-compatible\nendpoint) with failover, retry, guard validation, tool calling, MCP client, and OpenAPI integration support.\n\n## Installation\n\nYou can install library with [Composer](https://getcomposer.org/), it's the recommended installation.\n\n```bash\n$ composer require bycerfrance/llm-api-lib\n```\n\n## Providers\n\n### Built-in providers\n\n- **Google** -- Google Generative Language API\n- **Mistral** -- Mistral AI API\n- **OpenAI** -- OpenAI API\n- **OVH** -- OVH AI Endpoints\n\n### Generic (OpenAI-compatible)\n\nThe `Generic` provider connects to any OpenAI-compatible endpoint (local servers, proxies, third-party providers):\n\n```php\nuse ByCerfrance\\LlmApiLib\\Provider\\Generic;\n\n$provider = new Generic(\n    uri: 'https://my-local-server.com/v1/chat/completions',\n    apiKey: 'my-api-key',\n    model: 'my-model',\n    client: $httpClient, // PSR-18 ClientInterface\n);\n```\n\n### Model metadata\n\nUse `ModelInfo` to attach rich metadata to a provider (capabilities, quality/cost tiers, pricing, context window, output limit):\n\n```php\nuse ByCerfrance\\LlmApiLib\\Model\\ModelInfo;\nuse ByCerfrance\\LlmApiLib\\Model\\QualityTier;\nuse ByCerfrance\\LlmApiLib\\Model\\CostTier;\nuse ByCerfrance\\LlmApiLib\\Model\\Capability;\nuse ByCerfrance\\LlmApiLib\\Provider\\OpenAi;\n\n$model = new ModelInfo(\n    name: 'gpt-4o',\n    capabilities: [Capability::TEXT, Capability::IMAGE, Capability::TOOLS, Capability::JSON_OUTPUT],\n    qualityTier: QualityTier::PREMIUM,\n    costTier: CostTier::HIGH,\n    inputCost: 2.50,   // $ per million tokens\n    outputCost: 10.00,  // $ per million tokens\n    maxContextTokens: 128_000,\n    maxOutputTokens: 16_384,\n);\n\n$provider = new OpenAi(\n    apiKey: 'sk-...',\n    model: $model,       // ModelInfo or plain string\n    client: $httpClient,\n);\n```\n\n## Chat\n\n### Basic usage\n\n```php\n$llm = new \\ByCerfrance\\LlmApiLib\\Llm($provider);\n\n$completion = $llm-\u003echat('Salut !');\nprint $completion-\u003egetLastMessage()-\u003egetContent(); // \"Salut ! Comment allez-vous ?\"\n\n$completion = $llm-\u003echat($completion-\u003ewithNewMessage('Bien merci et toi ?'));\nprint $completion-\u003egetLastMessage()-\u003egetContent(); // \"Bien, merci. Comment puis-je vous aider ?\"\n```\n\n### With instructions\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\nuse ByCerfrance\\LlmApiLib\\Completion\\Message\\SystemMessage;\nuse ByCerfrance\\LlmApiLib\\Llm;\n\n$completion = new Completion(new SystemMessage(\n    'Tu es un assistant comptable, presentes toi comme tel.',\n));\n\n$llm = new Llm($provider);\n\n$completion = $llm-\u003echat($completion-\u003ewithNewMessage('Salut !'));\nprint $completion-\u003egetLastMessage()-\u003egetContent();\n// \"Bonjour, je suis votre assistant comptable. Comment puis-je vous aider ?\"\n```\n\n### Message types\n\nThe library provides typed message classes for convenience:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Message\\SystemMessage;\nuse ByCerfrance\\LlmApiLib\\Completion\\Message\\UserMessage;\nuse ByCerfrance\\LlmApiLib\\Completion\\Message\\Message;\nuse ByCerfrance\\LlmApiLib\\Completion\\Message\\RoleEnum;\n\n// Typed classes (recommended)\n$system = new SystemMessage('You are a helpful assistant.');\n$user = new UserMessage('Hello!');\n\n// Or using the generic Message class with explicit role\n$system = new Message('You are a helpful assistant.', role: RoleEnum::SYSTEM);\n```\n\n### Completion parameters\n\nFine-tune the LLM behavior with immutable `with*()` methods:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\n\n$completion = (new Completion(['Explain quantum computing']))\n    -\u003ewithModel('gpt-4o')           // Override the provider's default model\n    -\u003ewithMaxTokens(2000)           // Maximum tokens in the response\n    -\u003ewithTemperature(0.7)          // Creativity (0 = deterministic, 2 = very creative)\n    -\u003ewithTopP(0.9)                 // Nucleus sampling\n    -\u003ewithSeed(42);                 // Reproducible outputs (provider-dependent)\n```\n\n### Service tier\n\nControl the processing priority and pricing tier with `ServiceTier`:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\nuse ByCerfrance\\LlmApiLib\\Completion\\ServiceTier;\n\n$completion = (new Completion(['Summarize this report']))\n    -\u003ewithServiceTier(ServiceTier::FLEX);\n```\n\n| Value      | Description                                    |\n|------------|------------------------------------------------|\n| `AUTO`     | Let the provider choose the best tier          |\n| `DEFAULT`  | Standard processing                            |\n| `FLEX`     | Lower-priority, reduced-cost processing        |\n| `PRIORITY` | Higher-priority processing                     |\n\nEach value defines a `fallback()` method for provider compatibility: `PRIORITY` → `AUTO` → `DEFAULT`, `FLEX` → `AUTO` →\n`DEFAULT`. Providers that do not support `service_tier` (e.g., Mistral) automatically strip it from the payload.\n\n### Reasoning effort\n\nControl how much reasoning the model performs before generating a response with `ReasoningEffort`:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\nuse ByCerfrance\\LlmApiLib\\Completion\\ReasoningEffort;\n\n$completion = (new Completion(['Solve this math problem step by step']))\n    -\u003ewithReasoningEffort(ReasoningEffort::HIGH);\n```\n\n| Value    | Description                                     |\n|----------|-------------------------------------------------|\n| `NONE`   | No reasoning traces                             |\n| `LOW`    | Minimal reasoning                               |\n| `MEDIUM` | Moderate reasoning                              |\n| `HIGH`   | Full reasoning traces                           |\n| `XHIGH`  | Extended reasoning (OpenAI o3/o4-mini)          |\n\nEach value defines a `fallback()` method: `XHIGH` → `HIGH` → `MEDIUM` → `LOW` → `NONE`. Provider-specific builders\nuse this chain to map unsupported values to the closest supported one. For example, Mistral only supports `HIGH` and\n`NONE`, so `MEDIUM` falls back through `LOW` → `NONE`.\n\nSetting `reasoningEffort` automatically adds `Capability::REASONING` to the completion's required capabilities, ensuring\nonly providers that support reasoning are selected during failover.\n\n## Content Types\n\n### ArrayContent\n\nAllows combining multiple contents (`ContentInterface` or strings) into a single object. Useful for sending multiple\nelements in a single message.\n\nExample:\n\n```php\n$content = new ArrayContent(\n    new TextContent('First message'),\n    'Second message'\n);\n```\n\n### DocumentUrlContent\n\nRepresents a document accessible via a URL. Supports capabilities `document` and `ocr`.\n\nExample:\n\n```php\n$content = new DocumentUrlContent('https://example.com/document.pdf');\n```\n\nCreates a `DocumentUrlContent` instance from a local file path or stream. The file is automatically converted to a\nbase64-encoded data URL.\n\nExample:\n\n```php\n$content = DocumentUrlContent::fromFile('/path/to/document.pdf', 'custom-name.pdf');\n```\n\nParameters:\n\n- `$file`: Path to the file as a string or a stream resource.\n- `$name`: Optional custom name for the document.\n- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').\n\n### ImageUrlContent\n\nRepresents an image accessible via a URL. Supports capabilities `image` and `ocr`.\n\nExample:\n\n```php\n$content = new ImageUrlContent('https://example.com/image.jpg');\n```\n\nCreates an `ImageUrlContent` instance from a GD image resource. The image is automatically converted to a base64-encoded\ndata URL.\n\nExample:\n\n```php\n$content = ImageUrlContent::fromGdImage($gdImage, 'high');\n```\n\nParameters:\n\n- `$image`: A GD image resource.\n- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').\n- `$maxSize`: Optional maximum size for resizing the image.\n- `$format`: Optional image format ('jpeg', 'png', 'gif', 'webp').\n- `$quality`: Optional quality setting for JPEG/PNG/WebP formats.\n\nCreates an `ImageUrlContent` instance from a local file path or stream. The file is automatically converted to a\nbase64-encoded data URL.\n\nExample:\n\n```php\n$content = ImageUrlContent::fromFile('/path/to/image.png', 'low');\n```\n\nParameters:\n\n- `$file`: Path to the file as a string or a stream resource.\n- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').\n\n### InputAudioContent\n\nRepresents audio content encoded in base64 with a specified format. Supports capability `audio`.\n\nExample:\n\n```php\n$content = new InputAudioContent('base64encodeddata', 'wav');\n```\n\n### TextContent \u0026 JsonContent\n\n`TextContent` represents plain text or text read from a file. It supports the `text` capability.\n\n`JsonContent` represents structured data in JSON format. It also supports the `text` capability.\n\nExamples:\n\n```php\n$text = new TextContent('Hello, world!');\n$json = new JsonContent(['key' =\u003e 'value']);\n```\n\nWhen creating a `TextContent` instance, you can pass an associative array of placeholders that will be applied to the\ncontent using `str_replace`. This allows dynamic content generation based on placeholders in the text.\n\nExample:\n\n```php\n$content = new TextContent('Hello {name}, you are {age} years old.', ['name' =\u003e 'John', 'age' =\u003e 30]);\necho $content; // Outputs: \"Hello John, you are 30 years old.\"\n```\n\nThe placeholders are applied using the format `{key}` where `key` corresponds to the keys in the placeholder array.\n\nCreates a `TextContent` instance from a local file path or stream. The file content is automatically loaded and can be\nprocessed with optional placeholders.\n\nExample:\n\n```php\n$content = TextContent::fromFile('/path/to/text.txt', ['name' =\u003e 'John', 'age' =\u003e 30]);\n```\n\nParameters:\n\n- `$file`: Path to the file as a string or a stream resource.\n- `$placeholders`: Optional associative array of placeholders to apply to the content.\n\n## Response Formats\n\nControl the output format of the LLM response using `withResponseFormat()`.\n\n### Text (default)\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\ResponseFormat\\TextFormat;\n\n$completion = (new Completion(['Explain gravity']))\n    -\u003ewithResponseFormat(new TextFormat());\n```\n\n### JSON Object\n\nForces the LLM to return valid JSON. Requires a provider with `JSON_OUTPUT` capability.\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\ResponseFormat\\JsonObjectFormat;\n\n$completion = (new Completion(['List 3 colors as a JSON array']))\n    -\u003ewithResponseFormat(new JsonObjectFormat());\n\n$response = $llm-\u003echat($completion);\n$data = json_decode($response-\u003egetLastMessage()-\u003egetContent(), true);\n```\n\n### JSON Schema\n\nForces the LLM to return JSON conforming to a specific schema. Requires a provider with `JSON_SCHEMA` capability.\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\ResponseFormat\\JsonSchemaFormat;\n\n$completion = (new Completion(['Describe a person']))\n    -\u003ewithResponseFormat(new JsonSchemaFormat(\n        name: 'person',\n        schema: [\n            'type' =\u003e 'object',\n            'properties' =\u003e [\n                'name' =\u003e ['type' =\u003e 'string'],\n                'age' =\u003e ['type' =\u003e 'integer'],\n            ],\n            'required' =\u003e ['name', 'age'],\n        ],\n        strict: true,\n    ));\n\n$response = $llm-\u003echat($completion);\n// {\"name\": \"John\", \"age\": 30}\n```\n\n## Tools (Function Calling)\n\nTools allow the LLM to call external functions during inference. The library handles the tool execution loop\nautomatically.\n\n### Defining a tool\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Tool\\Tool;\n\n$weatherTool = new Tool(\n    name: 'get_weather',\n    description: 'Get the current weather for a location',\n    parameters: [\n        'type' =\u003e 'object',\n        'properties' =\u003e [\n            'location' =\u003e [\n                'type' =\u003e 'string',\n                'description' =\u003e 'The city name',\n            ],\n        ],\n        'required' =\u003e ['location'],\n    ],\n    callback: function (array $arguments): array {\n        // Your logic here\n        return [\n            'temperature' =\u003e 20,\n            'unit' =\u003e 'celsius',\n            'condition' =\u003e 'sunny',\n        ];\n    },\n);\n```\n\n### Using tools in a completion\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\nuse ByCerfrance\\LlmApiLib\\Llm;\n\n$completion = (new Completion(['Quel temps fait-il a Paris ?']))\n    -\u003ewithTools($weatherTool)\n    -\u003ewithMaxToolIterations(5); // Optional, default is 10\n\n$llm = new Llm($provider);\n$response = $llm-\u003echat($completion);\n\nprint $response-\u003egetLastMessage()-\u003egetContent();\n// \"Il fait actuellement 20°C a Paris avec un temps ensoleille.\"\n```\n\n### Multiple tools\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Tool\\ToolCollection;\n\n$completion = (new Completion(['...']))\n    -\u003ewithTools($weatherTool, $calculatorTool, $searchTool);\n\n// Or using a collection\n$tools = new ToolCollection($weatherTool, $calculatorTool);\n$completion = (new Completion(['...']))-\u003ewithTools($tools);\n```\n\n### Filtered tools\n\nUse `FilteredToolCollection` to restrict which tools are visible to the LLM. Supports include patterns (whitelist) and\nexclude patterns (prefix with `!`):\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Tool\\FilteredToolCollection;\n\n// Only expose specific tools\n$filtered = new FilteredToolCollection($toolCollection, ['get_weather', 'search']);\n\n// Exclude specific tools (expose everything else)\n$filtered = new FilteredToolCollection($toolCollection, ['!delete_user', '!drop_table']);\n```\n\n### Tool choice\n\nControl whether and how the model should use tools with `ToolChoice`:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\nuse ByCerfrance\\LlmApiLib\\Completion\\ToolChoice;\n\n$completion = (new Completion(['What is the weather in Paris?']))\n    -\u003ewithTools($weatherTool)\n    -\u003ewithToolChoice(ToolChoice::REQUIRED);\n```\n\n| Value      | Description                                      |\n|------------|--------------------------------------------------|\n| `AUTO`     | The model decides whether to call tools (default)|\n| `NONE`     | The model must not call any tool                 |\n| `REQUIRED` | The model must call at least one tool            |\n\nWhen `null` (default), `tool_choice` is not sent in the payload and the provider uses its own default (typically `auto`).\nMistral uses `any` instead of `required`; the library remaps this automatically.\n\n### Parallel tool calls\n\nControl whether the model can issue multiple tool calls in a single turn:\n\n```php\n$completion = (new Completion(['...']))\n    -\u003ewithTools($weatherTool, $calculatorTool)\n    -\u003ewithParallelToolCalls(false); // Force sequential tool calls\n```\n\nWhen `null` (default), `parallel_tool_calls` is not sent in the payload and the provider uses its own default (typically\n`true`). Set to `false` to force the model to call tools one at a time.\n\n### Tool execution loop\n\nThe library automatically:\n\n- Sends tools definition to the LLM\n- Detects when the LLM wants to call a tool\n- Executes the callback with the provided arguments\n- Sends the result back to the LLM\n- Continues until the LLM provides a final response or max iterations is reached\n\n## MCP Client (Model Context Protocol)\n\nThe library includes a full MCP client that connects to remote MCP servers, discovers tools, and executes them. MCP\nservers implement `ToolCollectionInterface` and can be passed directly to `withTools()`.\n\n### McpServer\n\n```php\nuse ByCerfrance\\LlmApiLib\\Mcp\\McpServer;\nuse ByCerfrance\\LlmApiLib\\Mcp\\Transport\\HttpStreamable;\n\n// Create transport\n$transport = new HttpStreamable(\n    uri: 'https://my-mcp-server.com/mcp',\n    client: $httpClient,\n    headers: ['Authorization' =\u003e 'Bearer my-token'],\n);\n\n// Create MCP server client\n$mcp = new McpServer($transport);\n\n// Use MCP tools in a completion (tools are discovered automatically via lazy initialization)\n$completion = (new Completion(['Search for documents about PHP']))\n    -\u003ewithTools($mcp);\n\n$response = $llm-\u003echat($completion);\n```\n\nThe MCP client handles the full lifecycle: initialization handshake, tool discovery (with pagination), tool execution\nvia\nJSON-RPC `tools/call`, and graceful shutdown.\n\n### OpenAPI integration\n\nConnect to any REST API described by an OpenAPI 3.x specification. Each operation becomes a tool the LLM can call.\n\n\u003e Requires the optional dependency: `composer require devizzent/cebe-php-openapi`\n\n```php\nuse ByCerfrance\\LlmApiLib\\Mcp\\OpenApi;\nuse cebe\\openapi\\Reader;\n\n$spec = Reader::readFromJsonFile('/path/to/openapi.json');\n\n$openApi = new OpenApi(\n    spec: $spec,\n    client: $httpClient,\n    headers: ['Authorization' =\u003e 'Bearer api-token'],\n    baseUrl: 'https://api.example.com', // Optional, overrides spec servers\n);\n\n// Use OpenAPI operations as tools\n$completion = (new Completion(['List all users']))\n    -\u003ewithTools($openApi);\n\n// Or filter specific operations\n$filtered = new FilteredToolCollection($openApi, ['listUsers', 'getUser']);\n$completion = (new Completion(['List all users']))\n    -\u003ewithTools($filtered);\n\n$response = $llm-\u003echat($completion);\n```\n\n## LlmTool (Agentic Sub-Model Delegation)\n\n`LlmTool` allows the orchestrator LLM to delegate tasks to a different model via tool calling:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Completion\\Tool\\LlmTool;\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\n\n$analysisTool = new LlmTool(\n    name: 'analyze_code',\n    description: 'Analyze code for security vulnerabilities',\n    parameters: [\n        'type' =\u003e 'object',\n        'properties' =\u003e [\n            'code' =\u003e ['type' =\u003e 'string', 'description' =\u003e 'The code to analyze'],\n            'language' =\u003e ['type' =\u003e 'string', 'description' =\u003e 'Programming language'],\n        ],\n        'required' =\u003e ['code'],\n    ],\n    llm: $specializedProvider, // A different LlmInterface (e.g., a more powerful model)\n    promptBuilder: fn(string $code, string $language = 'php') =\u003e new Completion([\n        \"Analyze this {$language} code for security issues:\\n{$code}\",\n    ]),\n);\n\n$completion = (new Completion(['Review my application for security issues']))\n    -\u003ewithTools($analysisTool);\n\n$response = $llm-\u003echat($completion);\n\n// Aggregate usage/cost across all sub-model calls\n$subModelUsage = $analysisTool-\u003egetLlm()-\u003egetUsage();\n$subModelCost = $analysisTool-\u003egetLlm()-\u003egetCost();\n```\n\n## Model Selection\n\n### Selection strategy\n\nWhen using multiple providers with `Llm`, control which provider is preferred:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Model\\SelectionStrategy;\nuse ByCerfrance\\LlmApiLib\\Completion\\Completion;\n\n$completion = (new Completion(['Complex reasoning task']))\n    -\u003ewithSelectionStrategy(SelectionStrategy::BEST_QUALITY);\n```\n\nAvailable strategies:\n\n| Strategy       | Description               | Scoring formula        |\n|----------------|---------------------------|------------------------|\n| `CHEAP`        | Prefer low-cost providers | 80% cost + 20% quality |\n| `BALANCED`     | Balance cost and quality  | 50% cost + 50% quality |\n| `BEST_QUALITY` | Prefer highest quality    | 80% quality + 20% cost |\n\nScoring is based on the `QualityTier` (BASIC, GOOD, PREMIUM) and `CostTier` (LOW, MEDIUM, HIGH) defined in each\nprovider's `ModelInfo`.\n\n## Response Handling\n\n### CompletionResponseInterface\n\nThe `chat()` method returns a `CompletionResponseInterface` which extends `CompletionInterface` with additional response\ndata:\n\n```php\n$response = $llm-\u003echat('Hello');\n\n// Access the response content\n$content = $response-\u003egetLastMessage()-\u003egetContent();\n\n// Per-request token usage\n$usage = $response-\u003egetUsage();\necho $usage-\u003egetPromptTokens();\necho $usage-\u003egetCompletionTokens();\necho $usage-\u003egetTotalTokens();\n\n// Finish reason\n$finishReason = $response-\u003egetFinishReason(); // FinishReason::STOP, LENGTH, TOOL_CALLS, CONTENT_FILTER\n\n// Continue the conversation (CompletionResponseInterface extends CompletionInterface)\n$response = $llm-\u003echat($response-\u003ewithNewMessage('Follow up question'));\n```\n\n### FinishReason\n\nThe `FinishReason` enum indicates why the LLM stopped generating:\n\n| Value            | Description                                          |\n|------------------|------------------------------------------------------|\n| `STOP`           | Normal completion                                    |\n| `LENGTH`         | Maximum token limit reached                          |\n| `TOOL_CALLS`     | Model wants to call tools (handled automatically)    |\n| `CONTENT_FILTER` | Content was filtered by the provider's safety system |\n\n## Retry\n\nThe `Retry` decorator wraps any `LlmInterface` and retries on failure with configurable backoff:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Retry;\n\n$retryableProvider = new Retry(\n    provider: $provider,\n    time: 5000,          // Base wait time in milliseconds (default: 5000)\n    retry: 3,            // Maximum retry attempts (default: 2)\n    multiplier: 2.0,     // Exponential backoff multiplier (default: 1 = constant delay)\n    retryOnGuard: false, // Retry on GuardException (default: false)\n);\n\n// Wait times: 5s, 10s, 20s (time * multiplier^attempt)\n$response = $retryableProvider-\u003echat('Hello');\n```\n\n## Guard System\n\nGuards validate LLM responses after each `chat()` call. If validation fails, a `GuardException` is thrown with the\nrejected response attached.\n\n### Custom guard\n\n```php\nuse ByCerfrance\\LlmApiLib\\Guard\\Guard;\nuse ByCerfrance\\LlmApiLib\\Guard\\GuardException;\n\n$guarded = new Guard(\n    provider: $provider,\n    guard: function (\\ByCerfrance\\LlmApiLib\\Completion\\CompletionResponseInterface $response): void {\n        $content = $response-\u003egetLastMessage()-\u003egetContent();\n        if (str_contains($content, 'I cannot')) {\n            throw new \\RuntimeException('Response contains a refusal');\n        }\n    },\n);\n\ntry {\n    $response = $guarded-\u003echat('...');\n} catch (GuardException $e) {\n    $rejectedResponse = $e-\u003egetResponse(); // Access the rejected response\n    echo $e-\u003egetMessage();\n}\n```\n\n### FinishReasonGuard\n\nA built-in guard that rejects responses with specific finish reasons (defaults to `LENGTH` and `CONTENT_FILTER`):\n\n```php\nuse ByCerfrance\\LlmApiLib\\Guard\\FinishReasonGuard;\nuse ByCerfrance\\LlmApiLib\\Completion\\FinishReason;\n\n// Default: rejects LENGTH and CONTENT_FILTER\n$guarded = new FinishReasonGuard($provider);\n\n// Custom: only reject LENGTH\n$guarded = new FinishReasonGuard($provider, FinishReason::LENGTH);\n```\n\n### Combining Guard + Retry\n\nGuards and retries compose naturally as decorators:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Guard\\FinishReasonGuard;\nuse ByCerfrance\\LlmApiLib\\Retry;\n\n// Retry up to 3 times if the response is truncated (LENGTH) or filtered\n$robust = new Retry(\n    provider: new FinishReasonGuard($provider),\n    retry: 3,\n    retryOnGuard: true, // Required to retry on GuardException\n);\n\n$response = $robust-\u003echat('...');\n```\n\n## Failover\n\nThe `Llm` class accepts multiple providers and implements automatic failover:\n\n```php\nuse ByCerfrance\\LlmApiLib\\Llm;\n\n$llm = new Llm($openAiProvider, $mistralProvider, $googleProvider);\n\n// If OpenAI fails, Mistral is tried. If Mistral fails, Google is tried.\n$response = $llm-\u003echat('Hello');\n```\n\n### Capability-based filtering\n\nBefore attempting providers, `Llm` automatically filters them by required capabilities. If a message contains an image,\nonly providers with the `IMAGE` capability are tried. If a `JsonSchemaFormat` is used, only providers with `JSON_SCHEMA`\nare tried.\n\n### Strategy-based ordering\n\nWhen a `SelectionStrategy` is set on the completion, providers are sorted by their score (based on `ModelInfo`\nquality/cost tiers) before the failover sequence begins.\n\n## Logging\n\nThe `chat()` method accepts an optional PSR-3 logger for per-call logging:\n\n```php\nuse Psr\\Log\\LoggerInterface;\n\n/** @var LoggerInterface $logger */\n$response = $llm-\u003echat($completion, logger: $logger);\n```\n\nThe library logs:\n\n- Provider selection and routing decisions\n- Request initiation and completion metrics (tokens, cost, finish reason)\n- Tool call counts and execution\n- Retry attempts with wait times\n- Failover transitions with error details\n\n## Usage \u0026 Cost Tracking\n\n### Token usage\n\nRetrieve aggregated token usage across all calls:\n\n```php\n$usage = $llm-\u003egetUsage();\necho $usage-\u003egetPromptTokens();      // Total input tokens\necho $usage-\u003egetCompletionTokens();  // Total output tokens\necho $usage-\u003egetTotalTokens();       // Total tokens\n```\n\n### Cost tracking\n\nCalculate monetary cost based on `ModelInfo` pricing:\n\n```php\n$cost = $llm-\u003egetCost();           // Total cost in dollars (4 decimal precision)\n$cost = $llm-\u003egetCost(precision: 6); // Higher precision\n```\n\nCost is computed as: `(promptTokens * inputCost / 1M) + (completionTokens * outputCost / 1M)`.\n\n### Context window \u0026 output limit\n\nQuery the model's maximum context window and output token limits:\n\n```php\n$maxTokens = $llm-\u003egetMaxContextTokens();  // e.g. 128000, or null if undefined\n$maxOutput = $llm-\u003egetMaxOutputTokens();   // e.g. 16384, or null if undefined\n```\n\nWhen using multi-provider `Llm`, both methods return the minimum across all providers.\n\n## Capabilities\n\nThis library supports a wide range of LLM capabilities, allowing developers to leverage advanced features such as\nmultimodal processing, structured output, and reasoning. The following table lists the supported capabilities along with\ntheir descriptions.\n\n| Capability      | Description (English)                                                                                                       |\n|-----------------|-----------------------------------------------------------------------------------------------------------------------------|\n| **text**        | Ability to read, process, and generate natural language text.                                                               |\n| **image**       | Ability to interpret visual content from images.                                                                            |\n| **ocr**         | Ability to extract textual content embedded within images (printed or handwritten).                                         |\n| **document**    | Ability to process structured, often multi-page documents (e.g., PDFs), including visual layout and textual interpretation. |\n| **audio**       | Ability to process and interpret speech or audio signals.                                                                   |\n| **video**       | Ability to understand and analyze visual-temporal content from videos.                                                      |\n| **reasoning**   | Ability to perform logical, analytical, or multi-step reasoning to derive conclusions.                                      |\n| **json_output** | Ability to generate responses strictly formatted as valid JSON.                                                             |\n| **json_schema** | Ability to generate responses that strictly follow a predefined JSON schema.                                                |\n| **code**        | Ability to interpret, generate, or transform programming code.                                                              |\n| **tools**       | Ability to call external tools or functions during inference.                                                               |\n| **multimodal**  | Ability to combine and reason across multiple input types (e.g., text + image + audio + video).                             |\n\nEach provider implementing the `LlmInterface` must declare its supported capabilities via the `getCapabilities()`\nmethod. The `Llm` class automatically filters providers based on compatibility with the requested capabilities, ensuring\nthat only suitable providers are used for each request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbycerfrance%2Fllmapilib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbycerfrance%2Fllmapilib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbycerfrance%2Fllmapilib/lists"}