AI

object

%AI

/phlo/resources/AI/AI.phlo
version 1.0
creator q-ai.nl
summary Unified AI facade with engine auto-detect
package ai
frontend false
backend true
requires @OpenAI? @Claude? @Gemini? @DeepSeek?
tags ai facade llm streaming tools embeddings
const

AI :: engines

line 10
AI::engines 是一个将 AI 引擎标识符映射到其各自名称的结构,便于在 Phlo 环境中进行引用和集成。
['claude' => 'Claude', 'gpt' => 'OpenAI', 'chatgpt' => 'OpenAI', 'o1' => 'OpenAI', 'o3' => 'OpenAI', 'o4' => 'OpenAI', 'deepseek' => 'DeepSeek', 'gemini' => 'Gemini']
static

AI :: sseDecodeEvents (string &$buffer, string $chunk):array

line 11
从缓冲区解码服务器发送的事件(SSE),提取以'data:'开头的数据行,并将其作为解码的JSON对象数组返回。
$buffer .= strtr($chunk, ["\r\n" => lf, "\r" => lf])
$events = []
while (($pos = strpos($buffer, lf.lf)) !== false){
	$raw = substr($buffer, 0, $pos)
	$buffer = substr($buffer, $pos + 2)
	$dataLines = []
	foreach (explode(lf, $raw) AS $line){
		$line = ltrim($line)
		if (!str_starts_with($line, 'data:')) continue
		$dataLines[] = ltrim(substr($line, 5))
	}
	if (!$dataLines) continue
	$payload = trim(implode(lf, $dataLines))
	if ($payload === '[DONE]' || $payload === void) continue
	$decoded = json_decode($payload)
	$decoded && $events[] = $decoded
}
return $events
static

AI :: sseStreamJSONWithTool (string $url, array $headers, array $payload, \Closure $extractText, \Closure $extractTool, ?\Closure $cb = null, ?\Closure $toolCb = null):obj

line 31
从服务器发送事件(SSE)端点流式传输JSON数据,处理传入事件并根据提供的回调提取文本和工具信息。
$req = %req
if (!$cb){
	$req->cli || %res->header('Content-Type', 'text/event-stream')
	$cb = fn($text) => last(print($text), $req->cli || [@ob_flush(), flush()], $text)
}
$answer = void
$toolJson = void
$buffer = void
$curl = curl_init($url)
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST')
curl_setopt($curl, CURLOPT_POSTFIELDS, $json)
curl_setopt($curl, CURLOPT_HTTPHEADER, [...$headers, 'Content-Type: application/json', 'Content-Length: '.strlen($json)])
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5)
curl_setopt($curl, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($extractText, $extractTool, $cb, $toolCb, &$buffer, &$answer, &$toolJson){
	foreach (static::sseDecodeEvents($buffer, $data) AS $event){
		$text = $extractText($event)
		(is_null($text) || $text === void || $text === false) || $answer .= $cb((string)$text, $event)
		$tool = $extractTool($event)
		if (!(is_null($tool) || $tool === void || $tool === false)){
			$toolJson .= (string)$tool
			if ($toolCb) $toolCb((string)$tool)
		}
	}
	return strlen($data)
})
$res = curl_exec($curl)
$res === false && error('HTTP error: '.curl_error($curl))
return new obj(answer: $answer, tool_json: $toolJson)
static

AI :: http (string $url, array $headers, bool $json = true, mixed $post = null):string

line 62
此函数初始化一个cURL会话以发送HTTP请求,允许使用GET和POST方法,并可选地对请求体进行JSON编码和自定义头部。
$curl = curl_init($url)
if ($post !== null){
	curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST')
	if ($json && !is_string($post)) $post = json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
	if ($json) $headers[] = 'Content-Type: application/json'
	curl_setopt($curl, CURLOPT_POSTFIELDS, $post)
}
curl_setopt_array($curl, [CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 300, CURLOPT_ENCODING => ''])
$res = curl_exec($curl)
if ($res === false) throw new \RuntimeException('HTTP error: '.curl_error($curl))
return $res
static

