LLMlight 0.3.0__tar.gz → 0.4.0__tar.gz

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.
@@ -17,25 +17,27 @@ from transformers import AutoTokenizer
17
17
  import copy
18
18
  import re
19
19
  from tqdm import tqdm
20
+ import tempfile
20
21
 
21
22
  from typing import List, Union
22
23
 
23
24
  from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
24
25
  from sklearn.metrics.pairwise import cosine_similarity
25
26
  from sentence_transformers import SentenceTransformer
27
+ from distfit import distfit
26
28
 
27
29
  from memvid import MemvidEncoder, MemvidRetriever
28
30
  # from memvid.config import get_default_config as memvid_get_default_config
29
31
 
30
- # import memory
31
- # import RAG
32
- # import utils
33
- from . import RAG
34
- from . import utils
35
- from . import memory
36
- # DEBUG
37
- # import utils
38
- # from RAG import RAG_with_RSE
32
+ try:
33
+ from . import RAG
34
+ from . import utils
35
+ from . import memory
36
+ except:
37
+ # DEBUG
38
+ import memory
39
+ import RAG
40
+ import utils
39
41
 
40
42
  logger = logging.getLogger(__name__)
41
43
 
@@ -52,25 +54,25 @@ class LLMlight:
52
54
  6. Enable Running.
53
55
 
54
56
  How LLMlight Works
55
- -----------------
57
+ -------------------
56
58
  LLMlight processes text through several key stages to generate intelligent responses:
57
59
 
58
- 1. Preprocessing Stage
60
+ 1. Context strategy
59
61
  ---------------------
60
62
  The input context can be processed in different ways:
61
- - No preprocessing: Uses the raw context directly
63
+ - No Context strategy: Uses the raw context directly
62
64
  - Chunk-wise processing: Breaks down the context into manageable chunks, processes each chunk independently, and combines results
63
65
  - Global reasoning: Creates a global summary of the context before processing
64
66
 
65
67
  2. Retrieval Method Stage
66
- ------------------------
68
+ --------------------------
67
69
  Three main approaches for retrieving relevant information:
68
70
  - Naive RAG: Splits text into chunks and uses similarity scoring to find the most relevant sections
69
71
  - RSE (Relevant Segment Extraction): Identifies and extracts complete relevant text segments
70
72
  - No retrieval: Uses the entire context directly
71
73
 
72
74
  3. Embedding Stage
73
- -----------------
75
+ -------------------
74
76
  Multiple embedding options for text representation:
75
77
  - TF-IDF: Best for structured documents with matching query terms
76
78
  - Bag of Words: Simple word frequency approach
@@ -78,7 +80,7 @@ class LLMlight:
78
80
  - BGE-small: Efficient embedding model for general use
79
81
 
80
82
  4. Prompting Stage
81
- -----------------
83
+ -------------------
82
84
  The system constructs prompts by combining:
83
85
  - System message: Defines the AI's role and behavior
84
86
  - Context: Processed and retrieved relevant information
@@ -86,50 +88,46 @@ class LLMlight:
86
88
  - Instructions: Additional guidance for response generation
87
89
 
88
90
  5. Response Generation
89
- ---------------------
91
+ -----------------------
92
+ The system can be configured through various parameters to optimize for different use cases, from simple Q&A to complex document analysis.
90
93
  The model generates responses using:
91
94
  - Temperature control: Adjusts response randomness (0.7 default)
92
95
  - Top-p sampling: Controls response diversity
93
96
  - Context window management: Handles token limits efficiently
94
97
 
95
- The system can be configured through various parameters to optimize for different use cases, from simple Q&A to complex document analysis.
96
-
97
98
  Processing Flow
98
- --------------
99
- The system follows a sequential processing flow where each stage builds upon the previous one. First, the input context undergoes preprocessing, where it can be either used as-is or transformed into chunks for more manageable processing. These chunks are then passed through the retrieval method stage, which determines how relevant information is extracted and organized.
99
+ ----------------
100
+ The system follows a sequential processing flow where each stage builds upon the previous one. First, the input context undergoes the context strategy, where it can be either used as-is or transformed into chunks for more manageable processing. These chunks are then passed through the retrieval method stage, which determines how relevant information is extracted and organized.
100
101
  During the embedding stage, the text is converted into numerical representations that capture its semantic meaning. This is crucial for the system to understand and process the content effectively. The embedding method chosen can significantly impact the system's ability to match queries with relevant content.
