pygpt-net 2.6.3__py3-none-any.whl → 2.6.4__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.
Files changed (66) hide show
  1. pygpt_net/CHANGELOG.txt +5 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/config.py +55 -65
  4. pygpt_net/controller/chat/chat.py +38 -35
  5. pygpt_net/controller/chat/render.py +144 -217
  6. pygpt_net/controller/chat/stream.py +51 -25
  7. pygpt_net/controller/config/config.py +39 -42
  8. pygpt_net/controller/config/field/checkbox.py +16 -12
  9. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  10. pygpt_net/controller/config/field/cmd.py +51 -57
  11. pygpt_net/controller/config/field/combo.py +33 -16
  12. pygpt_net/controller/config/field/dictionary.py +48 -55
  13. pygpt_net/controller/config/field/input.py +50 -32
  14. pygpt_net/controller/config/field/slider.py +40 -45
  15. pygpt_net/controller/config/field/textarea.py +20 -6
  16. pygpt_net/controller/config/placeholder.py +110 -231
  17. pygpt_net/controller/lang/mapping.py +57 -95
  18. pygpt_net/controller/lang/plugins.py +64 -55
  19. pygpt_net/controller/lang/settings.py +39 -38
  20. pygpt_net/controller/layout/layout.py +11 -2
  21. pygpt_net/controller/plugins/plugins.py +19 -1
  22. pygpt_net/controller/ui/mode.py +107 -125
  23. pygpt_net/core/bridge/bridge.py +5 -5
  24. pygpt_net/core/command/command.py +149 -219
  25. pygpt_net/core/ctx/ctx.py +94 -146
  26. pygpt_net/core/debug/debug.py +48 -58
  27. pygpt_net/core/models/models.py +74 -112
  28. pygpt_net/core/modes/modes.py +13 -21
  29. pygpt_net/core/plugins/plugins.py +154 -177
  30. pygpt_net/core/presets/presets.py +103 -176
  31. pygpt_net/core/render/web/body.py +2 -3
  32. pygpt_net/core/render/web/renderer.py +109 -180
  33. pygpt_net/core/text/utils.py +28 -44
  34. pygpt_net/core/tokens/tokens.py +104 -203
  35. pygpt_net/data/config/config.json +2 -2
  36. pygpt_net/data/config/models.json +2 -2
  37. pygpt_net/item/ctx.py +141 -139
  38. pygpt_net/plugin/agent/plugin.py +2 -1
  39. pygpt_net/plugin/audio_output/plugin.py +5 -2
  40. pygpt_net/plugin/base/plugin.py +77 -93
  41. pygpt_net/plugin/bitbucket/plugin.py +3 -2
  42. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  43. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  44. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  45. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  46. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  47. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  48. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  49. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  50. pygpt_net/plugin/experts/plugin.py +2 -2
  51. pygpt_net/plugin/facebook/plugin.py +3 -4
  52. pygpt_net/plugin/github/plugin.py +4 -2
  53. pygpt_net/plugin/google/plugin.py +3 -3
  54. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  55. pygpt_net/plugin/mailer/plugin.py +3 -5
  56. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  57. pygpt_net/plugin/real_time/plugin.py +52 -60
  58. pygpt_net/plugin/slack/plugin.py +3 -4
  59. pygpt_net/plugin/telegram/plugin.py +3 -4
  60. pygpt_net/plugin/twitter/plugin.py +3 -4
  61. pygpt_net/ui/widget/textarea/web.py +18 -14
  62. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/METADATA +7 -2
  63. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/RECORD +66 -66
  64. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/LICENSE +0 -0
  65. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/WHEEL +0 -0
  66. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 2.6.4 (2025-08-15)
2
+
3
+ - Fix: tool execution in OpenAI Agents.
4
+ - Optimizations.
5
+
1
6
  2.6.3 (2025-08-15)
2
7
 
3
8
  - Optimized streaming and CPU usage.
pygpt_net/__init__.py CHANGED
@@ -13,7 +13,7 @@ __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.3"
16
+ __version__ = "2.6.4"
17
17
  __build__ = "2025-08-15"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
pygpt_net/config.py CHANGED
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.21 15:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -16,11 +16,15 @@ import re
16
16
 
17
17
  from pathlib import Path
