Laravel AI SDK (laravel/ai) হলো agent, text generation আর অন্যান্য AI feature-এর জন্য Laravel-এর official package। এতে একটা openai-compatible driver আছে, যা OpenAI-র মতো /chat/completions endpoint আছে এমন যেকোনো server-এর সাথে কথা বলে, আর Tokens ঠিক তেমনই। config/ai.php-এ Tokens-এর URL আর key দিয়ে একটা provider declare করুন, তারপর agent call করার সময় নাম ধরে সেটা বেছে নিন।
Laravel-এর agent layer ছাড়া শুধু PHP-র সাধারণ OpenAI client চাইলে PHP পেজ দেখুন।
কী কী যাচাই করা হয়েছে
এই পেজ লেখা হয়েছে Laravel 13.x ও 12.x-এর জন্য Laravel AI SDK-র documentation (laravel.com/docs, 2026 সালের অক্টোবরে দেখা) আর laravel/ai 1.2.0-এর source দেখে (release হয়েছে 7 অক্টোবর 2026-এ)। 12.x-এর পেজে openai-compatible driver-এর কথা লেখা নেই, তাই ওই অংশ এসেছে 13.x-এর docs আর package-এর source থেকে। Code-টা documentation আর source দেখে মেলানো হয়েছে, Tokens-এর বিরুদ্ধে শুরু থেকে শেষ পর্যন্ত চালিয়ে দেখা হয়নি।
যা যা লাগবে#
- PHP 8.3 বা নতুন, আর Laravel 12 বা 13।
laravel/ai1.2.0-এর শর্তphp ^8.3আরilluminate/* ^12.0|^13.0। - API keys থেকে নেওয়া একটা Tokens key।
- /models থেকে একটা model ID। আপনার agent tool ব্যবহার করলে এমন model নিন যা tool calling সাপোর্ট করে (Choosing a model)।
Install করুন#
composer require laravel/ai
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migratePublish করলে config/ai.php আর একটা migration তৈরি হয়। Migration বানায় agent_conversations আর agent_conversation_messages table, যেখানে SDK conversation জমা রাখে। Conversation memory এখনই ব্যবহার না করলেও এটা চালিয়ে নিন।
আগের কোনো release থেকে আপনার config/ai.php আগে থেকেই থাকলে composer update laravel/ai চালান, তারপর আপনার file-টা এখনকার published file-এর সাথে মিলিয়ে দেখুন। পুরোনো copy-তে openai-compatible entry না-ও থাকতে পারে, সেক্ষেত্রে নিচে দেখানো মতো হাতে যোগ করে নিন।
Tokens provider সাজান#
config/ai.php-এর providers array-তে একটা provider যোগ করুন। নামটা আপনার ইচ্ছা, এই পেজে tokens ব্যবহার করা হয়েছে।
return [
'default' => 'tokens',
// ...
'providers' => [
// ... the providers that ship in the file stay as they are
'tokens' => [
'driver' => 'openai-compatible',
'url' => env('TOKENS_BASE_URL', 'https://tokens.bd/v1'),
'key' => env('TOKENS_API_KEY'),
'models' => [
'text' => [
'default' => env('TOKENS_MODEL', 'deepseek/deepseek-v4.1-flash'),
],
],
],
],
];TOKENS_API_KEY=tok_live_your_keyপ্রতিটা option কীভাবে কাজ করে:
urlবাধ্যতামূলক।https://tokens.bd/v1দিন,/v1-সহ। Driver শেষের slash কেটে তার নিচেchat/completions-এ post করে, তাই request যায়https://tokens.bd/v1/chat/completions-এ।keySDK-তে ঐচ্ছিক, থাকলে bearer token হিসেবে যায়। Tokens-এর ক্ষেত্রে এটা অবশ্যই লাগবে।models.text.defaultহলো সেই model, যা call-এ আলাদা করে কোনোটার নাম না দিলে ব্যবহার হয়। এটা না থাকলেmodel:না দেওয়া call-এInvalidArgumentExceptionthrow হয়, যেখানে লেখা থাকে provider-এর একটা default text model লাগবে।'default' => 'tokens'দিলে text-এর জন্য এই provider-ই default হয়ে যায়। Default না ছুঁতে চাইলে এটা বাদ দিন, আর নিচের উদাহরণগুলোর মতো প্রতিটা call-এprovider: 'tokens'দিন।
Key-কে config file থেকে দূরে রাখুন
ওপরের মতো env() দিয়ে key পড়ুন, আর .env version control-এর বাইরে রাখুন। যে server config cache করে রাখে, সেখানে .env বা config/ai.php বদলানোর পর php artisan config:clear চালান (অথবা আবার config:cache)।
Laravel 12 ও 13: variable-এর নাম আলাদা, config একই#
Laravel-এর docs-এ variable-এর নাম version ভেদে আলাদা, আর সেটা শুধু built-in openai provider-এর বেলায় কাজে আসে:
| Docs | Custom OpenAI URL-এর variable | openai-compatible provider |
|---|---|---|
| Laravel 13.x | OPENAI_URL | Documented। Shipped config/ai.php-এ env-এর নাম: OPENAI_COMPATIBLE_URL, OPENAI_COMPATIBLE_API_KEY। |
| Laravel 12.x | OPENAI_BASE_URL | পেজে বর্ণনা নেই। Driver আছে laravel/ai package-এ, যা Laravel 12 ও 13 দুটোই সাপোর্ট করে। |
Variable-এর নাম বলতে বোঝায় আপনার config/ai.php যা env()-কে দেয়, তার বেশি কিছু নয়। ওপরের tokens entry নিজের নাম ব্যবহার করে, তাই দুই version-এই চলে। File-এ আগে থেকে যে entry আছে সেটাই চাইলে .env-এ OPENAI_COMPATIBLE_URL=https://tokens.bd/v1 আর OPENAI_COMPATIBLE_API_KEY দিন, আর ওই entry-তে models.text.default যোগ করুন।
Built-in openai provider কেন নয়#
Built-in openai provider-ও custom url নেয়। Package-এর source অনুযায়ী সেটা request পাঠায় {url}/responses-এ, অর্থাৎ OpenAI Responses API-তে, আর openai-compatible driver পাঠায় {url}/chat/completions-এ। Tokens দুটোই চালায় (Chat Completions, Responses)। এই পেজে openai-compatible নেওয়া হয়েছে কারণ docs তৃতীয় পক্ষের gateway-র জন্য এই driver-টার কথাই বলে, আর এটা Chat Completions format পাঠায়, যা সবচেয়ে বেশি সাপোর্ট পাওয়া format।
প্রথম call করুন#
Anonymous agent-এর জন্য আলাদা class লাগে না। এটা route-এ রাখুন, নয়তো php artisan tinker-এ চালান:
use function Laravel\Ai\agent;
$response = agent(
instructions: 'Answer in one short paragraph.',
)->prompt(
'When should I use a queue instead of running code in the request?',
provider: 'tokens',
model: 'deepseek/deepseek-v4.1-flash',
);
echo $response->text;$response->text হলো উত্তর। (string) $response একই text দেয়। config/ai.php-এ আপনার দেওয়া নামের provider-এর জন্য provider-এ সাধারণ string দিলেই চলে। models.text.default set থাকলে model: বাদ দিতে পারেন।
ঠিকমতো চলছে কি না দেখুন#
আগে Laravel-এর বাইরে থেকে দেখুন key দিয়ে কোন কোন model চলে:
curl -s https://tokens.bd/v1/models -H "Authorization: Bearer $TOKENS_API_KEY"তারপর app-এর ভেতরে:
php artisan tinker\Laravel\Ai\agent(instructions: 'Reply with one word.')->prompt('Say ready.', provider: 'tokens')->textছোট একটা উত্তর এলে বুঝবেন URL, key আর model ঠিক আছে। 404 model_not_found মানে ID ভুল; 401 মানে key পৌঁছায়নি। নিচের Troubleshooting দেখুন।
Agent class#
যা বারবার কাজে লাগবে তার জন্য একটা agent class বানান, আর attribute দিয়ে তার provider ও model বেঁধে দিন:
php artisan make:agent ReviewCoachnamespace App\Ai\Agents;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
use Stringable;
#[Provider('tokens')]
#[Model('deepseek/deepseek-v4.1-flash')]
#[Timeout(120)]
class ReviewCoach implements Agent
{
use Promptable;
public function instructions(): Stringable|string
{
return 'You review pull request descriptions and suggest one improvement.';
}
}$response = (new \App\Ai\Agents\ReviewCoach)->prompt('Fix login redirect');
echo $response->text;Provider, Model আর Timeout attribute-গুলো আছে Laravel-এর docs-এর agent configuration section-এ। Provider একটা Lab enum-এর মান, একটা string বা একটা array নেয়।
Response stream করুন#
stream() একটা StreamableAgentResponse ফেরত দেয়। সেটা route থেকে return করলে browser-এ server-sent event হিসেবে চলে যায়:
use function Laravel\Ai\agent;
Route::get('/ask', function () {
return agent(instructions: 'Answer briefly.')
->stream('Explain job batching in Laravel.', provider: 'tokens');
});Event নিজে সামলাতে চাইলে, যেমন Artisan command-এ, stream-এর ওপর loop চালান:
use Laravel\Ai\Streaming\Events\Error;
use Laravel\Ai\Streaming\Events\TextDelta;
$stream = agent(instructions: 'Answer briefly.')
->stream('Explain job batching in Laravel.', provider: 'tokens');
foreach ($stream as $event) {
if ($event instanceof TextDelta) {
echo $event->delta;
} elseif ($event instanceof Error) {
// $event->type holds the error code, $event->message the text
fwrite(STDERR, "{$event->type}: {$event->message}\n");
}
}Laravel-এর docs-এ একটা then() callback-এর কথাও আছে, যেটা পুরো response stream হয়ে গেলে চলে; তার StreamedAgentResponse-এ থাকে text, events আর usage। Driver Tokens-এর কাছে stream_options.include_usage চায়, তাই token count আসে stream-এর শেষে। Wire format-এর জন্য Streaming দেখুন।
Tool#
Tool হলো এমন class যার একটা description, একটা schema আর একটা handle method আছে। একটা বানিয়ে নিন:
php artisan make:tool GetWeathernamespace App\Ai\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class GetWeather implements Tool
{
public function description(): Stringable|string
{
return 'Current weather for a city.';
}
public function handle(Request $request): Stringable|string
{
return $request['city'].': light rain, 29C'; // call your real weather source here
}
public function schema(JsonSchema $schema): array
{
return [
'city' => $schema->string()->required(),
];
}
}এটা anonymous agent-কে দিন, অথবা agent class-এর tools() method থেকে return করুন:
use App\Ai\Tools\GetWeather;
use function Laravel\Ai\agent;
$response = agent(
instructions: 'Use the weather tool when asked about weather.',
tools: [new GetWeather],
)->prompt('Is it raining in Dhaka?', provider: 'tokens');
echo $response->text;SDK tool-এর definition পাঠায় Chat Completions format-এ, আর loop-টা আপনার হয়ে নিজেই চালায়। Tool calling শুধু সেই model-এ চলে যা এটা সাপোর্ট করে, তাই /models-এ model-এর পেজ দেখুন আর পড়ুন Tool calling।
Timeout#
Agent-এর timeout-এর default 60 সেকেন্ড। এটা call পিছু, class পিছু, বা দুই জায়গাতেই বাড়াতে পারেন:
$response = $agent->prompt('...', provider: 'tokens', timeout: 180);Class-এর জন্য #[Timeout(180)] দিন। মানটা ওই request-এর HTTP client timeout, সেকেন্ডে। Laravel-এর HTTP client-এ এটা Guzzle-এর timeout option, যাকে Guzzle বলে request-এর মোট সময়। তাই লম্বা stream করা উত্তর ওই সীমায় কেটে যেতে পারে। আপনার সবচেয়ে লম্বা generation-এর চেয়ে বেশি মান দিন।
আরও কয়েকটা সীমা জেনে রাখুন:
- Upstream থেকে response header আসার জন্য gateway 600 সেকেন্ড পর্যন্ত অপেক্ষা করে, তাই ওর দিক থেকে লম্বা generation-এ সমস্যা নেই।
- তার আগেই PHP নিজে web request থামিয়ে দিতে পারে।
php.ini-তেmax_execution_timeদেখুন (web request-এর জন্য PHP-র default 30 সেকেন্ড), অথবা লম্বা কাজ queue-তে চালান।
Error সামলান#
SDK কিছু HTTP status নিজের exception-এ রূপান্তর করে। Tokens-এর ক্ষেত্রে:
| Status | Exception | Tokens-এর code |
|---|---|---|
| 402 | Laravel\Ai\Exceptions\InsufficientCreditsException | insufficient_credits, no_funding, outstanding_debt, member_cap_reached |
| 429 | Laravel\Ai\Exceptions\RateLimitedException | rate_limited, concurrency_limit, window_exhausted, model_limit_reached, rate_limit_exceeded |
| 502, 503, 504 | Laravel\Ai\Exceptions\ProviderOverloadedException | upstream_unreachable, no_upstream_available, upstream_timeout |
| connection ব্যর্থ হলে | Laravel\Ai\Exceptions\ProviderConnectionException | নেই; request Tokens পর্যন্ত পৌঁছায়ইনি |
| বাকি সব 4xx ও 5xx | Illuminate\Http\Client\RequestException | invalid_api_key, model_not_found, model_not_allowed_on_key আর বাকিগুলো |
Tokens-এর code থাকে response body-তে। SDK-র exception আসল error-টাকে previous exception হিসেবে রেখে দেয়:
use Illuminate\Http\Client\RequestException;
use Laravel\Ai\Exceptions\AiException;
try {
$text = agent(instructions: 'Say hi.')->prompt('hi', provider: 'tokens')->text;
} catch (AiException|RequestException $e) {
$request = $e instanceof RequestException ? $e : $e->getPrevious();
$response = $request instanceof RequestException ? $request->response : null;
logger()->warning('Tokens call failed', [
'status' => $response?->status(),
'code' => $response?->json('error.code'),
'request_id' => $response?->header('x-tokens-request-id'),
]);
throw $e;
}প্রতিটা ব্যর্থতার সাথে x-tokens-request-id log করে রাখুন। ওটা ধরে Support আপনার request খুঁজে বের করতে পারে (Errors)। 429 window_exhausted হলে window reset না হওয়া পর্যন্ত retry করে লাভ নেই; Retry-After বলে দেয় কখন।
openai-compatible driver কী কী করে#
Package-এর source অনুযায়ী এই driver text generation (streaming আর tool-সহ), embeddings আর transcription করে। Image generation আর text-to-speech চলে SDK-র অন্য provider দিয়ে, এটা দিয়ে নয়। Embeddings-এর জন্য provider config-এ models.embeddings.default লাগে; Tokens embeddings চালায় (Embeddings), তবে এই পেজে সেই setup ধাপে ধাপে দেখানো হয়নি।
সমস্যা হলে#
| লক্ষণ | কারণ ও সমাধান |
|---|---|
The [tokens] openai-compatible provider requires a 'url' to be configured | url ফাঁকা। tokens entry দেখুন আর config cache clear করুন। |
... requires a default text model | models.text.default দিন, নয়তো call-এ model: দিন। |
404 model_not_found | Model ID ভুল। /models থেকে copy করুন। |
প্রতিটা call-এ 404, error message-এ একটা path-এর উল্লেখ | URL-এ /v1 নেই। https://tokens.bd/v1 দিন। |
401 missing_api_key বা invalid_api_key | চলমান process-এ TOKENS_API_KEY ফাঁকা, প্রায়ই কারণ config cache করা। php artisan config:clear চালান। |
RateLimitedException | ওপরে দেখানো মতো error.code পড়ুন। rate_limited আর concurrency_limit Retry-After-এর পর ঠিক হয়; window_exhausted ঠিক হয় plan reset হলে। |
InsufficientCreditsException | Billing-এ টাকা যোগ করুন। |
cURL error 28: Operation timed out | timeout-এর মান response আসার সময়ের চেয়ে কম। বাড়ান। |
| Stream করা text মাঝপথে থেমে যায় | মোট timeout শেষ হয়ে গেছে, অথবা PHP-র max_execution_time request থামিয়ে দিয়েছে। দুটোই বাড়ান। |
Tokens-এর সব error code আছে Errors পেজে, আর সাধারণ সমাধান পাবেন Troubleshooting-এ।