101
102
  The prompting stage brings together all the processed information, combining it with the user's query and any specific instructions. This creates a comprehensive prompt that guides the model in generating an appropriate response. The final response generation stage uses this prompt to create a coherent and relevant output, with parameters like temperature and top-p sampling helping to control the response's characteristics.
102
103
  Throughout this process, the system maintains flexibility through various configuration options, allowing it to adapt to different types of queries and contexts. This modular approach enables the system to handle everything from simple questions to complex document analysis tasks efficiently.
103
104
 
104
-
105
105
  Parameters
106
106
  ----------
107
107
  model : str
108
- 'hermes-3-llama-3.2-3b'
109
- 'mistral-7b-grok'
110
- 'openhermes-2.5-mistral-7b'
111
- 'gemma-2-9b-it'
108
+ 'mistralai/mistral-small-3.2'
109
+ 'qwen/qwen3-coder-30b'
110
+ 'openai/gpt-oss-20b'
112
111
  system : str
113
112
  String of the system message.
114
113
  "I am a helpfull assistant"
115
- retrieval_method : str (default: 'RAG_basic')
114
+ retrieval_method : str (default: 'naive_rag')
116
115
  None: No processing. The entire context is used for the query.
117
- 'knowledge_base.mp4' Local or absolute path to your (video) memory file.
118
- 'RAG_basic': Context is processed using Navie RAG approach. Ideal for chats and when you need to answer specfic questions: Chunk of text are created. Use cosine similarity to for ranking. The top scoring chunks will be combined (n chunks) and used as input with the prompt.
116
+ 'naive_rag': Context is processed using Navie RAG approach. Ideal for chats and when you need to answer specfic questions: Chunk of text are created. Use cosine similarity to for ranking. The top scoring chunks will be combined (n chunks) and used as input with the prompt.
119
117
  'RSE': Context is processed using Navie RSE approach. Identify and extract entire segments of relevant text.
120
118
  embedding : str, dict (default: 'automatic')
121
- Specify the embedding. When using both video-memory and context, it can be specified with a dictionary: {'memory': 'memvid', 'RAG_basic': 'bert'}
122
- 'automatic': {'memory': 'memvid', 'RAG_basic': 'bert'}
119
+ Specify the embedding. When using both video-memory and context, it can be specified with a dictionary: {'memory': 'memvid', 'naive_rag': 'bert'}
123
120
  None: No embedding is performed.
121
+ 'automatic': {'memory': 'memvid', 'naive_rag': 'bert'}
124
122
  'memvid': This embedding can only be applied when using video-memory in the retrieval method.
125
123
  'tfidf': Best use when it is a structured documents and the words in the queries are matching.
126
124
  'bow': Bag of words approach. Best use when you expect words in the document and queries to be matching.
127
125
  'bert': Best use when document is more free text and the queries may not match exactly the words or sentences in the document.
128
126
  'bge-small':
129
- preprocessing : str (default: None)
127
+ context_strategy : str (default: None)
130
128
  None: No pre-processing. The original context is used in the pipeline of retrieval_method, embedding and the response.
131
- 'global-reasoning': The input context will be summarized per chunk globally. The total set of summarized context is then returned. The normal pipeline proceeds for the query, instructions, system etc.
132
129
  'chunk-wise': The input context will be analyze chunkwise based on the query, instructions and system. The total set of answered-chunks is then returned. The normal pipeline proceeds for the query, instructions, system etc.
130
+ 'global-reasoning': The input context will be summarized per chunk globally. The total set of summarized context is then returned. The normal pipeline proceeds for the query, instructions, system etc.
133
131
  temperature : float, optional
134
132
  Sampling temperature.
135
133
  0.7: (default)
@@ -146,13 +144,12 @@ class LLMlight:
146
144
  With a context window (n_ctx) of 4096 your can set size=1000 words with n chunks=5 and leave some space for instructions, system and the query.
147
145
  'overlap': overlap between chunks
