Skip to content
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ GEMINI_API_KEY=xxxxx
# Get your key from: https://console.scaleway.com/
SCALEWAY_API_KEY=xxxxx

# OpenRouter API Key (OpenAI compatible)
# Get your key from: https://openrouter.ai/keys
OPENROUTER_API_KEY=sk-or-xxxxx

# Optional: Set to 'true' to enable verbose output during tests
INTEGRATION_TEST_VERBOSE=false

Expand Down
123 changes: 87 additions & 36 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# PHP LLM - Agentic AI Framework for PHP

[![Latest Version](https://img.shields.io/packagist/v/soukicz/llm.svg)](https://packagist.org/packages/soukicz/llm)
[![License](https://img.shields.io/packagist/l/soukicz/llm.svg)](https://packagist.org/packages/soukicz/llm)

Build powerful **AI agents** that can use tools, self-correct, and take autonomous actions. A unified PHP framework for Large Language Models with support for Anthropic Claude, OpenAI GPT, Google Gemini, and more.

> **What is Agentic AI?** Agents that can call functions, validate outputs, iterate on responses, and make decisions autonomously - not just generate text.
Expand All @@ -24,7 +27,8 @@ composer require soukicz/llm
- 📝 **Built-in Tools** - TextEditorTool for file manipulation, embeddings API, and more
- ✅ **Self-Correcting** - Validate and refine outputs with feedback loops
- 📸 **Multimodal** - Process images and PDFs alongside text (with caching support)
- 🧠 **Reasoning Models** - Advanced thinking with o3 and o4-mini reasoning models
- 🧠 **Reasoning Models** - OpenAI reasoning models, Anthropic extended thinking, and Gemini thinking
- 📐 **Structured Output** - JSON Schema enforced responses across Anthropic, OpenAI, and Gemini
- 📡 **Streaming** - Real-time response streaming with optional listener for live progress updates
- ⚡ **Async & Caching** - Fast, cost-effective operations with prompt caching
- 💾 **State Persistence** - Save and resume conversations with thread IDs
Expand All @@ -44,13 +48,18 @@ All LLM clients in this library are **asynchronous by default** using Guzzle Pro
- **Agent Client** (`LLMAgentClient`) - High-level orchestrator that handles multi-turn conversations, automatic tool calling, feedback loops, and retries. Use this for building agents that need to iterate or use tools.

### Model Versions
Anthropic and OpenAI models require explicit version constants:
Many Anthropic and OpenAI models pin an explicit version constant:
```php
<?php
new AnthropicClaude45Haiku(AnthropicClaude45Haiku::VERSION_20251001)
new GPT54(GPT54::VERSION_2026_03_05)
```
The newest Anthropic models (e.g. Claude 4.6) and Google Gemini models do NOT require versions - just instantiate them directly:
```php
<?php
new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929)
new GPTo3(GPTo3::VERSION_2025_04_16)
new AnthropicClaude46Sonnet()
new Gemini25Flash()
```
Google Gemini models do NOT require versions - just instantiate them directly.

### Conversations & State
`LLMConversation` manages the message history and can be serialized/deserialized for persistence. Each conversation has an optional `threadId` (UUID) for tracking across sessions.
Expand All @@ -63,7 +72,7 @@ require_once __DIR__ . '/vendor/autoload.php';

use Soukicz\Llm\Cache\FileCache;
use Soukicz\Llm\Client\Anthropic\AnthropicClient;
use Soukicz\Llm\Client\Anthropic\Model\AnthropicClaude45Sonnet;
use Soukicz\Llm\Client\Anthropic\Model\AnthropicClaude46Sonnet;
use Soukicz\Llm\Client\LLMAgentClient;
use Soukicz\Llm\Message\LLMMessage;
use Soukicz\Llm\LLMConversation;
Expand All @@ -82,7 +91,7 @@ $agentClient = new LLMAgentClient();
$response = $agentClient->run(
client: $client,
request: new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
model: new AnthropicClaude46Sonnet(),
conversation: new LLMConversation([
LLMMessage::createFromUserString('What is PHP?')
]),
Expand Down Expand Up @@ -119,7 +128,7 @@ $client = new AnthropicClient(
apiKey: 'sk-ant-xxxxx',
cache: $cache,
customHttpMiddleware: null,
betaFeatures: [] // e.g., ['text-editor-20250116'] for TextEditorTool
betaFeatures: [] // Optional Anthropic beta feature flags
);

// OpenAI (organization parameter is required)
Expand Down Expand Up @@ -254,49 +263,79 @@ Use advanced reasoning for complex problems:
```php
use Soukicz\Llm\Config\ReasoningEffort;
use Soukicz\Llm\Config\ReasoningBudget;
use Soukicz\Llm\Client\Anthropic\Model\AnthropicClaude45Sonnet;
use Soukicz\Llm\Client\OpenAI\Model\GPT5;
use Soukicz\Llm\Client\Anthropic\Model\AnthropicClaude46Sonnet;
use Soukicz\Llm\Client\OpenAI\Model\GPT54;

// Control reasoning with effort level (for supported models)
// Control reasoning with effort level (OpenAI, Anthropic, and Gemini)
$request = new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
model: new GPT54(GPT54::VERSION_2026_03_05),
conversation: $conversation,
reasoningConfig: ReasoningEffort::HIGH // LOW, MEDIUM, or HIGH
reasoningConfig: ReasoningEffort::HIGH // NONE, MINIMAL, LOW, MEDIUM, HIGH, or EXTRA_HIGH
);

// Or use token-based budget control (for supported models)
// Or use token-based budget control (Anthropic only)
$request = new LLMRequest(
model: new GPT5(GPT5::VERSION_2025_08_07),
model: new AnthropicClaude46Sonnet(),
conversation: $conversation,
reasoningConfig: new ReasoningBudget(10000) // Max reasoning tokens
);
```

**→ [Reasoning Models Documentation](docs/guides/reasoning.md)**

### 📐 Structured Output

Force responses to match a JSON Schema and get them back as a PHP array - supported by Anthropic, OpenAI, and Gemini:

```php
use Soukicz\Llm\Config\StructuredOutputConfig;

$response = $agentClient->run($client, new LLMRequest(
model: new AnthropicClaude46Sonnet(),
conversation: new LLMConversation([
LLMMessage::createFromUserString('Extract user data: John Doe, age 30, email john@example.com')
]),
structuredOutputConfig: new StructuredOutputConfig([
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'age' => ['type' => 'integer'],
'email' => ['type' => 'string'],
],
'required' => ['name', 'age', 'email'],
'additionalProperties' => false,
]),
));

$data = $response->getLastStructuredData(); // ['name' => 'John Doe', 'age' => 30, 'email' => 'john@example.com']
```

> **Tip:** Strict schema validation is enabled by default - pass `strict: false` to relax it.

**→ [Structured Output Documentation](docs/guides/structured-output.md)**

## Advanced Features

### 📝 TextEditorTool - Built-in File Manipulation

Empower agents to read, write, and manage files with the built-in TextEditorTool:

```php
use Soukicz\Llm\Tool\TextEditorTool;
use Soukicz\Llm\Tool\TextEditorStorageFilesystem;
use Soukicz\Llm\Tool\TextEditor\TextEditorTool;
use Soukicz\Llm\Tool\TextEditor\TextEditorStorageFilesystem;

// Create filesystem storage with sandboxing
$storage = new TextEditorStorageFilesystem('/safe/workspace/path');
$textEditorTool = new TextEditorTool($storage);

// Enable for Anthropic Claude with beta features
// Works out of the box with Anthropic Claude - no beta flags needed on modern models
$client = new AnthropicClient(
apiKey: 'sk-ant-xxxxx',
cache: $cache,
betaFeatures: ['text-editor-20250116'] // Required for TextEditorTool
cache: $cache
);

$response = $agentClient->run($client, new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
model: new AnthropicClaude46Sonnet(),
conversation: new LLMConversation([
LLMMessage::createFromUserString('Create a PHP file with a hello world function')
]),
Expand Down Expand Up @@ -333,11 +372,17 @@ Built-in interfaces for logging and monitoring:

```php
use Soukicz\Llm\Log\LLMLogger;
use Soukicz\Llm\LLMRequest;
use Soukicz\Llm\LLMResponse;

// Implement custom logger
class MyLogger implements LLMLogger {
public function log(LLMRequest $request, LLMResponse $response): void {
// Log requests, responses, costs, tokens, etc.
public function requestStarted(LLMRequest $request): void {
echo "Request started\n";
}

public function requestFinished(LLMResponse $response): void {
// Log responses, costs, tokens, etc.
$cost = ($response->getInputPriceUsd() ?? 0) + ($response->getOutputPriceUsd() ?? 0);
echo "Cost: $" . $cost . "\n";
echo "Tokens: {$response->getInputTokens()} in, {$response->getOutputTokens()} out\n";
Expand Down Expand Up @@ -365,10 +410,10 @@ $request = new LLMRequest(
// Custom stop sequences to halt generation
stopSequences: ['END', '---'],

// Reasoning configuration (for o3/o4-mini models)
// Reasoning configuration (OpenAI reasoning models, Anthropic extended thinking, Gemini thinking)
reasoningConfig: ReasoningEffort::HIGH,
// OR
reasoningConfig: new ReasoningBudget(10000),
// OR token-based budget (Anthropic only):
// reasoningConfig: new ReasoningBudget(10000),

// Optional: Stream responses for real-time progress
// streamListener: new CallableStreamListener(fn($e) => print($e->delta)),
Expand All @@ -380,14 +425,14 @@ $cost = ($response->getInputPriceUsd() ?? 0) + ($response->getOutputPriceUsd() ?
echo "Cost: $" . $cost . "\n";
echo "Input tokens: " . $response->getInputTokens() . "\n";
echo "Output tokens: " . $response->getOutputTokens() . "\n";
echo "Stop reason: " . $response->getStopReason()->value . "\n"; // END_TURN, TOOL_USE, MAX_TOKENS, STOP_SEQUENCE
echo "Stop reason: " . $response->getStopReason()->value . "\n"; // FINISHED, TOOL_USE, LENGTH, SAFETY
```

## Supported Providers

- **Anthropic (Claude)** - Claude 3.5, 3.7, 4.0, 4.1, and 4.5 series models
- **OpenAI (GPT)** - GPT-4o, GPT-4.1, o3 and o4-mini (reasoning), and GPT-5 series models
- **Google Gemini** - Gemini 2.0 and 2.5 series models
- **Anthropic (Claude)** - Claude 3.5 through 4.6 series models
- **OpenAI (GPT)** - GPT-4o, GPT-4.1, o3 and o4-mini (reasoning), and GPT-5 through GPT-5.4 series models
- **Google Gemini** - Gemini 2.0 through 3.x series models
- **OpenAI-Compatible** - OpenRouter, local servers (Ollama, llama-server), and more
- **AWS Bedrock** - Via separate package ([`soukicz/llm-aws-bedrock`](https://github.com/soukicz/llm-aws-bedrock))

Expand All @@ -406,7 +451,8 @@ echo "Stop reason: " . $response->getStopReason()->value . "\n"; // END_TURN, TO
- [Feedback Loops](docs/guides/feedback-loops.md) - Self-correcting agents and validation
- [Multimodal Support](docs/guides/multimodal.md) - Images, PDFs, and caching
- [Streaming](docs/guides/streaming.md) - Real-time response streaming with progress listeners
- [Reasoning Models](docs/guides/reasoning.md) - o3/o4-mini with effort and budget control
- [Reasoning Models](docs/guides/reasoning.md) - Reasoning and extended thinking with effort and budget control
- [Structured Output](docs/guides/structured-output.md) - JSON Schema enforced responses

### Advanced Features
- [Caching](docs/guides/caching.md) - Prompt caching and cost reduction
Expand Down Expand Up @@ -465,6 +511,8 @@ $response = $agentClient->run($client, new LLMRequest(
### Self-Correcting JSON Parser
```php
// Agent that validates and corrects its own output
$iterations = 0;

$response = $agentClient->run(
client: $client,
request: new LLMRequest(
Expand All @@ -473,7 +521,11 @@ $response = $agentClient->run(
LLMMessage::createFromUserString('Extract user data as JSON: John Doe, age 30, email john@example.com')
])
),
feedbackCallback: function ($response) {
feedbackCallback: function ($response) use (&$iterations) {
if (++$iterations >= 3) {
return null; // Limit retry attempts
}

$text = $response->getLastText();
json_decode($text);

Expand All @@ -484,8 +536,7 @@ $response = $agentClient->run(
}

return null; // Valid JSON, stop iteration
},
maxIterations: 3 // Limit retry attempts
}
);
```

Expand All @@ -498,7 +549,7 @@ $chartData = base64_encode(file_get_contents('/sales-chart.png'));
$reportData = base64_encode(file_get_contents('/quarterly-report.pdf'));

$response = $agentClient->run($client, new LLMRequest(
model: new AnthropicClaude45Sonnet(AnthropicClaude45Sonnet::VERSION_20250929),
model: new AnthropicClaude46Sonnet(),
conversation: new LLMConversation([
LLMMessage::createFromUser(new LLMMessageContents([
new LLMMessageText('Analyze these documents and summarize the key insights'),
Expand Down Expand Up @@ -582,7 +633,7 @@ This project is open-sourced software licensed under the BSD-3-Clause license.

## Links

- [Documentation](docs/) - Full documentation
- [Documentation](https://soukicz.github.io/php-llm/) - Full documentation
- [GitHub](https://github.com/soukicz/llm) - Source code
- [Packagist](https://packagist.org/packages/soukicz/llm) - Composer package

Expand Down
4 changes: 3 additions & 1 deletion docs/examples/best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ For example, if an agent needs to fetch data from three different sources, runni

```php
<?php
use GuzzleHttp\Promise\Utils;

// Process multiple requests concurrently
$promises = [];

Expand All @@ -151,7 +153,7 @@ foreach ($items as $item) {
}

// Wait for all to complete
$responses = Promise\Utils::all($promises)->wait();
$responses = Utils::all($promises)->wait();

// Process results
foreach ($responses as $response) {
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Practical, copy-paste ready examples to help you get started with PHP LLM and bu

## Getting Started

- **[Quick Start](quick-start.md)** - Get up and running in minutes with basic examples for simple synchronous requests, conversation management, and streaming responses.
- **[Quick Start](quick-start.md)** - Get up and running in minutes with basic examples for simple synchronous requests, async requests, and conversation management.

## Core Functionality

Expand All @@ -24,7 +24,7 @@ Practical, copy-paste ready examples to help you get started with PHP LLM and bu

These examples cover:
- **Basic usage**: Simple requests, conversations
- **Advanced features**: Tools, multimodal, [streaming](../guides/streaming.md), caching, reasoning models
- **Advanced features**: Tools, multimodal, caching, reasoning models (see also the [streaming guide](../guides/streaming.md))
- **Production patterns**: Error handling, logging, retries, resilience
- **Best practices**: Security, performance, cost optimization
- **Real-world scenarios**: Practical code you can adapt to your needs
Expand Down
Loading
Loading