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->text

16.8: 安全

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。