pygpt-net 2.6.0.post2__py3-none-any.whl → 2.6.1__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 (61) hide show
  1. pygpt_net/CHANGELOG.txt +4 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +13 -9
  4. pygpt_net/controller/chat/response.py +5 -1
  5. pygpt_net/controller/model/editor.py +45 -4
  6. pygpt_net/controller/presets/editor.py +71 -16
  7. pygpt_net/controller/presets/presets.py +4 -3
  8. pygpt_net/core/agents/provider.py +2 -1
  9. pygpt_net/core/agents/runner.py +114 -8
  10. pygpt_net/core/agents/runners/helpers.py +3 -2
  11. pygpt_net/core/agents/runners/llama_workflow.py +176 -22
  12. pygpt_net/core/agents/runners/loop.py +22 -13
  13. pygpt_net/core/experts/experts.py +17 -23
  14. pygpt_net/core/idx/chat.py +24 -34
  15. pygpt_net/core/idx/response.py +5 -2
  16. pygpt_net/core/locale/locale.py +73 -45
  17. pygpt_net/core/render/web/body.py +152 -207
  18. pygpt_net/core/render/web/renderer.py +4 -2
  19. pygpt_net/data/config/config.json +2 -2
  20. pygpt_net/data/config/models.json +2 -2
  21. pygpt_net/data/locale/locale.de.ini +10 -8
  22. pygpt_net/data/locale/locale.en.ini +10 -8
  23. pygpt_net/data/locale/locale.es.ini +10 -8
  24. pygpt_net/data/locale/locale.fr.ini +10 -8
  25. pygpt_net/data/locale/locale.it.ini +10 -8
  26. pygpt_net/data/locale/locale.pl.ini +10 -8
  27. pygpt_net/data/locale/locale.uk.ini +10 -8
  28. pygpt_net/data/locale/locale.zh.ini +10 -8
  29. pygpt_net/item/ctx.py +2 -1
  30. pygpt_net/plugin/cmd_files/worker.py +19 -16
  31. pygpt_net/provider/agents/base.py +4 -1
  32. pygpt_net/provider/agents/llama_index/codeact_workflow.py +95 -0
  33. pygpt_net/provider/agents/llama_index/legacy/__init__.py +0 -0
  34. pygpt_net/provider/agents/llama_index/{openai.py → legacy/openai.py} +2 -2
  35. pygpt_net/provider/agents/llama_index/{openai_assistant.py → legacy/openai_assistant.py} +2 -2
  36. pygpt_net/provider/agents/llama_index/{planner.py → legacy/planner.py} +3 -3
  37. pygpt_net/provider/agents/llama_index/{react.py → legacy/react.py} +3 -3
  38. pygpt_net/provider/agents/llama_index/openai_workflow.py +52 -0
  39. pygpt_net/provider/agents/llama_index/planner_workflow.py +115 -0
  40. pygpt_net/provider/agents/llama_index/react_workflow.py +6 -4
  41. pygpt_net/provider/agents/llama_index/workflow/__init__.py +0 -0
  42. pygpt_net/provider/agents/llama_index/{codeact_agent_custom.py → workflow/codeact.py} +124 -8
  43. pygpt_net/provider/agents/llama_index/workflow/events.py +24 -0
  44. pygpt_net/provider/agents/llama_index/workflow/openai.py +634 -0
  45. pygpt_net/provider/agents/llama_index/workflow/planner.py +601 -0
  46. pygpt_net/provider/agents/openai/agent.py +1 -0
  47. pygpt_net/provider/agents/openai/agent_b2b.py +2 -0
  48. pygpt_net/provider/agents/openai/agent_planner.py +1 -0
  49. pygpt_net/provider/agents/openai/agent_with_experts.py +1 -0
  50. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +1 -0
  51. pygpt_net/provider/agents/openai/agent_with_feedback.py +1 -0
  52. pygpt_net/provider/agents/openai/evolve.py +1 -0
  53. pygpt_net/provider/core/preset/patch.py +11 -17
  54. pygpt_net/ui/widget/lists/experts.py +3 -2
  55. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.1.dist-info}/METADATA +12 -4
  56. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.1.dist-info}/RECORD +59 -53
  57. pygpt_net/data/config/presets/agent_react_workflow.json +0 -34
  58. pygpt_net/provider/agents/llama_index/code_act.py +0 -58
  59. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.1.dist-info}/LICENSE +0 -0
  60. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.1.dist-info}/WHEEL +0 -0
  61. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.1.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,7 @@