18
18
  from packaging.version import Version
19
+ from operator import itemgetter
19
20
 
20
21
  from pygpt_net.core.profile import Profile
21
22
  from pygpt_net.provider.core.config.json_file import JsonFileProvider
22
23
  from pygpt_net.core.types.console import Color
23
24
 
25
+ _RE_VERSION = re.compile(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]')
26
+ _RE_BUILD = re.compile(r'__build__\s*=\s*[\'"]([^\'"]*)[\'"]')
27
+
24
28
 
25
29
  class Config:
26
30
  CONFIG_DIR = 'pygpt-net'
@@ -46,9 +50,9 @@ class Config:
46
50
  self.initialized_base = False
47
51
  self.initialized_workdir = False
48
52
  self.db_echo = False
49
- self.data = {} # user config
50
- self.data_base = {} # base config
51
- self.data_session = {} # temporary config (session only)
53
+ self.data = {}
54
+ self.data_base = {}
55
+ self.data_session = {}
52
56
  self.version = version
53
57
  self.dirs = {
54
58
  "capture": "capture",
@@ -63,6 +67,9 @@ class Config:
63
67
  "upload": "upload",
64
68
  "tmp": "tmp",
65
69
  }
70
+ self._app_path = None
71
+ self._version_cache = version if version else None
72
+ self._build_cache = None
66
73
  self.provider = JsonFileProvider(window)
67
74
  self.provider.path_app = self.get_app_path()
68
75
  self.provider.meta = self.append_meta()
@@ -81,10 +88,8 @@ class Config:
81
88
 
82
89
  def install(self):
83
90
  """Install database and provider data"""
84
- self.window.core.db.echo = self.db_echo # verbose on/off
91
+ self.window.core.db.echo = self.db_echo
85
92
  self.window.core.db.init()
86
-
87
- # install provider configs
88
93
  self.provider.install()
89
94
 
90
95
  def get_path(self) -> str:
@@ -108,7 +113,6 @@ class Config:
108
113
  path = os.path.join(Path.home(), '.config', Config.CONFIG_DIR)
109
114
  if "PYGPT_WORKDIR" in os.environ and os.environ["PYGPT_WORKDIR"] != "":
110
115
  print("FORCE using workdir: {}".format(os.environ["PYGPT_WORKDIR"]))
111
- # convert relative path to absolute path if needed
112
116
  if not os.path.isabs(os.environ["PYGPT_WORKDIR"]):
113
117
  path = os.path.join(os.getcwd(), os.environ["PYGPT_WORKDIR"])
114
118
  else:
@@ -128,11 +132,11 @@ class Config:
128
132
  """
129
133
  is_test = os.environ.get('ENV_TEST') == '1'
130
134
  path = Path(Config.get_base_workdir())
131
- if not path.exists() and not is_test: # DISABLE in tests!!!
135
+ if not path.exists() and not is_test:
132
136
  path.mkdir(parents=True, exist_ok=True)
133
137
  path_file = "path.cfg"
134
138
  p = os.path.join(str(path), path_file)
135
- if not os.path.exists(p) and not is_test: # DISABLE in tests!!!
139
+ if not os.path.exists(p) and not is_test:
136
140
  with open(p, 'w', encoding='utf-8') as f:
137
141
  f.write("")
138
142
  else:
@@ -155,6 +159,7 @@ class Config:
155
159
  """
156
160
  self.path = path
157
161
  self.provider.path = path
162
+ self.initialized_workdir = True
158
163
  if reload:
159
164
  self.initialized = False
160
165
  self.init(True)
@@ -178,7 +183,7 @@ class Config:
178
183
  if dir not in self.dirs:
179
184
  raise Exception('Unknown dir: {}'.format(dir))
180
185
 
181
- dir_data_allowed = ["img", "capture", "upload"]
186
+ dir_data_allowed = ("img", "capture", "upload")
182
187
 
183
188
  if self.has("upload.data_dir") \
184
189
  and self.get("upload.data_dir") \
@@ -186,8 +191,7 @@ class Config:
186
191
  path = os.path.join(self.get_user_path(), self.dirs["data"], self.dirs[dir])
187
192
  else:
188
193
  path = os.path.join(self.get_user_path(), self.dirs[dir])
