@juspay/neurolink 7.33.3 → 7.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +37 -0
- package/dist/cli/commands/config.d.ts +3 -4
- package/dist/cli/commands/config.js +2 -3
- package/dist/cli/errorHandler.d.ts +1 -0
- package/dist/cli/errorHandler.js +28 -0
- package/dist/cli/factories/commandFactory.d.ts +23 -0
- package/dist/cli/factories/commandFactory.js +375 -60
- package/dist/cli/factories/ollamaCommandFactory.js +7 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +9 -164
- package/dist/cli/loop/optionsSchema.d.ts +15 -0
- package/dist/cli/loop/optionsSchema.js +59 -0
- package/dist/cli/loop/session.d.ts +15 -0
- package/dist/cli/loop/session.js +252 -0
- package/dist/cli/parser.d.ts +1 -0
- package/dist/cli/parser.js +158 -0
- package/dist/cli/utils/ollamaUtils.js +6 -0
- package/dist/config/{conversationMemoryConfig.d.ts → conversationMemory.d.ts} +1 -1
- package/dist/core/baseProvider.js +43 -4
- package/dist/core/constants.d.ts +12 -3
- package/dist/core/constants.js +22 -6
- package/dist/core/conversationMemoryFactory.d.ts +23 -0
- package/dist/core/conversationMemoryFactory.js +144 -0
- package/dist/core/conversationMemoryInitializer.d.ts +14 -0
- package/dist/core/conversationMemoryInitializer.js +127 -0
- package/dist/core/conversationMemoryManager.d.ts +3 -2
- package/dist/core/conversationMemoryManager.js +4 -3
- package/dist/core/factory.js +19 -0
- package/dist/core/redisConversationMemoryManager.d.ts +73 -0
- package/dist/core/redisConversationMemoryManager.js +483 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/factories/providerRegistry.js +2 -0
- package/dist/lib/config/{conversationMemoryConfig.d.ts → conversationMemory.d.ts} +1 -1
- package/dist/lib/core/baseProvider.js +43 -4
- package/dist/lib/core/constants.d.ts +12 -3
- package/dist/lib/core/constants.js +22 -6
- package/dist/lib/core/conversationMemoryFactory.d.ts +23 -0
- package/dist/lib/core/conversationMemoryFactory.js +144 -0
- package/dist/lib/core/conversationMemoryInitializer.d.ts +14 -0
- package/dist/lib/core/conversationMemoryInitializer.js +127 -0
- package/dist/lib/core/conversationMemoryManager.d.ts +3 -2
- package/dist/lib/core/conversationMemoryManager.js +4 -3
- package/dist/lib/core/factory.js +19 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +73 -0
- package/dist/lib/core/redisConversationMemoryManager.js +483 -0
- package/dist/lib/core/types.d.ts +1 -1
- package/dist/lib/factories/providerRegistry.js +2 -0
- package/dist/lib/mcp/servers/aiProviders/aiWorkflowTools.js +2 -2
- package/dist/lib/neurolink.d.ts +15 -9
- package/dist/lib/neurolink.js +218 -67
- package/dist/lib/providers/amazonBedrock.d.ts +4 -4
- package/dist/lib/providers/amazonBedrock.js +2 -2
- package/dist/lib/providers/anthropic.d.ts +4 -4
- package/dist/lib/providers/anthropic.js +3 -12
- package/dist/lib/providers/anthropicBaseProvider.js +1 -2
- package/dist/lib/providers/azureOpenai.d.ts +4 -4
- package/dist/lib/providers/azureOpenai.js +49 -8
- package/dist/lib/providers/googleAiStudio.d.ts +4 -4
- package/dist/lib/providers/googleAiStudio.js +2 -2
- package/dist/lib/providers/googleVertex.js +2 -2
- package/dist/lib/providers/huggingFace.d.ts +4 -4
- package/dist/lib/providers/huggingFace.js +1 -2
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +1 -2
- package/dist/lib/providers/mistral.d.ts +4 -4
- package/dist/lib/providers/mistral.js +4 -4
- package/dist/lib/providers/ollama.js +7 -8
- package/dist/lib/providers/openAI.d.ts +4 -4
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/providers/openaiCompatible.js +5 -2
- package/dist/lib/providers/sagemaker/language-model.d.ts +5 -0
- package/dist/lib/providers/sagemaker/language-model.js +9 -1
- package/dist/lib/session/globalSessionState.d.ts +27 -0
- package/dist/lib/session/globalSessionState.js +77 -0
- package/dist/lib/types/{conversationTypes.d.ts → conversation.d.ts} +32 -0
- package/dist/lib/types/generateTypes.d.ts +1 -1
- package/dist/lib/types/streamTypes.d.ts +1 -1
- package/dist/lib/utils/conversationMemory.d.ts +22 -0
- package/dist/lib/utils/conversationMemory.js +121 -0
- package/dist/lib/utils/conversationMemoryUtils.d.ts +1 -1
- package/dist/lib/utils/conversationMemoryUtils.js +2 -2
- package/dist/lib/utils/messageBuilder.d.ts +1 -1
- package/dist/lib/utils/messageBuilder.js +1 -1
- package/dist/lib/utils/providerHealth.js +7 -3
- package/dist/lib/utils/redis.d.ts +42 -0
- package/dist/lib/utils/redis.js +263 -0
- package/dist/lib/utils/tokenLimits.d.ts +2 -2
- package/dist/lib/utils/tokenLimits.js +10 -3
- package/dist/mcp/servers/aiProviders/aiWorkflowTools.js +2 -2
- package/dist/neurolink.d.ts +15 -9
- package/dist/neurolink.js +218 -67
- package/dist/providers/amazonBedrock.d.ts +4 -4
- package/dist/providers/amazonBedrock.js +2 -2
- package/dist/providers/anthropic.d.ts +4 -4
- package/dist/providers/anthropic.js +3 -12
- package/dist/providers/anthropicBaseProvider.js +1 -2
- package/dist/providers/azureOpenai.d.ts +4 -4
- package/dist/providers/azureOpenai.js +49 -8
- package/dist/providers/googleAiStudio.d.ts +4 -4
- package/dist/providers/googleAiStudio.js +2 -2
- package/dist/providers/googleVertex.js +2 -2
- package/dist/providers/huggingFace.d.ts +4 -4
- package/dist/providers/huggingFace.js +1 -2
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +1 -2
- package/dist/providers/mistral.d.ts +4 -4
- package/dist/providers/mistral.js +4 -4
- package/dist/providers/ollama.js +7 -8
- package/dist/providers/openAI.d.ts +4 -4
- package/dist/providers/openAI.js +2 -2
- package/dist/providers/openaiCompatible.js +5 -2
- package/dist/providers/sagemaker/language-model.d.ts +5 -0
- package/dist/providers/sagemaker/language-model.js +9 -1
- package/dist/session/globalSessionState.d.ts +27 -0
- package/dist/session/globalSessionState.js +77 -0
- package/dist/types/{conversationTypes.d.ts → conversation.d.ts} +32 -0
- package/dist/types/generateTypes.d.ts +1 -1
- package/dist/types/streamTypes.d.ts +1 -1
- package/dist/utils/conversationMemory.d.ts +22 -0
- package/dist/utils/conversationMemory.js +121 -0
- package/dist/utils/conversationMemoryUtils.d.ts +1 -1
- package/dist/utils/conversationMemoryUtils.js +2 -2
- package/dist/utils/messageBuilder.d.ts +1 -1
- package/dist/utils/messageBuilder.js +1 -1
- package/dist/utils/providerHealth.js +7 -3
- package/dist/utils/redis.d.ts +42 -0
- package/dist/utils/redis.js +263 -0
- package/dist/utils/tokenLimits.d.ts +2 -2
- package/dist/utils/tokenLimits.js +10 -3
- package/package.json +3 -1
- /package/dist/config/{conversationMemoryConfig.js → conversationMemory.js} +0 -0
- /package/dist/lib/config/{conversationMemoryConfig.js → conversationMemory.js} +0 -0
- /package/dist/lib/types/{conversationTypes.js → conversation.js} +0 -0
- /package/dist/types/{conversationTypes.js → conversation.js} +0 -0
@@ -2,7 +2,7 @@
|
|
2
2
|
* Conversation Memory Manager for NeuroLink
|
3
3
|
* Handles in-memory conversation storage, session management, and context injection
|
4
4
|
*/
|
5
|
-
import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage } from "../types/
|
5
|
+
import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage } from "../types/conversation.js";
|
6
6
|
export declare class ConversationMemoryManager {
|
7
7
|
private sessions;
|
8
8
|
config: ConversationMemoryConfig;
|
@@ -20,8 +20,9 @@ export declare class ConversationMemoryManager {
|
|
20
20
|
/**
|
21
21
|
* Build context messages for AI prompt injection (ULTRA-OPTIMIZED)
|
22
22
|
* Returns pre-stored message array with zero conversion overhead
|
23
|
+
* Now consistently async to match Redis implementation
|
23
24
|
*/
|
24
|
-
buildContextMessages(sessionId: string): ChatMessage[]
|
25
|
+
buildContextMessages(sessionId: string): Promise<ChatMessage[]>;
|
25
26
|
getSession(sessionId: string): SessionMemory | undefined;
|
26
27
|
createSummarySystemMessage(content: string): ChatMessage;
|
27
28
|
private _summarizeSession;
|
@@ -2,8 +2,8 @@
|
|
2
2
|
* Conversation Memory Manager for NeuroLink
|
3
3
|
* Handles in-memory conversation storage, session management, and context injection
|
4
4
|
*/
|
5
|
-
import { ConversationMemoryError } from "../types/
|
6
|
-
import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/
|
5
|
+
import { ConversationMemoryError } from "../types/conversation.js";
|
6
|
+
import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
|
7
7
|
import { logger } from "../utils/logger.js";
|
8
8
|
import { NeuroLink } from "../neurolink.js";
|
9
9
|
export class ConversationMemoryManager {
|
@@ -74,8 +74,9 @@ export class ConversationMemoryManager {
|
|
74
74
|
/**
|
75
75
|
* Build context messages for AI prompt injection (ULTRA-OPTIMIZED)
|
76
76
|
* Returns pre-stored message array with zero conversion overhead
|
77
|
+
* Now consistently async to match Redis implementation
|
77
78
|
*/
|
78
|
-
buildContextMessages(sessionId) {
|
79
|
+
async buildContextMessages(sessionId) {
|
79
80
|
const session = this.sessions.get(sessionId);
|
80
81
|
return session ? session.messages : [];
|
81
82
|
}
|
package/dist/core/factory.js
CHANGED
@@ -111,6 +111,25 @@ export class AIProviderFactory {
|
|
111
111
|
logger.debug(`[${functionTag}] No Vertex environment variables found (VERTEX_MODEL)`);
|
112
112
|
}
|
113
113
|
}
|
114
|
+
else if (providerName.toLowerCase().includes("azure")) {
|
115
|
+
const envModel = process.env.AZURE_OPENAI_MODEL ||
|
116
|
+
process.env.AZURE_OPENAI_DEPLOYMENT ||
|
117
|
+
process.env.AZURE_OPENAI_DEPLOYMENT_ID;
|
118
|
+
if (envModel) {
|
119
|
+
resolvedModelName = envModel;
|
120
|
+
logger.debug(`[${functionTag}] Environment variable found for Azure`, {
|
121
|
+
envVariable: process.env.AZURE_OPENAI_MODEL
|
122
|
+
? "AZURE_OPENAI_MODEL"
|
123
|
+
: process.env.AZURE_OPENAI_DEPLOYMENT
|
124
|
+
? "AZURE_OPENAI_DEPLOYMENT"
|
125
|
+
: "AZURE_OPENAI_DEPLOYMENT_ID",
|
126
|
+
resolvedModel: envModel,
|
127
|
+
});
|
128
|
+
}
|
129
|
+
else {
|
130
|
+
logger.debug(`[${functionTag}] No Azure environment variables found (AZURE_OPENAI_MODEL, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_DEPLOYMENT_ID)`);
|
131
|
+
}
|
132
|
+
}
|
114
133
|
else {
|
115
134
|
logger.debug(`[${functionTag}] Provider ${providerName} - no environment variable check implemented`);
|
116
135
|
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
/**
|
2
|
+
* Redis Conversation Memory Manager for NeuroLink
|
3
|
+
* Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
|
4
|
+
*/
|
5
|
+
import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage, RedisStorageConfig } from "../types/conversation.js";
|
6
|
+
/**
|
7
|
+
* Redis-based implementation of the ConversationMemoryManager
|
8
|
+
* Uses the same interface but stores data in Redis
|
9
|
+
*/
|
10
|
+
export declare class RedisConversationMemoryManager {
|
11
|
+
config: ConversationMemoryConfig;
|
12
|
+
private isInitialized;
|
13
|
+
private redisConfig;
|
14
|
+
private redisClient;
|
15
|
+
constructor(config: ConversationMemoryConfig, redisConfig?: RedisStorageConfig);
|
16
|
+
/**
|
17
|
+
* Initialize the memory manager with Redis connection
|
18
|
+
*/
|
19
|
+
initialize(): Promise<void>;
|
20
|
+
/**
|
21
|
+
* Store a conversation turn for a session
|
22
|
+
*/
|
23
|
+
storeConversationTurn(sessionId: string, userId: string | undefined, userMessage: string, aiResponse: string): Promise<void>;
|
24
|
+
/**
|
25
|
+
* Build context messages for AI prompt injection
|
26
|
+
*/
|
27
|
+
buildContextMessages(sessionId: string): Promise<ChatMessage[]>;
|
28
|
+
/**
|
29
|
+
* Get session data
|
30
|
+
*/
|
31
|
+
getSession(sessionId: string): Promise<SessionMemory | undefined>;
|
32
|
+
/**
|
33
|
+
* Create summary system message
|
34
|
+
*/
|
35
|
+
createSummarySystemMessage(content: string): ChatMessage;
|
36
|
+
/**
|
37
|
+
* Close Redis connection
|
38
|
+
*/
|
39
|
+
close(): Promise<void>;
|
40
|
+
/**
|
41
|
+
* Get statistics about conversation storage
|
42
|
+
*/
|
43
|
+
getStats(): Promise<ConversationMemoryStats>;
|
44
|
+
/**
|
45
|
+
* Clear a specific session
|
46
|
+
*/
|
47
|
+
clearSession(sessionId: string): Promise<boolean>;
|
48
|
+
/**
|
49
|
+
* Clear all sessions
|
50
|
+
*/
|
51
|
+
clearAllSessions(): Promise<void>;
|
52
|
+
/**
|
53
|
+
* Summarize messages for a session
|
54
|
+
*/
|
55
|
+
private _summarizeMessages;
|
56
|
+
/**
|
57
|
+
* Create summarization prompt
|
58
|
+
*/
|
59
|
+
private _createSummarizationPrompt;
|
60
|
+
/**
|
61
|
+
* Ensure Redis client is initialized
|
62
|
+
*/
|
63
|
+
private ensureInitialized;
|
64
|
+
/**
|
65
|
+
* Enforce session limit
|
66
|
+
*
|
67
|
+
* NOTE: This function is maintained only for backward compatibility with the
|
68
|
+
* in-memory implementation. In Redis, we do not actually delete sessions based on
|
69
|
+
* a global limit, as this could cause race conditions in a distributed environment.
|
70
|
+
* Each session is managed independently with its own TTL.
|
71
|
+
*/
|
72
|
+
private enforceSessionLimit;
|
73
|
+
}
|
@@ -0,0 +1,483 @@
|
|
1
|
+
/**
|
2
|
+
* Redis Conversation Memory Manager for NeuroLink
|
3
|
+
* Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
|
4
|
+
*/
|
5
|
+
import { ConversationMemoryError } from "../types/conversation.js";
|
6
|
+
import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
|
7
|
+
import { logger } from "../utils/logger.js";
|
8
|
+
import { NeuroLink } from "../neurolink.js";
|
9
|
+
import { createRedisClient, getSessionKey, getNormalizedConfig, serializeMessages, deserializeMessages, scanKeys, } from "../utils/redis.js";
|
10
|
+
/**
|
11
|
+
* Redis-based implementation of the ConversationMemoryManager
|
12
|
+
* Uses the same interface but stores data in Redis
|
13
|
+
*/
|
14
|
+
export class RedisConversationMemoryManager {
|
15
|
+
config;
|
16
|
+
isInitialized = false;
|
17
|
+
redisConfig;
|
18
|
+
redisClient = null;
|
19
|
+
constructor(config, redisConfig = {}) {
|
20
|
+
this.config = config;
|
21
|
+
this.redisConfig = getNormalizedConfig(redisConfig);
|
22
|
+
}
|
23
|
+
/**
|
24
|
+
* Initialize the memory manager with Redis connection
|
25
|
+
*/
|
26
|
+
async initialize() {
|
27
|
+
if (this.isInitialized) {
|
28
|
+
logger.debug("[RedisConversationMemoryManager] Already initialized, skipping");
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
try {
|
32
|
+
logger.debug("[RedisConversationMemoryManager] Initializing with config", {
|
33
|
+
host: this.redisConfig.host,
|
34
|
+
port: this.redisConfig.port,
|
35
|
+
keyPrefix: this.redisConfig.keyPrefix,
|
36
|
+
ttl: this.redisConfig.ttl,
|
37
|
+
});
|
38
|
+
this.redisClient = await createRedisClient(this.redisConfig);
|
39
|
+
this.isInitialized = true;
|
40
|
+
logger.info("RedisConversationMemoryManager initialized", {
|
41
|
+
storage: "redis",
|
42
|
+
host: this.redisConfig.host,
|
43
|
+
port: this.redisConfig.port,
|
44
|
+
maxSessions: this.config.maxSessions,
|
45
|
+
maxTurnsPerSession: this.config.maxTurnsPerSession,
|
46
|
+
});
|
47
|
+
logger.debug("[RedisConversationMemoryManager] Redis client created successfully", {
|
48
|
+
clientType: this.redisClient?.constructor?.name || "unknown",
|
49
|
+
isConnected: !!this.redisClient,
|
50
|
+
});
|
51
|
+
}
|
52
|
+
catch (error) {
|
53
|
+
logger.error("[RedisConversationMemoryManager] Failed to initialize", {
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
55
|
+
stack: error instanceof Error ? error.stack : undefined,
|
56
|
+
config: {
|
57
|
+
host: this.redisConfig.host,
|
58
|
+
port: this.redisConfig.port,
|
59
|
+
},
|
60
|
+
});
|
61
|
+
throw new ConversationMemoryError("Failed to initialize Redis conversation memory", "CONFIG_ERROR", { error: error instanceof Error ? error.message : String(error) });
|
62
|
+
}
|
63
|
+
}
|
64
|
+
/**
|
65
|
+
* Store a conversation turn for a session
|
66
|
+
*/
|
67
|
+
async storeConversationTurn(sessionId, userId, userMessage, aiResponse) {
|
68
|
+
logger.debug("[RedisConversationMemoryManager] Storing conversation turn", {
|
69
|
+
sessionId,
|
70
|
+
userId,
|
71
|
+
userMessageLength: userMessage.length,
|
72
|
+
aiResponseLength: aiResponse.length,
|
73
|
+
});
|
74
|
+
await this.ensureInitialized();
|
75
|
+
try {
|
76
|
+
if (!this.redisClient) {
|
77
|
+
throw new Error("Redis client not initialized");
|
78
|
+
}
|
79
|
+
// Generate Redis key
|
80
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId);
|
81
|
+
// Get existing messages
|
82
|
+
const messagesData = await this.redisClient.get(redisKey);
|
83
|
+
const messages = deserializeMessages(messagesData);
|
84
|
+
logger.info("[RedisConversationMemoryManager] Deserialized messages", {
|
85
|
+
messageCount: messages.length,
|
86
|
+
roles: messages.map((m) => m.role),
|
87
|
+
});
|
88
|
+
// Add new messages
|
89
|
+
messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
|
90
|
+
logger.info("[RedisConversationMemoryManager] Added new messages", {
|
91
|
+
newMessageCount: messages.length,
|
92
|
+
latestMessages: [
|
93
|
+
{
|
94
|
+
role: messages[messages.length - 2]?.role,
|
95
|
+
contentLength: messages[messages.length - 2]?.content.length,
|
96
|
+
},
|
97
|
+
{
|
98
|
+
role: messages[messages.length - 1]?.role,
|
99
|
+
contentLength: messages[messages.length - 1]?.content.length,
|
100
|
+
},
|
101
|
+
],
|
102
|
+
});
|
103
|
+
// Handle summarization or message limit
|
104
|
+
if (this.config.enableSummarization) {
|
105
|
+
const userAssistantCount = messages.filter((msg) => msg.role === "user" || msg.role === "assistant").length;
|
106
|
+
const currentTurnCount = Math.floor(userAssistantCount / MESSAGES_PER_TURN);
|
107
|
+
logger.debug("[RedisConversationMemoryManager] Checking summarization threshold", {
|
108
|
+
userAssistantCount,
|
109
|
+
currentTurnCount,
|
110
|
+
summarizationThreshold: this.config.summarizationThresholdTurns || 20,
|
111
|
+
shouldSummarize: currentTurnCount >=
|
112
|
+
(this.config.summarizationThresholdTurns || 20),
|
113
|
+
});
|
114
|
+
if (currentTurnCount >= (this.config.summarizationThresholdTurns || 20)) {
|
115
|
+
await this._summarizeMessages(sessionId, userId, messages);
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
else {
|
120
|
+
const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
|
121
|
+
MESSAGES_PER_TURN;
|
122
|
+
logger.debug("[RedisConversationMemoryManager] Checking message limit", {
|
123
|
+
currentMessageCount: messages.length,
|
124
|
+
maxMessages,
|
125
|
+
shouldTrimMessages: messages.length > maxMessages,
|
126
|
+
});
|
127
|
+
if (messages.length > maxMessages) {
|
128
|
+
const trimCount = messages.length - maxMessages;
|
129
|
+
logger.debug("[RedisConversationMemoryManager] Trimming messages", {
|
130
|
+
beforeCount: messages.length,
|
131
|
+
trimCount,
|
132
|
+
afterCount: maxMessages,
|
133
|
+
});
|
134
|
+
messages.splice(0, messages.length - maxMessages);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
// Save updated messages
|
138
|
+
const serializedData = serializeMessages(messages);
|
139
|
+
logger.debug("[RedisConversationMemoryManager] Saving messages to Redis", {
|
140
|
+
redisKey,
|
141
|
+
messageCount: messages.length,
|
142
|
+
serializedDataLength: serializedData.length,
|
143
|
+
});
|
144
|
+
await this.redisClient.set(redisKey, serializedData);
|
145
|
+
// Set TTL if configured
|
146
|
+
if (this.redisConfig.ttl > 0) {
|
147
|
+
logger.debug("[RedisConversationMemoryManager] Setting Redis TTL", {
|
148
|
+
redisKey,
|
149
|
+
ttl: this.redisConfig.ttl,
|
150
|
+
});
|
151
|
+
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
152
|
+
}
|
153
|
+
// Enforce session limit
|
154
|
+
await this.enforceSessionLimit();
|
155
|
+
logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
|
156
|
+
sessionId,
|
157
|
+
totalMessages: messages.length,
|
158
|
+
});
|
159
|
+
}
|
160
|
+
catch (error) {
|
161
|
+
throw new ConversationMemoryError(`Failed to store conversation turn in Redis for session ${sessionId}`, "STORAGE_ERROR", {
|
162
|
+
sessionId,
|
163
|
+
error: error instanceof Error ? error.message : String(error),
|
164
|
+
});
|
165
|
+
}
|
166
|
+
}
|
167
|
+
/**
|
168
|
+
* Build context messages for AI prompt injection
|
169
|
+
*/
|
170
|
+
async buildContextMessages(sessionId) {
|
171
|
+
logger.info("[RedisConversationMemoryManager] Building context messages", {
|
172
|
+
sessionId,
|
173
|
+
method: "buildContextMessages",
|
174
|
+
});
|
175
|
+
await this.ensureInitialized();
|
176
|
+
if (!this.redisClient) {
|
177
|
+
logger.warn("[RedisConversationMemoryManager] Redis client not available, returning empty context", {
|
178
|
+
sessionId,
|
179
|
+
});
|
180
|
+
return [];
|
181
|
+
}
|
182
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId);
|
183
|
+
logger.info("[RedisConversationMemoryManager] Getting messages from Redis", {
|
184
|
+
sessionId,
|
185
|
+
redisKey,
|
186
|
+
});
|
187
|
+
const messagesData = await this.redisClient.get(redisKey);
|
188
|
+
logger.info("[RedisConversationMemoryManager] Retrieved message data from Redis", {
|
189
|
+
sessionId,
|
190
|
+
redisKey,
|
191
|
+
hasData: !!messagesData,
|
192
|
+
dataLength: messagesData?.length || 0,
|
193
|
+
});
|
194
|
+
const messages = deserializeMessages(messagesData);
|
195
|
+
logger.info("[RedisConversationMemoryManager] Deserialized messages for context", {
|
196
|
+
sessionId,
|
197
|
+
messageCount: messages.length,
|
198
|
+
messageRoles: messages.map((m) => m.role),
|
199
|
+
firstMessagePreview: messages[0]?.content?.substring(0, 50),
|
200
|
+
lastMessagePreview: messages[messages.length - 1]?.content?.substring(0, 50),
|
201
|
+
});
|
202
|
+
return messages;
|
203
|
+
}
|
204
|
+
/**
|
205
|
+
* Get session data
|
206
|
+
*/
|
207
|
+
async getSession(sessionId) {
|
208
|
+
logger.debug("[RedisConversationMemoryManager] Getting session", {
|
209
|
+
sessionId,
|
210
|
+
method: "getSession",
|
211
|
+
});
|
212
|
+
await this.ensureInitialized();
|
213
|
+
if (!this.redisClient) {
|
214
|
+
logger.warn("[RedisConversationMemoryManager] Redis client not available", {
|
215
|
+
sessionId,
|
216
|
+
});
|
217
|
+
return undefined;
|
218
|
+
}
|
219
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId);
|
220
|
+
logger.debug("[RedisConversationMemoryManager] Getting session data from Redis", {
|
221
|
+
sessionId,
|
222
|
+
redisKey,
|
223
|
+
});
|
224
|
+
const messagesData = await this.redisClient.get(redisKey);
|
225
|
+
logger.debug("[RedisConversationMemoryManager] Retrieved session data", {
|
226
|
+
sessionId,
|
227
|
+
hasData: !!messagesData,
|
228
|
+
dataLength: messagesData?.length || 0,
|
229
|
+
});
|
230
|
+
if (!messagesData) {
|
231
|
+
logger.debug("[RedisConversationMemoryManager] No session data found", {
|
232
|
+
sessionId,
|
233
|
+
redisKey,
|
234
|
+
});
|
235
|
+
return undefined;
|
236
|
+
}
|
237
|
+
const messages = deserializeMessages(messagesData);
|
238
|
+
logger.debug("[RedisConversationMemoryManager] Deserialized session messages", {
|
239
|
+
sessionId,
|
240
|
+
messageCount: messages.length,
|
241
|
+
messageRoles: messages.map((m) => m.role),
|
242
|
+
});
|
243
|
+
// We don't store the full SessionMemory object in Redis,
|
244
|
+
// just the messages, so we recreate the SessionMemory object here
|
245
|
+
const session = {
|
246
|
+
sessionId,
|
247
|
+
messages,
|
248
|
+
createdAt: Date.now(), // We don't have this information
|
249
|
+
lastActivity: Date.now(), // We don't have this information
|
250
|
+
};
|
251
|
+
logger.debug("[RedisConversationMemoryManager] Created session memory object", {
|
252
|
+
sessionId,
|
253
|
+
messageCount: session.messages.length,
|
254
|
+
});
|
255
|
+
return session;
|
256
|
+
}
|
257
|
+
/**
|
258
|
+
* Create summary system message
|
259
|
+
*/
|
260
|
+
createSummarySystemMessage(content) {
|
261
|
+
return {
|
262
|
+
role: "system",
|
263
|
+
content: `Summary of previous conversation turns:\n\n${content}`,
|
264
|
+
};
|
265
|
+
}
|
266
|
+
/**
|
267
|
+
* Close Redis connection
|
268
|
+
*/
|
269
|
+
async close() {
|
270
|
+
if (this.redisClient) {
|
271
|
+
await this.redisClient.quit();
|
272
|
+
this.redisClient = null;
|
273
|
+
this.isInitialized = false;
|
274
|
+
logger.info("Redis connection closed");
|
275
|
+
}
|
276
|
+
}
|
277
|
+
/**
|
278
|
+
* Get statistics about conversation storage
|
279
|
+
*/
|
280
|
+
async getStats() {
|
281
|
+
await this.ensureInitialized();
|
282
|
+
if (!this.redisClient) {
|
283
|
+
return { totalSessions: 0, totalTurns: 0 };
|
284
|
+
}
|
285
|
+
// Get all session keys using SCAN instead of KEYS to avoid blocking
|
286
|
+
const pattern = `${this.redisConfig.keyPrefix}*`;
|
287
|
+
const keys = await scanKeys(this.redisClient, pattern);
|
288
|
+
logger.debug("[RedisConversationMemoryManager] Got session keys with SCAN", {
|
289
|
+
pattern,
|
290
|
+
keyCount: keys.length,
|
291
|
+
});
|
292
|
+
// Count messages in each session
|
293
|
+
let totalTurns = 0;
|
294
|
+
for (const key of keys) {
|
295
|
+
const messagesData = await this.redisClient.get(key);
|
296
|
+
const messages = deserializeMessages(messagesData);
|
297
|
+
totalTurns += messages.length / MESSAGES_PER_TURN;
|
298
|
+
}
|
299
|
+
return {
|
300
|
+
totalSessions: keys.length,
|
301
|
+
totalTurns,
|
302
|
+
};
|
303
|
+
}
|
304
|
+
/**
|
305
|
+
* Clear a specific session
|
306
|
+
*/
|
307
|
+
async clearSession(sessionId) {
|
308
|
+
await this.ensureInitialized();
|
309
|
+
if (!this.redisClient) {
|
310
|
+
return false;
|
311
|
+
}
|
312
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId);
|
313
|
+
const result = await this.redisClient.del(redisKey);
|
314
|
+
if (result > 0) {
|
315
|
+
logger.info("Redis session cleared", { sessionId });
|
316
|
+
return true;
|
317
|
+
}
|
318
|
+
return false;
|
319
|
+
}
|
320
|
+
/**
|
321
|
+
* Clear all sessions
|
322
|
+
*/
|
323
|
+
async clearAllSessions() {
|
324
|
+
await this.ensureInitialized();
|
325
|
+
if (!this.redisClient) {
|
326
|
+
return;
|
327
|
+
}
|
328
|
+
const pattern = `${this.redisConfig.keyPrefix}*`;
|
329
|
+
// Use SCAN instead of KEYS to avoid blocking the server
|
330
|
+
const keys = await scanKeys(this.redisClient, pattern);
|
331
|
+
logger.debug("[RedisConversationMemoryManager] Got session keys with SCAN for clearing", {
|
332
|
+
pattern,
|
333
|
+
keyCount: keys.length,
|
334
|
+
});
|
335
|
+
if (keys.length > 0) {
|
336
|
+
// Process keys in batches to avoid blocking Redis for too long
|
337
|
+
const batchSize = 100;
|
338
|
+
for (let i = 0; i < keys.length; i += batchSize) {
|
339
|
+
const batch = keys.slice(i, i + batchSize);
|
340
|
+
await this.redisClient.del(batch);
|
341
|
+
logger.debug("[RedisConversationMemoryManager] Cleared batch of sessions", {
|
342
|
+
batchIndex: Math.floor(i / batchSize) + 1,
|
343
|
+
batchSize: batch.length,
|
344
|
+
totalProcessed: i + batch.length,
|
345
|
+
totalKeys: keys.length,
|
346
|
+
});
|
347
|
+
}
|
348
|
+
logger.info("All Redis sessions cleared", { clearedCount: keys.length });
|
349
|
+
}
|
350
|
+
}
|
351
|
+
/**
|
352
|
+
* Summarize messages for a session
|
353
|
+
*/
|
354
|
+
async _summarizeMessages(sessionId, userId, messages) {
|
355
|
+
logger.info(`[RedisConversationMemory] Summarizing session ${sessionId}...`);
|
356
|
+
logger.debug("[RedisConversationMemoryManager] Starting message summarization", {
|
357
|
+
sessionId,
|
358
|
+
userId,
|
359
|
+
messageCount: messages.length,
|
360
|
+
messageTypes: messages.map((m) => m.role),
|
361
|
+
});
|
362
|
+
const targetTurns = this.config.summarizationTargetTurns || 10;
|
363
|
+
const splitIndex = Math.max(0, messages.length - targetTurns * MESSAGES_PER_TURN);
|
364
|
+
const messagesToSummarize = messages.slice(0, splitIndex);
|
365
|
+
const recentMessages = messages.slice(splitIndex);
|
366
|
+
if (messagesToSummarize.length === 0) {
|
367
|
+
return;
|
368
|
+
}
|
369
|
+
const summarizationPrompt = this._createSummarizationPrompt(messagesToSummarize);
|
370
|
+
const summarizer = new NeuroLink({
|
371
|
+
conversationMemory: { enabled: false },
|
372
|
+
});
|
373
|
+
try {
|
374
|
+
const providerName = this.config.summarizationProvider;
|
375
|
+
// Map provider names to correct format
|
376
|
+
let mappedProvider = providerName;
|
377
|
+
if (providerName === "vertex") {
|
378
|
+
mappedProvider = "googlevertex";
|
379
|
+
}
|
380
|
+
if (!mappedProvider) {
|
381
|
+
logger.error(`[RedisConversationMemory] Missing summarization provider`);
|
382
|
+
return;
|
383
|
+
}
|
384
|
+
logger.debug(`[RedisConversationMemory] Using provider: ${mappedProvider} for summarization`);
|
385
|
+
const summaryResult = await summarizer.generate({
|
386
|
+
input: { text: summarizationPrompt },
|
387
|
+
provider: mappedProvider,
|
388
|
+
model: this.config.summarizationModel,
|
389
|
+
disableTools: true,
|
390
|
+
});
|
391
|
+
if (!this.redisClient) {
|
392
|
+
throw new Error("Redis client not initialized");
|
393
|
+
}
|
394
|
+
if (summaryResult.content) {
|
395
|
+
const updatedMessages = [
|
396
|
+
this.createSummarySystemMessage(summaryResult.content),
|
397
|
+
...recentMessages,
|
398
|
+
];
|
399
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId);
|
400
|
+
await this.redisClient.set(redisKey, serializeMessages(updatedMessages));
|
401
|
+
// Set TTL if configured
|
402
|
+
if (this.redisConfig.ttl > 0) {
|
403
|
+
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
404
|
+
}
|
405
|
+
logger.info(`[RedisConversationMemory] Summarization complete for session ${sessionId}.`);
|
406
|
+
}
|
407
|
+
else {
|
408
|
+
logger.warn(`[RedisConversationMemory] Summarization failed for session ${sessionId}. History not modified.`);
|
409
|
+
}
|
410
|
+
}
|
411
|
+
catch (error) {
|
412
|
+
logger.error(`[RedisConversationMemory] Error during summarization for session ${sessionId}`, { error });
|
413
|
+
}
|
414
|
+
}
|
415
|
+
/**
|
416
|
+
* Create summarization prompt
|
417
|
+
*/
|
418
|
+
_createSummarizationPrompt(history) {
|
419
|
+
const formattedHistory = history
|
420
|
+
.map((msg) => `${msg.role}: ${msg.content}`)
|
421
|
+
.join("\n\n");
|
422
|
+
return `
|
423
|
+
You are a context summarization AI. Your task is to condense the following conversation history for another AI assistant.
|
424
|
+
The summary must be a concise, third-person narrative that retains all critical information, including key entities, technical details, decisions made, and any specific dates or times mentioned.
|
425
|
+
Ensure the summary flows logically and is ready to be used as context for the next turn in the conversation.
|
426
|
+
|
427
|
+
Conversation History to Summarize:
|
428
|
+
---
|
429
|
+
${formattedHistory}
|
430
|
+
---
|
431
|
+
`.trim();
|
432
|
+
}
|
433
|
+
/**
|
434
|
+
* Ensure Redis client is initialized
|
435
|
+
*/
|
436
|
+
async ensureInitialized() {
|
437
|
+
logger.debug("[RedisConversationMemoryManager] Ensuring initialization");
|
438
|
+
if (!this.isInitialized) {
|
439
|
+
logger.debug("[RedisConversationMemoryManager] Not initialized, initializing now");
|
440
|
+
await this.initialize();
|
441
|
+
}
|
442
|
+
else {
|
443
|
+
logger.debug("[RedisConversationMemoryManager] Already initialized");
|
444
|
+
}
|
445
|
+
}
|
446
|
+
/**
|
447
|
+
* Enforce session limit
|
448
|
+
*
|
449
|
+
* NOTE: This function is maintained only for backward compatibility with the
|
450
|
+
* in-memory implementation. In Redis, we do not actually delete sessions based on
|
451
|
+
* a global limit, as this could cause race conditions in a distributed environment.
|
452
|
+
* Each session is managed independently with its own TTL.
|
453
|
+
*/
|
454
|
+
async enforceSessionLimit() {
|
455
|
+
logger.debug("[RedisConversationMemoryManager] Enforcing session limit (compatibility function)");
|
456
|
+
if (!this.redisClient) {
|
457
|
+
logger.debug("[RedisConversationMemoryManager] No Redis client, skipping session limit check");
|
458
|
+
return;
|
459
|
+
}
|
460
|
+
const maxSessions = this.config.maxSessions || DEFAULT_MAX_SESSIONS;
|
461
|
+
const pattern = `${this.redisConfig.keyPrefix}*`;
|
462
|
+
logger.debug("[RedisConversationMemoryManager] Listing all session keys", {
|
463
|
+
pattern,
|
464
|
+
maxSessions,
|
465
|
+
});
|
466
|
+
// Use SCAN instead of KEYS to avoid blocking the server
|
467
|
+
const keys = await scanKeys(this.redisClient, pattern);
|
468
|
+
logger.debug("[RedisConversationMemoryManager] Found existing sessions using SCAN", {
|
469
|
+
sessionCount: keys.length,
|
470
|
+
maxSessions,
|
471
|
+
needsTrimming: keys.length > maxSessions,
|
472
|
+
});
|
473
|
+
// In the Redis implementation, we intentionally do not delete sessions based on a global limit.
|
474
|
+
// Each session is managed independently with its own TTL.
|
475
|
+
if (keys.length > maxSessions) {
|
476
|
+
logger.info("Redis session count exceeds limit, but not enforcing deletion", {
|
477
|
+
currentCount: keys.length,
|
478
|
+
maxSessions,
|
479
|
+
reason: "Redis sessions are managed independently with TTL",
|
480
|
+
});
|
481
|
+
}
|
482
|
+
}
|
483
|
+
}
|
package/dist/core/types.d.ts
CHANGED
@@ -3,7 +3,7 @@ import type { ZodUnknownSchema, ValidationSchema } from "../types/typeAliases.js
|
|
3
3
|
import type { GenerateResult } from "../types/generateTypes.js";
|
4
4
|
import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
|
5
5
|
import type { JsonValue } from "../types/common.js";
|
6
|
-
import type { ChatMessage, ConversationMemoryConfig } from "../types/
|
6
|
+
import type { ChatMessage, ConversationMemoryConfig } from "../types/conversation.js";
|
7
7
|
import type { TokenUsage, AnalyticsData } from "../types/analytics.js";
|
8
8
|
import type { EvaluationData } from "../index.js";
|
9
9
|
export type { EvaluationData };
|
@@ -49,6 +49,8 @@ export class ProviderRegistry {
|
|
49
49
|
const { AzureOpenAIProvider } = await import("../providers/azureOpenai.js");
|
50
50
|
return new AzureOpenAIProvider(modelName);
|
51
51
|
}, process.env.AZURE_MODEL ||
|
52
|
+
process.env.AZURE_OPENAI_MODEL ||
|
53
|
+
process.env.AZURE_OPENAI_DEPLOYMENT ||
|
52
54
|
process.env.AZURE_OPENAI_DEPLOYMENT_ID ||
|
53
55
|
"gpt-4o-mini", ["azure", "azureOpenai"]);
|
54
56
|
// Register Google Vertex AI provider
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* Conversation Memory Configuration
|
3
3
|
* Provides default values for conversation memory feature with environment variable support
|
4
4
|
*/
|
5
|
-
import type { ConversationMemoryConfig } from "../types/
|
5
|
+
import type { ConversationMemoryConfig } from "../types/conversation.js";
|
6
6
|
/**
|
7
7
|
* Default maximum number of turns per session
|
8
8
|
*/
|