1
+ 2.6.1 (2025-08-14)
2
+
3
+ - LlamaIndex Agents refactored to Workflows.
4
+
1
5
  2.6.0 (2025-08-13)
2
6
 
3
7
  - Added split responses to the OpenAI Agents in non-streaming mode.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
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.08.13 00:00:00 #
9
+ # Updated Date: 2025.08.14 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __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.0"
17
- __build__ = "2025-08-13"
16
+ __version__ = "2.6.1"
17
+ __build__ = "2025-08-14"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.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.08.09 01:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -72,12 +72,14 @@ from pygpt_net.plugin.agent import Plugin as AgentPlugin
72
72
  from pygpt_net.plugin.mailer import Plugin as MailerPlugin
73
73
 
74
74
  # agents (Llama-index)
75
- from pygpt_net.provider.agents.llama_index.openai import OpenAIAgent
76
- from pygpt_net.provider.agents.llama_index.openai_assistant import OpenAIAssistantAgent
77
- from pygpt_net.provider.agents.llama_index.planner import PlannerAgent
78
- from pygpt_net.provider.agents.llama_index.react import ReactAgent
75
+ # from pygpt_net.provider.agents.llama_index.legacy.openai import OpenAIAgent
76
+ from pygpt_net.provider.agents.llama_index.legacy.openai_assistant import OpenAIAssistantAgent
77
+ # from pygpt_net.provider.agents.llama_index.legacy.planner import PlannerAgent
78
+ from pygpt_net.provider.agents.llama_index.planner_workflow import PlannerAgent as PlannerWorkflowAgent
79
+ from pygpt_net.provider.agents.llama_index.openai_workflow import OpenAIAgent as OpenAIWorkflowAgent
80
+ # from pygpt_net.provider.agents.llama_index.legacy.react import ReactAgent
79
81
  from pygpt_net.provider.agents.llama_index.react_workflow import ReactWorkflowAgent
80
- from pygpt_net.provider.agents.llama_index.code_act import CodeActAgent
82
+ from pygpt_net.provider.agents.llama_index.codeact_workflow import CodeActAgent
81
83
  from pygpt_net.provider.agents.openai.agent import Agent as OpenAIAgentsBase
82
84
  from pygpt_net.provider.agents.openai.agent_with_experts import Agent as OpenAIAgentsExperts
83
85
  from pygpt_net.provider.agents.openai.agent_with_experts_feedback import Agent as OpenAIAgentsExpertsFeedback
@@ -420,10 +422,12 @@ def run(**kwargs):
420
422
  launcher.add_vector_store(store)
421
423
 
422
424
  # register base agents
423
- launcher.add_agent(OpenAIAgent()) # llama-index
425
+ # launcher.add_agent(OpenAIAgent()) # llama-index
426
+ launcher.add_agent(OpenAIWorkflowAgent()) # llama-index
424
427
  launcher.add_agent(OpenAIAssistantAgent()) # llama-index
425
- launcher.add_agent(PlannerAgent()) # llama-index
426
- launcher.add_agent(ReactAgent()) # llama-index
428
+ # launcher.add_agent(PlannerAgent()) # llama-index
429
+ launcher.add_agent(PlannerWorkflowAgent()) # llama-index
430
+ # launcher.add_agent(ReactAgent()) # llama-index
427
431
  launcher.add_agent(ReactWorkflowAgent()) # llama-index
428
432
  launcher.add_agent(CodeActAgent()) # llama-index
429
433
  launcher.add_agent(OpenAIAgentsBase()) # openai-agents
@@ -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.08.11 19:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, Any
@@ -164,6 +164,7 @@ class Response:
164
164
  :param context: BridgeContext
165
165
  :param extra: Extra data
