From b3828ddae9ec61ab86e9c1bd631974a320cfecb0 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Mon, 18 Nov 2024 15:12:39 +0100 Subject: [PATCH 01/11] feat: implement cache class in api.ts --- src/api.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/api.ts b/src/api.ts index 7d510ab..93a19b1 100644 --- a/src/api.ts +++ b/src/api.ts @@ -325,6 +325,87 @@ type CreateAttachmentParams = { metadata?: Maybe>; }; +// TODO: Move this code to a separate file +interface QueryPromptParams { + id?: string; + name?: string; + version?: number; +} + +export class PromptCache { + private static instance: PromptCache | null = null; + private prompts: Map; + private nameIndex: Map; + private nameVersionIndex: Map; + private lock: { locked: boolean }; + + private constructor() { + this.prompts = new Map(); + this.nameIndex = new Map(); + this.nameVersionIndex = new Map(); + this.lock = { locked: false }; + } + + static getInstance(): PromptCache { + if (!PromptCache.instance) { + PromptCache.instance = new PromptCache(); + } + return PromptCache.instance; + } + + private async withLock(fn: () => Promise | T): Promise { + while (this.lock.locked) { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + this.lock.locked = true; + try { + return await fn(); + } finally { + this.lock.locked = false; + } + } + + private getNameVersionKey(name: string, version: number): string { + return `${name}:${version}`; + } + + async get(params: QueryPromptParams): Promise { + return this.withLock(async () => { + const { id, name, version } = params; + let promptId: string | undefined; + + if (id) { + promptId = id; + } else if (name && version) { + promptId = this.getNameVersionKey(name, version); + } else if (name) { + promptId = this.nameIndex.get(name); + } + + return promptId ? this.prompts.get(promptId) : undefined; + }); + } + + async put(prompt: Prompt): Promise { + return this.withLock(async () => { + this.prompts.set(prompt.id, prompt); + this.nameIndex.set(prompt.name, prompt.id); + this.nameVersionIndex.set( + this.getNameVersionKey(prompt.name, prompt.version), + prompt.id + ); + }); + } + + async clear(): Promise { + return this.withLock(() => { + this.prompts.clear(); + this.nameIndex.clear(); + this.nameVersionIndex.clear(); + }); + } +} + /** * Represents the API client for interacting with the Literal service. * This class handles API requests, authentication, and provides methods From e2706f062dbe9e5811e5754a9a52ac7df6f8d92e Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Mon, 18 Nov 2024 15:51:41 +0100 Subject: [PATCH 02/11] feat: adds cache to getPrompt methods --- src/api.ts | 129 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/src/api.ts b/src/api.ts index 93a19b1..1d6ba42 100644 --- a/src/api.ts +++ b/src/api.ts @@ -332,8 +332,8 @@ interface QueryPromptParams { version?: number; } -export class PromptCache { - private static instance: PromptCache | null = null; +export class SharedCachePrompt { + private static instance: SharedCachePrompt | null = null; private prompts: Map; private nameIndex: Map; private nameVersionIndex: Map; @@ -346,11 +346,11 @@ export class PromptCache { this.lock = { locked: false }; } - static getInstance(): PromptCache { - if (!PromptCache.instance) { - PromptCache.instance = new PromptCache(); + static getInstance(): SharedCachePrompt { + if (!SharedCachePrompt.instance) { + SharedCachePrompt.instance = new SharedCachePrompt(); } - return PromptCache.instance; + return SharedCachePrompt.instance; } private async withLock(fn: () => Promise | T): Promise { @@ -421,6 +421,8 @@ export class PromptCache { * Then you can use the `api` object to make calls to the Literal service. */ export class API { + /** @ignore */ + private promptCache: SharedCachePrompt; /** @ignore */ public client: LiteralClient; /** @ignore */ @@ -453,6 +455,8 @@ export class API { throw new Error('LITERAL_API_URL not set'); } + this.promptCache = SharedCachePrompt.getInstance(); + this.apiKey = apiKey; this.url = url; this.environment = environment; @@ -2191,69 +2195,96 @@ export class API { } /** - * Retrieves a prompt by its id. + * Retrieves a prompt by its id. If the request fails, it will try to get the prompt from the cache. * * @param id ID of the prompt to retrieve. * @returns The prompt with given ID. */ public async getPromptById(id: string) { - const query = ` - query GetPrompt($id: String!) { - promptVersion(id: $id) { - createdAt - id - label - settings - status - tags - templateMessages - tools - type - updatedAt - url - variables - variablesDefaultValues - version - lineage { - name + try { + const query = ` + query GetPrompt($id: String!) { + promptVersion(id: $id) { + createdAt + id + label + settings + status + tags + templateMessages + tools + type + updatedAt + url + variables + variablesDefaultValues + version + lineage { + name + } } } - } - `; + `; - return await this.getPromptWithQuery(query, { id }); + const prompt = await this.getPromptWithQuery(query, { id }); + if (prompt) { + await this.promptCache.put(prompt); + return prompt; + } + return null; + } catch (error) { + console.warn( + `Failed to get prompt from DB, trying cache. Error: ${ + error instanceof Error ? error.message : String(error) + }` + ); + return await this.promptCache.get({ id }); + } } /** - * Retrieves a prompt by its name and optionally by its version. + * Retrieves a prompt by its name and optionally by its version. If the request fails, it will try to get the prompt from the cache. * * @param name - The name of the prompt to retrieve. * @param version - The version number of the prompt (optional). * @returns An instance of `Prompt` containing the prompt data, or `null` if not found. */ public async getPrompt(name: string, version?: number) { - const query = ` - query GetPrompt($name: String!, $version: Int) { - promptVersion(name: $name, version: $version) { - id - createdAt - updatedAt - type - templateMessages - tools - settings - variables - variablesDefaultValues - version - url - lineage { - name + try { + const query = ` + query GetPrompt($name: String!, $version: Int) { + promptVersion(name: $name, version: $version) { + id + createdAt + updatedAt + type + templateMessages + tools + settings + variables + variablesDefaultValues + version + url + lineage { + name + } } } + `; + const prompt = await this.getPromptWithQuery(query, { name, version }); + if (prompt) { + await this.promptCache.put(prompt); + return prompt; + } + return null; + } catch (error) { + console.warn( + `Failed to get prompt from DB, trying cache. Error: ${ + error instanceof Error ? error.message : String(error) + }` + ); + return await this.promptCache.get({ name, version }); } - `; - - return await this.getPromptWithQuery(query, { name, version }); } private async getPromptWithQuery( From 15c14280551f1dc3e6ac5485243d6922164f86c2 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Tue, 19 Nov 2024 09:59:59 +0100 Subject: [PATCH 03/11] fix: tests and implementation of nameVersionIndex --- src/api.ts | 16 +++-------- tests/api.test.ts | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/api.ts b/src/api.ts index 1d6ba42..50e61a2 100644 --- a/src/api.ts +++ b/src/api.ts @@ -377,7 +377,9 @@ export class SharedCachePrompt { if (id) { promptId = id; } else if (name && version) { - promptId = this.getNameVersionKey(name, version); + promptId = this.nameVersionIndex.get( + this.getNameVersionKey(name, version) + ); } else if (name) { promptId = this.nameIndex.get(name); } @@ -422,7 +424,7 @@ export class SharedCachePrompt { */ export class API { /** @ignore */ - private promptCache: SharedCachePrompt; + public promptCache: SharedCachePrompt; /** @ignore */ public client: LiteralClient; /** @ignore */ @@ -2233,11 +2235,6 @@ export class API { } return null; } catch (error) { - console.warn( - `Failed to get prompt from DB, trying cache. Error: ${ - error instanceof Error ? error.message : String(error) - }` - ); return await this.promptCache.get({ id }); } } @@ -2278,11 +2275,6 @@ export class API { } return null; } catch (error) { - console.warn( - `Failed to get prompt from DB, trying cache. Error: ${ - error instanceof Error ? error.message : String(error) - }` - ); return await this.promptCache.get({ name, version }); } } diff --git a/tests/api.test.ts b/tests/api.test.ts index a23ac2d..42e4777 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import { ChatGeneration, IGenerationMessage, LiteralClient } from '../src'; import { Dataset } from '../src/evaluation/dataset'; import { Score } from '../src/evaluation/score'; +import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; import { sleep } from './utils'; describe('End to end tests for the SDK', function () { @@ -597,6 +598,30 @@ describe('End to end tests for the SDK', function () { }); describe('Prompt api', () => { + const mockPromptData: PromptConstructor = { + id: 'test-id', + name: 'test-prompt', + version: 1, + createdAt: new Date().toISOString(), + type: 'CHAT', + templateMessages: [], + tools: [], + settings: { + provider: 'test', + model: 'test', + frequency_penalty: 0, + presence_penalty: 0, + temperature: 0, + top_p: 0, + max_tokens: 0 + }, + variables: [], + variablesDefaultValues: {}, + metadata: {}, + items: [], + provider: 'test' + }; + it('should get a prompt by name', async () => { const prompt = await client.api.getPrompt('Default'); @@ -657,6 +682,52 @@ is a templated list.`; expect(formatted[0].content).toBe(expected); }); + it('should fallback to cache when getPromptById DB call fails', async () => { + const prompt = new Prompt(client.api, mockPromptData); + await client.api.promptCache.put(prompt); + + jest + .spyOn(client.api as any, 'makeGqlCall') + .mockRejectedValueOnce(new Error('DB Error')); + + const result = await client.api.getPromptById(prompt.id); + expect(result).toEqual(prompt); + }); + + it('should fallback to cache when getPrompt DB call fails', async () => { + const prompt = new Prompt(client.api, mockPromptData); + await client.api.promptCache.put(prompt); + jest + .spyOn(client.api as any, 'makeGqlCall') + .mockRejectedValueOnce(new Error('DB Error')); + + const result = await client.api.getPrompt(prompt.name, prompt.version); + expect(result).toEqual(prompt); + }); + + it('should update cache with fresh data on successful DB call', async () => { + const prompt = new Prompt(client.api, mockPromptData); + + jest.spyOn(client.api as any, 'makeGqlCall').mockResolvedValueOnce({ + data: { promptVersion: prompt } + }); + + await client.api.getPromptById(prompt.id); + + const cachedPrompt = await client.api.promptCache.get({ id: prompt.id }); + expect(cachedPrompt).toBeDefined(); + expect(cachedPrompt?.id).toBe(prompt.id); + }); + + it('should return null when both DB and cache fail', async () => { + jest + .spyOn(client.api as any, 'makeGqlCall') + .mockRejectedValueOnce(new Error('DB Error')); + + const result = await client.api.getPromptById('non-existent-id'); + expect(result).toBeUndefined(); + }); + it('should get a prompt A/B testing configuration', async () => { const promptName = 'TypeScript SDK E2E Tests'; From 9625e9f2368feb91a8c62d37f9e3caa2df3ed4a3 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Tue, 19 Nov 2024 10:57:43 +0100 Subject: [PATCH 04/11] refactor: move logic to getPromptWithQuery --- src/api.ts | 118 +++++++++++++++++++++++++---------------------------- 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/src/api.ts b/src/api.ts index 50e61a2..4e5dfd8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -325,7 +325,6 @@ type CreateAttachmentParams = { metadata?: Maybe>; }; -// TODO: Move this code to a separate file interface QueryPromptParams { id?: string; name?: string; @@ -486,7 +485,11 @@ export class API { * @returns The data part of the response from the GraphQL endpoint. * @throws Will throw an error if the GraphQL call returns errors or if the request fails. */ - private async makeGqlCall(query: string, variables: any) { + private async makeGqlCall( + query: string, + variables: any, + timeout: number = 10000 + ) { try { const response = await axios({ url: this.graphqlEndpoint, @@ -495,7 +498,8 @@ export class API { data: { query: query, variables: variables - } + }, + timeout }); if (response.data.errors) { throw new Error(JSON.stringify(response.data.errors)); @@ -2198,13 +2202,9 @@ export class API { /** * Retrieves a prompt by its id. If the request fails, it will try to get the prompt from the cache. - * - * @param id ID of the prompt to retrieve. - * @returns The prompt with given ID. */ public async getPromptById(id: string) { - try { - const query = ` + const query = ` query GetPrompt($id: String!) { promptVersion(id: $id) { createdAt @@ -2226,16 +2226,38 @@ export class API { } } } - `; + `; + + return this.getPromptWithQuery(query, { id }); + } + + /** + * Private helper method to execute prompt queries with error handling and caching + */ + private async getPromptWithQuery( + query: string, + variables: Record + ) { + try { + const result = await this.makeGqlCall(query, variables); - const prompt = await this.getPromptWithQuery(query, { id }); - if (prompt) { - await this.promptCache.put(prompt); - return prompt; + if (!result.data || !result.data.promptVersion) { + return null; } - return null; + + const promptData = result.data.promptVersion; + promptData.provider = promptData.settings?.provider; + promptData.name = promptData.lineage?.name; + delete promptData.lineage; + if (promptData.settings) { + delete promptData.settings.provider; + } + + const prompt = new Prompt(this, promptData); + await this.promptCache.put(prompt); + return prompt; } catch (error) { - return await this.promptCache.get({ id }); + return await this.promptCache.get(variables); } } @@ -2247,57 +2269,27 @@ export class API { * @returns An instance of `Prompt` containing the prompt data, or `null` if not found. */ public async getPrompt(name: string, version?: number) { - try { - const query = ` - query GetPrompt($name: String!, $version: Int) { - promptVersion(name: $name, version: $version) { - id - createdAt - updatedAt - type - templateMessages - tools - settings - variables - variablesDefaultValues - version - url - lineage { - name - } + const query = ` + query GetPrompt($name: String!, $version: Int) { + promptVersion(name: $name, version: $version) { + id + createdAt + updatedAt + type + templateMessages + tools + settings + variables + variablesDefaultValues + version + url + lineage { + name } } - `; - const prompt = await this.getPromptWithQuery(query, { name, version }); - if (prompt) { - await this.promptCache.put(prompt); - return prompt; - } - return null; - } catch (error) { - return await this.promptCache.get({ name, version }); } - } - - private async getPromptWithQuery( - query: string, - variables: Record - ) { - const result = await this.makeGqlCall(query, variables); - - if (!result.data || !result.data.promptVersion) { - return null; - } - - const promptData = result.data.promptVersion; - promptData.provider = promptData.settings?.provider; - promptData.name = promptData.lineage?.name; - delete promptData.lineage; - if (promptData.settings) { - delete promptData.settings.provider; - } - - return new Prompt(this, promptData); + `; + return this.getPromptWithQuery(query, { name, version }); } /** From bbe697e3e2ffddeb21d39f29977126583a19e2bf Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Tue, 19 Nov 2024 11:04:15 +0100 Subject: [PATCH 05/11] refactor: if null in response get from cache --- src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.ts b/src/api.ts index 4e5dfd8..d5307e3 100644 --- a/src/api.ts +++ b/src/api.ts @@ -2242,7 +2242,7 @@ export class API { const result = await this.makeGqlCall(query, variables); if (!result.data || !result.data.promptVersion) { - return null; + return await this.promptCache.get(variables); } const promptData = result.data.promptVersion; From fc9888a2ab5cc1db95ee1b6b0c5de4f47d550b53 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Tue, 19 Nov 2024 11:08:11 +0100 Subject: [PATCH 06/11] feat: adds timeout logic --- src/api.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api.ts b/src/api.ts index d5307e3..235e6d5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -2238,11 +2238,13 @@ export class API { query: string, variables: Record ) { + const cachedPrompt = await this.promptCache.get(variables); + const timeout = cachedPrompt ? 1000 : 10000; try { - const result = await this.makeGqlCall(query, variables); + const result = await this.makeGqlCall(query, variables, timeout); if (!result.data || !result.data.promptVersion) { - return await this.promptCache.get(variables); + return cachedPrompt; } const promptData = result.data.promptVersion; @@ -2257,7 +2259,7 @@ export class API { await this.promptCache.put(prompt); return prompt; } catch (error) { - return await this.promptCache.get(variables); + return cachedPrompt; } } From 0460dc4f13c653d65defa658e8aca0c646c26f0d Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Wed, 20 Nov 2024 12:17:46 +0100 Subject: [PATCH 07/11] feat: wip removing lock, simplifying and generalising --- src/api.ts | 111 ++++++++++++++++------------------------------ tests/api.test.ts | 6 +-- 2 files changed, 42 insertions(+), 75 deletions(-) diff --git a/src/api.ts b/src/api.ts index 235e6d5..576d719 100644 --- a/src/api.ts +++ b/src/api.ts @@ -325,85 +325,52 @@ type CreateAttachmentParams = { metadata?: Maybe>; }; -interface QueryPromptParams { - id?: string; - name?: string; - version?: number; -} - -export class SharedCachePrompt { - private static instance: SharedCachePrompt | null = null; - private prompts: Map; - private nameIndex: Map; - private nameVersionIndex: Map; - private lock: { locked: boolean }; +export class SharedCache { + private static instance: SharedCache | null = null; + private cache: Map; private constructor() { - this.prompts = new Map(); - this.nameIndex = new Map(); - this.nameVersionIndex = new Map(); - this.lock = { locked: false }; + this.cache = new Map(); } - static getInstance(): SharedCachePrompt { - if (!SharedCachePrompt.instance) { - SharedCachePrompt.instance = new SharedCachePrompt(); + static getInstance(): SharedCache { + if (!SharedCache.instance) { + SharedCache.instance = new SharedCache(); } - return SharedCachePrompt.instance; + return SharedCache.instance; } - private async withLock(fn: () => Promise | T): Promise { - while (this.lock.locked) { - await new Promise((resolve) => setTimeout(resolve, 10)); - } - this.lock.locked = true; - try { - return await fn(); - } finally { - this.lock.locked = false; + public getPromptCacheKey(id: string, name: string, version: number): string { + if (id) { + return id; + } else if (name && version) { + return `${name}:${version}`; + } else if (name) { + return name; } + throw new Error('Either id or name must be provided'); } - private getNameVersionKey(name: string, version: number): string { - return `${name}:${version}`; + public getPrompt(key: string): Prompt { + return this.get(key); } - async get(params: QueryPromptParams): Promise { - return this.withLock(async () => { - const { id, name, version } = params; - let promptId: string | undefined; - - if (id) { - promptId = id; - } else if (name && version) { - promptId = this.nameVersionIndex.get( - this.getNameVersionKey(name, version) - ); - } else if (name) { - promptId = this.nameIndex.get(name); - } + public putPrompt(prompt: Prompt): void { + this.put(prompt.id, prompt); + this.put(prompt.name, prompt.id); + this.put(`${prompt.name}-${prompt.version}`, prompt.id); + } - return promptId ? this.prompts.get(promptId) : undefined; - }); + public get(key: string): any { + return this.cache.get(key); } - async put(prompt: Prompt): Promise { - return this.withLock(async () => { - this.prompts.set(prompt.id, prompt); - this.nameIndex.set(prompt.name, prompt.id); - this.nameVersionIndex.set( - this.getNameVersionKey(prompt.name, prompt.version), - prompt.id - ); - }); + public put(key: string, value: any): void { + this.cache.set(key, value); } - async clear(): Promise { - return this.withLock(() => { - this.prompts.clear(); - this.nameIndex.clear(); - this.nameVersionIndex.clear(); - }); + public clear(): void { + this.cache.clear(); } } @@ -423,7 +390,7 @@ export class SharedCachePrompt { */ export class API { /** @ignore */ - public promptCache: SharedCachePrompt; + public cache: SharedCache; /** @ignore */ public client: LiteralClient; /** @ignore */ @@ -456,7 +423,7 @@ export class API { throw new Error('LITERAL_API_URL not set'); } - this.promptCache = SharedCachePrompt.getInstance(); + this.cache = SharedCache.getInstance(); this.apiKey = apiKey; this.url = url; @@ -485,11 +452,7 @@ export class API { * @returns The data part of the response from the GraphQL endpoint. * @throws Will throw an error if the GraphQL call returns errors or if the request fails. */ - private async makeGqlCall( - query: string, - variables: any, - timeout: number = 10000 - ) { + private async makeGqlCall(query: string, variables: any, timeout?: number) { try { const response = await axios({ url: this.graphqlEndpoint, @@ -2238,8 +2201,12 @@ export class API { query: string, variables: Record ) { - const cachedPrompt = await this.promptCache.get(variables); - const timeout = cachedPrompt ? 1000 : 10000; + const { id, name, version } = variables; + const cachedPrompt = this.cache.getPrompt( + this.cache.getPromptCacheKey(id, name, version) + ); + const timeout = cachedPrompt ? 1000 : undefined; + try { const result = await this.makeGqlCall(query, variables, timeout); @@ -2256,7 +2223,7 @@ export class API { } const prompt = new Prompt(this, promptData); - await this.promptCache.put(prompt); + this.cache.putPrompt(prompt); return prompt; } catch (error) { return cachedPrompt; diff --git a/tests/api.test.ts b/tests/api.test.ts index 42e4777..ac23292 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -684,7 +684,7 @@ is a templated list.`; it('should fallback to cache when getPromptById DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - await client.api.promptCache.put(prompt); + client.api.cache.putPrompt(prompt); jest .spyOn(client.api as any, 'makeGqlCall') @@ -696,7 +696,7 @@ is a templated list.`; it('should fallback to cache when getPrompt DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - await client.api.promptCache.put(prompt); + client.api.cache.putPrompt(prompt); jest .spyOn(client.api as any, 'makeGqlCall') .mockRejectedValueOnce(new Error('DB Error')); @@ -714,7 +714,7 @@ is a templated list.`; await client.api.getPromptById(prompt.id); - const cachedPrompt = await client.api.promptCache.get({ id: prompt.id }); + const cachedPrompt = await client.api.cache.get(prompt.id); expect(cachedPrompt).toBeDefined(); expect(cachedPrompt?.id).toBe(prompt.id); }); From 3dab60319964a77590d98d268cf79fb5adc9e083 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Wed, 20 Nov 2024 15:59:37 +0100 Subject: [PATCH 08/11] fix: finishes to fix all tests --- src/api.ts | 18 ++++++++++++++---- tests/api.test.ts | 7 +++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/api.ts b/src/api.ts index 576d719..b313901 100644 --- a/src/api.ts +++ b/src/api.ts @@ -340,10 +340,14 @@ export class SharedCache { return SharedCache.instance; } - public getPromptCacheKey(id: string, name: string, version: number): string { + public getPromptCacheKey( + id?: string, + name?: string, + version?: number + ): string { if (id) { return id; - } else if (name && version) { + } else if (name && (version || version === 0)) { return `${name}:${version}`; } else if (name) { return name; @@ -357,8 +361,12 @@ export class SharedCache { public putPrompt(prompt: Prompt): void { this.put(prompt.id, prompt); - this.put(prompt.name, prompt.id); - this.put(`${prompt.name}-${prompt.version}`, prompt.id); + this.put(prompt.name, prompt); + this.put(`${prompt.name}:${prompt.version}`, prompt); + } + + public getCache(): Map { + return this.cache; } public get(key: string): any { @@ -2226,6 +2234,8 @@ export class API { this.cache.putPrompt(prompt); return prompt; } catch (error) { + console.log('key: ', this.cache.getPromptCacheKey(id, name, version)); + console.log('cachedPrompt: ', cachedPrompt); return cachedPrompt; } } diff --git a/tests/api.test.ts b/tests/api.test.ts index ac23292..9051a0e 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import 'dotenv/config'; import { v4 as uuidv4 } from 'uuid'; @@ -697,11 +698,9 @@ is a templated list.`; it('should fallback to cache when getPrompt DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); client.api.cache.putPrompt(prompt); - jest - .spyOn(client.api as any, 'makeGqlCall') - .mockRejectedValueOnce(new Error('DB Error')); + jest.spyOn(axios, 'post').mockRejectedValueOnce(new Error('DB Error')); - const result = await client.api.getPrompt(prompt.name, prompt.version); + const result = await client.api.getPrompt(prompt.id); expect(result).toEqual(prompt); }); From f1902e729808641d141e9085cde5ec533d564691 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Thu, 21 Nov 2024 16:08:46 +0100 Subject: [PATCH 09/11] feat: refactor and fix tests --- src/api.ts | 75 +++--------------- src/cache/prompt-cache-manager.ts | 29 +++++++ src/cache/sharedcache.ts | 36 +++++++++ tests/api.test.ts | 8 +- tests/cache.test.ts | 121 ++++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 69 deletions(-) create mode 100644 src/cache/prompt-cache-manager.ts create mode 100644 src/cache/sharedcache.ts create mode 100644 tests/cache.test.ts diff --git a/src/api.ts b/src/api.ts index b313901..83155fd 100644 --- a/src/api.ts +++ b/src/api.ts @@ -5,6 +5,8 @@ import { ReadStream } from 'fs'; import { v4 as uuidv4 } from 'uuid'; import { LiteralClient } from '.'; +import { PromptCacheManager } from './cache/prompt-cache-manager'; +import { sharedCache } from './cache/sharedcache'; import { Dataset, DatasetExperiment, @@ -325,63 +327,6 @@ type CreateAttachmentParams = { metadata?: Maybe>; }; -export class SharedCache { - private static instance: SharedCache | null = null; - private cache: Map; - - private constructor() { - this.cache = new Map(); - } - - static getInstance(): SharedCache { - if (!SharedCache.instance) { - SharedCache.instance = new SharedCache(); - } - return SharedCache.instance; - } - - public getPromptCacheKey( - id?: string, - name?: string, - version?: number - ): string { - if (id) { - return id; - } else if (name && (version || version === 0)) { - return `${name}:${version}`; - } else if (name) { - return name; - } - throw new Error('Either id or name must be provided'); - } - - public getPrompt(key: string): Prompt { - return this.get(key); - } - - public putPrompt(prompt: Prompt): void { - this.put(prompt.id, prompt); - this.put(prompt.name, prompt); - this.put(`${prompt.name}:${prompt.version}`, prompt); - } - - public getCache(): Map { - return this.cache; - } - - public get(key: string): any { - return this.cache.get(key); - } - - public put(key: string, value: any): void { - this.cache.set(key, value); - } - - public clear(): void { - this.cache.clear(); - } -} - /** * Represents the API client for interacting with the Literal service. * This class handles API requests, authentication, and provides methods @@ -398,7 +343,7 @@ export class SharedCache { */ export class API { /** @ignore */ - public cache: SharedCache; + private cache: typeof sharedCache; /** @ignore */ public client: LiteralClient; /** @ignore */ @@ -431,7 +376,7 @@ export class API { throw new Error('LITERAL_API_URL not set'); } - this.cache = SharedCache.getInstance(); + this.cache = sharedCache; this.apiKey = apiKey; this.url = url; @@ -2199,7 +2144,7 @@ export class API { } `; - return this.getPromptWithQuery(query, { id }); + return await this.getPromptWithQuery(query, { id }); } /** @@ -2210,8 +2155,8 @@ export class API { variables: Record ) { const { id, name, version } = variables; - const cachedPrompt = this.cache.getPrompt( - this.cache.getPromptCacheKey(id, name, version) + const cachedPrompt = sharedCache.get( + PromptCacheManager.getPromptCacheKey({ id, name, version }) ); const timeout = cachedPrompt ? 1000 : undefined; @@ -2231,11 +2176,9 @@ export class API { } const prompt = new Prompt(this, promptData); - this.cache.putPrompt(prompt); + PromptCacheManager.putPrompt(prompt); return prompt; } catch (error) { - console.log('key: ', this.cache.getPromptCacheKey(id, name, version)); - console.log('cachedPrompt: ', cachedPrompt); return cachedPrompt; } } @@ -2268,7 +2211,7 @@ export class API { } } `; - return this.getPromptWithQuery(query, { name, version }); + return await this.getPromptWithQuery(query, { name, version }); } /** diff --git a/src/cache/prompt-cache-manager.ts b/src/cache/prompt-cache-manager.ts new file mode 100644 index 0000000..214e42d --- /dev/null +++ b/src/cache/prompt-cache-manager.ts @@ -0,0 +1,29 @@ +import { Prompt } from '../prompt-engineering/prompt'; +import { sharedCache } from './sharedcache'; + +export class PromptCacheManager { + public static getPromptCacheKey({ + id, + name, + version + }: { + id?: string; + name?: string; + version?: number; + }): string { + if (id) { + return id; + } else if (name && typeof version === 'number') { + return `${name}:${version}`; + } else if (name) { + return name; + } + throw new Error('Either id or name must be provided'); + } + + public static putPrompt(prompt: Prompt): void { + sharedCache.put(prompt.id, prompt); + sharedCache.put(prompt.name, prompt); + sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); + } +} diff --git a/src/cache/sharedcache.ts b/src/cache/sharedcache.ts new file mode 100644 index 0000000..9c01fea --- /dev/null +++ b/src/cache/sharedcache.ts @@ -0,0 +1,36 @@ +const cache: Map = new Map(); + +class SharedCache { + private static instance: SharedCache; + + public constructor() { + if (SharedCache.instance) { + throw new Error('SharedCache can only be created once'); + } + SharedCache.instance = this; + } + + public getInstance(): SharedCache { + return this; + } + + public getCache(): Map { + return cache; + } + + public get(key: string): any { + return cache.get(key); + } + + public put(key: string, value: any): void { + cache.set(key, value); + } + + public clear(): void { + cache.clear(); + } +} + +export const sharedCache = new SharedCache(); + +export default sharedCache; diff --git a/tests/api.test.ts b/tests/api.test.ts index 9051a0e..e873f97 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -3,6 +3,8 @@ import 'dotenv/config'; import { v4 as uuidv4 } from 'uuid'; import { ChatGeneration, IGenerationMessage, LiteralClient } from '../src'; +import { PromptCacheManager } from '../src/cache/prompt-cache-manager'; +import { sharedCache } from '../src/cache/sharedcache'; import { Dataset } from '../src/evaluation/dataset'; import { Score } from '../src/evaluation/score'; import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; @@ -685,7 +687,7 @@ is a templated list.`; it('should fallback to cache when getPromptById DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - client.api.cache.putPrompt(prompt); + PromptCacheManager.putPrompt(prompt); jest .spyOn(client.api as any, 'makeGqlCall') @@ -697,7 +699,7 @@ is a templated list.`; it('should fallback to cache when getPrompt DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - client.api.cache.putPrompt(prompt); + PromptCacheManager.putPrompt(prompt); jest.spyOn(axios, 'post').mockRejectedValueOnce(new Error('DB Error')); const result = await client.api.getPrompt(prompt.id); @@ -713,7 +715,7 @@ is a templated list.`; await client.api.getPromptById(prompt.id); - const cachedPrompt = await client.api.cache.get(prompt.id); + const cachedPrompt = sharedCache.get(prompt.id); expect(cachedPrompt).toBeDefined(); expect(cachedPrompt?.id).toBe(prompt.id); }); diff --git a/tests/cache.test.ts b/tests/cache.test.ts new file mode 100644 index 0000000..9df7443 --- /dev/null +++ b/tests/cache.test.ts @@ -0,0 +1,121 @@ +import { API } from '../src/api'; +import { PromptCacheManager } from '../src/cache/prompt-cache-manager'; +import { sharedCache } from '../src/cache/sharedcache'; +import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; + +describe('Cache', () => { + let api: API; + let mockPrompt: Prompt; + + beforeAll(() => { + api = {} as API; + }); + + beforeEach(() => { + sharedCache.clear(); + + const mockPromptData: PromptConstructor = { + id: 'test-id', + type: 'CHAT', + createdAt: '2023-01-01T00:00:00Z', + name: 'test-name', + version: 1, + metadata: {}, + items: [], + templateMessages: [{ role: 'user', content: 'Hello', uuid: '123' }], + provider: 'test-provider', + settings: { + provider: 'test-provider', + model: 'test-model', + frequency_penalty: 0, + max_tokens: 100, + presence_penalty: 0, + temperature: 0.7, + top_p: 1 + }, + variables: [] + }; + mockPrompt = new Prompt(api, mockPromptData); + }); + + describe('PromptCacheManager', () => { + describe('getPromptCacheKey', () => { + it('should return id when provided', () => { + const key = PromptCacheManager.getPromptCacheKey({ + id: 'test-id', + name: 'test-name', + version: 1 + }); + expect(key).toBe('test-id'); + }); + + it('should return name:version when id not provided but name and version are', () => { + const key = PromptCacheManager.getPromptCacheKey({ + name: 'test-name', + version: 1 + }); + expect(key).toBe('test-name:1'); + }); + + it('should return name when only name provided', () => { + const key = PromptCacheManager.getPromptCacheKey({ name: 'test-name' }); + expect(key).toBe('test-name'); + }); + + it('should throw error when neither id nor name provided', () => { + expect(() => + PromptCacheManager.getPromptCacheKey({ version: 0 }) + ).toThrow('Either id or name must be provided'); + }); + }); + + describe('putPrompt', () => { + it('should store prompt with multiple keys', () => { + PromptCacheManager.putPrompt(mockPrompt); + + expect(sharedCache.get('test-id')).toEqual(mockPrompt); + expect(sharedCache.get('test-name')).toEqual(mockPrompt); + expect(sharedCache.get('test-name:1')).toEqual(mockPrompt); + }); + }); + }); + + describe('SharedCache', () => { + it('should return undefined for non-existent key', () => { + const value = sharedCache.get('non-existent'); + expect(value).toBeUndefined(); + }); + + it('should store and retrieve values', () => { + sharedCache.put('test-key', 'test-value'); + expect(sharedCache.get('test-key')).toBe('test-value'); + }); + + it('should clear all values', () => { + sharedCache.put('key1', 'value1'); + sharedCache.put('key2', 'value2'); + + sharedCache.clear(); + + expect(sharedCache.get('key1')).toBeUndefined(); + expect(sharedCache.get('key2')).toBeUndefined(); + }); + + it('should maintain singleton behavior', () => { + const instance1 = sharedCache; + const instance2 = sharedCache; + + instance1.put('test', 'value'); + expect(instance2.get('test')).toBe('value'); + expect(instance1).toBe(instance2); + }); + + it('should expose cache map', () => { + sharedCache.put('test', 'value'); + const cacheMap = sharedCache.getCache(); + + expect(cacheMap instanceof Map).toBe(true); + expect(cacheMap.get('test')).toBe('value'); + }); + }); +}); From b03d76db022cc82c1f2d62d1cff343d8de84ab2e Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Thu, 21 Nov 2024 19:24:14 +0100 Subject: [PATCH 10/11] feat: replace manager by utils --- src/api.ts | 6 +++--- src/cache/prompt-cache-manager.ts | 29 ----------------------------- src/cache/utils.ts | 27 +++++++++++++++++++++++++++ tests/api.test.ts | 6 +++--- tests/cache.test.ts | 18 +++++++++--------- 5 files changed, 42 insertions(+), 44 deletions(-) delete mode 100644 src/cache/prompt-cache-manager.ts create mode 100644 src/cache/utils.ts diff --git a/src/api.ts b/src/api.ts index 83155fd..9673ec8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -5,8 +5,8 @@ import { ReadStream } from 'fs'; import { v4 as uuidv4 } from 'uuid'; import { LiteralClient } from '.'; -import { PromptCacheManager } from './cache/prompt-cache-manager'; import { sharedCache } from './cache/sharedcache'; +import { getPromptCacheKey, putPrompt } from './cache/utils'; import { Dataset, DatasetExperiment, @@ -2156,7 +2156,7 @@ export class API { ) { const { id, name, version } = variables; const cachedPrompt = sharedCache.get( - PromptCacheManager.getPromptCacheKey({ id, name, version }) + getPromptCacheKey({ id, name, version }) ); const timeout = cachedPrompt ? 1000 : undefined; @@ -2176,7 +2176,7 @@ export class API { } const prompt = new Prompt(this, promptData); - PromptCacheManager.putPrompt(prompt); + putPrompt(prompt); return prompt; } catch (error) { return cachedPrompt; diff --git a/src/cache/prompt-cache-manager.ts b/src/cache/prompt-cache-manager.ts deleted file mode 100644 index 214e42d..0000000 --- a/src/cache/prompt-cache-manager.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Prompt } from '../prompt-engineering/prompt'; -import { sharedCache } from './sharedcache'; - -export class PromptCacheManager { - public static getPromptCacheKey({ - id, - name, - version - }: { - id?: string; - name?: string; - version?: number; - }): string { - if (id) { - return id; - } else if (name && typeof version === 'number') { - return `${name}:${version}`; - } else if (name) { - return name; - } - throw new Error('Either id or name must be provided'); - } - - public static putPrompt(prompt: Prompt): void { - sharedCache.put(prompt.id, prompt); - sharedCache.put(prompt.name, prompt); - sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); - } -} diff --git a/src/cache/utils.ts b/src/cache/utils.ts new file mode 100644 index 0000000..b83d6bc --- /dev/null +++ b/src/cache/utils.ts @@ -0,0 +1,27 @@ +import { Prompt } from '../prompt-engineering/prompt'; +import { sharedCache } from './sharedcache'; + +export function getPromptCacheKey({ + id, + name, + version +}: { + id?: string; + name?: string; + version?: number; +}): string { + if (id) { + return id; + } else if (name && typeof version === 'number') { + return `${name}:${version}`; + } else if (name) { + return name; + } + throw new Error('Either id or name must be provided'); +} + +export function putPrompt(prompt: Prompt): void { + sharedCache.put(prompt.id, prompt); + sharedCache.put(prompt.name, prompt); + sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); +} diff --git a/tests/api.test.ts b/tests/api.test.ts index e873f97..b220854 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -3,8 +3,8 @@ import 'dotenv/config'; import { v4 as uuidv4 } from 'uuid'; import { ChatGeneration, IGenerationMessage, LiteralClient } from '../src'; -import { PromptCacheManager } from '../src/cache/prompt-cache-manager'; import { sharedCache } from '../src/cache/sharedcache'; +import { putPrompt } from '../src/cache/utils'; import { Dataset } from '../src/evaluation/dataset'; import { Score } from '../src/evaluation/score'; import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; @@ -687,7 +687,7 @@ is a templated list.`; it('should fallback to cache when getPromptById DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - PromptCacheManager.putPrompt(prompt); + putPrompt(prompt); jest .spyOn(client.api as any, 'makeGqlCall') @@ -699,7 +699,7 @@ is a templated list.`; it('should fallback to cache when getPrompt DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - PromptCacheManager.putPrompt(prompt); + putPrompt(prompt); jest.spyOn(axios, 'post').mockRejectedValueOnce(new Error('DB Error')); const result = await client.api.getPrompt(prompt.id); diff --git a/tests/cache.test.ts b/tests/cache.test.ts index 9df7443..5101be9 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,6 +1,6 @@ import { API } from '../src/api'; -import { PromptCacheManager } from '../src/cache/prompt-cache-manager'; import { sharedCache } from '../src/cache/sharedcache'; +import { getPromptCacheKey, putPrompt } from '../src/cache/utils'; import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; describe('Cache', () => { @@ -38,10 +38,10 @@ describe('Cache', () => { mockPrompt = new Prompt(api, mockPromptData); }); - describe('PromptCacheManager', () => { + describe('Cache Utils', () => { describe('getPromptCacheKey', () => { it('should return id when provided', () => { - const key = PromptCacheManager.getPromptCacheKey({ + const key = getPromptCacheKey({ id: 'test-id', name: 'test-name', version: 1 @@ -50,7 +50,7 @@ describe('Cache', () => { }); it('should return name:version when id not provided but name and version are', () => { - const key = PromptCacheManager.getPromptCacheKey({ + const key = getPromptCacheKey({ name: 'test-name', version: 1 }); @@ -58,20 +58,20 @@ describe('Cache', () => { }); it('should return name when only name provided', () => { - const key = PromptCacheManager.getPromptCacheKey({ name: 'test-name' }); + const key = getPromptCacheKey({ name: 'test-name' }); expect(key).toBe('test-name'); }); it('should throw error when neither id nor name provided', () => { - expect(() => - PromptCacheManager.getPromptCacheKey({ version: 0 }) - ).toThrow('Either id or name must be provided'); + expect(() => getPromptCacheKey({ version: 0 })).toThrow( + 'Either id or name must be provided' + ); }); }); describe('putPrompt', () => { it('should store prompt with multiple keys', () => { - PromptCacheManager.putPrompt(mockPrompt); + putPrompt(mockPrompt); expect(sharedCache.get('test-id')).toEqual(mockPrompt); expect(sharedCache.get('test-name')).toEqual(mockPrompt); From d2b3b538ba921b10810c2b9f0dd4738d71152df4 Mon Sep 17 00:00:00 2001 From: Matthieu Olenga Date: Fri, 22 Nov 2024 09:37:28 +0100 Subject: [PATCH 11/11] refactor: apply clem comments --- src/api.ts | 15 ++++++++------- src/cache/utils.ts | 9 --------- tests/api.test.ts | 11 ++++++++--- tests/cache.test.ts | 44 +------------------------------------------- 4 files changed, 17 insertions(+), 62 deletions(-) diff --git a/src/api.ts b/src/api.ts index 9673ec8..23bba68 100644 --- a/src/api.ts +++ b/src/api.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; import { LiteralClient } from '.'; import { sharedCache } from './cache/sharedcache'; -import { getPromptCacheKey, putPrompt } from './cache/utils'; +import { getPromptCacheKey } from './cache/utils'; import { Dataset, DatasetExperiment, @@ -2152,12 +2152,9 @@ export class API { */ private async getPromptWithQuery( query: string, - variables: Record + variables: { id?: string; name?: string; version?: number } ) { - const { id, name, version } = variables; - const cachedPrompt = sharedCache.get( - getPromptCacheKey({ id, name, version }) - ); + const cachedPrompt = sharedCache.get(getPromptCacheKey(variables)); const timeout = cachedPrompt ? 1000 : undefined; try { @@ -2176,7 +2173,11 @@ export class API { } const prompt = new Prompt(this, promptData); - putPrompt(prompt); + + sharedCache.put(prompt.id, prompt); + sharedCache.put(prompt.name, prompt); + sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); + return prompt; } catch (error) { return cachedPrompt; diff --git a/src/cache/utils.ts b/src/cache/utils.ts index b83d6bc..535e242 100644 --- a/src/cache/utils.ts +++ b/src/cache/utils.ts @@ -1,6 +1,3 @@ -import { Prompt } from '../prompt-engineering/prompt'; -import { sharedCache } from './sharedcache'; - export function getPromptCacheKey({ id, name, @@ -19,9 +16,3 @@ export function getPromptCacheKey({ } throw new Error('Either id or name must be provided'); } - -export function putPrompt(prompt: Prompt): void { - sharedCache.put(prompt.id, prompt); - sharedCache.put(prompt.name, prompt); - sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); -} diff --git a/tests/api.test.ts b/tests/api.test.ts index b220854..e2e74c2 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -4,7 +4,6 @@ import { v4 as uuidv4 } from 'uuid'; import { ChatGeneration, IGenerationMessage, LiteralClient } from '../src'; import { sharedCache } from '../src/cache/sharedcache'; -import { putPrompt } from '../src/cache/utils'; import { Dataset } from '../src/evaluation/dataset'; import { Score } from '../src/evaluation/score'; import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; @@ -687,7 +686,9 @@ is a templated list.`; it('should fallback to cache when getPromptById DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - putPrompt(prompt); + sharedCache.put(prompt.id, prompt); + sharedCache.put(prompt.name, prompt); + sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); jest .spyOn(client.api as any, 'makeGqlCall') @@ -699,7 +700,11 @@ is a templated list.`; it('should fallback to cache when getPrompt DB call fails', async () => { const prompt = new Prompt(client.api, mockPromptData); - putPrompt(prompt); + + sharedCache.put(prompt.id, prompt); + sharedCache.put(prompt.name, prompt); + sharedCache.put(`${prompt.name}:${prompt.version}`, prompt); + jest.spyOn(axios, 'post').mockRejectedValueOnce(new Error('DB Error')); const result = await client.api.getPrompt(prompt.id); diff --git a/tests/cache.test.ts b/tests/cache.test.ts index 5101be9..86b0972 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,41 +1,9 @@ -import { API } from '../src/api'; import { sharedCache } from '../src/cache/sharedcache'; -import { getPromptCacheKey, putPrompt } from '../src/cache/utils'; -import { Prompt, PromptConstructor } from '../src/prompt-engineering/prompt'; +import { getPromptCacheKey } from '../src/cache/utils'; describe('Cache', () => { - let api: API; - let mockPrompt: Prompt; - - beforeAll(() => { - api = {} as API; - }); - beforeEach(() => { sharedCache.clear(); - - const mockPromptData: PromptConstructor = { - id: 'test-id', - type: 'CHAT', - createdAt: '2023-01-01T00:00:00Z', - name: 'test-name', - version: 1, - metadata: {}, - items: [], - templateMessages: [{ role: 'user', content: 'Hello', uuid: '123' }], - provider: 'test-provider', - settings: { - provider: 'test-provider', - model: 'test-model', - frequency_penalty: 0, - max_tokens: 100, - presence_penalty: 0, - temperature: 0.7, - top_p: 1 - }, - variables: [] - }; - mockPrompt = new Prompt(api, mockPromptData); }); describe('Cache Utils', () => { @@ -68,16 +36,6 @@ describe('Cache', () => { ); }); }); - - describe('putPrompt', () => { - it('should store prompt with multiple keys', () => { - putPrompt(mockPrompt); - - expect(sharedCache.get('test-id')).toEqual(mockPrompt); - expect(sharedCache.get('test-name')).toEqual(mockPrompt); - expect(sharedCache.get('test-name:1')).toEqual(mockPrompt); - }); - }); }); describe('SharedCache', () => {