148
146
  'top_chunks': Retrieval of the top N chunks when performing RAG analysis.
149
- endpoint : str
150
- Endpoint of the LLM API
151
- "http://localhost:1234/v1/chat/completions"
152
- './models/Hermes-3-Llama-3.2-3B.Q4_K_M.gguf'
153
- r'C:/Users/username/.lmstudio/models/lmstudio-community/gemma-2-9b-it-GGUF/gemma-2-9b-it-Q4_K_M.gguf'
154
147
  n_ctx : int, default: 4096
155
148
  The context window length is determined by the max tokens. A larger number of tokens will ask more cpu/gpu resources. Estimates: 1000 words or ~10.000 chars costs ~3000 tokens.
149
+ file_path : str
150
+ 'knowledge_base.mp4' Local or absolute path to your (video) memory file.
151
+ endpoint : str: Endpoint of the LLM API
152
+ "http://localhost:1234/v1/chat/completions"
156
153
 
157
154
  Examples
158
155
  --------
@@ -166,26 +163,27 @@ class LLMlight:
166
163
 
167
164
  """
168
165
  def __init__(self,
169
- model: str = "hermes-3-llama-3.2-3b",
170
- retrieval_method: str = 'RAG_basic',
166
+ model: str = None,
167
+ retrieval_method: (None, str) = 'naive_rag',
171
168
  embedding: (str, dict) = {'memory': 'memvid', 'context': 'bert'},
172
- preprocessing: str = None,
169
+ context_strategy: str = None,
173
170
  alpha: float = 0.05,
174
171
  top_chunks: int = 5,
175
172
  temperature: (int, float) = 0.7,
176
173
  top_p: (int, float) = 1.0,
177
- chunks: dict = {'method': 'chars', 'size': 1024, 'overlap': 200},
178
- endpoint: str = "http://localhost:1234/v1/chat/completions",
174
+ chunks: dict = {'method': 'chars', 'size': 1000, 'overlap': 200},
179
175
  n_ctx: int = 4096,
176
+ file_path: str = None,
177
+ endpoint: str = "http://localhost:1234/v1/chat/completions",
180
178
  verbose: (str, int) = 'info',
181
179
  ):
182
180
 
183
181
  # Set the logger
184
182
  set_logger(verbose)
185
-
183
+
186
184
  # Store data in self
187
185
  self.model = model
188
- self.preprocessing = preprocessing
186
+ self.context_strategy = context_strategy
189
187
  self.retrieval_method = retrieval_method
190
188
  self.alpha = alpha
191
189
  self.top_chunks = top_chunks
@@ -195,23 +193,49 @@ class LLMlight:
195
193
  self.n_ctx = n_ctx
196
194
  self.context = None
197
195
  self.embedding = _set_embedding(embedding)
196
+ self.tempdir = os.path.join(tempfile.gettempdir(), 'temp_LLMlight')
197
+ self.file_path = self.get_full_path(file_path)
198
+
199
+ # Make checks
200
+ if model is None:
201
+ models = self.get_available_models(validate=False)
202
+ logger.error(f'Model must be set.')
203
+ logger.info(f'Available models: {models}')
204
+ raise AssertionError('Model must be set.')
205
+
206
+ # Create tempdir
207
+ if not os.path.isdir(self.tempdir):
208
+ os.makedirs(self.tempdir, exist_ok=True)
198
209
 
199
210
  # Set chunk parameters
200
211
  if chunks is None: chunks = {}
201
212
  self.chunks = {**{'method': 'chars', 'size': 1000, 'overlap': 250}, **chunks}
202
213
 
203
214
  # Set Memory parameters.
204
- self.memory_init(retrieval_method)
215
+ if file_path:
216
+ self.memory_load(self.file_path)
217
+ # self.memory_save(overwrite=False)
218
+
205
219
 
206
220
  # Load local LLM gguf model
207
221
  if os.path.isfile(self.endpoint):
208
222
  self.llm = load_local_gguf_model(self.endpoint, n_ctx=self.n_ctx)
209
223
 
210
- logger.info('LLMlight is initialized!')
211
224
  logger.info(f'Model: {self.model}')
212
- logger.info(f'Preprocessing: {self.preprocessing or "disabled"}')
225
+ logger.info(f'Context Strategy: {self.context_strategy or "disabled"}')
213
226
  logger.info(f'Retrieval method: {self.retrieval_method or "disabled"}')
214
227
  logger.info(f'Embedding: {self.embedding or "disabled"}')
228
+ logger.info('LLMlight is initialized!')
229
+
230
+ def get_full_path(self, filepath: str) -> str | None:
231
+ # If filepath is absolute, return as-is
232
+ if filepath is None:
233
+ return None
234
+ elif os.path.isabs(filepath):
235
+ return filepath
236
+
237
+ # If it's relative or just a filename, prepend tempdir
238
+ return os.path.join(self.tempdir, filepath)
215
239
 
216
240
  def prompt(self,
217
241
  query: str,
@@ -288,17 +312,18 @@ class LLMlight:
288
312
  relevant_context = self.relevant_context_retrieval(query, context, return_type='list')
289
313
 
290
314
  # Append the relevant chunks of texts
315
+ if isinstance(relevant_context, str): relevant_context = [relevant_context]
291
316
  total_context = (relevant_memory or []) + (relevant_context or [])
292
317
 
293
- # Preprocessing on the context
294
- processed_context = self.compute_preprocessing(query, total_context, instructions, system)
318
+ # Context Strategu on the context
319
+ processed_context = self.compute_context_strategy(query, total_context, instructions, system)
295
320
 
296
321
  # Set the prompt
297
322
  logger.debug(processed_context)
298
323
 
299
324
  # Make the prompt
300
325
  prompt = self.set_prompt(query, instructions, processed_context, response_format=response_format)
301
- logger.info('Running model now..')
326
+ logger.info(f'Running model: {self.model} ')
302
327
 
303
328
  # Run model
304
329
  if os.path.isfile(self.endpoint):
@@ -407,7 +432,7 @@ class LLMlight:
407
432
  logger.error(f"{response.status_code} - {response}")
408
433
  return f"Error: {response.status_code} - {response}"
409
434
 
410
- def memory_init(self, file_path: str = "llmlight_memory.mp4", config: dict = None, embedding=None):
435
+ def memory_init(self, file_path: str = None, config: dict = None, embedding=None):
411
436
  """Build QR code video and index from chunks with unified codec handling.
