pygpt-net 2.6.60__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.
- pygpt_net/CHANGELOG.txt +14 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/chat/common.py +115 -6
- pygpt_net/controller/chat/input.py +4 -1
- pygpt_net/controller/chat/response.py +8 -2
- pygpt_net/controller/presets/presets.py +121 -6
- pygpt_net/controller/settings/editor.py +0 -15
- pygpt_net/controller/settings/profile.py +16 -4
- pygpt_net/controller/settings/workdir.py +30 -5
- pygpt_net/controller/theme/common.py +4 -2
- pygpt_net/controller/theme/markdown.py +4 -7
- pygpt_net/controller/theme/theme.py +2 -1
- pygpt_net/controller/ui/ui.py +32 -7
- pygpt_net/core/agents/custom/__init__.py +7 -1
- pygpt_net/core/agents/custom/llama_index/factory.py +17 -6
- pygpt_net/core/agents/custom/llama_index/runner.py +52 -4
- pygpt_net/core/agents/custom/llama_index/utils.py +12 -1
- pygpt_net/core/agents/custom/router.py +45 -6
- pygpt_net/core/agents/custom/runner.py +11 -5
- pygpt_net/core/agents/custom/schema.py +3 -1
- pygpt_net/core/agents/custom/utils.py +13 -1
- pygpt_net/core/agents/runners/llama_workflow.py +65 -5
- pygpt_net/core/agents/runners/openai_workflow.py +2 -1
- pygpt_net/core/db/viewer.py +11 -5
- pygpt_net/core/node_editor/graph.py +18 -9
- pygpt_net/core/node_editor/models.py +9 -2
- pygpt_net/core/node_editor/types.py +15 -1
- pygpt_net/core/presets/presets.py +216 -29
- pygpt_net/core/render/markdown/parser.py +0 -2
- pygpt_net/core/render/web/renderer.py +76 -11
- pygpt_net/data/config/config.json +5 -6
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +2 -38
- pygpt_net/data/css/style.dark.css +18 -0
- pygpt_net/data/css/style.light.css +20 -1
- pygpt_net/data/locale/locale.de.ini +66 -1
- pygpt_net/data/locale/locale.en.ini +64 -3
- pygpt_net/data/locale/locale.es.ini +66 -1
- pygpt_net/data/locale/locale.fr.ini +66 -1
- pygpt_net/data/locale/locale.it.ini +66 -1
- pygpt_net/data/locale/locale.pl.ini +67 -2
- pygpt_net/data/locale/locale.uk.ini +66 -1
- pygpt_net/data/locale/locale.zh.ini +66 -1
- pygpt_net/data/locale/plugin.cmd_system.en.ini +62 -66
- pygpt_net/item/ctx.py +23 -1
- pygpt_net/provider/agents/llama_index/flow_from_schema.py +2 -2
- pygpt_net/provider/agents/llama_index/workflow/codeact.py +9 -6
- pygpt_net/provider/agents/llama_index/workflow/openai.py +38 -11
- pygpt_net/provider/agents/llama_index/workflow/planner.py +36 -16
- pygpt_net/provider/agents/llama_index/workflow/supervisor.py +60 -10
- pygpt_net/provider/agents/openai/agent.py +3 -1
- pygpt_net/provider/agents/openai/agent_b2b.py +13 -9
- pygpt_net/provider/agents/openai/agent_planner.py +6 -2
- pygpt_net/provider/agents/openai/agent_with_experts.py +4 -1
- pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -2
- pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -2
- pygpt_net/provider/agents/openai/evolve.py +6 -2
- pygpt_net/provider/agents/openai/supervisor.py +3 -1
- pygpt_net/provider/api/openai/agents/response.py +1 -0
- pygpt_net/provider/core/config/patch.py +18 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -6
- pygpt_net/tools/agent_builder/tool.py +48 -26
- pygpt_net/tools/agent_builder/ui/dialogs.py +36 -28
- pygpt_net/ui/__init__.py +2 -4
- pygpt_net/ui/dialog/about.py +58 -38
- pygpt_net/ui/dialog/db.py +142 -3
- pygpt_net/ui/dialog/preset.py +47 -8
- pygpt_net/ui/layout/toolbox/presets.py +64 -16
- pygpt_net/ui/main.py +2 -2
- pygpt_net/ui/widget/dialog/confirm.py +27 -3
- pygpt_net/ui/widget/dialog/db.py +0 -0
- pygpt_net/ui/widget/draw/painter.py +90 -1
- pygpt_net/ui/widget/lists/preset.py +908 -60
- pygpt_net/ui/widget/node_editor/command.py +10 -10
- pygpt_net/ui/widget/node_editor/config.py +157 -0
- pygpt_net/ui/widget/node_editor/editor.py +223 -153
- pygpt_net/ui/widget/node_editor/item.py +12 -11
- pygpt_net/ui/widget/node_editor/node.py +246 -13
- pygpt_net/ui/widget/node_editor/view.py +179 -63
- pygpt_net/ui/widget/tabs/output.py +1 -1
- pygpt_net/ui/widget/textarea/input.py +157 -23
- pygpt_net/utils.py +114 -2
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.62.dist-info}/METADATA +26 -100
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.62.dist-info}/RECORD +86 -85
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.62.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.62.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.60.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:
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -33,11 +33,8 @@ class Markdown:
|
|
|
33
33
|
"""
|
|
34
34
|
if force:
|
|
35
35
|
self.window.controller.ui.store_state() # store state before theme change
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.load()
|
|
39
|
-
else:
|
|
40
|
-
self.set_default()
|
|
36
|
+
|
|
37
|
+
self.load()
|
|
41
38
|
self.apply()
|
|
42
39
|
|
|
43
40
|
if force:
|
|
@@ -95,7 +92,7 @@ class Markdown:
|
|
|
95
92
|
if base_name == 'web':
|
|
96
93
|
suffix = "-" + web_style
|
|
97
94
|
self.web_style = web_style
|
|
98
|
-
theme = self.window.core.config.get('theme')
|
|
95
|
+
theme = str(self.window.core.config.get('theme'))
|
|
99
96
|
name = str(base_name)
|
|
100
97
|
if theme.startswith('light'):
|
|
101
98
|
color = '.light'
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -83,6 +83,7 @@ class Theme:
|
|
|
83
83
|
:param name: theme name
|
|
84
84
|
:param force: force theme change (manual trigger)
|
|
85
85
|
"""
|
|
86
|
+
self.current_theme = name
|
|
86
87
|
window = self.window
|
|
87
88
|
core = window.core
|
|
88
89
|
controller = window.controller
|
pygpt_net/controller/ui/ui.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.
|
|
9
|
+
# Updated Date: 2025.09.26 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional
|
|
@@ -15,7 +15,7 @@ from PySide6.QtGui import QColor
|
|
|
15
15
|
|
|
16
16
|
from pygpt_net.core.types import MODE_IMAGE
|
|
17
17
|
from pygpt_net.core.events import BaseEvent, Event
|
|
18
|
-
from pygpt_net.utils import trans
|
|
18
|
+
from pygpt_net.utils import trans, short_num
|
|
19
19
|
|
|
20
20
|
from .mode import Mode
|
|
21
21
|
from .tabs import Tabs
|
|
@@ -51,6 +51,9 @@ class UI:
|
|
|
51
51
|
self._last_chat_model = None
|
|
52
52
|
self._last_chat_label = None
|
|
53
53
|
|
|
54
|
+
# Cache for Input tab tooltip to avoid redundant updates
|
|
55
|
+
self._last_input_tab_tooltip = None
|
|
56
|
+
|
|
54
57
|
def setup(self):
|
|
55
58
|
"""Setup UI"""
|
|
56
59
|
self.update_font_size()
|
|
@@ -150,25 +153,47 @@ class UI:
|
|
|
150
153
|
def update_tokens(self):
|
|
151
154
|
"""Update tokens counter in real-time"""
|
|
152
155
|
ui_nodes = self.window.ui.nodes
|
|
153
|
-
|
|
156
|
+
|
|
157
|
+
# Read raw input for accurate character count (without trimming)
|
|
158
|
+
raw_text = ui_nodes['input'].toPlainText()
|
|
159
|
+
prompt = raw_text.strip()
|
|
160
|
+
|
|
154
161
|
input_tokens, system_tokens, extra_tokens, ctx_tokens, ctx_len, ctx_len_all, \
|
|
155
162
|
sum_tokens, max_current, threshold = self.window.core.tokens.get_current(prompt)
|
|
156
163
|
attachments_tokens = self.window.controller.chat.attachment.get_current_tokens()
|
|
157
164
|
sum_tokens += attachments_tokens
|
|
158
165
|
|
|
159
|
-
ctx_string = f"{ctx_len} / {ctx_len_all} - {ctx_tokens} {trans('ctx.tokens')}"
|
|
166
|
+
ctx_string = f"{short_num(ctx_len)} / {short_num(ctx_len_all)} - {short_num(ctx_tokens)} {trans('ctx.tokens')}"
|
|
160
167
|
if ctx_string != self._last_ctx_string:
|
|
161
168
|
ui_nodes['prompt.context'].setText(ctx_string)
|
|
162
169
|
self._last_ctx_string = ctx_string
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
if max_current > 0:
|
|
172
|
+
max_str = short_num(max_current)
|
|
173
|
+
else:
|
|
174
|
+
max_str = "∞"
|
|
166
175
|
|
|
167
|
-
input_string = f"{input_tokens} + {system_tokens} + {ctx_tokens} + {extra_tokens} + {attachments_tokens} = {
|
|
176
|
+
input_string = f"{short_num(input_tokens)} + {short_num(system_tokens)} + {short_num(ctx_tokens)} + {short_num(extra_tokens)} + {short_num(attachments_tokens)} = {short_num(sum_tokens)} / {max_str}"
|
|
168
177
|
if input_string != self._last_input_string:
|
|
169
178
|
ui_nodes['input.counter'].setText(input_string)
|
|
170
179
|
self._last_input_string = input_string
|
|
171
180
|
|
|
181
|
+
# Update Input tab tooltip with live "<chars> chars (~<tokens> tokens)" string
|
|
182
|
+
try:
|
|
183
|
+
tabs = self.window.ui.tabs.get('input')
|
|
184
|
+
except Exception:
|
|
185
|
+
tabs = None
|
|
186
|
+
|
|
187
|
+
if tabs is not None:
|
|
188
|
+
try:
|
|
189
|
+
tooltip = trans("input.tab.tooltip").format(chars=short_num(len(raw_text)), tokens=short_num(input_tokens))
|
|
190
|
+
except Exception:
|
|
191
|
+
tooltip = ""
|
|
192
|
+
#tooltip = f"{short_num(len(raw_text))} chars (~{short_num(input_tokens)} tokens)"
|
|
193
|
+
if tooltip != self._last_input_tab_tooltip:
|
|
194
|
+
tabs.setTabToolTip(0, tooltip)
|
|
195
|
+
self._last_input_tab_tooltip = tooltip
|
|
196
|
+
|
|
172
197
|
def store_state(self):
|
|
173
198
|
"""Store UI state"""
|
|
174
199
|
self.window.controller.layout.scroll_save()
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -182,6 +182,12 @@ class Custom:
|
|
|
182
182
|
slots = node["slots"]
|
|
183
183
|
if "name" in slots and slots["name"]:
|
|
184
184
|
tab["label"] = slots["name"]
|
|
185
|
+
if "role" in slots:
|
|
186
|
+
opts["role"] = {
|
|
187
|
+
"type": "str",
|
|
188
|
+
"label": trans("agent.option.role"),
|
|
189
|
+
"default": slots["role"],
|
|
190
|
+
}
|
|
185
191
|
if "instruction" in slots:
|
|
186
192
|
opts["prompt"] = {
|
|
187
193
|
"type": "textarea",
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -32,7 +32,6 @@ class BuiltAgentLI:
|
|
|
32
32
|
class AgentFactoryLI:
|
|
33
33
|
"""
|
|
34
34
|
Build LlamaIndex ReActAgent/FunctionAgent from AgentNode + NodeRuntime and explicit LLM/tools.
|
|
35
|
-
Best practice: chat_history/max_iterations przekazujemy do konstruktora agenta.
|
|
36
35
|
"""
|
|
37
36
|
def __init__(self, window, logger) -> None:
|
|
38
37
|
self.window = window
|
|
@@ -45,7 +44,7 @@ class AgentFactoryLI:
|
|
|
45
44
|
node_runtime: NodeRuntime,
|
|
46
45
|
llm: Any, # LLM instance (z appki lub resolve_llm)
|
|
47
46
|
tools: List[Any], # BaseTool list
|
|
48
|
-
friendly_map: Dict[str,
|
|
47
|
+
friendly_map: Dict[str, Any],
|
|
49
48
|
force_router: bool = False,
|
|
50
49
|
chat_history: List[Any] = None,
|
|
51
50
|
max_iterations: int = 10,
|
|
@@ -62,23 +61,35 @@ class AgentFactoryLI:
|
|
|
62
61
|
|
|
63
62
|
node_tools = tools if (node_runtime.allow_local_tools or node_runtime.allow_remote_tools) else []
|
|
64
63
|
|
|
64
|
+
# Prefer FunctionAgent if the underlying LLM supports function-calling (recommended by LI).
|
|
65
|
+
# This yields more direct compliance with system_prompt for simple single-output tasks.
|
|
66
|
+
is_fc_model = False
|
|
67
|
+
try:
|
|
68
|
+
is_fc_model = bool(getattr(getattr(llm, "metadata", None), "is_function_calling_model", False))
|
|
69
|
+
except Exception:
|
|
70
|
+
is_fc_model = False
|
|
71
|
+
|
|
65
72
|
if multi_output:
|
|
66
|
-
agent_cls = FunctionAgent # routers
|
|
73
|
+
agent_cls = FunctionAgent # routers: keep JSON compliance
|
|
67
74
|
else:
|
|
68
|
-
agent_cls = FunctionAgent if
|
|
75
|
+
agent_cls = FunctionAgent if is_fc_model else ReActAgent
|
|
76
|
+
|
|
69
77
|
kwargs: Dict[str, Any] = {
|
|
70
78
|
"name": agent_name,
|
|
71
79
|
"system_prompt": instr,
|
|
72
80
|
"llm": llm,
|
|
73
81
|
"chat_history": chat_history or [],
|
|
74
82
|
"max_iterations": int(max_iterations),
|
|
83
|
+
# Provide a short description to reinforce the agent's purpose (if role present).
|
|
84
|
+
"description": (node_runtime.role or agent_name),
|
|
75
85
|
}
|
|
76
86
|
if node_tools:
|
|
77
87
|
kwargs["tools"] = coerce_li_tools(node_tools)
|
|
78
88
|
|
|
79
89
|
instance = agent_cls(**kwargs)
|
|
80
90
|
self.logger.debug(
|
|
81
|
-
f"[li] Built agent {node.id} ({agent_name}), multi_output={multi_output},
|
|
91
|
+
f"[li] Built agent {node.id} ({agent_name}), multi_output={multi_output}, "
|
|
92
|
+
f"routes={allowed_routes}, agent_cls={agent_cls.__name__}"
|
|
82
93
|
)
|
|
83
94
|
return BuiltAgentLI(
|
|
84
95
|
instance=instance,
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -210,6 +210,34 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
210
210
|
def _friendly_map(self) -> Dict[str, str]:
|
|
211
211
|
return {aid: a.name or aid for aid, a in self.fs.agents.items()}
|
|
212
212
|
|
|
213
|
+
def _friendly_map_for_routes(self, route_ids: List[str]) -> Dict[str, Any]:
|
|
214
|
+
"""
|
|
215
|
+
Build a friendly map for the given route ids:
|
|
216
|
+
- Always include a human-friendly name.
|
|
217
|
+
- Include role only if provided in preset options or schema and non-empty.
|
|
218
|
+
"""
|
|
219
|
+
out: Dict[str, Any] = {}
|
|
220
|
+
for rid in route_ids or []:
|
|
221
|
+
a = self.fs.agents.get(rid)
|
|
222
|
+
name = (a.name if a and a.name else rid)
|
|
223
|
+
# Prefer preset option, then schema role
|
|
224
|
+
role_opt = None
|
|
225
|
+
try:
|
|
226
|
+
role_opt = self.option_get(rid, "role", None)
|
|
227
|
+
except Exception:
|
|
228
|
+
role_opt = None
|
|
229
|
+
role_schema = getattr(a, "role", None) if a is not None else None
|
|
230
|
+
role_val = None
|
|
231
|
+
if isinstance(role_opt, str) and role_opt.strip():
|
|
232
|
+
role_val = role_opt.strip()
|
|
233
|
+
elif isinstance(role_schema, str) and role_schema.strip():
|
|
234
|
+
role_val = role_schema.strip()
|
|
235
|
+
item = {"name": name}
|
|
236
|
+
if role_val:
|
|
237
|
+
item["role"] = role_val
|
|
238
|
+
out[rid] = item
|
|
239
|
+
return out
|
|
240
|
+
|
|
213
241
|
async def _emit(self, ctx: Context, ev: Any):
|
|
214
242
|
if self.dbg.event_echo:
|
|
215
243
|
self.logger.debug(f"[event] emit {ev.__class__.__name__}")
|
|
@@ -240,13 +268,28 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
240
268
|
async def _emit_header(self, ctx: Context, name: str):
|
|
241
269
|
if self.dbg.event_echo:
|
|
242
270
|
self.logger.debug(f"[event] header emit begin name='{name}'")
|
|
243
|
-
await self._emit_agent_text(ctx,
|
|
271
|
+
await self._emit_agent_text(ctx, "", agent_name=name)
|
|
272
|
+
# await self._emit_agent_text(ctx, f"\n\n**{name}**\n\n", agent_name=name)
|
|
244
273
|
if self.dbg.event_echo:
|
|
245
274
|
self.logger.debug("[event] header emit done")
|
|
246
275
|
|
|
247
276
|
async def _emit_step_sep(self, ctx: Context, node_id: str):
|
|
248
277
|
try:
|
|
249
|
-
|
|
278
|
+
# Include human-friendly agent name in StepEvent meta for downstream ctx propagation.
|
|
279
|
+
a = self.fs.agents.get(node_id)
|
|
280
|
+
friendly_name = (a.name if a and a.name else node_id)
|
|
281
|
+
await self._emit(
|
|
282
|
+
ctx,
|
|
283
|
+
StepEvent(
|
|
284
|
+
name="next",
|
|
285
|
+
index=self._steps,
|
|
286
|
+
total=self.max_iterations,
|
|
287
|
+
meta={
|
|
288
|
+
"node": node_id,
|
|
289
|
+
"agent_name": friendly_name, # pass current agent display name
|
|
290
|
+
},
|
|
291
|
+
),
|
|
292
|
+
)
|
|
250
293
|
except Exception as e:
|
|
251
294
|
self.logger.error(f"[event] StepEvent emit failed: {e}")
|
|
252
295
|
|
|
@@ -412,6 +455,7 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
412
455
|
f"[runtime] model={getattr(node_rt.model,'name',str(node_rt.model))} "
|
|
413
456
|
f"allow_local={node_rt.allow_local_tools} allow_remote={node_rt.allow_remote_tools} "
|
|
414
457
|
f"instructions='{ellipsize(node_rt.instructions, self.dbg.preview_chars)}'"
|
|
458
|
+
f" role='{ellipsize(node_rt.role or '', self.dbg.preview_chars)}'"
|
|
415
459
|
)
|
|
416
460
|
|
|
417
461
|
llm_node = resolve_llm(self.window, node_rt.model, self.llm_base, self.stream)
|
|
@@ -430,13 +474,17 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
430
474
|
f"user='{ellipsize(user_msg_text, self.dbg.preview_chars)}'"
|
|
431
475
|
)
|
|
432
476
|
|
|
477
|
+
# Prepare friendly map with optional roles for this node's allowed routes
|
|
478
|
+
allowed_routes_now = list(node.outputs or [])
|
|
479
|
+
friendly_map = self._friendly_map_for_routes(allowed_routes_now)
|
|
480
|
+
|
|
433
481
|
# Build agent (chat_history/max_iterations in ctor – best practice)
|
|
434
482
|
built = self.factory.build(
|
|
435
483
|
node=node,
|
|
436
484
|
node_runtime=node_rt,
|
|
437
485
|
llm=llm_node,
|
|
438
486
|
tools=tools_node,
|
|
439
|
-
friendly_map=
|
|
487
|
+
friendly_map=friendly_map,
|
|
440
488
|
chat_history=chat_history_msgs,
|
|
441
489
|
max_iterations=self.max_iterations,
|
|
442
490
|
)
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -47,6 +47,7 @@ def make_option_getter(base_agent, preset: Optional[PresetItem]) -> OptionGetter
|
|
|
47
47
|
class NodeRuntime:
|
|
48
48
|
model: ModelItem
|
|
49
49
|
instructions: str
|
|
50
|
+
role: Optional[str]
|
|
50
51
|
allow_local_tools: bool
|
|
51
52
|
allow_remote_tools: bool
|
|
52
53
|
|
|
@@ -76,6 +77,15 @@ def resolve_node_runtime(
|
|
|
76
77
|
prompt_opt = option_get(node.id, "prompt", None)
|
|
77
78
|
instructions = (prompt_opt or getattr(node, "instruction", None) or base_prompt or "").strip()
|
|
78
79
|
|
|
80
|
+
# Role resolve (optional)
|
|
81
|
+
role_opt = option_get(node.id, "role", None)
|
|
82
|
+
role_from_schema = getattr(node, "role", None) if hasattr(node, "role") else None
|
|
83
|
+
role: Optional[str] = None
|
|
84
|
+
if isinstance(role_opt, str) and role_opt.strip():
|
|
85
|
+
role = role_opt.strip()
|
|
86
|
+
elif isinstance(role_from_schema, str) and role_from_schema.strip():
|
|
87
|
+
role = role_from_schema.strip()
|
|
88
|
+
|
|
79
89
|
allow_local_tools = bool(
|
|
80
90
|
option_get(
|
|
81
91
|
node.id, "allow_local_tools",
|
|
@@ -92,6 +102,7 @@ def resolve_node_runtime(
|
|
|
92
102
|
return NodeRuntime(
|
|
93
103
|
model=model_item,
|
|
94
104
|
instructions=instructions,
|
|
105
|
+
role=role,
|
|
95
106
|
allow_local_tools=allow_local_tools,
|
|
96
107
|
allow_remote_tools=allow_remote_tools,
|
|
97
108
|
)
|
|
@@ -6,14 +6,14 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
import json
|
|
14
14
|
import re
|
|
15
15
|
from dataclasses import dataclass
|
|
16
|
-
from typing import List, Optional, Tuple, Any
|
|
16
|
+
from typing import List, Optional, Tuple, Any, Dict
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@dataclass
|
|
@@ -25,13 +25,46 @@ class RouteDecision:
|
|
|
25
25
|
error: Optional[str] = None
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def build_router_instruction(
|
|
28
|
+
def build_router_instruction(
|
|
29
|
+
agent_name: str,
|
|
30
|
+
current_id: str,
|
|
31
|
+
allowed_routes: List[str],
|
|
32
|
+
friendly_map: Dict[str, Any],
|
|
33
|
+
) -> str:
|
|
29
34
|
"""
|
|
30
35
|
Builds an instruction that forces the model to output JSON with next route and content.
|
|
36
|
+
|
|
37
|
+
Additionally, if the provided friendly_map contains role information for any of the
|
|
38
|
+
allowed routes (e.g. friendly_map[id] is a dict with a "role" key), these roles
|
|
39
|
+
are included in the instruction to improve routing. This is optional and included
|
|
40
|
+
only when present and non-empty.
|
|
31
41
|
"""
|
|
32
42
|
allowed = ", ".join(allowed_routes)
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
|
|
44
|
+
# Normalize human-friendly names: accept either a plain string or a dict with
|
|
45
|
+
# common name keys such as "name", "title" or "label".
|
|
46
|
+
def _extract_name(val: Any, default_name: str) -> str:
|
|
47
|
+
if isinstance(val, str):
|
|
48
|
+
return val
|
|
49
|
+
if isinstance(val, dict):
|
|
50
|
+
return str(val.get("name") or val.get("title") or val.get("label") or default_name)
|
|
51
|
+
return default_name
|
|
52
|
+
|
|
53
|
+
# Extract names and optional roles for allowed routes, without changing input map semantics.
|
|
54
|
+
friendly_names = {rid: _extract_name(friendly_map.get(rid), rid) for rid in allowed_routes}
|
|
55
|
+
|
|
56
|
+
# Roles are optional; include only non-empty strings.
|
|
57
|
+
friendly_roles: Dict[str, str] = {}
|
|
58
|
+
for rid in allowed_routes:
|
|
59
|
+
val = friendly_map.get(rid)
|
|
60
|
+
role_val: Optional[str] = None
|
|
61
|
+
if isinstance(val, dict):
|
|
62
|
+
role_val = val.get("role")
|
|
63
|
+
if isinstance(role_val, str) and role_val.strip():
|
|
64
|
+
friendly_roles[rid] = role_val.strip()
|
|
65
|
+
|
|
66
|
+
# Base instruction
|
|
67
|
+
instr = (
|
|
35
68
|
"You are a routing-capable agent in a multi-agent flow.\n"
|
|
36
69
|
f"Your id is: {current_id}, name: {agent_name}.\n"
|
|
37
70
|
"You MUST respond ONLY with a single JSON object and nothing else.\n"
|
|
@@ -46,9 +79,15 @@ def build_router_instruction(agent_name: str, current_id: str, allowed_routes: L
|
|
|
46
79
|
"- content must contain the user-facing answer (you may include structured data as JSON or Markdown inside content).\n"
|
|
47
80
|
"- Do NOT add any commentary outside of the JSON. No leading or trailing text.\n"
|
|
48
81
|
"- If using tools, still return the final JSON with tool results summarized in content.\n"
|
|
49
|
-
f"- Human-friendly route names: {json.dumps(
|
|
82
|
+
f"- Human-friendly route names: {json.dumps(friendly_names)}\n"
|
|
50
83
|
)
|
|
51
84
|
|
|
85
|
+
# Append roles only if any are present (optional).
|
|
86
|
+
if friendly_roles:
|
|
87
|
+
instr += f"- Human-friendly route roles (optional): {json.dumps(friendly_roles)}\n"
|
|
88
|
+
|
|
89
|
+
return instr
|
|
90
|
+
|
|
52
91
|
|
|
53
92
|
def _extract_json_block(text: str) -> Optional[str]:
|
|
54
93
|
"""
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -171,6 +171,7 @@ class FlowOrchestrator:
|
|
|
171
171
|
f"[runtime] model={getattr(node_rt.model,'name',str(node_rt.model))} "
|
|
172
172
|
f"allow_local={node_rt.allow_local_tools} allow_remote={node_rt.allow_remote_tools} "
|
|
173
173
|
f"instructions='{instr_preview}'"
|
|
174
|
+
f" role='{node_rt.role}'"
|
|
174
175
|
)
|
|
175
176
|
|
|
176
177
|
# Memory selection and INPUT BUILD (memory-first policy)
|
|
@@ -241,13 +242,14 @@ class FlowOrchestrator:
|
|
|
241
242
|
run_kwargs["trace_id"] = trace_id
|
|
242
243
|
|
|
243
244
|
# Header for UI
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
ctx.set_agent_name(agent.name)
|
|
246
|
+
# title = f"\n\n**{built.name}**\n\n"
|
|
247
|
+
# ctx.stream = title
|
|
246
248
|
bridge.on_step(ctx, begin)
|
|
247
249
|
begin = False
|
|
248
250
|
handler.begin = begin
|
|
249
|
-
if not use_partial_ctx:
|
|
250
|
-
handler.to_buffer(title)
|
|
251
|
+
# if not use_partial_ctx:
|
|
252
|
+
# handler.to_buffer(title)
|
|
251
253
|
|
|
252
254
|
display_text = "" # what we show to UI for this step
|
|
253
255
|
next_id: Optional[str] = None
|
|
@@ -443,6 +445,10 @@ class FlowOrchestrator:
|
|
|
443
445
|
else:
|
|
444
446
|
bridge.on_next(ctx)
|
|
445
447
|
|
|
448
|
+
# set next agent name if not at the end
|
|
449
|
+
if current_ids and current_ids[0] in fs.agents:
|
|
450
|
+
ctx.set_agent_name(fs.agents[current_ids[0]].name)
|
|
451
|
+
|
|
446
452
|
# Step duration
|
|
447
453
|
dur = perf_counter() - step_start
|
|
448
454
|
self.logger.debug(f"[step {steps}] duration={dur:.3f}s")
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -31,6 +31,7 @@ class AgentNode(BaseNode):
|
|
|
31
31
|
inputs: List[str] = field(default_factory=list)
|
|
32
32
|
memory_out: Optional[str] = None # single mem by spec
|
|
33
33
|
memory_in: List[str] = field(default_factory=list) # not used, but kept for completeness
|
|
34
|
+
role: str = "" # Optional short description of agent's purpose
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@dataclass
|
|
@@ -91,6 +92,7 @@ def parse_schema(schema: List[Dict[str, Any]]) -> FlowSchema:
|
|
|
91
92
|
inputs=list(_safe_get(slots, "input", "in", default=[])) or [],
|
|
92
93
|
memory_out=(_safe_get(slots, "memory", "out", default=[None]) or [None])[0],
|
|
93
94
|
memory_in=list(_safe_get(slots, "memory", "in", default=[])) or [],
|
|
95
|
+
role=_safe_get(slots, "role", default="") or "",
|
|
94
96
|
)
|
|
95
97
|
fs.agents[nid] = node
|
|
96
98
|
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.25 14:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -116,6 +116,7 @@ def make_option_getter(base_agent, preset: Optional[PresetItem]) -> OptionGetter
|
|
|
116
116
|
class NodeRuntime:
|
|
117
117
|
model: ModelItem
|
|
118
118
|
instructions: str
|
|
119
|
+
role: Optional[str]
|
|
119
120
|
allow_local_tools: bool
|
|
120
121
|
allow_remote_tools: bool
|
|
121
122
|
|
|
@@ -138,6 +139,7 @@ def resolve_node_runtime(
|
|
|
138
139
|
Priority:
|
|
139
140
|
- model: get_option(node.id, "model") -> window.core.models.get(name) -> default_model
|
|
140
141
|
- prompt: get_option(node.id, "prompt") -> node.instruction -> base_prompt -> ""
|
|
142
|
+
- role: get_option(node.id, "role") -> node.role -> None (only used if provided and non-empty)
|
|
141
143
|
- allow_*: get_option(node.id, "allow_local_tools"/"allow_remote_tools")
|
|
142
144
|
-> schema flags -> defaults
|
|
143
145
|
"""
|
|
@@ -157,6 +159,15 @@ def resolve_node_runtime(
|
|
|
157
159
|
prompt_opt = option_get(node.id, "prompt", None)
|
|
158
160
|
instructions = (prompt_opt or getattr(node, "instruction", None) or base_prompt or "").strip()
|
|
159
161
|
|
|
162
|
+
# Role resolve (optional)
|
|
163
|
+
role_opt = option_get(node.id, "role", None)
|
|
164
|
+
role_from_schema = getattr(node, "role", None) if hasattr(node, "role") else None
|
|
165
|
+
role: Optional[str] = None
|
|
166
|
+
if isinstance(role_opt, str) and role_opt.strip():
|
|
167
|
+
role = role_opt.strip()
|
|
168
|
+
elif isinstance(role_from_schema, str) and role_from_schema.strip():
|
|
169
|
+
role = role_from_schema.strip()
|
|
170
|
+
|
|
160
171
|
# Tools flags resolve
|
|
161
172
|
allow_local_tools = bool(
|
|
162
173
|
option_get(
|
|
@@ -176,6 +187,7 @@ def resolve_node_runtime(
|
|
|
176
187
|
return NodeRuntime(
|
|
177
188
|
model=model_item,
|
|
178
189
|
instructions=instructions,
|
|
190
|
+
role=role,
|
|
179
191
|
allow_local_tools=allow_local_tools,
|
|
180
192
|
allow_remote_tools=allow_remote_tools,
|
|
181
193
|
)
|