Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
65.06% |
54 / 83 |
|
11.11% |
1 / 9 |
CRAP | |
0.00% |
0 / 1 |
| DataPlane | |
65.06% |
54 / 83 |
|
11.11% |
1 / 9 |
75.68 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| upsert | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
3.10 | |||
| query | |
78.95% |
15 / 19 |
|
0.00% |
0 / 1 |
6.34 | |||
| fetch | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
3.33 | |||
| delete | |
66.67% |
10 / 15 |
|
0.00% |
0 / 1 |
7.33 | |||
| update | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
5.31 | |||
| describeIndexStats | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| buildIndexHost | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| handleResponse | |
58.33% |
7 / 12 |
|
0.00% |
0 / 1 |
5.16 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Mbvb1223\Pinecone\Data; |
| 6 | |
| 7 | use GuzzleHttp\Client; |
| 8 | use GuzzleHttp\Exception\GuzzleException; |
| 9 | use Mbvb1223\Pinecone\Errors\PineconeApiException; |
| 10 | use Mbvb1223\Pinecone\Errors\PineconeException; |
| 11 | use Psr\Http\Message\ResponseInterface; |
| 12 | |
| 13 | class DataPlane |
| 14 | { |
| 15 | public function __construct(private readonly Client $httpClient) |
| 16 | { |
| 17 | } |
| 18 | |
| 19 | public function upsert(array $vectors, ?string $namespace = null): array |
| 20 | { |
| 21 | try { |
| 22 | $payload = ['vectors' => $vectors]; |
| 23 | if ($namespace) { |
| 24 | $payload['namespace'] = $namespace; |
| 25 | } |
| 26 | |
| 27 | $response = $this->httpClient->post('/vectors/upsert', [ |
| 28 | 'json' => $payload, |
| 29 | ]); |
| 30 | |
| 31 | return $this->handleResponse($response); |
| 32 | } catch (GuzzleException $e) { |
| 33 | throw new PineconeException('Failed to upsert vectors: ' . $e->getMessage(), 0, $e); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | public function query( |
| 38 | array $vector = [], |
| 39 | ?string $id = null, |
| 40 | int $topK = 10, |
| 41 | ?array $filter = null, |
| 42 | ?string $namespace = null, |
| 43 | bool $includeValues = false, |
| 44 | bool $includeMetadata = true |
| 45 | ): array { |
| 46 | try { |
| 47 | $payload = [ |
| 48 | 'topK' => $topK, |
| 49 | 'includeValues' => $includeValues, |
| 50 | 'includeMetadata' => $includeMetadata, |
| 51 | ]; |
| 52 | |
| 53 | if (!empty($vector)) { |
| 54 | $payload['vector'] = $vector; |
| 55 | } |
| 56 | |
| 57 | if ($id) { |
| 58 | $payload['id'] = $id; |
| 59 | } |
| 60 | |
| 61 | if ($filter) { |
| 62 | $payload['filter'] = $filter; |
| 63 | } |
| 64 | |
| 65 | if ($namespace) { |
| 66 | $payload['namespace'] = $namespace; |
| 67 | } |
| 68 | |
| 69 | $response = $this->httpClient->post('/query', [ |
| 70 | 'json' => $payload, |
| 71 | ]); |
| 72 | |
| 73 | return $this->handleResponse($response); |
| 74 | } catch (GuzzleException $e) { |
| 75 | throw new PineconeException('Failed to query vectors: ' . $e->getMessage(), 0, $e); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | public function fetch(array $ids, ?string $namespace = null): array |
| 80 | { |
| 81 | try { |
| 82 | $idQueries = implode('&', array_map(fn ($id) => 'ids=' . urlencode($id), $ids)); |
| 83 | |
| 84 | $namespaceQuery = $namespace ? '&namespace=' . urlencode($namespace) : ''; |
| 85 | |
| 86 | $response = $this->httpClient->get('/vectors/fetch?' . $idQueries . $namespaceQuery); |
| 87 | |
| 88 | return $this->handleResponse($response)['vectors'] ?? []; |
| 89 | } catch (GuzzleException $e) { |
| 90 | throw new PineconeException('Failed to fetch vectors: ' . $e->getMessage(), 0, $e); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | public function delete(array $ids = [], ?array $filter = null, ?string $namespace = null, bool $deleteAll = false): array |
| 95 | { |
| 96 | try { |
| 97 | $payload = []; |
| 98 | |
| 99 | if ($deleteAll) { |
| 100 | $payload['deleteAll'] = true; |
| 101 | } elseif (!empty($ids)) { |
| 102 | $payload['ids'] = $ids; |
| 103 | } elseif ($filter) { |
| 104 | $payload['filter'] = $filter; |
| 105 | } |
| 106 | |
| 107 | if ($namespace) { |
| 108 | $payload['namespace'] = $namespace; |
| 109 | } |
| 110 | |
| 111 | $response = $this->httpClient->post('/vectors/delete', [ |
| 112 | 'json' => $payload, |
| 113 | ]); |
| 114 | |
| 115 | return $this->handleResponse($response); |
| 116 | } catch (GuzzleException $e) { |
| 117 | throw new PineconeException('Failed to delete vectors: ' . $e->getMessage(), 0, $e); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | public function update(string $id, array $values = [], ?array $setMetadata = null, ?string $namespace = null): array |
| 122 | { |
| 123 | try { |
| 124 | $payload = ['id' => $id]; |
| 125 | |
| 126 | if (!empty($values)) { |
| 127 | $payload['values'] = $values; |
| 128 | } |
| 129 | |
| 130 | if ($setMetadata) { |
| 131 | $payload['setMetadata'] = $setMetadata; |
| 132 | } |
| 133 | |
| 134 | if ($namespace) { |
| 135 | $payload['namespace'] = $namespace; |
| 136 | } |
| 137 | |
| 138 | $response = $this->httpClient->post('/vectors/update', [ |
| 139 | 'json' => $payload, |
| 140 | ]); |
| 141 | |
| 142 | return $this->handleResponse($response); |
| 143 | } catch (GuzzleException $e) { |
| 144 | throw new PineconeException('Failed to update vector: ' . $e->getMessage(), 0, $e); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | public function describeIndexStats(?array $filter = null): array |
| 149 | { |
| 150 | try { |
| 151 | $payload = []; |
| 152 | if ($filter) { |
| 153 | $payload['filter'] = $filter; |
| 154 | } |
| 155 | |
| 156 | $response = $this->httpClient->post('/describe_index_stats', ['json' => (object) $payload]); |
| 157 | |
| 158 | return $this->handleResponse($response); |
| 159 | } catch (GuzzleException $e) { |
| 160 | throw new PineconeException('Failed to describe index stats: ' . $e->getMessage(), 0, $e); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | private function buildIndexHost(string $indexName): string |
| 165 | { |
| 166 | return "{$indexName}-{$this->config->getEnvironment()}.svc.{$this->config->getEnvironment()}.pinecone.io"; |
| 167 | } |
| 168 | |
| 169 | private function handleResponse(ResponseInterface $response): array |
| 170 | { |
| 171 | $statusCode = $response->getStatusCode(); |
| 172 | $body = $response->getBody()->getContents(); |
| 173 | |
| 174 | if ($statusCode >= 400) { |
| 175 | $data = json_decode($body, true) ?? []; |
| 176 | $message = $data['message'] ?? 'API request failed'; |
| 177 | throw new PineconeApiException($message, $statusCode, $data); |
| 178 | } |
| 179 | |
| 180 | if (empty($body)) { |
| 181 | return []; |
| 182 | } |
| 183 | |
| 184 | $decoded = json_decode($body, true); |
| 185 | if (json_last_error() !== JSON_ERROR_NONE) { |
| 186 | throw new PineconeException('Failed to decode JSON response: ' . json_last_error_msg()); |
| 187 | } |
| 188 | |
| 189 | return $decoded; |
| 190 | } |
| 191 | } |