Files

101 lines
3.6 KiB
PHP
Raw Permalink Normal View History

2026-08-10 15:51:00 +08:00
<?php
namespace App\Service\common\ai;
use App\BaseApp\BaseService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Throwable;
/**
* 讯飞星火 OpenAPI AgentOpenAI 兼容 /v1/chat/completions
* 每次调用从 AiRuntimeConfigService 取最新平台/密钥/模型
*/
class SparkAiAgent extends BaseService implements AiAgentInterface
{
public function __construct()
{
// Agent 本身不读用户态,避免工厂链路强制鉴权
$this->isAuth = false;
parent::__construct();
}
public function provider(): string
{
return 'spark';
}
/**
* 调用星火 chat completions
* Authorization 使用 Bearer + API Key与官方 OpenAPI 一致)
*/
public function chatCompletions(array $messages, array $options = []): AiChatResult
{
$cfg = AiRuntimeConfigService::getInstance()->resolve($this->provider());
$apiUrl = (string) ($cfg['api_url'] ?? '');
$password = (string) ($cfg['api_key'] ?? '');
$defaultModel = (string) ($cfg['model'] ?? 'lite');
$timeout = (float) ($cfg['timeout'] ?? 60);
if ($password === '') {
$this->utils->errorThrow('未配置讯飞星火 API 密钥请在「AI密钥管理」或 .env SPARK_API_PASSWORD 配置)');
}
if ($apiUrl === '') {
$this->utils->errorThrow('未配置讯飞星火接口地址');
}
if (empty($messages)) {
$this->utils->errorThrow('AI 对话消息不能为空');
}
$model = (string) ($options['model'] ?? $defaultModel);
$body = [
'model' => $model,
'messages' => array_values($messages),
];
// 可选参数透传,便于业务微调温度等
foreach (['temperature', 'max_tokens', 'top_p', 'stream'] as $key) {
if (array_key_exists($key, $options)) {
$body[$key] = $options[$key];
}
}
// 默认关闭流式,业务侧解析整包 JSON 更简单
if (!isset($body['stream'])) {
$body['stream'] = false;
}
try {
$client = new Client([
'timeout' => $timeout,
'connect_timeout' => min(10.0, $timeout),
]);
$response = $client->request('POST', $apiUrl, [
'headers' => [
'Authorization' => 'Bearer ' . $password,
'Content-Type' => 'application/json',
],
'json' => $body,
]);
$raw = json_decode((string) $response->getBody(), true);
if (!is_array($raw)) {
$this->utils->errorThrow('讯飞星火返回非 JSON');
}
$content = (string) data_get($raw, 'choices.0.message.content', '');
if ($content === '') {
$errMsg = (string) ($raw['error']['message'] ?? $raw['message'] ?? '');
$this->utils->errorThrow($errMsg !== '' ? ('讯飞星火调用失败:' . $errMsg) : '讯飞星火未返回有效内容');
}
$usage = is_array($raw['usage'] ?? null) ? $raw['usage'] : [];
return new AiChatResult(
$content,
$raw,
$this->provider(),
$model,
$usage,
(int) ($cfg['api_key_id'] ?? 0)
);
} catch (Throwable $e) {
if ($e instanceof GuzzleException) {
$this->utils->errorThrow('讯飞星火请求失败:' . $e->getMessage());
}
throw $e;
}
}
}