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.
Files changed (74) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/attachment/attachment.py +17 -8
  4. pygpt_net/controller/camera/camera.py +4 -4
  5. pygpt_net/controller/lang/custom.py +2 -2
  6. pygpt_net/controller/presets/editor.py +65 -1
  7. pygpt_net/controller/ui/mode.py +18 -3
  8. pygpt_net/core/agents/custom/llama_index/runner.py +15 -52
  9. pygpt_net/core/agents/custom/runner.py +194 -76
  10. pygpt_net/core/agents/runners/llama_workflow.py +60 -10
  11. pygpt_net/core/render/web/renderer.py +11 -0
  12. pygpt_net/data/config/config.json +3 -3
  13. pygpt_net/data/config/models.json +3 -3
  14. pygpt_net/data/config/presets/agent_openai_b2b.json +1 -15
  15. pygpt_net/data/config/presets/agent_openai_coder.json +0 -0
  16. pygpt_net/data/config/presets/agent_openai_evolve.json +1 -23
  17. pygpt_net/data/config/presets/agent_openai_planner.json +1 -21
  18. pygpt_net/data/config/presets/agent_openai_researcher.json +1 -21
  19. pygpt_net/data/config/presets/agent_openai_supervisor.json +1 -13
  20. pygpt_net/data/config/presets/agent_openai_writer.json +1 -15
  21. pygpt_net/data/config/presets/agent_supervisor.json +1 -11
  22. pygpt_net/data/js/app/runtime.js +10 -0
  23. pygpt_net/data/js/app/scroll.js +14 -0
  24. pygpt_net/data/js/app.min.js +6 -4
  25. pygpt_net/data/locale/locale.de.ini +32 -0
  26. pygpt_net/data/locale/locale.en.ini +37 -0
  27. pygpt_net/data/locale/locale.es.ini +32 -0
  28. pygpt_net/data/locale/locale.fr.ini +32 -0
  29. pygpt_net/data/locale/locale.it.ini +32 -0
  30. pygpt_net/data/locale/locale.pl.ini +34 -2
  31. pygpt_net/data/locale/locale.uk.ini +32 -0
  32. pygpt_net/data/locale/locale.zh.ini +32 -0
  33. pygpt_net/js_rc.py +7571 -7499
  34. pygpt_net/provider/agents/base.py +0 -0
  35. pygpt_net/provider/agents/llama_index/flow_from_schema.py +0 -0
  36. pygpt_net/provider/agents/llama_index/planner_workflow.py +15 -3
  37. pygpt_net/provider/agents/llama_index/workflow/codeact.py +0 -0
  38. pygpt_net/provider/agents/llama_index/workflow/planner.py +272 -44
  39. pygpt_net/provider/agents/llama_index/workflow/supervisor.py +0 -0
  40. pygpt_net/provider/agents/openai/agent.py +0 -0
  41. pygpt_net/provider/agents/openai/agent_b2b.py +4 -4
  42. pygpt_net/provider/agents/openai/agent_planner.py +631 -254
  43. pygpt_net/provider/agents/openai/agent_with_experts.py +0 -0
  44. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -4
  45. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -4
  46. pygpt_net/provider/agents/openai/evolve.py +6 -9
  47. pygpt_net/provider/agents/openai/flow_from_schema.py +0 -0
  48. pygpt_net/provider/agents/openai/supervisor.py +290 -37
  49. pygpt_net/provider/api/google/__init__.py +9 -3
  50. pygpt_net/provider/api/google/image.py +11 -1
  51. pygpt_net/provider/api/google/music.py +375 -0
  52. pygpt_net/provider/api/x_ai/__init__.py +0 -0
  53. pygpt_net/provider/core/agent/__init__.py +0 -0
  54. pygpt_net/provider/core/agent/base.py +0 -0
  55. pygpt_net/provider/core/agent/json_file.py +0 -0
  56. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -0
  57. pygpt_net/provider/llms/base.py +0 -0
  58. pygpt_net/provider/llms/deepseek_api.py +0 -0
  59. pygpt_net/provider/llms/google.py +0 -0
  60. pygpt_net/provider/llms/hugging_face_api.py +0 -0
  61. pygpt_net/provider/llms/hugging_face_router.py +0 -0
  62. pygpt_net/provider/llms/mistral.py +0 -0
  63. pygpt_net/provider/llms/perplexity.py +0 -0
  64. pygpt_net/provider/llms/x_ai.py +0 -0
  65. pygpt_net/ui/widget/dialog/confirm.py +34 -8
  66. pygpt_net/ui/widget/option/combo.py +149 -11
  67. pygpt_net/ui/widget/textarea/input.py +1 -1
  68. pygpt_net/ui/widget/textarea/web.py +1 -1
  69. pygpt_net/ui/widget/vision/camera.py +135 -12
  70. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/METADATA +13 -2
  71. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/RECORD +53 -52
  72. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/LICENSE +0 -0
  73. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/WHEEL +0 -0
  74. {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.26 17:00:00 #
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, Literal, Optional
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 EvaluationFeedback:
43
- feedback: str
44
- score: Literal["pass", "needs_improvement", "fail"]
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 StructuredPlan:
48
- plan: str
52
+ class Plan:
53
+ sub_tasks: List[SubTask]
49
54
 
50
- class Agent(BaseAgent):
51
55
 
52
- PROMPT_PLANNER = """
53
- Make a plan of task execution for the query by dividing a task into smaller steps.
54
- Do not provide any solutions here. The plan should only contain a list of steps as instructions
55
- for someone else to follow.
56
- Prepare a plan in the language in which the query was made.
57
- Format the plan using markdown.
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
- "Prepare a comprehensive and detailed response to the question based on the action plan. "
81
- "Follow each step outlined in the plan. "
82
- "If any feedback is provided, use it to improve the response."
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
- agent_name = preset.name if preset else "Agent"
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
- kwargs = {
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": self.get_option(preset, "base", "prompt"),
355
+ "instructions": base_instructions,
117
356
  "model": window.core.agents.provider.get_openai_model(model),
118
357
  }
119
358
  if handoffs:
120
- kwargs["handoffs"] = handoffs
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=self.get_option(preset, "base", "allow_local_tools"),
128
- allow_remote_tools= self.get_option(preset, "base", "allow_remote_tools"),
366
+ allow_local_tools=allow_local_tools,
367
+ allow_remote_tools=allow_remote_tools,
129
368
  )
130
- kwargs.update(tool_kwargs) # update kwargs with tools
131
- return OpenAIAgent(**kwargs)
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
- def get_evaluator(
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": "Evaluator",
157
- "instructions": instructions,
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": EvaluationFeedback,
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) # update kwargs with tools
413
+ kwargs.update(tool_kwargs) # update kwargs with tools
170
414
  return OpenAIAgent(**kwargs)
171
415
 
172
- def get_planner(
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": "StructuredPlanner",
196
- "instructions": 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": StructuredPlan,
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) # update kwargs with tools
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
- agent = self.get_agent(window, agent_kwargs)
255
-
256
- # get options
257
- planner_instructions = self.get_option(preset, "planner", "prompt")
258
- planner_model = self.get_option(preset, "planner", "model")
259
- planner_allow_local_tools = self.get_option(preset, "planner", "allow_local_tools")
260
- planner_allow_remote_tools = self.get_option(preset, "planner", "allow_remote_tools")
261
-
262
- feedback_instructions = self.get_option(preset, "feedback", "prompt")
263
- feedback_model = self.get_option(preset, "feedback", "model")
264
- feedback_allow_local_tools = self.get_option(preset, "feedback", "allow_local_tools")
265
- feedback_allow_remote_tools = self.get_option(preset, "feedback", "allow_remote_tools")
266
-
267
- kwargs = {
268
- "input": messages,
269
- "max_turns": int(max_steps),
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
- kwargs["run_config"] = RunConfig(model_provider=custom_provider)
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
- model_planner = window.core.models.get(planner_model)
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=model_planner,
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
- model_eval = window.core.models.get(feedback_model)
291
- evaluator = self.get_evaluator(
292
- window=window,
293
- model=model_eval,
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
- input_items: list[TResponseInputItem] = messages
301
- query = messages[-1]["content"] if messages else ""
302
- messages[-1]["content"] = f"Query: {query}\n\n"
303
-
304
- # run planner first
305
- planner_result = await Runner.run(planner, input_items)
306
- result: StructuredPlan = planner_result.final_output
307
-
308
- ctx.stream = f"**Plan:**\n {result.plan}\n\n"
309
- bridge.on_step(ctx, True)
310
-
311
- input_items.append({"content": f"Query: {query}\n\nPlan: {result.plan}", "role": "user"})
312
-
313
- if not stream:
314
- while True:
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
- response_id = result.last_response_id
326
- if verbose:
327
- print("Final response:", result)
328
-
329
- input_items = result.to_input_list()
330
- final_output, last_response_id = window.core.api.openai.responses.unpack_agent_response(result, ctx)
331
-
332
- if bridge.stopped():
333
- bridge.on_stop(ctx)
334
- break
335
-
336
- evaluator_result = await Runner.run(evaluator, input_items)
337
- ctx.set_agent_name(evaluator.name)
338
- result: EvaluationFeedback = evaluator_result.final_output
339
-
340
- print(f"Evaluator score: {result.score}")
341
- if result.score == "pass":
342
- if use_partial_ctx:
343
- ctx = bridge.on_next_ctx(
344
- ctx=ctx,
345
- input=result.feedback, # new ctx: input
346
- output=final_output, # prev ctx: output
347
- response_id=response_id,
348
- finish=True,
349
- stream=False,
350
- )
351
- else:
352
- print("Response is good enough, exiting.")
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
- final_output = result.plan + "\n___\n"
368
- handler = StreamHandler(window, bridge, final_output)
369
- while True:
370
- kwargs["input"] = input_items
371
- ctx.set_agent_name(agent.name)
372
- result = Runner.run_streamed(
373
- agent,
374
- **kwargs
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
- final_output, response_id = handler.handle(event, ctx)
383
-
384
- if not use_partial_ctx:
385
- bridge.on_next(ctx)
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
- if bridge.stopped():
388
- bridge.on_stop(ctx)
389
- break
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
- input_items = result.to_input_list()
392
- ctx.set_agent_name(evaluator.name)
393
- evaluator_result = await Runner.run(evaluator, input_items)
394
- result: EvaluationFeedback = evaluator_result.final_output
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
- info = f"\n___\n**{trans('agent.eval.score')}: {result.score}**\n\n"
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=result.feedback, # new ctx: input
403
- output=final_output, # prev ctx: 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=True,
772
+ stream=False,
407
773
  )
408
774
  else:
409
- ctx.stream = info
410
- bridge.on_step(ctx, False)
411
- final_output += info
775
+ bridge.on_next(ctx)
412
776
  break
413
777
 
414
- info += f"\n\n**{trans('agent.eval.next')}**\n\nFeedback: {result.feedback}\n___\n"
415
- input_items.append({"content": f"Feedback: {result.feedback}", "role": "user"})
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=result.feedback, # new ctx: input
421
- output=final_output, # prev ctx: output
422
- response_id=response_id,
423
- stream=True,
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.stream = info
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
- "base": {
442
- "label": trans("agent.option.section.base"),
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.option.prompt.base.desc"),
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": "o3-mini-low",
844
+ "default": "gpt-4o",
472
845
  },
473
- "prompt": {
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.PROMPT_PLANNER,
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": False,
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": False,
862
+ "default": True,
490
863
  },
491
864
  }
492
865
  },
493
- "feedback": {
494
- "label": trans("agent.option.section.feedback"),
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.feedback.desc"),
506
- "default": self.PROMPT_FEEDBACK,
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": False,
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": False,
897
+ "default": True,
519
898
  },
520
899
  }
521
900
  },
522
- }
523
-
524
-
901
+ }