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