189
- if not os.path.exists(path):
190
- os.makedirs(path, exist_ok=True)
194
+ os.makedirs(path, exist_ok=True)
191
195
 
192
196
  return path
193
197
 
@@ -208,10 +212,13 @@ class Config:
208
212
 
209
213
  :return: app root path
210
214
  """
211
- if self.is_compiled(): # if compiled with pyinstaller
212
- return os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
215
+ if hasattr(self, '_app_path') and self._app_path is not None:
216
+ return self._app_path
217
+ if self.is_compiled():
218
+ self._app_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
213
219
  else:
214
- return os.path.abspath(os.path.dirname(__file__))
220
+ self._app_path = os.path.abspath(os.path.dirname(__file__))
221
+ return self._app_path
215
222
 
216
223
  def get_user_path(self) -> str:
217
224
  """
@@ -229,15 +236,14 @@ class Config:
229
236
  """
230
237
  if not self.initialized:
231
238
 
232
- # if app initialization
233
239
  if all:
234
240
  v = self.get_version()
235
241
  build = self.get_build().replace('.', '-')
236
- os = self.window.core.platforms.get_os()
242
+ os_name = self.window.core.platforms.get_os()
237
243
  architecture = self.window.core.platforms.get_architecture()
238
244
 
239
245
  print("===================================================")
240
- print(f" {Color.BOLD}PyGPT {v}{Color.ENDC} build {build} ({os}, {architecture})")
246
+ print(f" {Color.BOLD}PyGPT {v}{Color.ENDC} build {build} ({os_name}, {architecture})")
241
247
  print(" Author: Marcin Szczyglinski")
242
248
  print(" GitHub: https://github.com/szczyglis-dev/py-gpt")
243
249
  print(" Website: https://pygpt.net")
@@ -246,7 +252,6 @@ class Config:
246
252
  print("")
247
253
  print(f"{Color.BOLD}Initializing...{Color.ENDC}")
248
254
 
249
- # prepare and install
250
255
  self.window.core.installer.install()
251
256
 
252
257
  self.load(all)
@@ -258,12 +263,15 @@ class Config:
258
263
 
259
264
  :return: version string
260
265
  """
266
+ if hasattr(self, '_version_cache') and self._version_cache is not None:
267
+ return self._version_cache
261
268
  path = os.path.abspath(os.path.join(self.get_app_path(), '__init__.py'))
262
269
  try:
263
270
  with open(path, 'r', encoding="utf-8") as f:
264
271
  data = f.read()
265
- result = re.search(r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format("__version__"), data)
266
- return result.group(1)
272
+ result = _RE_VERSION.search(data)
273
+ self._version_cache = result.group(1)
274
+ return self._version_cache
267
275
  except Exception as e:
268
276
  if self.window is not None:
269
277
  self.window.core.debug.log(e)
@@ -276,12 +284,15 @@ class Config:
276
284
 
277
285
  :return: build string
278
286
  """
287
+ if self._build_cache is not None:
288
+ return self._build_cache
279
289
  path = os.path.abspath(os.path.join(self.get_app_path(), '__init__.py'))
280
290
  try:
281
291
  with open(path, 'r', encoding="utf-8") as f:
282
292
  data = f.read()
283
- result = re.search(r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format("__build__"), data)
284
- return result.group(1)
293
+ result = _RE_BUILD.search(data)
294
+ self._build_cache = result.group(1)
295
+ return self._build_cache
285
296
  except Exception as e:
286
297
  if self.window is not None:
287
298
  self.window.core.debug.log(e)
@@ -312,9 +323,7 @@ class Config:
312
323
  :param default: default value
313
324
  :return: value
314
325
  """
315
- if key in self.data:
316
- return self.data[key]
317
- return default
326
+ return self.data.get(key, default)
318
327
 
319
328
  def get_session(self, key: str, default: any = None) -> any:
320
329
  """
@@ -324,9 +333,7 @@ class Config:
324
333
  :param default: default value
325
334
  :return: value
326
335
  """
327
- if key in self.data_session:
328
- return self.data_session[key]
329
- return default
336
+ return self.data_session.get(key, default)
330
337
 
331
338
  def get_lang(self) -> str:
332
339
  """
@@ -334,7 +341,7 @@ class Config:
334
341
 
335
342
  :return: language code
336
343
  """
