andriy_m16 May 2026 07:07

A lot of the LLM SDK libraries for PHP are either abandoned, Laravel-only, or add 50MB of dependencies for what is essentially a few HTTP calls. Here’s what we’ve settled on for production use.

The reality: Claude, OpenAI, Mistral, and most others expose simple REST APIs. You don’t need an SDK. You need a good HTTP client and proper streaming support if you want streaming responses.

Basic non-streaming call with Guzzle:

PHP
$client = new \GuzzleHttp\Client();
$response = $client->post('https://api.anthropic.com/v1/messages', [
'headers' => [
'x-api-key' => getenv('ANTHROPIC_KEY'),
'anthropic-version' => '2023-06-01',
'content-type' => 'application/json',
],
'json' => [
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'messages' => [['role' => 'user', 'content' => $prompt]],
],
]);
$data = json_decode($response->getBody(), true, flags: JSON_THROW_ON_ERROR);
$text = $data['content'][0]['text'];
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Streaming with Server-Sent Events is trickier in sync PHP but doable:

PHP
$response = $client->post($url, ['stream' => true, ...]);
$body = $response->getBody();
while (!$body->eof()) {
$line = trim($body->read(1024));
if (str_starts_with($line, 'data: ')) {
$chunk = json_decode(substr($line, 6), true);
// process chunk
}
}
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

In Swoole/Hyperf context you can do this inside a coroutine and push chunks via WebSocket.

What I’ve found works well:

  • Keep a thin wrapper class per provider (50–100 lines)
  • Handle rate limits with exponential backoff
  • Log every request/response with a correlation ID for debugging
  • Set aggressive timeouts — LLM APIs can hang for 30+ seconds on long outputs

Anyone doing function calling / tool use without SDK? That’s where it gets more complex.

Replies (1)
mdemir16 May 2026 08:02

Tool use without SDK is manageable. The API shape is just JSON — you define tools as a schema, the model returns a tool_use content block, you execute locally and return a tool_result message. About 40 lines of loop logic.

The tricky part is the conversation state management — you need to keep the full message history including tool calls and results as you go back and forth. Easy to get the structure wrong and get cryptic 400 errors from the API.

One pattern that helped us: a small Conversation value object that enforces the message array structure and validates before sending. Saved us from malformed payloads constantly.

0
Write a reply
Markdown. ```php blocks are runnable.