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.
- {fast_agent_mcp-0.1.13.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 +59 -371
- 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 +27 -11
- mcp_agent/agents/workflow/parallel_agent.py +182 -0
- mcp_agent/agents/workflow/router_agent.py +307 -0
- mcp_agent/app.py +3 -1
- mcp_agent/cli/commands/bootstrap.py +18 -7
- mcp_agent/cli/commands/setup.py +12 -4
- mcp_agent/cli/main.py +1 -1
- mcp_agent/cli/terminal.py +1 -1
- mcp_agent/config.py +24 -35
- mcp_agent/context.py +3 -1
- mcp_agent/context_dependent.py +3 -1
- 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 +15 -20
- mcp_agent/core/fastagent.py +151 -337
- mcp_agent/core/interactive_prompt.py +424 -0
- mcp_agent/core/mcp_content.py +19 -11
- mcp_agent/core/prompt.py +6 -2
- mcp_agent/core/validation.py +89 -16
- mcp_agent/executor/decorator_registry.py +6 -2
- mcp_agent/executor/temporal.py +35 -11
- mcp_agent/executor/workflow_signal.py +8 -2
- mcp_agent/human_input/handler.py +3 -1
- mcp_agent/llm/__init__.py +2 -0
- mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
- mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
- mcp_agent/llm/augmented_llm_playback.py +83 -0
- mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
- mcp_agent/llm/providers/__init__.py +8 -0
- mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
- mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
- mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
- mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
- mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
- mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
- mcp_agent/logging/logger.py +2 -2
- mcp_agent/mcp/gen_client.py +9 -3
- mcp_agent/mcp/interfaces.py +67 -45
- mcp_agent/mcp/logger_textio.py +97 -0
- mcp_agent/mcp/mcp_agent_client_session.py +12 -4
- mcp_agent/mcp/mcp_agent_server.py +3 -1
- mcp_agent/mcp/mcp_aggregator.py +124 -93
- mcp_agent/mcp/mcp_connection_manager.py +21 -7
- mcp_agent/mcp/prompt_message_multipart.py +59 -1
- mcp_agent/mcp/prompt_render.py +77 -0
- mcp_agent/mcp/prompt_serialization.py +20 -13
- 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 +15 -5
- mcp_agent/mcp/prompts/prompt_server.py +154 -87
- mcp_agent/mcp/prompts/prompt_template.py +26 -35
- mcp_agent/mcp/resource_utils.py +3 -1
- mcp_agent/mcp/sampling.py +24 -15
- mcp_agent/mcp_server/agent_server.py +8 -5
- mcp_agent/mcp_server_registry.py +22 -9
- mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
- mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
- mcp_agent/resources/examples/internal/agent.py +4 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
- mcp_agent/resources/examples/prompting/image_server.py +3 -1
- mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
- mcp_agent/ui/console_display.py +27 -7
- fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
- mcp_agent/core/agent_app.py +0 -570
- mcp_agent/core/agent_utils.py +0 -69
- mcp_agent/core/decorators.py +0 -448
- mcp_agent/core/factory.py +0 -422
- mcp_agent/core/proxies.py +0 -278
- mcp_agent/core/types.py +0 -22
- mcp_agent/eval/__init__.py +0 -0
- mcp_agent/mcp/stdio.py +0 -114
- 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 -189
- mcp_agent/resources/examples/researcher/researcher.py +0 -39
- mcp_agent/resources/examples/workflows/chaining.py +0 -45
- mcp_agent/resources/examples/workflows/evaluator.py +0 -79
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
- mcp_agent/resources/examples/workflows/human_input.py +0 -26
- mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
- mcp_agent/resources/examples/workflows/parallel.py +0 -79
- mcp_agent/resources/examples/workflows/router.py +0 -54
- mcp_agent/resources/examples/workflows/sse.py +0 -23
- mcp_agent/telemetry/__init__.py +0 -0
- mcp_agent/telemetry/usage_tracking.py +0 -19
- mcp_agent/workflows/__init__.py +0 -0
- mcp_agent/workflows/embedding/__init__.py +0 -0
- mcp_agent/workflows/embedding/embedding_base.py +0 -58
- mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
- mcp_agent/workflows/embedding/embedding_openai.py +0 -37
- mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
- mcp_agent/workflows/intent_classifier/__init__.py +0 -0
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
- mcp_agent/workflows/llm/__init__.py +0 -0
- mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
- mcp_agent/workflows/llm/providers/__init__.py +0 -8
- mcp_agent/workflows/orchestrator/__init__.py +0 -0
- mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
- mcp_agent/workflows/parallel/__init__.py +0 -0
- mcp_agent/workflows/parallel/fan_in.py +0 -320
- mcp_agent/workflows/parallel/fan_out.py +0 -181
- mcp_agent/workflows/parallel/parallel_llm.py +0 -149
- mcp_agent/workflows/router/__init__.py +0 -0
- mcp_agent/workflows/router/router_base.py +0 -338
- mcp_agent/workflows/router/router_embedding.py +0 -226
- 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 -304
- mcp_agent/workflows/swarm/__init__.py +0 -0
- mcp_agent/workflows/swarm/swarm.py +0 -292
- mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
- mcp_agent/workflows/swarm/swarm_openai.py +0 -41
- {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.13.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
- /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
- /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
|