{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/AI\/QdrantService.php",
"oldString": " \/**\n * Constructs a new QdrantService instance.\n *\n * @param string|null $host The Qdrant API host URL (uses env QDRANT_HOST if null)\n *\/",
"newString": " \/** @param string|null $host Qdrant API host (uses env QDRANT_HOST if null) *\/",
"originalFile": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\AI;\n\n\/\/ @responsibility: Qdrant-Vektor-DB für Similarity-Search\n\nuse Infrastructure\\Config\\CredentialService;\nuse RuntimeException;\n\nfinal class QdrantService\n{\n \/**\n * Default timeout for HTTP requests in seconds.\n *\/\n private const int DEFAULT_TIMEOUT = 30;\n\n \/**\n * Health check timeout in seconds.\n *\/\n private const int HEALTH_CHECK_TIMEOUT = 5;\n\n private readonly string $host;\n\n \/**\n * Constructs a new QdrantService instance.\n *\n * @param string|null $host The Qdrant API host URL (uses env QDRANT_HOST if null)\n *\/\n public function __construct(?string $host = null)\n {\n $this->host = $host ?? CredentialService::getQdrantHost();\n }\n\n \/**\n * Searches for similar vectors in a Qdrant collection.\n *\n * @param array<int, float> $vector Query embedding vector\n * @param string $collection Collection to search (default: documents)\n * @param int $limit Max results (default: 5)\n * @return array<int, array{id: int|string, score: float, payload: array<string, mixed>}>\n * @throws RuntimeException If API request fails\n *\/\n public function search(array $vector, string $collection = 'documents', int $limit = 5): array\n {\n $url = sprintf('%s\/collections\/%s\/points\/search', $this->host, urlencode($collection));\n\n $payload = [\n 'vector' => array_values($vector),\n 'limit' => $limit,\n 'with_payload' => true,\n ];\n\n $response = $this->makeRequest($url, $payload, self::DEFAULT_TIMEOUT);\n\n if (!isset($response['result']) || !is_array($response['result'])) {\n throw new RuntimeException('Invalid search response from Qdrant API');\n }\n\n return array_map(\n static function (mixed $item): array {\n if (!is_array($item)) {\n throw new RuntimeException('Invalid search result item format');\n }\n\n return [\n 'id' => $item['id'] ?? throw new RuntimeException('Missing id in search result'),\n 'score' => (float) ($item['score'] ?? throw new RuntimeException('Missing score in search result')),\n 'payload' => is_array($item['payload'] ?? null) ? $item['payload'] : [],\n ];\n },\n $response['result']\n );\n }\n\n \/** Checks if a collection exists in Qdrant. *\/\n public function collectionExists(string $collection): bool\n {\n $url = sprintf('%s\/collections\/%s', $this->host, urlencode($collection));\n\n try {\n $ch = curl_init($url);\n\n if ($ch === false) {\n return false;\n }\n\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_CUSTOMREQUEST => 'GET',\n ]);\n\n $result = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n curl_close($ch);\n\n return $result !== false && $httpCode === 200;\n } catch (\\Throwable) {\n return false;\n }\n }\n\n \/** Lists all available collections. Returns empty array on error. *\/\n public function listCollections(): array\n {\n $url = $this->host . '\/collections';\n\n try {\n $ch = curl_init($url);\n\n if ($ch === false) {\n return [];\n }\n\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_CUSTOMREQUEST => 'GET',\n ]);\n\n $result = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n curl_close($ch);\n\n if ($result === false || $httpCode !== 200) {\n return [];\n }\n\n $decoded = json_decode((string) $result, true);\n\n if (!is_array($decoded) || !isset($decoded['result']['collections'])) {\n return [];\n }\n\n $collections = [];\n foreach ($decoded['result']['collections'] as $collection) {\n if (is_array($collection) && isset($collection['name'])) {\n $collections[] = (string) $collection['name'];\n }\n }\n\n sort($collections);\n\n return $collections;\n } catch (\\Throwable) {\n return [];\n }\n }\n\n \/** Checks if Qdrant API is available (health check). *\/\n public function isAvailable(): bool\n {\n $url = $this->host . '\/';\n\n try {\n $ch = curl_init($url);\n\n if ($ch === false) {\n return false;\n }\n\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n ]);\n\n $result = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n curl_close($ch);\n\n return $result !== false && $httpCode === 200;\n } catch (\\Throwable) {\n return false;\n }\n }\n\n \/**\n * Retrieves collection metadata (vector size, point count, etc).\n * @return array<string, mixed>|null Null if collection doesn't exist\n * @throws RuntimeException If API request fails (excluding 404)\n *\/\n public function getCollectionInfo(string $collection): ?array\n {\n $url = sprintf('%s\/collections\/%s', $this->host, urlencode($collection));\n\n try {\n $ch = curl_init($url);\n\n if ($ch === false) {\n throw new RuntimeException('Failed to initialize cURL');\n }\n\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => self::DEFAULT_TIMEOUT,\n CURLOPT_CONNECTTIMEOUT => 10,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_CUSTOMREQUEST => 'GET',\n ]);\n\n $result = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n $curlError = curl_error($ch);\n\n curl_close($ch);\n\n if ($result === false) {\n throw new RuntimeException(\n sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error')\n );\n }\n\n if ($httpCode === 404) {\n return null;\n }\n\n if ($httpCode !== 200) {\n throw new RuntimeException(\n sprintf('Qdrant API returned HTTP %d: %s', $httpCode, $result)\n );\n }\n\n $decoded = json_decode((string) $result, true);\n\n if (!is_array($decoded)) {\n throw new RuntimeException('Failed to decode JSON response from Qdrant API');\n }\n\n return is_array($decoded['result'] ?? null) ? $decoded['result'] : null;\n } catch (RuntimeException $e) {\n throw $e;\n } catch (\\Throwable) {\n return null;\n }\n }\n\n \/** Makes an HTTP POST request to Qdrant API. @throws RuntimeException *\/\n private function makeRequest(string $url, array $payload, int $timeout): array\n {\n $ch = curl_init($url);\n\n if ($ch === false) {\n throw new RuntimeException('Failed to initialize cURL');\n }\n\n $jsonPayload = json_encode($payload);\n\n if ($jsonPayload === false) {\n curl_close($ch);\n\n throw new RuntimeException('Failed to encode JSON payload');\n }\n\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_POSTFIELDS => $jsonPayload,\n CURLOPT_TIMEOUT => $timeout,\n CURLOPT_CONNECTTIMEOUT => 10,\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application\/json',\n 'Content-Length: ' . strlen($jsonPayload),\n ],\n ]);\n\n $result = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n $curlError = curl_error($ch);\n\n curl_close($ch);\n\n if ($result === false) {\n throw new RuntimeException(\n sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error')\n );\n }\n\n if ($httpCode !== 200) {\n throw new RuntimeException(\n sprintf('Qdrant API returned HTTP %d: %s', $httpCode, $result)\n );\n }\n\n $decoded = json_decode((string) $result, true);\n\n if (!is_array($decoded)) {\n throw new RuntimeException('Failed to decode JSON response from Qdrant API');\n }\n\n return $decoded;\n }\n}\n",
"structuredPatch": [
{
"oldStart": 23,
"oldLines": 11,
"newStart": 23,
"newLines": 7,
"lines": [
" ",
" private readonly string $host;",
" ",
"- \/**",
"- * Constructs a new QdrantService instance.",
"- *",
"- * @param string|null $host The Qdrant API host URL (uses env QDRANT_HOST if null)",
"- *\/",
"+ \/** @param string|null $host Qdrant API host (uses env QDRANT_HOST if null) *\/",
" public function __construct(?string $host = null)",
" {",
" $this->host = $host ?? CredentialService::getQdrantHost();"
]
}
],
"userModified": false,
"replaceAll": false
}
}