fast-agent-mcp 0.1.12__py3-none-any.whl → 0.2.0__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 (169) hide show
  1. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
  2. fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
  3. mcp_agent/__init__.py +75 -0
  4. mcp_agent/agents/agent.py +61 -415
  5. mcp_agent/agents/base_agent.py +522 -0
  6. mcp_agent/agents/workflow/__init__.py +1 -0
  7. mcp_agent/agents/workflow/chain_agent.py +173 -0
  8. mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
  9. mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
  10. mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +11 -21
  11. mcp_agent/agents/workflow/parallel_agent.py +182 -0
  12. mcp_agent/agents/workflow/router_agent.py +307 -0
  13. mcp_agent/app.py +15 -19
  14. mcp_agent/cli/commands/bootstrap.py +19 -38
  15. mcp_agent/cli/commands/config.py +4 -4
  16. mcp_agent/cli/commands/setup.py +7 -14
  17. mcp_agent/cli/main.py +7 -10
  18. mcp_agent/cli/terminal.py +3 -3
  19. mcp_agent/config.py +25 -40
  20. mcp_agent/context.py +12 -21
  21. mcp_agent/context_dependent.py +3 -5
  22. mcp_agent/core/agent_types.py +10 -7
  23. mcp_agent/core/direct_agent_app.py +179 -0
  24. mcp_agent/core/direct_decorators.py +443 -0
  25. mcp_agent/core/direct_factory.py +476 -0
  26. mcp_agent/core/enhanced_prompt.py +23 -55
  27. mcp_agent/core/exceptions.py +8 -8
  28. mcp_agent/core/fastagent.py +145 -371
  29. mcp_agent/core/interactive_prompt.py +424 -0
  30. mcp_agent/core/mcp_content.py +17 -17
  31. mcp_agent/core/prompt.py +6 -9
  32. mcp_agent/core/request_params.py +6 -3
  33. mcp_agent/core/validation.py +92 -18
  34. mcp_agent/executor/decorator_registry.py +9 -17
  35. mcp_agent/executor/executor.py +8 -17
  36. mcp_agent/executor/task_registry.py +2 -4
  37. mcp_agent/executor/temporal.py +19 -41
  38. mcp_agent/executor/workflow.py +3 -5
  39. mcp_agent/executor/workflow_signal.py +15 -21
  40. mcp_agent/human_input/handler.py +4 -7
  41. mcp_agent/human_input/types.py +2 -3
  42. mcp_agent/llm/__init__.py +2 -0
  43. mcp_agent/llm/augmented_llm.py +450 -0
  44. mcp_agent/llm/augmented_llm_passthrough.py +162 -0
  45. mcp_agent/llm/augmented_llm_playback.py +83 -0
  46. mcp_agent/llm/memory.py +103 -0
  47. mcp_agent/{workflows/llm → llm}/model_factory.py +22 -16
  48. mcp_agent/{workflows/llm → llm}/prompt_utils.py +1 -3
  49. mcp_agent/llm/providers/__init__.py +8 -0
  50. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +8 -25
  51. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +56 -194
  52. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  53. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +99 -190
  54. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +72 -71
  55. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +65 -71
  56. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +16 -44
  57. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +4 -4
  58. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +9 -11
  59. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +8 -12
  60. mcp_agent/{workflows/llm → llm}/sampling_converter.py +3 -31
  61. mcp_agent/llm/sampling_format_converter.py +37 -0
  62. mcp_agent/logging/events.py +1 -5
  63. mcp_agent/logging/json_serializer.py +7 -6
  64. mcp_agent/logging/listeners.py +20 -23
  65. mcp_agent/logging/logger.py +17 -19
  66. mcp_agent/logging/rich_progress.py +10 -8
  67. mcp_agent/logging/tracing.py +4 -6
  68. mcp_agent/logging/transport.py +22 -22
  69. mcp_agent/mcp/gen_client.py +1 -3
  70. mcp_agent/mcp/interfaces.py +117 -110
  71. mcp_agent/mcp/logger_textio.py +97 -0
  72. mcp_agent/mcp/mcp_agent_client_session.py +7 -7
  73. mcp_agent/mcp/mcp_agent_server.py +8 -8
  74. mcp_agent/mcp/mcp_aggregator.py +102 -143
  75. mcp_agent/mcp/mcp_connection_manager.py +20 -27
  76. mcp_agent/mcp/prompt_message_multipart.py +68 -16
  77. mcp_agent/mcp/prompt_render.py +77 -0
  78. mcp_agent/mcp/prompt_serialization.py +30 -48
  79. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  80. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  81. mcp_agent/mcp/prompts/prompt_load.py +109 -0
  82. mcp_agent/mcp/prompts/prompt_server.py +155 -195
  83. mcp_agent/mcp/prompts/prompt_template.py +35 -66
  84. mcp_agent/mcp/resource_utils.py +7 -14
  85. mcp_agent/mcp/sampling.py +17 -17
  86. mcp_agent/mcp_server/agent_server.py +13 -17
  87. mcp_agent/mcp_server_registry.py +13 -22
  88. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +3 -2
  89. mcp_agent/resources/examples/in_dev/slides.py +110 -0
  90. mcp_agent/resources/examples/internal/agent.py +6 -3
  91. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  92. mcp_agent/resources/examples/internal/job.py +2 -1
  93. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  94. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  95. mcp_agent/resources/examples/internal/sizer.py +2 -1
  96. mcp_agent/resources/examples/internal/social.py +2 -1
  97. mcp_agent/resources/examples/prompting/agent.py +2 -1
  98. mcp_agent/resources/examples/prompting/image_server.py +4 -8
  99. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  100. mcp_agent/ui/console_display.py +16 -20
  101. fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
  102. mcp_agent/core/agent_app.py +0 -646
  103. mcp_agent/core/agent_utils.py +0 -71
  104. mcp_agent/core/decorators.py +0 -455
  105. mcp_agent/core/factory.py +0 -463
  106. mcp_agent/core/proxies.py +0 -269
  107. mcp_agent/core/types.py +0 -24
  108. mcp_agent/eval/__init__.py +0 -0
  109. mcp_agent/mcp/stdio.py +0 -111
  110. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  111. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  112. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  113. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  114. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  115. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  116. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  117. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -190
  118. mcp_agent/resources/examples/researcher/researcher.py +0 -38
  119. mcp_agent/resources/examples/workflows/chaining.py +0 -44
  120. mcp_agent/resources/examples/workflows/evaluator.py +0 -78
  121. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  122. mcp_agent/resources/examples/workflows/human_input.py +0 -25
  123. mcp_agent/resources/examples/workflows/orchestrator.py +0 -73
  124. mcp_agent/resources/examples/workflows/parallel.py +0 -78
  125. mcp_agent/resources/examples/workflows/router.py +0 -53
  126. mcp_agent/resources/examples/workflows/sse.py +0 -23
  127. mcp_agent/telemetry/__init__.py +0 -0
  128. mcp_agent/telemetry/usage_tracking.py +0 -18
  129. mcp_agent/workflows/__init__.py +0 -0
  130. mcp_agent/workflows/embedding/__init__.py +0 -0
  131. mcp_agent/workflows/embedding/embedding_base.py +0 -61
  132. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  133. mcp_agent/workflows/embedding/embedding_openai.py +0 -46
  134. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  135. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -481
  136. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  137. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -120
  138. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -134
  139. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -45
  140. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -45
  141. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -161
  142. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  143. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -60
  144. mcp_agent/workflows/llm/__init__.py +0 -0
  145. mcp_agent/workflows/llm/augmented_llm.py +0 -753
  146. mcp_agent/workflows/llm/augmented_llm_passthrough.py +0 -241
  147. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -109
  148. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  149. mcp_agent/workflows/llm/sampling_format_converter.py +0 -22
  150. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  151. mcp_agent/workflows/orchestrator/orchestrator.py +0 -578
  152. mcp_agent/workflows/parallel/__init__.py +0 -0
  153. mcp_agent/workflows/parallel/fan_in.py +0 -350
  154. mcp_agent/workflows/parallel/fan_out.py +0 -187
  155. mcp_agent/workflows/parallel/parallel_llm.py +0 -166
  156. mcp_agent/workflows/router/__init__.py +0 -0
  157. mcp_agent/workflows/router/router_base.py +0 -368
  158. mcp_agent/workflows/router/router_embedding.py +0 -240
  159. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  160. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  161. mcp_agent/workflows/router/router_llm.py +0 -320
  162. mcp_agent/workflows/swarm/__init__.py +0 -0
  163. mcp_agent/workflows/swarm/swarm.py +0 -320
  164. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  165. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  166. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  167. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  168. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  169. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
