| @@ -493,12 +493,8 @@ | ||
| 493 | 493 | $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? ''; |
| 494 | 494 | |
| 495 | 495 | // Pinecone's list endpoint returns vector IDs with pagination |
| 496 | 496 | // We then fetch the metadata for those specific IDs |
| 497 | - // NOTE (plan 793b82): /vectors/list is a GET endpoint with query parameters. | |
| 498 | - // A POST is answered 200-with-an-empty-body, which reads as "no vectors" — | |
| 499 | - // this call used to POST and only ever "worked" because the empty result | |
| 500 | - // tripped the query-based fallback below. | |
| 501 | 497 | $list_url = "https://{$host}/vectors/list"; |
| 502 | 498 | |
| 503 | 499 | // Calculate pagination token from page number |
| 504 | 500 | // Pinecone uses cursor-based pagination, so we need to handle this differently |
| @@ -522,20 +518,19 @@ | ||
| 522 | 518 | $list_params['paginationToken'] = $stored_tokens[$page]; |
| 523 | 519 | } |
| 524 | 520 | } |
| 525 | 521 | |
| 526 | - $response = wp_remote_get($list_url . '?' . http_build_query($list_params), array( | |
| 522 | + $response = wp_remote_post($list_url, array( | |
| 527 | 523 | 'headers' => array( |
| 528 | 524 | 'Api-Key' => $api_key, |
| 529 | - 'accept' => 'application/json' | |
| 525 | + 'Content-Type' => 'application/json' | |
| 530 | 526 | ), |
| 527 | + 'body' => json_encode($list_params), | |
| 531 | 528 | 'timeout' => 15 |
| 532 | 529 | )); |
| 533 | 530 | |
| 534 | 531 | if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { |
| 535 | - // Genuine transport/API failure — fall back to the query-based approach, | |
| 536 | - // but say so: a silent fallback is what masked the POST bug for a year. | |
| 537 | - error_log('MxChat Pinecone: /vectors/list GET failed (' . (is_wp_error($response) ? $response->get_error_message() : wp_remote_retrieve_response_code($response)) . ') — falling back to query-based listing.'); | |
| 532 | + // Fallback to query-based approach | |
| 538 | 533 | return $this->mxchat_query_based_list($pinecone_options, $page, $per_page, $bot_id, $content_type); |
| 539 | 534 | } |
| 540 | 535 | |
| 541 | 536 | $body = wp_remote_retrieve_body($response); |
| @@ -554,26 +549,15 @@ | ||
| 554 | 549 | $vector_ids[] = $vector['id']; |
| 555 | 550 | } |
| 556 | 551 | } |
| 557 | 552 | |
| 558 | - // A 200 with no vector ids is a REAL answer now (the namespace is empty), | |
| 559 | - // not the failure signature it was under POST — return the empty listing | |
| 560 | - // rather than routing into the fallback and masking a broken primary. | |
| 553 | + // If list endpoint returned empty, fall back to query-based approach | |
| 561 | 554 | if (empty($vector_ids)) { |
| 562 | - return array('data' => array(), 'total' => 0); | |
| 555 | + return $this->mxchat_query_based_list($pinecone_options, $page, $per_page, $bot_id, $content_type); | |
| 563 | 556 | } |
| 564 | 557 | |
| 565 | 558 | // Fetch metadata for these vector IDs |
| 566 | - $fetched = $this->mxchat_fetch_vectors_by_ids_for_list($pinecone_options, $vector_ids, $page, $per_page, $bot_id, $content_type); | |
| 567 | - | |
| 568 | - // null = the fetch itself failed in transport (distinct from "ids had no | |
| 569 | - // metadata matches") — log and fall back so the screen still lists. | |
| 570 | - if ($fetched === null) { | |
| 571 | - error_log('MxChat Pinecone: /vectors/fetch GET failed for the listing — falling back to query-based listing.'); | |
| 572 | - return $this->mxchat_query_based_list($pinecone_options, $page, $per_page, $bot_id, $content_type); | |
| 573 | - } | |
| 574 | - | |
| 575 | - return $fetched; | |
| 559 | + return $this->mxchat_fetch_vectors_by_ids_for_list($pinecone_options, $vector_ids, $page, $per_page, $bot_id, $content_type); | |
| 576 | 560 | } |
| 577 | 561 | |
| 578 | 562 | /** |
| 579 | 563 | * Query-based listing fallback when list endpoint fails |
| @@ -677,31 +661,29 @@ | ||
| 677 | 661 | $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? ''; |
| 678 | 662 | $host = $pinecone_options['mxchat_pinecone_host'] ?? ''; |
| 679 | 663 | $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? ''; |
| 680 | 664 | |
| 681 | - // NOTE (plan 793b82): /vectors/fetch is a GET endpoint, and Pinecone expects | |
| 682 | - // the ids repeated (ids=a&ids=b) — http_build_query would emit ids[0]=a. | |
| 683 | - // A POST here is answered 200-with-an-empty-body, which reads as "no vectors". | |
| 684 | - $fetch_query = array(); | |
| 685 | - foreach ($vector_ids as $fetch_vid) { | |
| 686 | - $fetch_query[] = 'ids=' . rawurlencode($fetch_vid); | |
| 687 | - } | |
| 665 | + $fetch_url = "https://{$host}/vectors/fetch"; | |
| 666 | + | |
| 667 | + $fetch_data = array( | |
| 668 | + 'ids' => $vector_ids | |
| 669 | + ); | |
| 670 | + | |
| 688 | 671 | if (!empty($namespace)) { |
| 689 | - $fetch_query[] = 'namespace=' . rawurlencode($namespace); | |
| 672 | + $fetch_data['namespace'] = $namespace; | |
| 690 | 673 | } |
| 691 | 674 | |
| 692 | - $response = wp_remote_get("https://{$host}/vectors/fetch?" . implode('&', $fetch_query), array( | |
| 675 | + $response = wp_remote_post($fetch_url, array( | |
| 693 | 676 | 'headers' => array( |
| 694 | 677 | 'Api-Key' => $api_key, |
| 695 | - 'accept' => 'application/json' | |
| 678 | + 'Content-Type' => 'application/json' | |
| 696 | 679 | ), |
| 680 | + 'body' => json_encode($fetch_data), | |
| 697 | 681 | 'timeout' => 15 |
| 698 | 682 | )); |
| 699 | 683 | |
| 700 | - // Transport/API failure is distinct from "nothing matched": return null so | |
| 701 | - // the caller can tell the difference (plan 793b82's empty-vs-failed rule). | |
| 702 | 684 | if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { |
| 703 | - return null; | |
| 685 | + return array('data' => array(), 'total' => 0); | |
| 704 | 686 | } |
| 705 | 687 | |
| 706 | 688 | $body = wp_remote_retrieve_body($response); |
| 707 | 689 | $data = json_decode($body, true); |
| @@ -1122,45 +1104,31 @@ | ||
| 1122 | 1104 | if (empty($vector_id_map)) { |
| 1123 | 1105 | return array(); |
| 1124 | 1106 | } |
| 1125 | 1107 | |
| 1126 | - // Batch check Pinecone using fetch API. | |
| 1127 | - // NOTE (plan 793b82): /vectors/fetch is a GET endpoint with the ids | |
| 1128 | - // repeated in the query string (ids=a&ids=b); the old POST here was | |
| 1129 | - // answered 200-with-an-empty-body, so this scan saw NOTHING as indexed | |
| 1130 | - // and callers re-embedded content Pinecone already had. Chunk size 100: | |
| 1131 | - // measured on a live serverless index, ~9KB of URL is accepted and | |
| 1132 | - // ~18KB draws HTTP 414, so 100 32-char ids (~4KB) leaves real margin. | |
| 1133 | - $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? ''; | |
| 1108 | + // Batch check Pinecone using fetch API (max 1000 IDs per request) | |
| 1134 | 1109 | $all_vector_ids = array_keys($vector_id_map); |
| 1135 | - $chunks = array_chunk($all_vector_ids, 100); | |
| 1110 | + $chunks = array_chunk($all_vector_ids, 1000); | |
| 1136 | 1111 | $processed_data = array(); |
| 1137 | 1112 | |
| 1138 | 1113 | foreach ($chunks as $chunk) { |
| 1139 | - $fetch_query = array(); | |
| 1140 | - foreach ($chunk as $fetch_vid) { | |
| 1141 | - $fetch_query[] = 'ids=' . rawurlencode($fetch_vid); | |
| 1142 | - } | |
| 1143 | - if (!empty($namespace)) { | |
| 1144 | - $fetch_query[] = 'namespace=' . rawurlencode($namespace); | |
| 1145 | - } | |
| 1114 | + $fetch_url = "https://{$host}/vectors/fetch"; | |
| 1146 | 1115 | |
| 1147 | - $response = wp_remote_get("https://{$host}/vectors/fetch?" . implode('&', $fetch_query), array( | |
| 1116 | + $response = wp_remote_post($fetch_url, array( | |
| 1148 | 1117 | 'headers' => array( |
| 1149 | 1118 | 'Api-Key' => $api_key, |
| 1150 | - 'accept' => 'application/json' | |
| 1119 | + 'Content-Type' => 'application/json' | |
| 1151 | 1120 | ), |
| 1121 | + 'body' => json_encode(array('ids' => $chunk)), | |
| 1152 | 1122 | 'timeout' => 30 |
| 1153 | 1123 | )); |
| 1154 | 1124 | |
| 1155 | 1125 | if (is_wp_error($response)) { |
| 1156 | - error_log('MxChat Pinecone: processed-content scan /vectors/fetch GET failed: ' . $response->get_error_message()); | |
| 1157 | 1126 | continue; |
| 1158 | 1127 | } |
| 1159 | 1128 | |
| 1160 | 1129 | $response_code = wp_remote_retrieve_response_code($response); |
| 1161 | 1130 | if ($response_code !== 200) { |
| 1162 | - error_log('MxChat Pinecone: processed-content scan /vectors/fetch GET returned HTTP ' . $response_code); | |
| 1163 | 1131 | continue; |
| 1164 | 1132 | } |
| 1165 | 1133 | |
| 1166 | 1134 | $body = wp_remote_retrieve_body($response); |
| @@ -1343,9 +1311,8 @@ | ||
| 1343 | 1311 | */ |
| 1344 | 1312 | public function fetch_pinecone_vectors_by_ids($pinecone_options, $vector_ids) { |
| 1345 | 1313 | $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? ''; |
| 1346 | 1314 | $host = $pinecone_options['mxchat_pinecone_host'] ?? ''; |
| 1347 | - $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? ''; | |
| 1348 | 1315 | |
| 1349 | 1316 | if (empty($api_key) || empty($host) || empty($vector_ids)) { |
| 1350 | 1317 | return array(); |
| 1351 | 1318 | } |
| @@ -1350,53 +1317,44 @@ | ||
| 1350 | 1317 | return array(); |
| 1351 | 1318 | } |
| 1352 | 1319 | |
| 1353 | 1320 | try { |
| 1354 | - // NOTE (plan 793b82): /vectors/fetch is a GET endpoint with the ids | |
| 1355 | - // repeated in the query string; the old POST here was answered | |
| 1356 | - // 200-with-an-empty-body. Chunked at 100 ids to stay well under the | |
| 1357 | - // measured HTTP 414 URL-length boundary. | |
| 1358 | - $vectors = array(); | |
| 1359 | - foreach (array_chunk(array_values($vector_ids), 100) as $chunk) { | |
| 1360 | - $fetch_query = array(); | |
| 1361 | - foreach ($chunk as $fetch_vid) { | |
| 1362 | - $fetch_query[] = 'ids=' . rawurlencode($fetch_vid); | |
| 1363 | - } | |
| 1364 | - if (!empty($namespace)) { | |
| 1365 | - $fetch_query[] = 'namespace=' . rawurlencode($namespace); | |
| 1366 | - } | |
| 1321 | + $fetch_url = "https://{$host}/vectors/fetch"; | |
| 1367 | 1322 | |
| 1368 | - $response = wp_remote_get("https://{$host}/vectors/fetch?" . implode('&', $fetch_query), array( | |
| 1369 | - 'headers' => array( | |
| 1370 | - 'Api-Key' => $api_key, | |
| 1371 | - 'accept' => 'application/json' | |
| 1372 | - ), | |
| 1373 | - 'timeout' => 30 | |
| 1374 | - )); | |
| 1323 | + // Pinecone fetch API allows fetching specific vectors by ID | |
| 1324 | + $fetch_data = array( | |
| 1325 | + 'ids' => array_values($vector_ids) | |
| 1326 | + ); | |
| 1375 | 1327 | |
| 1376 | - if (is_wp_error($response)) { | |
| 1377 | - error_log('MxChat Pinecone: fetch_pinecone_vectors_by_ids GET failed: ' . $response->get_error_message()); | |
| 1378 | - continue; | |
| 1379 | - } | |
| 1328 | + $response = wp_remote_post($fetch_url, array( | |
| 1329 | + 'headers' => array( | |
| 1330 | + 'Api-Key' => $api_key, | |
| 1331 | + 'Content-Type' => 'application/json' | |
| 1332 | + ), | |
| 1333 | + 'body' => json_encode($fetch_data), | |
| 1334 | + 'timeout' => 30 | |
| 1335 | + )); | |
| 1380 | 1336 | |
| 1381 | - if (wp_remote_retrieve_response_code($response) !== 200) { | |
| 1382 | - error_log('MxChat Pinecone: fetch_pinecone_vectors_by_ids GET returned HTTP ' . wp_remote_retrieve_response_code($response)); | |
| 1383 | - continue; | |
| 1384 | - } | |
| 1337 | + if (is_wp_error($response)) { | |
| 1338 | + return array(); | |
| 1339 | + } | |
| 1385 | 1340 | |
| 1386 | - $data = json_decode(wp_remote_retrieve_body($response), true); | |
| 1387 | - if (isset($data['vectors']) && is_array($data['vectors'])) { | |
| 1388 | - $vectors += $data['vectors']; | |
| 1389 | - } | |
| 1341 | + $response_code = wp_remote_retrieve_response_code($response); | |
| 1342 | + | |
| 1343 | + if ($response_code !== 200) { | |
| 1344 | + return array(); | |
| 1390 | 1345 | } |
| 1391 | 1346 | |
| 1392 | - if (empty($vectors)) { | |
| 1347 | + $body = wp_remote_retrieve_body($response); | |
| 1348 | + $data = json_decode($body, true); | |
| 1349 | + | |
| 1350 | + if (!isset($data['vectors'])) { | |
| 1393 | 1351 | return array(); |
| 1394 | 1352 | } |
| 1395 | 1353 | |
| 1396 | 1354 | $processed_data = array(); |
| 1397 | 1355 | |
| 1398 | - foreach ($vectors as $vector_id => $vector_data) { | |
| 1356 | + foreach ($data['vectors'] as $vector_id => $vector_data) { | |
| 1399 | 1357 | $metadata = $vector_data['metadata'] ?? array(); |
| 1400 | 1358 | $source_url = $metadata['source_url'] ?? ''; |
| 1401 | 1359 | |
| 1402 | 1360 | if (!empty($source_url)) { |
| @@ -1439,9 +1397,8 @@ | ||
| 1439 | 1397 | */ |
| 1440 | 1398 | public function mxchat_delete_all_from_pinecone($pinecone_options, $content_type_filter = '') { |
| 1441 | 1399 | $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? ''; |
| 1442 | 1400 | $host = $pinecone_options['mxchat_pinecone_host'] ?? ''; |
| 1443 | - $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? ''; | |
| 1444 | 1401 | |
| 1445 | 1402 | if (empty($api_key) || empty($host)) { |
| 1446 | 1403 | return array( |
| 1447 | 1404 | 'success' => false, |
| @@ -1486,9 +1443,9 @@ | ||
| 1486 | 1443 | $batch_size = 100; |
| 1487 | 1444 | $batches = array_chunk($vector_ids, $batch_size); |
| 1488 | 1445 | |
| 1489 | 1446 | foreach ($batches as $batch) { |
| 1490 | - $result = $this->mxchat_delete_pinecone_batch($batch, $api_key, $host, $namespace); | |
| 1447 | + $result = $this->mxchat_delete_pinecone_batch($batch, $api_key, $host); | |
| 1491 | 1448 | if ($result['success']) { |
| 1492 | 1449 | $total_deleted += count($batch); |
| 1493 | 1450 | } else { |
| 1494 | 1451 | $failed_batches++; |
| @@ -1529,9 +1486,9 @@ | ||
| 1529 | 1486 | |
| 1530 | 1487 | /** |
| 1531 | 1488 | * Deletes batch of vectors from Pinecone database |
| 1532 | 1489 | */ |
| 1533 | - public function mxchat_delete_pinecone_batch($vector_ids, $api_key, $host, $namespace = '') { | |
| 1490 | + public function mxchat_delete_pinecone_batch($vector_ids, $api_key, $host) { | |
| 1534 | 1491 | // Build the API endpoint |
| 1535 | 1492 | $api_endpoint = "https://{$host}/vectors/delete"; |
| 1536 | 1493 | |
| 1537 | 1494 | // Prepare the request body with the IDs |
| @@ -1537,13 +1494,8 @@ | ||
| 1537 | 1494 | // Prepare the request body with the IDs |
| 1538 | 1495 | $request_body = array( |
| 1539 | 1496 | 'ids' => $vector_ids |
| 1540 | 1497 | ); |
| 1541 | - | |
| 1542 | - // Add namespace if provided — omitting it targets the default namespace | |
| 1543 | - if (!empty($namespace)) { | |
| 1544 | - $request_body['namespace'] = $namespace; | |
| 1545 | - } | |
| 1546 | 1498 | |
| 1547 | 1499 | // Make the deletion request |
| 1548 | 1500 | $response = wp_remote_post($api_endpoint, array( |
| 1549 | 1501 | 'headers' => array( |