412
437
 
413
438
  Parameters
@@ -418,31 +443,48 @@ class LLMlight:
418
443
  Dictionary containing configuration parameters.
419
444
 
420
445
  """
421
- # Initialize video-memory
422
- if not os.path.isfile(file_path): file_path = None
446
+ if file_path: self.file_path = file_path
447
+ # Make check whether already loaded
448
+ if hasattr(self, 'memory') and self.memory.file_path == self.file_path:
449
+ logger.info(f'Memory already initialized: [{self.file_path}] <return>')
450
+ return
451
+
423
452
  # Initialize
424
453
  self.memory = memory.memvid_llm(file_path, config=config)
454
+ # Update file path
455
+ self.file_path = self.memory.file_path
425
456
 
426
457
  # Set the embedding
427
458
  if embedding is not None:
428
459
  self.embedding['memory'] = embedding
429
460
  logger.info(f'Memory embedding is set: {self.embedding or "disabled"}')
430
461
 
431
- def memory_load(self, file_path: str = None):
432
- # Initialize if not yet initialized
433
- if not hasattr(self.memory, 'encoder'):
434
- self.memory_init(file_path)
462
+ def memory_load(self, file_path: str = None, config: dict = None):
463
+ # Load
464
+ if file_path: self.file_path = file_path
465
+ # if not hasattr(self.memory, 'encoder'):
466
+ if hasattr(self, 'memory') and config is None and hasattr(self.memory, 'config'):
467
+ config = self.memory.config
468
+
469
+ if not hasattr(self, 'memory'):
470
+ self.memory = memory.memvid_llm(self.file_path, config=config)
471
+ self.file_path = self.memory.file_path
472
+ # Update file path
473
+ if not hasattr(self.memory, 'retriever'):
474
+ self.memory.load()
475
+ return
476
+
435
477
  # Load the retriever
436
- if not hasattr(self.memory, 'retriever'):
437
- # Load video-memory
438
- self.memory.load()
478
+ # if not hasattr(self.memory, 'retriever'):
479
+ # Load video-memory retriever
480
+ self.memory.load()
439
481
 
440
482
  def memory_save(self,
441
483
  file_path: str = None,
442
484
  codec: str = 'mp4v',
443
- auto_build_docker: bool = True,
485
+ auto_build_docker: bool = False,
444
486
  allow_fallback: bool = True,
445
- overwrite: bool = False,
487
+ overwrite: bool = True,
446
488
  show_progress: bool = True,
447
489
  ):
448
490
  """Build QR code video and index from chunks with unified codec handling.
