LLMlight 0.3.0__tar.gz → 0.5.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.5.0}/LLMlight/LLMlight.py +283 -128
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/RAG.py +5 -2
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/__init__.py +9 -7
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/examples.py +352 -69
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/memory.py +46 -10
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/read_outlook.py +2 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight/utils.py +17 -1
- llmlight-0.5.0/LLMlight.egg-info/PKG-INFO +263 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight.egg-info/requires.txt +2 -0
- llmlight-0.5.0/PKG-INFO +263 -0
- llmlight-0.5.0/README.md +231 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/pyproject.toml +16 -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.5.0}/LICENSE +0 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight.egg-info/SOURCES.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight.egg-info/dependency_links.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/LLMlight.egg-info/top_level.txt +0 -0
- {llmlight-0.3.0 → llmlight-0.5.0}/MANIFEST.in +0 -0
- {llmlight-0.3.0 → llmlight-0.5.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,
|
|
@@ -250,7 +274,7 @@ class LLMlight:
|
|
|
250
274
|
Whether to enable streaming (default is False).
|
|
251
275
|
return_type: bool, optional
|
|
252
276
|
Return dictionary in case the output is a json
|
|
253
|
-
'
|
|
277
|
+
'max': Output the full json
|
|
254
278
|
'dict': Convert json into dictionary.
|
|
255
279
|
'string': Return only the string answer (remove thinking strings using tags: <think> </think>).
|
|
256
280
|
'string_with_thinking' Return the full response which includes the thinking proces (if available).
|
|
@@ -276,7 +300,7 @@ class LLMlight:
|
|
|
276
300
|
if context is None: context = self.context
|
|
277
301
|
if temperature is None: temperature = self.temperature
|
|
278
302
|
if top_p is None: top_p = self.top_p
|
|
279
|
-
self.task = '
|
|
303
|
+
self.task = 'max'
|
|
280
304
|
|
|
281
305
|
# Set system message
|
|
282
306
|
system = set_system_message(system)
|
|
@@ -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):
|
|
@@ -319,7 +344,7 @@ class LLMlight:
|
|
|
319
344
|
# # context = '\n\n'.join(context.values())
|
|
320
345
|
# return context
|
|
321
346
|
|
|
322
|
-
def requests_post_gguf(self, prompt, system, temperature=0.8, top_p=1, headers=None, task='
|
|
347
|
+
def requests_post_gguf(self, prompt, system, temperature=0.8, top_p=1, headers=None, task='max', stream=False, return_type='string'):
|
|
323
348
|
# Note that it is better to use messages_prompt instead of a dict (messages_dict) because most GGUF-based models don't have a tokenizer/parser that can interpret the JSON-style message structure.
|
|
324
349
|
# Prepare data for request.
|
|
325
350
|
if headers is None: headers = {"Content-Type": "application/json"}
|
|
@@ -349,7 +374,7 @@ class LLMlight:
|
|
|
349
374
|
# Return
|
|
350
375
|
return response
|
|
351
376
|
|
|
352
|
-
def requests_post_http(self, prompt, system, temperature=0.8, top_p=1, headers=None, task='
|
|
377
|
+
def requests_post_http(self, prompt, system, temperature=0.8, top_p=1, headers=None, task='max', stream=False, return_type='string', max_tokens=None):
|
|
353
378
|
# Prepare data for request.
|
|
354
379
|
if headers is None: headers = {"Content-Type": "application/json"}
|
|
355
380
|
# Prepare messages
|
|
@@ -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]
|
|
564
|
+
|
|
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
|
|
487
571
|
|
|
488
|
-
logger.info('Creating
|
|
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
|
|
@@ -521,22 +604,41 @@ class LLMlight:
|
|
|
521
604
|
# Return
|
|
522
605
|
return results
|
|
523
606
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
def task(self,
|
|
607
|
+
def summarize(self,
|
|
527
608
|
query="Extract key insights while maintaining coherence of the previous summaries.",
|
|
528
|
-
instructions="",
|
|
529
|
-
system="You are a
|
|
530
|
-
response_format="**comprehensive, structured document covering all key insights**",
|
|
531
|
-
task='question',
|
|
609
|
+
instructions="Extract key insights from the **new text chunk** while maintaining coherence with **Previous summaries",
|
|
610
|
+
system="You are a professional summarizer with over two decades of experience. Your strength is that you know how to deal with partial and incomplete texts but you do not make up new stuff. Keep the focus on the original input.",
|
|
611
|
+
response_format="**Make a comprehensive, structured document covering all key insights**",
|
|
532
612
|
context=None,
|
|
533
613
|
return_type='string',
|
|
534
614
|
):
|
|
535
615
|
"""
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
616
|
+
Summarize large documents iteratively while maintaining coherence across text chunks.
|
|
617
|
+
|
|
618
|
+
This function splits the input text into smaller chunks and processes each part in sequence.
|
|
619
|
+
For every chunk, it generates a partial summary while incorporating the context of the
|
|
620
|
+
previous summaries. After all chunks have been processed, the function combines the partial
|
|
621
|
+
results into a final, coherent, and structured summary.
|
|
622
|
+
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
query : str, optional
|
|
626
|
+
The guiding task or question for summarization (default extracts key insights).
|
|
627
|
+
instructions : str, optional
|
|
628
|
+
Additional instructions for the summarizer, tailored to each chunk.
|
|
629
|
+
system : str
|
|
630
|
+
System message that sets the role and behavior of the summarizer.
|
|
631
|
+
response_format : str, optional
|
|
632
|
+
Defines the format of the final output (default is a structured document).
|
|
633
|
+
context : str or dict, optional
|
|
634
|
+
Input text or structured content to be summarized. If None, uses `self.context`.
|
|
635
|
+
return_type : str, optional
|
|
636
|
+
Format of the returned result (default "string").
|
|
637
|
+
|
|
638
|
+
Returns
|
|
639
|
+
-------
|
|
640
|
+
str
|
|
641
|
+
A comprehensive, coherent summary that integrates insights across all chunks.
|
|
540
642
|
|
|
541
643
|
"""
|
|
542
644
|
if system is None:
|
|
@@ -552,11 +654,11 @@ class LLMlight:
|
|
|
552
654
|
else:
|
|
553
655
|
context = self.context
|
|
554
656
|
|
|
555
|
-
logger.info(f'Processing the document for the given task..')
|
|
556
|
-
|
|
557
657
|
# Create chunks based on words
|
|
558
658
|
chunks = utils.chunk_text(context, method=self.chunks['method'], chunk_size=self.chunks['size'], overlap=self.chunks['overlap'])
|
|
559
659
|
|
|
660
|
+
logger.info(f'Processing the document using {len(chunks)} for the given task..')
|
|
661
|
+
|
|
560
662
|
# Build a structured prompt that includes all previous summaries
|
|
561
663
|
response_list = []
|
|
562
664
|
for i, chunk in enumerate(chunks):
|
|
@@ -573,10 +675,9 @@ class LLMlight:
|
|
|
573
675
|
+ f"{chunk}\n\n"
|
|
574
676
|
|
|
575
677
|
"### Instructions:\n"
|
|
576
|
-
+ "
|
|
577
|
-
+ f"{instructions}\n\n"
|
|
678
|
+
+ f"{instructions}**.\n\n"
|
|
578
679
|
|
|
579
|
-
f"###
|
|
680
|
+
f"### Question:\n"
|
|
580
681
|
f"{query}\n\n"
|
|
581
682
|
|
|
582
683
|
"### Improved Results:\n"
|
|
@@ -584,7 +685,7 @@ class LLMlight:
|
|
|
584
685
|
|
|
585
686
|
# Get the summary for the current chunk
|
|
586
687
|
# chunk_result = self.query_llm(prompt, system=system)
|
|
587
|
-
chunk_result= self.requests_post_http(prompt, system, temperature=self.temperature, top_p=self.top_p, task='
|
|
688
|
+
chunk_result = self.requests_post_http(prompt, system, temperature=self.temperature, top_p=self.top_p, task='max', stream=False, return_type='string')
|
|
588
689
|
|
|
589
690
|
response_list.append(f"Results {i+1}:\n" + chunk_result)
|
|
590
691
|
|
|
@@ -595,23 +696,20 @@ class LLMlight:
|
|
|
595
696
|
{results_total}
|
|
596
697
|
|
|
597
698
|
### Task:
|
|
598
|
-
|
|
699
|
+
Your task is to connect all the parts into a **coherent, well-structured document**. Make sure it becomes is a very good summary.
|
|
599
700
|
|
|
600
701
|
### Instructions:
|
|
601
702
|
- Maintain as much as possible the key insights but ensure logical flow.
|
|
602
703
|
- Connect insights smoothly while keeping essential details intact.
|
|
603
|
-
|
|
604
|
-
{response_format}
|
|
605
|
-
|
|
606
|
-
f"### {task}:\n"
|
|
607
|
-
{query}\n\n
|
|
704
|
+
- Only use bulletpoints when really needed.
|
|
705
|
+
- {response_format}
|
|
608
706
|
|
|
609
707
|
Begin your response below:
|
|
610
708
|
"""
|
|
611
709
|
logger.info('Combining all information to create a single coherent output..')
|
|
612
710
|
# Create the final summary.
|
|
613
711
|
# final_result = self.query_llm(final_prompt, system=system, return_type=return_type)
|
|
614
|
-
final_result = self.requests_post_http(final_prompt, system, temperature=self.temperature, top_p=self.top_p, task='
|
|
712
|
+
final_result = self.requests_post_http(final_prompt, system, temperature=self.temperature, top_p=self.top_p, task='max', stream=False, return_type=return_type)
|
|
615
713
|
# Return
|
|
616
714
|
return final_result
|
|
617
715
|
# return {'summary': final_result, 'summary_per_chunk': results_total}
|
|
@@ -921,17 +1019,17 @@ class LLMlight:
|
|
|
921
1019
|
# Return
|
|
922
1020
|
return query_vector, chunk_vectors
|
|
923
1021
|
|
|
924
|
-
def
|
|
1022
|
+
def compute_context_strategy(self, query, context, instructions, system):
|
|
925
1023
|
# Create advanced prompt using relevant chunks of text, the input query and instructions
|
|
926
1024
|
if context is not None:
|
|
927
|
-
if self.
|
|
1025
|
+
if self.context_strategy=='global-reasoning':
|
|
928
1026
|
# Global Reasoning
|
|
929
1027
|
relevant_context = self.global_reasoning(query, context, instructions, system, rewrite_query=False, return_per_chunk=True)
|
|
930
|
-
elif self.
|
|
1028
|
+
elif self.context_strategy=='chunk-wise':
|
|
931
1029
|
# Analyze per chunk
|
|
932
1030
|
relevant_context = self.chunk_wise(query, context, instructions, system, top_chunks=0, return_per_chunk=True)
|
|
933
1031
|
else:
|
|
934
|
-
logger.info(f'No
|
|
1032
|
+
logger.info(f'No Context Strategy method is applied.')
|
|
935
1033
|
relevant_context = context
|
|
936
1034
|
else:
|
|
937
1035
|
# Default
|
|
@@ -948,11 +1046,13 @@ class LLMlight:
|
|
|
948
1046
|
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
1047
|
|
|
950
1048
|
# Retrieve context from video memory
|
|
951
|
-
if
|
|
1049
|
+
if self.file_path and os.path.isfile(self.file_path):
|
|
1050
|
+
# and os.path.isfile(self.memory.index_path)
|
|
952
1051
|
logger.info(f"Initialize retrieval from memory.. Collect [{self.top_chunks}] chunks from video-memory using {self.embedding['memory']}.")
|
|
953
1052
|
# Initialize retriever
|
|
954
1053
|
# retriever = self.memory.MemvidRetriever(video_file=self.memory.file_path, index_file=self.memory['index_path'], config=self.memory.config)
|
|
955
|
-
self
|
|
1054
|
+
if not hasattr(self, 'memory'):
|
|
1055
|
+
self.memory_load()
|
|
956
1056
|
|
|
957
1057
|
# Retrieval based on embedding
|
|
958
1058
|
if self.embedding['memory']=='memvid':
|
|
@@ -984,7 +1084,7 @@ class LLMlight:
|
|
|
984
1084
|
|
|
985
1085
|
if context is not None:
|
|
986
1086
|
# Get relevant context using RAG and embedding
|
|
987
|
-
if self.retrieval_method == '
|
|
1087
|
+
if self.retrieval_method == 'naive_rag' and self.embedding['context'] in get_embeddings():
|
|
988
1088
|
# Find the best matching parts using simple retrieval method approach.
|
|
989
1089
|
logger.info(f"Initialize retrieval from context.. Collect [{self.top_chunks}] chunks from context using {self.embedding['context']}.")
|
|
990
1090
|
# Create chunks
|
|
@@ -1015,9 +1115,11 @@ class LLMlight:
|
|
|
1015
1115
|
logger.info(f'Computing probability distribution..')
|
|
1016
1116
|
# Compute probability
|
|
1017
1117
|
out = self.compute_probability(query, scores, embedding=self.embedding['memory'], n=1000)
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1118
|
+
|
|
1119
|
+
if out is not None:
|
|
1120
|
+
# Only keep significant chunks
|
|
1121
|
+
relevant_context = list(np.array(relevant_context)[out.get('y_bool', '')])
|
|
1122
|
+
logger.info(f'{len(relevant_context)} significant chunks found with alpha<={self.alpha}')
|
|
1021
1123
|
|
|
1022
1124
|
# Return relevant context
|
|
1023
1125
|
return relevant_context
|
|
@@ -1027,7 +1129,7 @@ class LLMlight:
|
|
|
1027
1129
|
if isinstance(context, list):
|
|
1028
1130
|
context = "\n\n---\n\n".join([f"### Chunk {i+1}:\n{s}" for i, s in enumerate(context)])
|
|
1029
1131
|
if context=='':
|
|
1030
|
-
logger.
|
|
1132
|
+
logger.info('No context is provided into the prompt.')
|
|
1031
1133
|
|
|
1032
1134
|
prompt = (
|
|
1033
1135
|
("Context:\n" + context + "\n\n" if context else "")
|
|
@@ -1040,7 +1142,7 @@ class LLMlight:
|
|
|
1040
1142
|
# Return
|
|
1041
1143
|
return prompt
|
|
1042
1144
|
|
|
1043
|
-
def read_pdf(self, file_path, title_pages=[1, 2], body_pages=[], reference_pages=[-1], return_type='
|
|
1145
|
+
def read_pdf(self, file_path, title_pages=[1, 2], body_pages=[], reference_pages=[-1], return_type='str'):
|
|
1044
1146
|
"""
|
|
1045
1147
|
Reads a PDF file and extracts its text content as a string.
|
|
1046
1148
|
|
|
@@ -1049,19 +1151,30 @@ class LLMlight:
|
|
|
1049
1151
|
|
|
1050
1152
|
Returns:
|
|
1051
1153
|
str: Extracted text from the PDF.
|
|
1154
|
+
dict: dictionary
|
|
1052
1155
|
|
|
1053
1156
|
"""
|
|
1157
|
+
context = ''
|
|
1158
|
+
|
|
1159
|
+
if 'http' in file_path[0:5]:
|
|
1160
|
+
logger.info('Downloading file from url..')
|
|
1161
|
+
url = file_path
|
|
1162
|
+
filename = wget.filename_from_url(url)
|
|
1163
|
+
file_path = os.path.join(self.tempdir, filename)
|
|
1164
|
+
context = wget.download(url, file_path)
|
|
1165
|
+
|
|
1054
1166
|
if os.path.isfile(file_path):
|
|
1055
1167
|
# Read pdf
|
|
1056
1168
|
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
1169
|
# if return_type=='dict':
|
|
1060
1170
|
# counts = utils.count_words(self.context['body'])
|
|
1061
1171
|
# self.context['body'] = self.context['body']
|
|
1062
1172
|
else:
|
|
1063
1173
|
logger.error(f'file_path does not exist: {file_path}')
|
|
1064
1174
|
|
|
1175
|
+
# Return
|
|
1176
|
+
return context
|
|
1177
|
+
|
|
1065
1178
|
def get_available_models(self, validate=False):
|
|
1066
1179
|
"""Retrieve available models from the configured API endpoint.
|
|
1067
1180
|
|
|
@@ -1084,7 +1197,7 @@ class LLMlight:
|
|
|
1084
1197
|
>>> # Import library
|
|
1085
1198
|
>>> from LLMlight import LLMlight
|
|
1086
1199
|
<<< # Initialize
|
|
1087
|
-
>>> client = LLMlight(endpoint='http://localhost:1234/v1/chat/completions'
|
|
1200
|
+
>>> client = LLMlight(endpoint='http://localhost:1234/v1/chat/completions')
|
|
1088
1201
|
>>> # Get models
|
|
1089
1202
|
>>> models = client.get_available_models(validate=False)
|
|
1090
1203
|
>>> # Print
|
|
@@ -1097,11 +1210,10 @@ class LLMlight:
|
|
|
1097
1210
|
- Relies on the `LLMlight` class for validation (must be importable).
|
|
1098
1211
|
"""
|
|
1099
1212
|
base_url = '/'.join(self.endpoint.split('/')[:3]) + '/'
|
|
1100
|
-
logger.info('Collecting models in the API endpoint
|
|
1213
|
+
logger.info(f'Collecting models in the API endpoint: {self.endpoint}')
|
|
1101
1214
|
|
|
1102
1215
|
try:
|
|
1103
1216
|
model_url = base_url.rstrip('/') + '/v1/models'
|
|
1104
|
-
logger.info(f'Looking up models: {model_url}')
|
|
1105
1217
|
response = requests.get(model_url, timeout=10)
|
|
1106
1218
|
if response.status_code == 200:
|
|
1107
1219
|
try:
|
|
@@ -1255,7 +1367,7 @@ def load_local_gguf_model(model_path: str, n_ctx: int=4096, n_threads: int=8, n_
|
|
|
1255
1367
|
# Return
|
|
1256
1368
|
return llm
|
|
1257
1369
|
|
|
1258
|
-
def compute_tokens(string, n_ctx=4096, task='
|
|
1370
|
+
def compute_tokens(string, n_ctx=4096, task='max'):
|
|
1259
1371
|
tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
|
1260
1372
|
# Tokenize the input string
|
|
1261
1373
|
tokens = tokenizer.encode(string, truncation=True, max_length=n_ctx)
|
|
@@ -1269,18 +1381,32 @@ def compute_tokens(string, n_ctx=4096, task='full'):
|
|
|
1269
1381
|
return used_tokens, max_tokens
|
|
1270
1382
|
|
|
1271
1383
|
|
|
1272
|
-
def compute_max_tokens(used_tokens, n_ctx=4096, task="
|
|
1384
|
+
def compute_max_tokens(used_tokens, n_ctx=4096, task="max"):
|
|
1273
1385
|
"""
|
|
1274
|
-
Compute the number of tokens that can be generated
|
|
1386
|
+
Compute the maximum number of tokens that can be generated for a given task,
|
|
1387
|
+
taking into account the number of tokens already used and the model's context window.
|
|
1275
1388
|
|
|
1276
|
-
Parameters
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1389
|
+
Parameters
|
|
1390
|
+
----------
|
|
1391
|
+
used_tokens : int
|
|
1392
|
+
Number of tokens already consumed in the current context.
|
|
1393
|
+
n_ctx : int, optional
|
|
1394
|
+
Total context window size of the model (default is 4096 tokens).
|
|
1395
|
+
task : str, optional
|
|
1396
|
+
Type of generation task. Determines the proportion of the remaining tokens to use.
|
|
1397
|
+
Options are:
|
|
1398
|
+
- "summarization": Use up to 50% of the context window, minimum 128 tokens.
|
|
1399
|
+
- "chat": Use up to 60% of the context window, minimum 128 tokens.
|
|
1400
|
+
- "code": Use up to 75% of the context window, minimum 128 tokens.
|
|
1401
|
+
- "longform": Use up to 90% of the context window, minimum 256 tokens.
|
|
1402
|
+
- "max": Use all remaining tokens.
|
|
1403
|
+
Any unrecognized task defaults to a safe fallback using 50% of the context window.
|
|
1281
1404
|
|
|
1282
|
-
Returns
|
|
1283
|
-
|
|
1405
|
+
Returns
|
|
1406
|
+
-------
|
|
1407
|
+
max_tokens : int
|
|
1408
|
+
Maximum number of tokens that can be generated for the specified task,
|
|
1409
|
+
ensuring at least a minimum number of tokens as defined per task type.
|
|
1284
1410
|
"""
|
|
1285
1411
|
|
|
1286
1412
|
available_tokens = max(n_ctx - used_tokens, 1) # Ensure at least 1
|
|
@@ -1294,7 +1420,7 @@ def compute_max_tokens(used_tokens, n_ctx=4096, task="full"):
|
|
|
1294
1420
|
max_tokens = max(min(available_tokens, int(n_ctx * 0.75)), 128)
|
|
1295
1421
|
elif task == "longform":
|
|
1296
1422
|
max_tokens = max(min(available_tokens, int(n_ctx * 0.9)), 256)
|
|
1297
|
-
elif task == "
|
|
1423
|
+
elif task == "max":
|
|
1298
1424
|
max_tokens = available_tokens
|
|
1299
1425
|
else:
|
|
1300
1426
|
# Default to safe fallback
|
|
@@ -1402,3 +1528,32 @@ def disable_tqdm():
|
|
|
1402
1528
|
"""Set the logger for verbosity messages."""
|
|
1403
1529
|
return (True if (logger.getEffectiveLevel()>=30) else False)
|
|
1404
1530
|
|
|
1531
|
+
# %% Retrieve files files.
|
|
1532
|
+
class wget:
|
|
1533
|
+
"""Retrieve file from url."""
|
|
1534
|
+
|
|
1535
|
+
def filename_from_url(url, ext=True):
|
|
1536
|
+
"""Return filename."""
|
|
1537
|
+
urlname = os.path.basename(url)
|
|
1538
|
+
if not ext: _, ext = os.path.splitext(urlname)
|
|
1539
|
+
return urlname
|
|
1540
|
+
|
|
1541
|
+
def download(url, writepath):
|
|
1542
|
+
"""Download.
|
|
1543
|
+
|
|
1544
|
+
Parameters
|
|
1545
|
+
----------
|
|
1546
|
+
url : str.
|
|
1547
|
+
Internet source.
|
|
1548
|
+
writepath : str.
|
|
1549
|
+
Directory to write the file.
|
|
1550
|
+
|
|
1551
|
+
Returns
|
|
1552
|
+
-------
|
|
1553
|
+
None.
|
|
1554
|
+
|
|
1555
|
+
"""
|
|
1556
|
+
r = requests.get(url, stream=True)
|
|
1557
|
+
with open(writepath, "wb") as fd:
|
|
1558
|
+
for chunk in r.iter_content(chunk_size=1024):
|
|
1559
|
+
fd.write(chunk)
|