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
@@ -1,535 +0,0 @@
1
- """
2
- Orchestrator implementation for MCP Agent applications.
3
- """
4
-
5
- from typing import (
6
- TYPE_CHECKING,
7
- List,
8
- Literal,
9
- Optional,
10
- Type,
11
- )
12
-
13
- from mcp_agent.agents.agent import Agent
14
- from mcp_agent.event_progress import ProgressAction
15
- from mcp_agent.logging.logger import get_logger
16
- from mcp_agent.workflows.llm.augmented_llm import (
17
- AugmentedLLM,
18
- MessageParamT,
19
- MessageT,
20
- ModelT,
21
- RequestParams,
22
- )
23
- from mcp_agent.workflows.orchestrator.orchestrator_models import (
24
- NextStep,
25
- Plan,
26
- PlanResult,
27
- Step,
28
- StepResult,
29
- TaskWithResult,
30
- format_plan_result,
31
- format_step_result_text,
32
- )
33
- from mcp_agent.workflows.orchestrator.orchestrator_prompts import (
34
- FULL_PLAN_PROMPT_TEMPLATE,
35
- ITERATIVE_PLAN_PROMPT_TEMPLATE,
36
- SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE, # Add the missing import
37
- SYNTHESIZE_PLAN_PROMPT_TEMPLATE,
38
- TASK_PROMPT_TEMPLATE,
39
- )
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
- ) -> None:
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(self, objective: str, request_params: RequestParams | None = None) -> PlanResult:
175
- """Execute task with result chaining between steps"""
176
- iterations = 0
177
- total_steps_executed = 0
178
-
179
- params = self.get_request_params(request_params)
180
- max_steps = getattr(params, "max_steps", params.max_iterations * 5) # Default to 5× max_iterations
181
-
182
- # Single progress event for orchestration start
183
- model = await self.select_model(params) or "unknown-model"
184
-
185
- # Log the progress with minimal required fields
186
- self.logger.info(
187
- "Planning task execution",
188
- data={
189
- "progress_action": self.verb,
190
- "model": model,
191
- "agent_name": self.name,
192
- "target": self.name,
193
- },
194
- )
195
-
196
- plan_result = PlanResult(objective=objective, step_results=[])
197
- plan_result.max_iterations_reached = False # Add a flag to track if we hit the limit
198
-
199
- while iterations < params.max_iterations:
200
- if self.plan_type == "iterative":
201
- # Get next plan/step
202
- next_step = await self._get_next_step(objective=objective, plan_result=plan_result, request_params=params)
203
- logger.debug(f"Iteration {iterations}: Iterative plan:", data=next_step)
204
- plan = Plan(steps=[next_step], is_complete=next_step.is_complete)
205
- # Validate agent names in the plan early
206
- self._validate_agent_names(plan)
207
- elif self.plan_type == "full":
208
- plan = await self._get_full_plan(objective=objective, plan_result=plan_result, request_params=params)
209
- logger.debug(f"Iteration {iterations}: Full Plan:", data=plan)
210
- # Validate agent names in the plan early
211
- self._validate_agent_names(plan)
212
- else:
213
- raise ValueError(f"Invalid plan type {self.plan_type}")
214
-
215
- plan_result.plan = plan
216
-
217
- if plan.is_complete:
218
- # Modified: Remove the requirement for steps to be executed
219
- plan_result.is_complete = True
220
-
221
- # Synthesize final result into a single message
222
- # Use the structured XML format for better context
223
- synthesis_prompt = SYNTHESIZE_PLAN_PROMPT_TEMPLATE.format(plan_result=format_plan_result(plan_result))
224
-
225
- # Use planner directly - planner already has PLANNING verb
226
- plan_result.result = await self.planner.generate_str(
227
- message=synthesis_prompt,
228
- request_params=params.model_copy(update={"max_iterations": 1}),
229
- )
230
-
231
- return plan_result
232
-
233
- # Execute each step, collecting results
234
- # Note that in iterative mode this will only be a single step
235
- for step in plan.steps:
236
- # Check if we've hit the step limit
237
- if total_steps_executed >= max_steps:
238
- self.logger.warning(f"Reached maximum step limit ({max_steps}) without completing objective.")
239
- plan_result.max_steps_reached = True
240
- break
241
-
242
- step_result = await self._execute_step(
243
- step=step,
244
- previous_result=plan_result,
245
- request_params=params,
246
- )
247
-
248
- plan_result.add_step_result(step_result)
249
- total_steps_executed += 1
250
-
251
- # Check if we need to break from the main loop due to hitting max_steps
252
- if hasattr(plan_result, "max_steps_reached") and plan_result.max_steps_reached:
253
- break
254
-
255
- logger.debug(f"Iteration {iterations}: Intermediate plan result:", data=plan_result)
256
-
257
- # Check for diminishing returns
258
- if iterations > 2 and len(plan.steps) <= 1:
259
- # If plan has 0-1 steps after multiple iterations, might be done
260
- self.logger.info("Minimal new steps detected, marking plan as complete")
261
- plan_result.is_complete = True
262
- break
263
-
264
- iterations += 1
265
-
266
- # If we reach here, either:
267
- # 1. We hit iteration limit without completing
268
- # 2. We hit max_steps limit without completing
269
- # 3. We detected diminishing returns (plan with 0-1 steps after multiple iterations)
270
-
271
- # Check if we hit iteration limits without completing
272
- if iterations >= params.max_iterations and not plan_result.is_complete:
273
- self.logger.warning(f"Failed to complete in {params.max_iterations} iterations.")
274
- # Mark that we hit the iteration limit
275
- plan_result.max_iterations_reached = True
276
-
277
- # Use the incomplete template when we've hit iteration limits
278
- synthesis_prompt = SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE.format(
279
- plan_result=format_plan_result(plan_result),
280
- max_iterations=params.max_iterations,
281
- )
282
- else:
283
- # Either plan is complete or we had diminishing returns (which we mark as complete)
284
- if not plan_result.is_complete:
285
- self.logger.info("Plan terminated due to diminishing returns, marking as complete")
286
- plan_result.is_complete = True
287
-
288
- # Use standard template for complete plans
289
- synthesis_prompt = SYNTHESIZE_PLAN_PROMPT_TEMPLATE.format(plan_result=format_plan_result(plan_result))
290
-
291
- # Generate the final synthesis with the appropriate template
292
- plan_result.result = await self.planner.generate_str(
293
- message=synthesis_prompt,
294
- request_params=params.model_copy(update={"max_iterations": 1}),
295
- )
296
-
297
- return plan_result
298
-
299
- async def _execute_step(
300
- self,
301
- step: Step,
302
- previous_result: PlanResult,
303
- request_params: RequestParams | None = None,
304
- ) -> StepResult:
305
- """Execute a step's subtasks in parallel and synthesize results"""
306
-
307
- step_result = StepResult(step=step, task_results=[])
308
- # Use structured XML format for context to help agents better understand the context
309
- context = format_plan_result(previous_result)
310
-
311
- # Execute tasks
312
- futures = []
313
- error_tasks = []
314
-
315
- for task in step.tasks:
316
- # Make sure we're using a valid agent name
317
- agent = self.agents.get(task.agent)
318
- if not agent:
319
- self.logger.error(f"AGENT VALIDATION ERROR: No agent found matching '{task.agent}'. Available agents: {list(self.agents.keys())}")
320
- error_tasks.append(
321
- (
322
- task,
323
- f"Error: Agent '{task.agent}' not found. This indicates a problem with the plan generation. Available agents: {', '.join(self.agents.keys())}",
324
- )
325
- )
326
- continue
327
-
328
- task_description = TASK_PROMPT_TEMPLATE.format(
329
- objective=previous_result.objective,
330
- task=task.description,
331
- context=context,
332
- )
333
-
334
- # Handle both Agent objects and AugmentedLLM objects
335
- from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM
336
-
337
- if isinstance(agent, AugmentedLLM):
338
- futures.append(agent.generate_str(message=task_description))
339
- else:
340
- # Traditional Agent objects with _llm property
341
- futures.append(agent._llm.generate_str(message=task_description))
342
-
343
- # Wait for all tasks (only if we have valid futures)
344
- results = await self.executor.execute(*futures) if futures else []
345
-
346
- # Process successful results
347
- task_index = 0
348
- for task in step.tasks:
349
- # Skip tasks that had agent errors (they're in error_tasks)
350
- if any(et[0] == task for et in error_tasks):
351
- continue
352
-
353
- if task_index < len(results):
354
- result = results[task_index]
355
- # Create a TaskWithResult that includes the agent name for attribution
356
- task_model = task.model_dump()
357
- task_result = TaskWithResult(
358
- description=task_model["description"],
359
- agent=task_model["agent"], # Track which agent produced this result
360
- result=str(result),
361
- )
362
- step_result.add_task_result(task_result)
363
- task_index += 1
364
-
365
- # Add error task results
366
- for task, error_message in error_tasks:
367
- task_model = task.model_dump()
368
- step_result.add_task_result(
369
- TaskWithResult(
370
- description=task_model["description"],
371
- agent=task_model["agent"],
372
- result=f"ERROR: {error_message}",
373
- )
374
- )
375
-
376
- # Use text formatting for display in logs
377
- step_result.result = format_step_result_text(step_result)
378
- return step_result
379
-
380
- async def _get_full_plan(
381
- self,
382
- objective: str,
383
- plan_result: PlanResult,
384
- request_params: RequestParams | None = None,
385
- ) -> Plan:
386
- """Generate full plan considering previous results"""
387
-
388
- params = self.get_request_params(request_params)
389
- params = params.model_copy(update={"use_history": False})
390
-
391
- agent_formats = []
392
- for agent_name in self.agents.keys():
393
- formatted = self._format_agent_info(agent_name)
394
- agent_formats.append(formatted)
395
-
396
- agents = "\n".join(agent_formats)
397
-
398
- # Create clear plan status indicator for the template
399
- plan_status = "Plan Status: Not Started"
400
- if plan_result.is_complete:
401
- plan_status = "Plan Status: Complete" if plan_result.is_complete else "Plan Status: In Progress"
402
-
403
- # Fix the iteration counting display
404
- max_iterations = params.max_iterations
405
- # Simplified iteration counting logic
406
- current_iteration = len(plan_result.step_results)
407
- current_iteration = min(current_iteration, max_iterations - 1) # Cap at max-1
408
- iterations_remaining = max(0, max_iterations - current_iteration - 1) # Ensure non-negative
409
- iterations_info = f"Planning Budget: Iteration {current_iteration + 1} of {max_iterations} (with {iterations_remaining} remaining)"
410
-
411
- prompt = FULL_PLAN_PROMPT_TEMPLATE.format(
412
- objective=objective,
413
- plan_result=format_plan_result(plan_result),
414
- plan_status=plan_status,
415
- iterations_info=iterations_info,
416
- agents=agents,
417
- )
418
-
419
- # Get raw JSON response from LLM
420
- return await self.planner.generate_structured(
421
- message=prompt,
422
- request_params=params,
423
- response_model=Plan,
424
- )
425
- # return data
426
-
427
- # steps = []
428
- # for step_data in data.steps:
429
- # tasks = []
430
- # for task_data in step_data.tasks:
431
- # task = AgentTask(
432
- # description=task_data.description,
433
- # agent=task_data.agent,
434
- # )
435
- # tasks.append(task)
436
-
437
- # # Create Step with the exact task objects we created
438
- # step = Step(description=step_data.description, tasks=tasks)
439
- # steps.append(step)
440
-
441
- # # Create final Plan
442
- # plan = Plan(steps=steps, is_complete=data.is_complete)
443
- # return plan
444
-
445
- async def _get_next_step(
446
- self,
447
- objective: str,
448
- plan_result: PlanResult,
449
- request_params: RequestParams | None = None,
450
- ) -> NextStep:
451
- """Generate just the next needed step"""
452
-
453
- params = self.get_request_params(request_params)
454
- params = params.model_copy(update={"use_history": False})
455
-
456
- # Format agents without numeric prefixes for cleaner XML
457
- # FIX: Iterate over agent names instead of agent objects
458
- agents = "\n".join([self._format_agent_info(agent_name) for agent_name in self.agents.keys()])
459
-
460
- # Create clear plan status indicator for the template
461
- plan_status = "Plan Status: Not Started"
462
- if plan_result:
463
- plan_status = "Plan Status: Complete" if plan_result.is_complete else "Plan Status: In Progress"
464
-
465
- # Add max_iterations info for the LLM
466
- max_iterations = params.max_iterations
467
- current_iteration = len(plan_result.step_results)
468
- iterations_remaining = max_iterations - current_iteration
469
- iterations_info = f"Planning Budget: {iterations_remaining} of {max_iterations} iterations remaining"
470
-
471
- prompt = ITERATIVE_PLAN_PROMPT_TEMPLATE.format(
472
- objective=objective,
473
- plan_result=format_plan_result(plan_result),
474
- plan_status=plan_status,
475
- iterations_info=iterations_info,
476
- agents=agents,
477
- )
478
-
479
- # Get raw JSON response from LLM
480
- return await self.planner.generate_structured(message=prompt, request_params=params, response_model=NextStep)
481
-
482
- def _format_server_info(self, server_name: str) -> str:
483
- """Format server information for display to planners using XML tags"""
484
- from mcp_agent.workflows.llm.prompt_utils import format_server_info
485
-
486
- server_config = self.server_registry.get_server_config(server_name)
487
-
488
- # Get description or empty string if not available
489
- description = ""
490
- if server_config and server_config.description:
491
- description = server_config.description
492
-
493
- return format_server_info(server_name, description)
494
-
495
- def _validate_agent_names(self, plan: Plan) -> None:
496
- """
497
- Validate all agent names in a plan before execution.
498
- This helps catch invalid agent references early.
499
- """
500
- invalid_agents = []
501
-
502
- for step in plan.steps:
503
- for task in step.tasks:
504
- if task.agent not in self.agents:
505
- invalid_agents.append(task.agent)
506
-
507
- if invalid_agents:
508
- available_agents = ", ".join(self.agents.keys())
509
- invalid_list = ", ".join(invalid_agents)
510
- error_msg = f"Plan contains invalid agent names: {invalid_list}. Available agents: {available_agents}"
511
- self.logger.error(error_msg)
512
- # We don't raise an exception here as the execution will handle invalid agents
513
- # by logging errors for individual tasks
514
-
515
- def _format_agent_info(self, agent_name: str) -> str:
516
- """Format Agent information for display to planners using XML tags"""
517
- from mcp_agent.workflows.llm.prompt_utils import format_agent_info
518
-
519
- agent = self.agents.get(agent_name)
520
- if not agent:
521
- self.logger.error(f"Agent '{agent_name}' not found in orchestrator agents")
522
- return ""
523
- instruction = agent.instruction
524
-
525
- # Get servers information
526
- server_info = []
527
- for server_name in agent.server_names:
528
- server_config = self.server_registry.get_server_config(server_name)
529
- description = ""
530
- if server_config and server_config.description:
531
- description = server_config.description
532
-
533
- server_info.append({"name": server_name, "description": description})
534
-
535
- return format_agent_info(agent.name, instruction, server_info if server_info else None)
File without changes