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.
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/LLMlight.py +237 -97
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/RAG.py +5 -2
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/__init__.py +9 -7
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/examples.py +335 -69
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/memory.py +46 -10
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/read_outlook.py +2 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight/utils.py +17 -1
- llmlight-0.4.0/LLMlight.egg-info/PKG-INFO +287 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight.egg-info/requires.txt +1 -0
- llmlight-0.4.0/PKG-INFO +287 -0
- llmlight-0.4.0/README.md +256 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/pyproject.toml +15 -4
- llmlight-0.3.0/LLMlight.egg-info/PKG-INFO +0 -302
- llmlight-0.3.0/PKG-INFO +0 -302
- llmlight-0.3.0/README.md +0 -250
- {llmlight-0.3.0 → llmlight-0.4.0}/LICENSE +0 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight.egg-info/SOURCES.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight.egg-info/dependency_links.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/LLMlight.egg-info/top_level.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/MANIFEST.in +0 -0
- {llmlight-0.3.0 → llmlight-0.4.0}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
from . import
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
60
|
+
1. Context strategy
|
|
59
61
|
---------------------
|
|
60
62
|
The input context can be processed in different ways:
|
|
61
|
-
- No
|
|
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
|
|
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
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
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: '
|
|
114
|
+
retrieval_method : str (default: 'naive_rag')
|
|
116
115
|
None: No processing. The entire context is used for the query.
|
|
117
|
-
'
|
|
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', '
|
|
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
|
-
|
|
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 =
|
|
170
|
-
retrieval_method: str = '
|
|
166
|
+
model: str = None,
|
|
167
|
+
retrieval_method: (None, str) = 'naive_rag',
|
|
171
168
|
embedding: (str, dict) = {'memory': 'memvid', 'context': 'bert'},
|
|
172
|
-
|
|
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':
|
|
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.
|
|
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
|
-
|
|
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'
|
|
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
|
-
#
|
|
294
|
-
processed_context = self.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
#
|
|
433
|
-
if
|
|
434
|
-
|
|
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
|
-
|
|
438
|
-
|
|
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 =
|
|
485
|
+
auto_build_docker: bool = False,
|
|
444
486
|
allow_fallback: bool = True,
|
|
445
|
-
overwrite: bool =
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
517
|
+
overwrite=True):
|
|
474
518
|
"""Add chunks to memory.
|
|
475
519
|
|
|
476
520
|
Parameters
|
|
477
521
|
----------
|
|
478
|
-
|
|
522
|
+
files : (str, list)
|
|
479
523
|
Path to file(s).
|
|
480
524
|
|
|
481
525
|
"""
|
|
482
|
-
self.memory.add(text=text, input_files=
|
|
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
|
|
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('
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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 == '
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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.
|
|
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='
|
|
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'
|
|
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
|
|
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
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
-
|
|
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)
|