337
- test_lang = os.environ.get('TEST_LANGUAGE') # if pytest
344
+ test_lang = os.environ.get('TEST_LANGUAGE')
338
345
  if test_lang:
339
346
  return test_lang
340
347
  return self.get('lang', 'en')
@@ -364,12 +371,7 @@ class Config:
364
371
  :param key: key
365
372
  :return: True if exists
366
373
  """
367
- if self.data is None:
368
- return False
369
-
370
- if key in self.data:
371
- return True
372
- return False
374
+ return key in self.data
373
375
 
374
376
  def has_session(self, key: str) -> bool:
375
377
  """
@@ -378,9 +380,7 @@ class Config:
378
380
  :param key: key
379
381
  :return: True if exists
380
382
  """
381
- if key in self.data_session:
382
- return True
383
- return False
383
+ return key in self.data_session
384
384
 
385
385
  def all(self) -> dict:
386
386
  """
@@ -404,32 +404,21 @@ class Config:
404
404
 
405
405
  :return: list with available languages (user + app)
406
406
  """
407
- langs = []
408
- path = os.path.join(self.get_app_path(), 'data', 'locale')
409
- if os.path.exists(path):
410
- for file in os.listdir(path):
407
+ langs_set = set()
408
+ path_app = os.path.join(self.get_app_path(), 'data', 'locale')
409
+ if os.path.exists(path_app):
410
+ for file in os.listdir(path_app):
411
411
  if file.startswith('locale.') and file.endswith(".ini"):
412
- lang_id = file.replace('locale.', '').replace('.ini', '')
413
- if lang_id not in langs:
414
- langs.append(lang_id)
415
-
416
- path = os.path.join(self.get_user_path(), 'locale')
417
- if os.path.exists(path):
418
- for file in os.listdir(path):
412
+ langs_set.add(file.replace('locale.', '').replace('.ini', ''))
413
+ path_user = os.path.join(self.get_user_path(), 'locale')
414
+ if os.path.exists(path_user):
415
+ for file in os.listdir(path_user):
419
416
  if file.startswith('locale.') and file.endswith(".ini"):
420
- lang_id = file.replace('locale.', '').replace('.ini', '')
421
- if lang_id not in langs:
422
- langs.append(lang_id)
423
-
424
- # sort by name
425
- langs.sort()
426
-
427
- # make English first
417
+ langs_set.add(file.replace('locale.', '').replace('.ini', ''))
418
+ langs = sorted(langs_set)
428
419
  if 'en' in langs:
429
420
  langs.remove('en')
430
421
  langs.insert(0, 'en')
431
-
432
- # make Polish second
433
422
  if 'pl' in langs:
434
423
  langs.remove('pl')
435
424
  langs.insert(1, 'pl')
@@ -469,7 +458,7 @@ class Config:
469
458
  """
470
459
  self.data = self.provider.load(all)
471
460
  if self.data is not None:
472
- self.data = dict(sorted(self.data.items(), key=lambda item: item[0])) # sort by key
461
+ self.data = dict(sorted(self.data.items(), key=itemgetter(0)))
473
462
 
474
463
  def load_base_config(self):
475
464
  """
@@ -477,7 +466,7 @@ class Config:
477
466
  """
478
467
  self.data_base = self.provider.load_base()
479
468
  if self.data_base is not None:
480
- self.data_base = dict(sorted(self.data_base.items(), key=lambda item: item[0])) # sort by key
469
+ self.data_base = dict(sorted(self.data_base.items(), key=itemgetter(0)))
481
470
  self.initialized_base = True
482
471
 
483
472
  def from_base_config(self):
@@ -543,11 +532,12 @@ class Config:
543
532
  if "app.env" not in self.data or not isinstance(self.data["app.env"], list):
544
533
  return
545
534
  list_loaded = []
535
+ conf = self.all()
546
536
  for item in self.data["app.env"]:
547
537
  if item['name'] is None or item['name'] == "":
548
538
  continue
549
539
  try:
550
- value = str(item['value'].format(**self.all()))
540
+ value = str(item['value'].format(**conf))
551
541
  os.environ[item['name']] = value
552
542
  list_loaded.append(item['name'])
553
543
  except Exception as e:
@@ -561,4 +551,4 @@ class Config:
561
551
 
562
552
  :param filename: filename
563
553
  """
564
- self.provider.save(self.data, filename)
554
+ self.provider.save(self.data, filename)
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 00:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any
@@ -37,68 +37,71 @@ class Chat:
37
37
  :param window: Window instance
38
38
  """
39
39
  self.window = window
40
- self.attachment = Attachment(window)
41
- self.audio = Audio(window)
42
- self.command = Command(window)
43
- self.common = Common(window)
44
- self.files = Files(window)
45
- self.image = Image(window)
46
- self.input = Input(window)
47
- self.output = Output(window)
48
- self.render = Render(window)
49
- self.response = Response(window)
50
- self.stream = Stream(window)
51
- self.text = Text(window)
52
- self.vision = Vision(window)
53
-
54
- def init(self):
40
+ w = window
41
+ self.attachment = Attachment(w)
42
+ self.audio = Audio(w)
43
+ self.command = Command(w)
44
+ self.common = Common(w)
45
+ self.files = Files(w)
46
+ self.image = Image(w)
47
+ self.input = Input(w)
48
+ self.output = Output(w)
49
+ self.render = Render(w)
50
+ self.response = Response(w)
51
+ self.stream = Stream(w)
52
+ self.text = Text(w)
53
+ self.vision = Vision(w)
54
+
55
+ def init(self) -> None:
55
56
  """Init"""
56
57
  self.render.setup() # setup render engine
57
58
 
58
- def setup(self):
59
+ def setup(self) -> None:
59
60
  """Setup"""
60
61
  self.common.setup()
61
62
  self.attachment.setup()
62
63
 
63
- def reload(self):
64
+ def reload(self) -> None:
64
65
  """Reload"""
65
66
  self.common.setup()
66
67
  self.render.reload()
67
68
  self.attachment.reload()
68
69
 
69
- def handle_error(self, err: Any):
70
+ def handle_error(self, err: Any) -> None:
70
71
  """
71
72
  Handle error
72
73
 
73
74
  :param err: Exception
74
75
  """
75
- self.window.core.debug.log(err)
76
- self.window.ui.dialogs.alert(str(err))
77
- self.window.update_status(str(err))
78
- self.window.controller.chat.common.unlock_input() # always unlock input on error
79
- self.window.stateChanged.emit(self.window.STATE_ERROR)
80
- self.window.dispatch(AppEvent(AppEvent.INPUT_ERROR)) # app event
81
-
82
- # stop agent on error
83
- if self.window.controller.agent.legacy.enabled():
84
- self.window.controller.agent.legacy.on_stop()
85
-
86
- def log_ctx(self, ctx: CtxItem, mode: str):
76
+ w = self.window
77
+ err_str = str(err)
78
+ w.core.debug.log(err)
79
+ w.ui.dialogs.alert(err_str)
80
+ w.update_status(err_str)
81
+ self.common.unlock_input() # always unlock input on error
82
+ w.stateChanged.emit(w.STATE_ERROR)
83
+ w.dispatch(AppEvent(AppEvent.INPUT_ERROR)) # app event
84
+
85
+ if w.controller.agent.legacy.enabled():
86
+ w.controller.agent.legacy.on_stop()
87
+
88
+ def log_ctx(self, ctx: CtxItem, mode: str) -> None:
87
89
  """
88
90
  Log context item
89
91
 
90
92
  :param ctx: CtxItem
91
93
  :param mode: mode (input/output)
92
94
  """
95
+ upper_mode = mode.upper()
93
96
  if self.window.core.config.get("log.ctx"):
94
- self.log("[ctx] {}: {}".format(mode.upper(), ctx.dump())) # log
97
+ self.log(f"[ctx] {upper_mode}: {ctx.dump()}") # log
95
98
  else:
96
- self.log("[ctx] {}.".format(mode.upper()))
99
+ self.log(f"[ctx] {upper_mode}.")
97
100
 
98
- def log(self, data: Any):
101
+ def log(self, data: Any) -> None:
99
102
  """
100
103
  Log data to debug
101
104
 
102
105
  :param data: Data to log
103
106
  """
104
- self.window.core.debug.info("[chat] " + str(data))
107
+ self.window.core.debug.info(f"[chat] {data}")