cursor-recursive-rag 0.2.0-alpha.2 → 0.2.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/README.md +179 -203
- package/dist/adapters/llm/anthropic.d.ts +27 -0
- package/dist/adapters/llm/anthropic.d.ts.map +1 -0
- package/dist/adapters/llm/anthropic.js +287 -0
- package/dist/adapters/llm/anthropic.js.map +1 -0
- package/dist/adapters/llm/base.d.ts +62 -0
- package/dist/adapters/llm/base.d.ts.map +1 -0
- package/dist/adapters/llm/base.js +140 -0
- package/dist/adapters/llm/base.js.map +1 -0
- package/dist/adapters/llm/deepseek.d.ts +24 -0
- package/dist/adapters/llm/deepseek.d.ts.map +1 -0
- package/dist/adapters/llm/deepseek.js +228 -0
- package/dist/adapters/llm/deepseek.js.map +1 -0
- package/dist/adapters/llm/groq.d.ts +25 -0
- package/dist/adapters/llm/groq.d.ts.map +1 -0
- package/dist/adapters/llm/groq.js +265 -0
- package/dist/adapters/llm/groq.js.map +1 -0
- package/dist/adapters/llm/index.d.ts +62 -0
- package/dist/adapters/llm/index.d.ts.map +1 -0
- package/dist/adapters/llm/index.js +380 -0
- package/dist/adapters/llm/index.js.map +1 -0
- package/dist/adapters/llm/ollama.d.ts +23 -0
- package/dist/adapters/llm/ollama.d.ts.map +1 -0
- package/dist/adapters/llm/ollama.js +261 -0
- package/dist/adapters/llm/ollama.js.map +1 -0
- package/dist/adapters/llm/openai.d.ts +22 -0
- package/dist/adapters/llm/openai.d.ts.map +1 -0
- package/dist/adapters/llm/openai.js +232 -0
- package/dist/adapters/llm/openai.js.map +1 -0
- package/dist/adapters/llm/openrouter.d.ts +27 -0
- package/dist/adapters/llm/openrouter.d.ts.map +1 -0
- package/dist/adapters/llm/openrouter.js +305 -0
- package/dist/adapters/llm/openrouter.js.map +1 -0
- package/dist/adapters/vector/index.d.ts.map +1 -1
- package/dist/adapters/vector/index.js +8 -0
- package/dist/adapters/vector/index.js.map +1 -1
- package/dist/adapters/vector/redis-native.d.ts +35 -0
- package/dist/adapters/vector/redis-native.d.ts.map +1 -0
- package/dist/adapters/vector/redis-native.js +170 -0
- package/dist/adapters/vector/redis-native.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +4 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +374 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/maintenance.d.ts +4 -0
- package/dist/cli/commands/maintenance.d.ts.map +1 -0
- package/dist/cli/commands/maintenance.js +237 -0
- package/dist/cli/commands/maintenance.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +9 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +639 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/setup.js +5 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/memoryConfig.d.ts +427 -0
- package/dist/config/memoryConfig.d.ts.map +1 -0
- package/dist/config/memoryConfig.js +258 -0
- package/dist/config/memoryConfig.js.map +1 -0
- package/dist/config/rulesConfig.d.ts +486 -0
- package/dist/config/rulesConfig.d.ts.map +1 -0
- package/dist/config/rulesConfig.js +345 -0
- package/dist/config/rulesConfig.js.map +1 -0
- package/dist/dashboard/coreTools.d.ts +14 -0
- package/dist/dashboard/coreTools.d.ts.map +1 -0
- package/dist/dashboard/coreTools.js +413 -0
- package/dist/dashboard/coreTools.js.map +1 -0
- package/dist/dashboard/public/index.html +1982 -13
- package/dist/dashboard/server.d.ts +1 -8
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +846 -13
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/toolRegistry.d.ts +192 -0
- package/dist/dashboard/toolRegistry.d.ts.map +1 -0
- package/dist/dashboard/toolRegistry.js +322 -0
- package/dist/dashboard/toolRegistry.js.map +1 -0
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +9 -6
- package/dist/proxy/index.js.map +1 -1
- package/dist/server/index.js +21 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/tools/crawl.d.ts.map +1 -1
- package/dist/server/tools/crawl.js +8 -0
- package/dist/server/tools/crawl.js.map +1 -1
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +19 -1
- package/dist/server/tools/index.js.map +1 -1
- package/dist/server/tools/ingest.d.ts.map +1 -1
- package/dist/server/tools/ingest.js +5 -0
- package/dist/server/tools/ingest.js.map +1 -1
- package/dist/server/tools/memory.d.ts +250 -0
- package/dist/server/tools/memory.d.ts.map +1 -0
- package/dist/server/tools/memory.js +472 -0
- package/dist/server/tools/memory.js.map +1 -0
- package/dist/server/tools/recursive-query.d.ts.map +1 -1
- package/dist/server/tools/recursive-query.js +6 -0
- package/dist/server/tools/recursive-query.js.map +1 -1
- package/dist/server/tools/search.d.ts.map +1 -1
- package/dist/server/tools/search.js +6 -0
- package/dist/server/tools/search.js.map +1 -1
- package/dist/services/activity-log.d.ts +10 -0
- package/dist/services/activity-log.d.ts.map +1 -0
- package/dist/services/activity-log.js +53 -0
- package/dist/services/activity-log.js.map +1 -0
- package/dist/services/categoryManager.d.ts +110 -0
- package/dist/services/categoryManager.d.ts.map +1 -0
- package/dist/services/categoryManager.js +549 -0
- package/dist/services/categoryManager.js.map +1 -0
- package/dist/services/contextEnvironment.d.ts +206 -0
- package/dist/services/contextEnvironment.d.ts.map +1 -0
- package/dist/services/contextEnvironment.js +481 -0
- package/dist/services/contextEnvironment.js.map +1 -0
- package/dist/services/conversationProcessor.d.ts +99 -0
- package/dist/services/conversationProcessor.d.ts.map +1 -0
- package/dist/services/conversationProcessor.js +311 -0
- package/dist/services/conversationProcessor.js.map +1 -0
- package/dist/services/cursorChatReader.d.ts +129 -0
- package/dist/services/cursorChatReader.d.ts.map +1 -0
- package/dist/services/cursorChatReader.js +419 -0
- package/dist/services/cursorChatReader.js.map +1 -0
- package/dist/services/decayCalculator.d.ts +85 -0
- package/dist/services/decayCalculator.d.ts.map +1 -0
- package/dist/services/decayCalculator.js +182 -0
- package/dist/services/decayCalculator.js.map +1 -0
- package/dist/services/enhancedVectorStore.d.ts +102 -0
- package/dist/services/enhancedVectorStore.d.ts.map +1 -0
- package/dist/services/enhancedVectorStore.js +245 -0
- package/dist/services/enhancedVectorStore.js.map +1 -0
- package/dist/services/hybridScorer.d.ts +120 -0
- package/dist/services/hybridScorer.d.ts.map +1 -0
- package/dist/services/hybridScorer.js +334 -0
- package/dist/services/hybridScorer.js.map +1 -0
- package/dist/services/knowledgeExtractor.d.ts +45 -0
- package/dist/services/knowledgeExtractor.d.ts.map +1 -0
- package/dist/services/knowledgeExtractor.js +436 -0
- package/dist/services/knowledgeExtractor.js.map +1 -0
- package/dist/services/knowledgeStorage.d.ts +102 -0
- package/dist/services/knowledgeStorage.d.ts.map +1 -0
- package/dist/services/knowledgeStorage.js +383 -0
- package/dist/services/knowledgeStorage.js.map +1 -0
- package/dist/services/maintenanceScheduler.d.ts +89 -0
- package/dist/services/maintenanceScheduler.d.ts.map +1 -0
- package/dist/services/maintenanceScheduler.js +479 -0
- package/dist/services/maintenanceScheduler.js.map +1 -0
- package/dist/services/memoryMetadataStore.d.ts +62 -0
- package/dist/services/memoryMetadataStore.d.ts.map +1 -0
- package/dist/services/memoryMetadataStore.js +570 -0
- package/dist/services/memoryMetadataStore.js.map +1 -0
- package/dist/services/recursiveRetrieval.d.ts +122 -0
- package/dist/services/recursiveRetrieval.d.ts.map +1 -0
- package/dist/services/recursiveRetrieval.js +443 -0
- package/dist/services/recursiveRetrieval.js.map +1 -0
- package/dist/services/relationshipGraph.d.ts +77 -0
- package/dist/services/relationshipGraph.d.ts.map +1 -0
- package/dist/services/relationshipGraph.js +411 -0
- package/dist/services/relationshipGraph.js.map +1 -0
- package/dist/services/rlmSafeguards.d.ts +273 -0
- package/dist/services/rlmSafeguards.d.ts.map +1 -0
- package/dist/services/rlmSafeguards.js +705 -0
- package/dist/services/rlmSafeguards.js.map +1 -0
- package/dist/services/rulesAnalyzer.d.ts +119 -0
- package/dist/services/rulesAnalyzer.d.ts.map +1 -0
- package/dist/services/rulesAnalyzer.js +768 -0
- package/dist/services/rulesAnalyzer.js.map +1 -0
- package/dist/services/rulesMerger.d.ts +75 -0
- package/dist/services/rulesMerger.d.ts.map +1 -0
- package/dist/services/rulesMerger.js +404 -0
- package/dist/services/rulesMerger.js.map +1 -0
- package/dist/services/rulesParser.d.ts +127 -0
- package/dist/services/rulesParser.d.ts.map +1 -0
- package/dist/services/rulesParser.js +594 -0
- package/dist/services/rulesParser.js.map +1 -0
- package/dist/services/smartChunker.d.ts +110 -0
- package/dist/services/smartChunker.d.ts.map +1 -0
- package/dist/services/smartChunker.js +520 -0
- package/dist/services/smartChunker.js.map +1 -0
- package/dist/types/categories.d.ts +105 -0
- package/dist/types/categories.d.ts.map +1 -0
- package/dist/types/categories.js +108 -0
- package/dist/types/categories.js.map +1 -0
- package/dist/types/extractedKnowledge.d.ts +233 -0
- package/dist/types/extractedKnowledge.d.ts.map +1 -0
- package/dist/types/extractedKnowledge.js +56 -0
- package/dist/types/extractedKnowledge.js.map +1 -0
- package/dist/types/index.d.ts +9 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +12 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/llmProvider.d.ts +282 -0
- package/dist/types/llmProvider.d.ts.map +1 -0
- package/dist/types/llmProvider.js +48 -0
- package/dist/types/llmProvider.js.map +1 -0
- package/dist/types/memory.d.ts +227 -0
- package/dist/types/memory.d.ts.map +1 -0
- package/dist/types/memory.js +76 -0
- package/dist/types/memory.js.map +1 -0
- package/dist/types/relationships.d.ts +167 -0
- package/dist/types/relationships.d.ts.map +1 -0
- package/dist/types/relationships.js +106 -0
- package/dist/types/relationships.js.map +1 -0
- package/dist/types/rulesOptimizer.d.ts +345 -0
- package/dist/types/rulesOptimizer.d.ts.map +1 -0
- package/dist/types/rulesOptimizer.js +22 -0
- package/dist/types/rulesOptimizer.js.map +1 -0
- package/docs/cursor-recursive-rag-memory-spec.md +4569 -0
- package/docs/cursor-recursive-rag-tasks.md +1355 -0
- package/package.json +6 -3
- package/restart-rag.sh +16 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RLM Safeguards - Anti-Pattern Mitigations
|
|
3
|
+
*
|
|
4
|
+
* Based on the Recursive Language Models paper's Negative Results (Appendix A),
|
|
5
|
+
* this service implements safeguards to prevent common anti-patterns:
|
|
6
|
+
*
|
|
7
|
+
* 1. Model-specific configurations (not one-size-fits-all)
|
|
8
|
+
* 2. Capability detection for code execution ability
|
|
9
|
+
* 3. Token budget management (reserve for answers)
|
|
10
|
+
* 4. Multi-signal termination detection (not just tags)
|
|
11
|
+
* 5. Sub-call throttling and caching
|
|
12
|
+
* 6. Circuit breaker for runaway trajectories
|
|
13
|
+
* 7. Model prior-based pre-filtering
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'crypto';
|
|
16
|
+
export const DEFAULT_MODEL_CAPABILITIES = {
|
|
17
|
+
codeExecution: 'good',
|
|
18
|
+
contextWindow: 128000,
|
|
19
|
+
outputTokens: 4096,
|
|
20
|
+
supportsStreaming: true,
|
|
21
|
+
supportsToolUse: true,
|
|
22
|
+
};
|
|
23
|
+
export const MODEL_CONFIGS = {
|
|
24
|
+
'gpt-4': {
|
|
25
|
+
maxSubCalls: 100,
|
|
26
|
+
maxIterations: 20,
|
|
27
|
+
warnOnExcessiveCalls: false,
|
|
28
|
+
costMultiplier: 1.0,
|
|
29
|
+
requiresExtraWarnings: false,
|
|
30
|
+
preferredChunkSize: 10,
|
|
31
|
+
},
|
|
32
|
+
'gpt-4o': {
|
|
33
|
+
maxSubCalls: 100,
|
|
34
|
+
maxIterations: 20,
|
|
35
|
+
warnOnExcessiveCalls: false,
|
|
36
|
+
costMultiplier: 0.5,
|
|
37
|
+
requiresExtraWarnings: false,
|
|
38
|
+
preferredChunkSize: 15,
|
|
39
|
+
},
|
|
40
|
+
'gpt-4o-mini': {
|
|
41
|
+
maxSubCalls: 80,
|
|
42
|
+
maxIterations: 15,
|
|
43
|
+
warnOnExcessiveCalls: false,
|
|
44
|
+
costMultiplier: 0.1,
|
|
45
|
+
requiresExtraWarnings: false,
|
|
46
|
+
preferredChunkSize: 12,
|
|
47
|
+
},
|
|
48
|
+
'claude-3-opus': {
|
|
49
|
+
maxSubCalls: 100,
|
|
50
|
+
maxIterations: 20,
|
|
51
|
+
warnOnExcessiveCalls: false,
|
|
52
|
+
costMultiplier: 1.5,
|
|
53
|
+
requiresExtraWarnings: false,
|
|
54
|
+
preferredChunkSize: 10,
|
|
55
|
+
},
|
|
56
|
+
'claude-3-sonnet': {
|
|
57
|
+
maxSubCalls: 80,
|
|
58
|
+
maxIterations: 15,
|
|
59
|
+
warnOnExcessiveCalls: false,
|
|
60
|
+
costMultiplier: 0.3,
|
|
61
|
+
requiresExtraWarnings: false,
|
|
62
|
+
preferredChunkSize: 12,
|
|
63
|
+
},
|
|
64
|
+
'claude-3-haiku': {
|
|
65
|
+
maxSubCalls: 60,
|
|
66
|
+
maxIterations: 12,
|
|
67
|
+
warnOnExcessiveCalls: true,
|
|
68
|
+
costMultiplier: 0.05,
|
|
69
|
+
requiresExtraWarnings: false,
|
|
70
|
+
preferredChunkSize: 8,
|
|
71
|
+
},
|
|
72
|
+
'qwen': {
|
|
73
|
+
maxSubCalls: 50,
|
|
74
|
+
maxIterations: 10,
|
|
75
|
+
warnOnExcessiveCalls: true,
|
|
76
|
+
costMultiplier: 0.2,
|
|
77
|
+
requiresExtraWarnings: true,
|
|
78
|
+
preferredChunkSize: 8,
|
|
79
|
+
},
|
|
80
|
+
'llama': {
|
|
81
|
+
maxSubCalls: 40,
|
|
82
|
+
maxIterations: 8,
|
|
83
|
+
warnOnExcessiveCalls: true,
|
|
84
|
+
costMultiplier: 0.1,
|
|
85
|
+
requiresExtraWarnings: true,
|
|
86
|
+
preferredChunkSize: 6,
|
|
87
|
+
},
|
|
88
|
+
'local': {
|
|
89
|
+
maxSubCalls: 20,
|
|
90
|
+
maxIterations: 5,
|
|
91
|
+
warnOnExcessiveCalls: true,
|
|
92
|
+
costMultiplier: 0.01,
|
|
93
|
+
requiresExtraWarnings: true,
|
|
94
|
+
preferredChunkSize: 5,
|
|
95
|
+
},
|
|
96
|
+
'default': {
|
|
97
|
+
maxSubCalls: 50,
|
|
98
|
+
maxIterations: 10,
|
|
99
|
+
warnOnExcessiveCalls: true,
|
|
100
|
+
costMultiplier: 0.5,
|
|
101
|
+
requiresExtraWarnings: false,
|
|
102
|
+
preferredChunkSize: 10,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
export const MODEL_CAPABILITIES = {
|
|
106
|
+
'gpt-4': {
|
|
107
|
+
codeExecution: 'excellent',
|
|
108
|
+
contextWindow: 128000,
|
|
109
|
+
outputTokens: 4096,
|
|
110
|
+
supportsStreaming: true,
|
|
111
|
+
supportsToolUse: true,
|
|
112
|
+
},
|
|
113
|
+
'gpt-4o': {
|
|
114
|
+
codeExecution: 'excellent',
|
|
115
|
+
contextWindow: 128000,
|
|
116
|
+
outputTokens: 4096,
|
|
117
|
+
supportsStreaming: true,
|
|
118
|
+
supportsToolUse: true,
|
|
119
|
+
},
|
|
120
|
+
'gpt-4o-mini': {
|
|
121
|
+
codeExecution: 'good',
|
|
122
|
+
contextWindow: 128000,
|
|
123
|
+
outputTokens: 4096,
|
|
124
|
+
supportsStreaming: true,
|
|
125
|
+
supportsToolUse: true,
|
|
126
|
+
},
|
|
127
|
+
'claude-3-opus': {
|
|
128
|
+
codeExecution: 'excellent',
|
|
129
|
+
contextWindow: 200000,
|
|
130
|
+
outputTokens: 4096,
|
|
131
|
+
supportsStreaming: true,
|
|
132
|
+
supportsToolUse: true,
|
|
133
|
+
},
|
|
134
|
+
'claude-3-sonnet': {
|
|
135
|
+
codeExecution: 'excellent',
|
|
136
|
+
contextWindow: 200000,
|
|
137
|
+
outputTokens: 4096,
|
|
138
|
+
supportsStreaming: true,
|
|
139
|
+
supportsToolUse: true,
|
|
140
|
+
},
|
|
141
|
+
'claude-3-haiku': {
|
|
142
|
+
codeExecution: 'good',
|
|
143
|
+
contextWindow: 200000,
|
|
144
|
+
outputTokens: 4096,
|
|
145
|
+
supportsStreaming: true,
|
|
146
|
+
supportsToolUse: true,
|
|
147
|
+
},
|
|
148
|
+
'qwen': {
|
|
149
|
+
codeExecution: 'good',
|
|
150
|
+
contextWindow: 32000,
|
|
151
|
+
outputTokens: 2048,
|
|
152
|
+
supportsStreaming: true,
|
|
153
|
+
supportsToolUse: false,
|
|
154
|
+
},
|
|
155
|
+
'llama': {
|
|
156
|
+
codeExecution: 'limited',
|
|
157
|
+
contextWindow: 8192,
|
|
158
|
+
outputTokens: 2048,
|
|
159
|
+
supportsStreaming: true,
|
|
160
|
+
supportsToolUse: false,
|
|
161
|
+
},
|
|
162
|
+
'local': {
|
|
163
|
+
codeExecution: 'limited',
|
|
164
|
+
contextWindow: 4096,
|
|
165
|
+
outputTokens: 1024,
|
|
166
|
+
supportsStreaming: false,
|
|
167
|
+
supportsToolUse: false,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Get model configuration, with fallback to default
|
|
172
|
+
*/
|
|
173
|
+
export function getModelConfig(modelName) {
|
|
174
|
+
// Normalize model name
|
|
175
|
+
const normalized = modelName.toLowerCase();
|
|
176
|
+
// Find matching config
|
|
177
|
+
for (const [key, config] of Object.entries(MODEL_CONFIGS)) {
|
|
178
|
+
if (normalized.includes(key)) {
|
|
179
|
+
return config;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return MODEL_CONFIGS['default'];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get model capabilities, with fallback to default
|
|
186
|
+
*/
|
|
187
|
+
export function getModelCapabilities(modelName) {
|
|
188
|
+
const normalized = modelName.toLowerCase();
|
|
189
|
+
for (const [key, capabilities] of Object.entries(MODEL_CAPABILITIES)) {
|
|
190
|
+
if (normalized.includes(key)) {
|
|
191
|
+
return capabilities;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return DEFAULT_MODEL_CAPABILITIES;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Choose retrieval strategy based on model capabilities and query complexity
|
|
198
|
+
*/
|
|
199
|
+
export function chooseRetrievalStrategy(capabilities, queryComplexity) {
|
|
200
|
+
// Models without coding ability can't use recursive REPL approach
|
|
201
|
+
if (capabilities.codeExecution === 'none' || capabilities.codeExecution === 'limited') {
|
|
202
|
+
return queryComplexity === 'simple' ? 'direct' : 'iterative';
|
|
203
|
+
}
|
|
204
|
+
// Good code execution - use iterative for moderate, recursive for complex
|
|
205
|
+
if (capabilities.codeExecution === 'good') {
|
|
206
|
+
if (queryComplexity === 'simple')
|
|
207
|
+
return 'direct';
|
|
208
|
+
if (queryComplexity === 'moderate')
|
|
209
|
+
return 'iterative';
|
|
210
|
+
return 'recursive';
|
|
211
|
+
}
|
|
212
|
+
// Excellent code execution - use recursive for anything complex
|
|
213
|
+
if (queryComplexity === 'simple')
|
|
214
|
+
return 'direct';
|
|
215
|
+
return 'recursive';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Token Budget Manager
|
|
219
|
+
* Reserves output tokens for final answers to prevent running out
|
|
220
|
+
*/
|
|
221
|
+
export class TokenBudgetManager {
|
|
222
|
+
budget;
|
|
223
|
+
constructor(modelName, reserveRatio = 0.25) {
|
|
224
|
+
const capabilities = getModelCapabilities(modelName);
|
|
225
|
+
const reserved = Math.floor(capabilities.outputTokens * reserveRatio);
|
|
226
|
+
this.budget = {
|
|
227
|
+
totalOutputTokens: capabilities.outputTokens,
|
|
228
|
+
reservedForAnswer: reserved,
|
|
229
|
+
maxThinkingTokens: capabilities.outputTokens - reserved,
|
|
230
|
+
currentUsed: 0,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
canUseTokens(count) {
|
|
234
|
+
return this.budget.currentUsed + count <= this.budget.maxThinkingTokens;
|
|
235
|
+
}
|
|
236
|
+
useTokens(count) {
|
|
237
|
+
this.budget.currentUsed += count;
|
|
238
|
+
}
|
|
239
|
+
getRemainingThinkingTokens() {
|
|
240
|
+
return this.budget.maxThinkingTokens - this.budget.currentUsed;
|
|
241
|
+
}
|
|
242
|
+
getReservedForAnswer() {
|
|
243
|
+
return this.budget.reservedForAnswer;
|
|
244
|
+
}
|
|
245
|
+
getBudget() {
|
|
246
|
+
return { ...this.budget };
|
|
247
|
+
}
|
|
248
|
+
reset() {
|
|
249
|
+
this.budget.currentUsed = 0;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Multi-Signal Termination Detector
|
|
254
|
+
* Uses multiple signals to determine when to terminate, not just tags
|
|
255
|
+
*/
|
|
256
|
+
export class TerminationDetector {
|
|
257
|
+
signals;
|
|
258
|
+
requiredSignals;
|
|
259
|
+
constructor(requiredSignals = 2) {
|
|
260
|
+
this.requiredSignals = requiredSignals;
|
|
261
|
+
this.signals = {
|
|
262
|
+
explicitTag: false,
|
|
263
|
+
confidenceStatement: false,
|
|
264
|
+
noMoreActions: false,
|
|
265
|
+
answerValidation: false,
|
|
266
|
+
iterationLimitReached: false,
|
|
267
|
+
budgetExhausted: false,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check if response contains explicit termination tags
|
|
272
|
+
*/
|
|
273
|
+
checkExplicitTag(response) {
|
|
274
|
+
const patterns = [
|
|
275
|
+
/\bFINAL\s*\(/i,
|
|
276
|
+
/\bFINAL_VAR\s*\(/i,
|
|
277
|
+
/\bANSWER\s*:/i,
|
|
278
|
+
/"type"\s*:\s*"answer"/i,
|
|
279
|
+
];
|
|
280
|
+
this.signals.explicitTag = patterns.some(p => p.test(response));
|
|
281
|
+
return this.signals.explicitTag;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Check if response expresses confidence in the answer
|
|
285
|
+
*/
|
|
286
|
+
checkConfidenceStatement(response) {
|
|
287
|
+
const patterns = [
|
|
288
|
+
/\b(I am confident|I believe|The answer is|Based on the context)/i,
|
|
289
|
+
/\b(sufficient information|enough information|can conclude)/i,
|
|
290
|
+
/\b(definitively|conclusively|clearly shows)/i,
|
|
291
|
+
];
|
|
292
|
+
this.signals.confidenceStatement = patterns.some(p => p.test(response));
|
|
293
|
+
return this.signals.confidenceStatement;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Check if response doesn't request more operations
|
|
297
|
+
*/
|
|
298
|
+
checkNoMoreActions(response) {
|
|
299
|
+
const actionPatterns = [
|
|
300
|
+
/"type"\s*:\s*"(peek|filter|chunk|subQuery|search)"/i,
|
|
301
|
+
/\b(let me|I will|I need to|next I should)\b/i,
|
|
302
|
+
/\b(examine|investigate|look at|check)\b.*\b(more|further|additional)\b/i,
|
|
303
|
+
];
|
|
304
|
+
this.signals.noMoreActions = !actionPatterns.some(p => p.test(response));
|
|
305
|
+
return this.signals.noMoreActions;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Validate that answer is actually an answer, not a plan
|
|
309
|
+
*/
|
|
310
|
+
validateAnswer(answer, query) {
|
|
311
|
+
// Check answer isn't just a plan/thought
|
|
312
|
+
const planPatterns = [
|
|
313
|
+
/^(I will|Let me|I should|I need to|First,|Next,|Then,)/i,
|
|
314
|
+
/^(To answer this|To find out|To determine)/i,
|
|
315
|
+
/^(Looking at|Examining|Checking)/i,
|
|
316
|
+
];
|
|
317
|
+
if (planPatterns.some(p => p.test(answer.trim()))) {
|
|
318
|
+
this.signals.answerValidation = false;
|
|
319
|
+
return { valid: false, reason: 'Answer appears to be a plan, not a result' };
|
|
320
|
+
}
|
|
321
|
+
// Check answer isn't too short
|
|
322
|
+
if (answer.trim().length < 20) {
|
|
323
|
+
this.signals.answerValidation = false;
|
|
324
|
+
return { valid: false, reason: 'Answer is too short to be meaningful' };
|
|
325
|
+
}
|
|
326
|
+
// Check answer contains some substance
|
|
327
|
+
const hasSubstance = answer.match(/\b(is|are|was|were|can|could|should|would|found|discovered|determined)\b/i);
|
|
328
|
+
if (!hasSubstance) {
|
|
329
|
+
this.signals.answerValidation = false;
|
|
330
|
+
return { valid: false, reason: 'Answer lacks conclusive statements' };
|
|
331
|
+
}
|
|
332
|
+
this.signals.answerValidation = true;
|
|
333
|
+
return { valid: true, confidence: 0.8 };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Set iteration limit reached
|
|
337
|
+
*/
|
|
338
|
+
setIterationLimitReached() {
|
|
339
|
+
this.signals.iterationLimitReached = true;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Set budget exhausted
|
|
343
|
+
*/
|
|
344
|
+
setBudgetExhausted() {
|
|
345
|
+
this.signals.budgetExhausted = true;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if we should terminate based on multiple signals
|
|
349
|
+
*/
|
|
350
|
+
shouldTerminate() {
|
|
351
|
+
// Forced termination conditions
|
|
352
|
+
if (this.signals.iterationLimitReached) {
|
|
353
|
+
return { terminate: true, reason: 'Iteration limit reached', confidence: 1.0 };
|
|
354
|
+
}
|
|
355
|
+
if (this.signals.budgetExhausted) {
|
|
356
|
+
return { terminate: true, reason: 'Budget exhausted', confidence: 1.0 };
|
|
357
|
+
}
|
|
358
|
+
// Count positive signals
|
|
359
|
+
const positiveSignals = [
|
|
360
|
+
this.signals.explicitTag,
|
|
361
|
+
this.signals.confidenceStatement,
|
|
362
|
+
this.signals.noMoreActions,
|
|
363
|
+
this.signals.answerValidation,
|
|
364
|
+
].filter(Boolean).length;
|
|
365
|
+
if (positiveSignals >= this.requiredSignals) {
|
|
366
|
+
return {
|
|
367
|
+
terminate: true,
|
|
368
|
+
reason: `${positiveSignals} termination signals detected`,
|
|
369
|
+
confidence: positiveSignals / 4,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { terminate: false, reason: 'Insufficient termination signals', confidence: positiveSignals / 4 };
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get current signals
|
|
376
|
+
*/
|
|
377
|
+
getSignals() {
|
|
378
|
+
return { ...this.signals };
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Reset signals
|
|
382
|
+
*/
|
|
383
|
+
reset() {
|
|
384
|
+
this.signals = {
|
|
385
|
+
explicitTag: false,
|
|
386
|
+
confidenceStatement: false,
|
|
387
|
+
noMoreActions: false,
|
|
388
|
+
answerValidation: false,
|
|
389
|
+
iterationLimitReached: false,
|
|
390
|
+
budgetExhausted: false,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Circuit Breaker for runaway trajectories
|
|
396
|
+
* Prevents excessive failures from consuming resources
|
|
397
|
+
*/
|
|
398
|
+
export class CircuitBreaker {
|
|
399
|
+
failures = 0;
|
|
400
|
+
lastFailure = null;
|
|
401
|
+
state = 'closed';
|
|
402
|
+
threshold;
|
|
403
|
+
resetTimeMs;
|
|
404
|
+
constructor(threshold = 3, resetTimeMs = 60000) {
|
|
405
|
+
this.threshold = threshold;
|
|
406
|
+
this.resetTimeMs = resetTimeMs;
|
|
407
|
+
}
|
|
408
|
+
async execute(operation) {
|
|
409
|
+
// Check if circuit is open
|
|
410
|
+
if (this.state === 'open') {
|
|
411
|
+
if (this.lastFailure && Date.now() - this.lastFailure.getTime() > this.resetTimeMs) {
|
|
412
|
+
this.state = 'half-open';
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
throw new Error('Circuit breaker is open - too many recent failures');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const result = await operation();
|
|
420
|
+
this.onSuccess();
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
this.onFailure();
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
onSuccess() {
|
|
429
|
+
this.failures = 0;
|
|
430
|
+
this.state = 'closed';
|
|
431
|
+
}
|
|
432
|
+
onFailure() {
|
|
433
|
+
this.failures++;
|
|
434
|
+
this.lastFailure = new Date();
|
|
435
|
+
if (this.failures >= this.threshold) {
|
|
436
|
+
this.state = 'open';
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getState() {
|
|
440
|
+
return this.state;
|
|
441
|
+
}
|
|
442
|
+
getFailures() {
|
|
443
|
+
return this.failures;
|
|
444
|
+
}
|
|
445
|
+
reset() {
|
|
446
|
+
this.failures = 0;
|
|
447
|
+
this.lastFailure = null;
|
|
448
|
+
this.state = 'closed';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Sub-Call Throttler with Caching
|
|
453
|
+
* Prevents excessive sub-calls and caches results
|
|
454
|
+
*/
|
|
455
|
+
export class SubCallThrottler {
|
|
456
|
+
callCounts = new Map();
|
|
457
|
+
cache = new Map();
|
|
458
|
+
maxCallsPerKey;
|
|
459
|
+
cacheTtlMs;
|
|
460
|
+
constructor(maxCallsPerKey = 3, cacheTtlMs = 300000) {
|
|
461
|
+
this.maxCallsPerKey = maxCallsPerKey;
|
|
462
|
+
this.cacheTtlMs = cacheTtlMs;
|
|
463
|
+
}
|
|
464
|
+
async throttledCall(key, llm, prompt, options) {
|
|
465
|
+
// Check cache first
|
|
466
|
+
const cacheKey = this.hashPrompt(prompt);
|
|
467
|
+
const cached = this.cache.get(cacheKey);
|
|
468
|
+
if (cached && Date.now() - cached.timestamp < this.cacheTtlMs) {
|
|
469
|
+
return cached.result;
|
|
470
|
+
}
|
|
471
|
+
// Check call count for this key
|
|
472
|
+
const count = this.callCounts.get(key) ?? 0;
|
|
473
|
+
if (count >= this.maxCallsPerKey) {
|
|
474
|
+
throw new Error(`Maximum sub-calls (${this.maxCallsPerKey}) exceeded for key: ${key}`);
|
|
475
|
+
}
|
|
476
|
+
// Make the call
|
|
477
|
+
this.callCounts.set(key, count + 1);
|
|
478
|
+
const result = await llm.invoke(prompt, options);
|
|
479
|
+
// Cache the result
|
|
480
|
+
this.cache.set(cacheKey, { result, timestamp: Date.now() });
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
hashPrompt(prompt) {
|
|
484
|
+
return createHash('md5').update(prompt).digest('hex').substring(0, 16);
|
|
485
|
+
}
|
|
486
|
+
getCallCount(key) {
|
|
487
|
+
return this.callCounts.get(key) ?? 0;
|
|
488
|
+
}
|
|
489
|
+
getCacheSize() {
|
|
490
|
+
return this.cache.size;
|
|
491
|
+
}
|
|
492
|
+
clearCache() {
|
|
493
|
+
this.cache.clear();
|
|
494
|
+
}
|
|
495
|
+
reset() {
|
|
496
|
+
this.callCounts.clear();
|
|
497
|
+
this.cache.clear();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Model Prior-Based Pre-Filter
|
|
502
|
+
* Filters context using patterns before semantic analysis
|
|
503
|
+
*/
|
|
504
|
+
export class PriorBasedFilter {
|
|
505
|
+
/**
|
|
506
|
+
* Pre-filter chunks using keyword patterns
|
|
507
|
+
*/
|
|
508
|
+
filterByKeywords(chunks, keywords, options) {
|
|
509
|
+
if (keywords.length === 0)
|
|
510
|
+
return chunks;
|
|
511
|
+
const flags = options?.caseSensitive ? '' : 'i';
|
|
512
|
+
const patterns = keywords.map(k => new RegExp(k, flags));
|
|
513
|
+
return chunks.filter(chunk => {
|
|
514
|
+
if (options?.matchAll) {
|
|
515
|
+
return patterns.every(p => p.test(chunk.content));
|
|
516
|
+
}
|
|
517
|
+
return patterns.some(p => p.test(chunk.content));
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Extract keywords from query using heuristics
|
|
522
|
+
*/
|
|
523
|
+
extractQueryKeywords(query) {
|
|
524
|
+
const keywords = [];
|
|
525
|
+
const queryLower = query.toLowerCase();
|
|
526
|
+
// Common technical terms
|
|
527
|
+
const technicalPatterns = [
|
|
528
|
+
/\b(error|exception|bug|issue|problem)\b/gi,
|
|
529
|
+
/\b(function|method|class|component|module)\b/gi,
|
|
530
|
+
/\b(api|endpoint|route|handler)\b/gi,
|
|
531
|
+
/\b(database|query|sql|mongodb)\b/gi,
|
|
532
|
+
/\b(auth|authentication|authorization|login)\b/gi,
|
|
533
|
+
/\b(test|testing|unit|integration)\b/gi,
|
|
534
|
+
];
|
|
535
|
+
for (const pattern of technicalPatterns) {
|
|
536
|
+
const matches = query.match(pattern);
|
|
537
|
+
if (matches) {
|
|
538
|
+
keywords.push(...matches.map(m => m.toLowerCase()));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Extract quoted strings
|
|
542
|
+
const quotedMatches = query.match(/"([^"]+)"/g);
|
|
543
|
+
if (quotedMatches) {
|
|
544
|
+
keywords.push(...quotedMatches.map(m => m.replace(/"/g, '')));
|
|
545
|
+
}
|
|
546
|
+
// Extract capitalized words (likely class/function names)
|
|
547
|
+
const capitalizedMatches = query.match(/\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g);
|
|
548
|
+
if (capitalizedMatches) {
|
|
549
|
+
keywords.push(...capitalizedMatches);
|
|
550
|
+
}
|
|
551
|
+
return [...new Set(keywords)];
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Smart pre-filter that combines multiple strategies
|
|
555
|
+
*/
|
|
556
|
+
smartPreFilter(chunks, query, maxChunks = 50) {
|
|
557
|
+
// Extract keywords from query
|
|
558
|
+
const keywords = this.extractQueryKeywords(query);
|
|
559
|
+
if (keywords.length === 0) {
|
|
560
|
+
// No keywords - return top chunks by importance
|
|
561
|
+
return [...chunks]
|
|
562
|
+
.sort((a, b) => b.importance - a.importance)
|
|
563
|
+
.slice(0, maxChunks);
|
|
564
|
+
}
|
|
565
|
+
// Filter by keywords
|
|
566
|
+
let filtered = this.filterByKeywords(chunks, keywords);
|
|
567
|
+
// If too few results, relax to any match
|
|
568
|
+
if (filtered.length < 5 && keywords.length > 1) {
|
|
569
|
+
filtered = this.filterByKeywords(chunks, keywords, { matchAll: false });
|
|
570
|
+
}
|
|
571
|
+
// If still no results, fall back to importance-based selection
|
|
572
|
+
if (filtered.length === 0) {
|
|
573
|
+
return [...chunks]
|
|
574
|
+
.sort((a, b) => b.importance - a.importance)
|
|
575
|
+
.slice(0, maxChunks);
|
|
576
|
+
}
|
|
577
|
+
// Sort by importance and limit
|
|
578
|
+
return [...filtered]
|
|
579
|
+
.sort((a, b) => b.importance - a.importance)
|
|
580
|
+
.slice(0, maxChunks);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* RLM Safeguards Coordinator
|
|
585
|
+
* Combines all safeguards into a single interface
|
|
586
|
+
*/
|
|
587
|
+
export class RLMSafeguards {
|
|
588
|
+
tokenBudget;
|
|
589
|
+
terminationDetector;
|
|
590
|
+
circuitBreaker;
|
|
591
|
+
throttler;
|
|
592
|
+
priorFilter;
|
|
593
|
+
modelConfig;
|
|
594
|
+
modelCapabilities;
|
|
595
|
+
constructor(modelName, options) {
|
|
596
|
+
this.modelConfig = getModelConfig(modelName);
|
|
597
|
+
this.modelCapabilities = getModelCapabilities(modelName);
|
|
598
|
+
this.tokenBudget = new TokenBudgetManager(modelName);
|
|
599
|
+
this.terminationDetector = new TerminationDetector(options?.requiredTerminationSignals ?? 2);
|
|
600
|
+
this.circuitBreaker = new CircuitBreaker(options?.circuitBreakerThreshold ?? 3, options?.circuitBreakerResetMs ?? 60000);
|
|
601
|
+
this.throttler = new SubCallThrottler(options?.maxSubCallsPerKey ?? this.modelConfig.maxSubCalls, options?.cacheTtlMs ?? 300000);
|
|
602
|
+
this.priorFilter = new PriorBasedFilter();
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Choose appropriate retrieval strategy
|
|
606
|
+
*/
|
|
607
|
+
chooseStrategy(queryComplexity) {
|
|
608
|
+
return chooseRetrievalStrategy(this.modelCapabilities, queryComplexity);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Pre-filter chunks before processing
|
|
612
|
+
*/
|
|
613
|
+
preFilter(chunks, query, maxChunks) {
|
|
614
|
+
return this.priorFilter.smartPreFilter(chunks, query, maxChunks ?? this.modelConfig.preferredChunkSize * 5);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Execute a sub-call with all safeguards
|
|
618
|
+
*/
|
|
619
|
+
async safeSubCall(key, llm, prompt, options) {
|
|
620
|
+
// Check circuit breaker
|
|
621
|
+
return this.circuitBreaker.execute(async () => {
|
|
622
|
+
// Check token budget
|
|
623
|
+
const estimatedTokens = Math.ceil(prompt.length / 4);
|
|
624
|
+
if (!this.tokenBudget.canUseTokens(estimatedTokens)) {
|
|
625
|
+
throw new Error('Token budget exceeded');
|
|
626
|
+
}
|
|
627
|
+
// Throttled and cached call
|
|
628
|
+
const result = await this.throttler.throttledCall(key, llm, prompt, options);
|
|
629
|
+
// Record token usage
|
|
630
|
+
this.tokenBudget.useTokens(estimatedTokens + Math.ceil(result.length / 4));
|
|
631
|
+
return result;
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Check response and update termination signals
|
|
636
|
+
*/
|
|
637
|
+
checkResponse(response, query) {
|
|
638
|
+
this.terminationDetector.checkExplicitTag(response);
|
|
639
|
+
this.terminationDetector.checkConfidenceStatement(response);
|
|
640
|
+
this.terminationDetector.checkNoMoreActions(response);
|
|
641
|
+
// If it looks like an answer, validate it
|
|
642
|
+
if (this.terminationDetector.getSignals().noMoreActions) {
|
|
643
|
+
this.terminationDetector.validateAnswer(response, query);
|
|
644
|
+
}
|
|
645
|
+
return this.terminationDetector.shouldTerminate();
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Mark iteration limit reached
|
|
649
|
+
*/
|
|
650
|
+
markIterationLimitReached() {
|
|
651
|
+
this.terminationDetector.setIterationLimitReached();
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Mark budget exhausted
|
|
655
|
+
*/
|
|
656
|
+
markBudgetExhausted() {
|
|
657
|
+
this.terminationDetector.setBudgetExhausted();
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Get model configuration
|
|
661
|
+
*/
|
|
662
|
+
getModelConfig() {
|
|
663
|
+
return { ...this.modelConfig };
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Get model capabilities
|
|
667
|
+
*/
|
|
668
|
+
getModelCapabilities() {
|
|
669
|
+
return { ...this.modelCapabilities };
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get current state
|
|
673
|
+
*/
|
|
674
|
+
getState() {
|
|
675
|
+
return {
|
|
676
|
+
tokenBudget: this.tokenBudget.getBudget(),
|
|
677
|
+
terminationSignals: this.terminationDetector.getSignals(),
|
|
678
|
+
circuitBreakerState: this.circuitBreaker.getState(),
|
|
679
|
+
cacheSize: this.throttler.getCacheSize(),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Reset all state
|
|
684
|
+
*/
|
|
685
|
+
reset() {
|
|
686
|
+
this.tokenBudget.reset();
|
|
687
|
+
this.terminationDetector.reset();
|
|
688
|
+
this.circuitBreaker.reset();
|
|
689
|
+
this.throttler.reset();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
let instance = null;
|
|
693
|
+
export function createRLMSafeguards(modelName, options) {
|
|
694
|
+
return new RLMSafeguards(modelName, options);
|
|
695
|
+
}
|
|
696
|
+
export function getRLMSafeguards(modelName = 'default', options) {
|
|
697
|
+
if (!instance) {
|
|
698
|
+
instance = new RLMSafeguards(modelName, options);
|
|
699
|
+
}
|
|
700
|
+
return instance;
|
|
701
|
+
}
|
|
702
|
+
export function resetRLMSafeguards() {
|
|
703
|
+
instance = null;
|
|
704
|
+
}
|
|
705
|
+
//# sourceMappingURL=rlmSafeguards.js.map
|