AI :: resolve (...$args):array

line 75
根据提供的参数解析要使用的AI引擎,如果没有指定特定引擎,则默认为'OpenAI'。
$via = $args['via'] ?? void
unset($args['via'])
$args['model'] ??= %app->model ?? void
if ($via) $via = static::engines[strtolower($via)] ?? $via
elseif (isset($args['model'])) $via = static::engines[strtolower(explode('-', $args['model'])[0])] ?? void
return [$via ?: 'OpenAI', $args]
method

%AI -> chat (...$args)

line 83
此函数初始化与AI引擎的聊天会话,在返回聊天实例之前解析引擎和参数。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->chat(...$args)
method

%AI -> stream (...$args):Generator

line 87
此函数解析指定的引擎和参数,然后使用Phlo框架启动流处理。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->stream(...$args)
method

%AI -> stream_with_tool (...$args):obj

line 91
此函数解析引擎和参数,然后使用Phlo中指定的工具流式传输数据。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->stream_with_tool(...$args)
method

%AI -> embedding (...$args)

line 95
此函数解析引擎和参数,然后使用提供的参数调用指定引擎的embedding方法。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->embedding(...$args)
method

%AI -> vision (...$args)

line 99
此函数将指定的参数解析为引擎,然后使用这些参数调用解析后的引擎上的 vision 方法。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->vision(...$args)
method

%AI -> transcribe (...$args)

line 103
使用指定的AI引擎和参数转录音频输入。
[$engine, $args] = static::resolve(...$args)
return phlo($engine)->transcribe(...$args)
object

%Claude

/phlo/resources/AI/Claude.phlo
version 1.1
creator q-ai.nl
summary Anthropic Claude API
package ai
frontend false
backend true
requires creds:Claude @AI
tags ai claude anthropic chat vision embeddings
const

Claude :: model

line 10
为指定资源创建模型实例,允许与底层数据结构进行交互。
'claude-sonnet-4-5-20250929'
static

Claude :: context (...$args)

line 12
通过设置消息和系统参数来初始化Claude的上下文,确保用户和助手消息被正确格式化和存储。
$args['messages'] ??= []
if (isset($args['system'])){
	$args['system'] = [['type' => 'text', 'text' => $args['system']]]
}
if (isset($args['assistant']) && array_push($args['messages'], ['role' => 'assistant', 'content' => $args['assistant']])) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], ['role' => 'user', 'content' => $args['user']])) unset($args['user'])
return $args
static

Claude :: tool ($tool):array

line 22
这构建了一个关联数组,包含工具的名称和描述,以及其输入模式,定义了工具的属性和必需参数。
[
	'name' => $tool->name,
	'description' => $tool->desc,
	'input_schema' => [
		'type' => 'object',
		'properties' => loop($tool->args, fn($data, $arg) => array_filter($data, fn($key) => in_array($key, ['type', 'enum', 'description']), ARRAY_FILTER_USE_KEY)),
		'required' => array_keys($tool->args),
	],
]
method

%Claude -> embedding ($input, $model = 'text-embedding-3-small')

line 32
使用指定的模型为给定输入生成嵌入,默认为'text-embedding-3-small'。
%OpenAI->embedding($input, $model)
method

%Claude -> vision ($text, $image, $stream = false, ...$args)

line 34
此函数将文本和图像发送到视觉模型,选项是流式传输响应或以聊天格式返回。
$data = is_string($image) && str_starts_with($image, 'http') ? file_get_contents($image) : $image
$messages = [['role' => 'user', 'content' => [['type' => 'text', 'text' => $text], ['type' => 'image', 'source' => ['type' => 'base64', 'media_type' => 'image/jpeg', 'data' => base64_encode($data)]]]]]
if ($stream) return $this->stream(...$args, messages: $messages)
else return $this->chat(...$args, messages: $messages)
method

%Claude -> chat (...$args)

