1tomany/llm-sdk
Laravel-friendly PHP SDK for working with LLM providers. Provides a clean client API, request/response handling, and configurable drivers so you can send prompts, manage completions, and integrate AI features into your app with minimal boilerplate.
Installation:
composer require 1tomany/llm-sdk
For Laravel, prefer the Symfony bundle for autowiring and config support.
First Use Case: Generate a simple LLM response using OpenAI:
use OneToMany\LlmSdk\Clients\OpenAI\Client;
use OneToMany\LlmSdk\Requests\GenerateOutputRequest;
$client = new Client('your-api-key');
$request = new GenerateOutputRequest(
model: 'gpt-4',
prompt: 'Hello, world!',
maxTokens: 100
);
$response = $client->generateOutput($request);
echo $response->getResponse();
Where to Look First:
examples/outputs/generate.php for basic usage.Requests and Responses classes (e.g., GenerateOutputRequest, CreateEmbeddingResponse) for payload structure.$client = ClientFactory::create('openai', ['api_key' => env('OPENAI_KEY')]);
$request = new GenerateOutputRequest(/* ... */);
$response = $client->generateOutput($request);
// Register clients in a service provider
$factory = new ClientFactory();
$factory->register('openai', new OpenAI\Client(env('OPENAI_KEY')));
// Define an action (e.g., SummarizeArticleAction)
$action = new SummarizeArticleAction($factory);
$result = $action->execute($articleText);
ClientFactory: Central registry for all LLM clients.BaseAction: Abstract class for reusable AI workflows (extend for custom logic).$query = new ProcessQueryRequest();
$query->addPrompt('Explain Laravel middleware');
$query->addFile($filePath);
$query->setModel('gpt-4');
$compiled = $client->compileQuery($query);
$hash = $compiled->getHash(); // For caching/deduplication
$response = $client->processQuery($compiled);
getHash() to cache responses (e.g., Redis).$compiled->toArray() before execution.$fileRequest = new FileRequest(
file: fopen('document.pdf', 'r'),
purpose: 'assistants'
);
$client->uploadFile($fileRequest);
$client->deleteFile($fileId);
$store = $client->createSearchStore('my-store', 'gemini-1.5-pro');
$client->importFileToSearchStore($storeId, $fileId);
$results = $client->searchStore($storeId, 'query text');
ClientFactory and actions in AppServiceProvider:
public function register()
{
$this->app->singleton(ClientFactory::class, function () {
$factory = new ClientFactory();
$factory->register('openai', new OpenAI\Client(env('OPENAI_KEY')));
return $factory;
});
}
ClientFactory or actions into controllers/services:
public function __construct(private ClientFactory $factory) {}
.env support:
# config/packages/1tomany_llm.yaml
one_to_many_llm:
clients:
openai:
api_key: '%env(OPENAI_KEY)%'
model: 'gpt-4'
Mock client for unit tests:
$factory->register('mock', new Mock\Client());
$mockClient = $factory->get('mock');
$mockClient->setResponse(['choices' => [['text' => 'Mocked reply']]]);
ClientFactory to return test clients:
$factory = Mockery::mock(ClientFactory::class);
$factory->shouldReceive('get')->andReturn(new Mock\Client());
$action = new MyAction($factory);
$batch = $client->createBatch();
$batch->addQuery($query1);
$batch->addQuery($query2);
$results = $client->readBatch($batchId);
$hash = $compiled->getHash();
$cached = cache()->get("llm_response_{$hash}");
if (!$cached) {
$response = $client->processQuery($compiled);
cache()->put("llm_response_{$hash}", $response, now()->addHours(1));
}
Platform Limitations:
Model-Specific Quirks:
Content objects for prompts/files. OpenAI uses arrays.
// Gemini
$query->addContent(new Content\Text('Prompt text'));
// OpenAI
$query->addPrompt('Prompt text');
$mockClient->setResponse(['choices' => [...]]);
Schema Validation:
title field. The SDK extracts this for request IDs:
$schema = ['title' => 'MySchema', 'properties' => [...]];
Rate Limiting:
throttle:
Route::middleware(['throttle:100,1'])->group(...);
File Handling:
$embeddingRequest->setIncludeFiles(true);
Request Logging: Compile queries before sending to inspect payloads:
$compiled = $client->compileQuery($query);
\Log::info('LLM Request:', $compiled->toArray());
Error Messages: Normalized exceptions include:
InvalidRequestException: Malformed payloads.ApiException: HTTP errors (e.g., 429 rate limits).PlatformException: Provider-specific issues.
Use try/catch to handle gracefully:try {
$response = $client->generateOutput($request);
} catch (ApiException $e) {
\Log::error('LLM API Error:', ['status' => $e->getStatusCode()]);
return response()->json(['error' => 'Service unavailable'], 503);
}
Hash Collisions:
CompileQueryResponse::getHash() uses SHA-256 of the request payload. Rare but possible for identical payloads with different metadata (e.g., timestamps
How can I help you explore Laravel packages today?