pygpt-net 2.6.61__py3-none-any.whl → 2.6.62__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 (58) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/controller/chat/response.py +8 -2
  4. pygpt_net/controller/settings/profile.py +16 -4
  5. pygpt_net/controller/settings/workdir.py +30 -5
  6. pygpt_net/controller/theme/common.py +4 -2
  7. pygpt_net/controller/theme/markdown.py +2 -2
  8. pygpt_net/controller/theme/theme.py +2 -1
  9. pygpt_net/controller/ui/ui.py +31 -3
  10. pygpt_net/core/agents/custom/llama_index/runner.py +18 -3
  11. pygpt_net/core/agents/custom/runner.py +10 -5
  12. pygpt_net/core/agents/runners/llama_workflow.py +65 -5
  13. pygpt_net/core/agents/runners/openai_workflow.py +2 -1
  14. pygpt_net/core/node_editor/types.py +13 -1
  15. pygpt_net/core/render/web/renderer.py +76 -11
  16. pygpt_net/data/config/config.json +2 -2
  17. pygpt_net/data/config/models.json +2 -2
  18. pygpt_net/data/css/style.dark.css +18 -0
  19. pygpt_net/data/css/style.light.css +20 -1
  20. pygpt_net/data/locale/locale.de.ini +2 -0
  21. pygpt_net/data/locale/locale.en.ini +2 -0
  22. pygpt_net/data/locale/locale.es.ini +2 -0
  23. pygpt_net/data/locale/locale.fr.ini +2 -0
  24. pygpt_net/data/locale/locale.it.ini +2 -0
  25. pygpt_net/data/locale/locale.pl.ini +3 -1
  26. pygpt_net/data/locale/locale.uk.ini +2 -0
  27. pygpt_net/data/locale/locale.zh.ini +2 -0
  28. pygpt_net/item/ctx.py +23 -1
  29. pygpt_net/provider/agents/llama_index/workflow/codeact.py +9 -6
  30. pygpt_net/provider/agents/llama_index/workflow/openai.py +38 -11
  31. pygpt_net/provider/agents/llama_index/workflow/planner.py +36 -16
  32. pygpt_net/provider/agents/llama_index/workflow/supervisor.py +60 -10
  33. pygpt_net/provider/agents/openai/agent.py +3 -1
  34. pygpt_net/provider/agents/openai/agent_b2b.py +13 -9
  35. pygpt_net/provider/agents/openai/agent_planner.py +6 -2
  36. pygpt_net/provider/agents/openai/agent_with_experts.py +4 -1
  37. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -2
  38. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -2
  39. pygpt_net/provider/agents/openai/evolve.py +6 -2
  40. pygpt_net/provider/agents/openai/supervisor.py +3 -1
  41. pygpt_net/provider/api/openai/agents/response.py +1 -0
  42. pygpt_net/provider/core/config/patch.py +8 -0
  43. pygpt_net/tools/agent_builder/tool.py +6 -0
  44. pygpt_net/tools/agent_builder/ui/dialogs.py +0 -41
  45. pygpt_net/ui/layout/toolbox/presets.py +14 -2
  46. pygpt_net/ui/main.py +2 -2
  47. pygpt_net/ui/widget/dialog/confirm.py +27 -3
  48. pygpt_net/ui/widget/draw/painter.py +90 -1
  49. pygpt_net/ui/widget/lists/preset.py +289 -25
  50. pygpt_net/ui/widget/node_editor/editor.py +53 -15
  51. pygpt_net/ui/widget/node_editor/node.py +82 -104
  52. pygpt_net/ui/widget/node_editor/view.py +4 -5
  53. pygpt_net/ui/widget/textarea/input.py +155 -21
  54. {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.62.dist-info}/METADATA +17 -8
  55. {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.62.dist-info}/RECORD +58 -58
  56. {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.62.dist-info}/LICENSE +0 -0
  57. {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.62.dist-info}/WHEEL +0 -0
  58. {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.62.dist-info}/entry_points.txt +0 -0
@@ -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.09.17 05:00:00 #
9
+ # Updated Date: 2025.09.26 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -16,15 +16,13 @@ import html as _html
16
16
  from dataclasses import dataclass, field