line 41
此函数与Claude API交互以发送聊天请求并处理响应,返回一个包含答案、模型信息、令牌使用情况和交互过程中使用的任何工具的对象。
$args['model'] ??= static::model
$args['max_tokens'] ??= 4096
$token = $args['token'] ?? null
unset($args['token'])
$args = static::context(...$args)
$res = $this->request('messages', token: $token, POST: $args)
$return = new obj(answer: void, model: $res->model, finish: $res->stop_reason, tokens: ($res->usage->input_tokens ?? 0) + ($res->usage->output_tokens ?? 0), tokens_in: $res->usage->input_tokens ?? 0, tokens_out: $res->usage->output_tokens ?? 0)
$tools = []
foreach ($res->content AS $block){
	if ($block->type === 'text') $return->answer = $block->text
	elseif ($block->type === 'tool_use') $tools[] = new obj(name: $block->name, args: (array)$block->input)
}
if ($tools) $return->tools = $tools
return $return
method

%Claude -> parseSSE (string $url, array $headers, array $payload):Generator

line 58
建立与指定URL的服务器推送事件(SSE)连接,发送JSON有效负载和头信息,并从流中生成接收到的数据,适当地处理错误和连接状态。
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
$ctx = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", [...$headers, 'Content-Type: application/json', 'Content-Length: '.strlen($json)]), 'content' => $json, 'timeout' => 300, 'ignore_errors' => true]])
$stream = fopen($url, 'r', false, $ctx)
$stream || error('SSE connection failed')
$status = (int)explode(' ', $http_response_header[0] ?? 'HTTP/1.1 200')[1]
if ($status >= 400){
	$err = json_decode((string)stream_get_contents($stream))
	fclose($stream)
	error('Claude error '.$status.': '.($err->error->message ?? 'unknown'))
}
$finish = null
$usage = null
while (!feof($stream)){
	$line = fgets($stream)
	if ($line === false) break
	$line = rtrim($line, "\r\n")
	if (!str_starts_with($line, 'data:')) continue
	$data = ltrim(substr($line, 5))
	if ($data === void) continue
	$p = json_decode($data)
	if (!$p) continue
	if (isset($p->error)) error('Claude stream error: '.$p->error->message)
	if (($p->type ?? null) === 'message_delta'){
		$finish = $p->delta->stop_reason ?? $finish
		if (isset($p->usage)) $usage = $p->usage
	}
	if (($p->type ?? null) === 'content_block_delta' && ($p->delta->type ?? null) === 'text_delta' && isset($p->delta->text)) yield obj(text: $p->delta->text)
}
fclose($stream)
yield obj(done: true, finish: $finish, tokens_in: $usage?->input_tokens, tokens_out: $usage?->output_tokens)
method

%Claude -> stream (...$args):Generator

line 90
使用指定的参数从Claude API流式传输消息,包括模型和令牌,同时设置必要的身份验证头。
%app->streaming = true
$args['model'] ??= static::model
$args['max_tokens'] ??= 4096
$token = $args['token'] ?? null
unset($args['token'], $args['cb'])
$args = static::context(...$args)
$args['stream'] = true
if ($token) $headers = ['anthropic-version: 2023-06-01', 'anthropic-beta: oauth-2025-04-20', 'Authorization: Bearer '.$token]
else $headers = ['anthropic-version: 2023-06-01', 'x-api-key: '.%creds->Claude]
return $this->parseSSE('https://api.anthropic.com/v1/messages', $headers, $args)
method

%Claude -> request ($uri, ...$args)

line 103
使用指定的URI和参数向Claude API发送请求,处理使用令牌或API密钥的身份验证,并返回解码的JSON响应。
$token = $args['token'] ?? null
if ($token) $headers = ['anthropic-version: 2023-06-01', 'anthropic-beta: oauth-2025-04-20', 'Authorization: Bearer '.$token]
else $headers = ['anthropic-version: 2023-06-01', 'x-api-key: '.%creds->Claude]
$res = json_decode(AI::http("https://api.anthropic.com/v1/$uri", $headers, true, $args['POST'] ?? null))
if (isset($res->error)) error('Claude Request error: '.$res->error->message)
return $res
object

