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
@@ -1,578 +0,0 @@
1
- """
2
- Orchestrator implementation for MCP Agent applications.
3
- """
4
-
5
- from typing import (
6
- List,
7
- Literal,
8
- Optional,
9
- TYPE_CHECKING,
10
- Type,
11
- )
12
-
13
- from mcp_agent.agents.agent import Agent
14
- from mcp_agent.event_progress import ProgressAction
15
- from mcp_agent.workflows.llm.augmented_llm import (
16
- AugmentedLLM,
17
- MessageParamT,
18
- MessageT,
19
- ModelT,
20
- RequestParams,
21
- )
22
- from mcp_agent.workflows.orchestrator.orchestrator_models import (
23
- format_plan_result,
24
- format_step_result_text,
25
- NextStep,
26
- Plan,
27
- PlanResult,
28
- Step,
29
- StepResult,
30
- TaskWithResult,
31
- )
32
- from mcp_agent.workflows.orchestrator.orchestrator_prompts import (
33
- FULL_PLAN_PROMPT_TEMPLATE,
34
- ITERATIVE_PLAN_PROMPT_TEMPLATE,
35
- SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE, # Add the missing import
36
- SYNTHESIZE_PLAN_PROMPT_TEMPLATE,
37
- TASK_PROMPT_TEMPLATE,
38
- )
39
- from mcp_agent.logging.logger import get_logger
40
-
41
- if TYPE_CHECKING:
42
- from mcp_agent.context import Context
43
-
44
- logger = get_logger(__name__)
45
-
46
-
47
- class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
48
- """
49
- In the orchestrator-workers workflow, a central planner LLM dynamically breaks down tasks and
50
- delegates them to pre-configured worker LLMs. The planner synthesizes their results in a loop
51
- until the task is complete.
52
-
53
- When to use this workflow:
54
- - This workflow is well-suited for complex tasks where you can't predict the
55
- subtasks needed (in coding, for example, the number of files that need to be
56
- changed and the nature of the change in each file likely depend on the task).
57
-
58
- Example where orchestrator-workers is useful:
59
- - Coding products that make complex changes to multiple files each time.
60
- - Search tasks that involve gathering and analyzing information from multiple sources
61
- for possible relevant information.
62
-
63
- Note:
64
- All agents must be pre-configured with LLMs before being passed to the orchestrator.
65
- This ensures consistent model behavior and configuration across all components.
66
- """
67
-
68
- def __init__(
69
- self,
70
- name: str,
71
- planner: AugmentedLLM, # Pre-configured planner
72
- available_agents: List[Agent | AugmentedLLM],
73
- plan_type: Literal["full", "iterative"] = "full",
74
- context: Optional["Context"] = None,
75
- **kwargs,
76
- ):
77
- """
78
- Args:
79
- name: Name of the orchestrator workflow
80
- planner: Pre-configured planner LLM to use for planning steps
81
- available_agents: List of pre-configured agents available to this orchestrator
82
- plan_type: "full" planning generates the full plan first, then executes. "iterative" plans next step and loops.
83
- context: Application context
84
- """
85
- # Initialize logger early so we can log
86
- self.logger = logger
87
-
88
- # Set a fixed verb - always use PLANNING for all orchestrator activities
89
- self.verb = ProgressAction.PLANNING
90
-
91
- # Initialize with orchestrator-specific defaults
92
- orchestrator_params = RequestParams(
93
- use_history=False, # Orchestrator doesn't support history
94
- max_iterations=5, # Reduced from 10 to prevent excessive iterations
95
- maxTokens=8192, # Higher default for planning
96
- parallel_tool_calls=True,
97
- )
98
-
99
- # If kwargs contains request_params, merge our defaults while preserving the model config
100
- if "request_params" in kwargs:
101
- base_params = kwargs["request_params"]
102
- # Create merged params starting with our defaults
103
- merged = orchestrator_params.model_copy()
104
- # Update with base params to get model config
105
- if isinstance(base_params, dict):
106
- merged = merged.model_copy(update=base_params)
107
- else:
108
- merged = merged.model_copy(update=base_params.model_dump())
109
- # Force specific settings
110
- merged.use_history = False
111
- kwargs["request_params"] = merged
112
- else:
113
- kwargs["request_params"] = orchestrator_params
114
-
115
- # Pass verb to AugmentedLLM
116
- kwargs["verb"] = self.verb
117
-
118
- super().__init__(context=context, **kwargs)
119
-
120
- self.planner = planner
121
-
122
- if hasattr(self.planner, "verb"):
123
- self.planner.verb = self.verb
124
-
125
- self.plan_type = plan_type
126
- self.server_registry = self.context.server_registry
127
- self.agents = {agent.name: agent for agent in available_agents}
128
-
129
- # Initialize logger
130
- self.name = name
131
-
132
- # Store agents by name - COMPLETE REWRITE OF AGENT STORAGE
133
- self.agents = {}
134
- for agent in available_agents:
135
- agent_name = agent.name
136
- self.logger.info(f"Adding agent '{agent_name}' to orchestrator")
137
- self.agents[agent_name] = agent
138
-
139
- async def generate(
140
- self,
141
- message: str | MessageParamT | List[MessageParamT],
142
- request_params: RequestParams | None = None,
143
- ) -> List[MessageT]:
144
- """Request an LLM generation, which may run multiple iterations, and return the result"""
145
- params = self.get_request_params(request_params)
146
- objective = str(message)
147
- plan_result = await self.execute(objective=objective, request_params=params)
148
-
149
- return [plan_result.result]
150
-
151
- async def generate_str(
152
- self,
153
- message: str | MessageParamT | List[MessageParamT],
154
- request_params: RequestParams | None = None,
155
- ) -> str:
156
- """Request an LLM generation and return the string representation of the result"""
157
- params = self.get_request_params(request_params)
158
-
159
- result = await self.generate(
160
- message=message,
161
- request_params=params,
162
- )
163
-
164
- return str(result[0])
165
-
166
- async def generate_structured(
167
- self,
168
- message: str | MessageParamT | List[MessageParamT],
169
- response_model: Type[ModelT],
170
- request_params: RequestParams | None = None,
171
- ) -> ModelT:
172
- return None
173
-
174
- async def execute(
175
- self, objective: str, request_params: RequestParams | None = None
176
- ) -> PlanResult:
177
- """Execute task with result chaining between steps"""
178
- iterations = 0
179
- total_steps_executed = 0
180
-
181
- params = self.get_request_params(request_params)
182
- max_steps = getattr(
183
- params, "max_steps", params.max_iterations * 5
184
- ) # Default to 5× max_iterations
185
-
186
- # Single progress event for orchestration start
187
- model = await self.select_model(params) or "unknown-model"
188
-
189
- # Log the progress with minimal required fields
190
- self.logger.info(
191
- "Planning task execution",
192
- data={
193
- "progress_action": self.verb,
194
- "model": model,
195
- "agent_name": self.name,
196
- "target": self.name,
197
- },
198
- )
199
-
200
- plan_result = PlanResult(objective=objective, step_results=[])
201
- plan_result.max_iterations_reached = (
202
- False # Add a flag to track if we hit the limit
203
- )
204
-
205
- while iterations < params.max_iterations:
206
- if self.plan_type == "iterative":
207
- # Get next plan/step
208
- next_step = await self._get_next_step(
209
- objective=objective, plan_result=plan_result, request_params=params
210
- )
211
- logger.debug(f"Iteration {iterations}: Iterative plan:", data=next_step)
212
- plan = Plan(steps=[next_step], is_complete=next_step.is_complete)
213
- # Validate agent names in the plan early
214
- self._validate_agent_names(plan)
215
- elif self.plan_type == "full":
216
- plan = await self._get_full_plan(
217
- objective=objective, plan_result=plan_result, request_params=params
218
- )
219
- logger.debug(f"Iteration {iterations}: Full Plan:", data=plan)
220
- # Validate agent names in the plan early
221
- self._validate_agent_names(plan)
222
- else:
223
- raise ValueError(f"Invalid plan type {self.plan_type}")
224
-
225
- plan_result.plan = plan
226
-
227
- if plan.is_complete:
228
- # Modified: Remove the requirement for steps to be executed
229
- plan_result.is_complete = True
230
-
231
- # Synthesize final result into a single message
232
- # Use the structured XML format for better context
233
- synthesis_prompt = SYNTHESIZE_PLAN_PROMPT_TEMPLATE.format(
234
- plan_result=format_plan_result(plan_result)
235
- )
236
-
237
- # Use planner directly - planner already has PLANNING verb
238
- plan_result.result = await self.planner.generate_str(
239
- message=synthesis_prompt,
240
- request_params=params.model_copy(update={"max_iterations": 1}),
241
- )
242
-
243
- return plan_result
244
-
245
- # Execute each step, collecting results
246
- # Note that in iterative mode this will only be a single step
247
- for step in plan.steps:
248
- # Check if we've hit the step limit
249
- if total_steps_executed >= max_steps:
250
- self.logger.warning(
251
- f"Reached maximum step limit ({max_steps}) without completing objective."
252
- )
253
- plan_result.max_steps_reached = True
254
- break
255
-
256
- step_result = await self._execute_step(
257
- step=step,
258
- previous_result=plan_result,
259
- request_params=params,
260
- )
261
-
262
- plan_result.add_step_result(step_result)
263
- total_steps_executed += 1
264
-
265
- # Check if we need to break from the main loop due to hitting max_steps
266
- if (
267
- hasattr(plan_result, "max_steps_reached")
268
- and plan_result.max_steps_reached
269
- ):
270
- break
271
-
272
- logger.debug(
273
- f"Iteration {iterations}: Intermediate plan result:", data=plan_result
274
- )
275
-
276
- # Check for diminishing returns
277
- if iterations > 2 and len(plan.steps) <= 1:
278
- # If plan has 0-1 steps after multiple iterations, might be done
279
- self.logger.info("Minimal new steps detected, marking plan as complete")
280
- plan_result.is_complete = True
281
- break
282
-
283
- iterations += 1
284
-
285
- # If we reach here, either:
286
- # 1. We hit iteration limit without completing
287
- # 2. We hit max_steps limit without completing
288
- # 3. We detected diminishing returns (plan with 0-1 steps after multiple iterations)
289
-
290
- # Check if we hit iteration limits without completing
291
- if iterations >= params.max_iterations and not plan_result.is_complete:
292
- self.logger.warning(
293
- f"Failed to complete in {params.max_iterations} iterations."
294
- )
295
- # Mark that we hit the iteration limit
296
- plan_result.max_iterations_reached = True
297
-
298
- # Use the incomplete template when we've hit iteration limits
299
- synthesis_prompt = SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE.format(
300
- plan_result=format_plan_result(plan_result),
301
- max_iterations=params.max_iterations,
302
- )
303
- else:
304
- # Either plan is complete or we had diminishing returns (which we mark as complete)
305
- if not plan_result.is_complete:
306
- self.logger.info(
307
- "Plan terminated due to diminishing returns, marking as complete"
308
- )
309
- plan_result.is_complete = True
310
-
311
- # Use standard template for complete plans
312
- synthesis_prompt = SYNTHESIZE_PLAN_PROMPT_TEMPLATE.format(
313
- plan_result=format_plan_result(plan_result)
314
- )
315
-
316
- # Generate the final synthesis with the appropriate template
317
- plan_result.result = await self.planner.generate_str(
318
- message=synthesis_prompt,
319
- request_params=params.model_copy(update={"max_iterations": 1}),
320
- )
321
-
322
- return plan_result
323
-
324
- async def _execute_step(
325
- self,
326
- step: Step,
327
- previous_result: PlanResult,
328
- request_params: RequestParams | None = None,
329
- ) -> StepResult:
330
- """Execute a step's subtasks in parallel and synthesize results"""
331
-
332
- step_result = StepResult(step=step, task_results=[])
333
- # Use structured XML format for context to help agents better understand the context
334
- context = format_plan_result(previous_result)
335
-
336
- # Execute tasks
337
- futures = []
338
- error_tasks = []
339
-
340
- for task in step.tasks:
341
- # Make sure we're using a valid agent name
342
- agent = self.agents.get(task.agent)
343
- if not agent:
344
- self.logger.error(
345
- f"AGENT VALIDATION ERROR: No agent found matching '{task.agent}'. Available agents: {list(self.agents.keys())}"
346
- )
347
- error_tasks.append(
348
- (
349
- task,
350
- f"Error: Agent '{task.agent}' not found. This indicates a problem with the plan generation. Available agents: {', '.join(self.agents.keys())}",
351
- )
352
- )
353
- continue
354
-
355
- task_description = TASK_PROMPT_TEMPLATE.format(
356
- objective=previous_result.objective,
357
- task=task.description,
358
- context=context,
359
- )
360
-
361
- # Handle both Agent objects and AugmentedLLM objects
362
- from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM
363
-
364
- if isinstance(agent, AugmentedLLM):
365
- futures.append(agent.generate_str(message=task_description))
366
- else:
367
- # Traditional Agent objects with _llm property
368
- futures.append(agent._llm.generate_str(message=task_description))
369
-
370
- # Wait for all tasks (only if we have valid futures)
371
- results = await self.executor.execute(*futures) if futures else []
372
-
373
- # Process successful results
374
- task_index = 0
375
- for task in step.tasks:
376
- # Skip tasks that had agent errors (they're in error_tasks)
377
- if any(et[0] == task for et in error_tasks):
378
- continue
379
-
380
- if task_index < len(results):
381
- result = results[task_index]
382
- # Create a TaskWithResult that includes the agent name for attribution
383
- task_model = task.model_dump()
384
- task_result = TaskWithResult(
385
- description=task_model["description"],
386
- agent=task_model["agent"], # Track which agent produced this result
387
- result=str(result),
388
- )
389
- step_result.add_task_result(task_result)
390
- task_index += 1
391
-
392
- # Add error task results
393
- for task, error_message in error_tasks:
394
- task_model = task.model_dump()
395
- step_result.add_task_result(
396
- TaskWithResult(
397
- description=task_model["description"],
398
- agent=task_model["agent"],
399
- result=f"ERROR: {error_message}",
400
- )
401
- )
402
-
403
- # Use text formatting for display in logs
404
- step_result.result = format_step_result_text(step_result)
405
- return step_result
406
-
407
- async def _get_full_plan(
408
- self,
409
- objective: str,
410
- plan_result: PlanResult,
411
- request_params: RequestParams | None = None,
412
- ) -> Plan:
413
- """Generate full plan considering previous results"""
414
-
415
- params = self.get_request_params(request_params)
416
- params = params.model_copy(update={"use_history": False})
417
-
418
- agent_formats = []
419
- for agent_name in self.agents.keys():
420
- formatted = self._format_agent_info(agent_name)
421
- agent_formats.append(formatted)
422
-
423
- agents = "\n".join(agent_formats)
424
-
425
- # Create clear plan status indicator for the template
426
- plan_status = "Plan Status: Not Started"
427
- if plan_result.is_complete:
428
- plan_status = (
429
- "Plan Status: Complete"
430
- if plan_result.is_complete
431
- else "Plan Status: In Progress"
432
- )
433
-
434
- # Fix the iteration counting display
435
- max_iterations = params.max_iterations
436
- # Simplified iteration counting logic
437
- current_iteration = len(plan_result.step_results)
438
- current_iteration = min(current_iteration, max_iterations - 1) # Cap at max-1
439
- iterations_remaining = max(
440
- 0, max_iterations - current_iteration - 1
441
- ) # Ensure non-negative
442
- iterations_info = f"Planning Budget: Iteration {current_iteration + 1} of {max_iterations} (with {iterations_remaining} remaining)"
443
-
444
- prompt = FULL_PLAN_PROMPT_TEMPLATE.format(
445
- objective=objective,
446
- plan_result=format_plan_result(plan_result),
447
- plan_status=plan_status,
448
- iterations_info=iterations_info,
449
- agents=agents,
450
- )
451
-
452
- # Get raw JSON response from LLM
453
- return await self.planner.generate_structured(
454
- message=prompt,
455
- request_params=params,
456
- response_model=Plan,
457
- )
458
- # return data
459
-
460
- # steps = []
461
- # for step_data in data.steps:
462
- # tasks = []
463
- # for task_data in step_data.tasks:
464
- # task = AgentTask(
465
- # description=task_data.description,
466
- # agent=task_data.agent,
467
- # )
468
- # tasks.append(task)
469
-
470
- # # Create Step with the exact task objects we created
471
- # step = Step(description=step_data.description, tasks=tasks)
472
- # steps.append(step)
473
-
474
- # # Create final Plan
475
- # plan = Plan(steps=steps, is_complete=data.is_complete)
476
- # return plan
477
-
478
- async def _get_next_step(
479
- self,
480
- objective: str,
481
- plan_result: PlanResult,
482
- request_params: RequestParams | None = None,
483
- ) -> NextStep:
484
- """Generate just the next needed step"""
485
-
486
- params = self.get_request_params(request_params)
487
- params = params.model_copy(update={"use_history": False})
488
-
489
- # Format agents without numeric prefixes for cleaner XML
490
- # FIX: Iterate over agent names instead of agent objects
491
- agents = "\n".join(
492
- [self._format_agent_info(agent_name) for agent_name in self.agents.keys()]
493
- )
494
-
495
- # Create clear plan status indicator for the template
496
- plan_status = "Plan Status: Not Started"
497
- if plan_result:
498
- plan_status = (
499
- "Plan Status: Complete"
500
- if plan_result.is_complete
501
- else "Plan Status: In Progress"
502
- )
503
-
504
- # Add max_iterations info for the LLM
505
- max_iterations = params.max_iterations
506
- current_iteration = len(plan_result.step_results)
507
- iterations_remaining = max_iterations - current_iteration
508
- iterations_info = f"Planning Budget: {iterations_remaining} of {max_iterations} iterations remaining"
509
-
510
- prompt = ITERATIVE_PLAN_PROMPT_TEMPLATE.format(
511
- objective=objective,
512
- plan_result=format_plan_result(plan_result),
513
- plan_status=plan_status,
514
- iterations_info=iterations_info,
515
- agents=agents,
516
- )
517
-
518
- # Get raw JSON response from LLM
519
- return await self.planner.generate_structured(
520
- message=prompt, request_params=params, response_model=NextStep
521
- )
522
-
523
- def _format_server_info(self, server_name: str) -> str:
524
- """Format server information for display to planners using XML tags"""
525
- from mcp_agent.workflows.llm.prompt_utils import format_server_info
526
-
527
- server_config = self.server_registry.get_server_config(server_name)
528
-
529
- # Get description or empty string if not available
530
- description = ""
531
- if server_config and server_config.description:
532
- description = server_config.description
533
-
534
- return format_server_info(server_name, description)
535
-
536
- def _validate_agent_names(self, plan: Plan) -> None:
537
- """
538
- Validate all agent names in a plan before execution.
539
- This helps catch invalid agent references early.
540
- """
541
- invalid_agents = []
542
-
543
- for step in plan.steps:
544
- for task in step.tasks:
545
- if task.agent not in self.agents:
546
- invalid_agents.append(task.agent)
547
-
548
- if invalid_agents:
549
- available_agents = ", ".join(self.agents.keys())
550
- invalid_list = ", ".join(invalid_agents)
551
- error_msg = f"Plan contains invalid agent names: {invalid_list}. Available agents: {available_agents}"
552
- self.logger.error(error_msg)
553
- # We don't raise an exception here as the execution will handle invalid agents
554
- # by logging errors for individual tasks
555
-
556
- def _format_agent_info(self, agent_name: str) -> str:
557
- """Format Agent information for display to planners using XML tags"""
558
- from mcp_agent.workflows.llm.prompt_utils import format_agent_info
559
-
560
- agent = self.agents.get(agent_name)
561
- if not agent:
562
- self.logger.error(f"Agent '{agent_name}' not found in orchestrator agents")
563
- return ""
564
- instruction = agent.instruction
565
-
566
- # Get servers information
567
- server_info = []
568
- for server_name in agent.server_names:
569
- server_config = self.server_registry.get_server_config(server_name)
570
- description = ""
571
- if server_config and server_config.description:
572
- description = server_config.description
573
-
574
- server_info.append({"name": server_name, "description": description})
575
-
576
- return format_agent_info(
577
- agent.name, instruction, server_info if server_info else None
578
- )
File without changes