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 modelgpt-5.4-mini,路由到 OpenAI),因此只在 creds.ini 中有 OpenAI 密钥的应用程序可以开箱即用。要更改应用程序的默认值,请在 app.phlo 中:

prop %AI.model = 'claude-opus-4-8'

这是 Phlo 的资源配置模式:在 %<resource> 前缀一个 propstatic,转译器会将其注入到该资源中,无需分叉(请参见高级,“在不分叉的情况下修改资源”)。该模型还选择了默认引擎,因此这一行将整个应用指向 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->text

18.8: 安全

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