@@ -461,31 +503,73 @@ class LLMlight:
461
503
  show_progress : bool (default: True)
462
504
 
463
505
  """
464
- self.memory.save(file_path=file_path, codec=codec, auto_build_docker=auto_build_docker, allow_fallback=allow_fallback, overwrite=overwrite, show_progress=show_progress)
506
+ if file_path is not None: self.file_path = file_path
507
+ self.memory.save(file_path=self.file_path, codec=codec, auto_build_docker=auto_build_docker, allow_fallback=allow_fallback, overwrite=overwrite, show_progress=show_progress)
508
+ self.memory.load()
465
509
 
466
510
  def memory_add(self,
467
511
  text: Union[str, List[str]] = None,
468
- input_files: Union[str, List[str]] = None,
512
+ files: Union[str, List[str]] = None,
469
513
  dirpath: str = None,
470
514
  filetypes: List[str] = ['.pdf', '.txt', '.epub', '.md', '.doc', '.docx', '.rtf', '.html', '.htm'],
471
515
  chunk_size: int = 512,
472
516
  chunk_overlap: int = 100,
473
- overwrite=False):
517
+ overwrite=True):
474
518
  """Add chunks to memory.
475
519
 
476
520
  Parameters
477
521
  ----------
478
- input_files : (str, list)
522
+ files : (str, list)
479
523
  Path to file(s).
480
524
 