17
17
 
18
18
  from datetime import datetime
19
- from typing import Optional, List, Any, Dict, Tuple
19
+ from typing import Optional, List, Any, Tuple
20
20
  from time import monotonic
21
21
  from io import StringIO
22
22
 
23
- from PySide6.QtCore import QTimer, QUrl, QCoreApplication, QEventLoop, QEvent
24
- from PySide6.QtWebEngineCore import QWebEnginePage
23
+ from PySide6.QtCore import QTimer, QCoreApplication, QEventLoop, QEvent
25
24
 
26
25
  from pygpt_net.core.render.base import BaseRenderer
27
- from pygpt_net.core.text.utils import has_unclosed_code_tag
28
26
  from pygpt_net.item.ctx import CtxItem, CtxMeta
29
27
  from pygpt_net.ui.widget.textarea.input import ChatInput
30
28
  from pygpt_net.ui.widget.textarea.web import ChatWebOutput
@@ -155,6 +153,7 @@ class Renderer(BaseRenderer):
155
153
  app_path = self.window.core.config.get_app_path() if self.window else ""
156
154
  self._icon_expand = os.path.join(app_path, "data", "icons", "expand.svg")
157
155
  self._icon_sync = os.path.join(app_path, "data", "icons", "sync.svg")
156
+ self._agent_avatar = os.path.join(app_path, "data", "icons", "robot.svg")
158
157
  self._file_prefix = 'file:///' if self.window and self.window.core.platforms.is_windows() else 'file://'
159
158
 
160
159
  # Bridge readiness for node append/replace path
@@ -378,6 +377,15 @@ class Renderer(BaseRenderer):
378
377
  self.tool_output_end()
379
378
  self.prev_chunk_replace = False
380
379
 
380
+ # Ensure stream header identity is up-to-date (agent/preset override)
381
+ try:
382
+ header = self.get_name_header(ctx, stream=True)
383
+ if pid is not None:
384
+ self.pids[pid].header = header
385
+ self._stream_header[pid] = header or ""
386
+ except Exception:
387
+ pass
388
+
381
389
  def end(self, meta: CtxMeta, ctx: CtxItem, stream: bool = False):
382
390
  """
383
391
  Render end
@@ -1163,6 +1171,26 @@ class Renderer(BaseRenderer):
1163
1171
  meta = ctx.meta
1164
1172
  if meta is None:
1165
1173
  return ""
1174
+
1175
+ # Agent-provided display name override:
1176
+ # If ctx.get_agent_name() returns a non-empty name, force "fake personalize":
1177
+ # - use that name
1178
+ # - optionally attach default avatar when enabled via config
1179
+ # - treat as personalized header regardless of preset
1180
+ agent_name = self._get_agent_name(ctx)
1181
+ if agent_name:
1182
+ avatar_html = ""
1183
+ try:
1184
+ use_default = self.window.core.config.get("agent.avatar.default", True)
1185
+ if use_default and os.path.exists(self._agent_avatar):
1186
+ avatar_html = f"<img src=\"{self._file_prefix}{self._agent_avatar}\" class=\"avatar\"> "
1187
+ except Exception:
1188
+ pass
1189
+ if stream:
1190
+ return f"{avatar_html}{agent_name}"
1191
+ else:
1192
+ return f"<div class=\"name-header name-bot\">{avatar_html}{agent_name}</div>"
1193
+
1166
1194
  preset_id = meta.preset
1167
1195
  if preset_id is None or preset_id == "":
1168
1196
  return ""
@@ -1904,23 +1932,60 @@ class Renderer(BaseRenderer):
1904
1932
 
1905
1933
  # ------------------------- Helpers: build JSON blocks -------------------------
1906
1934
 
1935
+ def _get_agent_name(self, ctx: CtxItem) -> Optional[str]:
1936
+ """
1937
+ Resolve agent-provided name from ctx if available.
1938
+
1939
+ This is used to force "fake personalize" on the UI:
1940
+ - when present and non-empty, we use this name,
1941
+ - optionally attach default avatar when enabled via config,
1942
+ - we set personalize flag to True in node payloads.
1943
+ """
1944
+ try:
1945
+ if hasattr(ctx, "get_agent_name"):
1946
+ name = ctx.get_agent_name()
1947
+ if isinstance(name, str):
1948
+ name = name.strip()
1949
+ return name or None
1950
+ except Exception:
1951
+ pass
1952
+ return None
1953
+
1907
1954
  def _output_identity(self, ctx: CtxItem) -> Tuple[str, Optional[str], bool]:
1908
1955
  """