@@ -0,0 +1,591 @@
1
+ """
2
+ OrchestratorAgent implementation using the BaseAgent adapter pattern.
3
+
4
+ This workflow provides an implementation that manages complex tasks by
5
+ dynamically planning, delegating to specialized agents, and synthesizing results.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Literal, Optional, Type
9
+
10
+ from mcp.types import TextContent
11
+
12
+ from mcp_agent.agents.agent import Agent
13
+ from mcp_agent.agents.base_agent import BaseAgent
14
+ from mcp_agent.agents.workflow.orchestrator_models import (
15
+ NextStep,
16
+ Plan,
17
+ PlanResult,
18
+ Step,
19
+ TaskWithResult,
20
+ format_plan_result,
21
+ format_step_result_text,
22
+ )
23
+ from mcp_agent.agents.workflow.orchestrator_prompts import (
24
+ FULL_PLAN_PROMPT_TEMPLATE,
25
+ ITERATIVE_PLAN_PROMPT_TEMPLATE,
26
+ SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE,
27
+ SYNTHESIZE_PLAN_PROMPT_TEMPLATE,
28
+ TASK_PROMPT_TEMPLATE,
29
+ )
30
+ from mcp_agent.core.agent_types import AgentConfig
31
+ from mcp_agent.core.exceptions import AgentConfigError
32
+ from mcp_agent.core.request_params import RequestParams
33
+ from mcp_agent.logging.logger import get_logger
34
+ from mcp_agent.mcp.interfaces import ModelT
35
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
36
+
37
+ logger = get_logger(__name__)
38
+
39
+
40
+ class OrchestratorAgent(BaseAgent):
41
+ """
42
+ An agent that implements the orchestrator workflow pattern.
43
+
44
+ Dynamically creates execution plans and delegates tasks
45
+ to specialized worker agents, synthesizing their results into a cohesive output.
46
+ Supports both full planning and iterative planning modes.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ config: AgentConfig,
52
+ agents: List[Agent],
53
+ plan_type: Literal["full", "iterative"] = "full",
54
+ context: Optional[Any] = None,
55
+ **kwargs,
56
+ ) -> None:
57
+ """
58
+ Initialize an OrchestratorAgent.
59
+
60
+ Args:
61
+ config: Agent configuration or name
62
+ agents: List of specialized worker agents available for task execution
63
+ plan_type: Planning mode ("full" or "iterative")
64
+ context: Optional context object
65
+ **kwargs: Additional keyword arguments to pass to BaseAgent
66
+ """
67
+ super().__init__(config, context=context, **kwargs)
68
+
69
+ if not agents:
70
+ raise AgentConfigError("At least one worker agent must be provided")
71
+
72
+ self.plan_type = plan_type
73
+
74
+ # Store agents by name for easier lookup
75
+ self.agents: Dict[str, Agent] = {}
76
+ for agent in agents:
77
+ agent_name = agent.name
78
+ self.logger.info(f"Adding agent '{agent_name}' to orchestrator")
79
+ self.agents[agent_name] = agent
80
+
81
+ # For tracking state during execution
82
+ self.plan_result: Optional[PlanResult] = None
83
+
84
+ async def generate(
85
+ self,
86
+ multipart_messages: List[PromptMessageMultipart],
87
+ request_params: Optional[RequestParams] = None,
88
+ ) -> PromptMessageMultipart:
89
+ """
90
+ Execute an orchestrated plan to process the input.
91
+
92
+ Args:
93
+ multipart_messages: Messages to process
94
+ request_params: Optional request parameters
95
+
96
+ Returns:
97
+ The final synthesized response from the orchestration
98
+ """
99
+ # Extract user request
100
+ objective = multipart_messages[-1].all_text() if multipart_messages else ""
101
+
102
+ # Initialize execution parameters
103
+ params = self._merge_request_params(request_params)
104
+
105
+ # Execute the plan
106
+ plan_result = await self._execute_plan(objective, params)
107
+ self.plan_result = plan_result
108
+
109
+ # Return the result
110
+ return PromptMessageMultipart(
111
+ role="assistant",
112
+ content=[TextContent(type="text", text=plan_result.result or "No result available")],
113
+ )
114
+
115
+ async def structured(
116
+ self,
117
+ prompt: List[PromptMessageMultipart],
118
+ model: Type[ModelT],
119
+ request_params: Optional[RequestParams] = None,
120
+ ) -> Optional[ModelT]:
121
+ """
122
+ Execute an orchestration plan and parse the result into a structured format.
123
+
124
+ Args:
125
+ prompt: List of messages to process
126
+ model: Pydantic model to parse the response into
127
+ request_params: Optional request parameters
128
+
129
+ Returns:
130
+ The parsed final response, or None if parsing fails
131
+ """
132
+ # Generate orchestration result
133
+ response = await self.generate(prompt, request_params)
134
+
135
+ # Try to parse the response into the specified model
136
+ try:
137
+ result_text = response.all_text()
138
+ prompt_message = PromptMessageMultipart(
139
+ role="user", content=[TextContent(type="text", text=result_text)]
140
+ )
141
+
142
+ # Use the LLM's structured parsing capability
143
+ return await self._llm.structured([prompt_message], model, request_params)
144
+ except Exception as e:
145
+ self.logger.warning(f"Failed to parse orchestration result: {str(e)}")
146
+ return None
147
+
148
+ async def initialize(self) -> None:
149
+ """Initialize the orchestrator agent and worker agents."""
150
+ await super().initialize()
151
+
152
+ # Initialize all worker agents if not already initialized
153
+ for agent_name, agent in self.agents.items():
154
+ if not getattr(agent, "initialized", False):
155
+ self.logger.debug(f"Initializing agent: {agent_name}")
156
+ await agent.initialize()
157
+
158
+ self.initialized = True
159
+
160
+ async def shutdown(self) -> None:
161
+ """Shutdown the orchestrator agent and worker agents."""
162
+ await super().shutdown()
163
+
164
+ # Shutdown all worker agents
165
+ for agent_name, agent in self.agents.items():
166
+ try:
167
+ await agent.shutdown()
168
+ except Exception as e:
169
+ self.logger.warning(f"Error shutting down agent {agent_name}: {str(e)}")
170
+
171
+ async def _execute_plan(self, objective: str, request_params: RequestParams) -> PlanResult:
172
+ """
173
+ Execute a plan to achieve the given objective.
174
+
175
+ Args:
176
+ objective: The objective to achieve
177
+ request_params: Request parameters for execution
178
+
179
+ Returns:
180
+ PlanResult containing execution results and final output
181
+ """
182
+ iterations = 0
183
+ total_steps_executed = 0
184
+ max_iterations = request_params.max_iterations
185
+ max_steps = getattr(request_params, "max_steps", max_iterations * 5)
186
+
187
+ # Initialize plan result
188
+ plan_result = PlanResult(objective=objective, step_results=[])
189
+ plan_result.max_iterations_reached = False
190
+
191
+ while iterations < max_iterations:
192
+ # Generate plan based on planning mode
193
+ if self.plan_type == "iterative":
194
+ next_step = await self._get_next_step(objective, plan_result, request_params)
195
+ if next_step is None:
196
+ self.logger.error("Failed to generate next step, ending iteration early")
197
+ plan_result.max_iterations_reached = True
198
+ break
199
+
200
+ logger.debug(f"Iteration {iterations}: Iterative plan:", data=next_step)
201
+ plan = Plan(steps=[next_step], is_complete=next_step.is_complete)
202
+ elif self.plan_type == "full":
203
+ plan = await self._get_full_plan(objective, plan_result, request_params)
204
+ if plan is None:
205
+ self.logger.error("Failed to generate full plan, ending iteration early")
206
+ plan_result.max_iterations_reached = True
207
+ break
208
+
209
+ logger.debug(f"Iteration {iterations}: Full Plan:", data=plan)
210
+ else:
211
+ raise ValueError(f"Invalid plan type: {self.plan_type}")
212
+
213
+ # Validate agent names early
214
+ self._validate_agent_names(plan)
215
+
216
+ # Store plan in result
217
+ plan_result.plan = plan
218
+
219
+ # Execute the steps in the plan
220
+ for step in plan.steps:
221
+ # Check if we've hit the step limit
222
+ if total_steps_executed >= max_steps:
223
+ self.logger.warning(
224
+ f"Reached maximum step limit ({max_steps}) without completing objective"
225
+ )
226
+ plan_result.max_steps_reached = True
227
+ break
228
+
229
+ # Execute the step and collect results
230
+ step_result = await self._execute_step(step, plan_result, request_params)
231
+
232
+ plan_result.add_step_result(step_result)
233
+ total_steps_executed += 1
234
+
235
+ # Check if we need to break due to hitting max steps
236
+ if getattr(plan_result, "max_steps_reached", False):
237
+ break
238
+
239
+ # If the plan is marked complete, finalize the result
240
+ if plan.is_complete:
241
+ plan_result.is_complete = True
242
+ break
243
+
244
+ # Increment iteration counter
245
+ iterations += 1
246
+
247
+ # Generate final result based on execution status
248
+ if iterations >= max_iterations and not plan_result.is_complete:
249
+ self.logger.warning(f"Failed to complete in {max_iterations} iterations")
250
+ plan_result.max_iterations_reached = True
251
+
252
+ # Use incomplete plan template
253
+ synthesis_prompt = SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE.format(
254
+ plan_result=format_plan_result(plan_result), max_iterations=max_iterations
255
+ )
256
+ else:
257
+ # Either plan is complete or we had other limits
258
+ if not plan_result.is_complete:
259
+ plan_result.is_complete = True
260
+
261
+ # Use standard template
262
+ synthesis_prompt = SYNTHESIZE_PLAN_PROMPT_TEMPLATE.format(
263
+ plan_result=format_plan_result(plan_result)
264
+ )
265
+
266
+ # Generate final synthesis
267
+ plan_result.result = await self._planner_generate_str(
268
+ synthesis_prompt, request_params.model_copy(update={"max_iterations": 1})
269
+ )
270
+
271
+ return plan_result
272
+
273
+ async def _execute_step(
274
+ self, step: Step, previous_result: PlanResult, request_params: RequestParams
275
+ ) -> Any:
276
+ """
277
+ Execute a single step from the plan.
278
+
279
+ Args:
280
+ step: The step to execute
281
+ previous_result: Results of the plan execution so far
282
+ request_params: Request parameters
283
+
284
+ Returns:
285
+ Result of executing the step
286
+ """
287
+ from mcp_agent.agents.workflow.orchestrator_models import StepResult
288
+
289
+ # Initialize step result
290
+ step_result = StepResult(step=step, task_results=[])
291
+
292
+ # Format context for tasks
293
+ context = format_plan_result(previous_result)
294
+
295
+ # Execute all tasks in parallel
296
+ futures = []
297
+ error_tasks = []
298
+
299
+ for task in step.tasks:
300
+ # Check agent exists
301
+ agent = self.agents.get(task.agent)
302
+ if not agent:
303
+ self.logger.error(
304
+ f"No agent found matching '{task.agent}'. Available agents: {list(self.agents.keys())}"
305
+ )
306
+ error_tasks.append(
307
+ (
308
+ task,
309
+ f"Error: Agent '{task.agent}' not found. Available agents: {', '.join(self.agents.keys())}",
310
+ )
311
+ )
312
+ continue
313
+
314
+ # Prepare task prompt
315
+ task_description = TASK_PROMPT_TEMPLATE.format(
316
+ objective=previous_result.objective, task=task.description, context=context
317
+ )
318
+
319
+ # Queue task for execution
320
+ futures.append(
321
+ (
322
+ task,
323
+ agent.generate(
324
+ [
325
+ PromptMessageMultipart(
326
+ role="user",
327
+ content=[TextContent(type="text", text=task_description)],
328
+ )
329
+ ]
330
+ ),
331
+ )
332
+ )
333
+
334
+ # Wait for all tasks
335
+ task_results = []
336
+ for future in futures:
337
+ task, future_obj = future
338
+ try:
339
+ result = await future_obj
340
+ result_text = result.all_text()
341
+
342
+ # Create task result
343
+ task_model = task.model_dump()
344
+ task_result = TaskWithResult(
345
+ description=task_model["description"],
346
+ agent=task_model["agent"],
347
+ result=result_text,
348
+ )
349
+ task_results.append(task_result)
350
+ except Exception as e:
351
+ self.logger.error(f"Error executing task: {str(e)}")
352
+ # Add error result
353
+ task_model = task.model_dump()
354
+ task_results.append(
355
+ TaskWithResult(
356
+ description=task_model["description"],
357
+ agent=task_model["agent"],
358
+ result=f"ERROR: {str(e)}",
359
+ )
360
+ )
361
+
362
+ # Add all task results to step result
363
+ for task_result in task_results:
364
+ step_result.add_task_result(task_result)
365
+
366
+ # Add error task results
367
+ for task, error_message in error_tasks:
368
+ task_model = task.model_dump()
369
+ step_result.add_task_result(
370
+ TaskWithResult(
371
+ description=task_model["description"],
372
+ agent=task_model["agent"],
373
+ result=f"ERROR: {error_message}",
374
+ )
375
+ )
376
+
377
+ # Format step result
378
+ step_result.result = format_step_result_text(step_result)
379
+ return step_result
380
+
381
+ async def _get_full_plan(
382
+ self, objective: str, plan_result: PlanResult, request_params: RequestParams
383
+ ) -> Optional[Plan]:
384
+ """
385
+ Generate a full plan with all steps.
386
+
387
+ Args:
388
+ objective: The objective to achieve
389
+ plan_result: Current plan execution state
390
+ request_params: Request parameters
391
+
392
+ Returns:
393
+ Complete Plan with all steps, or None if parsing fails
394
+ """
395
+ # Format agent information for the prompt
396
+ agent_formats = []
397
+ for agent_name in self.agents.keys():
398
+ formatted = self._format_agent_info(agent_name)
399
+ agent_formats.append(formatted)
400
+
401
+ agents = "\n".join(agent_formats)
402
+
403
+ # Determine plan status
404
+ if plan_result.is_complete:
405
+ plan_status = "Plan Status: Complete"
406
+ elif plan_result.step_results:
407
+ plan_status = "Plan Status: In Progress"
408
+ else:
409
+ plan_status = "Plan Status: Not Started"
410
+
411
+ # Calculate iteration information
412
+ max_iterations = request_params.max_iterations
413
+ current_iteration = len(plan_result.step_results)
414
+ current_iteration = min(current_iteration, max_iterations - 1)
415
+ iterations_remaining = max(0, max_iterations - current_iteration - 1)
416
+ iterations_info = f"Planning Budget: Iteration {current_iteration + 1} of {max_iterations} (with {iterations_remaining} remaining)"
417
+
418
+ # Format the planning prompt
419
+ prompt = FULL_PLAN_PROMPT_TEMPLATE.format(
420
+ objective=objective,
421
+ plan_result=format_plan_result(plan_result),
422
+ plan_status=plan_status,
423
+ iterations_info=iterations_info,
424
+ agents=agents,
425
+ )
426
+
427
+ # Get structured response from LLM
428
+ try:
429
+ plan_msg = PromptMessageMultipart(
430
+ role="user", content=[TextContent(type="text", text=prompt)]
431
+ )
432
+ return await self._llm.structured([plan_msg], Plan, request_params)
433
+ except Exception as e:
434
+ self.logger.error(f"Failed to parse plan: {str(e)}")
435
+ return None
436
+
437
+ async def _get_next_step(
438
+ self, objective: str, plan_result: PlanResult, request_params: RequestParams
439
+ ) -> Optional[NextStep]:
440
+ """
441
+ Generate just the next step for iterative planning.
442
+
443
+ Args:
444
+ objective: The objective to achieve
445
+ plan_result: Current plan execution state
446
+ request_params: Request parameters
447
+
448
+ Returns:
449
+ Next step to execute, or None if parsing fails
450
+ """
451
+ # Format agent information
452
+ agents = "\n".join(
453
+ [self._format_agent_info(agent_name) for agent_name in self.agents.keys()]
454
+ )
455
+
456
+ # Determine plan status
457
+ if plan_result.is_complete:
458
+ plan_status = "Plan Status: Complete"
459
+ elif plan_result.step_results:
460
+ plan_status = "Plan Status: In Progress"
461
+ else:
462
+ plan_status = "Plan Status: Not Started"
463
+
464
+ # Calculate iteration information
465
+ max_iterations = request_params.max_iterations
466
+ current_iteration = len(plan_result.step_results)
467
+ iterations_remaining = max_iterations - current_iteration
468
+ iterations_info = (
469
+ f"Planning Budget: {iterations_remaining} of {max_iterations} iterations remaining"
470
+ )
471
+
472
+ # Format the planning prompt
473
+ prompt = ITERATIVE_PLAN_PROMPT_TEMPLATE.format(
474
+ objective=objective,
475
+ plan_result=format_plan_result(plan_result),
476
+ plan_status=plan_status,
477
+ iterations_info=iterations_info,
478
+ agents=agents,
479
+ )
480
+
481
+ # Get structured response from LLM
482
+ try:
483
+ plan_msg = PromptMessageMultipart(
484
+ role="user", content=[TextContent(type="text", text=prompt)]
485
+ )
486
+ return await self._llm.structured([plan_msg], NextStep, request_params)
487
+ except Exception as e:
488
+ self.logger.error(f"Failed to parse next step: {str(e)}")
489
+ return None
490
+
491
+ def _validate_agent_names(self, plan: Plan) -> None:
492
+ """
493
+ Validate all agent names in a plan before execution.
494
+
495
+ Args:
496
+ plan: The plan to validate
497
+ """
498
+ if plan is None:
499
+ self.logger.error("Cannot validate agent names: plan is None")
500
+ return
501
+
502
+ invalid_agents = []
503
+
504
+ for step in plan.steps:
505
+ for task in step.tasks:
506
+ if task.agent not in self.agents:
507
+ invalid_agents.append(task.agent)
508
+
509
+ if invalid_agents:
510
+ available_agents = ", ".join(self.agents.keys())
511
+ invalid_list = ", ".join(invalid_agents)
512
+ self.logger.error(
513
+ f"Plan contains invalid agent names: {invalid_list}. Available agents: {available_agents}"
514
+ )
515
+
516
+ def _format_agent_info(self, agent_name: str) -> str:
517
+ """
518
+ Format agent information for display in prompts.
519
+
520
+ Args:
521
+ agent_name: Name of the agent to format
522
+
523
+ Returns:
524
+ Formatted agent information string
525
+ """
526
+ agent = self.agents.get(agent_name)
527
+ if not agent:
528
+ self.logger.error(f"Agent '{agent_name}' not found in orchestrator agents")
529
+ return ""
530
+
531
+ # Get agent instruction or default description
532
+ instruction = (
533
+ agent.instruction if hasattr(agent, "instruction") else f"Agent '{agent_name}'"
534
+ )
535
+
536
+ # Format with XML tags
537
+ return f'<fastagent:agent name="{agent_name}">{instruction}</fastagent:agent>'
538
+
539
+ async def _planner_generate_str(self, message: str, request_params: RequestParams) -> str:
540
+ """
541
+ Generate string response from the orchestrator's own LLM.
542
+
543
+ Args:
544
+ message: Message to send to the LLM
545
+ request_params: Request parameters
546
+
547
+ Returns:
548
+ String response from the LLM
549
+ """
550
+ # Create prompt message
551
+ prompt = PromptMessageMultipart(
552
+ role="user", content=[TextContent(type="text", text=message)]
553
+ )
554
+
555
+ # Get response from LLM
556
+ response = await self._llm.generate([prompt], request_params)
557
+ return response.all_text()
558
+
559
+ def _merge_request_params(self, request_params: Optional[RequestParams]) -> RequestParams:
560
+ """
561
+ Merge provided request parameters with defaults.
562
+
563
+ Args:
564
+ request_params: Optional request parameters to merge
565
+
566
+ Returns:
567
+ Merged request parameters
568
+ """
569
+ # Create orchestrator-specific defaults
570
+ defaults = RequestParams(
571
+ use_history=False, # Orchestrator doesn't use history
572
+ max_iterations=5, # Default to 5 iterations
573
+ maxTokens=8192, # Higher limit for planning
574
+ parallel_tool_calls=True,
575
+ )
576
+
577
+ # If base params provided, merge with defaults
578
+ if request_params:
579
+ # Create copy of defaults
580
+ params = defaults.model_copy()
581
+ # Update with provided params
582
+ if isinstance(request_params, dict):
583
+ params = params.model_copy(update=request_params)
584
+ else:
585
+ params = params.model_copy(update=request_params.model_dump())
586
+
587
+ # Force specific settings
588
+ params.use_history = False
589
+ return params
590
+
591
+ return defaults
@@ -2,7 +2,7 @@ from typing import List
2
2
 
