Files
sbnews/server/app/common/service/ai/EmbeddingClient.php
2026-06-11 12:15:29 +08:00

112 lines
3.7 KiB
PHP

<?php
namespace app\common\service\ai;
use app\common\model\ai\AiConfig;
class EmbeddingClient
{
private const DASHSCOPE_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode';
private const DASHSCOPE_EMBEDDING_MODEL = 'text-embedding-v4';
protected string $apiKey;
protected string $baseUrl;
protected string $model;
protected int $defaultDim;
public function __construct()
{
$this->apiKey = (string) (AiConfig::getVal('embedding_api_key', '') ?: env('DASHSCOPE_API_KEY', ''));
$this->baseUrl = (string) (AiConfig::getVal('embedding_base_url', '') ?: self::DASHSCOPE_BASE_URL);
$this->model = (string) (AiConfig::getVal('embedding_model', '') ?: self::DASHSCOPE_EMBEDDING_MODEL);
$this->defaultDim = (int) (AiConfig::getVal('embedding_dim', '') ?: '1024');
}
/**
* @param string|array<int, string> $input
*/
public function embed($input, array $options = []): array
{
$apiKey = $options['api_key'] ?? $this->apiKey;
$baseUrl = rtrim($options['base_url'] ?? $this->baseUrl, '/');
$model = $options['model'] ?? $this->model;
if ($apiKey === '' || $baseUrl === '' || $model === '') {
return [
'success' => false,
'error' => 'Embedding API配置未完成',
'embeddings' => [],
'cost_ms' => 0,
];
}
$payload = [
'model' => $model,
'input' => $input,
];
if (!empty($options['dimensions'])) {
$payload['dimensions'] = (int) $options['dimensions'];
} elseif ($this->defaultDim > 0) {
$payload['dimensions'] = $this->defaultDim;
}
$startTime = microtime(true);
$ch = curl_init($baseUrl . '/v1/embeddings');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
$costMs = (int) ((microtime(true) - $startTime) * 1000);
if ($error) {
return [
'success' => false,
'error' => 'Embedding cURL错误: ' . $error,
'embeddings' => [],
'cost_ms' => $costMs,
];
}
$data = json_decode((string) $response, true);
if ($httpCode !== 200 || empty($data['data']) || !is_array($data['data'])) {
return [
'success' => false,
'error' => $data['error']['message'] ?? ('HTTP ' . $httpCode),
'embeddings' => [],
'cost_ms' => $costMs,
];
}
$embeddings = [];
foreach ($data['data'] as $row) {
$embedding = $row['embedding'] ?? [];
if (is_array($embedding) && !empty($embedding)) {
$embeddings[] = array_map('floatval', $embedding);
}
}
return [
'success' => !empty($embeddings),
'error' => !empty($embeddings) ? '' : 'Embedding结果为空',
'embeddings' => $embeddings,
'model' => $model,
'dimension' => count($embeddings[0] ?? []),
'tokens' => (int) ($data['usage']['total_tokens'] ?? 0),
'cost_ms' => $costMs,
];
}
}