1909
- Resolve output identity (name, avatar file:// path) based on preset.
1956
+ Resolve output identity (name, avatar file:// path) based on preset or ctx-provided agent name.
1910
1957
 
1911
1958
  :param ctx: context item
1912
1959
  :return: (name, avatar, personalize)
1913
1960
  """
1961
+ # 1) Agent-provided name override -> force personalize, optionally default avatar
1962
+ agent_name = self._get_agent_name(ctx)
1963
+ if agent_name:
1964
+ avatar = None
1965
+ try:
1966
+ if self.window.core.config.get("agent.avatar.default", True) and os.path.exists(self._agent_avatar):
1967
+ avatar = f"{self._file_prefix}{self._agent_avatar}"
1968
+ except Exception:
1969
+ pass
1970
+ return agent_name, avatar, True
1971
+
1972
+ # 2) Fallback to preset-based personalize
1914
1973
  meta = ctx.meta
1915
1974
  if meta is None:
1916
- return self.pids[self.get_or_create_pid(meta)].name_bot if meta else "", None, False
1975
+ return "", None, False
1976
+
1977
+ pid = self.get_or_create_pid(meta)
1978
+ default_name = self.pids[pid].name_bot if pid in self.pids else ""
1979
+
1917
1980
  preset_id = meta.preset
1918
1981
  if not preset_id:
1919
- return self.pids[self.get_or_create_pid(meta)].name_bot, None, False
1982
+ return default_name, None, False
1983
+
1920
1984
  preset = self.window.core.presets.get(preset_id)
1921
1985
  if preset is None or not preset.ai_personalize:
1922
- return self.pids[self.get_or_create_pid(meta)].name_bot, None, False
1923
- name = preset.ai_name or self.pids[self.get_or_create_pid(meta)].name_bot
1986
+ return default_name, None, False
1987
+
1988
+ name = preset.ai_name or default_name
1924
1989
  avatar = None
1925
1990
  if preset.ai_avatar:
1926
1991
  presets_dir = self.window.core.config.get_user_dir("presets")
@@ -1928,7 +1993,7 @@ class Renderer(BaseRenderer):
1928
1993
  avatar_path = os.path.join(avatars_dir, preset.ai_avatar)
1929
1994
  if os.path.exists(avatar_path):
1930
1995
  avatar = f"{self._file_prefix}{avatar_path}"
1931
- return name, avatar, bool(preset.ai_personalize)
1996
+ return name, avatar, True
1932
1997
 
1933
1998
  def _build_render_block(
1934
1999
  self,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.61",
4
- "app.version": "2.6.61",
3
+ "version": "2.6.62",
4
+ "app.version": "2.6.62",
5
5
  "updated_at": "2025-09-26T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.61",
4
- "app.version": "2.6.61",
3
+ "version": "2.6.62",
4
+ "app.version": "2.6.62",
5
5
  "updated_at": "2025-09-26T00:00:00"
6
6
  },
7
7
  "items": {
@@ -144,4 +144,22 @@ QCalendarWidget QAbstractItemView::item:hover {{
144
144
  QCalendarWidget QMenu::item:selected:focus,
145
145
  QCalendarWidget QMenu::item:selected {{
146
146
  background: #3a4045;
147
+ }}
148
+
149
+ /* Node editor */
150
+ NodeEditor {{
151
+ qproperty-gridBackColor: #242629;
152
+ qproperty-gridPenColor: #3b3f46;
153
+
154
+ qproperty-nodeBackgroundColor: #2d2f34;
155
+ qproperty-nodeBorderColor: #4b4f57;
156
+ qproperty-nodeSelectionColor: #ff9900;
157
+ qproperty-nodeTitleColor: #3a3d44;
158
+
159
+ qproperty-portInputColor: #66b2ff;
160
+ qproperty-portOutputColor: #70e070;
161
+ qproperty-portConnectedColor: #ffd166;
162
+
163
+ qproperty-edgeColor: #c0c0c0;
164
+ qproperty-edgeSelectedColor: #ff8a5c;
147
165
  }}
@@ -6,6 +6,7 @@
6
6
  QWidget {{
7
7
  color: #000;
8
8
  margin: 1px;
9
+ background-color: #eeeeee;
9
10
  }}
10
11
 
11
12
  QListView,
@@ -248,7 +249,7 @@ QCalendarWidget QToolButton:hover {{
248
249
  height: 20px;
249
250
  }}
250
251
  QMenu::indicator {{
251
- border: 1px solid gray; /* tu ustawiasz border */
252
+ border: 1px solid gray;
252
253
  }}
253
254
  QCalendarWidget QAbstractItemView::item:selected:focus,
254
255
  QCalendarWidget QAbstractItemView::item:selected {{
@@ -259,4 +260,22 @@ QCalendarWidget QAbstractItemView::item:hover {{
259
260
  }}
260
261
  .file-explorer QTreeView::branch {{
261
262
  background: #fff;
263
+ }}
264
+
265
+ /* Node editor */
266
+ NodeEditor {{
267
+ qproperty-gridBackColor: #ffffff;
268
+ qproperty-gridPenColor: #eaeaea;
269
+
270
+ qproperty-nodeBackgroundColor: #2d2f34;
271
+ qproperty-nodeBorderColor: #4b4f57;
272
+ qproperty-nodeSelectionColor: #ff9900;
273
+ qproperty-nodeTitleColor: #3a3d44;
274
+
275
+ qproperty-portInputColor: #66b2ff;
276
+ qproperty-portOutputColor: #70e070;
277
+ qproperty-portConnectedColor: #ffd166;
278
+
279
+ qproperty-edgeColor: #c0c0c0;
280
+ qproperty-edgeSelectedColor: #ff8a5c;
262
281
  }}
@@ -658,6 +658,7 @@ event.control.voice_cmd.toggle = Sprachsteuerung: Umschalten
658
658
  event.control.voice_msg.start = Spracheingabe: Start
659
659
  event.control.voice_msg.stop = Spracheingabe: Stopp
660
660
  event.control.voice_msg.toggle = Spracheingabe: Umschalten
661
+ exit.msg = Gefällt dir PyGPT? Unterstütze die Entwicklung des Projekts:
661
662
  expert.wait.failed: Aufruf des Experten fehlgeschlagen
662
663
  expert.wait.status: Warten auf Experten...
663
664
  files.delete.confirm = Datei/Verzeichnis löschen?
@@ -734,6 +735,7 @@ input.search.placeholder = Suchen...
734
735
  input.send_clear = Nach dem Senden löschen
735
736
  input.stream = Streamen
736
737
  input.tab = Eingabe
738
+ input.tab.tooltip = {chars} Zeichen (~{tokens} Token)
737
739
  interpreter.all = Verlauf ausführen (alle)
738
740
  interpreter.auto_clear = Bei Senden löschen
739
741
  interpreter.btn.clear = Ausgabe löschen
@@ -663,6 +663,7 @@ event.control.voice_cmd.toggle = Voice control: Toggle
663
663
  event.control.voice_msg.start = Voice input: Start
664
664
  event.control.voice_msg.stop = Voice input: Stop
665
665
  event.control.voice_msg.toggle = Voice input: Toggle
666
+ exit.msg = Do you like PyGPT? Support the development of the project:
666
667
  expert.wait.failed: Failed calling expert
667
668
  expert.wait.status: Waiting for expert...
668
669
  files.delete.confirm = Delete file/directory?
@@ -739,6 +740,7 @@ input.search.placeholder = Search...
739
740
  input.send_clear = Clear on send
740
741
  input.stream = Stream
741
742
  input.tab = Input
743
+ input.tab.tooltip = {chars} chars (~{tokens} tokens)
742
744
  interpreter.all = Execute history (all)
743
745
  interpreter.auto_clear = Clear output on send
744
746
  interpreter.btn.clear = Clear output
@@ -659,6 +659,7 @@ event.control.voice_cmd.toggle = Control de voz: Conmutar
659
659
  event.control.voice_msg.start = Entrada de voz: Iniciar
660
660
  event.control.voice_msg.stop = Entrada de voz: Detener
661
661
  event.control.voice_msg.toggle = Entrada de voz: Conmutar
662
+ exit.msg = ¿Te gusta PyGPT? Apoya el desarrollo del proyecto:
662
663
  expert.wait.failed: Error al llamar al experto
663
664
  expert.wait.status: Esperando al experto...
664
665
  files.delete.confirm = ¿Eliminar archivo/directorio?
@@ -735,6 +736,7 @@ input.search.placeholder = Buscar...
735
736
  input.send_clear = Limpiar al enviar
736
737
  input.stream = Transmisión
737
738
  input.tab = Entrada
739
+ input.tab.tooltip = {chars} caracteres (~{tokens} fichas)
738
740
  interpreter.all = Ejecutar historial (todo)
739
741
  interpreter.auto_clear = Limpiar al enviar
740
742
  interpreter.btn.clear = Limpiar salida
@@ -658,6 +658,7 @@ event.control.voice_cmd.toggle = Commande vocale : Basculer
658
658
  event.control.voice_msg.start = Entrée vocale : Commencer
659
659
  event.control.voice_msg.stop = Entrée vocale : Arrêter
660
660
  event.control.voice_msg.toggle = Entrée vocale : Basculer
661
+ exit.msg = PyGPT vous plaît-il ? Soutenez le développement du projet :
661
662
  expert.wait.failed: Échec de l'appel à l'expert
662
663
  expert.wait.status: En attente de l'expert...
663
664
  files.delete.confirm = Supprimer le fichier/répertoire ?
@@ -734,6 +735,7 @@ input.search.placeholder = Rechercher...
734
735
  input.send_clear = Effacer après envoi
735
736
  input.stream = Flux
736
737
  input.tab = Entrée
738
+ input.tab.tooltip = {chars} caractères (~{tokens} jetons)
737
739
  interpreter.all = Exécuter l'historique (tous)
738
740
  interpreter.auto_clear = Effacer à l'envoi
739
741
  interpreter.btn.clear = Effacer la sortie
@@ -658,6 +658,7 @@ event.control.voice_cmd.toggle = Comando vocale: Commuta
658
658
  event.control.voice_msg.start = Input vocale: Avvia
659
659
  event.control.voice_msg.stop = Input vocale: Arresta
660
660
  event.control.voice_msg.toggle = Input vocale: Commuta
661
+ exit.msg = Ti piace PyGPT? Sostieni lo sviluppo del progetto:
661
662
  expert.wait.failed: Chiamata all'esperto non riuscita
662
663
  expert.wait.status: In attesa dell'esperto...
663
664
  files.delete.confirm = Eliminare il file/la cartella?
@@ -734,6 +735,7 @@ input.search.placeholder = Cerca...
734
735
  input.send_clear = Pulisci dopo l'invio
735
736
  input.stream = Flusso
736
737
  input.tab = Input
738
+ input.tab.tooltip = {chars} caratteri (~{tokens} token)
737
739
  interpreter.all = Esegui cronologia (tutto)
738
740
  interpreter.auto_clear = Cancella all'invio
739
741
  interpreter.btn.clear = Cancella output
@@ -244,7 +244,7 @@ clipboard.copied_to = Skopiowano do schowka:
244
244
  cmd.enabled = + Narzędzia
245
245
  cmd.tip = Wskazówka: Aby umożliwić wykonanie narzędzi z wtyczek, musisz włączyć opcję "+ Narzędzia".
246
246
  coming_soon = Dostępne wkrótce...
247
- common.down = Przesuń w dół
247
+ common.down = Przesuń w dół
248
248
  common.up = Przesuń w górę
249
249
  confirm.assistant.delete = Na pewno usunąć asystenta?
250
250
  confirm.assistant.files.clear = Wyczyścić pliki (tylko lokalnie)?
@@ -659,6 +659,7 @@ event.control.voice_cmd.toggle = Kontrola głosowa: Przełącz
659
659
  event.control.voice_msg.start = Wejście głosowe: Rozpocznij
660
660
  event.control.voice_msg.stop = Wejście głosowe: Zatrzymaj
661
661
  event.control.voice_msg.toggle = Wejście głosowe: Przełącz
662
+ exit.msg = Podoba Ci się PyGPT? Wesprzyj rozwój projektu:
662
663
  expert.wait.failed: Nie udało się wywołać eksperta
663
664
  expert.wait.status: Oczekiwanie na eksperta...
664
665
  files.delete.confirm = Usunąć plik/katalog?
@@ -735,6 +736,7 @@ input.search.placeholder = Szukaj...
735
736
  input.send_clear = Wyczyść po wysłaniu
736
737
  input.stream = Stream
737
738
  input.tab = Input
739
+ input.tab.tooltip = {chars} znaków (~{tokens} tokenów)
738
740
  interpreter.all = Wykonaj historię (wszystko)
739
741
  interpreter.auto_clear = Wyczyść wyjście przy wysyłaniu
740
742
  interpreter.btn.clear = Wyczyść wyjście
@@ -658,6 +658,7 @@ event.control.voice_cmd.toggle = Керування голосом: Перемк
658
658
  event.control.voice_msg.start = Голосовий вхід: Розпочати
659
659
  event.control.voice_msg.stop = Голосовий вхід: Зупинити
660
660
  event.control.voice_msg.toggle = Голосовий вхід: Перемкнути
661
+ exit.msg = Вам подобається PyGPT? Підтримайте розвиток проєкту:
661
662
  expert.wait.failed: Виклик експерта не вдався
662
663
  expert.wait.status: Очікування експерта...
663
664
  files.delete.confirm = Видалити файл/директорію?
@@ -734,6 +735,7 @@ input.search.placeholder = Пошук...
734
735
  input.send_clear = Очистити після відправлення
735
736
  input.stream = Потік
736
737
  input.tab = Введення
738
+ input.tab.tooltip = {chars} символів (~{tokens} токенів)
737
739
  interpreter.all = Виконати історію (все)
738
740
  interpreter.auto_clear = Очистити при відправці
739
741
  interpreter.btn.clear = Очистити вивід
@@ -658,6 +658,7 @@ event.control.voice_cmd.toggle = 语音控制:切换
658
658
  event.control.voice_msg.start = 语音输入:开始
659
659
  event.control.voice_msg.stop = 语音输入:停止
660
660
  event.control.voice_msg.toggle = 语音输入:切换
661
+ exit.msg = 你喜欢PyGPT吗?支持项目的发展:
661
662
  expert.wait.failed: 调用专家失败
662
663
  expert.wait.status: 等待专家...
663
664
  files.delete.confirm = 刪除文件/目錄?
@@ -734,6 +735,7 @@ input.search.placeholder = 搜索...
734
735
  input.send_clear = 發送後清除
735
736
  input.stream = 流
736
737
  input.tab = 輸入
738
+ input.tab.tooltip = {chars} 字符 (~{tokens} 令牌)
737
739
  interpreter.all = 执行历史记录(全部)
738
740
  interpreter.auto_clear = 发送时清除
739
741
  interpreter.btn.clear = 清除输出
pygpt_net/item/ctx.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.09.05 18:00:00 #
9
+ # Updated Date: 2025.09.26 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -209,6 +209,28 @@ class CtxItem:
209
209
  if src:
210
210
  setattr(self, name, dp(src))
211
211
 
212
+ def set_agent_name(self, name: Optional[str]):
213
+ """
214
+ Set AI/agent name
215
+
216
+ :param name: name
217
+ """
218
+ self.ai_name = name
219
+ if name:
220
+ if self.extra is None:
221
+ self.extra = {}
222
+ self.extra['agent_name'] = name
223
+
224
+ def get_agent_name(self) -> Optional[str]:
225
+ """
226
+ Get AI/agent name
227
+
228
+ :return: name
229
+ """
230
+ if self.extra and isinstance(self.extra, dict):
231
+ return self.extra.get('agent_name', None)
232
+ return None
233
+
212
234
  def is_empty(self) -> bool:
213
235
  """
214
236
  Check if context item is empty
@@ -5,7 +5,7 @@
5
5
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
6
6
  # MIT License #
7
7
  # Created By : Marcin Szczygliński #
8
- # Updated Date: 2025.08.14 01:00:00 #
8
+ # Updated Date: 2025.09.26 14:00:00 #
9
9
  # ================================================== #
10
10
 
11
11
  # >>> Based on LlamaIndex CodeActAgent implementation, with custom plugin tool support <<<
@@ -124,6 +124,9 @@ class CodeActAgent(BaseWorkflowAgent):
124
124
  _plugin_tool_fn: Union[Callable, Awaitable] = PrivateAttr(default=None)
125
125
  _on_stop: Optional[Callable] = PrivateAttr(default=None)
126
126
 
127
+ # Always emit this human-friendly agent name in workflow events for UI consumption.
128
+ _display_agent_name: str = PrivateAttr(default="CodeAct")
129
+
127
130
  def __init__(
128
131
  self,
129
132
  code_execute_fn: Union[Callable, Awaitable],
@@ -296,13 +299,13 @@ class CodeActAgent(BaseWorkflowAgent):
296
299
  StepEvent(name=name, index=index, total=total, meta=meta or {})
297
300
  )
298
301
  except Exception:
299
- # Fallback for older versions of AgentStream
302
+ # Fallback for environments lacking StepEvent wiring.
300
303
  try:
301
304
  ctx.write_event_to_stream(
302
305
  AgentStream(
303
306
  delta="",
304
307
  response="",
305
- current_agent_name="PlannerWorkflow",
308
+ current_agent_name=self._display_agent_name, # always "CodeAct"
306
309
  tool_calls=[],
307
310
  raw={"StepEvent": {"name": name, "index": index, "total": total, "meta": meta or {}}}
308
311
  )
@@ -367,7 +370,7 @@ class CodeActAgent(BaseWorkflowAgent):
367
370
  current_llm_input.insert(0, ChatMessage(role="system", content=system_prompt))
368
371
 
369
372
  ctx.write_event_to_stream(
370
- AgentInput(input=current_llm_input, current_agent_name=self.name)
373
+ AgentInput(input=current_llm_input, current_agent_name=self._display_agent_name) # always "CodeAct"
371
374
  )
372
375
 
373
376
  if any(tool.metadata.name == "handoff" for tool in tools):
@@ -400,7 +403,7 @@ class CodeActAgent(BaseWorkflowAgent):
400
403
  response=full_response_text,
401
404
  tool_calls=[],
402
405
  raw=raw,
403
- current_agent_name=self.name,
406
+ current_agent_name=self._display_agent_name, # always "CodeAct"
404
407
  )
405
408
  )
406
409
 
@@ -443,7 +446,7 @@ class CodeActAgent(BaseWorkflowAgent):
443
446
  response=message,
444
447
  tool_calls=tool_calls,
445
448
  raw=raw,
446
- current_agent_name=self.name,
449
+ current_agent_name=self._display_agent_name, # always "CodeAct"
447
450
  )