%DeepSeek

/phlo/resources/AI/DeepSeek.phlo
version 1.1
creator q-ai.nl
summary DeepSeek API
package ai
frontend false
backend true
requires creds:DeepSeek @AI
tags ai deepseek chat embeddings
const

DeepSeek :: model

line 10
DeepSeek::model 是一个组件,便于在 DeepSeek 框架内创建和管理模型,从而实现高效的数据处理和交互。
'deepseek-chat'
static

DeepSeek :: context (...$args)

line 12
DeepSeek::$context 初始化 'messages' 数组,并根据提供的参数填充系统、助手和用户消息。
$args['messages'] ??= []
if (isset($args['system']) && array_unshift($args['messages'], ['role' => 'system', 'content' => $args['system']])) unset($args['system'])
if (isset($args['assistant']) && array_push($args['messages'], ['role' => 'assistant', 'content' => $args['assistant']])) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], ['role' => 'user', 'content' => $args['user']])) unset($args['user'])
return $args
static

DeepSeek :: tool ($tool):array

line 20
在DeepSeek中定义一个工具,包括其名称、描述和参数,强制对提供的参数进行严格验证。
[
	'type' => 'function',
	'function' => [
		'name' => $tool->name,
		'description' => $tool->desc,
		'parameters' => [
			'type' => 'object',
			'properties' => loop($tool->args, fn($data, $arg) => array_filter($data, fn($key) => in_array($key, ['type', 'enum', 'desc']), ARRAY_FILTER_USE_KEY)),
			'additionalProperties' => false,
			'required' => array_keys($tool->args),
		],
		'strict' => true,
	],
]
method

%DeepSeek -> embedding ($input, $model = 'text-embedding-3-small')

line 35
使用指定的模型为给定输入生成嵌入,默认模型为'text-embedding-3-small'。
%OpenAI->embedding($input, $model)
method

%DeepSeek -> chat (...$args)

line 37
此函数向模型发送聊天完成请求,并返回一个包含响应的对象,包括答案、使用的模型、完成原因和令牌使用详情。
$args['model'] ??= static::model
$args = static::context(...$args)
$res = $this->request('chat/completions', POST: $args)
$return = new obj(answer: $res->choices[0]->message->content, model: $res->model, finish: $res->choices[0]->finish_reason, tokens: $res->usage->total_tokens, tokens_in: $res->usage->prompt_tokens, tokens_out: $res->usage->completion_tokens)
if (isset($res->choices[0]->message->tool_calls)) $return->tools = array_map(fn($tool) => new obj(name: $tool->function->name, args: json_decode($tool->function->arguments, true)), (array)$res->choices[0]->message->tool_calls)
return $return
method

%DeepSeek -> parseSSE (string $url, array $headers, array $payload):Generator

line 46
建立与指定URL的服务器推送事件(SSE)连接,发送JSON负载和头部,并在完成之前逐步输出流中的解析数据。
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
$ctx = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", [...$headers, 'Content-Type: application/json', 'Content-Length: '.strlen($json)]), 'content' => $json, 'timeout' => 300, 'ignore_errors' => true]])
$stream = fopen($url, 'r', false, $ctx)
$stream || error('SSE connection failed')
$status = (int)explode(' ', $http_response_header[0] ?? 'HTTP/1.1 200')[1]
if ($status >= 400){
	$err = json_decode((string)stream_get_contents($stream))
	fclose($stream)
	error('DeepSeek error '.$status.': '.($err->error->message ?? 'unknown'))
}
$finish = null
$usage = null
while (!feof($stream)){
	$line = fgets($stream)
	if ($line === false) break
	$line = rtrim($line, "\r\n")
	if (!str_starts_with($line, 'data:')) continue
	$data = ltrim(substr($line, 5))
	if ($data === '[DONE]' || $data === void) continue
	$p = json_decode($data)
	if (!$p) continue
	if (isset($p->error)) error('DeepSeek stream error: '.$p->error->message)
	if (isset($p->usage)) $usage = $p->usage
	$text = $p->choices[0]->delta->content ?? null
	if (!is_null($text)) yield obj(text: $text)
	if ($p->choices[0]->finish_reason ?? null) $finish = $p->choices[0]->finish_reason
}
fclose($stream)
yield obj(done: true, finish: $finish, tokens_in: $usage?->prompt_tokens, tokens_out: $usage?->completion_tokens)
method

