pygpt-net 2.6.62__py3-none-any.whl → 2.6.64__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 +11 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/attachment/attachment.py +17 -8
- pygpt_net/controller/camera/camera.py +4 -4
- pygpt_net/controller/lang/custom.py +2 -2
- pygpt_net/controller/presets/editor.py +65 -1
- pygpt_net/controller/ui/mode.py +18 -3
- pygpt_net/core/agents/custom/llama_index/runner.py +15 -52
- pygpt_net/core/agents/custom/runner.py +194 -76
- pygpt_net/core/agents/runners/llama_workflow.py +60 -10
- pygpt_net/core/render/web/renderer.py +11 -0
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/presets/agent_openai_b2b.json +1 -15
- pygpt_net/data/config/presets/agent_openai_coder.json +0 -0
- pygpt_net/data/config/presets/agent_openai_evolve.json +1 -23
- pygpt_net/data/config/presets/agent_openai_planner.json +1 -21
- pygpt_net/data/config/presets/agent_openai_researcher.json +1 -21
- pygpt_net/data/config/presets/agent_openai_supervisor.json +1 -13
- pygpt_net/data/config/presets/agent_openai_writer.json +1 -15
- pygpt_net/data/config/presets/agent_supervisor.json +1 -11
- pygpt_net/data/js/app/runtime.js +10 -0
- pygpt_net/data/js/app/scroll.js +14 -0
- pygpt_net/data/js/app.min.js +6 -4
- pygpt_net/data/locale/locale.de.ini +32 -0
- pygpt_net/data/locale/locale.en.ini +37 -0
- pygpt_net/data/locale/locale.es.ini +32 -0
- pygpt_net/data/locale/locale.fr.ini +32 -0
- pygpt_net/data/locale/locale.it.ini +32 -0
- pygpt_net/data/locale/locale.pl.ini +34 -2
- pygpt_net/data/locale/locale.uk.ini +32 -0
- pygpt_net/data/locale/locale.zh.ini +32 -0
- pygpt_net/js_rc.py +7571 -7499
- pygpt_net/provider/agents/base.py +0 -0
- pygpt_net/provider/agents/llama_index/flow_from_schema.py +0 -0
- pygpt_net/provider/agents/llama_index/planner_workflow.py +15 -3
- pygpt_net/provider/agents/llama_index/workflow/codeact.py +0 -0
- pygpt_net/provider/agents/llama_index/workflow/planner.py +272 -44
- pygpt_net/provider/agents/llama_index/workflow/supervisor.py +0 -0
- pygpt_net/provider/agents/openai/agent.py +0 -0
- pygpt_net/provider/agents/openai/agent_b2b.py +4 -4
- pygpt_net/provider/agents/openai/agent_planner.py +631 -254
- pygpt_net/provider/agents/openai/agent_with_experts.py +0 -0
- pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -4
- pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -4
- pygpt_net/provider/agents/openai/evolve.py +6 -9
- pygpt_net/provider/agents/openai/flow_from_schema.py +0 -0
- pygpt_net/provider/agents/openai/supervisor.py +290 -37
- pygpt_net/provider/api/google/__init__.py +9 -3
- pygpt_net/provider/api/google/image.py +11 -1
- pygpt_net/provider/api/google/music.py +375 -0
- pygpt_net/provider/api/x_ai/__init__.py +0 -0
- pygpt_net/provider/core/agent/__init__.py +0 -0
- pygpt_net/provider/core/agent/base.py +0 -0
- pygpt_net/provider/core/agent/json_file.py +0 -0
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/llms/base.py +0 -0
- pygpt_net/provider/llms/deepseek_api.py +0 -0
- pygpt_net/provider/llms/google.py +0 -0
- pygpt_net/provider/llms/hugging_face_api.py +0 -0
- pygpt_net/provider/llms/hugging_face_router.py +0 -0
- pygpt_net/provider/llms/mistral.py +0 -0
- pygpt_net/provider/llms/perplexity.py +0 -0
- pygpt_net/provider/llms/x_ai.py +0 -0
- pygpt_net/ui/widget/dialog/confirm.py +34 -8
- pygpt_net/ui/widget/option/combo.py +149 -11
- pygpt_net/ui/widget/textarea/input.py +1 -1
- pygpt_net/ui/widget/textarea/web.py +1 -1
- pygpt_net/ui/widget/vision/camera.py +135 -12
- {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/METADATA +13 -2
- {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/RECORD +53 -52
- {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
File without changes
|
|
@@ -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.27 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Dict, Any, List
|
|
@@ -56,12 +56,16 @@ class PlannerAgent(BaseAgent):
|
|
|
56
56
|
prompt_step = self.get_option(preset, "step", "prompt")
|
|
57
57
|
prompt_plan_initial = self.get_option(preset, "plan", "prompt")
|
|
58
58
|
prompt_plan_refine = self.get_option(preset, "plan_refine", "prompt")
|
|
59
|
+
prompt_plan_refine_each_step = self.get_option(preset, "plan_refine", "after_each_subtask")
|
|
59
60
|
if not prompt_step:
|
|
60
61
|
prompt_step = DEFAULT_EXECUTE_PROMPT
|
|
61
62
|
if not prompt_plan_initial:
|
|
62
63
|
prompt_plan_initial = DEFAULT_INITIAL_PLAN_PROMPT
|
|
63
64
|
if not prompt_plan_refine:
|
|
64
65
|
prompt_plan_refine = DEFAULT_PLAN_REFINE_PROMPT
|
|
66
|
+
if prompt_plan_refine_each_step is None:
|
|
67
|
+
prompt_plan_refine_each_step = True
|
|
68
|
+
|
|
65
69
|
|
|
66
70
|
return PlannerWorkflow(
|
|
67
71
|
tools=tools,
|
|
@@ -69,8 +73,9 @@ class PlannerAgent(BaseAgent):
|
|
|
69
73
|
verbose=verbose,
|
|
70
74
|
max_steps=max_steps,
|
|
71
75
|
system_prompt=prompt_step,
|
|
72
|
-
initial_plan_prompt=
|
|
73
|
-
plan_refine_prompt=
|
|
76
|
+
initial_plan_prompt=prompt_plan_initial,
|
|
77
|
+
plan_refine_prompt=prompt_plan_refine,
|
|
78
|
+
refine_after_each_subtask=prompt_plan_refine_each_step,
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
def get_options(self) -> Dict[str, Any]:
|
|
@@ -79,6 +84,7 @@ class PlannerAgent(BaseAgent):
|
|
|
79
84
|
|
|
80
85
|
:return: dict of options
|
|
81
86
|
"""
|
|
87
|
+
# step model -> from globals
|
|
82
88
|
return {
|
|
83
89
|
"step": {
|
|
84
90
|
"label": trans("agent.planner.step.label"),
|
|
@@ -111,6 +117,12 @@ class PlannerAgent(BaseAgent):
|
|
|
111
117
|
"description": trans("agent.planner.refine.prompt.desc"),
|
|
112
118
|
"default": DEFAULT_PLAN_REFINE_PROMPT,
|
|
113
119
|
},
|
|
120
|
+
"after_each_subtask": {
|
|
121
|
+
"type": "bool",
|
|
122
|
+
"label": trans("agent.option.refine.after_each"),
|
|
123
|
+
"description": trans("agent.option.refine.after_each.desc"),
|
|
124
|
+
"default": True,
|
|
125
|
+
},
|
|
114
126
|
}
|
|
115
127
|
},
|
|
116
128
|
}
|
|
File without changes
|
|
@@ -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.27 10:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import List, Optional, Callable
|
|
@@ -22,6 +22,7 @@ from llama_index.core.workflow import (
|
|
|
22
22
|
step,
|
|
23
23
|
)
|
|
24
24
|
from llama_index.core.llms.llm import LLM
|
|
25
|
+
# noqa
|
|
25
26
|
from llama_index.core.prompts import PromptTemplate
|
|
26
27
|
from llama_index.core.tools.types import BaseTool
|
|
27
28
|
|
|
@@ -43,6 +44,10 @@ except Exception:
|
|
|
43
44
|
except Exception:
|
|
44
45
|
ChatMemoryBuffer = None
|
|
45
46
|
|
|
47
|
+
# Translation utility
|
|
48
|
+
from pygpt_net.utils import trans
|
|
49
|
+
|
|
50
|
+
|
|
46
51
|
class SubTask(BaseModel):
|
|
47
52
|
name: str = Field(..., description="The name of the sub-task.")
|
|
48
53
|
input: str = Field(..., description="The input prompt for the sub-task.")
|
|
@@ -56,6 +61,16 @@ class Plan(BaseModel):
|
|
|
56
61
|
sub_tasks: List[SubTask] = Field(..., description="The sub-tasks in the plan.")
|
|
57
62
|
|
|
58
63
|
|
|
64
|
+
# Structured refinement output to emulate the legacy Planner's refine behavior.
|
|
65
|
+
class PlanRefinement(BaseModel):
|
|
66
|
+
is_done: bool = Field(..., description="Whether the overall task is already satisfied.")
|
|
67
|
+
reason: Optional[str] = Field(None, description="Short justification why the plan is complete or needs update.")
|
|
68
|
+
plan: Optional[Plan] = Field(
|
|
69
|
+
default=None,
|
|
70
|
+
description="An updated plan that replaces the remaining sub-tasks. Omit if is_done=True or no update is needed.",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
59
74
|
DEFAULT_INITIAL_PLAN_PROMPT = """\
|
|
60
75
|
You have the following prior context/memory (may be empty):
|
|
61
76
|
{memory_context}
|
|
@@ -70,14 +85,29 @@ The tools available are:
|
|
|
70
85
|
Overall Task: {task}
|
|
71
86
|
"""
|
|
72
87
|
|
|
88
|
+
# Refinement prompt tuned to prevent premature completion and enforce "final deliverable present" rule.
|
|
73
89
|
DEFAULT_PLAN_REFINE_PROMPT = """\
|
|
74
90
|
You have the following prior context/memory (may be empty):
|
|
75
91
|
{memory_context}
|
|
76
92
|
|
|
77
|
-
Think step-by-step. Given an overall task, a set of tools, and completed sub-tasks,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
Think step-by-step. Given an overall task, a set of tools, and completed sub-tasks, decide whether the overall task is already satisfied.
|
|
94
|
+
If not, update the remaining sub-tasks so that the overall task can still be completed.
|
|
95
|
+
|
|
96
|
+
Completion criteria (ALL must be true to set is_done=true):
|
|
97
|
+
- A final, user-facing answer that directly satisfies "Overall Task" already exists within "Completed Sub-Tasks + Outputs".
|
|
98
|
+
- The final answer matches any explicit format and language requested in "Overall Task".
|
|
99
|
+
- No critical transformation/summarization/finalization step remains among "Remaining Sub-Tasks" (e.g., steps like: provide/present/report/answer/summarize/finalize/deliver the result).
|
|
100
|
+
- The final answer does not rely on placeholders such as "will be provided later" or "see plan above".
|
|
101
|
+
|
|
102
|
+
If ANY of the above is false, set is_done=false.
|
|
103
|
+
|
|
104
|
+
Update policy:
|
|
105
|
+
- If the remaining sub-tasks are already reasonable and correctly ordered, do not propose changes: set is_done=false and omit "plan".
|
|
106
|
+
- Only propose a new "plan" if you need to REPLACE the "Remaining Sub-Tasks" (e.g., wrong order, missing critical steps, or new info from completed outputs).
|
|
107
|
+
- Do NOT repeat any completed sub-task. New sub-tasks must replace only the "Remaining Sub-Tasks".
|
|
108
|
+
|
|
109
|
+
Output schema:
|
|
110
|
+
- Return a JSON object matching the schema with fields: is_done (bool), reason (string), and optional plan (Plan).
|
|
81
111
|
|
|
82
112
|
The tools available are:
|
|
83
113
|
{tools_str}
|
|
@@ -122,6 +152,7 @@ class PlannerWorkflow(Workflow):
|
|
|
122
152
|
clear_executor_memory_between_subtasks: bool = False,
|
|
123
153
|
executor_memory_factory: Optional[Callable[[], object]] = None,
|
|
124
154
|
on_stop: Optional[Callable] = None,
|
|
155
|
+
refine_after_each_subtask: bool = True,
|
|
125
156
|
):
|
|
126
157
|
super().__init__(timeout=None, verbose=verbose)
|
|
127
158
|
self._planner_llm = llm
|
|
@@ -135,8 +166,8 @@ class PlannerWorkflow(Workflow):
|
|
|
135
166
|
self._on_stop = on_stop
|
|
136
167
|
|
|
137
168
|
# Human-friendly display names propagated to UI via workflow events.
|
|
138
|
-
self._display_planner_name: str = "
|
|
139
|
-
self._display_executor_name: str = "
|
|
169
|
+
self._display_planner_name: str = trans("agent.planner.display.planner") # UI label
|
|
170
|
+
self._display_executor_name: str = trans("agent.planner.display.executor_agent") # UI label
|
|
140
171
|
|
|
141
172
|
self._executor = FunctionAgent(
|
|
142
173
|
name="PlannerExecutor",
|
|
@@ -158,6 +189,9 @@ class PlannerWorkflow(Workflow):
|
|
|
158
189
|
|
|
159
190
|
self._clear_exec_mem_between_subtasks = clear_executor_memory_between_subtasks
|
|
160
191
|
|
|
192
|
+
# Controls whether the legacy-style refine happens after every sub-task execution.
|
|
193
|
+
self._refine_after_each_subtask = refine_after_each_subtask
|
|
194
|
+
|
|
161
195
|
def _stopped(self) -> bool:
|
|
162
196
|
"""
|
|
163
197
|
Check if the workflow has been stopped.
|
|
@@ -171,6 +205,32 @@ class PlannerWorkflow(Workflow):
|
|
|
171
205
|
return False
|
|
172
206
|
return False
|
|
173
207
|
|
|
208
|
+
# Build human-friendly, step-scoped labels to display in the UI instead of agent names.
|
|
209
|
+
def _agent_label(
|
|
210
|
+
self,
|
|
211
|
+
step: str,
|
|
212
|
+
index: Optional[int] = None,
|
|
213
|
+
total: Optional[int] = None,
|
|
214
|
+
subtask_name: Optional[str] = None,
|
|
215
|
+
) -> str:
|
|
216
|
+
if step == "subtask":
|
|
217
|
+
if index and total:
|
|
218
|
+
base = trans("agent.planner.label.subtask.index_total").format(index=index, total=total)
|
|
219
|
+
elif index:
|
|
220
|
+
base = trans("agent.planner.label.subtask.index").format(index=index)
|
|
221
|
+
else:
|
|
222
|
+
base = trans("agent.planner.label.subtask")
|
|
223
|
+
return trans("agent.planner.label.with_name").format(base=base, name=subtask_name) if subtask_name else base
|
|
224
|
+
if step == "refine":
|
|
225
|
+
if index and total:
|
|
226
|
+
return trans("agent.planner.label.refine.index_total").format(index=index, total=total)
|
|
227
|
+
return trans("agent.planner.label.refine.index").format(index=index) if index else trans("agent.planner.label.refine")
|
|
228
|
+
if step in {"make_plan", "plan"}:
|
|
229
|
+
return trans("agent.planner.label.plan")
|
|
230
|
+
if step in {"execute", "execute_plan"}:
|
|
231
|
+
return trans("agent.planner.label.execute")
|
|
232
|
+
return trans("agent.planner.label.step")
|
|
233
|
+
|
|
174
234
|
def _emit_step_event(
|
|
175
235
|
self,
|
|
176
236
|
ctx: Context,
|
|
@@ -188,9 +248,15 @@ class PlannerWorkflow(Workflow):
|
|
|
188
248
|
:param total: The total number of steps (optional).
|
|
189
249
|
:param meta: Additional metadata for the step (optional).
|
|
190
250
|
"""
|
|
191
|
-
# Always pass a friendly
|
|
251
|
+
# Always pass a friendly per-step label as "agent_name".
|
|
192
252
|
m = dict(meta or {})
|
|
193
|
-
|
|
253
|
+
label = self._agent_label(
|
|
254
|
+
step=name,
|
|
255
|
+
index=index,
|
|
256
|
+
total=total,
|
|
257
|
+
subtask_name=m.get("name"),
|
|
258
|
+
)
|
|
259
|
+
m["agent_name"] = label
|
|
194
260
|
|
|
195
261
|
try:
|
|
196
262
|
ctx.write_event_to_stream(
|
|
@@ -203,7 +269,7 @@ class PlannerWorkflow(Workflow):
|
|
|
203
269
|
AgentStream(
|
|
204
270
|
delta="",
|
|
205
271
|
response="",
|
|
206
|
-
current_agent_name=
|
|
272
|
+
current_agent_name=label,
|
|
207
273
|
tool_calls=[],
|
|
208
274
|
raw={"StepEvent": {"name": name, "index": index, "total": total, "meta": m}}
|
|
209
275
|
)
|
|
@@ -307,22 +373,23 @@ class PlannerWorkflow(Workflow):
|
|
|
307
373
|
self,
|
|
308
374
|
ctx: Context,
|
|
309
375
|
text: str,
|
|
310
|
-
agent_name: str =
|
|
376
|
+
agent_name: Optional[str] = None
|
|
311
377
|
):
|
|
312
378
|
"""
|
|
313
379
|
Emit a text message to the context stream.
|
|
314
380
|
|
|
315
|
-
:param ctx: The context to write the event to
|
|
381
|
+
:param ctx: The context to write the event to.
|
|
316
382
|
:param text: The text message to emit.
|
|
317
|
-
:param agent_name: The name
|
|
383
|
+
:param agent_name: The name/label to display in UI (we pass per-step labels here).
|
|
318
384
|
"""
|
|
385
|
+
label = agent_name or self._display_planner_name
|
|
319
386
|
# Always try to include agent name; fall back to minimal event for older validators.
|
|
320
387
|
try:
|
|
321
388
|
ctx.write_event_to_stream(
|
|
322
389
|
AgentStream(
|
|
323
390
|
delta=text,
|
|
324
391
|
response=text,
|
|
325
|
-
current_agent_name=
|
|
392
|
+
current_agent_name=label,
|
|
326
393
|
tool_calls=[],
|
|
327
394
|
raw={},
|
|
328
395
|
)
|
|
@@ -406,12 +473,13 @@ class PlannerWorkflow(Workflow):
|
|
|
406
473
|
ctx_text = "Completed sub-tasks context:\n" + "\n".join(parts)
|
|
407
474
|
return self._truncate(ctx_text, char_limit or 8000)
|
|
408
475
|
|
|
409
|
-
async def _run_subtask(self, ctx: Context, prompt: str) -> str:
|
|
476
|
+
async def _run_subtask(self, ctx: Context, prompt: str, agent_label: Optional[str] = None) -> str:
|
|
410
477
|
"""
|
|
411
478
|
Run a sub-task using the executor agent.
|
|
412
479
|
|
|
413
480
|
:param ctx: The context in which the sub-task is executed.
|
|
414
481
|
:param prompt: The prompt for the sub-task.
|
|
482
|
+
:param agent_label: Per-step UI label (e.g., 'Sub-task 1/7: ...') used instead of agent name.
|
|
415
483
|
"""
|
|
416
484
|
if self._clear_exec_mem_between_subtasks:
|
|
417
485
|
self._reset_executor_memory()
|
|
@@ -452,15 +520,15 @@ class PlannerWorkflow(Workflow):
|
|
|
452
520
|
if delta:
|
|
453
521
|
has_stream = True
|
|
454
522
|
stream_buf.append(str(delta))
|
|
455
|
-
#
|
|
523
|
+
# Force the per-step label for executor events.
|
|
456
524
|
try:
|
|
457
|
-
e.current_agent_name = self._display_executor_name
|
|
525
|
+
e.current_agent_name = agent_label or self._display_executor_name
|
|
458
526
|
except Exception:
|
|
459
527
|
try:
|
|
460
528
|
e = AgentStream(
|
|
461
529
|
delta=getattr(e, "delta", ""),
|
|
462
530
|
response=getattr(e, "response", ""),
|
|
463
|
-
current_agent_name=self._display_executor_name,
|
|
531
|
+
current_agent_name=agent_label or self._display_executor_name,
|
|
464
532
|
tool_calls=getattr(e, "tool_calls", []),
|
|
465
533
|
raw=getattr(e, "raw", {}),
|
|
466
534
|
)
|
|
@@ -478,7 +546,7 @@ class PlannerWorkflow(Workflow):
|
|
|
478
546
|
AgentStream(
|
|
479
547
|
delta=last_answer,
|
|
480
548
|
response=last_answer,
|
|
481
|
-
current_agent_name=self._display_executor_name,
|
|
549
|
+
current_agent_name=agent_label or self._display_executor_name,
|
|
482
550
|
tool_calls=e.tool_calls,
|
|
483
551
|
raw=e.raw,
|
|
484
552
|
)
|
|
@@ -502,9 +570,73 @@ class PlannerWorkflow(Workflow):
|
|
|
502
570
|
try:
|
|
503
571
|
return await _stream()
|
|
504
572
|
except Exception as ex:
|
|
505
|
-
await self._emit_text(
|
|
573
|
+
await self._emit_text(
|
|
574
|
+
ctx,
|
|
575
|
+
f"\n`{trans('agent.planner.ui.subtask_failed').format(error=ex)}`",
|
|
576
|
+
agent_name=agent_label or self._display_executor_name,
|
|
577
|
+
)
|
|
506
578
|
return last_answer or ("".join(stream_buf).strip() if stream_buf else "")
|
|
507
579
|
|
|
580
|
+
# Helper to render sub-tasks into a readable string for prompts and UI.
|
|
581
|
+
def _format_subtasks(self, sub_tasks: List[SubTask]) -> str:
|
|
582
|
+
parts = []
|
|
583
|
+
for i, st in enumerate(sub_tasks, 1):
|
|
584
|
+
parts.append(
|
|
585
|
+
f"[{i}] name={st.name}\n"
|
|
586
|
+
f" input={st.input}\n"
|
|
587
|
+
f" expected_output={st.expected_output}\n"
|
|
588
|
+
f" dependencies={st.dependencies}"
|
|
589
|
+
)
|
|
590
|
+
return "\n".join(parts) if parts else "(none)"
|
|
591
|
+
|
|
592
|
+
# Helper to render completed outputs for refinement prompt.
|
|
593
|
+
def _format_completed(self, completed: list[tuple[str, str]]) -> str:
|
|
594
|
+
if not completed:
|
|
595
|
+
return "(none)"
|
|
596
|
+
parts = []
|
|
597
|
+
for i, (name, out) in enumerate(completed, 1):
|
|
598
|
+
parts.append(f"[{i}] {name} -> {self._truncate((out or '').strip(), 2000)}")
|
|
599
|
+
joined = "\n".join(parts)
|
|
600
|
+
return self._truncate(joined, self._memory_char_limit or 8000)
|
|
601
|
+
|
|
602
|
+
async def _refine_plan(
|
|
603
|
+
self,
|
|
604
|
+
ctx: Context,
|
|
605
|
+
task: str,
|
|
606
|
+
tools_str: str,
|
|
607
|
+
completed: list[tuple[str, str]],
|
|
608
|
+
remaining: List[SubTask],
|
|
609
|
+
memory_context: str,
|
|
610
|
+
agent_label: Optional[str] = None,
|
|
611
|
+
) -> Optional[PlanRefinement]:
|
|
612
|
+
"""
|
|
613
|
+
Ask the planner LLM to refine the plan. Returns a PlanRefinement or None on failure.
|
|
614
|
+
"""
|
|
615
|
+
completed_text = self._format_completed(completed)
|
|
616
|
+
remaining_text = self._format_subtasks(remaining)
|
|
617
|
+
|
|
618
|
+
# Emit a lightweight status line to the UI.
|
|
619
|
+
await self._emit_text(
|
|
620
|
+
ctx,
|
|
621
|
+
f"\n`{trans('agent.planner.ui.refining_remaining_plan')}`",
|
|
622
|
+
agent_name=agent_label or trans("agent.planner.label.refine"),
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
refinement = await self._planner_llm.astructured_predict(
|
|
627
|
+
PlanRefinement,
|
|
628
|
+
self._plan_refine_prompt,
|
|
629
|
+
tools_str=tools_str,
|
|
630
|
+
task=task,
|
|
631
|
+
completed_outputs=completed_text,
|
|
632
|
+
remaining_sub_tasks=remaining_text,
|
|
633
|
+
memory_context=memory_context,
|
|
634
|
+
)
|
|
635
|
+
return refinement
|
|
636
|
+
except (ValueError, ValidationError):
|
|
637
|
+
# Graceful fallback if the model fails to conform to schema.
|
|
638
|
+
return None
|
|
639
|
+
|
|
508
640
|
@step
|
|
509
641
|
async def make_plan(self, ctx: Context, ev: QueryEvent) -> PlanReady:
|
|
510
642
|
"""
|
|
@@ -531,14 +663,16 @@ class PlannerWorkflow(Workflow):
|
|
|
531
663
|
except (ValueError, ValidationError):
|
|
532
664
|
plan = Plan(sub_tasks=[SubTask(name="default", input=ev.query, expected_output="", dependencies=[])])
|
|
533
665
|
|
|
534
|
-
lines = ["`
|
|
666
|
+
lines = [f"`{trans('agent.planner.ui.current_plan')}`"]
|
|
535
667
|
for i, st in enumerate(plan.sub_tasks, 1):
|
|
668
|
+
header = trans("agent.planner.ui.subtask_header.one").format(index=i, name=st.name)
|
|
536
669
|
lines.append(
|
|
537
|
-
f"\n
|
|
538
|
-
f"
|
|
539
|
-
f"
|
|
670
|
+
f"\n{header}\n"
|
|
671
|
+
f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
|
|
672
|
+
f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
|
|
540
673
|
)
|
|
541
|
-
|
|
674
|
+
# Use a per-step label for plan creation
|
|
675
|
+
await self._emit_text(ctx, "\n".join(lines), agent_name=self._agent_label("make_plan"))
|
|
542
676
|
return PlanReady(plan=plan, query=ev.query)
|
|
543
677
|
|
|
544
678
|
@step
|
|
@@ -555,36 +689,54 @@ class PlannerWorkflow(Workflow):
|
|
|
555
689
|
last_answer = ""
|
|
556
690
|
completed: list[tuple[str, str]] = [] # (name, output)
|
|
557
691
|
|
|
558
|
-
|
|
692
|
+
# Start executing with a per-step label
|
|
693
|
+
execute_label = self._agent_label("execute")
|
|
694
|
+
await self._emit_text(ctx, f"\n\n`{trans('agent.planner.ui.executing_plan')}`", agent_name=execute_label)
|
|
695
|
+
|
|
696
|
+
# Prepare static prompt parts for refinement.
|
|
697
|
+
tools_str = ""
|
|
698
|
+
for t in self._tools:
|
|
699
|
+
tools_str += f"{(t.metadata.name or '').strip()}: {(t.metadata.description or '').strip()}\n"
|
|
700
|
+
memory_context = self._memory_to_text(self._memory)
|
|
701
|
+
|
|
702
|
+
i = 0 # manual index to allow in-place plan updates during refinement
|
|
703
|
+
while i < len(plan_sub_tasks):
|
|
704
|
+
st = plan_sub_tasks[i]
|
|
705
|
+
total = len(plan_sub_tasks)
|
|
706
|
+
|
|
707
|
+
# Compute label for this sub-task
|
|
708
|
+
subtask_label = self._agent_label("subtask", index=i + 1, total=total, subtask_name=st.name)
|
|
559
709
|
|
|
560
|
-
for i, st in enumerate(plan_sub_tasks, 1):
|
|
561
710
|
self._emit_step_event(
|
|
562
711
|
ctx,
|
|
563
712
|
name="subtask",
|
|
564
|
-
index=i,
|
|
713
|
+
index=i + 1,
|
|
565
714
|
total=total,
|
|
566
715
|
meta={
|
|
567
716
|
"name": st.name,
|
|
568
717
|
"expected_output": st.expected_output,
|
|
569
718
|
"dependencies": st.dependencies,
|
|
570
719
|
"input": st.input,
|
|
571
|
-
#
|
|
572
|
-
"agent_name":
|
|
720
|
+
# UI label for this sub-task step
|
|
721
|
+
"agent_name": subtask_label,
|
|
573
722
|
},
|
|
574
723
|
)
|
|
575
724
|
|
|
576
|
-
header = (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
725
|
+
header = trans("agent.planner.ui.subtask_header.progress").format(
|
|
726
|
+
index=i + 1, total=total, name=st.name
|
|
727
|
+
)
|
|
728
|
+
header_block = (
|
|
729
|
+
f"\n\n{header}\n"
|
|
730
|
+
f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
|
|
731
|
+
f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
|
|
580
732
|
)
|
|
581
733
|
|
|
582
734
|
# stop callback
|
|
583
735
|
if self._stopped():
|
|
584
|
-
await self._emit_text(ctx, "\n`
|
|
585
|
-
return FinalEvent(result=last_answer or "
|
|
736
|
+
await self._emit_text(ctx, f"\n`{trans('agent.planner.ui.execution_stopped')}`", agent_name=execute_label)
|
|
737
|
+
return FinalEvent(result=last_answer or trans("agent.planner.ui.execution_stopped"))
|
|
586
738
|
|
|
587
|
-
await self._emit_text(ctx,
|
|
739
|
+
await self._emit_text(ctx, header_block, agent_name=subtask_label)
|
|
588
740
|
|
|
589
741
|
# build context for sub-task
|
|
590
742
|
ctx_text = self._build_context_for_subtask(
|
|
@@ -593,7 +745,7 @@ class PlannerWorkflow(Workflow):
|
|
|
593
745
|
char_limit=self._memory_char_limit,
|
|
594
746
|
)
|
|
595
747
|
|
|
596
|
-
# make composed prompt for sub-task
|
|
748
|
+
# make composed prompt for sub-task (internal; do not translate)
|
|
597
749
|
if ctx_text:
|
|
598
750
|
composed_prompt = (
|
|
599
751
|
f"{ctx_text}\n\n"
|
|
@@ -604,18 +756,94 @@ class PlannerWorkflow(Workflow):
|
|
|
604
756
|
else:
|
|
605
757
|
composed_prompt = st.input
|
|
606
758
|
|
|
607
|
-
# run the sub-task
|
|
608
|
-
sub_answer = await self._run_subtask(ctx, composed_prompt)
|
|
759
|
+
# run the sub-task with the per-step label
|
|
760
|
+
sub_answer = await self._run_subtask(ctx, composed_prompt, agent_label=subtask_label)
|
|
609
761
|
sub_answer = (sub_answer or "").strip()
|
|
610
762
|
|
|
611
|
-
await self._emit_text(
|
|
763
|
+
await self._emit_text(
|
|
764
|
+
ctx,
|
|
765
|
+
f"\n\n`{trans('agent.planner.ui.subtask_finished').format(index=i + 1, total=total, name=st.name)}`",
|
|
766
|
+
agent_name=subtask_label,
|
|
767
|
+
)
|
|
612
768
|
|
|
613
769
|
# save completed sub-task
|
|
614
770
|
completed.append((st.name, sub_answer))
|
|
615
771
|
if sub_answer:
|
|
616
772
|
last_answer = sub_answer
|
|
617
773
|
|
|
618
|
-
#
|
|
774
|
+
# Early stop check (external cancel)
|
|
775
|
+
if self._stopped():
|
|
776
|
+
await self._emit_text(ctx, f"\n`{trans('agent.planner.ui.execution_stopped')}`", agent_name=execute_label)
|
|
777
|
+
return FinalEvent(result=last_answer or trans("agent.planner.ui.execution_stopped"))
|
|
778
|
+
|
|
779
|
+
# Optional legacy-style refine after each sub-task
|
|
780
|
+
i += 1 # move pointer to the next item before potential replacement of tail
|
|
781
|
+
if self._refine_after_each_subtask and i < len(plan_sub_tasks):
|
|
782
|
+
remaining = plan_sub_tasks[i:]
|
|
783
|
+
# Label for refine step
|
|
784
|
+
refine_label = self._agent_label("refine", index=i, total=len(plan_sub_tasks))
|
|
785
|
+
|
|
786
|
+
# Emit a step event for refine to keep UI parity with the legacy Planner.
|
|
787
|
+
self._emit_step_event(
|
|
788
|
+
ctx,
|
|
789
|
+
name="refine",
|
|
790
|
+
index=i,
|
|
791
|
+
total=len(plan_sub_tasks),
|
|
792
|
+
meta={"agent_name": refine_label},
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
refinement = await self._refine_plan(
|
|
796
|
+
ctx=ctx,
|
|
797
|
+
task=ev.query,
|
|
798
|
+
tools_str=tools_str,
|
|
799
|
+
completed=completed,
|
|
800
|
+
remaining=remaining,
|
|
801
|
+
memory_context=memory_context,
|
|
802
|
+
agent_label=refine_label,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
# If refinement failed to parse, skip gracefully.
|
|
806
|
+
if refinement is None:
|
|
807
|
+
continue
|
|
808
|
+
|
|
809
|
+
# If the planner states the task is complete, stop early.
|
|
810
|
+
if getattr(refinement, "is_done", False):
|
|
811
|
+
reason = getattr(refinement, "reason", "") or "Planner judged the task as satisfied."
|
|
812
|
+
await self._emit_text(
|
|
813
|
+
ctx,
|
|
814
|
+
f"\n`{trans('agent.planner.ui.plan_marked_complete').format(reason=reason)}`",
|
|
815
|
+
agent_name=refine_label,
|
|
816
|
+
)
|
|
817
|
+
await self._emit_text(
|
|
818
|
+
ctx,
|
|
819
|
+
f"\n\n`{trans('agent.planner.ui.plan_execution_finished')}`",
|
|
820
|
+
agent_name=execute_label,
|
|
821
|
+
)
|
|
822
|
+
return FinalEvent(result=last_answer or trans("agent.planner.ui.plan_finished"))
|
|
823
|
+
|
|
824
|
+
# If an updated plan was provided, replace the remaining sub-tasks.
|
|
825
|
+
if refinement.plan and refinement.plan.sub_tasks is not None:
|
|
826
|
+
# Filter out any sub-tasks that repeat completed names to avoid loops.
|
|
827
|
+
completed_names = {n for (n, _) in completed}
|
|
828
|
+
new_remaining = [st for st in refinement.plan.sub_tasks if st.name not in completed_names]
|
|
829
|
+
|
|
830
|
+
# If nothing changes, continue.
|
|
831
|
+
current_remaining_repr = self._format_subtasks(remaining)
|
|
832
|
+
new_remaining_repr = self._format_subtasks(new_remaining)
|
|
833
|
+
if new_remaining_repr.strip() != current_remaining_repr.strip():
|
|
834
|
+
plan_sub_tasks = plan_sub_tasks[:i] + new_remaining
|
|
835
|
+
# Present the updated tail of the plan to the UI.
|
|
836
|
+
lines = [f"`{trans('agent.planner.ui.updated_remaining_plan')}`"]
|
|
837
|
+
for k, st_upd in enumerate(new_remaining, i + 1):
|
|
838
|
+
upd_header = trans("agent.planner.ui.subtask_header.progress").format(
|
|
839
|
+
index=k, total=len(plan_sub_tasks), name=st_upd.name
|
|
840
|
+
)
|
|
841
|
+
lines.append(
|
|
842
|
+
f"\n{upd_header}\n"
|
|
843
|
+
f"{trans('agent.planner.ui.expected_output')} {st_upd.expected_output}\n"
|
|
844
|
+
f"{trans('agent.planner.ui.dependencies')} {st_upd.dependencies}\n\n"
|
|
845
|
+
)
|
|
846
|
+
await self._emit_text(ctx, "\n".join(lines), agent_name=refine_label)
|
|
619
847
|
|
|
620
|
-
await self._emit_text(ctx, "\n\n`
|
|
621
|
-
return FinalEvent(result=last_answer or "
|
|
848
|
+
await self._emit_text(ctx, f"\n\n`{trans('agent.planner.ui.plan_execution_finished')}`", agent_name=execute_label)
|
|
849
|
+
return FinalEvent(result=last_answer or trans("agent.planner.ui.plan_finished"))
|
|
File without changes
|
|
File without changes
|
|
@@ -449,13 +449,13 @@ class Agent(BaseAgent):
|
|
|
449
449
|
"type": "bool",
|
|
450
450
|
"label": trans("agent.option.tools.local"),
|
|
451
451
|
"description": trans("agent.option.tools.local.desc"),
|
|
452
|
-
"default":
|
|
452
|
+
"default": True,
|
|
453
453
|
},
|
|
454
454
|
"allow_remote_tools": {
|
|
455
455
|
"type": "bool",
|
|
456
456
|
"label": trans("agent.option.tools.remote"),
|
|
457
457
|
"description": trans("agent.option.tools.remote.desc"),
|
|
458
|
-
"default":
|
|
458
|
+
"default": True,
|
|
459
459
|
},
|
|
460
460
|
}
|
|
461
461
|
},
|
|
@@ -483,13 +483,13 @@ class Agent(BaseAgent):
|
|
|
483
483
|
"type": "bool",
|
|
484
484
|
"label": trans("agent.option.tools.local"),
|
|
485
485
|
"description": trans("agent.option.tools.local.desc"),
|
|
486
|
-
"default":
|
|
486
|
+
"default": True,
|
|
487
487
|
},
|
|
488
488
|
"allow_remote_tools": {
|
|
489
489
|
"type": "bool",
|
|
490
490
|
"label": trans("agent.option.tools.remote"),
|
|
491
491
|
"description": trans("agent.option.tools.remote.desc"),
|
|
492
|
-
"default":
|
|
492
|
+
"default": True,
|
|
493
493
|
},
|
|
494
494
|
}
|
|
495
495
|
},
|