mhrlife / phpai-kit
PHP library for OpenAI communication with advanced tool function support
Installs: 25
Dependents: 0
Suggesters: 0
Security: 0
Stars: 6
Watchers: 0
Forks: 2
Open Issues: 0
pkg:composer/mhrlife/phpai-kit
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.10
- open-telemetry/api: ^1.7
- open-telemetry/exporter-otlp: ^1.3
- open-telemetry/sdk: ^1.9
- openai-php/client: ^0.18.0
- php-http/guzzle7-adapter: ^1.1
- predis/predis: ^3.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.90
- phpstan/phpstan: ^2.1
This package is auto-updated.
Last update: 2025-12-01 12:16:24 UTC
README
A powerful PHP library for OpenAI communication with advanced tool function support, automatic JSON Schema generation, and structured output parsing.
Features
- Tool Functions with Attributes: Decorate functions with
#[Tool]to make them AI-callable - Automatic JSON Schema: Automatically converts PHP classes to OpenAI-compatible JSON Schema
- Structured Output: Type-safe output models using OpenAI's
response_formatwith JSON Schema - Agent Execution Loop: Handles tool calls automatically until task completion
- PHPDoc Support: Enhanced type inference from PHPDoc annotations (
@var array<string>, etc.) - Type Safety: Full PHP 8.1+ type support with PHPStan level 5
- Works with OpenRouter: Compatible with OpenAI API and OpenRouter
Requirements
- PHP 8.1 or higher
- OpenAI API key
Installation
composer require mhrlife/phpai-kit
Quick Start
1. Define Your Models
use Mhrlife\PhpaiKit\Attributes\Tool; // Input parameter class class WeatherParams { public string $location; public ?string $unit = 'celsius'; } // Output model class class WeatherReport { public string $location; public float $temperature; public string $description; }
2. Create Tool Functions
#[Tool("get_weather", "Get current weather for a location")] function getWeather(WeatherParams $params): array { // Your implementation return [ 'location' => $params->location, 'temperature' => 22.5, 'description' => 'Sunny', ]; }
3. Create and Run Agent
use OpenAI; $openai = OpenAI::factory() ->withApiKey($apiKey) ->make(); $agent = \Mhrlife\PhpaiKit\create_agent( $openai, tools: [getWeather(...)], output: WeatherReport::class ); $report = $agent->run("What's the weather in Paris?"); echo "Temperature: {$report->temperature}°C\n";
Core Concepts
Tool Functions
Tool functions are regular PHP functions decorated with the #[Tool] attribute:
#[Tool("function_name", "Description for AI")] function myTool(InputClass $params): mixed { // Function must accept exactly one parameter (a class) // Return value can be any serializable type }
Requirements:
- Must have exactly one parameter
- Parameter must be a class (not scalar type)
- Must have
#[Tool]attribute with name and description
Automatic Schema Generation
The library automatically converts your PHP classes to JSON Schema:
class SearchParams { public string $query; // Required string public ?int $limit = 10; // Optional int with default /** @var array<string> */ public array $filters = []; // Array type from PHPDoc }
Supported Types:
- Scalar types:
string,int,float,bool - Arrays with PHPDoc:
array<Type>,Type[] - Nested objects: Any class type
- Enums: String and integer-backed enums
- Union types:
string|int - Nullable types:
?string,string|null
Enum Support:
The library automatically extracts enum values for OpenAI schema constraints:
enum Status: string { case PENDING = 'pending'; case ACTIVE = 'active'; case INACTIVE = 'inactive'; } class TaskParams { public string $title; public Status $status; // Enum values are extracted as constraints }
Generates schema with enum constraint:
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["pending", "active", "inactive"]
}
}
}
Structured Output
Define output models to get type-safe responses:
class AnalysisResult { public string $summary; public int $score; public array $tags; } $agent = create_agent( $openai, tools: $tools, output: AnalysisResult::class ); $result = $agent->run("Analyze this text..."); // $result is instance of AnalysisResult
Advanced Usage
Using AgentBuilder
For more control, use the fluent builder API:
use Mhrlife\PhpaiKit\Agent\AgentBuilder; $agent = (new AgentBuilder($openai)) ->withModel('gpt-4o-mini') ->withTools([tool1(...), tool2(...)]) ->withOutput(OutputClass::class) ->build();
Observability with Callbacks
Add callbacks for tracing, logging, and monitoring. Important: Callbacks must be passed to agent->run(), not at agent creation time.
use function Mhrlife\PhpaiKit\Callbacks\create_langfuse_callback; // Create agent WITHOUT callbacks $agent = create_agent( $openai, tools: [getWeather(...)] ); // Pass callbacks at runtime for proper trace hierarchy $result = $agent->run( "What's the weather in Paris?", callbacks: [create_langfuse_callback()] );
Why pass callbacks to run()?
This ensures all LLM calls and tool executions are properly nested within a single trace. Each agent->run() creates one trace with nested spans for generations and tool calls.
Built-in Callbacks:
Langfuse Tracing - Complete observability with OpenTelemetry:
use function Mhrlife\PhpaiKit\Callbacks\{ initialize_langfuse, create_langfuse_callback }; // Optional: Initialize once with credentials initialize_langfuse( publicKey: 'pk-...', secretKey: 'sk-...' ); // Or use environment variables: // LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY // Pass callback to each run $agent->run($message, callbacks: [create_langfuse_callback()]);
Custom Callbacks:
Implement the AgentCallback interface:
use Mhrlife\PhpaiKit\Callbacks\AgentCallback; class MyCallback implements AgentCallback { public function onRunStart(array $context): void { // Called when agent starts // $context: ['model' => string, 'input' => mixed, 'has_output_class' => bool] } public function onRunEnd(array $context): void { // Called when agent completes // $context: ['output' => mixed, 'total_iterations' => int] } public function onGenerationStart(array $context): void { // Called before each LLM call // $context: ['iteration' => int, 'messages' => array, 'model' => string] } public function onGenerationEnd(array $context): void { // Called after each LLM call // $context: ['finish_reason' => string, 'content' => string, 'tool_calls' => array, 'usage' => object] } public function onToolCallStart(array $context): void { // Called before tool execution // $context: ['tool_name' => string, 'arguments' => array, 'tool_call_id' => string] } public function onToolCallEnd(array $context): void { // Called after tool execution // $context: ['tool_name' => string, 'arguments' => array, 'result' => mixed, 'tool_call_id' => string] } public function onError(array $context): void { // Called on errors // $context: ['error' => string, 'exception' => Throwable] } } // Use your custom callback $agent->run($message, callbacks: [new MyCallback()]);
Multiple Tools
Register multiple tools to give your agent more capabilities:
#[Tool("search_web", "Search the web")] function searchWeb(SearchParams $params): array { /* ... */ } #[Tool("calculate", "Perform calculations")] function calculate(CalcParams $params): array { /* ... */ } $agent = create_agent( $openai, tools: [searchWeb(...), calculate(...)], output: ResultClass::class );
Complex Types with PHPDoc
Use PHPDoc for advanced type definitions:
class AdvancedParams { /** * List of user IDs to process * @var array<int> */ public array $userIds; /** * Optional configuration map * @var array<string, string>|null */ public ?array $config = null; }
Error Handling
The library throws specific exceptions for different error types:
use Mhrlife\PhpaiKit\Exceptions\{AgentException, ToolException, SchemaException}; try { $result = $agent->run("Your prompt"); } catch (ToolException $e) { // Tool execution failed } catch (SchemaException $e) { // Schema generation failed } catch (AgentException $e) { // Agent runtime error }
Vector Database with Filtering
Store and search documents using vector embeddings with Redis:
use Mhrlife\PhpaiKit\VectorDB\{ RedisVectorDB, Document, DocumentSearch, IndexConfig, Filter, FilterOp, FilterableField, FilterFieldType, NumericRange }; // Create vector DB with filterable fields $vectorDB = new RedisVectorDB('my_index', $embeddingClient, $redis); $vectorDB->createIndex(new IndexConfig( dimensions: 1536, distanceMetric: 'COSINE', filterableFields: [ new FilterableField('category', FilterFieldType::Tag), new FilterableField('year', FilterFieldType::Numeric), ] )); // Store documents with metadata $vectorDB->storeDocumentsBatch([ new Document('go', 'Go is a fast compiled language', ['category' => 'backend', 'year' => 2009]), new Document('php', 'PHP is a flexible language', ['category' => 'backend', 'year' => 1995]), ]); // Search with filters $results = $vectorDB->searchDocuments(new DocumentSearch( query: 'fast programming language', topK: 3, filters: [ new Filter('category', FilterOp::Eq, 'backend'), ] )); // Range filter example $results = $vectorDB->searchDocuments(new DocumentSearch( query: 'programming language', topK: 3, filters: [ new Filter('year', FilterOp::Range, new NumericRange(2000, 2010)), ] ));
Filter Operators:
FilterOp::Eq- Exact tag matchFilterOp::In- Match any in list (array value)FilterOp::Contains- Text containsFilterOp::Range- Numeric range (useNumericRange)FilterOp::Gte- Greater than or equalFilterOp::Lte- Less than or equal
Field Types:
FilterFieldType::Tag- Exact match (categories, tags)FilterFieldType::Text- Full-text searchFilterFieldType::Numeric- Numeric range queries
Examples
See the examples/ directory for complete working examples:
examples/test_without_api.php- Test all features without API calls (recommended first)examples/weather_example.php- Full weather tool example with OpenAI APIexamples/langfuse_tracing_example.php- Complete example with Langfuse tracingexamples/vector_search_example.php- Vector search with filteringexamples/image_input.php- Image analysis with vision capabilities
Run the non-API example to see all features in action:
php examples/test_without_api.php
For Langfuse tracing:
export LANGFUSE_PUBLIC_KEY="pk-..." export LANGFUSE_SECRET_KEY="sk-..." php examples/langfuse_tracing_example.php
Image Analysis with Vision
The agent supports vision capabilities for image analysis. You can pass images to the agent in multiple ways:
Example 1: Base64 Encoded Image
$agent = create_agent($openai); $base64Image = "data:image/png;base64,iVBORw0KGgoAAAANS..."; $response = $agent->run([ ['role' => 'user', 'content' => [ [ 'type' => 'text', 'text' => 'Describe this image in detail.' ], [ 'type' => 'image_url', 'image_url' => ['url' => $base64Image] ] ]], ]); echo $response;
Example 2: Image from URL
$response = $agent->run([ ['role' => 'user', 'content' => [ [ 'type' => 'text', 'text' => 'What is in this image?' ], [ 'type' => 'image_url', 'image_url' => [ 'url' => 'https://example.com/image.png' ] ] ]], ]); echo $response;
Example 3: Local File to Base64
// Load local image and convert to base64 $imagePath = '/path/to/image.png'; $imageData = file_get_contents($imagePath); $base64Image = 'data:image/png;base64,' . base64_encode($imageData); $response = $agent->run([ ['role' => 'user', 'content' => [ [ 'type' => 'text', 'text' => 'Analyze this image.' ], [ 'type' => 'image_url', 'image_url' => ['url' => $base64Image] ] ]], ]); echo $response;
Supported Image Formats:
- PNG
- JPG/JPEG
- GIF
- WebP
See examples/image_input.php for complete working examples with all three approaches.
Architecture
The library is organized into clear components:
- Attributes:
#[Tool]decorator for marking functions - Schema: JSON Schema generation from PHP classes
- Tools: Tool registration and execution
- Agent: Main agent with execution loop
- Output: Structured output parsing
Development
Run Tests
composer test
The test suite includes comprehensive unit tests for:
- TypeMapper: PHP to JSON Schema type conversion
- SchemaGenerator: Automatic schema generation from classes
- ToolRegistry: Tool registration and management
- ToolDefinition: OpenAI format conversion
- ToolExecutor: Tool execution and parameter handling
- OutputParser: Structured output parsing
All 117 tests pass successfully.
Run Static Analysis
composer lint
Format Code
composer format
Check Formatting
composer format:check
How It Works
- Registration: Tools are registered with their metadata extracted via reflection
- Schema Generation: Parameter classes are converted to JSON Schema automatically
- Execution Loop:
- Agent sends request with tool definitions
- OpenAI responds with tool calls
- Library executes tools and sends results back
- Loop continues until completion
- Output Parsing: Final response is parsed into typed output model
License
MIT
Author
Mohammad Hoseinirad
Contributing
Contributions are welcome! Please ensure code passes PHPStan level 5 and follows PSR-12 formatting.