%DeepSeek -> stream (...$args):Generator

line 77
通过设置适当的参数并返回解析的服务器发送事件响应,启用DeepSeek API的流式传输。
%app->streaming = true
$args['model'] ??= static::model
unset($args['cb'])
$args = static::context(...$args)
$args['stream'] = true
return $this->parseSSE('https://api.deepseek.com/v1/chat/completions', ['Authorization: Bearer '.%creds->DeepSeek], $args)
method

%DeepSeek -> request ($uri, $JSON = true, ...$args)

line 86
使用指定的URI和可选参数向DeepSeek API发送请求,返回解码的JSON响应。如果发生错误,它会触发一个包含响应详细信息的错误消息。
$res = json_decode(AI::http("https://api.deepseek.com/v1/$uri", ['Authorization: Bearer '.%creds->DeepSeek], $JSON, $args['POST'] ?? null))
if (isset($res->error)) error('DeepSeek Request error: '.$res->error->message)
return $res
object

%Gemini

/phlo/resources/AI/Gemini.phlo
version 1.1
creator q-ai.nl
summary Google Gemini API
package ai
frontend false
backend true
requires creds:Gemini @AI
tags ai gemini google chat vision embeddings
const

Gemini :: model

line 10
在Gemini框架中创建模型,允许定义和操作数据结构。
'gemini-2.0-flash'
const

Gemini :: endpoint

line 11
定义一个用于访问Gemini API的端点,允许与给定URL处的指定模型进行交互。
'https://generativelanguage.googleapis.com/v1beta/models/'
static

Gemini :: context (...$args)

line 13
Gemini::$context 处理输入参数以构建对话的结构化上下文,将系统、助手和用户消息组织到内容数组中。
$args['contents'] ??= []
if (isset($args['system'])){
	$args['systemInstruction'] = ['parts' => [['text' => $args['system']]]]
	unset($args['system'])
}
if (isset($args['assistant']) && array_push($args['contents'], ['role' => 'model', 'parts' => [['text' => $args['assistant']]]])) unset($args['assistant'])
if (isset($args['user']) && array_push($args['contents'], ['role' => 'user', 'parts' => [['text' => $args['user']]]])) unset($args['user'])
return $args
static

Gemini :: tool ($tool):array

line 24
Gemini::$tool 获取工具的名称和描述,以及其参数,包括类型、属性和必需的参数。
[
	'name' => $tool->name,
	'description' => $tool->desc,
	'parameters' => [
		'type' => 'object',
		'properties' => loop($tool->args, fn($data, $arg) => array_filter($data, fn($key) => in_array($key, ['type', 'enum', 'description']), ARRAY_FILTER_USE_KEY)),
		'required' => array_keys($tool->args),
	],
]
method

%Gemini -> embedding ($input, $model = 'text-embedding-004')

line 34
使用指定的模型为给定的输入文本生成嵌入,默认为'text-embedding-004'。
$this->request($model.':embedContent', POST: ['content' => ['parts' => [['text' => $input]]]])->embedding->values
method

%Gemini -> vision ($text, $image, $stream = false, ...$args)

line 36
通过结合文本和图像生成视觉响应,并提供可选的实时输出流功能。
$model = $args['model'] ?? static::model
unset($args['model'])
$contents = [['role' => 'user', 'parts' => [['text' => $text], ['inline_data' => ['mime_type' => 'image/jpeg', 'data' => base64_encode(is_string($image) && str_starts_with($image, 'http') ? file_get_contents($image) : $image)]]]]]
if ($stream) return $this->stream(...$args, model: $model, contents: $contents)
else return $this->chat(...$args, model: $model, contents: $contents)
method

