QdrantClient.php
- Pfad:
src/Infrastructure/AI/QdrantClient.php - Namespace: Infrastructure\AI
- Zeilen: 237 | Größe: 6,391 Bytes
- Geändert: 2025-12-25 12:59:47 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 97
- Dependencies: 100 (25%)
- LOC: 87 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 2
- use Infrastructure\Config\CredentialService
- use RuntimeException
Klassen 1
-
QdrantClientclass Zeile 12
Funktionen 8
-
__construct()public Zeile 18 -
ensureCollection()public Zeile 26 -
getCollectionStats()public Zeile 60 -
upsertPoint()public Zeile 86 -
search()public Zeile 116 -
deletePoints()public Zeile 144 -
generateUuid()public Zeile 164 -
request()private Zeile 179
Verwendet von 7
- ChatServiceProvider.php use
- ChunkSearchService.php constructor
- ChunkSearchService.php use
- ChunkSyncService.php constructor
- ChunkSyncService.php use
- HybridSearchService.php constructor
- HybridSearchService.php use
Code
<?php
declare(strict_types=1);
namespace Infrastructure\AI;
// @responsibility: HTTP-Client für Qdrant Vector-Datenbank
use Infrastructure\Config\CredentialService;
use RuntimeException;
final class QdrantClient
{
private const int TIMEOUT = 60;
private string $host;
public function __construct(?string $host = null)
{
$this->host = $host ?? CredentialService::getQdrantHost();
}
/**
* Ensures a collection exists with proper configuration.
*/
public function ensureCollection(string $collection, int $vectorSize = 1024): bool
{
$url = sprintf('%s/collections/%s', $this->host, $collection);
try {
$response = $this->request($url, [], 'GET');
if (isset($response['result'])) {
return true;
}
} catch (RuntimeException) {
// Collection doesn't exist, create it
}
$payload = [
'vectors' => [
'size' => $vectorSize,
'distance' => 'Cosine',
],
];
try {
$this->request($url, $payload, 'PUT');
return true;
} catch (RuntimeException $e) {
throw new RuntimeException('Failed to create collection: ' . $e->getMessage());
}
}
/**
* Gets collection statistics.
*
* @return array{points_count: int, status: string}|null
*/
public function getCollectionStats(string $collection): ?array
{
$url = sprintf('%s/collections/%s', $this->host, $collection);
try {
$response = $this->request($url, [], 'GET');
if (!isset($response['result'])) {
return null;
}
return [
'points_count' => (int) ($response['result']['points_count'] ?? 0),
'status' => (string) ($response['result']['status'] ?? 'unknown'),
];
} catch (RuntimeException) {
return null;
}
}
/**
* Upserts a point to a collection.
*
* @param array<int, float> $vector
* @param array<string, mixed> $payload
*/
public function upsertPoint(string $collection, string $id, array $vector, array $payload): bool
{
$url = sprintf('%s/collections/%s/points', $this->host, $collection);
$data = [
'points' => [
[
'id' => $id,
'vector' => array_values($vector),
'payload' => $payload,
],
],
];
try {
$this->request($url, $data, 'PUT');
return true;
} catch (RuntimeException) {
return false;
}
}
/**
* Searches for similar vectors.
*
* @param array<int, float> $vector
* @param array<string, mixed>|null $filter
* @return array<array<string, mixed>>
*/
public function search(string $collection, array $vector, int $limit = 5, ?array $filter = null): array
{
$url = sprintf('%s/collections/%s/points/search', $this->host, $collection);
$payload = [
'vector' => array_values($vector),
'limit' => $limit,
'with_payload' => true,
];
if ($filter !== null) {
$payload['filter'] = $filter;
}
$response = $this->request($url, $payload, 'POST');
if (!isset($response['result']) || !is_array($response['result'])) {
return [];
}
return $response['result'];
}
/**
* Deletes points by IDs.
*
* @param array<string> $ids
*/
public function deletePoints(string $collection, array $ids): bool
{
$url = sprintf('%s/collections/%s/points/delete', $this->host, $collection);
$data = [
'points' => $ids,
];
try {
$this->request($url, $data, 'POST');
return true;
} catch (RuntimeException) {
return false;
}
}
/**
* Generates a UUID v4.
*/
public function generateUuid(): string
{
$data = random_bytes(16);
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* Makes an HTTP request to Qdrant.
*
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
private function request(string $url, array $payload, string $method): array
{
$ch = curl_init($url);
if ($ch === false) {
throw new RuntimeException('Failed to initialize cURL');
}
$headers = ['Content-Type: application/json'];
if ($method === 'GET') {
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => self::TIMEOUT,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => 'GET',
]);
} else {
$jsonPayload = json_encode($payload);
if ($jsonPayload === false) {
curl_close($ch);
throw new RuntimeException('Failed to encode JSON payload');
}
$headers[] = 'Content-Length: ' . strlen($jsonPayload);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => self::TIMEOUT,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $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 ?: 'Unknown error'));
}
if ($httpCode >= 400) {
throw new RuntimeException(sprintf('Qdrant API returned HTTP %d: %s', $httpCode, $result));
}
$decoded = json_decode((string) $result, true);
return is_array($decoded) ? $decoded : [];
}
}