$vector The query embedding vector * @param string $collection The collection name to search in (default: documents) * @param int $limit Maximum number of results to return (default: 5) * * @return array}> Array of search results with scores and payloads * * @throws RuntimeException If the API request fails or returns invalid data * * @example * $service = new QdrantService(); * $vector = [0.123, -0.456, 0.789, ...]; // 1024-dimensional vector * $results = $service->search($vector, 'documents', 5); * // Returns: [ * // ['id' => 1, 'score' => 0.89, 'payload' => ['content' => '...', 'title' => '...']], * // ['id' => 2, 'score' => 0.76, 'payload' => ['content' => '...', 'title' => '...']] * // ] */ public function search(array $vector, string $collection = 'documents', int $limit = 5): array { $url = sprintf('%s/collections/%s/points/search', $this->host, urlencode($collection)); $payload = [ 'vector' => array_values($vector), 'limit' => $limit, 'with_payload' => true, ]; $response = $this->makeRequest($url, $payload, self::DEFAULT_TIMEOUT); if (!isset($response['result']) || !is_array($response['result'])) { throw new RuntimeException('Invalid search response from Qdrant API'); } return array_map( static function (mixed $item): array { if (!is_array($item)) { throw new RuntimeException('Invalid search result item format'); } return [ 'id' => $item['id'] ?? throw new RuntimeException('Missing id in search result'), 'score' => (float) ($item['score'] ?? throw new RuntimeException('Missing score in search result')), 'payload' => is_array($item['payload'] ?? null) ? $item['payload'] : [], ]; }, $response['result'] ); } /** * Checks if a collection exists in Qdrant. * * Verifies the existence of a specific collection by attempting to retrieve * its information. This is useful before performing searches or operations. * * @param string $collection The collection name to check * * @return bool True if the collection exists, false otherwise * * @example * $service = new QdrantService(); * if ($service->collectionExists('documents')) { * echo "Collection exists"; * } else { * echo "Collection not found"; * } */ public function collectionExists(string $collection): bool { $url = sprintf('%s/collections/%s', $this->host, urlencode($collection)); try { $ch = curl_init($url); if ($ch === false) { return false; } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_CUSTOMREQUEST => 'GET', ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $result !== false && $httpCode === 200; } catch (\Throwable) { return false; } } /** * Checks if the Qdrant API is available and responding. * * Performs a health check by querying the Qdrant root endpoint. * This is a quick operation with a short timeout. * * @return bool True if the API is available, false otherwise * * @example * $service = new QdrantService(); * if ($service->isAvailable()) { * echo "Qdrant is running"; * } else { * echo "Qdrant is not available"; * } */ public function isAvailable(): bool { $url = $this->host . '/'; try { $ch = curl_init($url); if ($ch === false) { return false; } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $result !== false && $httpCode === 200; } catch (\Throwable) { return false; } } /** * Retrieves metadata and information about a collection. * * Fetches detailed information about a specific collection including * vector size, distance metric, number of points, and other metadata. * * @param string $collection The collection name to query * * @return array|null The collection information array, or null if collection does not exist * * @throws RuntimeException If the API request fails (excluding 404) * * @example * $service = new QdrantService(); * $info = $service->getCollectionInfo('documents'); * // Returns: [ * // 'status' => 'green', * // 'vectors_count' => 1234, * // 'points_count' => 1234, * // 'config' => ['params' => ['vectors' => ['size' => 1024, 'distance' => 'Cosine']]] * // ] */ public function getCollectionInfo(string $collection): ?array { $url = sprintf('%s/collections/%s', $this->host, urlencode($collection)); try { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::DEFAULT_TIMEOUT, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_CUSTOMREQUEST => 'GET', ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($result === false) { throw new RuntimeException( sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error') ); } if ($httpCode === 404) { return null; } if ($httpCode !== 200) { throw new RuntimeException( sprintf('Qdrant API returned HTTP %d: %s', $httpCode, $result) ); } $decoded = json_decode((string) $result, true); if (!is_array($decoded)) { throw new RuntimeException('Failed to decode JSON response from Qdrant API'); } return is_array($decoded['result'] ?? null) ? $decoded['result'] : null; } catch (RuntimeException $e) { throw $e; } catch (\Throwable) { return null; } } /** * Makes an HTTP POST request to the Qdrant API. * * Internal helper method that handles cURL initialization, request execution, * error handling, and response parsing for POST requests. * * @param string $url The API endpoint URL * @param array $payload The JSON payload to send * @param int $timeout The request timeout in seconds * * @return array The decoded JSON response * * @throws RuntimeException If the request fails or returns an error */ private function makeRequest(string $url, array $payload, int $timeout): array { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } $jsonPayload = json_encode($payload); if ($jsonPayload === false) { curl_close($ch); throw new RuntimeException('Failed to encode JSON payload'); } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $jsonPayload, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonPayload), ], ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($result === false) { throw new RuntimeException( sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error') ); } if ($httpCode !== 200) { throw new RuntimeException( sprintf('Qdrant API returned HTTP %d: %s', $httpCode, $result) ); } $decoded = json_decode((string) $result, true); if (!is_array($decoded)) { throw new RuntimeException('Failed to decode JSON response from Qdrant API'); } return $decoded; } }