481
525
  """
482
- self.memory.add(text=text, input_files=input_files, dirpath=dirpath, filetypes=filetypes, chunk_size=chunk_size, chunk_overlap=chunk_overlap, overwrite=overwrite)
526
+ self.memory.add(text=text, input_files=files, dirpath=dirpath, filetypes=filetypes, chunk_size=chunk_size, chunk_overlap=chunk_overlap, overwrite=overwrite, tempdir=self.tempdir)
483
527
 
484
- def compute_probability(self, query, scores, embedding, n=5000):
528
+ def memory_chunks(self, n=10, return_type='disk'):
529
+ """Return the top n memory stack.
530
+
531
+ Parameters
532
+ ----------
533
+ n : int, optional
534
+ Top n chunks to be returned. The default is 5.
535
+ return_type : str, optional
536
+ Retrieve chunks from memory or disk. The default is 'disk'.
537
+ 'memory'
538
+ 'disk'
539
+
540
+ Returns
541
+ -------
542
+ chunks : list
543
+ Top n returned chunks.
544
+
545
+ """
546
+
547
+ if not hasattr(self, 'memory'):
548
+ logger.warning('Memory is not initialized. Hint: client.memory_init()')
549
+ return
485
550
  if not hasattr(self.memory, 'retriever'):
486
- logger.warning('No chunks to encode for null distribution. Use client.add_chunks() first')
551
+ logger.warning('Memory is empty. Use client.memory_add() to add text and then client.memory_save() to store to disk.')
552
+ return
553
+
554
+ if len(self.memory.encoder.chunks) > 0:
555
+ logger.warning(f'Encoder contains {len(self.memory.encoder.chunks)} chunks that are not saved yet. Save to disk with: client.memory_save()')
556
+ # logger.warning(f'Total chunks on disk: {len(self.memory.retriever.index_manager.metadata)}')
557
+ # logger.warning(f'Total chunks on memory: {len(self.memory.encoder.chunks)}')
558
+
559
+ logger.info(f'Retrieving the first top {n} chunks from {return_type}.')
560
+ if return_type == 'disk':
561
+ chunks = list(map(lambda x: x.get('text'), self.memory.retriever.index_manager.metadata))[0:n]
562
+ else:
563
+ chunks = self.memory.encoder.chunks[0:n]
487
564
 
488
- logger.info('Creating random chunks of text with the same frequency distribution.')
565
+ return chunks
566
+
567
+ def compute_probability(self, query, scores, embedding, n=5000):
568
+ if not hasattr(self, 'memory') or not hasattr(self.memory, 'retriever'):
569
+ logger.debug('No chunks to encode for null distribution. Use client.add_chunks() first.')
570
+ return
571
+
572
+ logger.info('Creating null distribution -> For the detect of chunks with significant scores.')
489
573
  if self.embedding['memory']=='memvid':
490
574
  results = self.memory.retriever.index_manager.search(query, top_k=n)
491
575
  random_scores = np.array(list(map(lambda x: x[1], results)))
@@ -505,9 +589,8 @@ class LLMlight:
505
589
  # relevant_chunks = [random_chunks[i] for i in top_indices]
506
590
  # relevant_scores = [scores[i] for i in top_indices]
507
591
 
508
- from distfit import distfit
509
592
  model = distfit(method='parametric', alpha=self.alpha, bound=bound, verbose='warning')
510
- model.fit_transform(random_scores);
593
+ _ = model.fit_transform(random_scores)
511
594
 
512
595
  results = model.predict(scores, alpha=self.alpha, todf=False, multtest='fdr_bh')
513
596
  # results['y_bool'] = results['P']<=self.alpha
@@ -921,17 +1004,17 @@ class LLMlight:
921
1004
  # Return
922
1005
  return query_vector, chunk_vectors
923
1006
 
924
- def compute_preprocessing(self, query, context, instructions, system):
1007
+ def compute_context_strategy(self, query, context, instructions, system):
925
1008
  # Create advanced prompt using relevant chunks of text, the input query and instructions
926
1009
  if context is not None:
927
- if self.preprocessing=='global-reasoning':
1010
+ if self.context_strategy=='global-reasoning':
928
1011
  # Global Reasoning
929
1012
  relevant_context = self.global_reasoning(query, context, instructions, system, rewrite_query=False, return_per_chunk=True)
930
- elif self.preprocessing=='chunk-wise':
1013
+ elif self.context_strategy=='chunk-wise':
931
1014
  # Analyze per chunk
932
1015
  relevant_context = self.chunk_wise(query, context, instructions, system, top_chunks=0, return_per_chunk=True)
933
1016
  else:
934
- logger.info(f'No preprocessing method is applied.')
1017
+ logger.info(f'No Context Strategy method is applied.')
935
1018
  relevant_context = context
936
1019
  else:
937
1020
  # Default
@@ -948,11 +1031,13 @@ class LLMlight:
948
1031
  logger.warning('Documents are stored in the encoder but not saved into video memory! Use save first: client.memory_save() to include the information.')
949
1032
 
950
1033
  # Retrieve context from video memory
951
- if (self.memory.file_path is not None) and os.path.isfile(self.memory.file_path) and os.path.isfile(self.memory.index_path):
1034
+ if self.file_path and os.path.isfile(self.file_path):
1035
+ # and os.path.isfile(self.memory.index_path)
952
1036
  logger.info(f"Initialize retrieval from memory.. Collect [{self.top_chunks}] chunks from video-memory using {self.embedding['memory']}.")
953
1037
  # Initialize retriever
954
1038
  # retriever = self.memory.MemvidRetriever(video_file=self.memory.file_path, index_file=self.memory['index_path'], config=self.memory.config)
955
- self.memory_load()
1039
+ if not hasattr(self, 'memory'):
1040
+ self.memory_load()
956
1041
 
957
1042
  # Retrieval based on embedding
958
1043
  if self.embedding['memory']=='memvid':
@@ -984,7 +1069,7 @@ class LLMlight:
984
1069
 
985
1070
  if context is not None:
986
1071
  # Get relevant context using RAG and embedding
987
- if self.retrieval_method == 'RAG_basic' and self.embedding['context'] in get_embeddings():
1072
+ if self.retrieval_method == 'naive_rag' and self.embedding['context'] in get_embeddings():
988
1073
  # Find the best matching parts using simple retrieval method approach.
989
1074
  logger.info(f"Initialize retrieval from context.. Collect [{self.top_chunks}] chunks from context using {self.embedding['context']}.")
990
1075
  # Create chunks
@@ -1015,9 +1100,11 @@ class LLMlight:
1015
1100
  logger.info(f'Computing probability distribution..')
1016
1101
  # Compute probability
1017
1102
  out = self.compute_probability(query, scores, embedding=self.embedding['memory'], n=1000)
1018
- # Only keep significant chunks
1019
- relevant_context = list(np.array(relevant_context)[out['y_bool']])
1020
- logger.info(f'{len(relevant_context)} significant chunks found with alpha<={self.alpha}')
1103
+
1104
+ if out is not None:
1105
+ # Only keep significant chunks
1106
+ relevant_context = list(np.array(relevant_context)[out.get('y_bool', '')])
1107
+ logger.info(f'{len(relevant_context)} significant chunks found with alpha<={self.alpha}')
1021
1108
 
1022
1109
  # Return relevant context
1023
1110
  return relevant_context
@@ -1027,7 +1114,7 @@ class LLMlight:
1027
1114
  if isinstance(context, list):
1028
1115
  context = "\n\n---\n\n".join([f"### Chunk {i+1}:\n{s}" for i, s in enumerate(context)])
1029
1116
  if context=='':
1030
- logger.warning('No context is provided into the prompt.')
1117
+ logger.info('No context is provided into the prompt.')
1031
1118
 
1032
1119
  prompt = (
1033
1120
  ("Context:\n" + context + "\n\n" if context else "")
@@ -1040,7 +1127,7 @@ class LLMlight:
1040
1127
  # Return
1041
1128
  return prompt
1042
1129
 
1043
- def read_pdf(self, file_path, title_pages=[1, 2], body_pages=[], reference_pages=[-1], return_type='dict'):
1130
+ def read_pdf(self, file_path, title_pages=[1, 2], body_pages=[], reference_pages=[-1], return_type='str'):
1044
1131
  """