166
166
  """
167
+ global_mode = self.window.core.config.get('mode', MODE_AGENT_LLAMA)
167
168
  ctx = context.ctx
168
169
  if self.window.controller.kernel.stopped():
169
170
  output = ctx.output
@@ -266,6 +267,9 @@ class Response:
266
267
  self.window.dispatch(event)
267
268
 
268
269
  # if continue reasoning
270
+ if global_mode not in [MODE_AGENT_LLAMA, MODE_AGENT_OPENAI]:
271
+ return # no agent mode, nothing to do
272
+
269
273
  if ctx.extra is None or (type(ctx.extra) == dict and "agent_finish" not in ctx.extra):
270
274
  self.window.update_status(trans("status.agent.reasoning"))
271
275
  self.window.controller.chat.common.lock_input() # lock input, re-enable stop button
@@ -6,10 +6,11 @@
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.19 17:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
13
+ import json
13
14
  from typing import Optional, Any
14
15
 
15
16
  from pygpt_net.core.events import Event
@@ -107,7 +108,14 @@ class Editor:
107
108
  "description": "model.llama_index.env.desc",
108
109
  "advanced": True,
109
110
  },
111
+ "extra_json": {
112
+ "type": "textarea",
113
+ "label": "model.extra",
114
+ "description": "model.extra.desc",
115
+ "advanced": True,
116
+ },
110
117
  }
118
+ self.custom_fields = ["extra_json"]
111
119
 
112
120
  def get_options(self):
113
121
  """
@@ -220,8 +228,12 @@ class Editor:
220
228
  model = self.window.core.models.items[self.current]
221
229
  data_dict = model.to_dict()
222
230
  for key in options:
223
- value = data_dict[key]
224
- options[key]["value"] = value
231
+ if key in data_dict:
232
+ value = data_dict[key]
233
+ options[key]["value"] = value
234
+
235
+ # custom fields
236
+ options["extra_json"]["value"] = json.dumps(model.extra, indent=4) if model.extra else ""
225
237
 
226
238
  if self.current is not None and self.current in self.window.core.models.items:
227
239
  self.set_tab_by_id(self.current)
@@ -229,15 +241,24 @@ class Editor:
229
241
  # load and apply options to config dialog
230
242
  self.window.controller.config.load_options("model", options)
231
243
 
232
- def save(self, persist: bool = True):
244
+ def save(
245
+ self,
246
+ persist: bool = True,
247
+ force: bool = False
248
+ ):
233
249
  """
234
250
  Save models editor
235
251
 
236
252
  :param persist: persist to file and close dialog
253
+ :param force: force save without validation
237
254
  """
238
255
  options = copy.deepcopy(self.get_options()) # copy options
239
256
  data_dict = {}
257
+
258
+ # base fields
240
259
  for key in options:
260
+ if key in self.custom_fields:
261
+ continue
241
262
  value = self.window.controller.config.get_value(
242
263
  parent_id="model",
243
264
  key=key,
@@ -245,6 +266,26 @@ class Editor:
245
266
  )
246
267
  data_dict[key] = value
247
268
 
269
+ # custom fields
270
+ if "extra_json" in options:
271
+ extra_json = self.window.controller.config.get_value(
272
+ parent_id="model",
273
+ key="extra_json",
274
+ option=options["extra_json"],
275
+ )
276
+ try:
277
+ if extra_json:
278
+ decoded = json.loads(extra_json)
279
+ data_dict["extra"] = decoded
280
+ else:
281
+ data_dict["extra"] = {}
282
+ except json.JSONDecodeError as error:
283
+ self.window.ui.dialogs.alert(
284
+ "JSON decoding error in 'extra' field. Please check the syntax:\n\n{}".format(error)
285
+ )
286
+ if not force:
287
+ return # if JSON is invalid, do not save
288
+
248
289
  # update current model
249
290
  if self.current in self.window.core.models.items:
250
291
  self.window.core.models.items[self.current].from_dict(data_dict)
@@ -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.08.09 19:00:00 #
9
+ # Updated Date: 2025.08.14 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -172,12 +172,22 @@ class Editor:
172
172
  "label": "preset.agent_provider",
