18: AI
Phlo 将多个 AI 提供商(OpenAI、Claude、Gemini、DeepSeek、Grok)整合在一个统一的外观下。您选择一个模型,Phlo 会选择合适的引擎。流式传输到 DOM 使用与 Phlo 其余部分相同的 apply() 机制:没有单独的客户端库,也没有单独的事件总线。
18.1: 资源
将以下内容添加到 data/app.json:
{
"resources": [..., "AI/AI", "AI/OpenAI"]
}
每个提供者一个文件。AI/AI 是外观,它根据模型路由到正确的引擎:
| 模型包含 | 引擎 |
|---|---|
gpt-*,o1-*,o3-*,o4-*,chatgpt-* |
OpenAI |
claude-* |
Claude |
deepseek-* |
DeepSeek |
gemini-* |
Gemini |
grok-* |
Grok |
或者通过 via: 参数显式指定:%AI->chat(via: 'claude', model: ...)。
model 参数是可选的。如果没有提供,外观将使用其默认值 prop model(gpt-5.4-mini,路由到 OpenAI),因此只在 creds.ini 中有 OpenAI 密钥的应用程序可以开箱即用。要更改应用程序的默认值,请在 app.phlo 中:
prop %AI.model = 'claude-opus-4-8'
这是 Phlo 的资源配置模式:在 %<resource> 前缀一个 prop 或 static,转译器会将其注入到该资源中,无需分叉(请参见高级,“在不分叉的情况下修改资源”)。该模型还选择了默认引擎,因此这一行将整个应用指向 Claude。
外观为每个引擎暴露相同的方法,但并不是每个提供者都支持每一个:
| 引擎 | chat | stream | tools | vision | embeddings | transcribe |
|---|---|---|---|---|---|---|
| OpenAI | 是 | 是 | 是 | 是 | native | 是 |
| Claude | 是 | 是 | 是 | 是 | OpenAI | 否 |
| Gemini | 是 | 是 | 是 | 是 | native | 否 |
| DeepSeek | 是 | 是 | 是 | 否 | OpenAI | 否 |
| Grok | 是 | 是 | 是 | 是 | OpenAI | 否 |
embeddings 列中的 OpenAI 意味着该引擎没有自己的嵌入模型,而是委托给 OpenAI,因此它也需要一个 OpenAI 密钥。DeepSeek 和 Grok 是在 OpenAI 之上的薄层(相同协议,不同端点和密钥),因此它们共享其方法集;no 单元格意味着提供者在该调用后没有模型或端点,调用将会出错。矩阵是真相的来源:仅调用标记为您目标引擎的能力。
凭据放在 data/creds.ini 中:
OpenAI = sk-...
Claude = sk-ant-...
Grok = xai-...
Phlo 的 security/creds 会自动加载到 %creds->OpenAI 等。有关完整的凭证格式、环境变量和优先级,请参见配置。
18.2: 一个单一的答案
简短的问题,一个答案:
$answer = %AI->chat(
model: 'gpt-4o-mini',
user: 'Summarize this article: '.$article->text,
)
echo $answer->answer
甚至可以更短,通过 answer 辅助函数:
$verdict = answer('Is "carrot" a vegetable?', 'yes', 'no', 'maybe')
answer() 是内置于 AI/answer 的。它以低温度进行一次调用,并仅返回最纯粹的答案。使用选项时,它会从给定的可能性中进行选择。
18.3: 流式传输到 DOM
这是 Phlo 的 apply() 协议真正闪光的地方。一个异步 route,它将一个个 token 写入一个元素:
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))
}
}
设置 %res->streaming = true,每次调用 apply() 时,都会立即将数据刷新到客户端,而不是在响应结束之前进行缓冲。每个令牌通过您在其他地方使用的相同 apply() 协议附加到 #answer:无需 SSE 管道,无需手动 flush(),无需编写 JS,也无需管理状态,立即实现流式 UI。
18.4: 工具(函数调用)
$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'])
}
工具调用会以数组的形式返回在 $res->tools 下,数组包含 {name, args}。Phlo 的外观模式规范化了提供者之间的差异。
18.5: 愿景
$res = %AI->vision('What is in this photo?', '/uploads/photo.jpg')
echo $res->answer
与 OpenAI、Claude、Gemini 和 Grok 一起工作。
18.6: 嵌入
$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'])
默认模型是特定于提供者的。对于 OpenAI,它是 text-embedding-3-small。
18.7: 转录
$file = %files->save(%payload->file('audio'))
$res = %AI->transcribe($file, model: 'whisper-1', language: 'nl')
echo $res->text18.8: 安全
- AI 调用成本高且不可预测。要积极缓存,使用
%apcu进行会话范围缓存,使用JSONDB进行更长的 TTL。 - 在将用户输入放入提示之前进行过滤。Phlo 的
esc()用于 HTML;对于提示,请使用您自己的清理工具或严格的工具架构。 - 记录提示/答案可能会有隐私影响。默认情况下,Phlo 不会记录;您的
data/errors.json仅记录异常。