Backup #749

ID749
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Infrastructure/AI/QdrantService.php
Version6
Typ modified
Größe12.6 KB
Hash81f7fc7061ea27792e29bffa78c928fdedabac51d5455803c7df62df45874997
Datum2025-12-23 07:59:32
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

declare(strict_types=1);

namespace Infrastructure\AI;

// @responsibility: Qdrant-Vektor-DB für Similarity-Search

use Infrastructure\Config\CredentialService;
use RuntimeException;
 * - Retrieve collection metadata and information
 * - Perform health checks on the Qdrant service
 *
 * This service uses cURL for HTTP requests without external dependencies.
 * All methods include proper timeout handling and exception management.
 *
 * @package Infrastructure\AI
 * @author  System Generated
 * @version 1.0.0
 */
final class QdrantService
{
    /**
     * Default timeout for HTTP requests in seconds.
     */
    private const int DEFAULT_TIMEOUT = 30;

    /**
     * Health check timeout in seconds.
     */
    private const int HEALTH_CHECK_TIMEOUT = 5;

    private readonly string $host;

    /**
     * Constructs a new QdrantService instance.
     *
     * @param string|null $host The Qdrant API host URL (uses env QDRANT_HOST if null)
     */
    public function __construct(?string $host = null)
    {
        $this->host = $host ?? CredentialService::getQdrantHost();
    }

    /**
     * Searches for similar vectors in a Qdrant collection.
     *
     * Performs a vector similarity search using the provided embedding vector.
     * Returns the most similar points from the collection based on cosine distance.
     *
     * @param array<int, float> $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<int, array{id: int|string, score: float, payload: array<string, mixed>}> 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;
        }
    }

    /**
     * Lists all available collections in Qdrant.
     *
     * Retrieves a list of all collection names from the Qdrant database.
     * Returns an empty array if Qdrant is not available or on error.
     *
     * @return array<int, string> Array of collection names
     *
     * @example
     * $service = new QdrantService();
     * $collections = $service->listCollections();
     * // Returns: ['documents', 'mail', 'entities']
     */
    public function listCollections(): array
    {
        $url = $this->host . '/collections';

        try {
            $ch = curl_init($url);

            if ($ch === false) {
                return [];
            }

            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);

            if ($result === false || $httpCode !== 200) {
                return [];
            }

            $decoded = json_decode((string) $result, true);

            if (!is_array($decoded) || !isset($decoded['result']['collections'])) {
                return [];
            }

            $collections = [];
            foreach ($decoded['result']['collections'] as $collection) {
                if (is_array($collection) && isset($collection['name'])) {
                    $collections[] = (string) $collection['name'];
                }
            }

            sort($collections);

            return $collections;
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * 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<string, mixed>|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<string, mixed> $payload The JSON payload to send
     * @param int                  $timeout The request timeout in seconds
     *
     * @return array<string, mixed> 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;
    }
}

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
1958 13 modified 9.2 KB 2025-12-28 02:27
1957 12 modified 9.7 KB 2025-12-28 02:27
1956 11 modified 10.3 KB 2025-12-28 02:27
1955 10 modified 10.7 KB 2025-12-28 02:26
1954 9 modified 11.1 KB 2025-12-28 02:26
1953 8 modified 11.6 KB 2025-12-28 02:26
1952 7 modified 12.3 KB 2025-12-28 02:26
749 6 modified 12.6 KB 2025-12-23 07:59
745 5 modified 12.8 KB 2025-12-23 07:58
378 4 modified 12.7 KB 2025-12-22 08:25
377 3 modified 12.7 KB 2025-12-22 08:25
376 2 modified 12.7 KB 2025-12-22 08:25
64 1 modified 10.8 KB 2025-12-20 18:48

← Zurück zur Übersicht