settings = $settings; $api_key = $this->settings->get( 'vectorDBProviders.pinecone.key' ); $this->client = new Client( [ 'timeout' => 30, 'headers' => [ 'Api-Key' => $api_key, 'Content-Type' => 'application/json', ], ] ); } /** * Upsert vectors to Pinecone * * @param array $vectors Array of vectors with id, values, and metadata. * @return bool Success status. * @throws \Exception If upsert fails. */ public function upsert( array $vectors ): bool { $index_url = $this->settings->get( 'vectorDBProviders.pinecone.indexUrl' ); $namespace = $this->settings->get( 'vectorDBProviders.pinecone.namespace', 'default' ); if ( empty( $index_url ) ) { throw new \Exception( 'Pinecone indexUrl is not configured.' ); } $url = rtrim( $index_url, '/' ) . '/vectors/upsert'; // Batch vectors in groups of 100. $batches = array_chunk( $vectors, 100 ); foreach ( $batches as $batch ) { $payload = [ 'vectors' => $batch, 'namespace' => $namespace, ]; try { $response = $this->client->post( $url, [ 'json' => $payload, ] ); $body = json_decode( $response->getBody()->getContents(), true ); } catch ( \Exception $e ) { Log::error( 'Pinecone upsert failed', [ 'error' => $e->getMessage(), ] ); throw new \Exception( sprintf( /* translators: %s: error message */ __( 'Failed to upsert vectors: %s', 'aisentic' ), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped esc_html( $e->getMessage() ) ) ); } } return true; } /** * Query vectors from Pinecone * * @param array $vector Query vector. * @param int $top_k Number of results to return. * @param array $filter Optional metadata filter. * @return array Query results. * @throws \Exception If query fails. */ public function query( array $vector, int $top_k = 5, array $filter = [] ): array { $index_url = $this->settings->get( 'vectorDBProviders.pinecone.indexUrl' ); $namespace = $this->settings->get( 'vectorDBProviders.pinecone.namespace', 'default' ); if ( empty( $index_url ) ) { throw new \Exception( 'Pinecone indexUrl is not configured.' ); } $url = rtrim( $index_url, '/' ) . '/query'; $payload = [ 'vector' => $vector, 'topK' => $top_k, 'namespace' => $namespace, 'includeMetadata' => true, ]; if ( ! empty( $filter ) ) { $payload['filter'] = $filter; } try { $response = $this->client->post( $url, [ 'json' => $payload, ] ); $body = json_decode( $response->getBody()->getContents(), true ); if ( isset( $body['matches'] ) ) { return $body['matches']; } return []; } catch ( \Exception $e ) { Log::error( 'Pinecone query failed', [ 'error' => $e->getMessage(), ] ); throw new \Exception( sprintf( /* translators: %s: error message */ __( 'Failed to query vectors: %s', 'aisentic' ), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped esc_html( $e->getMessage() ) ) ); } } /** * Delete vectors from Pinecone * * @param array $ids Vector IDs to delete. * @return bool Success status. * @throws \Exception If deletion fails. */ public function delete( array $ids ): bool { $index_url = $this->settings->get( 'vectorDBProviders.pinecone.indexUrl' ); $namespace = $this->settings->get( 'vectorDBProviders.pinecone.namespace', 'default' ); if ( empty( $index_url ) ) { throw new \Exception( 'Pinecone indexUrl is not configured.' ); } $url = rtrim( $index_url, '/' ) . '/vectors/delete'; $payload = [ 'ids' => $ids, 'namespace' => $namespace, ]; try { $response = $this->client->post( $url, [ 'json' => $payload, ] ); return true; } catch ( \Exception $e ) { Log::error( 'Pinecone delete failed', [ 'error' => $e->getMessage(), ] ); throw new \Exception( sprintf( /* translators: %s: error message */ __( 'Failed to delete vectors: %s', 'aisentic' ), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped esc_html( $e->getMessage() ) ) ); } } /** * Delete all vectors in the namespace * * @return bool Success status. * @throws \Exception If deletion fails. */ public function deleteAll(): bool { $index_url = $this->settings->get( 'vectorDBProviders.pinecone.indexUrl' ); $namespace = $this->settings->get( 'vectorDBProviders.pinecone.namespace', 'default' ); if ( empty( $index_url ) ) { throw new \Exception( 'Pinecone indexUrl is not configured.' ); } $url = rtrim( $index_url, '/' ) . '/vectors/delete'; $payload = [ 'deleteAll' => true, 'namespace' => $namespace, ]; try { $response = $this->client->post( $url, [ 'json' => $payload, ] ); return true; } catch ( \Exception $e ) { Log::error( 'Pinecone deleteAll failed', [ 'error' => $e->getMessage(), ] ); throw new \Exception( sprintf( /* translators: %s: error message */ __( 'Failed to delete all vectors: %s', 'aisentic' ), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped esc_html( $e->getMessage() ) ) ); } } }