agentphp/sdk
Composer 安装命令:
composer require agentphp/sdk
包简介
A comprehensive PHP SDK for building AI conversational agents with support for multiple LLM providers (OpenAI, Anthropic, Google), tools, streaming, and human-in-the-loop workflows
关键字:
README 文档
README
A comprehensive PHP SDK for building AI conversational agents with support for multiple LLM providers (OpenAI, Anthropic, Google), tools, streaming, and human-in-the-loop workflows.
Features
- Multiple LLM Providers: OpenAI (GPT-4), Anthropic (Claude), Google (Gemini)
- Tool System: Define and execute custom tools with automatic validation
- Streaming: Real-time token streaming for responsive UIs
- Middleware Pipeline: Logging, retry, rate limiting, caching
- Memory Management: Conversation and sliding window memory
- Human-in-the-Loop: Approval workflows for sensitive operations
- Framework Integration: Laravel and Symfony support
Requirements
- PHP 8.0 or higher
- Composer
- Guzzle HTTP client
Installation
composer require agentphp/sdk
Quick Start
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt('You are a helpful assistant.') ->build(); $response = $agent->chat('Hello, how are you?'); echo $response->getContent();
Table of Contents
- Standalone Usage (Legacy PHP)
- Laravel Integration
- Symfony Integration
- Providers
- System Prompts
- Tools
- Streaming
- Middleware
- Human-in-the-Loop
- Error Handling
Standalone Usage (Legacy PHP)
Basic Chat
<?php require 'vendor/autoload.php'; use AgentPHP\Core\AgentBuilder; // Create an agent with OpenAI $agent = AgentBuilder::create() ->withOpenAI('your-openai-api-key', 'gpt-4') ->withSystemPrompt('You are a helpful assistant.') ->withName('MyAgent') ->build(); // Simple chat $response = $agent->chat('What is the capital of France?'); echo $response->getContent(); // "The capital of France is Paris." // Get usage information $usage = $response->getUsage(); echo "Tokens used: " . $usage->getTotalTokens();
Conversation Memory
use AgentPHP\Core\AgentBuilder; // With conversation memory (default) $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withConversationMemory() ->build(); // The agent remembers previous messages $agent->chat('My name is John'); $response = $agent->chat('What is my name?'); echo $response->getContent(); // "Your name is John." // With sliding window memory (limits context size) $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSlidingWindowMemory(4000) // Max 4000 tokens ->build();
Configuration Options
use AgentPHP\Core\AgentBuilder; use AgentPHP\Core\AgentConfig; // Using AgentBuilder (recommended) $agent = AgentBuilder::create() ->withOpenAI('your-api-key', 'gpt-4') ->withName('MyAssistant') ->withSystemPrompt('You are a coding expert.') ->withTemperature(0.7) ->withMaxTokens(2000) ->withMaxIterations(10) ->withTimeout(30) ->withStreaming(false) ->build(); // Using AgentConfig directly $config = AgentConfig::fromArray([ 'name' => 'MyAssistant', 'system_prompt' => 'You are a coding expert.', 'temperature' => 0.7, 'max_tokens' => 2000, 'max_iterations' => 10, 'timeout' => 30, 'streaming' => false, ]); $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withConfig($config) ->build();
Complete Standalone Example
<?php require 'vendor/autoload.php'; use AgentPHP\Core\AgentBuilder; use AgentPHP\Tools\AbstractTool; use AgentPHP\Tools\ToolSchemaBuilder; // Define a custom tool class WeatherTool extends AbstractTool { protected string $name = 'get_weather'; protected string $description = 'Get the current weather for a location'; public function getParameters(): array { return ToolSchemaBuilder::create() ->addString('location', 'City name', required: true) ->build(); } protected function handle(array $arguments) { $location = $arguments['location']; // Simulate weather API return [ 'temperature' => 22, 'condition' => 'sunny' ]; } } // Create agent with tools and middleware $agent = AgentBuilder::create() ->withOpenAI(getenv('OPENAI_API_KEY'), 'gpt-4') ->withSystemPrompt('You are a helpful assistant with weather capabilities.') ->withTool(new WeatherTool()) ->withRetry(3, 1000, 2.0) ->withConversationMemory() ->build(); // Chat $response = $agent->chat('What is the weather in Paris?'); echo $response->getContent();
Laravel Integration
Installation
composer require agentphp/sdk
The service provider is auto-discovered. For manual registration:
// config/app.php 'providers' => [ AgentPHP\Framework\Laravel\AgentServiceProvider::class, ], 'aliases' => [ 'Agent' => AgentPHP\Framework\Laravel\Facades\Agent::class, ],
Publish Configuration
php artisan vendor:publish --tag=agent-config
Configuration
// config/agent.php return [ 'default' => env('AGENT_PROVIDER', 'openai'), 'name' => env('AGENT_NAME', 'AgentPHP'), 'system_prompt' => env('AGENT_SYSTEM_PROMPT', 'You are a helpful assistant.'), 'providers' => [ 'openai' => [ 'api_key' => env('OPENAI_API_KEY'), 'model' => env('OPENAI_MODEL', 'gpt-4'), 'timeout' => 30, ], 'anthropic' => [ 'api_key' => env('ANTHROPIC_API_KEY'), 'model' => env('ANTHROPIC_MODEL', 'claude-3-sonnet-20240229'), ], 'google' => [ 'api_key' => env('GOOGLE_API_KEY'), 'model' => env('GOOGLE_MODEL', 'gemini-pro'), ], ], 'memory' => [ 'driver' => 'conversation', // or 'sliding_window' 'max_tokens' => 4000, ], 'middleware' => [ 'logging' => ['enabled' => true, 'level' => 'debug'], 'retry' => ['enabled' => true, 'max_retries' => 3], 'rate_limit' => ['enabled' => false, 'max_requests' => 60], ], 'human_loop' => [ 'enabled' => false, 'timeout' => 300, ], 'tools' => [ // \App\Tools\SearchTool::class, ], ];
Environment Variables
AGENT_PROVIDER=openai AGENT_NAME=MyAgent OPENAI_API_KEY=your-openai-api-key OPENAI_MODEL=gpt-4
Using the Facade
use AgentPHP\Framework\Laravel\Facades\Agent; // Simple chat $response = Agent::chat('Hello!'); echo $response->getContent(); // With streaming $stream = Agent::chatStream('Tell me a story'); foreach ($stream->getChunks() as $chunk) { echo $chunk->getContent(); } // Get conversation history $history = Agent::getHistory(); // Reset conversation Agent::reset();
Using Dependency Injection
use AgentPHP\Core\Agent; class ChatController extends Controller { public function __construct( private Agent $agent ) {} public function chat(Request $request) { $response = $this->agent->chat($request->input('message')); return response()->json([ 'message' => $response->getContent(), 'usage' => $response->getUsage()->toArray(), ]); } }
Using AgentBuilder in Laravel
use AgentPHP\Core\AgentBuilder; class CustomAgentController extends Controller { public function chat(Request $request) { // Create a custom agent instance $agent = AgentBuilder::create() ->withOpenAI(config('agent.providers.openai.api_key')) ->withSystemPrompt('You are a customer service agent.') ->withTemperature(0.5) ->withTool(new SearchTool()) ->withLogging(app('log')) ->build(); return response()->json([ 'response' => $agent->chat($request->input('message'))->getContent(), ]); } }
Registering Tools in Laravel
You can register tools directly on the Agent instance:
use AgentPHP\Core\Agent; class ChatController extends Controller { public function __construct( private Agent $agent ) { // Register tools directly on the agent $this->agent->addTools([ new \App\Tools\WeatherTool(), new \App\Tools\CalculatorTool(), new \App\Tools\SearchTool(), ]); } public function chat(Request $request) { $response = $this->agent->chat($request->input('message')); return response()->json(['message' => $response->getContent()]); } }
Or register a single tool:
$this->agent->addTool(new WeatherTool());
Alternatively, you can register tools globally in the ServiceProvider (less recommended):
// In AppServiceProvider use AgentPHP\Tools\ToolRegistry; public function boot(ToolRegistry $registry) { $registry->register(new \App\Tools\SearchTool()); }
Laravel Streaming Response
use AgentPHP\Core\Agent; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\StreamedResponse; public function stream(Request $request): StreamedResponse { $message = $request->input('message'); return new StreamedResponse(function () use ($message) { $agent = app(Agent::class); $stream = $agent->chatStream($message); foreach ($stream->getChunks() as $chunk) { if ($chunk->hasContent()) { echo "data: " . json_encode(['content' => $chunk->getContent()]) . "\n\n"; ob_flush(); flush(); } if ($chunk->isDone()) { break; } } echo "data: [DONE]\n\n"; ob_flush(); flush(); }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', ]); }
Symfony Integration
Installation
composer require agentphp/sdk
Enable the Bundle
// config/bundles.php return [ // ... AgentPHP\Framework\Symfony\AgentBundle::class => ['all' => true], ];
Configuration
# config/packages/agent.yaml agent: provider: openai name: MyAgent system_prompt: 'You are a helpful assistant.' providers: openai: api_key: '%env(OPENAI_API_KEY)%' model: gpt-4 timeout: 30 anthropic: api_key: '%env(ANTHROPIC_API_KEY)%' model: claude-3-sonnet-20240229 google: api_key: '%env(GOOGLE_API_KEY)%' model: gemini-pro memory: driver: conversation # or sliding_window max_tokens: 4000 max_iterations: 10 temperature: 0.7 max_tokens: ~ # null for provider default timeout: 30 streaming: false human_loop: enabled: false timeout: 300 auto_approve: false middleware: logging: enabled: true level: debug retry: enabled: true max_retries: 3 delay: 1000 multiplier: 2.0 rate_limit: enabled: false max_requests: 60 window: 60 cache: enabled: false ttl: 3600 tools: []
Environment Variables
OPENAI_API_KEY=your-openai-api-key ANTHROPIC_API_KEY=your-anthropic-api-key
Using in Controllers
use AgentPHP\Core\Agent; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; class ChatController extends AbstractController { public function __construct( private Agent $agent ) {} public function chat(Request $request): JsonResponse { $message = $request->request->get('message'); $response = $this->agent->chat($message); return $this->json([ 'message' => $response->getContent(), 'usage' => $response->getUsage()->toArray(), ]); } }
Symfony Streaming Response
use AgentPHP\Core\Agent; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\StreamedResponse; public function stream(Request $request): StreamedResponse { $message = $request->request->get('message'); return new StreamedResponse(function () use ($message) { $stream = $this->agent->chatStream($message); foreach ($stream->getChunks() as $chunk) { if ($chunk->hasContent()) { echo "data: " . json_encode(['content' => $chunk->getContent()]) . "\n\n"; ob_flush(); flush(); } if ($chunk->isDone()) { break; } } echo "data: [DONE]\n\n"; ob_flush(); flush(); }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', ]); }
Using AgentBuilder in Symfony
use AgentPHP\Core\AgentBuilder; class CustomAgentService { public function createSpecializedAgent(): Agent { return AgentBuilder::create() ->withAnthropic($_ENV['ANTHROPIC_API_KEY'], 'claude-3-opus-20240229') ->withSystemPrompt('You are an expert programmer.') ->withTemperature(0.3) ->withMaxTokens(4000) ->build(); } }
Registering Tools as Services
# config/services.yaml services: App\Tools\SearchTool: tags: ['agent.tool'] App\Tools\CalculatorTool: tags: ['agent.tool']
Providers
OpenAI
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withOpenAI( apiKey: 'your-api-key', model: 'gpt-4', // or gpt-4-turbo, gpt-3.5-turbo baseUrl: null, // Custom base URL (optional) organization: null // Organization ID (optional) ) ->build(); // Or with ProviderConfig use AgentPHP\Providers\OpenAIProvider; use AgentPHP\Providers\ProviderConfig; $config = new ProviderConfig([ 'api_key' => 'your-api-key', 'model' => 'gpt-4-turbo', 'base_url' => 'https://api.openai.com/v1', 'organization' => 'org-xxx', 'timeout' => 30, ]); $provider = new OpenAIProvider($config); $agent = AgentBuilder::create() ->withCustomProvider($provider) ->build();
Anthropic (Claude)
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withAnthropic( apiKey: 'your-api-key', model: 'claude-3-opus-20240229' // or claude-3-sonnet, claude-3-haiku ) ->build(); // Available models: // - claude-3-opus-20240229 (most capable) // - claude-3-sonnet-20240229 (balanced) // - claude-3-haiku-20240307 (fastest)
Google (Gemini)
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withGoogle( apiKey: 'your-api-key', model: 'gemini-pro' // or gemini-pro-vision ) ->build();
Custom Provider
use AgentPHP\Contracts\ProviderInterface; use AgentPHP\Core\AgentBuilder; use AgentPHP\Core\ChatResponse; class MyCustomProvider implements ProviderInterface { public function getName(): string { return 'custom'; } public function chat(array $messages, array $options = []): ChatResponse { // Your implementation } public function chatStream(array $messages, callable $callback, array $options = []): ChatResponse { // Your streaming implementation } // ... other interface methods } $agent = AgentBuilder::create() ->withCustomProvider(new MyCustomProvider()) ->build();
Provider Factory
use AgentPHP\Providers\ProviderFactory; // Create provider by name $provider = ProviderFactory::create('openai', [ 'api_key' => 'your-key', 'model' => 'gpt-4', ]); $provider = ProviderFactory::create('anthropic', [ 'api_key' => 'your-key', 'model' => 'claude-3-sonnet-20240229', ]);
System Prompts
System prompts define the behavior and personality of your agent.
Basic System Prompt
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt('You are a helpful assistant that speaks formally.') ->build();
Detailed System Prompt
$systemPrompt = <<<PROMPT You are an expert customer service agent for TechCorp. Your responsibilities: - Answer questions about our products - Help troubleshoot technical issues - Process refund requests when appropriate Guidelines: - Always be polite and professional - If you don't know something, say so - Never share confidential information - Escalate to a human agent for complex issues Available products: - TechWidget Pro (\$99) - TechGadget Plus (\$149) - TechService Premium (monthly subscription) PROMPT; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt($systemPrompt) ->build();
Dynamic System Prompts
$userName = 'John'; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt("You are helping user: {$userName}") ->build(); // Or update at runtime $agent->setSystemPrompt("New context: User is a premium member.");
Multi-Language Support
$userLanguage = 'es'; $systemPrompt = match($userLanguage) { 'es' => 'Eres un asistente util. Responde siempre en espanol.', 'fr' => 'Vous etes un assistant utile. Repondez toujours en francais.', default => 'You are a helpful assistant. Always respond in English.', }; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt($systemPrompt) ->build();
Role-Based System Prompts
function getSystemPromptForRole(string $role): string { return match($role) { 'developer' => 'You are a senior software developer. Provide code examples and technical explanations.', 'teacher' => 'You are a patient teacher. Explain concepts simply and use analogies.', 'analyst' => 'You are a data analyst. Focus on facts, statistics, and data-driven insights.', default => 'You are a helpful assistant.', }; } $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt(getSystemPromptForRole('developer')) ->build();
Tools
Tools allow your agent to perform actions and access external data.
Creating a Tool
Extend AbstractTool and implement the handle() method:
use AgentPHP\Tools\AbstractTool; use AgentPHP\Tools\ToolSchemaBuilder; class WeatherTool extends AbstractTool { protected string $name = 'get_weather'; protected string $description = 'Get the current weather for a location'; public function getParameters(): array { return ToolSchemaBuilder::create() ->addString('location', 'The city and country', required: true) ->addEnum('unit', 'Temperature unit', ['celsius', 'fahrenheit'], required: false) ->build(); } /** * Handle the tool execution. * * @param array $arguments The validated arguments from the LLM * @return mixed The result (will be wrapped in ToolResult automatically) */ protected function handle(array $arguments) { $location = $arguments['location']; $unit = $arguments['unit'] ?? 'celsius'; // Call weather API $weather = $this->fetchWeather($location, $unit); return [ 'temperature' => $weather['temp'], 'condition' => $weather['condition'], 'humidity' => $weather['humidity'], ]; } private function fetchWeather(string $location, string $unit): array { // Your API implementation return [ 'temp' => 22, 'condition' => 'sunny', 'humidity' => 45, ]; } }
The AbstractTool base class:
- Provides
execute(array $arguments): ToolResultwhich wraps yourhandle()result - Automatically converts your return value to a
ToolResult::success() - Catches exceptions and converts them to
ToolResult::failure() - Sets
requiresApproval()tofalseby default (override if needed)
Registering Tools
use AgentPHP\Core\AgentBuilder; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withTool(new WeatherTool()) ->withTool(new SearchTool()) ->withTool(new CalculatorTool()) ->build(); // Or register multiple at once $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withTools([ new WeatherTool(), new SearchTool(), new CalculatorTool(), ]) ->build();
Tool Schema Builder
use AgentPHP\Tools\ToolSchemaBuilder; $schema = ToolSchemaBuilder::create() // String with validation ->addString('email', 'User email address', required: true) // Enum (select from options) ->addEnum('priority', 'Task priority', ['low', 'medium', 'high']) // Integer with range ->addInteger('age', 'User age', required: true) // Number (float) ->addNumber('price', 'Product price') // Boolean ->addBoolean('active', 'Is active') // Array of strings ->addArray('tags', 'List of tags', itemType: 'string') // Nested object ->addObject('address', 'Shipping address', [ 'street' => ['type' => 'string', 'description' => 'Street address'], 'city' => ['type' => 'string', 'description' => 'City'], 'zip' => ['type' => 'string', 'description' => 'ZIP code'], ]) ->build();
Tool with Approval Required
For sensitive operations, set $requiresApproval = true:
class DeleteUserTool extends AbstractTool { protected string $name = 'delete_user'; protected string $description = 'Permanently delete a user account'; protected bool $requiresApproval = true; // Requires human approval public function getParameters(): array { return ToolSchemaBuilder::create() ->addString('user_id', 'The user ID to delete', required: true) ->addString('reason', 'Reason for deletion', required: true) ->build(); } protected function handle(array $arguments) { $userId = $arguments['user_id']; // Delete user logic $this->userService->delete($userId); return [ 'deleted' => true, 'user_id' => $userId, ]; } }
Using Tools with Agent
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withSystemPrompt('You can help users check the weather.') ->withTool(new WeatherTool()) ->build(); // The agent will automatically use the tool when appropriate $response = $agent->chat('What is the weather in Paris?'); echo $response->getContent(); // "The current weather in Paris is 22C and sunny with 45% humidity." // Check if tools were used if ($response->hasToolCalls()) { foreach ($response->getToolCalls() as $call) { echo "Tool used: " . $call->getName(); } }
Tool Registry
use AgentPHP\Tools\ToolRegistry; $registry = new ToolRegistry(); // Register tools $registry->register(new WeatherTool()); $registry->register(new SearchTool()); // Check if tool exists if ($registry->has('get_weather')) { $tool = $registry->get('get_weather'); } // Get all tool names $names = $registry->names(); // ['get_weather', 'search'] // Get tools requiring approval $sensitiveTools = $registry->getRequiringApproval(); // Filter tools $readOnlyTools = $registry->filter(fn($tool) => !$tool->requiresApproval());
Manual Tool Execution
use AgentPHP\Tools\ToolExecutor; $executor = new ToolExecutor($registry); // Execute a specific tool $result = $executor->executeByName('get_weather', [ 'location' => 'Tokyo', 'unit' => 'celsius', ]); if ($result->isSuccess()) { echo $result->getResultAsString(); } else { echo "Error: " . $result->getError(); }
Streaming
Streaming allows you to receive tokens in real-time as they are generated. All providers (OpenAI, Anthropic, Google) support streaming.
How Streaming Works
When you call $agent->chatStream(), it returns a StreamInterface object. This object provides methods to iterate over chunks of text as they arrive from the LLM provider, instead of waiting for the complete response.
Legacy PHP - Terminal/CLI Streaming
Complete example for running in terminal:
<?php // File: cli_stream.php require 'vendor/autoload.php'; use AgentPHP\Core\AgentBuilder; // Create agent $agent = AgentBuilder::create() ->withOpenAI(getenv('OPENAI_API_KEY'), 'gpt-4') ->withSystemPrompt('You are a helpful assistant.') ->build(); echo "Assistant: "; // Get the stream $stream = $agent->chatStream('Tell me a short story about a robot'); // Process each chunk as it arrives foreach ($stream->getChunks() as $chunk) { // Check if chunk has text content if ($chunk->hasContent()) { echo $chunk->getContent(); // Force output to display immediately if (function_exists('ob_flush')) { @ob_flush(); } flush(); } } echo "\n\n--- Stream complete ---\n";
Run it:
php cli_stream.php
Legacy PHP - Web Browser SSE Streaming
Complete example for streaming to a web browser using Server-Sent Events (SSE):
Backend (stream.php):
<?php // File: stream.php require 'vendor/autoload.php'; use AgentPHP\Core\AgentBuilder; // Disable output buffering if (ob_get_level()) { ob_end_clean(); } // Set SSE headers header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // Required for nginx // Get message from request $message = $_GET['message'] ?? $_POST['message'] ?? 'Hello'; // Create agent $agent = AgentBuilder::create() ->withOpenAI(getenv('OPENAI_API_KEY'), 'gpt-4') ->withSystemPrompt('You are a helpful assistant.') ->build(); try { $stream = $agent->chatStream($message); foreach ($stream->getChunks() as $chunk) { if ($chunk->hasContent()) { // Send SSE data echo "data: " . json_encode([ 'type' => 'content', 'content' => $chunk->getContent() ]) . "\n\n"; flush(); } // Check for errors if ($chunk->isError()) { echo "data: " . json_encode([ 'type' => 'error', 'message' => $chunk->getContent() ]) . "\n\n"; flush(); break; } } // Send completion signal echo "data: " . json_encode(['type' => 'done']) . "\n\n"; flush(); } catch (\Exception $e) { echo "data: " . json_encode([ 'type' => 'error', 'message' => $e->getMessage() ]) . "\n\n"; flush(); }
Frontend (index.html):
<!DOCTYPE html> <html> <head> <title>Chat Streaming</title> <style> #chat { max-width: 600px; margin: 20px auto; } #response { min-height: 200px; border: 1px solid #ccc; padding: 15px; white-space: pre-wrap; font-family: sans-serif; } #message { width: 80%; padding: 10px; } button { padding: 10px 20px; } </style> </head> <body> <div id="chat"> <h2>Chat with AI</h2> <input type="text" id="message" placeholder="Type your message..."> <button onclick="sendMessage()">Send</button> <h3>Response:</h3> <div id="response"></div> </div> <script> let eventSource = null; function sendMessage() { const message = document.getElementById('message').value; const responseDiv = document.getElementById('response'); // Clear previous response responseDiv.textContent = ''; // Close existing connection if (eventSource) { eventSource.close(); } // Create new SSE connection eventSource = new EventSource('stream.php?message=' + encodeURIComponent(message)); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.type === 'content') { responseDiv.textContent += data.content; } else if (data.type === 'done') { eventSource.close(); console.log('Stream complete'); } else if (data.type === 'error') { responseDiv.textContent += '\n\nError: ' + data.message; eventSource.close(); } }; eventSource.onerror = function() { eventSource.close(); console.log('Connection closed'); }; } </script> </body> </html>
Legacy PHP - AJAX POST Streaming
For POST requests with the Fetch API:
Backend (api_stream.php):
<?php // File: api_stream.php require 'vendor/autoload.php'; use AgentPHP\Core\AgentBuilder; // Handle CORS if needed header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers: Content-Type'); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit(0); } // Disable output buffering while (ob_get_level()) { ob_end_clean(); } // Set SSE headers header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // Get JSON body $input = json_decode(file_get_contents('php://input'), true); $message = $input['message'] ?? 'Hello'; $agent = AgentBuilder::create() ->withOpenAI(getenv('OPENAI_API_KEY'), 'gpt-4') ->build(); $stream = $agent->chatStream($message); foreach ($stream->getChunks() as $chunk) { if ($chunk->hasContent()) { echo "data: " . json_encode(['content' => $chunk->getContent()]) . "\n\n"; flush(); } } echo "data: [DONE]\n\n"; flush();
Frontend JavaScript (Fetch API):
async function streamChat(message) { const responseDiv = document.getElementById('response'); responseDiv.textContent = ''; const response = await fetch('api_stream.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value); const lines = text.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.substring(6).trim(); if (data === '[DONE]') { console.log('Stream complete'); return; } try { const parsed = JSON.parse(data); if (parsed.content) { responseDiv.textContent += parsed.content; } } catch (e) { // Skip invalid JSON } } } } }
StreamChunk Methods
Each chunk in the stream provides these methods:
foreach ($stream->getChunks() as $chunk) { // Get text content $text = $chunk->getContent(); $text = $chunk->getText(); // Alias // Check what type of chunk this is $chunk->hasContent(); // Has text content? $chunk->isContent(); // Is a content chunk? $chunk->isDone(); // Is the final chunk? $chunk->isError(); // Is an error chunk? $chunk->isFinal(); // Is done, stop, or error? // For tool calls during streaming $chunk->hasToolCall(); // Has complete tool call? $chunk->hasToolCallDelta(); // Has partial tool call data? $chunk->getToolCall(); // Get ToolCall object $chunk->getToolCallDelta(); // Get partial tool call array // Metadata $chunk->getType(); // 'content', 'tool_call', 'done', 'error', etc. $chunk->getIndex(); // Chunk index number $chunk->getFinishReason(); // 'stop', 'length', 'tool_calls', etc. $chunk->getModel(); // Model name (usually in first/last chunk) $chunk->getUsage(); // Usage stats (usually in last chunk) }
Getting the Complete Response After Streaming
$stream = $agent->chatStream('Tell me about PHP'); $fullText = ''; // Collect all chunks foreach ($stream->getChunks() as $chunk) { if ($chunk->hasContent()) { $content = $chunk->getContent(); echo $content; // Stream to output $fullText .= $content; // Collect for later } } // After streaming, you have the complete text echo "\n\nComplete response length: " . strlen($fullText) . " characters\n";
Streaming with Different Providers
All providers use the same streaming interface:
// OpenAI $agent = AgentBuilder::create() ->withOpenAI(getenv('OPENAI_API_KEY'), 'gpt-4') ->build(); // Anthropic $agent = AgentBuilder::create() ->withAnthropic(getenv('ANTHROPIC_API_KEY'), 'claude-3-sonnet-20240229') ->build(); // Google $agent = AgentBuilder::create() ->withGoogle(getenv('GOOGLE_API_KEY'), 'gemini-pro') ->build(); // Same streaming code works for all providers $stream = $agent->chatStream('Hello!'); foreach ($stream->getChunks() as $chunk) { echo $chunk->getContent(); }
Error Handling in Streams
try { $stream = $agent->chatStream('Tell me a story'); foreach ($stream->getChunks() as $chunk) { if ($chunk->isError()) { throw new \Exception('Stream error: ' . $chunk->getContent()); } echo $chunk->getContent(); } } catch (\AgentPHP\Exceptions\ProviderException $e) { echo "Provider error: " . $e->getMessage(); } catch (\AgentPHP\Exceptions\RateLimitException $e) { echo "Rate limited. Retry after: " . $e->getRetryAfter() . " seconds"; } catch (\Exception $e) { echo "Error: " . $e->getMessage(); }
Nginx Configuration for SSE
If using nginx, add this to your location block:
location /stream.php { proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; # If using FastCGI fastcgi_buffering off; fastcgi_keep_conn on; }
Apache Configuration for SSE
For Apache with mod_php, streaming should work out of the box. If using PHP-FPM:
<Location /stream.php> SetEnv no-gzip 1 SetEnv proxy-nokeepalive 1 </Location>
Middleware
Middleware allows you to intercept and modify requests/responses.
Built-in Middleware
Logging Middleware
use AgentPHP\Core\AgentBuilder; use Psr\Log\LoggerInterface; $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withLogging($logger, 'info') // PSR-3 logger ->build(); // Or with Laravel $agent = AgentBuilder::create() ->withOpenAI(config('agent.providers.openai.api_key')) ->withLogging(app('log'), 'debug') ->build();
Retry Middleware
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withRetry( maxRetries: 3, // Number of retry attempts delay: 1000, // Initial delay in milliseconds multiplier: 2.0 // Exponential backoff multiplier ) ->build();
Rate Limit Middleware
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withRateLimit( maxRequests: 60, // Maximum requests window: 60 // Time window in seconds ) ->build();
Timeout Middleware
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withTimeout(30) // 30 seconds timeout ->build();
Combining Middleware
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withLogging($logger) ->withRetry(3, 1000, 2.0) ->withRateLimit(60, 60) ->withTimeout(30) ->build();
Custom Middleware
use AgentPHP\Contracts\MiddlewareInterface; use AgentPHP\Core\ChatRequest; use AgentPHP\Core\ChatResponse; class MetricsMiddleware implements MiddlewareInterface { public function __construct( private MetricsService $metrics ) {} public function process(ChatRequest $request, callable $next): ChatResponse { $startTime = microtime(true); try { // Call next middleware/handler $response = $next($request); // Record success metrics $duration = microtime(true) - $startTime; $this->metrics->record('chat.success', $duration); $this->metrics->increment('chat.tokens', $response->getUsage()->getTotalTokens()); return $response; } catch (\Exception $e) { // Record error metrics $this->metrics->increment('chat.errors'); throw $e; } } } // Use the middleware $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withMiddleware(new MetricsMiddleware($metricsService)) ->build();
Authentication Middleware
class AuthenticationMiddleware implements MiddlewareInterface { public function process(ChatRequest $request, callable $next): ChatResponse { // Check if user is authenticated if (!$this->auth->check()) { throw new UnauthorizedException('User must be authenticated'); } // Check rate limits for user $userId = $this->auth->id(); if ($this->rateLimiter->tooManyAttempts($userId, 100)) { throw new RateLimitException('Too many requests'); } $this->rateLimiter->hit($userId); return $next($request); } }
Content Filter Middleware
class ContentFilterMiddleware implements MiddlewareInterface { private array $blockedWords = ['spam', 'inappropriate']; public function process(ChatRequest $request, callable $next): ChatResponse { // Filter input $messages = $request->getMessages(); foreach ($messages as $message) { if ($this->containsBlockedContent($message['content'] ?? '')) { throw new ContentPolicyException('Message contains blocked content'); } } $response = $next($request); // Filter output $content = $response->getContent(); if ($this->containsBlockedContent($content)) { throw new ContentPolicyException('Response contains blocked content'); } return $response; } private function containsBlockedContent(string $text): bool { foreach ($this->blockedWords as $word) { if (stripos($text, $word) !== false) { return true; } } return false; } }
Human-in-the-Loop
Enable approval workflows for sensitive operations.
Basic Setup
use AgentPHP\Core\AgentBuilder; use AgentPHP\HumanLoop\ApprovalHandler; $approvalHandler = ApprovalHandler::create(); $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withHumanApproval($approvalHandler) ->withTool(new DeleteUserTool()) // requiresApproval() returns true ->build();
Custom Approval Handler
use AgentPHP\Contracts\HumanLoopInterface; use AgentPHP\Tools\ToolCall; class SlackApprovalHandler implements HumanLoopInterface { public function requestApproval(ToolCall $call): bool { // Send Slack notification $this->slack->send([ 'channel' => '#approvals', 'text' => "Approval needed for: {$call->getName()}", 'data' => $call->getArguments(), ]); // Wait for response (implement your logic) return $this->waitForApproval($call->getId()); } public function onApproved(ToolCall $call): void { $this->slack->send([ 'channel' => '#approvals', 'text' => "Approved: {$call->getName()}", ]); } public function onDenied(ToolCall $call, string $reason): void { $this->slack->send([ 'channel' => '#approvals', 'text' => "Denied: {$call->getName()} - {$reason}", ]); } }
Auto-Approval for Development
$agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->withAutoApproval() // Skip approval in development ->build(); // Or skip approval for specific tools $agent = AgentBuilder::create() ->withOpenAI('your-api-key') ->skipApproval() // Skip all approvals ->build();
Error Handling
use AgentPHP\Exceptions\AgentException; use AgentPHP\Exceptions\ProviderException; use AgentPHP\Exceptions\RateLimitException; use AgentPHP\Exceptions\TimeoutException; use AgentPHP\Exceptions\ToolException; use AgentPHP\Exceptions\ValidationException; try { $response = $agent->chat('Hello'); } catch (RateLimitException $e) { // Handle rate limiting $retryAfter = $e->getRetryAfter(); sleep($retryAfter); } catch (TimeoutException $e) { // Handle timeout echo "Request timed out after {$e->getTimeout()} seconds"; } catch (ToolException $e) { // Handle tool errors echo "Tool '{$e->getToolName()}' failed: {$e->getMessage()}"; } catch (ValidationException $e) { // Handle validation errors foreach ($e->getErrors() as $field => $error) { echo "{$field}: {$error}"; } } catch (ProviderException $e) { // Handle provider-specific errors echo "Provider error: {$e->getMessage()}"; } catch (AgentException $e) { // Handle all other agent errors echo "Agent error: {$e->getMessage()}"; }
Development
Setup
# Clone the repository git clone https://github.com/agentphp/sdk.git cd sdk # Install dependencies composer install # Run tests composer test # Run code style check composer cs:check # Run static analysis composer analyse
Testing
# Run all tests composer test # Run unit tests only composer test:unit # Run integration tests only composer test:integration # Run tests with coverage composer test:coverage
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
The MIT License (MIT). Please see License File for more information.
Security
If you discover any security-related issues, please email security@agentphp.dev instead of using the issue tracker.
agentphp/sdk 适用场景与选型建议
agentphp/sdk 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 6 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 01 月 02 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「php」 「Agent」 「ai」 「chatbot」 「Gemini」 「openai」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 agentphp/sdk 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 agentphp/sdk 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 agentphp/sdk 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Custom laravel monolog logger for datadog logs management, both api and agent ways
The Message Submission Agent Diagnostics tool (msadiag) facilitates testing the compatibility of third party message submission agents.
Standalone replacement for php's native get_browser() function
A PHP desktop/mobile user agent parser with support for Laravel, based on Mobiledetect
Random user agent generator in PHP.
Alfabank REST API integration
统计信息
- 总下载量: 6
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 27
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-01-02