Backup #1955
| ID | 1955 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Infrastructure/AI/QdrantService.php |
| Version | 10 |
| Typ |
modified |
| Größe | 10.7 KB |
| Hash | 15a943024618e426b193394bd50044aef32a4d85a46a25e88c03cb3090211fd6 |
| Datum | 2025-12-28 02:26:50 |
| Geändert von | claude-code-hook |
| Grund | Claude 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;
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.
*
* @param array<int, float> $vector Query embedding vector
* @param string $collection Collection to search (default: documents)
* @param int $limit Max results (default: 5)
* @return array<int, array{id: int|string, score: float, payload: array<string, mixed>}>
* @throws RuntimeException If API request fails
*/
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. */
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. Returns empty array on error. */
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
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