Text
Generate text, stream responses, and extract structured data from AI models.
Quick Example
use Atlasphp\Atlas\Atlas;
$response = Atlas::text('openai', 'gpt-4o')
->instructions('You are a helpful assistant.')
->message('What is Laravel?')
->asText();
echo $response->text;Text Generation
$response = Atlas::text('openai', 'gpt-4o')
->instructions('You are a technical writer.')
->withMaxTokens(500)
->withTemperature(0.7)
->message('Explain dependency injection.')
->asText();
$response->text; // Generated text
$response->usage; // Usage object (inputTokens, outputTokens)
$response->finishReason; // FinishReason enum (Stop, Length, ToolCalls, etc.)With Conversation History
When using conversations, Atlas automatically loads message history — you don't need to manage it yourself. Just use ->for($user) or ->forConversation($id) on an agent and Atlas handles the rest.
For stateless usage without persistence, you can manually pass conversation history:
use Atlasphp\Atlas\Messages\UserMessage;
use Atlasphp\Atlas\Messages\AssistantMessage;
$response = Atlas::text('openai', 'gpt-4o')
->instructions('You are a helpful assistant.')
->withMessages([
new UserMessage('What is PHP?'),
new AssistantMessage('PHP is a server-side scripting language.'),
])
->message('Tell me more about its history.')
->asText();Automatic History
Most applications should use conversations instead of withMessages(). Atlas loads history, manages limits, and persists new messages automatically. Use withMessages() only when you need full manual control over what the model sees.
With Media Input (Vision)
use Atlasphp\Atlas\Input\Image;
$response = Atlas::text('openai', 'gpt-4o')
->instructions('Describe what you see.')
->message('What is in this image?', Image::fromUrl('https://example.com/photo.jpg'))
->asText();Usage & Token Tracking
Every response includes a Usage object with detailed token consumption. Use it for cost tracking, budgeting, and monitoring.
$response = Atlas::text('openai', 'gpt-4o')
->message('Explain quantum computing')
->asText();
$usage = $response->usage;
$usage->inputTokens; // Tokens in the prompt/input
$usage->outputTokens; // Tokens in the generated response
$usage->totalTokens(); // inputTokens + outputTokens
$usage->reasoningTokens; // Tokens used for reasoning/thinking (Anthropic, OpenAI o-series)
$usage->cachedTokens; // Tokens served from provider cache (reduced cost)
$usage->cacheWriteTokens; // Tokens written to provider cacheUsage Properties
| Property | Type | Description |
|---|---|---|
inputTokens | int | Tokens consumed by the prompt, instructions, and conversation history |
outputTokens | int | Tokens generated in the response |
reasoningTokens | ?int | Tokens used for internal reasoning (e.g. Anthropic extended thinking, OpenAI o1) |
cachedTokens | ?int | Input tokens served from the provider's prompt cache |
cacheWriteTokens | ?int | Input tokens written to the provider's prompt cache |
Counting Tokens Before Sending
Usage tells you what a call cost after it ran. To count the input tokens before sending — for cost estimates, context-window checks, or budget enforcement — call countTokens() on the builder. It uses each provider's native count endpoint where available (and a heuristic otherwise), counting the full payload including system prompt, tools, and media:
$count = Atlas::text('openai', 'gpt-4o')
->message('Explain quantum computing')
->countTokens();
$count->inputTokens; // 5
$count->estimated; // false (exact, from the provider)See the Token Counting guide for provider support, cost estimation, and enforcing your own budget via middleware.
Cost Estimation
$cost = ($usage->inputTokens * 0.0025 / 1000)
+ ($usage->outputTokens * 0.01 / 1000);
// Account for cached tokens (typically cheaper)
$cachedDiscount = ($usage->cachedTokens ?? 0) * 0.00125 / 1000;
$adjustedCost = $cost - $cachedDiscount;Multi-Step Usage
When tools are involved, usage is accumulated across all steps in the tool loop:
$response = Atlas::text('openai', 'gpt-4o')
->withTools([WeatherTool::class])
->message('What is the weather?')
->asText();
// Total usage across all round trips
$response->usage->inputTokens; // Sum of all step inputs
$response->usage->outputTokens; // Sum of all step outputs
$response->usage->totalTokens(); // Grand total
// Per-step breakdown
foreach ($response->steps as $step) {
echo "Step: {$step->usage->inputTokens} in / {$step->usage->outputTokens} out\n";
}All Modalities
Usage is available on text, structured, and embeddings responses. Image, audio, video, moderation, and reranking responses may not include token usage depending on the provider.
Streaming
Stream responses in real-time. asStream() returns a StreamResponse that implements IteratorAggregate. For complete details on SSE delivery, broadcasting, frontend integration, and testing, see the Streaming Guide.
Basic Streaming
$stream = Atlas::text('openai', 'gpt-4o')
->instructions('Tell me a story.')
->message('Once upon a time...')
->asStream();
foreach ($stream as $chunk) {
echo $chunk->text;
}SSE Response (Server-Sent Events)
Return a stream directly from a Laravel route:
Route::get('/stream', function () {
return Atlas::text('openai', 'gpt-4o')
->message('Tell me a joke')
->asStream();
});StreamResponse implements Laravel's Responsable — it automatically sends SSE headers.
Broadcasting
Broadcast chunks to a WebSocket channel:
use Illuminate\Broadcasting\Channel;
$stream = Atlas::text('openai', 'gpt-4o')
->message('Explain quantum physics')
->asStream();
$stream->broadcastOn(new Channel('chat.1'));
foreach ($stream as $chunk) {
// Chunks are automatically broadcast as StreamChunkReceived events
}Callbacks
$stream = Atlas::text('openai', 'gpt-4o')
->message('Hello')
->asStream();
$stream
->onChunk(function ($chunk) {
// Called for each chunk
})
->then(function ($stream) {
// Called after stream completes
$fullText = $stream->getText();
$usage = $stream->getUsage();
});
foreach ($stream as $chunk) {
// consume
}Stream Accessors
After iteration, access accumulated data:
$stream->getText(); // Full accumulated text
$stream->getUsage(); // Usage (available after last chunk)
$stream->getFinishReason(); // FinishReason
$stream->getToolCalls(); // Tool calls (if any)
$stream->getReasoning(); // Thinking/reasoning content (if model supports it)Streaming with Tools
When tools are present, asStream() runs the tool loop synchronously, then streams the results — yielding tool call chunks followed by text segments:
$stream = Atlas::text('openai', 'gpt-4o')
->message('What is the weather in NYC?')
->withTools([WeatherTool::class])
->asStream();
foreach ($stream as $chunk) {
match ($chunk->type) {
ChunkType::ToolCall => handleToolCalls($chunk->toolCalls),
ChunkType::Text => echo $chunk->text,
ChunkType::Done => handleCompletion($chunk->usage),
default => null,
};
}Chainable Callbacks
Multiple then() callbacks can be registered — they fire in order after the stream completes:
$stream
->then(fn ($s) => Log::info('Completed', ['tokens' => $s->getUsage()?->totalTokens()]))
->then(fn ($s) => cache()->put('last_response', $s->getText()));Structured Output
Extract typed data using a JSON Schema definition:
use Atlasphp\Atlas\Schema\Schema;
$schema = Schema::object('analysis', 'Sentiment analysis')
->enum('sentiment', 'Overall sentiment', ['positive', 'negative', 'neutral'])
->number('confidence', 'Confidence score 0-1')
->stringArray('keywords', 'Key topics')
->build();
$response = Atlas::text('openai', 'gpt-4o')
->instructions('Analyze the sentiment of the given text.')
->withSchema($schema)
->message('I love this product! Fast shipping and great quality.')
->asStructured();
$data = $response->structured;
// ['sentiment' => 'positive', 'confidence' => 0.95, 'keywords' => ['product', 'shipping', 'quality']]See Schema for the full field type reference.
Tool Calling
Use tools without agents for inline tool execution:
$response = Atlas::text('openai', 'gpt-4o')
->instructions('Help the user with weather information.')
->withTools([WeatherTool::class])
->message('What is the weather in Paris?')
->asText();
$response->text; // Final response after tool execution
$response->steps; // Each round trip in the tool loopReasoning (Thinking)
Reasoning lets a model work through a problem before answering. Atlas exposes one provider-normalized knob — pick an effort and Atlas maps it to each provider's native format:
use Atlasphp\Atlas\Enums\ReasoningEffort;
$response = Atlas::text('openai', 'gpt-5')
->reasoning(ReasoningEffort::High)
->message('Prove the square root of 2 is irrational.')
->asText();
$response->reasoning; // reasoning summary, when the model returns one
$response->usage->reasoningTokens; // reasoning token count, when reportedEffort levels are Minimal, Low, Medium, and High. How each maps:
| Provider | Native parameter | Notes |
|---|---|---|
| OpenAI | reasoning.effort | minimal is gpt-5 only; o-series takes low/medium/high. |
| Anthropic | thinking.budget_tokens | max_tokens is auto-raised above the budget; temperature is dropped (required with thinking). |
| Google Gemini | generationConfig.thinkingConfig | Effort becomes thinkingBudget. |
| xAI (Grok) | reasoning.effort | Collapsed to low/high. |
| OpenAI-compatible (Ollama, LM Studio) | reasoning_effort | Ignored by models that don't reason. |
Budget-based providers (Anthropic, Gemini) derive a token budget from the effort — Minimal 1024, Low 4096, Medium 8192, High 16000 — which you can override, and you can request thought summaries:
Atlas::text('anthropic', 'claude-sonnet-4-5')
->reasoning(ReasoningEffort::High, budgetTokens: 24000, includeSummary: true)
->message('…')
->asText();Reasoning works inside the tool-call loop: Atlas replays each provider's signed reasoning context across steps (Anthropic signed thinking blocks, OpenAI encrypted reasoning items) automatically, so multi-step tool conversations keep working with thinking enabled.
Notes:
- Reasoning only takes effect on reasoning-capable models.
- A raw
withProviderOptions()value overrides->reasoning()if both set the same key. - On Anthropic, reasoning isn't combined with a forced tool — structured output (
->withSchema()),->forceTools(), or->toolChoice(...)for a specific/required tool all force a tool call, which Anthropic disallows alongside thinking. On those turns the forced tool wins and thinking is skipped; anautochoice keeps thinking.
Queue Support
Dispatch any request to a queue by calling ->queue() before the terminal method. The terminal method (asText, asStream, etc.) returns a PendingExecution instead of a response:
$pending = Atlas::text('openai', 'gpt-4o')
->message('Write a long essay about AI')
->queue()
->asText();
$pending->executionId; // Available immediately for UI trackingThe job dispatches automatically when $pending goes out of scope. You can also chain callbacks or dispatch explicitly:
Atlas::text('openai', 'gpt-4o')
->message('Write a long essay about AI')
->queue()
->asText()
->then(fn ($response) => logger()->info($response->text))
->catch(fn ($e) => logger()->error($e->getMessage()));Queue Options
// Custom queue name
Atlas::text('openai', 'gpt-4o')
->message('Generate report')
->queue('atlas-heavy')
->asText();
// Shorthand — queue() accepts a queue name
Atlas::text('openai', 'gpt-4o')
->message('Generate report')
->queue('atlas-heavy')
->asText();
// Custom connection
Atlas::text('openai', 'gpt-4o')
->message('Generate report')
->queue()
->onConnection('redis')
->asText();
// Delay execution
Atlas::text('openai', 'gpt-4o')
->queue()
->withQueueDelay(300)
->message('Follow up in 5 minutes')
->asText();
// Broadcast results to a WebSocket channel
use Illuminate\Broadcasting\Channel;
Atlas::text('openai', 'gpt-4o')
->message('Analyze this data')
->queue()
->broadcastOn(new Channel('execution.' . $user->id))
->asText();Queue Configuration
Default queue settings in config/atlas.php:
'queue' => [
'connection' => env('ATLAS_QUEUE_CONNECTION'), // null = default
'queue' => env('ATLAS_QUEUE', 'default'),
'tries' => (int) env('ATLAS_QUEUE_TRIES', 3),
'backoff' => (int) env('ATLAS_QUEUE_BACKOFF', 30),
'timeout' => (int) env('ATLAS_QUEUE_TIMEOUT', 300),
'after_commit' => (bool) env('ATLAS_QUEUE_AFTER_COMMIT', true),
],For per-request queue overrides (withQueueTimeout(), withQueueTries(), withQueueBackoff()), HTTP retry (withTimeout(), withRetry(), withoutRetry()), and execution tracking, see the Queue & Background Jobs guide.
Provider Options
Pass provider-specific options:
$response = Atlas::text('openai', 'gpt-4o')
->withProviderOptions(['seed' => 12345, 'top_p' => 0.9])
->message('Hello')
->asText();TextResponse
| Property | Type | Description |
|---|---|---|
text | string | Generated text |
usage | Usage | Token counts (inputTokens, outputTokens, reasoningTokens, cachedTokens) |
finishReason | FinishReason | Why generation stopped (Stop, Length, ToolCalls, ContentFilter) |
toolCalls | array | Tool calls from the response |
reasoning | ?string | Reasoning/thinking content (if supported) |
steps | array | Tool loop history (when tools are used) |
meta | array | Additional metadata |
providerToolCalls | array | Provider-executed tool invocations (web_search_call, code_interpreter_call, etc.) |
annotations | array | Content annotations from the provider (url_citation, file_citation) |
StreamResponse
| Method | Returns | Description |
|---|---|---|
broadcastOn(Channel) | static | Broadcast chunks to a channel |
onChunk(Closure) | static | Callback for each chunk |
then(Closure) | static | Callback after stream completes (chainable — multiple allowed) |
getText() | string | Accumulated text (after iteration) |
getUsage() | ?Usage | Token usage (after iteration) |
getFinishReason() | ?FinishReason | Finish reason (after iteration) |
getToolCalls() | array | Tool calls (after iteration) |
getReasoning() | string | Thinking/reasoning content (after iteration) |
toResponse($request) | StreamedResponse | Convert to SSE HTTP response |
StructuredResponse
| Property | Type | Description |
|---|---|---|
structured | array | Parsed structured data matching the schema |
usage | Usage | Token counts |
finishReason | FinishReason | Why generation stopped |
Builder Reference
| Method | Description |
|---|---|
instructions(string) | Set system instructions |
message(string, Media) | Set user message with optional media |
withMessages(array) | Set conversation history |
withMaxTokens(int) | Maximum response tokens |
withTemperature(float) | Sampling temperature |
withSchema(Schema) | Schema for structured output |
withTools(array) | Add tools for auto tool calling |
withProviderTools(array) | Add provider tools (WebSearch, etc.) |
withMaxSteps(?int) | Max tool loop iterations |
withConcurrent(bool) | Enable concurrent tool execution |
withProviderOptions(array) | Provider-specific options |
withVariables(array) | Variables for instruction interpolation |
withMeta(array) | Metadata passed to middleware/events |
withMiddleware(array) | Per-request provider middleware |
queue() | Dispatch to queue instead of inline |