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: Veiligheid
- AI-aanroepen zijn duur en niet-deterministisch. Cache agressief,
%apcuvoor sessiescope,JSONDBvoor langere TTL's. - Filter gebruikersinvoer voordat je het in een prompt plaatst. Phlo's
esc()is voor HTML; voor prompts gebruik je je eigen sanitizer of een strikte tool schema. - Het loggen van prompts/antwoorden kan privacy-implicaties hebben. Standaard logt Phlo niet; je
data/errors.jsonziet alleen uitzonderingen.