%Gemini -> chat (...$args)

line 44
根据指定的模型和参数生成内容,返回一个对象,其中包括生成的答案、使用的模型、结束原因和令牌使用详情。
$model = $args['model'] ?? static::model
unset($args['model'])
$args = static::context(...$args)
$res = $this->request($model.':generateContent', POST: $args)
$return = new obj(answer: void, model: $model, finish: $res->candidates[0]->finishReason ?? void, tokens: ($res->usageMetadata->promptTokenCount ?? 0) + ($res->usageMetadata->candidatesTokenCount ?? 0), tokens_in: $res->usageMetadata->promptTokenCount ?? 0, tokens_out: $res->usageMetadata->candidatesTokenCount ?? 0)
$tools = []
foreach ($res->candidates[0]->content->parts ?? [] AS $part){
	if (isset($part->text)) $return->answer = $part->text
	elseif (isset($part->functionCall)) $tools[] = new obj(name: $part->functionCall->name, args: (array)$part->functionCall->args)
}
if ($tools) $return->tools = $tools
return $return
method

%Gemini -> parseSSE (string $url, array $headers, array $payload):Generator

line 59
建立与指定URL的服务器推送事件(SSE)连接,发送JSON负载和头部,并生成从流中接收到的数据。
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
$ctx = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", [...$headers, 'Content-Type: application/json', 'Content-Length: '.strlen($json)]), 'content' => $json, 'timeout' => 300, 'ignore_errors' => true]])
$stream = fopen($url, 'r', false, $ctx)
$stream || error('SSE connection failed')
$status = (int)explode(' ', $http_response_header[0] ?? 'HTTP/1.1 200')[1]
if ($status >= 400){
	$err = json_decode((string)stream_get_contents($stream))
	fclose($stream)
	error('Gemini error '.$status.': '.($err->error->message ?? 'unknown'))
}
$finish = null
$usage = null
while (!feof($stream)){
	$line = fgets($stream)
	if ($line === false) break
	$line = rtrim($line, "\r\n")
	if (!str_starts_with($line, 'data:')) continue
	$data = ltrim(substr($line, 5))
	if ($data === void) continue
	$p = json_decode($data)
	if (!$p) continue
	if (isset($p->error)) error('Gemini stream error: '.$p->error->message)
	if (isset($p->usageMetadata)) $usage = $p->usageMetadata
	if ($p->candidates[0]->finishReason ?? null) $finish = $p->candidates[0]->finishReason
	$text = $p->candidates[0]->content->parts[0]->text ?? null
	if (!is_null($text)) yield obj(text: $text)
}
fclose($stream)
yield obj(done: true, finish: $finish, tokens_in: $usage?->promptTokenCount, tokens_out: $usage?->candidatesTokenCount)
method

%Gemini -> stream (...$args):Generator

line 90
通过服务器推送事件(SSE)从指定模型流式传输实时数据。它通过设置流上下文并包含必要的凭据来准备请求。
%app->streaming = true
$model = $args['model'] ?? static::model
unset($args['model'], $args['cb'])
$args = static::context(...$args)
return $this->parseSSE(static::endpoint.$model.':streamGenerateContent?alt=sse', ['x-goog-api-key: '.%creds->Gemini], $args)
method

%Gemini -> request ($path, ...$args)

line 98
向指定路径的Gemini API发送请求,带有可选参数,处理错误并返回解码的JSON响应。
$res = json_decode(AI::http(static::endpoint.$path, ['x-goog-api-key: '.%creds->Gemini], true, $args['POST'] ?? null))
if (isset($res->error)) error('Gemini Request error: '.$res->error->message)
return $res
object

%OpenAI

/phlo/resources/AI/OpenAI.phlo
version 1.0
creator q-ai.nl
summary Basic OpenAI functions
package ai
frontend false
backend true
requires creds:OpenAI @AI
tags ai openai llm chat embeddings audio vision
const