3
3
  from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
- from mcp_agent.workflows.orchestrator.orchestrator_prompts import (
5
+ from mcp_agent.agents.workflow.orchestrator_prompts import (
6
6
  PLAN_RESULT_TEMPLATE,
7
7
  STEP_RESULT_TEMPLATE,
8
8
  TASK_RESULT_TEMPLATE,
@@ -50,21 +50,15 @@ class Plan(BaseModel):
50
50
  description="List of steps to execute sequentially",
51
51
  default_factory=list,
52
52
  )
53
- is_complete: bool = Field(
54
- description="Whether the overall plan objective is complete"
55
- )
53
+ is_complete: bool = Field(description="Whether the overall plan objective is complete")
56
54
 
57
55
 
58
56
  class TaskWithResult(Task):
59
57
  """An individual task with its result"""
60
58
 
61
- result: str = Field(
62
- description="Result of executing the task", default="Task completed"
63
- )
59
+ result: str = Field(description="Result of executing the task", default="Task completed")
64
60
 
65
- agent: str = Field(
66
- description="Name of the agent that executed this task", default=""
67
- )
61
+ agent: str = Field(description="Name of the agent that executed this task", default="")
68
62
 
69
63
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
70
64
 
@@ -76,11 +70,9 @@ class StepResult(BaseModel):
76
70
  task_results: List[TaskWithResult] = Field(
77
71
  description="Results of executing each task", default_factory=list
78
72
  )
79
- result: str = Field(
80
- description="Result of executing the step", default="Step completed"
81
- )
73
+ result: str = Field(description="Result of executing the step", default="Step completed")
82
74
 
83
- def add_task_result(self, task_result: TaskWithResult):
75
+ def add_task_result(self, task_result: TaskWithResult) -> None:
84
76
  """Add a task result to this step"""
85
77
  if not isinstance(self.task_results, list):
86
78
  self.task_results = []
@@ -108,7 +100,7 @@ class PlanResult(BaseModel):
108
100
  result: str | None = None
109
101
  """Result of executing the plan"""
110
102
 
111
- def add_step_result(self, step_result: StepResult):
103
+ def add_step_result(self, step_result: StepResult) -> None:
112
104
  """Add a step result to this plan"""
113
105
  if not isinstance(self.step_results, list):
114
106
  self.step_results = []
@@ -118,9 +110,7 @@ class PlanResult(BaseModel):
118
110
  class NextStep(Step):
119
111
  """Single next step in iterative planning"""
120
112
 
121
- is_complete: bool = Field(
122
- description="Whether the overall plan objective is complete"
123
- )
113
+ is_complete: bool = Field(description="Whether the overall plan objective is complete")
124
114
 
125
115
 
126
116
  def format_task_result_text(task_result: TaskWithResult) -> str:
@@ -162,7 +152,7 @@ def format_plan_result_text(plan_result: PlanResult) -> str:
162
152
 
163
153
  def format_task_result_xml(task_result: TaskWithResult) -> str:
164
154
  """Format a task result with XML tags for better semantic understanding"""
165
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
155
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
166
156
 
167
157
  return format_fastagent_tag(
168
158
  "task-result",
@@ -178,7 +168,7 @@ def format_task_result_xml(task_result: TaskWithResult) -> str:
178
168
 
179
169
  def format_step_result_xml(step_result: StepResult) -> str:
180
170
  """Format a step result with XML tags for better semantic understanding"""
181
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
171
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
182
172
 
183
173
  # Format each task result with XML
184
174
  task_results = []
@@ -200,7 +190,7 @@ def format_step_result_xml(step_result: StepResult) -> str:
200
190
 
201
191
  def format_plan_result(plan_result: PlanResult) -> str:
202
192
  """Format the full plan execution state with XML for better semantic understanding"""
203
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
193
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
204
194
 
205
195
  # Format objective
206
196
  objective_tag = format_fastagent_tag("objective", plan_result.objective)