448
451
 
449
452
  async def handle_tool_call_results(
@@ -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.14 01:00:00 #
9
+ # Updated Date: 2025.09.26 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from __future__ import annotations
@@ -34,6 +34,7 @@ from llama_index.core.agent.workflow import (
34
34
  ToolCallResult,
35
35
  AgentStream,
36
36
  AgentOutput,
37
+ AgentInput, # ensure AgentInput propagation includes agent name
37
38
  )
38
39
 
39
40
  # v12/v13 compatibility imports
@@ -167,6 +168,9 @@ class OpenAIWorkflowAgent(Workflow):
167
168
  self._on_stop = on_stop
168
169
  self.verbose = verbose
169
170
 
171
+ # human-friendly display name propagated to UI via workflow events
172
+ self._display_agent_name: str = "FunctionAgent"
173
+
170
174
  # construct FunctionAgent once, will override tools/system_prompt/memory per run
171
175
  self._agent = FunctionAgent(
172
176
  name="OpenAIWorkflowAgent",
@@ -287,9 +291,13 @@ class OpenAIWorkflowAgent(Workflow):
287
291
  :param total: Total number of steps (optional)
288
292
  :param meta: Optional metadata dictionary for the step event
289
293
  """
294
+ # Always include agent_name so UI can set it before first token arrives.
295
+ m = dict(meta or {})
296
+ m.setdefault("agent_name", self._display_agent_name)
297
+
290
298
  try:
291
299
  if StepEvent is not None:
292
- ctx.write_event_to_stream(StepEvent(name=name, index=index, total=total, meta=meta or {}))
300
+ ctx.write_event_to_stream(StepEvent(name=name, index=index, total=total, meta=m))
293
301
  return
294
302
  except Exception:
295
303
  pass
@@ -300,9 +308,9 @@ class OpenAIWorkflowAgent(Workflow):
300
308
  AgentStream(
301
309
  delta="",
302
310
  response="",
303
- current_agent_name="OpenAIWorkflowAgent",
311
+ current_agent_name=self._display_agent_name,
304
312
  tool_calls=[],
305
- raw={"StepEvent": {"name": name, "index": index, "total": total, "meta": meta or {}}},
313
+ raw={"StepEvent": {"name": name, "index": index, "total": total, "meta": m}},
306
314
  )
307
315
  )
308
316
  except Exception:
@@ -499,14 +507,14 @@ class OpenAIWorkflowAgent(Workflow):
499
507
  self,
500
508
  ctx: Context,
501
509
  text: str,
502
- agent_name: str = "OpenAIWorkflowAgent"
510
+ agent_name: str = "FunctionAgent"
503
511
  ):
504
512
  """
505
513
  Emit text to the context stream, handling validation errors gracefully.
506
514
 
507
515
  :param ctx: Context for the workflow
508
516
  :param text: Text to emit to the stream
509
- :param agent_name: Name of the agent to set in the event (default: "OpenAIWorkflowAgent")
517
+ :param agent_name: Name of the agent to set in the event (default: "FunctionAgent")
510
518
  """
511
519
  try:
512
520
  ctx.write_event_to_stream(AgentStream(delta=text))
@@ -561,12 +569,31 @@ class OpenAIWorkflowAgent(Workflow):
561
569
  pass
562
570
  return last_answer
563
571
 
572
+ if isinstance(e, AgentInput):
573
+ # Ensure the input event also carries the display name for UI
574
+ try:
575
+ e.current_agent_name = self._display_agent_name
576
+ except Exception:
577
+ pass
578
+ ctx.write_event_to_stream(e)
579
+ continue
580
+
564
581
  if isinstance(e, AgentStream):
565
582
  if getattr(e, "delta", None):
566
583
  has_stream = True
567
- if not getattr(e, "current_agent_name", None):
584
+ # Always enforce agent name for consistency in UI
585
+ try:
586
+ e.current_agent_name = self._display_agent_name
587
+ except Exception:
588
+ # If immutable, rebuild a compatible event object
568
589
  try:
569
- e.current_agent_name = "OpenAIWorkflowAgent"
590
+ e = AgentStream(
591
+ delta=getattr(e, "delta", ""),
592
+ response=getattr(e, "response", ""),
593
+ current_agent_name=self._display_agent_name,
594
+ tool_calls=getattr(e, "tool_calls", []),
595
+ raw=getattr(e, "raw", {}),
596
+ )
570
597
  except Exception:
571
598
  pass
572
599
  ctx.write_event_to_stream(e)
@@ -581,9 +608,9 @@ class OpenAIWorkflowAgent(Workflow):
581
608
  AgentStream(
582
609
  delta=content,
583
610
  response=content,
584
- current_agent_name="OpenAIWorkflowAgent",
585
- tool_calls=e.tool_calls,
586
- raw=e.raw,
611
+ current_agent_name=self._display_agent_name,
612
+ tool_calls=getattr(e, "tool_calls", []),
613
+ raw=getattr(e, "raw", {}),
587
614
  )
588
615
  )
589
616
  continue