173
173
  "description": "preset.agent_provider.desc",
174
174
  "use": "agent_provider_llama",
175
+ "extra": {
176
+ "urls": {
177
+ "Help": "https://pygpt.readthedocs.io/en/latest/modes.html#agent-llamaindex"
178
+ },
179
+ }
175
180
  },
176
181
  "agent_provider_openai": {
177
182
  "type": "combo",
178
183
  "label": "preset.agent_provider",
179
184
  "description": "preset.agent_provider.desc",
180
185
  "use": "agent_provider_openai",
186
+ "extra": {
187
+ "urls": {
188
+ "Help": "https://pygpt.readthedocs.io/en/latest/modes.html#agent-openai"
189
+ },
190
+ }
181
191
  },
182
192
  "assistant_id": {
183
193
  "type": "text",
@@ -255,6 +265,7 @@ class Editor:
255
265
 
256
266
  # add hooks for config update in real-time
257
267
  self.window.ui.add_hook("update.preset.prompt", self.hook_update)
268
+ self.window.ui.add_hook("update.preset.agent_provider", self.hook_update)
258
269
  self.window.ui.add_hook("update.preset.agent_provider_openai", self.hook_update)
259
270
 
260
271
  # register functions dictionary
@@ -275,7 +286,7 @@ class Editor:
275
286
  if not self.tab_options_idx:
276
287
  return
277
288
  mode = self.window.core.config.get('mode')
278
- if mode != MODE_AGENT_OPENAI:
289
+ if mode not in [MODE_AGENT_OPENAI, MODE_AGENT_LLAMA]:
279
290
  # show base prompt
280
291
  self.window.ui.tabs['preset.editor.extra'].setTabVisible(0, True)
281
292
  # hide all tabs
@@ -303,12 +314,21 @@ class Editor:
303
314
  # show base prompt
304
315
  self.window.ui.tabs['preset.editor.extra'].setTabVisible(0, True)
305
316
  return
317
+
306
318
  mode = self.window.core.config.get('mode')
307
- if mode == MODE_AGENT_OPENAI:
319
+ key_agent = ""
320
+
321
+ if mode in [MODE_AGENT_OPENAI, MODE_AGENT_LLAMA]:
322
+ # get current provider
323
+ if mode == MODE_AGENT_LLAMA:
324
+ key_agent = "agent_provider"
325
+ elif mode == MODE_AGENT_OPENAI:
326
+ key_agent = "agent_provider_openai"
327
+
308
328
  current_provider = self.window.controller.config.get_value(
309
329
  parent_id=self.id,
310
- key="agent_provider_openai",
311
- option=self.options["agent_provider_openai"],
330
+ key=key_agent,
331
+ option=self.options[key_agent],
312
332
  )
313
333
  if current_provider is None or current_provider == "":
314
334
  # show base prompt
@@ -344,22 +364,38 @@ class Editor:
344
364
 
345
365
  :param preset: preset item
346
366
  """
347
- if preset.agent_provider_openai is None or preset.agent_provider_openai == "":
367
+ mode = self.window.core.config.get('mode')
368
+ id = None
369
+ if mode == MODE_AGENT_OPENAI:
370
+ if preset.agent_provider_openai is None or preset.agent_provider_openai == "":
371
+ return
372
+ id = preset.agent_provider_openai
373
+ elif mode == MODE_AGENT_LLAMA:
374
+ if preset.agent_provider is None or preset.agent_provider == "":
375
+ return
376
+ id = preset.agent_provider
377
+ else:
348
378
  return
349
379
 
350
380
  # update options in UI
351
- id = preset.agent_provider_openai
352
381
  agent = self.window.core.agents.provider.get(id)
353
382
  if not agent:
354
383
  return
355
384
  if not preset.extra or id not in preset.extra:
356
385
  return
386
+
357
387
  data_dict = preset.extra[id]
358
388
  option_tabs = agent.get_options()
359
389
  for option_tab_id in data_dict:
360
- option_key = "agent." + preset.agent_provider_openai + "." + option_tab_id
390
+ parent_key = ""
391
+ if mode == MODE_AGENT_OPENAI:
392
+ parent_key = preset.agent_provider_openai
393
+ elif mode == MODE_AGENT_LLAMA:
394
+ parent_key = preset.agent_provider
395
+ option_key = "agent." + parent_key + "." + option_tab_id
361
396
  if option_key not in self.window.ui.config:
362
397
  continue
398
+
363
399
  extra_options = option_tabs.get(option_tab_id, {}).get('options', {})
364
400
  for key in extra_options:
365
401
  value = data_dict[option_tab_id].get(key, None)
@@ -390,7 +426,7 @@ class Editor:
390
426
  if not self.tab_options_idx:
391
427
  return
392
428
  mode = self.window.core.config.get('mode')
393
- if mode != MODE_AGENT_OPENAI:
429
+ if mode not in [MODE_AGENT_OPENAI, MODE_AGENT_LLAMA]:
394
430
  return
395
431
 
396
432
  # load defaults for all tabs
@@ -427,13 +463,18 @@ class Editor:
427
463
  return
428
464
 
429
465
  mode = self.window.core.config.get('mode')
430
- if mode != MODE_AGENT_OPENAI:
466
+ if mode not in [MODE_AGENT_OPENAI, MODE_AGENT_LLAMA]:
431
467
  return
432
468
 
433
469
  preset = self.window.core.presets.get_by_uuid(self.current)
434
470
  if not preset:
435
471
  return
436
- current_provider_id = preset.agent_provider_openai if preset else None
472
+
473
+ current_provider_id = None
474
+ if mode == MODE_AGENT_OPENAI:
475
+ current_provider_id = preset.agent_provider_openai if preset else None
476
+ elif mode == MODE_AGENT_LLAMA:
477
+ current_provider_id = preset.agent_provider if preset else None
437
478
 
438
479
  # load defaults for all tabs
439
480
  for id in self.tab_options_idx:
@@ -475,10 +516,18 @@ class Editor:
475
516
  :param id: preset id
476
517
  :param preset: preset item
477
518
  """
519
+ mode = self.window.core.config.get('mode')
478
520
  exclude_ids = [
479
521
  "__prompt__",
480
522
  ]
481
- id = preset.agent_provider_openai
523
+ id = None
524
+ if mode == MODE_AGENT_OPENAI:
525
+ id = preset.agent_provider_openai
526
+ elif mode == MODE_AGENT_LLAMA:
527
+ id = preset.agent_provider
528
+ else:
529
+ return
530
+
482
531
  options = {}
483
532
  agent = self.window.core.agents.provider.get(id)
484
533
  if not agent:
@@ -568,14 +617,20 @@ class Editor:
568
617
  :return: None
569
618
  """
570
619
  mode = self.window.core.config.get('mode')
571
- if mode != MODE_AGENT_OPENAI:
620
+ if mode not in [MODE_AGENT_OPENAI, MODE_AGENT_LLAMA]:
572
621
  return
573
622
 
623
+ parent_key = ""
624
+ if mode == MODE_AGENT_OPENAI:
625
+ parent_key = "agent_provider_openai"
626
+ elif mode == MODE_AGENT_LLAMA:
627
+ parent_key = "agent_provider"
628
+
574
629
  # get current provider
575
630
  current_provider = self.window.controller.config.get_value(
576
631
  parent_id=self.id,
577
- key="agent_provider_openai",
578
- option=self.options["agent_provider_openai"],
632
+ key=parent_key,
633
+ option=self.options[parent_key],
579
634
  )
580
635
  if current_provider is None or current_provider == "":
581
636
  return
@@ -624,7 +679,7 @@ class Editor:
624
679
  self.window.controller.presets.from_global() # update current preset
625
680
 
626
681
  # show/hide extra options
627
- elif key == "agent_provider_openai":
682
+ elif key in ["agent_provider_openai", "agent_provider"]:
628
683
  self.toggle_extra_options_by_provider()
629
684
  self.append_default_prompt()
630
685
  self.load_extra_defaults_current()
@@ -6,12 +6,13 @@
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.08.01 03:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
13
13
  from typing import Optional, List, Dict
14
14
 
15
+ from PySide6.QtCore import QTimer
15
16
  from PySide6.QtGui import QTextCursor
16
17
  from PySide6.QtWidgets import QTextEdit
17
18
 
@@ -529,7 +530,7 @@ class Presets:
529
530
  if preset_id is not None and preset_id != "":
530
531
  if preset_id in self.window.core.presets.items:
531
532
  self.window.core.presets.enable(preset_id)
532
- self.refresh()
533
+ QTimer.singleShot(100, self.refresh) # delay refresh
533
534
 
534
535
  def disable(self, idx: Optional[int] = None):
535
536
  """
@@ -543,7 +544,7 @@ class Presets:
543
544
  if preset_id is not None and preset_id != "":
544
545
  if preset_id in self.window.core.presets.items:
545
546
  self.window.core.presets.disable(preset_id)
546
- self.refresh()
547
+ QTimer.singleShot(100, self.refresh) # delay refresh
547
548
 
548
549
  def clear(self, force: bool = False):
549
550
  """
@@ -48,7 +48,8 @@ class Provider:
48
48
  :param id: agent id
49
49
  :return: agent provider
50
50
  """
51
- return self.agents[id]
51
+ if id in self.agents:
52
+ return self.agents[id]
52
53
 
53
54
  def all(self) -> Dict[str, BaseAgent]:
54
55
  """
@@ -6,11 +6,11 @@
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.08.03 14:00:00 #
9
+ # Updated Date: 2025.08.14 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import asyncio
13
- from typing import Optional, Dict, Any
13
+ from typing import Optional, Dict, Any, Union
14
14
 
15
15
  from llama_index.core.base.llms.types import ChatMessage, MessageRole
16
16
 
@@ -31,6 +31,8 @@ from .runners.llama_workflow import LlamaWorkflow
31
31
  from .runners.openai_workflow import OpenAIWorkflow
32
32
  from .runners.helpers import Helpers
33
33
  from .runners.loop import Loop
34
+ from ...item.ctx import CtxItem
35
+
34
36
 
35
37
  class Runner:
36
38
  def __init__(self, window=None):
@@ -50,6 +52,9 @@ class Runner:
50
52
  self.llama_steps = LlamaSteps(window)
51
53
  self.llama_workflow = LlamaWorkflow(window)
52
54
  self.openai_workflow = OpenAIWorkflow(window)
55
+ self.APPEND_SYSTEM_PROMPT_TO_MSG = [
56
+ "react", # llama-index
57
+ ]
53
58
 
54
59
  def call(
55
60
  self,
@@ -107,12 +112,7 @@ class Runner:
107
112
  tools = []
108
113
 
109
114
  # append system prompt
110
- if agent_id not in [
111
- "openai_agent_base", # openai-agents
112
- "openai_agent_experts", # openai-agents
113
- "openai_assistant", # llama-index
114
- "code_act", # llama-index
115
- ]:
115
+ if agent_id in self.APPEND_SYSTEM_PROMPT_TO_MSG:
116
116
  if system_prompt:
117
117
  msg = ChatMessage(
118
118
  role=MessageRole.SYSTEM,
@@ -178,6 +178,112 @@ class Runner:
178
178
  self.last_error = e
179
179
  return False
180
180
 
181
+ def call_once(
182
+ self,
183
+ context: BridgeContext,
184
+ extra: Dict[str, Any],
185
+ signals: BridgeSignals
186
+ ) -> Union[CtxItem, bool, None]:
187
+ """
188
+ Call an agent once (quick call to the agent)
189
+
190
+ :param context: BridgeContext
191
+ :param extra: extra data
192
+ :param signals: BridgeSignals
193
+ :return: CtxItem if success, True if stopped, None on error
194
+ """
195
+ if self.window.controller.kernel.stopped():
196
+ return True # abort if stopped
197
+
198
+ agent_id = extra.get("agent_provider", "openai")
199
+ verbose = self.window.core.config.get("agent.llama.verbose", False)
200
+
201
+ try:
202
+ # prepare input ctx
203
+ ctx = context.ctx
204
+ ctx.extra["agent_input"] = True # mark as user input
205
+ ctx.extra["agent_output"] = True # mark as user input
206
+ ctx.agent_call = True # disables reply from plugin commands
207
+ prompt = context.prompt
208
+
209
+ # prepare agent
210
+ model = context.model
211
+ vector_store_idx = extra.get("agent_idx", None)
212
+ system_prompt = context.system_prompt
213
+ max_steps = self.window.core.config.get("agent.llama.steps", 10)
214
+ is_cmd = self.window.core.command.is_cmd(inline=False)
215
+ llm = self.window.core.idx.llm.get(model, stream=False)
216
+ workdir = self.window.core.config.get_workdir_prefix()
217
+
218
+ # tools
219
+ self.window.core.agents.tools.context = context
220
+ self.window.core.agents.tools.agent_idx = vector_store_idx
221
+
222
+ if "agent_tools" in extra:
223
+ tools = extra["agent_tools"] # use tools from extra if provided
224
+ else:
225
+ tools = self.window.core.agents.tools.prepare(context, extra, force=True)
226
+
227
+ if "agent_history" in extra:
228
+ history = extra["agent_history"]
229
+ else:
230
+ history = self.window.core.agents.memory.prepare(context)
231
+
232
+ # disable tools if cmd is not enabled
233
+ if not is_cmd:
234
+ tools = []
235
+
236
+ # append system prompt
237
+ if agent_id in self.APPEND_SYSTEM_PROMPT_TO_MSG:
238
+ if system_prompt:
239
+ msg = ChatMessage(
240
+ role=MessageRole.SYSTEM,
241
+ content=system_prompt,
242
+ )
243
+ history.insert(0, msg)
244
+
245
+ agent_kwargs = {
246
+ "context": context,
247
+ "tools": tools,
248
+ "llm": llm,
249
+ "model": model,
250
+ "chat_history": history,
251
+ "max_iterations": max_steps,
252
+ "verbose": verbose,
253
+ "system_prompt": system_prompt,
254
+ "are_commands": is_cmd,
255
+ "workdir": workdir,
256
+ "preset": context.preset if context else None,
257
+ }
258
+
259
+ if self.window.core.agents.provider.has(agent_id):
260
+ provider = self.window.core.agents.provider.get(agent_id)
261
+ agent = provider.get_agent(self.window, agent_kwargs)
262
+ if verbose:
263
+ print("Using Agent: " + str(agent_id) + ", model: " + str(model.id))
264
+ else:
265
+ raise Exception("Agent not found: " + str(agent_id))
266
+
267
+ # run agent and return result
268
+ mode = provider.get_mode()
269
+ kwargs = {
270
+ "agent": agent,
271
+ "ctx": ctx,
272
+ "prompt": prompt,
273
+ "signals": signals,
274
+ "verbose": verbose,
275
+ "history": history,
276
+ "llm": llm,
277
+ }
278
+ # TODO: add support for other modes
279
+ if mode == AGENT_MODE_WORKFLOW:
280
+ return asyncio.run(self.llama_workflow.run_once(**kwargs))
281
+
282
+ except Exception as e:
283
+ self.window.core.debug.error(e)
284
+ self.last_error = e
285
+ return None
286
+
181
287
  def get_error(self) -> Optional[Exception]:
182
288
  """
183
289
  Get last error
@@ -6,12 +6,12 @@
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.08.06 01:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
13
13
  import re
14
- import weakref
14
+ import time
15
15
  from typing import Optional, Tuple
16
16
 
17
17
  from pygpt_net.core.bridge.context import BridgeContext
@@ -82,6 +82,7 @@ class Helpers:
82
82
  # ctx.attachments = from_ctx.attachments # copy from parent if appended from plugins
83
83
  # ctx.files = from_ctx.files # copy from parent if appended from plugins
84
84
  ctx.extra = from_ctx.extra.copy() # copy extra data
85
+ ctx.output_timestamp = int(time.time()) # set output timestamp
85
86
  return ctx
86
87
 
87
88
  def send_stream(