OpenAI :: model

line 10
此函数使用指定的标识符初始化OpenAI模型,在这种情况下为'gpt-4o-mini'。
'gpt-4o-mini'
const

OpenAI :: voices

line 11
OpenAI::voices 是可用于 Phlo 应用程序的预定义语音选项列表,包括 'alloy'、'echo'、'fable'、'onyx'、'nova' 和 'shimmer'。
['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']
static

OpenAI :: context (...$args):array

line 12
此函数在OpenAI的上下文中初始化'messages'数组,并根据输入参数添加系统、助手和用户消息。
$args['messages'] ??= []
if (isset($args['system']) && array_unshift($args['messages'], ['role' => 'system', 'content' => $args['system']])) unset($args['system'])
if (isset($args['assistant']) && array_push($args['messages'], ['role' => 'assistant', 'content' => $args['assistant']])) unset($args['assistant'])
if (isset($args['user']) && array_push($args['messages'], ['role' => 'user', 'content' => $args['user']])) unset($args['user'])
return $args
static

OpenAI :: tool ($tool):array

line 19
在OpenAI中定义一个具有指定参数的函数,包括类型、枚举和描述,同时强制执行参数的严格验证。
[
	'type' => 'function',
	'function' => [
		'name' => $tool->name,
		'description' => $tool->desc,
		'parameters' => [
			'type' => 'object',
			'properties' => loop($tool->args, fn($data, $arg) => array_filter($data, fn($key) => in_array($key, ['type', 'enum', 'desc']), ARRAY_FILTER_USE_KEY)),
			'additionalProperties' => false,
			'required' => array_keys($tool->args),
		],
		'strict' => true,
	],
]
method

%OpenAI -> chat (...$args):obj

line 33
此函数与OpenAI API交互,根据提供的参数生成聊天完成,返回一个包含响应详细信息的对象,例如生成的答案、使用的模型和令牌使用统计信息。
$args['model'] ??= static::model
$token = $args['token'] ?? null
unset($args['token'])
$args = static::context(...$args)
$res = $this->request('chat/completions', token: $token, POST: $args)
$return = new obj(answer: $res->choices[0]->message->content, model: $res->model, finish: $res->choices[0]->finish_reason, tokens: $res->usage->total_tokens, tokens_in: $res->usage->prompt_tokens, tokens_out: $res->usage->completion_tokens)
if (isset($res->choices[0]->message->tool_calls)) $return->tools = array_map(fn($tool) => new obj(name: $tool->function->name, args: json_decode($tool->function->arguments, true)), (array)$res->choices[0]->message->tool_calls)
return $return
method

%OpenAI -> embedding ($input, $model = 'text-embedding-3-small')

line 43
使用OpenAI指定的模型为给定输入生成嵌入。
$this->request('embeddings', POST: ['input' => $input, 'model' => $model])->data[0]->embedding
method

%OpenAI -> parseSSE (string $url, array $headers, array $payload):Generator

line 44
建立与指定 URL 的服务器推送事件 (SSE) 连接,发送 JSON 有效负载和头信息,并在完成之前逐步返回从流中接收到的数据。
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
$ctx = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", [...$headers, 'Content-Type: application/json', 'Content-Length: '.strlen($json)]), 'content' => $json, 'timeout' => 300, 'ignore_errors' => true]])
$stream = fopen($url, 'r', false, $ctx)
$stream || error('SSE connection failed')
$status = (int)explode(' ', $http_response_header[0] ?? 'HTTP/1.1 200')[1]
if ($status >= 400){
	$err = json_decode((string)stream_get_contents($stream))
	fclose($stream)
	error('OpenAI error '.$status.': '.($err->error->message ?? 'unknown'))
}
$finish = null
$usage = null
while (!feof($stream)){
	$line = fgets($stream)
	if ($line === false) break
	$line = rtrim($line, "\r\n")
	if (!str_starts_with($line, 'data:')) continue
	$data = ltrim(substr($line, 5))
	if ($data === '[DONE]' || $data === void) continue
	$p = json_decode($data)
	if (!$p) continue
	if (isset($p->error)) error('OpenAI stream error: '.$p->error->message)
	if (isset($p->usage)) $usage = $p->usage
	$text = $p->choices[0]->delta->content ?? null
	if (!is_null($text)) yield obj(text: $text)
	if ($p->choices[0]->finish_reason ?? null) $finish = $p->choices[0]->finish_reason
}
fclose($stream)
yield obj(done: true, finish: $finish, tokens_in: $usage?->prompt_tokens, tokens_out: $usage?->completion_tokens)
method

