Getting Started with ubxty/azure-ai
Companion to the README. Walks through Azure subscription setup, AOAI resource creation, deployment, and the first
invoke().
1. Prerequisites
- Azure subscription.
- An Azure OpenAI resource (or Microsoft Foundry project).
- PHP 8.2+, Laravel 11 or 12.
- One model deployed in the resource (a deployment is a named, billable instantiation of a model).
The minimum RBAC role is Cognitive Services OpenAI User (or higher). It grants read on the resource and invoke on the data-plane deployment.
2. Install
composer require ubxty/azure-aiAuto-pulled: ubxty/core-ai ^2.1.3. Service provider is auto-discovered.
3. Create the resource + deployment
Portal walkthrough
- Azure Portal → Create a resource → search
Azure OpenAI→ Create. - Pick a Subscription + Resource group + Region (
East USandSweden Centralare the most permissive for newer models). - Pick a Pricing tier (Standard S0 is fine for low-throughput; PTUs for production workloads).
- Click Create. After deployment (about 2 minutes), open the resource.
- Resource → Keys and Endpoint → copy KEY 1 (or KEY 2) and Endpoint (looks like
https://your-resource.openai.azure.com). - Resource → Model deployments → Create deployment → pick a model (
gpt-4o,gpt-4o-mini,text-embedding-3-small) → give it a Deployment name (this is what you pass asmodelIdtoAzure::invoke). - Resource → Access control (IAM) → grant the principal running your app
Cognitive Services OpenAI User.
Foundry project walkthrough
- Microsoft Foundry → New project → pick subscription + resource group + region.
- Project → Deployments → Deploy model → pick a model → name the deployment.
- Project → Endpoints → copy the OpenAI v1 base URL (looks like
https://resource.services.ai.azure.com/api/projects/p/openai/v1) and the API key. - The package detects the
/openai/v1suffix and uses Bearer auth +modelin body. Noapi-versionquery string is used on v1 endpoints.
4. Plug credentials into .env
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_KEY=…
AZURE_OPENAI_API_VERSION=2024-10-21
AZURE_OPENAI_DEFAULT_MODEL=gpt-4oFor Foundry v1:
AZURE_OPENAI_ENDPOINT=https://resource.services.ai.azure.com/api/projects/p/openai/v1
AZURE_OPENAI_API_KEY=…
AZURE_OPENAI_DEFAULT_MODEL=gpt-4oAZURE_OPENAI_API_VERSION is optional on Foundry v1 (the URL already pins v1).
5. Run the interactive wizard
php artisan azure:configureWalks through:
- Endpoint URL (paste from portal).
- API key (one-time input; not echoed).
- API version (defaults to
2024-10-21). - Default deployment name.
Writes .env directly, then auto-runs php artisan config:clear.
6. Smoke-test
php artisan azure:testThe test command:
- Lists deployments (or probes the endpoint with a minimal chat call on Foundry v1).
- Runs a small request against the configured default deployment.
- Reports input/output tokens, latency, and cost.
If it returns Connection failed, the most common causes are:
- The endpoint URL is missing the scheme (
https://...). - The deployment name doesn't match a live deployment.
- The resource is in a region that doesn't have the model deployed.
7. First call
use Ubxty\AzureAi\Facades\Azure;
$result = Azure::invoke(
modelId: 'gpt-4o', // the *deployment* name from portal
systemPrompt: 'You are a careful summariser.',
userMessage: 'Q3 revenue was $4.2M, up 18% YoY.',
maxTokens: 256,
temperature: 0.2,
);
echo $result['response'];
echo $result['cost']; // in USDBy DI:
class FooService
{
public function __construct(private AzureManager $azure) {}
public function handle(): array
{
return $this->azure->invoke('gpt-4o', '…', '…');
}
}8. Multi-turn
$result = Azure::converse(
modelId: 'gpt-4o',
messages: [
['role' => 'user', 'content' => 'What is the capital of France?'],
['role' => 'assistant', 'content' => 'Paris.'],
['role' => 'user', 'content' => 'And Germany?'],
],
);9. Streaming
return Azure::converseStream(
modelId: 'gpt-4o',
messages: [['role' => 'user', 'content' => 'Tell me a story.']],
onChunk: fn (string $chunk) => echo $chunk,
);For SSE-backed StreamedResponse:
return Azure::converseStream(
modelId: 'gpt-4o',
messages: [['role' => 'user', 'content' => 'Tell me a story.']],
onChunk: fn (string $chunk) => echo $chunk,
);10. Add a second key for multi-region failover
# Primary (US East)
AZURE_OPENAI_ENDPOINT=https://your-east-resource.openai.azure.com
AZURE_OPENAI_API_KEY=…
AZURE_OPENAI_API_VERSION=2024-10-21
# Secondary (Sweden Central)
AZURE_OPENAI_ENDPOINT_DR=https://your-eu-resource.openai.azure.com
AZURE_OPENAI_API_KEY_DR=…
AZURE_OPENAI_API_VERSION_DR=2024-10-21In config/core-ai.php under azure_ai.connections.default.keys:
'connections' => [
'default' => [
'keys' => [
['label' => 'East', 'endpoint' => env('AZURE_OPENAI_ENDPOINT'), 'api_key' => env('AZURE_OPENAI_API_KEY'), 'api_version' => env('AZURE_OPENAI_API_VERSION', '2024-10-21')],
['label' => 'EU', 'endpoint' => env('AZURE_OPENAI_ENDPOINT_DR'), 'api_key' => env('AZURE_OPENAI_API_KEY_DR'), 'api_version' => env('AZURE_OPENAI_API_VERSION_DR', '2024-10-21')],
],
],
],The package rotates to the second key on rate-limit or auth-failure.
11. Multimodal setup
For image analysis (vision-capable deployments only — gpt-4o, gpt-4-turbo, gpt-4o-mini):
$result = Azure::conversation('gpt-4o')
->system('You extract line items from invoices.')
->user('Extract all items.')
->userWithImage('Anything I missed?', '/tmp/invoice.jpg')
->maxTokens(4096)
->send();The image block is sent as image_url per the OpenAI vision wire format. For documents, the package extracts text content and embeds it as a text part (Azure doesn't have native document parts).
12. Where to go next
endpoint-flavours.md— Traditional vs Foundry v1.caching-strategy.md— All 7 cost levers with worked math.embeddings.md— Batch embeddings withembed().real-world-patterns.md— 12 production patterns.faq.md— 25+ Q&A.