1045
1132
  Reads a PDF file and extracts its text content as a string.
1046
1133
 
@@ -1049,19 +1136,30 @@ class LLMlight:
1049
1136
 
1050
1137
  Returns:
1051
1138
  str: Extracted text from the PDF.
1139
+ dict: dictionary
1052
1140
 
1053
1141
  """
1142
+ context = ''
1143
+
1144
+ if 'http' in file_path[0:5]:
1145
+ logger.info('Downloading file from url..')
1146
+ url = file_path
1147
+ filename = wget.filename_from_url(url)
1148
+ file_path = os.path.join(self.tempdir, filename)
1149
+ context = wget.download(url, file_path)
1150
+
1054
1151
  if os.path.isfile(file_path):
1055
1152
  # Read pdf
1056
1153
  context = utils.read_pdf(file_path, title_pages=title_pages, body_pages=body_pages, reference_pages=reference_pages, return_type=return_type)
1057
- # Return
1058
- return context
1059
1154
  # if return_type=='dict':
1060
1155
  # counts = utils.count_words(self.context['body'])
1061
1156
  # self.context['body'] = self.context['body']
1062
1157
  else:
1063
1158
  logger.error(f'file_path does not exist: {file_path}')
1064
1159
 
1160
+ # Return
1161
+ return context
1162
+
1065
1163
  def get_available_models(self, validate=False):
1066
1164
  """Retrieve available models from the configured API endpoint.
1067
1165
 
@@ -1084,7 +1182,7 @@ class LLMlight:
1084
1182
  >>> # Import library
1085
1183
  >>> from LLMlight import LLMlight
1086
1184
  <<< # Initialize
1087
- >>> client = LLMlight(endpoint='http://localhost:1234/v1/chat/completions', verbose='info')
1185
+ >>> client = LLMlight(endpoint='http://localhost:1234/v1/chat/completions')
1088
1186
  >>> # Get models
1089
1187
  >>> models = client.get_available_models(validate=False)
1090
1188
  >>> # Print
@@ -1097,11 +1195,10 @@ class LLMlight:
1097
1195
  - Relies on the `LLMlight` class for validation (must be importable).