%OpenAI -> stream (...$args):Generator

line 75
从OpenAI API流式传输聊天完成的响应,使用提供的模型和身份验证令牌。
$args['model'] ??= static::model
$token = $args['token'] ?? null
unset($args['token'], $args['cb'])
$args = static::context(...$args)
$args['stream'] = true
$bearer = $token ?? %creds->OpenAI
return $this->parseSSE('https://api.openai.com/v1/chat/completions', ['Authorization: Bearer '.$bearer], $args)
method

%OpenAI -> transcribe ($file, $model = 'whisper-1', ...$args):obj

line 84
使用指定模型对给定文件中的音频进行转录,返回持续时间、语言和转录文本等详细信息。
if (is_string($file)) $file = new CURLFile($file)
elseif (is_a($file, 'file')) $file = $file->curl
$res = $this->request('audio/transcriptions', false, POST: [...$args, 'model' => $model, 'file' => $file, 'response_format' => 'verbose_json'])
return obj (
	model: $model,
	duration: $res->duration,
	lang: $res->language,
	text: $res->text,
)
method

%OpenAI -> vision ($text, $image, $stream = false, ...$args):obj

line 95
此函数将文本和图像发送到OpenAI视觉模型,选项是流式传输响应或以聊天格式返回。
$args['model'] ??= static::model
$messages = [['role' => 'user', 'content' => [['type' => 'text', 'text' => $text], ['type' => 'image_url', 'image_url' => ['url' => $image]]]]]
if ($stream) return $this->stream(...$args, messages: $messages)
else return $this->chat(...$args, messages: $messages)
method

%OpenAI -> request ($uri, $JSON = true, $token = null, ...$args)

line 101
使用指定的URI和可选参数向OpenAI API发送请求,返回解码的JSON响应或请求失败时的错误消息。
$bearer = $token ?? %creds->OpenAI
$res = json_decode(AI::http("https://api.openai.com/v1/$uri", ['Authorization: Bearer '.$bearer], $JSON, $args['POST'] ?? null))
if (isset($res->error)) error('OpenAI Request error: '.$res->error->message)
return $res

Functions

function

answer($question, ...$options)

/phlo/resources/AI/answer.phlo line 10
生成对给定问题的简洁回答,或者通过从提供的选项中选择,或者通过形成直接的回应。输出严格限制为答案,没有额外的文本或标点符号。
	$prompt = 'You are an AI answer machine. '
	$prompt .= 'You give short, direct answers without repeating the subject. '
	$prompt .= 'You add no explanation, no extra text, and no quotation marks. '
	$prompt .= 'Always answer in the same language as the question.'.lf
	if ($options){
		$prompt .= 'The user asks a question. You choose exactly one of the options below as the answer. '
		$prompt .= 'Your answer matches exactly one of the options, with no extra words or punctuation. '
		$prompt .= 'If none of the options apply, answer with "-".'.lf.lf
		$prompt .= 'Question:'.lf.$question.lf.lf
		$prompt .= 'Options:'.lf
		$prompt .= implode(lf, $options)
	}
	else {
		$prompt .= 'Give a short and precise answer to the question. '
		$prompt .= 'No introduction, no explanation, no list, and no final period.'.lf.lf
		$prompt .= 'Question:'.lf.$question
	}
	$answer = %OpenAI->chat(user: $prompt, temperature: .1)->answer
	return $answer === dash ? null : $answer

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