Skip to content

Commit 7021709

Browse files
committed
chore(pinecone): continue vector store refinements
1 parent fb6fcb0 commit 7021709

2 files changed

Lines changed: 92 additions & 14 deletions

File tree

src/rag/vector_stores/PineconeVectorStore.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class PineconeVectorStore implements IVectorStore {
8888
if (this.isInitialized) return;
8989

9090
// Ping the index to verify API key and host are correct.
91-
const res = await this._fetch('/describe_index_stats', { method: 'POST', body: '{}' });
91+
const res = await this._fetchWithRetry('/describe_index_stats', { method: 'POST', body: '{}' });
9292
if (!res.ok) {
9393
const body = await res.text().catch(() => 'unknown error');
9494
throw new Error(`Pinecone initialization failed (${res.status}): ${body}`);
@@ -110,7 +110,7 @@ export class PineconeVectorStore implements IVectorStore {
110110
/** Health check — verify index is reachable (legacy). */
111111
async healthCheck(): Promise<boolean> {
112112
try {
113-
const res = await this._fetch('/describe_index_stats', { method: 'POST', body: '{}' });
113+
const res = await this._fetchWithRetry('/describe_index_stats', { method: 'POST', body: '{}' });
114114
return res.ok;
115115
} catch {
116116
return false;
@@ -145,11 +145,7 @@ export class PineconeVectorStore implements IVectorStore {
145145
*/
146146
async dropCollection(name: string): Promise<void> {
147147
await this._ensureInit();
148-
const ns = name || this.config.namespace || '';
149-
await this._fetch('/vectors/delete', {
150-
method: 'POST',
151-
body: JSON.stringify({ deleteAll: true, namespace: ns }),
152-
});
148+
await this.delete(name, undefined, { deleteAll: true });
153149
}
154150

155151
// =========================================================================
@@ -464,7 +460,7 @@ export class PineconeVectorStore implements IVectorStore {
464460
}
465461

466462
if (hasDeleteAll) {
467-
await this._fetch('/vectors/delete', {
463+
await this._fetchWithRetry('/vectors/delete', {
468464
method: 'POST',
469465
body: JSON.stringify({ deleteAll: true, namespace: ns }),
470466
});
@@ -477,7 +473,7 @@ export class PineconeVectorStore implements IVectorStore {
477473
let deleted = 0;
478474
for (let i = 0; i < ids.length; i += batchSize) {
479475
const batch = ids.slice(i, i + batchSize);
480-
const res = await this._fetch('/vectors/delete', {
476+
const res = await this._fetchWithRetry('/vectors/delete', {
481477
method: 'POST',
482478
body: JSON.stringify({ ids: batch, namespace: ns }),
483479
});
@@ -537,9 +533,6 @@ export class PineconeVectorStore implements IVectorStore {
537533

538534
/**
539535
* Retry transient Pinecone failures with exponential backoff.
540-
*
541-
* This is intentionally scoped to metadata-heavy operations in this adapter,
542-
* where Pinecone documents lower request-per-second limits.
543536
*/
544537
private async _fetchWithRetry(
545538
path: string,

src/rag/vector_stores/__tests__/PineconeVectorStore.test.ts

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,21 @@ describe('PineconeVectorStore', () => {
145145
await store.initialize();
146146
expect(fetchCalls.length).toBe(count);
147147
});
148+
149+
it('retries throttled initialization checks with exponential backoff', async () => {
150+
vi.useFakeTimers();
151+
fetchResponseQueue.push(errResponse(429, 'rate limited'));
152+
fetchResponseQueue.push(okJson({ namespaces: {}, totalVectorCount: 0 }));
153+
store = new PineconeVectorStore(makeConfig());
154+
155+
const initPromise = store.initialize();
156+
await vi.runAllTimersAsync();
157+
await initPromise;
158+
159+
expect(fetchCalls).toHaveLength(2);
160+
expect(fetchCalls[0].url).toContain('/describe_index_stats');
161+
expect(fetchCalls[1].url).toContain('/describe_index_stats');
162+
});
148163
});
149164

150165
// =========================================================================
@@ -550,6 +565,27 @@ describe('PineconeVectorStore', () => {
550565
expect(result.deletedCount).toBe(3);
551566
});
552567

568+
it('retries throttled ID deletes with exponential backoff', async () => {
569+
fetchResponseQueue.push(okJson({})); // init
570+
store = new PineconeVectorStore(makeConfig());
571+
await store.initialize();
572+
resetMocks();
573+
vi.useFakeTimers();
574+
575+
fetchResponseQueue.push(errResponse(429, 'rate limited'));
576+
fetchResponseQueue.push(okJson({}));
577+
578+
const resultPromise = store.delete('ns', ['a', 'b']);
579+
580+
await vi.runAllTimersAsync();
581+
const result = await resultPromise;
582+
583+
expect(fetchCalls).toHaveLength(2);
584+
expect(parseFetchBody(fetchCalls[0]).ids).toEqual(['a', 'b']);
585+
expect(parseFetchBody(fetchCalls[1]).ids).toEqual(['a', 'b']);
586+
expect(result.deletedCount).toBe(2);
587+
});
588+
553589
it('deleteAll sends deleteAll=true', async () => {
554590
fetchResponseQueue.push(okJson({})); // init
555591
store = new PineconeVectorStore(makeConfig());
@@ -564,6 +600,27 @@ describe('PineconeVectorStore', () => {
564600
expect(result.deletedCount).toBe(-1); // Pinecone doesn't return count.
565601
});
566602

603+
it('retries throttled deleteAll requests with exponential backoff', async () => {
604+
fetchResponseQueue.push(okJson({})); // init
605+
store = new PineconeVectorStore(makeConfig());
606+
await store.initialize();
607+
resetMocks();
608+
vi.useFakeTimers();
609+
610+
fetchResponseQueue.push(errResponse(429, 'rate limited'));
611+
fetchResponseQueue.push(okJson({}));
612+
613+
const resultPromise = store.delete('ns', undefined, { deleteAll: true });
614+
615+
await vi.runAllTimersAsync();
616+
const result = await resultPromise;
617+
618+
expect(fetchCalls).toHaveLength(2);
619+
expect(parseFetchBody(fetchCalls[0]).deleteAll).toBe(true);
620+
expect(parseFetchBody(fetchCalls[1]).deleteAll).toBe(true);
621+
expect(result.deletedCount).toBe(-1);
622+
});
623+
567624
it('deletes by metadata filter through /vectors/delete', async () => {
568625
fetchResponseQueue.push(okJson({})); // init
569626
store = new PineconeVectorStore(makeConfig());
@@ -711,9 +768,37 @@ describe('PineconeVectorStore', () => {
711768
store = new PineconeVectorStore(makeConfig());
712769
await store.initialize();
713770
resetMocks();
771+
vi.useFakeTimers();
772+
773+
mockFetch
774+
.mockRejectedValueOnce(new Error('network error'))
775+
.mockRejectedValueOnce(new Error('network error'))
776+
.mockRejectedValueOnce(new Error('network error'))
777+
.mockRejectedValueOnce(new Error('network error'));
778+
779+
const healthPromise = store.healthCheck();
780+
await vi.runAllTimersAsync();
714781

715-
mockFetch.mockRejectedValueOnce(new Error('network error'));
716-
expect(await store.healthCheck()).toBe(false);
782+
expect(await healthPromise).toBe(false);
783+
});
784+
785+
it('retries throttled health checks with exponential backoff', async () => {
786+
fetchResponseQueue.push(okJson({})); // init
787+
store = new PineconeVectorStore(makeConfig());
788+
await store.initialize();
789+
resetMocks();
790+
vi.useFakeTimers();
791+
792+
fetchResponseQueue.push(errResponse(429, 'rate limited'));
793+
fetchResponseQueue.push(okJson({ namespaces: {}, totalVectorCount: 0 }));
794+
795+
const healthPromise = store.healthCheck();
796+
await vi.runAllTimersAsync();
797+
798+
expect(await healthPromise).toBe(true);
799+
expect(fetchCalls).toHaveLength(2);
800+
expect(fetchCalls[0].url).toContain('/describe_index_stats');
801+
expect(fetchCalls[1].url).toContain('/describe_index_stats');
717802
});
718803
});
719804

0 commit comments

Comments
 (0)