1098
1196
  """
1099
1197
  base_url = '/'.join(self.endpoint.split('/')[:3]) + '/'
1100
- logger.info('Collecting models in the API endpoint..')
1198
+ logger.info(f'Collecting models in the API endpoint: {self.endpoint}')
1101
1199
 
1102
1200
  try:
1103
1201
  model_url = base_url.rstrip('/') + '/v1/models'
1104
- logger.info(f'Looking up models: {model_url}')
1105
1202
  response = requests.get(model_url, timeout=10)
1106
1203
  if response.status_code == 200:
1107
1204
  try:
@@ -1271,16 +1368,30 @@ def compute_tokens(string, n_ctx=4096, task='full'):
1271
1368
 
1272
1369
  def compute_max_tokens(used_tokens, n_ctx=4096, task="full"):
1273
1370
  """
1274
- Compute the number of tokens that can be generated based on the task type.
1371
+ Compute the maximum number of tokens that can be generated for a given task,
1372
+ taking into account the number of tokens already used and the model's context window.
1275
1373
 
1276
- Parameters:
1277
- - used_tokens (int): Tokens already used in the input prompt.
1278
- - n_ctx (int): Total context window size of the model.
1279
- - task (str): The use case for generation. Options:
1280
- 'summarization', 'chat', 'code', 'longform', 'full'
1374
+ Parameters
1375
+ ----------
1376
+ used_tokens : int
1377
+ Number of tokens already consumed in the current context.
1378
+ n_ctx : int, optional
1379
+ Total context window size of the model (default is 4096 tokens).
1380
+ task : str, optional
1381
+ Type of generation task. Determines the proportion of the remaining tokens to use.
1382
+ Options are:
1383
+ - "summarization": Use up to 50% of the context window, minimum 128 tokens.
1384
+ - "chat": Use up to 60% of the context window, minimum 128 tokens.
1385
+ - "code": Use up to 75% of the context window, minimum 128 tokens.
1386
+ - "longform": Use up to 90% of the context window, minimum 256 tokens.
1387
+ - "full": Use all remaining tokens.
1388
+ Any unrecognized task defaults to a safe fallback using 50% of the context window.
1281
1389
 
1282
- Returns:
1283
- - max_tokens (int): Tokens allowed for generation.
1390
+ Returns
1391
+ -------
1392
+ max_tokens : int
1393
+ Maximum number of tokens that can be generated for the specified task,
1394
+ ensuring at least a minimum number of tokens as defined per task type.
1284
1395
  """
1285
1396
 
1286
1397
  available_tokens = max(n_ctx - used_tokens, 1) # Ensure at least 1
@@ -1402,3 +1513,32 @@ def disable_tqdm():
1402
1513
  """Set the logger for verbosity messages."""
1403
1514
  return (True if (logger.getEffectiveLevel()>=30) else False)
1404
1515
 
1516
+ # %% Retrieve files files.
1517
+ class wget:
1518
+ """Retrieve file from url."""
1519
+
1520
+ def filename_from_url(url, ext=True):
1521
+ """Return filename."""
1522
+ urlname = os.path.basename(url)
1523
+ if not ext: _, ext = os.path.splitext(urlname)
1524
+ return urlname
1525
+
1526
+ def download(url, writepath):
1527
+ """Download.
1528
+
1529
+ Parameters
1530
+ ----------
1531
+ url : str.
1532
+ Internet source.
1533
+ writepath : str.
1534
+ Directory to write the file.
1535
+
1536
+ Returns
1537
+ -------
1538
+ None.
1539
+
1540
+ """
1541
+ r = requests.get(url, stream=True)
1542
+ with open(writepath, "wb") as fd:
1543
+ for chunk in r.iter_content(chunk_size=1024):
1544
+ fd.write(chunk)
@@ -6,8 +6,11 @@ import re
6
6
  from sentence_transformers import SentenceTransformer
7
7
  import logging
8
8
 
9
- from . import utils
10
- # import utils
9
+ try:
10
+ from . import utils
11
+ except:
12
+ # DEBUG
13
+ import utils
11
14
 
12
15
  logger = logging.getLogger(__name__)
13
16