Skip to content

Commit 62ec042

Browse files
authored
Merge pull request #1036 from FlowiseAI/feature/InMemoryCache
Feature/In-Mem LLMcache
2 parents 0e2cba0 + 41e6124 commit 62ec042

File tree

11 files changed

+147
-3
lines changed

11 files changed

+147
-3
lines changed

packages/components/credentials/UpstashRedisApi.credential.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ class UpstashRedisApi implements INodeCredential {
44
label: string
55
name: string
66
version: number
7+
description: string
78
inputs: INodeParams[]
89

910
constructor() {
1011
this.label = 'Upstash Redis API'
1112
this.name = 'upstashRedisApi'
1213
this.version = 1.0
14+
this.description =
15+
'Refer to <a target="_blank" href="https://upstash.com/docs/redis/overall/getstarted">official guide</a> on how to create redis instance and get redis REST URL and Token'
1316
this.inputs = [
1417
{
1518
label: 'Upstash Redis REST URL',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { getBaseClasses, ICommonObject, INode, INodeData, INodeParams } from '../../../src'
2+
import { BaseCache } from 'langchain/schema'
3+
import hash from 'object-hash'
4+
5+
class InMemoryCache implements INode {
6+
label: string
7+
name: string
8+
version: number
9+
description: string
10+
type: string
11+
icon: string
12+
category: string
13+
baseClasses: string[]
14+
inputs: INodeParams[]
15+
credential: INodeParams
16+
17+
constructor() {
18+
this.label = 'InMemory Cache'
19+
this.name = 'inMemoryCache'
20+
this.version = 1.0
21+
this.type = 'InMemoryCache'
22+
this.description = 'Cache LLM response in memory, will be cleared once app restarted'
23+
this.icon = 'inmemorycache.png'
24+
this.category = 'Cache'
25+
this.baseClasses = [this.type, ...getBaseClasses(InMemoryCacheExtended)]
26+
this.inputs = []
27+
}
28+
29+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
30+
const memoryMap = options.cachePool.getLLMCache(options.chatflowid) ?? new Map()
31+
const inMemCache = new InMemoryCacheExtended(memoryMap)
32+
33+
inMemCache.lookup = async (prompt: string, llmKey: string): Promise<any | null> => {
34+
const memory = options.cachePool.getLLMCache(options.chatflowid) ?? inMemCache.cache
35+
return Promise.resolve(memory.get(getCacheKey(prompt, llmKey)) ?? null)
36+
}
37+
38+
inMemCache.update = async (prompt: string, llmKey: string, value: any): Promise<void> => {
39+
inMemCache.cache.set(getCacheKey(prompt, llmKey), value)
40+
options.cachePool.addLLMCache(options.chatflowid, inMemCache.cache)
41+
}
42+
return inMemCache
43+
}
44+
}
45+
46+
const getCacheKey = (...strings: string[]): string => hash(strings.join('_'))
47+
48+
class InMemoryCacheExtended extends BaseCache {
49+
cache: Map<string, any>
50+
51+
constructor(map: Map<string, any>) {
52+
super()
53+
this.cache = map
54+
}
55+
56+
lookup(prompt: string, llmKey: string): Promise<any | null> {
57+
return Promise.resolve(this.cache.get(getCacheKey(prompt, llmKey)) ?? null)
58+
}
59+
60+
async update(prompt: string, llmKey: string, value: any): Promise<void> {
61+
this.cache.set(getCacheKey(prompt, llmKey), value)
62+
}
63+
}
64+
65+
module.exports = { nodeClass: InMemoryCache }
Loading

packages/components/nodes/cache/MomentoCache/MomentoCache.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class MomentoCache implements INode {
1919
this.name = 'momentoCache'
2020
this.version = 1.0
2121
this.type = 'MomentoCache'
22+
this.description = 'Cache LLM response using Momento, a distributed, serverless cache'
2223
this.icon = 'momento.png'
2324
this.category = 'Cache'
2425
this.baseClasses = [this.type, ...getBaseClasses(LangchainMomentoCache)]

packages/components/nodes/cache/RedisCache/RedisCache.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class RedisCache implements INode {
1919
this.name = 'redisCache'
2020
this.version = 1.0
2121
this.type = 'RedisCache'
22+
this.description = 'Cache LLM response in Redis, useful for sharing cache across multiple processes or servers'
2223
this.icon = 'redis.svg'
2324
this.category = 'Cache'
2425
this.baseClasses = [this.type, ...getBaseClasses(LangchainRedisCache)]

packages/components/nodes/cache/UpstashRedisCache/UpstashRedisCache.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class UpstashRedisCache implements INode {
1818
this.name = 'upstashRedisCache'
1919
this.version = 1.0
2020
this.type = 'UpstashRedisCache'
21+
this.description = 'Cache LLM response in Upstash Redis, serverless data for Redis and Kafka'
2122
this.icon = 'upstash.png'
2223
this.category = 'Cache'
2324
this.baseClasses = [this.type, ...getBaseClasses(LangchainUpstashRedisCache)]

packages/components/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"node-fetch": "^2.6.11",
5858
"node-html-markdown": "^1.3.0",
5959
"notion-to-md": "^3.1.1",
60+
"object-hash": "^3.0.0",
6061
"pdf-parse": "^1.1.1",
6162
"pdfjs-dist": "^3.7.107",
6263
"pg": "^8.11.2",
@@ -73,6 +74,7 @@
7374
"devDependencies": {
7475
"@types/gulp": "4.0.9",
7576
"@types/node-fetch": "2.6.2",
77+
"@types/object-hash": "^3.0.2",
7678
"@types/pg": "^8.10.2",
7779
"@types/ws": "^8.5.3",
7880
"gulp": "^4.0.2",

packages/server/src/CachePool.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { IActiveCache } from './Interface'
2+
3+
/**
4+
* This pool is to keep track of in-memory cache used for LLM and Embeddings
5+
*/
6+
export class CachePool {
7+
activeLLMCache: IActiveCache = {}
8+
activeEmbeddingCache: IActiveCache = {}
9+
10+
/**
11+
* Add to the llm cache pool
12+
* @param {string} chatflowid
13+
* @param {Map<any, any>} value
14+
*/
15+
addLLMCache(chatflowid: string, value: Map<any, any>) {
16+
this.activeLLMCache[chatflowid] = value
17+
}
18+
19+
/**
20+
* Add to the embedding cache pool
21+
* @param {string} chatflowid
22+
* @param {Map<any, any>} value
23+
*/
24+
addEmbeddingCache(chatflowid: string, value: Map<any, any>) {
25+
this.activeEmbeddingCache[chatflowid] = value
26+
}
27+
28+
/**
29+
* Get item from llm cache pool
30+
* @param {string} chatflowid
31+
*/
32+
getLLMCache(chatflowid: string): Map<any, any> | undefined {
33+
return this.activeLLMCache[chatflowid]
34+
}
35+
36+
/**
37+
* Get item from embedding cache pool
38+
* @param {string} chatflowid
39+
*/
40+
getEmbeddingCache(chatflowid: string): Map<any, any> | undefined {
41+
return this.activeEmbeddingCache[chatflowid]
42+
}
43+
}
44+
45+
let cachePoolInstance: CachePool | undefined
46+
47+
export function getInstance(): CachePool {
48+
if (cachePoolInstance === undefined) {
49+
cachePoolInstance = new CachePool()
50+
}
51+
52+
return cachePoolInstance
53+
}

packages/server/src/Interface.ts

+4
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ export interface IActiveChatflows {
157157
}
158158
}
159159

160+
export interface IActiveCache {
161+
[key: string]: Map<any, any>
162+
}
163+
160164
export interface IOverrideConfig {
161165
node: string
162166
nodeId: string

packages/server/src/index.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ import { ChatMessage } from './database/entities/ChatMessage'
5353
import { Credential } from './database/entities/Credential'
5454
import { Tool } from './database/entities/Tool'
5555
import { ChatflowPool } from './ChatflowPool'
56+
import { CachePool } from './CachePool'
5657
import { ICommonObject, INodeOptionsValue } from 'flowise-components'
5758
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
5859

5960
export class App {
6061
app: express.Application
6162
nodesPool: NodesPool
6263
chatflowPool: ChatflowPool
64+
cachePool: CachePool
6365
AppDataSource = getDataSource()
6466

6567
constructor() {
@@ -91,6 +93,9 @@ export class App {
9193
// Initialize Rate Limit
9294
const AllChatFlow: IChatFlow[] = await getAllChatFlow()
9395
await initializeRateLimiter(AllChatFlow)
96+
97+
// Initialize cache pool
98+
this.cachePool = new CachePool()
9499
})
95100
.catch((err) => {
96101
logger.error('❌ [server]: Error during Data Source initialization:', err)
@@ -944,8 +949,10 @@ export class App {
944949
incomingInput.question,
945950
incomingInput.history,
946951
chatId,
952+
chatflowid,
947953
this.AppDataSource,
948-
incomingInput?.overrideConfig
954+
incomingInput?.overrideConfig,
955+
this.cachePool
949956
)
950957

951958
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)

packages/server/src/utils/index.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ChatMessage } from '../database/entities/ChatMessage'
3535
import { Credential } from '../database/entities/Credential'
3636
import { Tool } from '../database/entities/Tool'
3737
import { DataSource } from 'typeorm'
38+
import { CachePool } from '../CachePool'
3839

3940
const QUESTION_VAR_PREFIX = 'question'
4041
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
@@ -197,8 +198,10 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD
197198
* @param {IComponentNodes} componentNodes
198199
* @param {string} question
199200
* @param {string} chatId
201+
* @param {string} chatflowid
200202
* @param {DataSource} appDataSource
201203
* @param {ICommonObject} overrideConfig
204+
* @param {CachePool} cachePool
202205
*/
203206
export const buildLangchain = async (
204207
startingNodeIds: string[],
@@ -209,8 +212,10 @@ export const buildLangchain = async (
209212
question: string,
210213
chatHistory: IMessage[],
211214
chatId: string,
215+
chatflowid: string,
212216
appDataSource: DataSource,
213-
overrideConfig?: ICommonObject
217+
overrideConfig?: ICommonObject,
218+
cachePool?: CachePool
214219
) => {
215220
const flowNodes = cloneDeep(reactFlowNodes)
216221

@@ -245,9 +250,11 @@ export const buildLangchain = async (
245250
logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
246251
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, {
247252
chatId,
253+
chatflowid,
248254
appDataSource,
249255
databaseEntities,
250-
logger
256+
logger,
257+
cachePool
251258
})
252259
logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
253260
} catch (e: any) {

0 commit comments

Comments
 (0)