webscout 2.3b0__py3-none-any.whl → 2.5__py3-none-any.whl

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.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

@@ -0,0 +1,680 @@
1
+ # thread.py
2
+ # https://github.com/ddh0/easy-llama/
3
+ from ._version import __version__, __llama_cpp_version__
4
+
5
+ """Submodule containing the Thread class, used for interaction with a Model"""
6
+
7
+ import sys
8
+
9
+ from .model import Model, assert_model_is_loaded, _SupportsWriteAndFlush
10
+ from .utils import RESET_ALL, cls, print_verbose, truncate
11
+ from .samplers import SamplerSettings, DefaultSampling
12
+ from typing import Optional, Literal, Union
13
+
14
+ from .formats import blank as formats_blank
15
+
16
+
17
+ class Thread:
18
+ """
19
+ Provide functionality to facilitate easy interactions with a Model
20
+
21
+ This is just a brief overview of webscout.Local.Thread.
22
+ To see a full description of each method and its parameters,
23
+ call help(Thread), or see the relevant docstring.
24
+
25
+ The following methods are available:
26
+ - `.add_message()` - Add a message to `Thread.messages`
27
+ - `.as_string()` - Return this thread's complete message history as a string
28
+ - `.create_message()` - Create a message using the format of this thread
29
+ - `.inference_str_from_messages()` - Using the list of messages, return a string suitable for inference
30
+ - `.interact()` - Start an interactive, terminal-based chat session
31
+ - `.len_messages()` - Get the total length of all messages in tokens
32
+ - `.print_stats()` - Print stats about the context usage in this thread
33
+ - `.reset()` - Clear the list of messages
34
+ - `.send()` - Send a message in this thread
35
+
36
+ The following attributes are available:
37
+ - `.format` - The format being used for messages in this thread
38
+ - `.messages` - The list of messages in this thread
39
+ - `.model` - The `webscout.Local.Model` instance used by this thread
40
+ - `.sampler` - The SamplerSettings object used in this thread
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ model: Model,
46
+ format: dict[str, Union[str, list]],
47
+ sampler: SamplerSettings = DefaultSampling
48
+ ):
49
+ """
50
+ Given a Model and a format, construct a Thread instance.
51
+
52
+ model: The Model to use for text generation
53
+ format: The format specifying how messages should be structured (see webscout.Local.formats)
54
+
55
+ The following parameter is optional:
56
+ - sampler: The SamplerSettings object used to control text generation
57
+ """
58
+
59
+ assert isinstance(model, Model), \
60
+ "Thread: model should be an " + \
61
+ f"instance of webscout.Local.Model, not {type(model)}"
62
+
63
+ assert_model_is_loaded(model)
64
+
65
+ assert isinstance(format, dict), \
66
+ f"Thread: format should be dict, not {type(format)}"
67
+
68
+ if any(k not in format.keys() for k in formats_blank.keys()):
69
+ raise KeyError(
70
+ "Thread: format is missing one or more required keys, see " + \
71
+ "webscout.Local.formats.blank for an example"
72
+ )
73
+
74
+ assert isinstance(format['stops'], list), \
75
+ "Thread: format['stops'] should be list, not " + \
76
+ f"{type(format['stops'])}"
77
+
78
+ assert all(
79
+ hasattr(sampler, attr) for attr in [
80
+ 'max_len_tokens',
81
+ 'temp',
82
+ 'top_p',
83
+ 'min_p',
84
+ 'frequency_penalty',
85
+ 'presence_penalty',
86
+ 'repeat_penalty',
87
+ 'top_k'
88
+ ]
89
+ ), 'Thread: sampler is missing one or more required attributes'
90
+
91
+ self.model: Model = model
92
+ self.format: dict[str, Union[str, list]] = format
93
+ self.messages: list[dict[str, str]] = [
94
+ self.create_message("system", self.format['system_content'])
95
+ ]
96
+ self.sampler: SamplerSettings = sampler
97
+
98
+ if self.model.verbose:
99
+ print_verbose("new Thread instance with the following attributes:")
100
+ print_verbose(f"model == {self.model}")
101
+ print_verbose(f"format['system_prefix'] == {truncate(repr(self.format['system_prefix']))}")
102
+ print_verbose(f"format['system_content'] == {truncate(repr(self.format['system_content']))}")
103
+ print_verbose(f"format['system_postfix'] == {truncate(repr(self.format['system_postfix']))}")
104
+ print_verbose(f"format['user_prefix'] == {truncate(repr(self.format['user_prefix']))}")
105
+ print_verbose(f"format['user_content'] == {truncate(repr(self.format['user_content']))}")
106
+ print_verbose(f"format['user_postfix'] == {truncate(repr(self.format['user_postfix']))}")
107
+ print_verbose(f"format['bot_prefix'] == {truncate(repr(self.format['bot_prefix']))}")
108
+ print_verbose(f"format['bot_content'] == {truncate(repr(self.format['bot_content']))}")
109
+ print_verbose(f"format['bot_postfix'] == {truncate(repr(self.format['bot_postfix']))}")
110
+ print_verbose(f"format['stops'] == {truncate(repr(self.format['stops']))}")
111
+ print_verbose(f"sampler.temp == {self.sampler.temp}")
112
+ print_verbose(f"sampler.top_p == {self.sampler.top_p}")
113
+ print_verbose(f"sampler.min_p == {self.sampler.min_p}")
114
+ print_verbose(f"sampler.frequency_penalty == {self.sampler.frequency_penalty}")
115
+ print_verbose(f"sampler.presence_penalty == {self.sampler.presence_penalty}")
116
+ print_verbose(f"sampler.repeat_penalty == {self.sampler.repeat_penalty}")
117
+ print_verbose(f"sampler.top_k == {self.sampler.top_k}")
118
+
119
+
120
+ def __repr__(self) -> str:
121
+ repr_str = f"Thread({repr(self.model)}, {repr(self.format)}, "
122
+ repr_str += f"{repr(self.sampler)})"
123
+ # system message is created from format, so not represented
124
+ if len(self.messages) <= 1:
125
+ return repr_str
126
+ else:
127
+ for msg in self.messages:
128
+ if msg['role'] == 'user':
129
+ repr_str += "\nThread.add_message('user', " + repr(msg['content']) + ')'
130
+ elif msg['role'] == 'bot':
131
+ repr_str += "\nThread.add_message('bot', " + repr(msg['content']) + ')'
132
+ return repr_str
133
+ def save_conversation(self, filepath: str) -> None:
134
+ """
135
+ Saves the conversation history to a JSON file.
136
+
137
+ filepath: The path to the file where the conversation should be saved.
138
+ """
139
+ import json
140
+
141
+ data = [{'role': msg['role'], 'content': msg['content']} for msg in self.messages]
142
+ with open(filepath, 'w') as f:
143
+ json.dump(data, f, indent=4)
144
+ def __str__(self) -> str:
145
+ return self.as_string()
146
+
147
+ def __len__(self) -> int:
148
+ """
149
+ `len(Thread)` returns the length of the Thread in tokens
150
+
151
+ To get the number of messages in the Thread, use `len(Thread.messages)`
152
+ """
153
+ return self.len_messages()
154
+
155
+ def create_message(
156
+ self,
157
+ role: Literal['system', 'user', 'bot'],
158
+ content: str
159
+ ) -> dict[str, str]:
160
+ """
161
+ Create a message using the format of this Thread
162
+ """
163
+
164
+ assert role.lower() in ['system', 'user', 'bot'], \
165
+ f"create_message: role should be 'system', 'user', or 'bot', not '{role.lower()}'"
166
+
167
+ assert isinstance(content, str), \
168
+ f"create_message: content should be str, not {type(content)}"
169
+
170
+ if role.lower() == 'system':
171
+ return {
172
+ "role": "system",
173
+ "prefix": self.format['system_prefix'],
174
+ "content": content,
175
+ "postfix": self.format['system_postfix']
176
+ }
177
+
178
+ elif role.lower() == 'user':
179
+ return {
180
+ "role": "user",
181
+ "prefix": self.format['user_prefix'],
182
+ "content": content,
183
+ "postfix": self.format['user_postfix']
184
+ }
185
+
186
+ elif role.lower() == 'bot':
187
+ return {
188
+ "role": "bot",
189
+ "prefix": self.format['bot_prefix'],
190
+ "content": content,
191
+ "postfix": self.format['bot_postfix']
192
+ }
193
+
194
+ def len_messages(self) -> int:
195
+ """
196
+ Return the total length of all messages in this thread, in tokens.
197
+
198
+ Equivalent to `len(Thread)`."""
199
+
200
+ return self.model.get_length(self.as_string())
201
+
202
+ def add_message(
203
+ self,
204
+ role: Literal['system', 'user', 'bot'],
205
+ content: str
206
+ ) -> None:
207
+ """
208
+ Create a message and append it to `Thread.messages`.
209
+
210
+ `Thread.add_message(...)` is a shorthand for
211
+ `Thread.messages.append(Thread.create_message(...))`
212
+ """
213
+ self.messages.append(
214
+ self.create_message(
215
+ role=role,
216
+ content=content
217
+ )
218
+ )
219
+
220
+ def inference_str_from_messages(self) -> str:
221
+ """
222
+ Using the list of messages, construct a string suitable for inference,
223
+ respecting the format and context length of this thread.
224
+ """
225
+
226
+ messages = self.messages
227
+
228
+ context_len_budget = self.model.context_length
229
+ if len(messages) > 0:
230
+ sys_msg = messages[0]
231
+ sys_msg_str = (
232
+ sys_msg['prefix'] + sys_msg['content'] + sys_msg['postfix']
233
+ )
234
+ context_len_budget -= self.model.get_length(sys_msg_str)
235
+ else:
236
+ sys_msg_str = ''
237
+
238
+ inf_str = ''
239
+
240
+ # Start at most recent message and work backwards up the history
241
+ # excluding system message. Once we exceed thread
242
+ # max_context_length, break without including that message
243
+ for message in reversed(messages[1:]):
244
+ context_len_budget -= self.model.get_length(
245
+ message['prefix'] + message['content'] + message['postfix']
246
+ )
247
+
248
+ if context_len_budget <= 0:
249
+ break
250
+
251
+ msg_str = (
252
+ message['prefix'] + message['content'] + message['postfix']
253
+ )
254
+
255
+ inf_str = msg_str + inf_str
256
+
257
+ inf_str = sys_msg_str + inf_str
258
+ inf_str += self.format['bot_prefix']
259
+
260
+ return inf_str
261
+
262
+
263
+ def send(self, prompt: str) -> str:
264
+ """
265
+ Send a message in this thread. This adds your message and the bot's
266
+ response to the list of messages.
267
+
268
+ Returns a string containing the response to your message.
269
+ """
270
+
271
+ self.add_message("user", prompt)
272
+ output = self.model.generate(
273
+ self.inference_str_from_messages(),
274
+ stops=self.format['stops'],
275
+ sampler=self.sampler
276
+ )
277
+ self.add_message("bot", output)
278
+
279
+ return output
280
+ def load_conversation(self, filepath: str) -> None:
281
+ """
282
+ Loads a conversation history from a JSON file.
283
+
284
+ filepath: The path to the file containing the conversation history.
285
+ """
286
+ import json
287
+
288
+ with open(filepath, 'r') as f:
289
+ data = json.load(f)
290
+
291
+ self.messages = []
292
+ for item in data:
293
+ self.messages.append(self.create_message(item['role'], item['content']))
294
+
295
+ def _interactive_update_sampler(self) -> None:
296
+ """Interactively update the sampler settings used in this Thread"""
297
+ print()
298
+ try:
299
+ new_max_len_tokens = input(f'max_len_tokens: {self.sampler.max_len_tokens} -> ')
300
+ new_temp = input(f'temp: {self.sampler.temp} -> ')
301
+ new_top_p = input(f'top_p: {self.sampler.top_p} -> ')
302
+ new_min_p = input(f'min_p: {self.sampler.min_p} -> ')
303
+ new_frequency_penalty = input(f'frequency_penalty: {self.sampler.frequency_penalty} -> ')
304
+ new_presence_penalty = input(f'presence_penalty: {self.sampler.presence_penalty} -> ')
305
+ new_repeat_penalty = input(f'repeat_penalty: {self.sampler.repeat_penalty} -> ')
306
+ new_top_k = input(f'top_k: {self.sampler.top_k} -> ')
307
+
308
+ except KeyboardInterrupt:
309
+ print('\nwebscout.Local: sampler settings not updated\n')
310
+ return
311
+ print()
312
+
313
+ try:
314
+ self.sampler.max_len_tokens = int(new_max_len_tokens)
315
+ except ValueError:
316
+ pass
317
+ else:
318
+ print('webscout.Local: max_len_tokens updated')
319
+
320
+ try:
321
+ self.sampler.temp = float(new_temp)
322
+ except ValueError:
323
+ pass
324
+ else:
325
+ print('webscout.Local: temp updated')
326
+
327
+ try:
328
+ self.sampler.top_p = float(new_top_p)
329
+ except ValueError:
330
+ pass
331
+ else:
332
+ print('webscout.Local: top_p updated')
333
+
334
+ try:
335
+ self.sampler.min_p = float(new_min_p)
336
+ except ValueError:
337
+ pass
338
+ else:
339
+ print('webscout.Local: min_p updated')
340
+
341
+ try:
342
+ self.sampler.frequency_penalty = float(new_frequency_penalty)
343
+ except ValueError:
344
+ pass
345
+ else:
346
+ print('webscout.Local: frequency_penalty updated')
347
+
348
+ try:
349
+ self.sampler.presence_penalty = float(new_presence_penalty)
350
+ except ValueError:
351
+ pass
352
+ else:
353
+ print('webscout.Local: presence_penalty updated')
354
+
355
+ try:
356
+ self.sampler.repeat_penalty = float(new_repeat_penalty)
357
+ except ValueError:
358
+ pass
359
+ else:
360
+ print('webscout.Local: repeat_penalty updated')
361
+
362
+ try:
363
+ self.sampler.top_k = int(new_top_k)
364
+ except ValueError:
365
+ pass
366
+ else:
367
+ print('webscout.Local: top_k updated')
368
+ print()
369
+
370
+
371
+ def _interactive_input(
372
+ self,
373
+ prompt: str,
374
+ _dim_style: str,
375
+ _user_style: str,
376
+ _bot_style: str
377
+ ) -> tuple:
378
+ """
379
+ Recive input from the user, while handling multi-line input
380
+ and commands
381
+ """
382
+ full_user_input = '' # may become multiline
383
+
384
+ while True:
385
+ user_input = input(prompt)
386
+
387
+ if user_input.endswith('\\'):
388
+ full_user_input += user_input[:-1] + '\n'
389
+
390
+ elif user_input == '!':
391
+
392
+ print()
393
+ try:
394
+ command = input(f'{RESET_ALL} ! {_dim_style}')
395
+ except KeyboardInterrupt:
396
+ print('\n')
397
+ continue
398
+
399
+ if command == '':
400
+ print(f'\n[no command]\n')
401
+
402
+ elif command.lower() in ['reset', 'restart']:
403
+ self.reset()
404
+ print(f'\n[thread reset]\n')
405
+
406
+ elif command.lower() in ['cls', 'clear']:
407
+ cls()
408
+ print()
409
+
410
+ elif command.lower() in ['ctx', 'context']:
411
+ print(f"\n{self.len_messages()}\n")
412
+
413
+ elif command.lower() in ['stats', 'print_stats']:
414
+ print()
415
+ self.print_stats()
416
+ print()
417
+
418
+ elif command.lower() in ['sampler', 'samplers', 'settings']:
419
+ self._interactive_update_sampler()
420
+
421
+ elif command.lower() in ['str', 'string', 'as_string']:
422
+ print(f"\n{self.as_string()}\n")
423
+
424
+ elif command.lower() in ['save']:
425
+ print()
426
+ try:
427
+ filepath = input(f'Enter filepath to save conversation: ')
428
+ self.save_conversation(filepath)
429
+ print(f'[conversation saved to {filepath}]\n')
430
+ except Exception as e:
431
+ print(f'[error saving conversation: {e}]\n')
432
+ elif command.lower() in ['repr', 'save', 'backup']:
433
+ print(f"\n{repr(self)}\n")
434
+
435
+ elif command.lower() in ['remove', 'rem', 'delete', 'del']:
436
+ print()
437
+ old_len = len(self.messages)
438
+ del self.messages[-1]
439
+ assert len(self.messages) == (old_len - 1)
440
+ print('[removed last message]\n')
441
+ elif command.lower() in ['load']:
442
+ print()
443
+ try:
444
+ filepath = input(f'Enter filepath to load conversation: ')
445
+ self.load_conversation(filepath)
446
+ print(f'[conversation loaded from {filepath}]\n')
447
+ except Exception as e:
448
+ print(f'[error loading conversation: {e}]\n')
449
+ elif command.lower() in ['last', 'repeat']:
450
+ last_msg = self.messages[-1]
451
+ if last_msg['role'] == 'user':
452
+ print(f"\n{_user_style}{last_msg['content']}{RESET_ALL}\n")
453
+ elif last_msg['role'] == 'bot':
454
+ print(f"\n{_bot_style}{last_msg['content']}{RESET_ALL}\n")
455
+
456
+ elif command.lower() in ['inf', 'inference', 'inf_str']:
457
+ print(f'\n"""{self.inference_str_from_messages()}"""\n')
458
+
459
+ elif command.lower() in ['reroll', 're-roll', 're', 'swipe']:
460
+ old_len = len(self.messages)
461
+ del self.messages[-1]
462
+ assert len(self.messages) == (old_len - 1)
463
+ return '', None
464
+
465
+ elif command.lower() in ['exit', 'quit']:
466
+ print(RESET_ALL)
467
+ return None, None
468
+
469
+ elif command.lower() in ['help', '/?', '?']:
470
+ print()
471
+ print('reset / restart -- Reset the thread to its original state')
472
+ print('clear / cls -- Clear the terminal')
473
+ print('context / ctx -- Get the context usage in tokens')
474
+ print('print_stats / stats -- Get the context usage stats')
475
+ print('sampler / settings -- Update the sampler settings')
476
+ print('string / str -- Print the message history as a string')
477
+ print('repr / save -- Print the representation of the thread')
478
+ print('remove / delete -- Remove the last message')
479
+ print('last / repeat -- Repeat the last message')
480
+ print('inference / inf -- Print the inference string')
481
+ print('reroll / swipe -- Regenerate the last message')
482
+ print('exit / quit -- Exit the interactive chat (can also use ^C)')
483
+ print('help / ? -- Show this screen')
484
+ print()
485
+ print("TIP: type < at the prompt and press ENTER to prefix the bot's next message.")
486
+ print(' for example, type "Sure!" to bypass refusals')
487
+ print()
488
+ print('save -- Save the conversation to a JSON file')
489
+ print('load -- Load a conversation from a JSON file')
490
+ else:
491
+ print(f'\n[unknown command]\n')
492
+
493
+ elif user_input == '<': # the next bot message will start with...
494
+
495
+ print()
496
+ try:
497
+ next_message_start = input(f'{_dim_style} < ')
498
+
499
+ except KeyboardInterrupt:
500
+ print(f'{RESET_ALL}\n')
501
+ continue
502
+
503
+ else:
504
+ print()
505
+ return '', next_message_start
506
+
507
+ elif user_input.endswith('<'):
508
+
509
+ print()
510
+
511
+ msg = user_input.removesuffix('<')
512
+ self.add_message("user", msg)
513
+
514
+ try:
515
+ next_message_start = input(f'{_dim_style} < ')
516
+
517
+ except KeyboardInterrupt:
518
+ print(f'{RESET_ALL}\n')
519
+ continue
520
+
521
+ else:
522
+ print()
523
+ return '', next_message_start
524
+
525
+ else:
526
+ full_user_input += user_input
527
+ return full_user_input, None
528
+
529
+
530
+ def interact(
531
+ self,
532
+ color: bool = True,
533
+ header: Optional[str] = None,
534
+ stream: bool = True
535
+ ) -> None:
536
+ """
537
+ Start an interactive chat session using this Thread.
538
+
539
+ While text is being generated, press `^C` to interrupt the bot.
540
+ Then you have the option to press `ENTER` to re-roll, or to simply type
541
+ another message.
542
+
543
+ At the prompt, press `^C` to end the chat session.
544
+
545
+ Type `!` and press `ENTER` to enter a basic command prompt. For a list
546
+ of commands, type `help` at this prompt.
547
+
548
+ Type `<` and press `ENTER` to prefix the bot's next message, for
549
+ example with `Sure!`.
550
+
551
+ The following parameters are optional:
552
+ - color: Whether to use colored text to differentiate user / bot
553
+ - header: Header text to print at the start of the interaction
554
+ - stream: Whether to stream text as it is generated
555
+ """
556
+ print()
557
+
558
+ # fresh import of color codes in case `color` param has changed
559
+ from .utils import USER_STYLE, BOT_STYLE, DIM_STYLE, SPECIAL_STYLE
560
+
561
+ # disable color codes if explicitly disabled by `color` param
562
+ if not color:
563
+ USER_STYLE = ''
564
+ BOT_STYLE = ''
565
+ DIM_STYLE = ''
566
+ SPECIAL_STYLE = ''
567
+
568
+ if header is not None:
569
+ print(f"{SPECIAL_STYLE}{header}{RESET_ALL}\n")
570
+
571
+ while True:
572
+
573
+ prompt = f"{RESET_ALL} > {USER_STYLE}"
574
+
575
+ try:
576
+ user_prompt, next_message_start = self._interactive_input(
577
+ prompt,
578
+ DIM_STYLE,
579
+ USER_STYLE,
580
+ BOT_STYLE
581
+ )
582
+ except KeyboardInterrupt:
583
+ print(f"{RESET_ALL}\n")
584
+ return
585
+
586
+ # got 'exit' or 'quit' command
587
+ if user_prompt is None and next_message_start is None:
588
+ break
589
+
590
+ if next_message_start is not None:
591
+ try:
592
+ if stream:
593
+ print(f"{BOT_STYLE}{next_message_start}", end='', flush=True)
594
+ output = next_message_start + self.model.stream_print(
595
+ self.inference_str_from_messages() + next_message_start,
596
+ stops=self.format['stops'],
597
+ sampler=self.sampler,
598
+ end=''
599
+ )
600
+ else:
601
+ print(f"{BOT_STYLE}", end='', flush=True)
602
+ output = next_message_start + self.model.generate(
603
+ self.inference_str_from_messages() + next_message_start,
604
+ stops=self.format['stops'],
605
+ sampler=self.sampler
606
+ )
607
+ print(output, end='', flush=True)
608
+ except KeyboardInterrupt:
609
+ print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
610
+ continue
611
+ else:
612
+ self.add_message("bot", output)
613
+ else:
614
+ print(BOT_STYLE)
615
+ if user_prompt != "":
616
+ self.add_message("user", user_prompt)
617
+ try:
618
+ if stream:
619
+ output = self.model.stream_print(
620
+ self.inference_str_from_messages(),
621
+ stops=self.format['stops'],
622
+ sampler=self.sampler,
623
+ end=''
624
+ )
625
+ else:
626
+ output = self.model.generate(
627
+ self.inference_str_from_messages(),
628
+ stops=self.format['stops'],
629
+ sampler=self.sampler
630
+ )
631
+ print(output, end='', flush=True)
632
+ except KeyboardInterrupt:
633
+ print(f"{DIM_STYLE} [message not added to history; press ENTER to re-roll]\n")
634
+ continue
635
+ else:
636
+ self.add_message("bot", output)
637
+
638
+ if output.endswith("\n\n"):
639
+ print(RESET_ALL, end = '', flush=True)
640
+ elif output.endswith("\n"):
641
+ print(RESET_ALL)
642
+ else:
643
+ print(f"{RESET_ALL}\n")
644
+
645
+
646
+ def reset(self) -> None:
647
+ """
648
+ Clear the list of messages, which resets the thread to its original
649
+ state
650
+ """
651
+ self.messages: list[dict[str, str]] = [
652
+ self.create_message("system", self.format['system_content'])
653
+ ]
654
+
655
+
656
+ def as_string(self) -> str:
657
+ """Return this thread's message history as a string"""
658
+ ret = ''
659
+ for msg in self.messages:
660
+ ret += msg['prefix']
661
+ ret += msg['content']
662
+ ret += msg['postfix']
663
+ return ret
664
+
665
+
666
+ def print_stats(
667
+ self,
668
+ end: str = '\n',
669
+ file: _SupportsWriteAndFlush = sys.stdout,
670
+ flush: bool = True
671
+ ) -> None:
672
+ """Print stats about the context usage in this thread"""
673
+ thread_len_tokens = self.len_messages()
674
+ max_ctx_len = self.model.context_length
675
+ context_used_percentage = round((thread_len_tokens/max_ctx_len)*100)
676
+ print(f"{thread_len_tokens} / {max_ctx_len} tokens", file=file, flush=flush)
677
+ print(f"{context_used_percentage}% of context used", file=file, flush=flush)
678
+ print(f"{len(self.messages)} messages", end=end, file=file, flush=flush)
679
+ if not flush:
680
+ file.flush()