16: AI
Phlo 将多个 AI 提供商(OpenAI、Claude、Gemini、DeepSeek、Grok)整合在一个统一的外观下。您选择一个模型,Phlo 会选择合适的引擎。流式传输到 DOM 使用与 Phlo 其余部分相同的 apply() 机制:没有单独的客户端库,也没有单独的事件总线。
16.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 参数是可选的。如果没有提供,外观将回退到 %app->model,然后是它自己的默认值 gpt-5.4-mini(路由到 OpenAI),因此仅在 creds.ini 中有 OpenAI 密钥的应用程序可以开箱即用。设置 %app->model 可以按应用更改默认值,或者使用构建模块覆盖 %AI.model 以系统范围内更改,包括默认引擎。
外观为每个引擎暴露相同的方法,但并非每个提供者都支持每一个:
| 引擎 | chat | stream | tools | vision | embeddings | transcribe |
|---|---|---|---|---|---|---|
| OpenAI | 是 | 是 | 是 | 是 | 本地 | 是 |
| Claude | 是 | 是 | 是 | 是 | OpenAI | 否 |
| Gemini | 是 | 是 | 是 | 是 | 本地 | 否 |
| DeepSeek | 是 | 是 | 是 | 否 | OpenAI | 否 |
| Grok | 是 | 是 | 是 | 是 | OpenAI | 否 |
OpenAI 在嵌入列中意味着该引擎没有自己的嵌入模型,而是委托给 OpenAI,因此它也需要一个 OpenAI 密钥。DeepSeek 和 Grok 是在 OpenAI 之上的薄层(相同协议,不同端点和密钥),因此它们共享其方法集;no 单元格意味着提供者在该调用后没有模型或端点,将会出错。矩阵是真相的来源:仅调用标记为您目标引擎的能力。
凭据放在 data/creds.ini 中:
OpenAI = sk-...
Claude = sk-ant-...
Grok = xai-...
Phlo 的 security/creds 会自动将它们加载到 %creds->OpenAI 等中。有关完整凭据格式、环境变量和优先级,请参见配置。
16.2: 一个单一的答案
简短的问题,一个答案:
$answer = %AI->chat(
model: 'gpt-4o-mini',
user: '总结这篇文章:'.$article->text,
)
echo $answer->answer
或者更简短,通过 answer 辅助函数:
$verdict = answer('“胡萝卜”是蔬菜吗?', '是', '否', '可能')
answer() 是内置于 AI/answer 的。它以低温度进行一次调用,并仅返回最纯粹的答案。通过选项,它变成了从给定可能性中的选择。
16.3: 流式传输到 DOM
这是 Phlo 的 apply() 协议真正闪光的地方。一个异步路由,将一个个令牌写入一个元素:
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。
16.4: 工具(函数调用)
$tool = obj(
name: 'get_weather',
desc: '获取某个地点的当前天气',
args: arr(
location: arr(type: 'string', desc: '城市和国家,例如 "Paris, FR"'),
),
)
$res = %AI->chat(
model: 'gpt-4o-mini',
user: '阿姆斯特丹的天气如何?',
tools: [OpenAI::tool($tool)],
)
foreach ($res->tools ?? [] AS $call){
if ($call->name === 'get_weather') weather::fetch($call->args['location'])
}
工具调用以数组的形式返回在 $res->tools 下,包含 {name, args}。Phlo 的外观规范化了提供者之间的差异。
16.5: 愿景
$res = %AI->vision('这张照片里有什么?', '/uploads/photo.jpg')
echo $res->answer
与 OpenAI、Claude、Gemini 和 Grok 兼容。
16.6: 嵌入
默认模型是特定于提供者的。对于 OpenAI,它是 text-embedding-3-small。
16.7: 转录
$file = %files->save(%payload->file('audio'))
$res = %AI->transcribe($file, model: 'whisper-1', language: 'nl')
echo $res->text16.8: 安全
- AI 调用成本高且不可预测。要积极缓存,使用
%apcu进行会话范围缓存,使用JSONDB进行更长的 TTL。 - 在将用户输入放入提示之前进行过滤。Phlo 的
esc()用于 HTML;对于提示,请使用您自己的清理工具或严格的工具架构。 - 记录提示/答案可能会有隐私影响。默认情况下,Phlo 不会记录;您的
data/errors.json仅记录异常。