https://github.com/bycerfrance/llmapilib
PHP library for interacting with multiple LLM providers (Google, Mistral, OpenAI, OVH and any OpenAI-compatible endpoint) with failover, retry, and tool calling support.
https://github.com/bycerfrance/llmapilib
api chat completion google llm mistral openai ovh php
Last synced: about 1 month ago
JSON representation
PHP library for interacting with multiple LLM providers (Google, Mistral, OpenAI, OVH and any OpenAI-compatible endpoint) with failover, retry, and tool calling support.
- Host: GitHub
- URL: https://github.com/bycerfrance/llmapilib
- Owner: ByCerfrance
- License: mit
- Created: 2026-02-24T17:21:14.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-28T23:01:27.000Z (about 2 months ago)
- Last Synced: 2026-04-29T00:11:24.405Z (about 2 months ago)
- Topics: api, chat, completion, google, llm, mistral, openai, ovh, php
- Language: PHP
- Homepage: https://www.vigicorp.fr
- Size: 345 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# LLM API Library
[](https://github.com/ByCerfrance/LlmApiLib/releases)

[](https://github.com/ByCerfrance/LlmApiLib/blob/main/LICENSE)
[](https://github.com/ByCerfrance/LlmApiLib/actions/workflows/tests.yml?query=branch%3Amain)
[](https://packagist.org/packages/bycerfrance/llm-api-lib)
PHP 8.3+ library for interacting with multiple LLM providers (Google, Mistral, OpenAI, OVH and any OpenAI-compatible
endpoint) with failover, retry, guard validation, tool calling, MCP client, and OpenAPI integration support.
## Installation
You can install library with [Composer](https://getcomposer.org/), it's the recommended installation.
```bash
$ composer require bycerfrance/llm-api-lib
```
## Providers
### Built-in providers
- **Google** -- Google Generative Language API
- **Mistral** -- Mistral AI API
- **OpenAI** -- OpenAI API
- **OVH** -- OVH AI Endpoints
### Generic (OpenAI-compatible)
The `Generic` provider connects to any OpenAI-compatible endpoint (local servers, proxies, third-party providers):
```php
use ByCerfrance\LlmApiLib\Provider\Generic;
$provider = new Generic(
uri: 'https://my-local-server.com/v1/chat/completions',
apiKey: 'my-api-key',
model: 'my-model',
client: $httpClient, // PSR-18 ClientInterface
);
```
### Model metadata
Use `ModelInfo` to attach rich metadata to a provider (capabilities, quality/cost tiers, pricing, context window, output limit):
```php
use ByCerfrance\LlmApiLib\Model\ModelInfo;
use ByCerfrance\LlmApiLib\Model\QualityTier;
use ByCerfrance\LlmApiLib\Model\CostTier;
use ByCerfrance\LlmApiLib\Model\Capability;
use ByCerfrance\LlmApiLib\Provider\OpenAi;
$model = new ModelInfo(
name: 'gpt-4o',
capabilities: [Capability::TEXT, Capability::IMAGE, Capability::TOOLS, Capability::JSON_OUTPUT],
qualityTier: QualityTier::PREMIUM,
costTier: CostTier::HIGH,
inputCost: 2.50, // $ per million tokens
outputCost: 10.00, // $ per million tokens
maxContextTokens: 128_000,
maxOutputTokens: 16_384,
);
$provider = new OpenAi(
apiKey: 'sk-...',
model: $model, // ModelInfo or plain string
client: $httpClient,
);
```
## Chat
### Basic usage
```php
$llm = new \ByCerfrance\LlmApiLib\Llm($provider);
$completion = $llm->chat('Salut !');
print $completion->getLastMessage()->getContent(); // "Salut ! Comment allez-vous ?"
$completion = $llm->chat($completion->withNewMessage('Bien merci et toi ?'));
print $completion->getLastMessage()->getContent(); // "Bien, merci. Comment puis-je vous aider ?"
```
### With instructions
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
use ByCerfrance\LlmApiLib\Completion\Message\SystemMessage;
use ByCerfrance\LlmApiLib\Llm;
$completion = new Completion(new SystemMessage(
'Tu es un assistant comptable, presentes toi comme tel.',
));
$llm = new Llm($provider);
$completion = $llm->chat($completion->withNewMessage('Salut !'));
print $completion->getLastMessage()->getContent();
// "Bonjour, je suis votre assistant comptable. Comment puis-je vous aider ?"
```
### Message types
The library provides typed message classes for convenience:
```php
use ByCerfrance\LlmApiLib\Completion\Message\SystemMessage;
use ByCerfrance\LlmApiLib\Completion\Message\UserMessage;
use ByCerfrance\LlmApiLib\Completion\Message\Message;
use ByCerfrance\LlmApiLib\Completion\Message\RoleEnum;
// Typed classes (recommended)
$system = new SystemMessage('You are a helpful assistant.');
$user = new UserMessage('Hello!');
// Or using the generic Message class with explicit role
$system = new Message('You are a helpful assistant.', role: RoleEnum::SYSTEM);
```
### Completion parameters
Fine-tune the LLM behavior with immutable `with*()` methods:
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
$completion = (new Completion(['Explain quantum computing']))
->withModel('gpt-4o') // Override the provider's default model
->withMaxTokens(2000) // Maximum tokens in the response
->withTemperature(0.7) // Creativity (0 = deterministic, 2 = very creative)
->withTopP(0.9) // Nucleus sampling
->withSeed(42); // Reproducible outputs (provider-dependent)
```
### Service tier
Control the processing priority and pricing tier with `ServiceTier`:
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
use ByCerfrance\LlmApiLib\Completion\ServiceTier;
$completion = (new Completion(['Summarize this report']))
->withServiceTier(ServiceTier::FLEX);
```
| Value | Description |
|------------|------------------------------------------------|
| `AUTO` | Let the provider choose the best tier |
| `DEFAULT` | Standard processing |
| `FLEX` | Lower-priority, reduced-cost processing |
| `PRIORITY` | Higher-priority processing |
Each value defines a `fallback()` method for provider compatibility: `PRIORITY` → `AUTO` → `DEFAULT`, `FLEX` → `AUTO` →
`DEFAULT`. Providers that do not support `service_tier` (e.g., Mistral) automatically strip it from the payload.
### Reasoning effort
Control how much reasoning the model performs before generating a response with `ReasoningEffort`:
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
use ByCerfrance\LlmApiLib\Completion\ReasoningEffort;
$completion = (new Completion(['Solve this math problem step by step']))
->withReasoningEffort(ReasoningEffort::HIGH);
```
| Value | Description |
|----------|-------------------------------------------------|
| `NONE` | No reasoning traces |
| `LOW` | Minimal reasoning |
| `MEDIUM` | Moderate reasoning |
| `HIGH` | Full reasoning traces |
| `XHIGH` | Extended reasoning (OpenAI o3/o4-mini) |
Each value defines a `fallback()` method: `XHIGH` → `HIGH` → `MEDIUM` → `LOW` → `NONE`. Provider-specific builders
use this chain to map unsupported values to the closest supported one. For example, Mistral only supports `HIGH` and
`NONE`, so `MEDIUM` falls back through `LOW` → `NONE`.
Setting `reasoningEffort` automatically adds `Capability::REASONING` to the completion's required capabilities, ensuring
only providers that support reasoning are selected during failover.
## Content Types
### ArrayContent
Allows combining multiple contents (`ContentInterface` or strings) into a single object. Useful for sending multiple
elements in a single message.
Example:
```php
$content = new ArrayContent(
new TextContent('First message'),
'Second message'
);
```
### DocumentUrlContent
Represents a document accessible via a URL. Supports capabilities `document` and `ocr`.
Example:
```php
$content = new DocumentUrlContent('https://example.com/document.pdf');
```
Creates a `DocumentUrlContent` instance from a local file path or stream. The file is automatically converted to a
base64-encoded data URL.
Example:
```php
$content = DocumentUrlContent::fromFile('/path/to/document.pdf', 'custom-name.pdf');
```
Parameters:
- `$file`: Path to the file as a string or a stream resource.
- `$name`: Optional custom name for the document.
- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').
### ImageUrlContent
Represents an image accessible via a URL. Supports capabilities `image` and `ocr`.
Example:
```php
$content = new ImageUrlContent('https://example.com/image.jpg');
```
Creates an `ImageUrlContent` instance from a GD image resource. The image is automatically converted to a base64-encoded
data URL.
Example:
```php
$content = ImageUrlContent::fromGdImage($gdImage, 'high');
```
Parameters:
- `$image`: A GD image resource.
- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').
- `$maxSize`: Optional maximum size for resizing the image.
- `$format`: Optional image format ('jpeg', 'png', 'gif', 'webp').
- `$quality`: Optional quality setting for JPEG/PNG/WebP formats.
Creates an `ImageUrlContent` instance from a local file path or stream. The file is automatically converted to a
base64-encoded data URL.
Example:
```php
$content = ImageUrlContent::fromFile('/path/to/image.png', 'low');
```
Parameters:
- `$file`: Path to the file as a string or a stream resource.
- `$detail`: Optional detail level for processing (e.g., 'auto', 'low', 'high').
### InputAudioContent
Represents audio content encoded in base64 with a specified format. Supports capability `audio`.
Example:
```php
$content = new InputAudioContent('base64encodeddata', 'wav');
```
### TextContent & JsonContent
`TextContent` represents plain text or text read from a file. It supports the `text` capability.
`JsonContent` represents structured data in JSON format. It also supports the `text` capability.
Examples:
```php
$text = new TextContent('Hello, world!');
$json = new JsonContent(['key' => 'value']);
```
When creating a `TextContent` instance, you can pass an associative array of placeholders that will be applied to the
content using `str_replace`. This allows dynamic content generation based on placeholders in the text.
Example:
```php
$content = new TextContent('Hello {name}, you are {age} years old.', ['name' => 'John', 'age' => 30]);
echo $content; // Outputs: "Hello John, you are 30 years old."
```
The placeholders are applied using the format `{key}` where `key` corresponds to the keys in the placeholder array.
Creates a `TextContent` instance from a local file path or stream. The file content is automatically loaded and can be
processed with optional placeholders.
Example:
```php
$content = TextContent::fromFile('/path/to/text.txt', ['name' => 'John', 'age' => 30]);
```
Parameters:
- `$file`: Path to the file as a string or a stream resource.
- `$placeholders`: Optional associative array of placeholders to apply to the content.
## Response Formats
Control the output format of the LLM response using `withResponseFormat()`.
### Text (default)
```php
use ByCerfrance\LlmApiLib\Completion\ResponseFormat\TextFormat;
$completion = (new Completion(['Explain gravity']))
->withResponseFormat(new TextFormat());
```
### JSON Object
Forces the LLM to return valid JSON. Requires a provider with `JSON_OUTPUT` capability.
```php
use ByCerfrance\LlmApiLib\Completion\ResponseFormat\JsonObjectFormat;
$completion = (new Completion(['List 3 colors as a JSON array']))
->withResponseFormat(new JsonObjectFormat());
$response = $llm->chat($completion);
$data = json_decode($response->getLastMessage()->getContent(), true);
```
### JSON Schema
Forces the LLM to return JSON conforming to a specific schema. Requires a provider with `JSON_SCHEMA` capability.
```php
use ByCerfrance\LlmApiLib\Completion\ResponseFormat\JsonSchemaFormat;
$completion = (new Completion(['Describe a person']))
->withResponseFormat(new JsonSchemaFormat(
name: 'person',
schema: [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'age' => ['type' => 'integer'],
],
'required' => ['name', 'age'],
],
strict: true,
));
$response = $llm->chat($completion);
// {"name": "John", "age": 30}
```
## Tools (Function Calling)
Tools allow the LLM to call external functions during inference. The library handles the tool execution loop
automatically.
### Defining a tool
```php
use ByCerfrance\LlmApiLib\Completion\Tool\Tool;
$weatherTool = new Tool(
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city name',
],
],
'required' => ['location'],
],
callback: function (array $arguments): array {
// Your logic here
return [
'temperature' => 20,
'unit' => 'celsius',
'condition' => 'sunny',
];
},
);
```
### Using tools in a completion
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
use ByCerfrance\LlmApiLib\Llm;
$completion = (new Completion(['Quel temps fait-il a Paris ?']))
->withTools($weatherTool)
->withMaxToolIterations(5); // Optional, default is 10
$llm = new Llm($provider);
$response = $llm->chat($completion);
print $response->getLastMessage()->getContent();
// "Il fait actuellement 20°C a Paris avec un temps ensoleille."
```
### Multiple tools
```php
use ByCerfrance\LlmApiLib\Completion\Tool\ToolCollection;
$completion = (new Completion(['...']))
->withTools($weatherTool, $calculatorTool, $searchTool);
// Or using a collection
$tools = new ToolCollection($weatherTool, $calculatorTool);
$completion = (new Completion(['...']))->withTools($tools);
```
### Filtered tools
Use `FilteredToolCollection` to restrict which tools are visible to the LLM. Supports include patterns (whitelist) and
exclude patterns (prefix with `!`):
```php
use ByCerfrance\LlmApiLib\Completion\Tool\FilteredToolCollection;
// Only expose specific tools
$filtered = new FilteredToolCollection($toolCollection, ['get_weather', 'search']);
// Exclude specific tools (expose everything else)
$filtered = new FilteredToolCollection($toolCollection, ['!delete_user', '!drop_table']);
```
### Tool choice
Control whether and how the model should use tools with `ToolChoice`:
```php
use ByCerfrance\LlmApiLib\Completion\Completion;
use ByCerfrance\LlmApiLib\Completion\ToolChoice;
$completion = (new Completion(['What is the weather in Paris?']))
->withTools($weatherTool)
->withToolChoice(ToolChoice::REQUIRED);
```
| Value | Description |
|------------|--------------------------------------------------|
| `AUTO` | The model decides whether to call tools (default)|
| `NONE` | The model must not call any tool |
| `REQUIRED` | The model must call at least one tool |
When `null` (default), `tool_choice` is not sent in the payload and the provider uses its own default (typically `auto`).
Mistral uses `any` instead of `required`; the library remaps this automatically.
### Parallel tool calls
Control whether the model can issue multiple tool calls in a single turn:
```php
$completion = (new Completion(['...']))
->withTools($weatherTool, $calculatorTool)
->withParallelToolCalls(false); // Force sequential tool calls
```
When `null` (default), `parallel_tool_calls` is not sent in the payload and the provider uses its own default (typically
`true`). Set to `false` to force the model to call tools one at a time.
### Tool execution loop
The library automatically:
- Sends tools definition to the LLM
- Detects when the LLM wants to call a tool
- Executes the callback with the provided arguments
- Sends the result back to the LLM
- Continues until the LLM provides a final response or max iterations is reached
## MCP Client (Model Context Protocol)
The library includes a full MCP client that connects to remote MCP servers, discovers tools, and executes them. MCP
servers implement `ToolCollectionInterface` and can be passed directly to `withTools()`.
### McpServer
```php
use ByCerfrance\LlmApiLib\Mcp\McpServer;
use ByCerfrance\LlmApiLib\Mcp\Transport\HttpStreamable;
// Create transport
$transport = new HttpStreamable(
uri: 'https://my-mcp-server.com/mcp',
client: $httpClient,
headers: ['Authorization' => 'Bearer my-token'],
);
// Create MCP server client
$mcp = new McpServer($transport);
// Use MCP tools in a completion (tools are discovered automatically via lazy initialization)
$completion = (new Completion(['Search for documents about PHP']))
->withTools($mcp);
$response = $llm->chat($completion);
```
The MCP client handles the full lifecycle: initialization handshake, tool discovery (with pagination), tool execution
via
JSON-RPC `tools/call`, and graceful shutdown.
### OpenAPI integration
Connect to any REST API described by an OpenAPI 3.x specification. Each operation becomes a tool the LLM can call.
> Requires the optional dependency: `composer require devizzent/cebe-php-openapi`
```php
use ByCerfrance\LlmApiLib\Mcp\OpenApi;
use cebe\openapi\Reader;
$spec = Reader::readFromJsonFile('/path/to/openapi.json');
$openApi = new OpenApi(
spec: $spec,
client: $httpClient,
headers: ['Authorization' => 'Bearer api-token'],
baseUrl: 'https://api.example.com', // Optional, overrides spec servers
);
// Use OpenAPI operations as tools
$completion = (new Completion(['List all users']))
->withTools($openApi);
// Or filter specific operations
$filtered = new FilteredToolCollection($openApi, ['listUsers', 'getUser']);
$completion = (new Completion(['List all users']))
->withTools($filtered);
$response = $llm->chat($completion);
```
## LlmTool (Agentic Sub-Model Delegation)
`LlmTool` allows the orchestrator LLM to delegate tasks to a different model via tool calling:
```php
use ByCerfrance\LlmApiLib\Completion\Tool\LlmTool;
use ByCerfrance\LlmApiLib\Completion\Completion;
$analysisTool = new LlmTool(
name: 'analyze_code',
description: 'Analyze code for security vulnerabilities',
parameters: [
'type' => 'object',
'properties' => [
'code' => ['type' => 'string', 'description' => 'The code to analyze'],
'language' => ['type' => 'string', 'description' => 'Programming language'],
],
'required' => ['code'],
],
llm: $specializedProvider, // A different LlmInterface (e.g., a more powerful model)
promptBuilder: fn(string $code, string $language = 'php') => new Completion([
"Analyze this {$language} code for security issues:\n{$code}",
]),
);
$completion = (new Completion(['Review my application for security issues']))
->withTools($analysisTool);
$response = $llm->chat($completion);
// Aggregate usage/cost across all sub-model calls
$subModelUsage = $analysisTool->getLlm()->getUsage();
$subModelCost = $analysisTool->getLlm()->getCost();
```
## Model Selection
### Selection strategy
When using multiple providers with `Llm`, control which provider is preferred:
```php
use ByCerfrance\LlmApiLib\Model\SelectionStrategy;
use ByCerfrance\LlmApiLib\Completion\Completion;
$completion = (new Completion(['Complex reasoning task']))
->withSelectionStrategy(SelectionStrategy::BEST_QUALITY);
```
Available strategies:
| Strategy | Description | Scoring formula |
|----------------|---------------------------|------------------------|
| `CHEAP` | Prefer low-cost providers | 80% cost + 20% quality |
| `BALANCED` | Balance cost and quality | 50% cost + 50% quality |
| `BEST_QUALITY` | Prefer highest quality | 80% quality + 20% cost |
Scoring is based on the `QualityTier` (BASIC, GOOD, PREMIUM) and `CostTier` (LOW, MEDIUM, HIGH) defined in each
provider's `ModelInfo`.
## Response Handling
### CompletionResponseInterface
The `chat()` method returns a `CompletionResponseInterface` which extends `CompletionInterface` with additional response
data:
```php
$response = $llm->chat('Hello');
// Access the response content
$content = $response->getLastMessage()->getContent();
// Per-request token usage
$usage = $response->getUsage();
echo $usage->getPromptTokens();
echo $usage->getCompletionTokens();
echo $usage->getTotalTokens();
// Finish reason
$finishReason = $response->getFinishReason(); // FinishReason::STOP, LENGTH, TOOL_CALLS, CONTENT_FILTER
// Continue the conversation (CompletionResponseInterface extends CompletionInterface)
$response = $llm->chat($response->withNewMessage('Follow up question'));
```
### FinishReason
The `FinishReason` enum indicates why the LLM stopped generating:
| Value | Description |
|------------------|------------------------------------------------------|
| `STOP` | Normal completion |
| `LENGTH` | Maximum token limit reached |
| `TOOL_CALLS` | Model wants to call tools (handled automatically) |
| `CONTENT_FILTER` | Content was filtered by the provider's safety system |
## Retry
The `Retry` decorator wraps any `LlmInterface` and retries on failure with configurable backoff:
```php
use ByCerfrance\LlmApiLib\Retry;
$retryableProvider = new Retry(
provider: $provider,
time: 5000, // Base wait time in milliseconds (default: 5000)
retry: 3, // Maximum retry attempts (default: 2)
multiplier: 2.0, // Exponential backoff multiplier (default: 1 = constant delay)
retryOnGuard: false, // Retry on GuardException (default: false)
);
// Wait times: 5s, 10s, 20s (time * multiplier^attempt)
$response = $retryableProvider->chat('Hello');
```
## Guard System
Guards validate LLM responses after each `chat()` call. If validation fails, a `GuardException` is thrown with the
rejected response attached.
### Custom guard
```php
use ByCerfrance\LlmApiLib\Guard\Guard;
use ByCerfrance\LlmApiLib\Guard\GuardException;
$guarded = new Guard(
provider: $provider,
guard: function (\ByCerfrance\LlmApiLib\Completion\CompletionResponseInterface $response): void {
$content = $response->getLastMessage()->getContent();
if (str_contains($content, 'I cannot')) {
throw new \RuntimeException('Response contains a refusal');
}
},
);
try {
$response = $guarded->chat('...');
} catch (GuardException $e) {
$rejectedResponse = $e->getResponse(); // Access the rejected response
echo $e->getMessage();
}
```
### FinishReasonGuard
A built-in guard that rejects responses with specific finish reasons (defaults to `LENGTH` and `CONTENT_FILTER`):
```php
use ByCerfrance\LlmApiLib\Guard\FinishReasonGuard;
use ByCerfrance\LlmApiLib\Completion\FinishReason;
// Default: rejects LENGTH and CONTENT_FILTER
$guarded = new FinishReasonGuard($provider);
// Custom: only reject LENGTH
$guarded = new FinishReasonGuard($provider, FinishReason::LENGTH);
```
### Combining Guard + Retry
Guards and retries compose naturally as decorators:
```php
use ByCerfrance\LlmApiLib\Guard\FinishReasonGuard;
use ByCerfrance\LlmApiLib\Retry;
// Retry up to 3 times if the response is truncated (LENGTH) or filtered
$robust = new Retry(
provider: new FinishReasonGuard($provider),
retry: 3,
retryOnGuard: true, // Required to retry on GuardException
);
$response = $robust->chat('...');
```
## Failover
The `Llm` class accepts multiple providers and implements automatic failover:
```php
use ByCerfrance\LlmApiLib\Llm;
$llm = new Llm($openAiProvider, $mistralProvider, $googleProvider);
// If OpenAI fails, Mistral is tried. If Mistral fails, Google is tried.
$response = $llm->chat('Hello');
```
### Capability-based filtering
Before attempting providers, `Llm` automatically filters them by required capabilities. If a message contains an image,
only providers with the `IMAGE` capability are tried. If a `JsonSchemaFormat` is used, only providers with `JSON_SCHEMA`
are tried.
### Strategy-based ordering
When a `SelectionStrategy` is set on the completion, providers are sorted by their score (based on `ModelInfo`
quality/cost tiers) before the failover sequence begins.
## Logging
The `chat()` method accepts an optional PSR-3 logger for per-call logging:
```php
use Psr\Log\LoggerInterface;
/** @var LoggerInterface $logger */
$response = $llm->chat($completion, logger: $logger);
```
The library logs:
- Provider selection and routing decisions
- Request initiation and completion metrics (tokens, cost, finish reason)
- Tool call counts and execution
- Retry attempts with wait times
- Failover transitions with error details
## Usage & Cost Tracking
### Token usage
Retrieve aggregated token usage across all calls:
```php
$usage = $llm->getUsage();
echo $usage->getPromptTokens(); // Total input tokens
echo $usage->getCompletionTokens(); // Total output tokens
echo $usage->getTotalTokens(); // Total tokens
```
### Cost tracking
Calculate monetary cost based on `ModelInfo` pricing:
```php
$cost = $llm->getCost(); // Total cost in dollars (4 decimal precision)
$cost = $llm->getCost(precision: 6); // Higher precision
```
Cost is computed as: `(promptTokens * inputCost / 1M) + (completionTokens * outputCost / 1M)`.
### Context window & output limit
Query the model's maximum context window and output token limits:
```php
$maxTokens = $llm->getMaxContextTokens(); // e.g. 128000, or null if undefined
$maxOutput = $llm->getMaxOutputTokens(); // e.g. 16384, or null if undefined
```
When using multi-provider `Llm`, both methods return the minimum across all providers.
## Capabilities
This library supports a wide range of LLM capabilities, allowing developers to leverage advanced features such as
multimodal processing, structured output, and reasoning. The following table lists the supported capabilities along with
their descriptions.
| Capability | Description (English) |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------|
| **text** | Ability to read, process, and generate natural language text. |
| **image** | Ability to interpret visual content from images. |
| **ocr** | Ability to extract textual content embedded within images (printed or handwritten). |
| **document** | Ability to process structured, often multi-page documents (e.g., PDFs), including visual layout and textual interpretation. |
| **audio** | Ability to process and interpret speech or audio signals. |
| **video** | Ability to understand and analyze visual-temporal content from videos. |
| **reasoning** | Ability to perform logical, analytical, or multi-step reasoning to derive conclusions. |
| **json_output** | Ability to generate responses strictly formatted as valid JSON. |
| **json_schema** | Ability to generate responses that strictly follow a predefined JSON schema. |
| **code** | Ability to interpret, generate, or transform programming code. |
| **tools** | Ability to call external tools or functions during inference. |
| **multimodal** | Ability to combine and reason across multiple input types (e.g., text + image + audio + video). |
Each provider implementing the `LlmInterface` must declare its supported capabilities via the `getCapabilities()`
method. The `Llm` class automatically filters providers based on compatibility with the requested capabilities, ensuring
that only suitable providers are used for each request.