Files
nl-admin-api/app/Service/common/ai/SparkAiAgent.php
2026-08-10 15:51:00 +08:00

101 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
}
}