fast-agent-mcp 0.1.13__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 (147) hide show
  1. {fast_agent_mcp-0.1.13.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 +59 -371
  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 +27 -11
  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 +3 -1
  14. mcp_agent/cli/commands/bootstrap.py +18 -7
  15. mcp_agent/cli/commands/setup.py +12 -4
  16. mcp_agent/cli/main.py +1 -1
  17. mcp_agent/cli/terminal.py +1 -1
  18. mcp_agent/config.py +24 -35
  19. mcp_agent/context.py +3 -1
  20. mcp_agent/context_dependent.py +3 -1
  21. mcp_agent/core/agent_types.py +10 -7
  22. mcp_agent/core/direct_agent_app.py +179 -0
  23. mcp_agent/core/direct_decorators.py +443 -0
  24. mcp_agent/core/direct_factory.py +476 -0
  25. mcp_agent/core/enhanced_prompt.py +15 -20
  26. mcp_agent/core/fastagent.py +151 -337
  27. mcp_agent/core/interactive_prompt.py +424 -0
  28. mcp_agent/core/mcp_content.py +19 -11
  29. mcp_agent/core/prompt.py +6 -2
  30. mcp_agent/core/validation.py +89 -16
  31. mcp_agent/executor/decorator_registry.py +6 -2
  32. mcp_agent/executor/temporal.py +35 -11
  33. mcp_agent/executor/workflow_signal.py +8 -2
  34. mcp_agent/human_input/handler.py +3 -1
  35. mcp_agent/llm/__init__.py +2 -0
  36. mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
  37. mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
  38. mcp_agent/llm/augmented_llm_playback.py +83 -0
  39. mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
  40. mcp_agent/llm/providers/__init__.py +8 -0
  41. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
  42. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
  43. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  44. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
  45. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
  46. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
  47. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
  48. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
  49. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
  50. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
  51. mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
  52. mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
  53. mcp_agent/logging/logger.py +2 -2
  54. mcp_agent/mcp/gen_client.py +9 -3
  55. mcp_agent/mcp/interfaces.py +67 -45
  56. mcp_agent/mcp/logger_textio.py +97 -0
  57. mcp_agent/mcp/mcp_agent_client_session.py +12 -4
  58. mcp_agent/mcp/mcp_agent_server.py +3 -1
  59. mcp_agent/mcp/mcp_aggregator.py +124 -93
  60. mcp_agent/mcp/mcp_connection_manager.py +21 -7
  61. mcp_agent/mcp/prompt_message_multipart.py +59 -1
  62. mcp_agent/mcp/prompt_render.py +77 -0
  63. mcp_agent/mcp/prompt_serialization.py +20 -13
  64. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  65. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  66. mcp_agent/mcp/prompts/prompt_load.py +15 -5
  67. mcp_agent/mcp/prompts/prompt_server.py +154 -87
  68. mcp_agent/mcp/prompts/prompt_template.py +26 -35
  69. mcp_agent/mcp/resource_utils.py +3 -1
  70. mcp_agent/mcp/sampling.py +24 -15
  71. mcp_agent/mcp_server/agent_server.py +8 -5
  72. mcp_agent/mcp_server_registry.py +22 -9
  73. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
  74. mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
  75. mcp_agent/resources/examples/internal/agent.py +4 -2
  76. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  77. mcp_agent/resources/examples/prompting/image_server.py +3 -1
  78. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  79. mcp_agent/ui/console_display.py +27 -7
  80. fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
  81. mcp_agent/core/agent_app.py +0 -570
  82. mcp_agent/core/agent_utils.py +0 -69
  83. mcp_agent/core/decorators.py +0 -448
  84. mcp_agent/core/factory.py +0 -422
  85. mcp_agent/core/proxies.py +0 -278
  86. mcp_agent/core/types.py +0 -22
  87. mcp_agent/eval/__init__.py +0 -0
  88. mcp_agent/mcp/stdio.py +0 -114
  89. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  90. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  91. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  92. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  93. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  94. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  95. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  96. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -189
  97. mcp_agent/resources/examples/researcher/researcher.py +0 -39
  98. mcp_agent/resources/examples/workflows/chaining.py +0 -45
  99. mcp_agent/resources/examples/workflows/evaluator.py +0 -79
  100. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  101. mcp_agent/resources/examples/workflows/human_input.py +0 -26
  102. mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
  103. mcp_agent/resources/examples/workflows/parallel.py +0 -79
  104. mcp_agent/resources/examples/workflows/router.py +0 -54
  105. mcp_agent/resources/examples/workflows/sse.py +0 -23
  106. mcp_agent/telemetry/__init__.py +0 -0
  107. mcp_agent/telemetry/usage_tracking.py +0 -19
  108. mcp_agent/workflows/__init__.py +0 -0
  109. mcp_agent/workflows/embedding/__init__.py +0 -0
  110. mcp_agent/workflows/embedding/embedding_base.py +0 -58
  111. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  112. mcp_agent/workflows/embedding/embedding_openai.py +0 -37
  113. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  114. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
  115. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  116. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
  117. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
  118. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
  119. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
  120. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
  121. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  122. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
  123. mcp_agent/workflows/llm/__init__.py +0 -0
  124. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
  125. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  126. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  127. mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
  128. mcp_agent/workflows/parallel/__init__.py +0 -0
  129. mcp_agent/workflows/parallel/fan_in.py +0 -320
  130. mcp_agent/workflows/parallel/fan_out.py +0 -181
  131. mcp_agent/workflows/parallel/parallel_llm.py +0 -149
  132. mcp_agent/workflows/router/__init__.py +0 -0
  133. mcp_agent/workflows/router/router_base.py +0 -338
  134. mcp_agent/workflows/router/router_embedding.py +0 -226
  135. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  136. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  137. mcp_agent/workflows/router/router_llm.py +0 -304
  138. mcp_agent/workflows/swarm/__init__.py +0 -0
  139. mcp_agent/workflows/swarm/swarm.py +0 -292
  140. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  141. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  142. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  143. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  144. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  145. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
  146. /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
  147. /mcp_agent/{workflows/llm → llm}/prompt_utils.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,
@@ -67,7 +67,9 @@ class StepResult(BaseModel):
67
67
  """Result of executing a step"""
68
68
 
69
69
  step: Step = Field(description="The step that was executed", default_factory=Step)
70
- task_results: List[TaskWithResult] = Field(description="Results of executing each task", default_factory=list)
70
+ task_results: List[TaskWithResult] = Field(
71
+ description="Results of executing each task", default_factory=list
72
+ )
71
73
  result: str = Field(description="Result of executing the step", default="Step completed")
72
74
 
73
75
  def add_task_result(self, task_result: TaskWithResult) -> None:
@@ -113,12 +115,16 @@ class NextStep(Step):
113
115
 
114
116
  def format_task_result_text(task_result: TaskWithResult) -> str:
115
117
  """Format a task result as plain text for display"""
116
- return TASK_RESULT_TEMPLATE.format(task_description=task_result.description, task_result=task_result.result)
118
+ return TASK_RESULT_TEMPLATE.format(
119
+ task_description=task_result.description, task_result=task_result.result
120
+ )
117
121
 
118
122
 
119
123
  def format_step_result_text(step_result: StepResult) -> str:
120
124
  """Format a step result as plain text for display"""
121
- tasks_str = "\n".join(f" - {format_task_result_text(task)}" for task in step_result.task_results)
125
+ tasks_str = "\n".join(
126
+ f" - {format_task_result_text(task)}" for task in step_result.task_results
127
+ )
122
128
  return STEP_RESULT_TEMPLATE.format(
123
129
  step_description=step_result.step.description,
124
130
  step_result=step_result.result,
@@ -129,7 +135,10 @@ def format_step_result_text(step_result: StepResult) -> str:
129
135
  def format_plan_result_text(plan_result: PlanResult) -> str:
130
136
  """Format the full plan execution state as plain text for display"""
131
137
  steps_str = (
132
- "\n\n".join(f"{i + 1}:\n{format_step_result_text(step)}" for i, step in enumerate(plan_result.step_results))
138
+ "\n\n".join(
139
+ f"{i + 1}:\n{format_step_result_text(step)}"
140
+ for i, step in enumerate(plan_result.step_results)
141
+ )
133
142
  if plan_result.step_results
134
143
  else "No steps executed yet"
135
144
  )
@@ -143,18 +152,23 @@ def format_plan_result_text(plan_result: PlanResult) -> str:
143
152
 
144
153
  def format_task_result_xml(task_result: TaskWithResult) -> str:
145
154
  """Format a task result with XML tags for better semantic understanding"""
146
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
155
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
147
156
 
148
157
  return format_fastagent_tag(
149
158
  "task-result",
150
- f"\n<fastagent:description>{task_result.description}</fastagent:description>\n" f"<fastagent:result>{task_result.result}</fastagent:result>\n",
151
- {"description": task_result.description[:50] + "..." if len(task_result.description) > 50 else task_result.description},
159
+ f"\n<fastagent:description>{task_result.description}</fastagent:description>\n"
160
+ f"<fastagent:result>{task_result.result}</fastagent:result>\n",
161
+ {
162
+ "description": task_result.description[:50] + "..."
163
+ if len(task_result.description) > 50
164
+ else task_result.description
165
+ },
152
166
  )
153
167
 
154
168
 
155
169
  def format_step_result_xml(step_result: StepResult) -> str:
156
170
  """Format a step result with XML tags for better semantic understanding"""
157
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
171
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
158
172
 
159
173
  # Format each task result with XML
160
174
  task_results = []
@@ -176,7 +190,7 @@ def format_step_result_xml(step_result: StepResult) -> str:
176
190
 
177
191
  def format_plan_result(plan_result: PlanResult) -> str:
178
192
  """Format the full plan execution state with XML for better semantic understanding"""
179
- from mcp_agent.workflows.llm.prompt_utils import format_fastagent_tag
193
+ from mcp_agent.llm.prompt_utils import format_fastagent_tag
180
194
 
181
195
  # Format objective
182
196
  objective_tag = format_fastagent_tag("objective", plan_result.objective)
@@ -197,7 +211,9 @@ def format_plan_result(plan_result: PlanResult) -> str:
197
211
  else:
198
212
  # No steps executed yet
199
213
  progress_content = (
200
- f"{objective_tag}\n" f"<fastagent:steps>No steps executed yet</fastagent:steps>\n" f"<fastagent:status>Not Started</fastagent:status>\n"
214
+ f"{objective_tag}\n"
215
+ f"<fastagent:steps>No steps executed yet</fastagent:steps>\n"
216
+ f"<fastagent:status>Not Started</fastagent:status>\n"
201
217
  )
202
218
 
203
219
  return format_fastagent_tag("progress", progress_content)