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
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.27 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from typing import Dict, Any, Tuple,
|
|
13
|
+
from typing import Dict, Any, Tuple, Optional, List
|
|
14
14
|
|
|
15
15
|
from agents import (
|
|
16
16
|
Agent as OpenAIAgent,
|
|
@@ -38,56 +38,111 @@ from pygpt_net.utils import trans
|
|
|
38
38
|
|
|
39
39
|
from ..base import BaseAgent
|
|
40
40
|
|
|
41
|
+
|
|
42
|
+
# ---------- Structured types to mirror the LlamaIndex Planner ----------
|
|
41
43
|
@dataclass
|
|
42
|
-
class
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
class SubTask:
|
|
45
|
+
name: str
|
|
46
|
+
input: str
|
|
47
|
+
expected_output: str
|
|
48
|
+
dependencies: List[str]
|
|
49
|
+
|
|
45
50
|
|
|
46
51
|
@dataclass
|
|
47
|
-
class
|
|
48
|
-
|
|
52
|
+
class Plan:
|
|
53
|
+
sub_tasks: List[SubTask]
|
|
49
54
|
|
|
50
|
-
class Agent(BaseAgent):
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Example:
|
|
60
|
-
--------
|
|
61
|
-
|
|
62
|
-
**Sub-task 1: <name 1>**
|
|
63
|
-
|
|
64
|
-
- Description: <subtask description 1>
|
|
65
|
-
- Expected output: <expected output 1>
|
|
66
|
-
- Dependencies: []
|
|
67
|
-
- Required Tools: []
|
|
68
|
-
|
|
69
|
-
**Sub-task 2: <name 2>**
|
|
70
|
-
|
|
71
|
-
- Description: <subtask description 2>
|
|
72
|
-
- Expected output: <expected output 2>
|
|
73
|
-
- Dependencies: [<name 1>]
|
|
74
|
-
- Required Tools: [WebSearch]
|
|
75
|
-
|
|
76
|
-
...
|
|
77
|
-
"""
|
|
56
|
+
@dataclass
|
|
57
|
+
class PlanRefinement:
|
|
58
|
+
is_done: bool
|
|
59
|
+
reason: Optional[str]
|
|
60
|
+
plan: Optional[Plan]
|
|
61
|
+
|
|
78
62
|
|
|
63
|
+
class Agent(BaseAgent):
|
|
64
|
+
# System prompts used as templates, exposed in options (planner.initial_prompt, refine.prompt).
|
|
65
|
+
DEFAULT_INITIAL_PLAN_PROMPT = """\
|
|
66
|
+
You have the following prior context/memory (may be empty):
|
|
67
|
+
{memory_context}
|
|
68
|
+
|
|
69
|
+
Think step-by-step. Given a task and a set of tools, create a comprehensive, end-to-end plan to accomplish the task.
|
|
70
|
+
Keep in mind not every task needs to be decomposed into multiple sub-tasks if it is simple enough.
|
|
71
|
+
The plan should end with a sub-task that can achieve the overall task.
|
|
72
|
+
|
|
73
|
+
The tools available are:
|
|
74
|
+
{tools_str}
|
|
75
|
+
|
|
76
|
+
Overall Task: {task}
|
|
77
|
+
|
|
78
|
+
Return a JSON object that matches this schema exactly:
|
|
79
|
+
{
|
|
80
|
+
"sub_tasks": [
|
|
81
|
+
{
|
|
82
|
+
"name": "string",
|
|
83
|
+
"input": "string",
|
|
84
|
+
"expected_output": "string",
|
|
85
|
+
"dependencies": ["string", "..."]
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
DEFAULT_PLAN_REFINE_PROMPT = """\
|
|
92
|
+
You have the following prior context/memory (may be empty):
|
|
93
|
+
{memory_context}
|
|
94
|
+
|
|
95
|
+
Think step-by-step. Given an overall task, a set of tools, and completed sub-tasks, decide whether the overall task is already satisfied.
|
|
96
|
+
If not, update the remaining sub-tasks so that the overall task can still be completed.
|
|
97
|
+
|
|
98
|
+
Completion criteria (ALL must be true to set is_done=true):
|
|
99
|
+
- A final, user-facing answer that directly satisfies "Overall Task" already exists within "Completed Sub-Tasks + Outputs".
|
|
100
|
+
- The final answer matches any explicit format and language requested in "Overall Task".
|
|
101
|
+
- No critical transformation/summarization/finalization step remains among "Remaining Sub-Tasks" (e.g., steps like: provide/present/report/answer/summarize/finalize/deliver the result).
|
|
102
|
+
- The final answer does not rely on placeholders such as "will be provided later" or "see plan above".
|
|
103
|
+
|
|
104
|
+
If ANY of the above is false, set is_done=false.
|
|
105
|
+
|
|
106
|
+
Update policy:
|
|
107
|
+
- If the remaining sub-tasks are already reasonable and correctly ordered, do not propose changes: set is_done=false and omit "plan".
|
|
108
|
+
- 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).
|
|
109
|
+
- Do NOT repeat any completed sub-task. New sub-tasks must replace only the "Remaining Sub-Tasks".
|
|
110
|
+
|
|
111
|
+
Output schema (strict JSON):
|
|
112
|
+
{
|
|
113
|
+
"is_done": true|false,
|
|
114
|
+
"reason": "string or null",
|
|
115
|
+
"plan": {
|
|
116
|
+
"sub_tasks": [
|
|
117
|
+
{
|
|
118
|
+
"name": "string",
|
|
119
|
+
"input": "string",
|
|
120
|
+
"expected_output": "string",
|
|
121
|
+
"dependencies": ["string", "..."]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
} | null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
The tools available are:
|
|
128
|
+
{tools_str}
|
|
129
|
+
|
|
130
|
+
Completed Sub-Tasks + Outputs:
|
|
131
|
+
{completed_outputs}
|
|
132
|
+
|
|
133
|
+
Remaining Sub-Tasks:
|
|
134
|
+
{remaining_sub_tasks}
|
|
135
|
+
|
|
136
|
+
Overall Task: {task}
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# Base executor instruction used by the main execution agent (internal default).
|
|
140
|
+
# Note: keep this concise but explicit that tools must be used for any external action.
|
|
79
141
|
PROMPT = (
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
PROMPT_FEEDBACK = (
|
|
86
|
-
"You evaluate a result and decide if it's good enough. "
|
|
87
|
-
"If it's not good enough, you provide feedback on what needs to be improved. "
|
|
88
|
-
"Never give it a pass on the first try. After 5 attempts, "
|
|
89
|
-
"you can give it a pass if the result is good enough - do not go for perfection, "
|
|
90
|
-
"but ensure all tasks are completed."
|
|
142
|
+
"You are an execution agent. Follow each sub-task strictly and use the available tools to take actions. "
|
|
143
|
+
"Do not claim that you cannot access files or the web; instead, invoke the appropriate tool. "
|
|
144
|
+
"For local files prefer the sequence: cwd -> find (pattern, path, recursive=true) -> read_file(path). "
|
|
145
|
+
"Return only the final output unless explicitly asked for intermediate thoughts."
|
|
91
146
|
)
|
|
92
147
|
|
|
93
148
|
def __init__(self, *args, **kwargs):
|
|
@@ -96,6 +151,180 @@ class Agent(BaseAgent):
|
|
|
96
151
|
self.type = AGENT_TYPE_OPENAI
|
|
97
152
|
self.mode = AGENT_MODE_OPENAI
|
|
98
153
|
self.name = "Planner"
|
|
154
|
+
self._memory_char_limit = 8000 # consistent with the LlamaIndex workflow
|
|
155
|
+
|
|
156
|
+
# ---------- Helpers: planning/execution parity with LlamaIndex + bridge persistence ----------
|
|
157
|
+
|
|
158
|
+
def _truncate(self, text: str, limit: int) -> str:
|
|
159
|
+
if not text or not limit or limit <= 0:
|
|
160
|
+
return text or ""
|
|
161
|
+
if len(text) <= limit:
|
|
162
|
+
return text
|
|
163
|
+
return "...[truncated]...\n" + text[-limit:]
|
|
164
|
+
|
|
165
|
+
def _memory_to_text(self, messages: Optional[List[Dict[str, Any]]]) -> str:
|
|
166
|
+
if not messages:
|
|
167
|
+
return ""
|
|
168
|
+
try:
|
|
169
|
+
parts = []
|
|
170
|
+
for m in messages:
|
|
171
|
+
role = m.get("role", "user")
|
|
172
|
+
content = m.get("content", "")
|
|
173
|
+
parts.append(f"{role}: {content}")
|
|
174
|
+
text = "\n".join(parts)
|
|
175
|
+
except Exception:
|
|
176
|
+
try:
|
|
177
|
+
text = str(messages)
|
|
178
|
+
except Exception:
|
|
179
|
+
text = ""
|
|
180
|
+
return self._truncate(text, self._memory_char_limit)
|
|
181
|
+
|
|
182
|
+
def _tools_to_str(self, tools: List[Any]) -> str:
|
|
183
|
+
out = []
|
|
184
|
+
for t in tools or []:
|
|
185
|
+
try:
|
|
186
|
+
meta = getattr(t, "metadata", None)
|
|
187
|
+
if meta is not None:
|
|
188
|
+
name = (getattr(meta, "name", "") or "").strip()
|
|
189
|
+
desc = (getattr(meta, "description", "") or "").strip()
|
|
190
|
+
if name or desc:
|
|
191
|
+
out.append(f"{name}: {desc}")
|
|
192
|
+
continue
|
|
193
|
+
# Fallback for function-style tools
|
|
194
|
+
name = (getattr(t, "name", "") or "").strip()
|
|
195
|
+
desc = (getattr(t, "description", "") or "").strip()
|
|
196
|
+
if name or desc:
|
|
197
|
+
out.append(f"{name}: {desc}")
|
|
198
|
+
continue
|
|
199
|
+
if isinstance(t, dict):
|
|
200
|
+
name = (t.get("name") or "").strip()
|
|
201
|
+
desc = (t.get("description") or "").strip()
|
|
202
|
+
if name or desc:
|
|
203
|
+
out.append(f"{name}: {desc}")
|
|
204
|
+
continue
|
|
205
|
+
out.append(str(t))
|
|
206
|
+
except Exception:
|
|
207
|
+
out.append(str(t))
|
|
208
|
+
return "\n".join(out)
|
|
209
|
+
|
|
210
|
+
def _format_subtasks(self, sub_tasks: List[SubTask]) -> str:
|
|
211
|
+
parts = []
|
|
212
|
+
for i, st in enumerate(sub_tasks or [], 1):
|
|
213
|
+
parts.append(
|
|
214
|
+
f"[{i}] name={st.name}\n"
|
|
215
|
+
f" input={st.input}\n"
|
|
216
|
+
f" expected_output={st.expected_output}\n"
|
|
217
|
+
f" dependencies={st.dependencies}"
|
|
218
|
+
)
|
|
219
|
+
return "\n".join(parts) if parts else "(none)"
|
|
220
|
+
|
|
221
|
+
def _format_completed(self, completed: List[Tuple[str, str]]) -> str:
|
|
222
|
+
if not completed:
|
|
223
|
+
return "(none)"
|
|
224
|
+
parts = []
|
|
225
|
+
for i, (name, out) in enumerate(completed, 1):
|
|
226
|
+
parts.append(f"[{i}] {name} -> {self._truncate((out or '').strip(), 2000)}")
|
|
227
|
+
joined = "\n".join(parts)
|
|
228
|
+
return self._truncate(joined, self._memory_char_limit or 8000)
|
|
229
|
+
|
|
230
|
+
def _build_context_for_subtask(
|
|
231
|
+
self,
|
|
232
|
+
completed: List[Tuple[str, str]],
|
|
233
|
+
dependencies: List[str],
|
|
234
|
+
char_limit: int,
|
|
235
|
+
) -> str:
|
|
236
|
+
if not completed:
|
|
237
|
+
return ""
|
|
238
|
+
if dependencies:
|
|
239
|
+
selected = [(n, out) for (n, out) in completed if n in set(dependencies)]
|
|
240
|
+
if not selected:
|
|
241
|
+
return ""
|
|
242
|
+
else:
|
|
243
|
+
selected = completed
|
|
244
|
+
|
|
245
|
+
parts = []
|
|
246
|
+
for idx, (name, output) in enumerate(selected, 1):
|
|
247
|
+
clean = (output or "").strip()
|
|
248
|
+
if not clean:
|
|
249
|
+
continue
|
|
250
|
+
parts.append(f"[{idx}] {name} -> {clean}")
|
|
251
|
+
|
|
252
|
+
if not parts:
|
|
253
|
+
return ""
|
|
254
|
+
ctx_text = "Completed sub-tasks context:\n" + "\n".join(parts)
|
|
255
|
+
return self._truncate(ctx_text, char_limit or 8000)
|
|
256
|
+
|
|
257
|
+
def _compose_subtask_prompt(self, st: SubTask, completed: List[Tuple[str, str]]) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Compose the prompt for a single sub-task. Keep it explicit that tools should be used.
|
|
260
|
+
"""
|
|
261
|
+
ctx_text = self._build_context_for_subtask(
|
|
262
|
+
completed=completed,
|
|
263
|
+
dependencies=st.dependencies or [],
|
|
264
|
+
char_limit=self._memory_char_limit,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Small, generic tool usage hint keeps the model from refusing actions.
|
|
268
|
+
tool_hint = (
|
|
269
|
+
"Use tools to take actions. For file operations use: "
|
|
270
|
+
"'cwd' -> 'find' (pattern, path, recursive=true) -> 'read_file(path)'."
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if ctx_text:
|
|
274
|
+
return (
|
|
275
|
+
f"{ctx_text}\n\n"
|
|
276
|
+
f"{tool_hint}\n"
|
|
277
|
+
f"Now execute the next sub-task: {st.name}\n"
|
|
278
|
+
f"Instructions:\n{st.input}\n"
|
|
279
|
+
f"Return only the final output."
|
|
280
|
+
)
|
|
281
|
+
return (
|
|
282
|
+
f"{tool_hint}\n"
|
|
283
|
+
f"{st.input}\n\n"
|
|
284
|
+
f"Return only the final output."
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def _agent_label(
|
|
288
|
+
self,
|
|
289
|
+
step: str,
|
|
290
|
+
index: Optional[int] = None,
|
|
291
|
+
total: Optional[int] = None,
|
|
292
|
+
subtask_name: Optional[str] = None,
|
|
293
|
+
) -> str:
|
|
294
|
+
if step == "subtask":
|
|
295
|
+
if index and total:
|
|
296
|
+
base = trans("agent.planner.label.subtask.index_total").format(index=index, total=total)
|
|
297
|
+
elif index:
|
|
298
|
+
base = trans("agent.planner.label.subtask.index").format(index=index)
|
|
299
|
+
else:
|
|
300
|
+
base = trans("agent.planner.label.subtask")
|
|
301
|
+
return trans("agent.planner.label.with_name").format(base=base, name=subtask_name) if subtask_name else base
|
|
302
|
+
if step == "refine":
|
|
303
|
+
if index and total:
|
|
304
|
+
return trans("agent.planner.label.refine.index_total").format(index=index, total=total)
|
|
305
|
+
return trans("agent.planner.label.refine.index").format(index=index) if index else trans(
|
|
306
|
+
"agent.planner.label.refine")
|
|
307
|
+
if step in {"make_plan", "plan"}:
|
|
308
|
+
return trans("agent.planner.label.plan")
|
|
309
|
+
if step in {"execute", "execute_plan"}:
|
|
310
|
+
return trans("agent.planner.label.execute")
|
|
311
|
+
return trans("agent.planner.label.step")
|
|
312
|
+
|
|
313
|
+
def prepare_model(
|
|
314
|
+
self,
|
|
315
|
+
model: ModelItem,
|
|
316
|
+
window: Any,
|
|
317
|
+
previous_response_id: Optional[str],
|
|
318
|
+
kwargs: Dict[str, Any]
|
|
319
|
+
) -> Dict[str, Any]:
|
|
320
|
+
"""
|
|
321
|
+
Prepare per-run kwargs (keep parity with other agents).
|
|
322
|
+
"""
|
|
323
|
+
if model.provider == "openai" and previous_response_id:
|
|
324
|
+
kwargs["previous_response_id"] = previous_response_id
|
|
325
|
+
return kwargs
|
|
326
|
+
|
|
327
|
+
# ---------- OpenAI Agents providers ----------
|
|
99
328
|
|
|
100
329
|
def get_agent(self, window, kwargs: Dict[str, Any]):
|
|
101
330
|
"""
|
|
@@ -107,56 +336,71 @@ class Agent(BaseAgent):
|
|
|
107
336
|
"""
|
|
108
337
|
context = kwargs.get("context", BridgeContext())
|
|
109
338
|
preset = context.preset
|
|
110
|
-
|
|
339
|
+
# Keep a stable display name; fallback to translated 'Executor' if no preset
|
|
340
|
+
agent_name = (
|
|
341
|
+
preset.name if preset and getattr(preset, "name", None) else trans("agent.planner.display.executor"))
|
|
111
342
|
model = kwargs.get("model", ModelItem())
|
|
112
343
|
tools = kwargs.get("function_tools", [])
|
|
113
344
|
handoffs = kwargs.get("handoffs", [])
|
|
114
|
-
|
|
345
|
+
|
|
346
|
+
# Use prompt from options if provided; fallback to internal default.
|
|
347
|
+
step_prompt = self.get_option(preset, "step", "prompt") if preset else None
|
|
348
|
+
base_instructions = step_prompt or self.PROMPT
|
|
349
|
+
|
|
350
|
+
allow_local_tools = bool(kwargs.get("allow_local_tools", False))
|
|
351
|
+
allow_remote_tools = bool(kwargs.get("allow_remote_tools", False))
|
|
352
|
+
|
|
353
|
+
cfg = {
|
|
115
354
|
"name": agent_name,
|
|
116
|
-
"instructions":
|
|
355
|
+
"instructions": base_instructions,
|
|
117
356
|
"model": window.core.agents.provider.get_openai_model(model),
|
|
118
357
|
}
|
|
119
358
|
if handoffs:
|
|
120
|
-
|
|
359
|
+
cfg["handoffs"] = handoffs
|
|
121
360
|
|
|
122
361
|
tool_kwargs = append_tools(
|
|
123
362
|
tools=tools,
|
|
124
363
|
window=window,
|
|
125
364
|
model=model,
|
|
126
365
|
preset=preset,
|
|
127
|
-
allow_local_tools=
|
|
128
|
-
allow_remote_tools=
|
|
366
|
+
allow_local_tools=allow_local_tools,
|
|
367
|
+
allow_remote_tools=allow_remote_tools,
|
|
129
368
|
)
|
|
130
|
-
|
|
131
|
-
|
|
369
|
+
# NOTE: do not remove this update; it attaches tools so the agent can invoke them.
|
|
370
|
+
cfg.update(tool_kwargs)
|
|
371
|
+
|
|
372
|
+
# Optional: expose tool names inside instructions to gently steer the model.
|
|
373
|
+
try:
|
|
374
|
+
tool_names = [getattr(t, "name", "").strip() for t in tool_kwargs.get("tools", [])]
|
|
375
|
+
tool_names = [n for n in tool_names if n]
|
|
376
|
+
if tool_names:
|
|
377
|
+
cfg["instructions"] = (
|
|
378
|
+
f"{cfg['instructions']} "
|
|
379
|
+
f"Available tools: {', '.join(tool_names)}."
|
|
380
|
+
)
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
132
383
|
|
|
133
|
-
|
|
384
|
+
return OpenAIAgent(**cfg)
|
|
385
|
+
|
|
386
|
+
def get_planner(
|
|
134
387
|
self,
|
|
135
388
|
window,
|
|
136
389
|
model: ModelItem,
|
|
137
|
-
instructions: str,
|
|
138
390
|
preset: PresetItem,
|
|
139
391
|
tools: list,
|
|
140
392
|
allow_local_tools: bool = False,
|
|
141
393
|
allow_remote_tools: bool = False,
|
|
142
394
|
) -> OpenAIAgent:
|
|
143
395
|
"""
|
|
144
|
-
Return Agent provider instance
|
|
145
|
-
|
|
146
|
-
:param window: window instance
|
|
147
|
-
:param model: Model item for the evaluator agent
|
|
148
|
-
:param instructions: Instructions for the evaluator agent
|
|
149
|
-
:param preset: Preset item for additional context
|
|
150
|
-
:param tools: List of function tools to use
|
|
151
|
-
:param allow_local_tools: Whether to allow local tools
|
|
152
|
-
:param allow_remote_tools: Whether to allow remote tools
|
|
153
|
-
:return: Agent provider instance
|
|
396
|
+
Return Agent provider instance producing a structured Plan.
|
|
154
397
|
"""
|
|
155
398
|
kwargs = {
|
|
156
|
-
"name": "
|
|
157
|
-
|
|
399
|
+
"name": "StructuredPlanner",
|
|
400
|
+
# Minimal instructions; the full template is injected as user content.
|
|
401
|
+
"instructions": "Return a JSON object matching the provided schema.",
|
|
158
402
|
"model": window.core.agents.provider.get_openai_model(model),
|
|
159
|
-
"output_type":
|
|
403
|
+
"output_type": Plan,
|
|
160
404
|
}
|
|
161
405
|
tool_kwargs = append_tools(
|
|
162
406
|
tools=tools,
|
|
@@ -166,36 +410,26 @@ class Agent(BaseAgent):
|
|
|
166
410
|
allow_local_tools=allow_local_tools,
|
|
167
411
|
allow_remote_tools=allow_remote_tools,
|
|
168
412
|
)
|
|
169
|
-
kwargs.update(tool_kwargs)
|
|
413
|
+
kwargs.update(tool_kwargs) # update kwargs with tools
|
|
170
414
|
return OpenAIAgent(**kwargs)
|
|
171
415
|
|
|
172
|
-
def
|
|
416
|
+
def get_refiner(
|
|
173
417
|
self,
|
|
174
418
|
window,
|
|
175
419
|
model: ModelItem,
|
|
176
|
-
instructions: str,
|
|
177
420
|
preset: PresetItem,
|
|
178
421
|
tools: list,
|
|
179
422
|
allow_local_tools: bool = False,
|
|
180
423
|
allow_remote_tools: bool = False,
|
|
181
424
|
) -> OpenAIAgent:
|
|
182
425
|
"""
|
|
183
|
-
Return Agent provider instance
|
|
184
|
-
|
|
185
|
-
:param window: window instance
|
|
186
|
-
:param model: Model item for the evaluator agent
|
|
187
|
-
:param instructions: Instructions for the evaluator agent
|
|
188
|
-
:param preset: Preset item for additional context
|
|
189
|
-
:param tools: List of function tools to use
|
|
190
|
-
:param allow_local_tools: Whether to allow local tools
|
|
191
|
-
:param allow_remote_tools: Whether to allow remote tools
|
|
192
|
-
:return: Agent provider instance
|
|
426
|
+
Return Agent provider instance producing a structured PlanRefinement.
|
|
193
427
|
"""
|
|
194
428
|
kwargs = {
|
|
195
|
-
"name": "
|
|
196
|
-
"instructions":
|
|
429
|
+
"name": "PlanRefiner",
|
|
430
|
+
"instructions": "Refine remaining plan steps and return a strict JSON object as instructed.",
|
|
197
431
|
"model": window.core.agents.provider.get_openai_model(model),
|
|
198
|
-
"output_type":
|
|
432
|
+
"output_type": PlanRefinement,
|
|
199
433
|
}
|
|
200
434
|
tool_kwargs = append_tools(
|
|
201
435
|
tools=tools,
|
|
@@ -205,7 +439,7 @@ class Agent(BaseAgent):
|
|
|
205
439
|
allow_local_tools=allow_local_tools,
|
|
206
440
|
allow_remote_tools=allow_remote_tools,
|
|
207
441
|
)
|
|
208
|
-
kwargs.update(tool_kwargs)
|
|
442
|
+
kwargs.update(tool_kwargs)
|
|
209
443
|
return OpenAIAgent(**kwargs)
|
|
210
444
|
|
|
211
445
|
async def run(
|
|
@@ -237,7 +471,7 @@ class Agent(BaseAgent):
|
|
|
237
471
|
model = agent_kwargs.get("model", ModelItem())
|
|
238
472
|
verbose = agent_kwargs.get("verbose", False)
|
|
239
473
|
context = agent_kwargs.get("context", BridgeContext())
|
|
240
|
-
max_steps = agent_kwargs.get("max_iterations", 10)
|
|
474
|
+
max_steps = int(agent_kwargs.get("max_iterations", 10))
|
|
241
475
|
tools = agent_kwargs.get("function_tools", [])
|
|
242
476
|
preset = context.preset
|
|
243
477
|
|
|
@@ -251,185 +485,335 @@ class Agent(BaseAgent):
|
|
|
251
485
|
if experts:
|
|
252
486
|
agent_kwargs["handoffs"] = experts
|
|
253
487
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
488
|
+
# Executor must have access to the same tool set as planner/refiner.
|
|
489
|
+
# If not explicitly provided, inherit allow_* flags from planner options.
|
|
490
|
+
exec_allow_local_tools = agent_kwargs.get("allow_local_tools")
|
|
491
|
+
exec_allow_remote_tools = agent_kwargs.get("allow_remote_tools")
|
|
492
|
+
if exec_allow_local_tools is None:
|
|
493
|
+
exec_allow_local_tools = bool(self.get_option(preset, "planner", "allow_local_tools"))
|
|
494
|
+
if exec_allow_remote_tools is None:
|
|
495
|
+
exec_allow_remote_tools = bool(self.get_option(preset, "planner", "allow_remote_tools"))
|
|
496
|
+
|
|
497
|
+
# executor agent (FunctionAgent equivalent)
|
|
498
|
+
agent_exec_kwargs = dict(agent_kwargs)
|
|
499
|
+
agent_exec_kwargs["allow_local_tools"] = bool(exec_allow_local_tools)
|
|
500
|
+
agent_exec_kwargs["allow_remote_tools"] = bool(exec_allow_remote_tools)
|
|
501
|
+
agent = self.get_agent(window, agent_exec_kwargs)
|
|
502
|
+
|
|
503
|
+
# options
|
|
504
|
+
planner_model_name = self.get_option(preset, "planner", "model")
|
|
505
|
+
planner_model = window.core.models.get(planner_model_name) if planner_model_name else agent_kwargs.get("model",
|
|
506
|
+
ModelItem())
|
|
507
|
+
planner_allow_local_tools = bool(self.get_option(preset, "planner", "allow_local_tools"))
|
|
508
|
+
planner_allow_remote_tools = bool(self.get_option(preset, "planner", "allow_remote_tools"))
|
|
509
|
+
planner_prompt_tpl = self.get_option(preset, "planner", "initial_prompt") or self.DEFAULT_INITIAL_PLAN_PROMPT
|
|
510
|
+
|
|
511
|
+
refine_model_name = self.get_option(preset, "refine", "model") or planner_model_name
|
|
512
|
+
refine_allow_local_tools = bool(self.get_option(preset, "refine", "allow_local_tools"))
|
|
513
|
+
refine_allow_remote_tools = bool(self.get_option(preset, "refine", "allow_remote_tools"))
|
|
514
|
+
refine_prompt_tpl = self.get_option(preset, "refine", "prompt") or self.DEFAULT_PLAN_REFINE_PROMPT
|
|
515
|
+
_after_each_val = self.get_option(preset, "refine", "after_each_subtask")
|
|
516
|
+
refine_after_each = True if _after_each_val is None else bool(_after_each_val)
|
|
517
|
+
|
|
518
|
+
# Common Runner kwargs baseline
|
|
519
|
+
common_kwargs: Dict[str, Any] = {
|
|
520
|
+
"max_turns": max_steps,
|
|
270
521
|
}
|
|
271
522
|
if model.provider != "openai":
|
|
272
523
|
custom_provider = get_custom_model_provider(window, model)
|
|
273
|
-
|
|
524
|
+
common_kwargs["run_config"] = RunConfig(model_provider=custom_provider)
|
|
274
525
|
else:
|
|
275
526
|
set_openai_env(window)
|
|
276
|
-
if previous_response_id:
|
|
277
|
-
kwargs["previous_response_id"] = previous_response_id
|
|
278
527
|
|
|
279
|
-
|
|
528
|
+
# Build tool list description and memory context for prompts
|
|
529
|
+
tools_str = self._tools_to_str(tools)
|
|
530
|
+
query = messages[-1]["content"] if messages else ""
|
|
531
|
+
memory_context = self._memory_to_text(messages)
|
|
532
|
+
|
|
533
|
+
# Step lifecycle control for bridge
|
|
534
|
+
begin = True # first block only
|
|
535
|
+
|
|
536
|
+
# ---------- Make plan (structured) ----------
|
|
280
537
|
planner = self.get_planner(
|
|
281
538
|
window=window,
|
|
282
|
-
model=
|
|
283
|
-
instructions=planner_instructions,
|
|
539
|
+
model=planner_model,
|
|
284
540
|
preset=preset,
|
|
285
541
|
tools=tools,
|
|
286
542
|
allow_local_tools=planner_allow_local_tools,
|
|
287
543
|
allow_remote_tools=planner_allow_remote_tools,
|
|
288
544
|
)
|
|
289
545
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
instructions=feedback_instructions,
|
|
295
|
-
preset=preset,
|
|
296
|
-
tools=tools,
|
|
297
|
-
allow_local_tools=feedback_allow_local_tools,
|
|
298
|
-
allow_remote_tools=feedback_allow_remote_tools,
|
|
546
|
+
plan_prompt = planner_prompt_tpl.format(
|
|
547
|
+
memory_context=memory_context,
|
|
548
|
+
tools_str=tools_str,
|
|
549
|
+
task=query,
|
|
299
550
|
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
kwargs["input"] = input_items
|
|
316
|
-
ctx.set_agent_name(agent.name)
|
|
317
|
-
if bridge.stopped():
|
|
318
|
-
bridge.on_stop(ctx)
|
|
319
|
-
break
|
|
320
|
-
|
|
321
|
-
result = await Runner.run(
|
|
322
|
-
agent,
|
|
323
|
-
**kwargs
|
|
551
|
+
plan_input_items: List[TResponseInputItem] = [{"role": "user", "content": plan_prompt}]
|
|
552
|
+
|
|
553
|
+
try:
|
|
554
|
+
planner_result = await Runner.run(planner, plan_input_items)
|
|
555
|
+
plan_obj: Optional[Plan] = planner_result.final_output # type: ignore
|
|
556
|
+
except Exception:
|
|
557
|
+
plan_obj = None
|
|
558
|
+
|
|
559
|
+
if not plan_obj or not getattr(plan_obj, "sub_tasks", None):
|
|
560
|
+
plan_obj = Plan(sub_tasks=[
|
|
561
|
+
SubTask(
|
|
562
|
+
name="default",
|
|
563
|
+
input=f"{query}",
|
|
564
|
+
expected_output="",
|
|
565
|
+
dependencies=[],
|
|
324
566
|
)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
break
|
|
354
|
-
|
|
355
|
-
print("Re-running with feedback")
|
|
356
|
-
input_items.append({"content": f"Feedback: {result.feedback}", "role": "user"})
|
|
357
|
-
|
|
358
|
-
if use_partial_ctx:
|
|
359
|
-
ctx = bridge.on_next_ctx(
|
|
360
|
-
ctx=ctx,
|
|
361
|
-
input=result.feedback, # new ctx: input
|
|
362
|
-
output=final_output, # prev ctx: output
|
|
363
|
-
response_id=response_id,
|
|
364
|
-
stream=False,
|
|
365
|
-
)
|
|
567
|
+
])
|
|
568
|
+
|
|
569
|
+
# Present current plan as a dedicated step
|
|
570
|
+
plan_lines = [f"`{trans('agent.planner.ui.current_plan')}`"]
|
|
571
|
+
for i, st in enumerate(plan_obj.sub_tasks, 1):
|
|
572
|
+
header = trans("agent.planner.ui.subtask_header.one").format(index=i, name=st.name)
|
|
573
|
+
plan_lines.append(
|
|
574
|
+
f"\n{header}\n"
|
|
575
|
+
f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
|
|
576
|
+
f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
|
|
577
|
+
)
|
|
578
|
+
plan_text = "\n".join(plan_lines)
|
|
579
|
+
|
|
580
|
+
ctx.set_agent_name(self._agent_label("make_plan"))
|
|
581
|
+
ctx.stream = plan_text
|
|
582
|
+
bridge.on_step(ctx, begin)
|
|
583
|
+
begin = False
|
|
584
|
+
|
|
585
|
+
# Persist plan step boundary without leaking inputs
|
|
586
|
+
if use_partial_ctx:
|
|
587
|
+
ctx = bridge.on_next_ctx(
|
|
588
|
+
ctx=ctx,
|
|
589
|
+
input="",
|
|
590
|
+
output=plan_text,
|
|
591
|
+
response_id="",
|
|
592
|
+
finish=False,
|
|
593
|
+
stream=stream,
|
|
594
|
+
)
|
|
366
595
|
else:
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
596
|
+
bridge.on_next(ctx)
|
|
597
|
+
|
|
598
|
+
# ---------- Execute plan with optional refinement after each sub-task ----------
|
|
599
|
+
plan_sub_tasks: List[SubTask] = list(plan_obj.sub_tasks)
|
|
600
|
+
last_answer = ""
|
|
601
|
+
completed: List[Tuple[str, str]] = [] # (name, output)
|
|
602
|
+
|
|
603
|
+
# Prepare static prompt parts for refinement
|
|
604
|
+
memory_context = self._memory_to_text(messages) # re-evaluate after plan message
|
|
605
|
+
|
|
606
|
+
# shared stream handler for sub-task streaming
|
|
607
|
+
handler = StreamHandler(window, bridge)
|
|
608
|
+
|
|
609
|
+
# keep track of previous response id for provider continuity
|
|
610
|
+
prev_rid: Optional[str] = previous_response_id
|
|
611
|
+
|
|
612
|
+
i = 0
|
|
613
|
+
while i < len(plan_sub_tasks):
|
|
614
|
+
if bridge.stopped():
|
|
615
|
+
bridge.on_stop(ctx)
|
|
616
|
+
break
|
|
617
|
+
|
|
618
|
+
st = plan_sub_tasks[i]
|
|
619
|
+
total = len(plan_sub_tasks)
|
|
620
|
+
|
|
621
|
+
# UI header for the sub-task
|
|
622
|
+
subtask_label = self._agent_label("subtask", index=i + 1, total=total, subtask_name=st.name)
|
|
623
|
+
header = trans("agent.planner.ui.subtask_header.progress").format(
|
|
624
|
+
index=i + 1, total=total, name=st.name
|
|
625
|
+
)
|
|
626
|
+
header_block = (
|
|
627
|
+
f"\n\n{header}\n"
|
|
628
|
+
f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
|
|
629
|
+
f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# Compose sub-task prompt and open a new persisted step
|
|
633
|
+
composed_prompt = self._compose_subtask_prompt(st, completed)
|
|
634
|
+
ctx.set_agent_name(subtask_label)
|
|
635
|
+
ctx.stream = header_block
|
|
636
|
+
bridge.on_step(ctx, False) # open a new step block
|
|
637
|
+
|
|
638
|
+
exec_kwargs = dict(common_kwargs)
|
|
639
|
+
exec_items: List[TResponseInputItem] = [{"role": "user", "content": composed_prompt}]
|
|
640
|
+
exec_kwargs["input"] = exec_items
|
|
641
|
+
exec_kwargs = self.prepare_model(model, window, prev_rid, exec_kwargs)
|
|
642
|
+
|
|
643
|
+
sub_answer = ""
|
|
644
|
+
sub_rid = ""
|
|
645
|
+
|
|
646
|
+
if not stream:
|
|
647
|
+
try:
|
|
648
|
+
result = await Runner.run(agent, **exec_kwargs)
|
|
649
|
+
sub_rid = getattr(result, "last_response_id", "") or ""
|
|
650
|
+
sub_answer = str(getattr(result, "final_output", "") or "")
|
|
651
|
+
except Exception as ex:
|
|
652
|
+
sub_answer = trans("agent.planner.ui.subtask_failed").format(error=ex)
|
|
653
|
+
|
|
654
|
+
if sub_answer:
|
|
655
|
+
ctx.stream = sub_answer
|
|
656
|
+
bridge.on_step(ctx, True)
|
|
657
|
+
else:
|
|
658
|
+
result = Runner.run_streamed(agent, **exec_kwargs)
|
|
376
659
|
handler.reset()
|
|
660
|
+
handler.begin = False
|
|
377
661
|
async for event in result.stream_events():
|
|
378
662
|
if bridge.stopped():
|
|
379
663
|
result.cancel()
|
|
380
664
|
bridge.on_stop(ctx)
|
|
381
665
|
break
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
666
|
+
sub_answer, sub_rid = handler.handle(event, ctx)
|
|
667
|
+
|
|
668
|
+
# Save completed sub-task
|
|
669
|
+
sub_answer = (sub_answer or "").strip()
|
|
670
|
+
completed.append((st.name, sub_answer))
|
|
671
|
+
if sub_answer:
|
|
672
|
+
last_answer = sub_answer
|
|
673
|
+
if sub_rid:
|
|
674
|
+
prev_rid = sub_rid
|
|
675
|
+
response_id = sub_rid # keep latest rid for return
|
|
676
|
+
|
|
677
|
+
# Close persisted step (finish only if last and no refine)
|
|
678
|
+
is_last_subtask = (i + 1 == len(plan_sub_tasks))
|
|
679
|
+
will_refine = (refine_after_each and not is_last_subtask)
|
|
680
|
+
if use_partial_ctx:
|
|
681
|
+
ctx = bridge.on_next_ctx(
|
|
682
|
+
ctx=ctx,
|
|
683
|
+
input="",
|
|
684
|
+
output=sub_answer if sub_answer else header_block.strip(),
|
|
685
|
+
response_id=sub_rid,
|
|
686
|
+
finish=(is_last_subtask and not will_refine),
|
|
687
|
+
stream=stream,
|
|
688
|
+
)
|
|
689
|
+
if stream:
|
|
690
|
+
handler.new()
|
|
691
|
+
else:
|
|
692
|
+
bridge.on_next(ctx)
|
|
693
|
+
|
|
694
|
+
if bridge.stopped():
|
|
695
|
+
bridge.on_stop(ctx)
|
|
696
|
+
break
|
|
697
|
+
|
|
698
|
+
# Optional legacy-style refine after each sub-task (if there are remaining ones)
|
|
699
|
+
i += 1
|
|
700
|
+
if refine_after_each and i < len(plan_sub_tasks):
|
|
701
|
+
remaining = plan_sub_tasks[i:]
|
|
702
|
+
refine_label = self._agent_label("refine", index=i, total=len(plan_sub_tasks))
|
|
703
|
+
|
|
704
|
+
# Start refine step
|
|
705
|
+
refine_display = f"\n`{trans('agent.planner.ui.refining_remaining_plan')}`"
|
|
706
|
+
ctx.set_agent_name(refine_label)
|
|
707
|
+
ctx.stream = refine_display
|
|
708
|
+
bridge.on_step(ctx, False)
|
|
709
|
+
|
|
710
|
+
# Build refine prompt
|
|
711
|
+
completed_text = self._format_completed(completed)
|
|
712
|
+
remaining_text = self._format_subtasks(remaining)
|
|
713
|
+
refine_prompt = refine_prompt_tpl.format(
|
|
714
|
+
memory_context=memory_context,
|
|
715
|
+
tools_str=tools_str,
|
|
716
|
+
completed_outputs=completed_text,
|
|
717
|
+
remaining_sub_tasks=remaining_text,
|
|
718
|
+
task=query,
|
|
719
|
+
)
|
|
720
|
+
model_refiner = window.core.models.get(refine_model_name) if refine_model_name else planner_model
|
|
721
|
+
refiner = self.get_refiner(
|
|
722
|
+
window=window,
|
|
723
|
+
model=model_refiner,
|
|
724
|
+
preset=preset,
|
|
725
|
+
tools=tools,
|
|
726
|
+
allow_local_tools=refine_allow_local_tools,
|
|
727
|
+
allow_remote_tools=refine_allow_remote_tools,
|
|
728
|
+
)
|
|
386
729
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
730
|
+
refinement: Optional[PlanRefinement] = None
|
|
731
|
+
refine_rid = ""
|
|
732
|
+
try:
|
|
733
|
+
refinement_result = await Runner.run(refiner, [{"role": "user", "content": refine_prompt}])
|
|
734
|
+
refinement = refinement_result.final_output # type: ignore
|
|
735
|
+
refine_rid = getattr(refinement_result, "last_response_id", "") or ""
|
|
736
|
+
except Exception:
|
|
737
|
+
refinement = None
|
|
738
|
+
|
|
739
|
+
if refinement is None:
|
|
740
|
+
refine_display += f"\n`{trans('agent.planner.ui.refine_failed_parse')}`"
|
|
741
|
+
ctx.stream = f"\n`{trans('agent.planner.ui.refine_failed_parse')}`"
|
|
742
|
+
bridge.on_step(ctx, True)
|
|
743
|
+
# finalize refine step
|
|
744
|
+
if use_partial_ctx:
|
|
745
|
+
ctx = bridge.on_next_ctx(
|
|
746
|
+
ctx=ctx,
|
|
747
|
+
input="",
|
|
748
|
+
output=refine_display,
|
|
749
|
+
response_id=refine_rid,
|
|
750
|
+
finish=False,
|
|
751
|
+
stream=False,
|
|
752
|
+
)
|
|
753
|
+
else:
|
|
754
|
+
bridge.on_next(ctx)
|
|
755
|
+
continue
|
|
390
756
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
757
|
+
if getattr(refinement, "is_done", False):
|
|
758
|
+
reason = getattr(refinement, "reason", "") or "Planner judged the task as satisfied."
|
|
759
|
+
done_msg = f"\n`{trans('agent.planner.ui.plan_marked_complete').format(reason=reason)}`"
|
|
760
|
+
refine_display += done_msg
|
|
761
|
+
ctx.stream = done_msg
|
|
762
|
+
bridge.on_step(ctx, True)
|
|
395
763
|
|
|
396
|
-
|
|
397
|
-
if result.score == "pass":
|
|
398
|
-
info += f"\n\n**{trans('agent.eval.score.good')}**\n"
|
|
764
|
+
# finalize refine step as the last block
|
|
399
765
|
if use_partial_ctx:
|
|
400
766
|
ctx = bridge.on_next_ctx(
|
|
401
767
|
ctx=ctx,
|
|
402
|
-
input=
|
|
403
|
-
output=
|
|
404
|
-
response_id=response_id,
|
|
768
|
+
input="",
|
|
769
|
+
output=refine_display,
|
|
770
|
+
response_id=refine_rid or (response_id or ""),
|
|
405
771
|
finish=True,
|
|
406
|
-
stream=
|
|
772
|
+
stream=False,
|
|
407
773
|
)
|
|
408
774
|
else:
|
|
409
|
-
ctx
|
|
410
|
-
bridge.on_step(ctx, False)
|
|
411
|
-
final_output += info
|
|
775
|
+
bridge.on_next(ctx)
|
|
412
776
|
break
|
|
413
777
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
778
|
+
if refinement.plan and getattr(refinement.plan, "sub_tasks", None):
|
|
779
|
+
completed_names = {n for (n, _) in completed}
|
|
780
|
+
new_remaining = [st for st in refinement.plan.sub_tasks if st.name not in completed_names]
|
|
781
|
+
|
|
782
|
+
current_remaining_repr = self._format_subtasks(remaining)
|
|
783
|
+
new_remaining_repr = self._format_subtasks(new_remaining)
|
|
784
|
+
if new_remaining_repr.strip() != current_remaining_repr.strip():
|
|
785
|
+
plan_sub_tasks = plan_sub_tasks[:i] + new_remaining
|
|
786
|
+
# Present the updated tail of the plan
|
|
787
|
+
lines = [f"`{trans('agent.planner.ui.updated_remaining_plan')}`"]
|
|
788
|
+
for k, st_upd in enumerate(new_remaining, i + 1):
|
|
789
|
+
upd_header = trans("agent.planner.ui.subtask_header.progress").format(
|
|
790
|
+
index=k, total=len(plan_sub_tasks), name=st_upd.name
|
|
791
|
+
)
|
|
792
|
+
lines.append(
|
|
793
|
+
f"\n{upd_header}\n"
|
|
794
|
+
f"{trans('agent.planner.ui.expected_output')} {st_upd.expected_output}\n"
|
|
795
|
+
f"{trans('agent.planner.ui.dependencies')} {st_upd.dependencies}\n\n"
|
|
796
|
+
)
|
|
797
|
+
upd_text = "\n".join(lines)
|
|
798
|
+
refine_display += "\n" + upd_text
|
|
799
|
+
ctx.stream = upd_text
|
|
800
|
+
bridge.on_step(ctx, True)
|
|
801
|
+
|
|
802
|
+
# finalize refine step (no extra noise)
|
|
417
803
|
if use_partial_ctx:
|
|
418
804
|
ctx = bridge.on_next_ctx(
|
|
419
805
|
ctx=ctx,
|
|
420
|
-
input=
|
|
421
|
-
output=
|
|
422
|
-
response_id=
|
|
423
|
-
|
|
806
|
+
input="",
|
|
807
|
+
output=refine_display,
|
|
808
|
+
response_id=refine_rid,
|
|
809
|
+
finish=False,
|
|
810
|
+
stream=False,
|
|
424
811
|
)
|
|
425
|
-
handler.new()
|
|
426
812
|
else:
|
|
427
|
-
ctx
|
|
428
|
-
bridge.on_step(ctx, False)
|
|
429
|
-
handler.to_buffer(info)
|
|
430
|
-
|
|
431
|
-
return ctx, final_output, response_id
|
|
813
|
+
bridge.on_next(ctx)
|
|
432
814
|
|
|
815
|
+
# Return last answer (final block already closed in the loop)
|
|
816
|
+
return ctx, (last_answer or trans("agent.planner.ui.plan_finished")), (response_id or "")
|
|
433
817
|
|
|
434
818
|
def get_options(self) -> Dict[str, Any]:
|
|
435
819
|
"""
|
|
@@ -437,28 +821,17 @@ class Agent(BaseAgent):
|
|
|
437
821
|
|
|
438
822
|
:return: dict of options
|
|
439
823
|
"""
|
|
824
|
+
# step model -> from globals
|
|
440
825
|
return {
|
|
441
|
-
"
|
|
442
|
-
"label": trans("agent.
|
|
826
|
+
"step": {
|
|
827
|
+
"label": trans("agent.planner.step.label"),
|
|
443
828
|
"options": {
|
|
444
829
|
"prompt": {
|
|
445
830
|
"type": "textarea",
|
|
446
831
|
"label": trans("agent.option.prompt"),
|
|
447
|
-
"description": trans("agent.
|
|
832
|
+
"description": trans("agent.planner.step.prompt.desc"),
|
|
448
833
|
"default": self.PROMPT,
|
|
449
834
|
},
|
|
450
|
-
"allow_local_tools": {
|
|
451
|
-
"type": "bool",
|
|
452
|
-
"label": trans("agent.option.tools.local"),
|
|
453
|
-
"description": trans("agent.option.tools.local.desc"),
|
|
454
|
-
"default": False,
|
|
455
|
-
},
|
|
456
|
-
"allow_remote_tools": {
|
|
457
|
-
"type": "bool",
|
|
458
|
-
"label": trans("agent.option.tools.remote"),
|
|
459
|
-
"description": trans("agent.option.tools.remote.desc"),
|
|
460
|
-
"default": False,
|
|
461
|
-
},
|
|
462
835
|
}
|
|
463
836
|
},
|
|
464
837
|
"planner": {
|
|
@@ -468,30 +841,30 @@ class Agent(BaseAgent):
|
|
|
468
841
|
"label": trans("agent.option.model"),
|
|
469
842
|
"type": "combo",
|
|
470
843
|
"use": "models",
|
|
471
|
-
"default": "
|
|
844
|
+
"default": "gpt-4o",
|
|
472
845
|
},
|
|
473
|
-
"
|
|
846
|
+
"initial_prompt": {
|
|
474
847
|
"type": "textarea",
|
|
475
848
|
"label": trans("agent.option.prompt"),
|
|
476
849
|
"description": trans("agent.option.prompt.planner.desc"),
|
|
477
|
-
"default": self.
|
|
850
|
+
"default": self.DEFAULT_INITIAL_PLAN_PROMPT,
|
|
478
851
|
},
|
|
479
852
|
"allow_local_tools": {
|
|
480
853
|
"type": "bool",
|
|
481
854
|
"label": trans("agent.option.tools.local"),
|
|
482
855
|
"description": trans("agent.option.tools.local.desc"),
|
|
483
|
-
"default":
|
|
856
|
+
"default": True,
|
|
484
857
|
},
|
|
485
858
|
"allow_remote_tools": {
|
|
486
859
|
"type": "bool",
|
|
487
860
|
"label": trans("agent.option.tools.remote"),
|
|
488
861
|
"description": trans("agent.option.tools.remote.desc"),
|
|
489
|
-
"default":
|
|
862
|
+
"default": True,
|
|
490
863
|
},
|
|
491
864
|
}
|
|
492
865
|
},
|
|
493
|
-
"
|
|
494
|
-
"label": trans("agent.
|
|
866
|
+
"refine": {
|
|
867
|
+
"label": trans("agent.planner.refine.label"),
|
|
495
868
|
"options": {
|
|
496
869
|
"model": {
|
|
497
870
|
"label": trans("agent.option.model"),
|
|
@@ -502,23 +875,27 @@ class Agent(BaseAgent):
|
|
|
502
875
|
"prompt": {
|
|
503
876
|
"type": "textarea",
|
|
504
877
|
"label": trans("agent.option.prompt"),
|
|
505
|
-
"description": trans("agent.option.prompt.
|
|
506
|
-
"default": self.
|
|
878
|
+
"description": trans("agent.option.prompt.refine.desc"),
|
|
879
|
+
"default": self.DEFAULT_PLAN_REFINE_PROMPT,
|
|
880
|
+
},
|
|
881
|
+
"after_each_subtask": {
|
|
882
|
+
"type": "bool",
|
|
883
|
+
"label": trans("agent.option.refine.after_each"),
|
|
884
|
+
"description": trans("agent.option.refine.after_each.desc"),
|
|
885
|
+
"default": True,
|
|
507
886
|
},
|
|
508
887
|
"allow_local_tools": {
|
|
509
888
|
"type": "bool",
|
|
510
889
|
"label": trans("agent.option.tools.local"),
|
|
511
890
|
"description": trans("agent.option.tools.local.desc"),
|
|
512
|
-
"default":
|
|
891
|
+
"default": True,
|
|
513
892
|
},
|
|
514
893
|
"allow_remote_tools": {
|
|
515
894
|
"type": "bool",
|
|
516
895
|
"label": trans("agent.option.tools.remote"),
|
|
517
896
|
"description": trans("agent.option.tools.remote.desc"),
|
|
518
|
-
"default":
|
|
897
|
+
"default": True,
|
|
519
898
|
},
|
|
520
899
|
}
|
|
521
900
|
},
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
|
|
901
|
+
}
|