baseUrl() !== '' && $this->apiKey() !== ''; } public function upsertByUrl(string $imageUrl, int|string $id, array $metadata = []): array { $response = $this->postJson( $this->url((string) config('vision.vector_gateway.upsert_endpoint', '/vectors/upsert')), [ 'url' => $imageUrl, 'id' => (string) $id, 'metadata' => $metadata, ] ); if ($response->failed()) { throw new RuntimeException($this->failureMessage('Vector upsert', $response)); } $json = $response->json(); return is_array($json) ? $json : []; } /** * @return list}> */ public function searchByUrl(string $imageUrl, int $limit = 5): array { $response = $this->postJson( $this->url((string) config('vision.vector_gateway.search_endpoint', '/vectors/search')), [ 'url' => $imageUrl, 'limit' => max(1, $limit), ] ); if ($response->failed()) { throw new RuntimeException($this->failureMessage('Vector search', $response)); } return $this->extractMatches($response->json()); } public function deleteByIds(array $ids): array { $response = $this->postJson( $this->url((string) config('vision.vector_gateway.delete_endpoint', '/vectors/delete')), [ 'ids' => array_values(array_map(static fn (int|string $id): string => (string) $id, $ids)), ] ); if ($response->failed()) { throw new RuntimeException($this->failureMessage('Vector delete', $response)); } $json = $response->json(); return is_array($json) ? $json : []; } private function request(): PendingRequest { if (! $this->isConfigured()) { throw new RuntimeException('Vision vector gateway is not configured. Set VISION_VECTOR_GATEWAY_URL and VISION_VECTOR_GATEWAY_API_KEY.'); } return Http::acceptJson() ->withHeaders([ 'X-API-Key' => $this->apiKey(), ]) ->connectTimeout(max(1, (int) config('vision.vector_gateway.connect_timeout_seconds', 5))) ->timeout(max(1, (int) config('vision.vector_gateway.timeout_seconds', 20))) ->retry( max(0, (int) config('vision.vector_gateway.retries', 1)), max(0, (int) config('vision.vector_gateway.retry_delay_ms', 250)), throw: false, ); } /** * @param array $payload */ private function postJson(string $url, array $payload): Response { $response = $this->request()->post($url, $payload); if (! $response instanceof Response) { throw new RuntimeException('Vector gateway request did not return an HTTP response.'); } return $response; } private function baseUrl(): string { return rtrim((string) config('vision.vector_gateway.base_url', ''), '/'); } private function apiKey(): string { return trim((string) config('vision.vector_gateway.api_key', '')); } private function url(string $path): string { return $this->baseUrl() . '/' . ltrim($path, '/'); } private function failureMessage(string $operation, Response $response): string { $body = trim($response->body()); if ($body === '') { return $operation . ' failed with HTTP ' . $response->status() . '.'; } return $operation . ' failed with HTTP ' . $response->status() . ': ' . $body; } /** * @param mixed $json * @return list}> */ private function extractMatches(mixed $json): array { $candidates = []; if (is_array($json)) { $candidates = $this->extractCandidateRows($json); } $results = []; foreach ($candidates as $candidate) { if (! is_array($candidate)) { continue; } $id = $candidate['id'] ?? $candidate['point_id'] ?? $candidate['payload']['id'] ?? $candidate['metadata']['id'] ?? null; if (! is_int($id) && ! is_string($id)) { continue; } $score = $candidate['score'] ?? $candidate['similarity'] ?? $candidate['distance'] ?? 0.0; $metadata = $candidate['metadata'] ?? $candidate['payload'] ?? []; if (! is_array($metadata)) { $metadata = []; } $results[] = [ 'id' => $id, 'score' => (float) $score, 'metadata' => $metadata, ]; } return $results; } /** * @param array $json * @return array */ private function extractCandidateRows(array $json): array { $keys = ['results', 'matches', 'points', 'data']; foreach ($keys as $key) { if (! isset($json[$key]) || ! is_array($json[$key])) { continue; } $value = $json[$key]; if (array_is_list($value)) { return $value; } foreach (['results', 'matches', 'points', 'items'] as $nestedKey) { if (isset($value[$nestedKey]) && is_array($value[$nestedKey]) && array_is_list($value[$nestedKey])) { return $value[$nestedKey]; } } } return array_is_list($json) ? $json : []; } }