18: AI
Phlo bundelt een aantal AI-providers (OpenAI, Claude, Gemini, DeepSeek, Grok) achter een enkele façade. Jij kiest een model, Phlo kiest de juiste engine. Streaming naar de DOM gebruikt dezelfde apply() mechanismen als de rest van Phlo: geen aparte client-side bibliotheek, geen aparte event bus.
18.1: Hulpmiddelen
Voeg toe aan data/app.json:
{
"resources": [..., "AI/AI", "AI/OpenAI"]
}
Één bestand per provider. AI/AI is de façade, die naar de juiste engine routeert op basis van het model:
| Model bevat | Engine |
|---|---|
gpt-*, o1-*, o3-*, o4-*, chatgpt-* |
OpenAI |
claude-* |
Claude |
deepseek-* |
DeepSeek |
gemini-* |
Gemini |
grok-* |
Grok |
Of expliciet via het via: argument: %AI->chat(via: 'claude', model: ...).
Het model argument is optioneel. Zonder een model gebruikt de façade zijn eigen standaard, prop model (gpt-5.4-mini, dat naar OpenAI routeert), zodat een app met alleen een OpenAI sleutel in creds.ini direct werkt. Verander de standaard voor een app vanuit app.phlo:
prop %AI.model = 'claude-opus-4-8'
Dit is het resource-configuratiepatroon van Phlo: prefix een prop of static met %<resource> en de transpiler injecteert het in die resource, geen fork nodig (zie Geavanceerd, "Hulpmiddelen aanpassen zonder te forken"). Het model selecteert ook de standaardengine, dus deze ene regel wijst de hele app naar Claude.
De facade exposeert dezelfde methoden voor elke engine, maar niet elke provider ondersteunt elke methode:
| Engine | chat | stream | tools | vision | embeddings | transcribe |
|---|---|---|---|---|---|---|
| OpenAI | ja | ja | ja | ja | native | ja |
| Claude | ja | ja | ja | ja | OpenAI | nee |
| Gemini | ja | ja | ja | ja | native | nee |
| DeepSeek | ja | ja | ja | nee | OpenAI | nee |
| Grok | ja | ja | ja | ja | OpenAI | nee |
OpenAI in de embeddings-kolom betekent dat de engine geen eigen embeddingmodel heeft en deze delegeert aan OpenAI, dus het heeft ook een OpenAI-sleutel nodig. DeepSeek en Grok zijn dunne lagen bovenop OpenAI (zelfde protocol, andere endpoint en sleutel), dus ze delen dezelfde set methoden; een nee cel betekent dat de provider geen model of endpoint achter die oproep heeft en het zal een fout geven. De matrix is de bron van waarheid: roep alleen een mogelijkheid aan die gemarkeerd is voor de engine die je target.
Inloggegevens gaan in data/creds.ini:
OpenAI = sk-...
Claude = sk-ant-...
Grok = xai-...
Phlo's security/creds laadt ze automatisch in %creds->OpenAI enzovoort. Zie Configuratie voor het volledige formaat van de inloggegevens, omgevingsvariabelen en prioriteit.
18.2: Een enkel antwoord
Korte vraag, één antwoord:
$answer = %AI->chat(
model: 'gpt-4o-mini',
user: 'Summarize this article: '.$article->text,
)
echo $answer->answer
Of zelfs korter, via de answer helper:
$verdict = answer('Is "carrot" a vegetable?', 'yes', 'no', 'maybe')
answer() is ingebouwd in AI/answer. Het doet één oproep met een lage temperatuur en retourneert alleen het puurste antwoord. Met opties wordt het een keuze uit de gegeven mogelijkheden.
18.3: Streamen naar de DOM
Dit is waar Phlo's apply() protocol echt tot zijn recht komt. Een async route die token voor token in een element schrijft:
route async POST chat::ask {
%res->streaming = true
foreach (%AI->stream(user: %payload->question) AS $chunk){
if (isset($chunk->text)) apply(append: arr('#answer' => $chunk->text))
}
}
Stel %res->streaming = true in en elke apply() wordt onmiddellijk naar de client geflusht op het moment dat je het aanroept, in plaats van gebufferd te worden totdat de reactie eindigt. Elk token wordt aan #answer toegevoegd via hetzelfde apply() protocol dat je overal elders gebruikt: geen SSE-plumbing, geen handmatige flush(), geen JS om te schrijven en geen status om te beheren, een streaming UI direct beschikbaar.
18.4: Tools (functieaanroep)
$tool = obj(
name: 'get_weather',
desc: 'Get the current weather for a location',
args: arr(
location: arr(type: 'string', desc: 'City and country, e.g. "Paris, FR"'),
),
)
$res = %AI->chat(
model: 'gpt-4o-mini',
user: 'What is the weather in Amsterdam?',
tools: [OpenAI::tool($tool)],
)
foreach ($res->tools ?? [] AS $call){
if ($call->name === 'get_weather') weather::fetch($call->args['location'])
}
Tool-aanroepen komen terug onder $res->tools als een array van {name, args}. Phlo's facade normaliseert de verschillen tussen de providers.
18.5: Visie
Werkt met OpenAI, Claude, Gemini en Grok.
18.6: Insluitingen
$vector = %AI->embedding('Phlo is a transpile-to-PHP framework', model: 'text-embedding-3-small')
%vectors->store(id: 'doc-1', vector: $vector, meta: ['source' => 'about'])
Het standaardmodel is specifiek voor de provider. Voor OpenAI is het text-embedding-3-small.
18.7: Transcribe
$file = %files->save(%payload->file('audio'))
$res = %AI->transcribe($file, model: 'whisper-1', language: 'nl')
echo $res->text18.8: Safety
- AI calls are expensive and non-deterministic. Cache aggressively,
%apcufor session scope,JSONDBfor longer TTLs. - Filter user input before you put it in a prompt. Phlo's
esc()is for HTML; for prompts use your own sanitizer or a strict tool schema. - Logging prompts/answers can have privacy implications. By default Phlo does not log; your
data/errors.jsononly sees exceptions.
Reference. The AI resources are documented per node in the Manual, generated from the resource files so it never drifts from the code.
Laatst bijgewerkt op 23-08-2026