fast-agent-mcp 0.0.16__py3-none-any.whl → 0.1.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.0.16.dist-info → fast_agent_mcp-0.1.0.dist-info}/METADATA +11 -10
- {fast_agent_mcp-0.0.16.dist-info → fast_agent_mcp-0.1.0.dist-info}/RECORD +17 -16
- mcp_agent/cli/commands/bootstrap.py +1 -1
- mcp_agent/cli/commands/setup.py +4 -1
- mcp_agent/cli/main.py +13 -3
- mcp_agent/core/agent_app.py +1 -1
- mcp_agent/core/enhanced_prompt.py +3 -3
- mcp_agent/core/fastagent.py +87 -49
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +188 -0
- mcp_agent/resources/examples/data-analysis/analysis.py +32 -32
- mcp_agent/resources/examples/workflows/evaluator.py +3 -1
- mcp_agent/resources/examples/workflows/orchestrator.py +1 -1
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +120 -63
- mcp_agent/workflows/orchestrator/orchestrator.py +114 -66
- {fast_agent_mcp-0.0.16.dist-info → fast_agent_mcp-0.1.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.0.16.dist-info → fast_agent_mcp-0.1.0.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.0.16.dist-info → fast_agent_mcp-0.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -129,6 +129,27 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
129
129
|
self.logger = logger
|
130
130
|
self.name = name
|
131
131
|
|
132
|
+
# Store agents by name - COMPLETE REWRITE OF AGENT STORAGE
|
133
|
+
self.agents = {}
|
134
|
+
for agent in available_agents:
|
135
|
+
# Fix: Remove all special handling of agent names and store them exactly as they are
|
136
|
+
agent_name = agent.name
|
137
|
+
|
138
|
+
# Verify if the name is actually "None" (string) or None (NoneType)
|
139
|
+
if agent_name == "None":
|
140
|
+
# Try to get a better name from config if available
|
141
|
+
if hasattr(agent, "config") and agent.config and agent.config.name:
|
142
|
+
agent_name = agent.config.name
|
143
|
+
elif agent_name is None:
|
144
|
+
# Try to get a better name from config if available
|
145
|
+
if hasattr(agent, "config") and agent.config and agent.config.name:
|
146
|
+
agent_name = agent.config.name
|
147
|
+
else:
|
148
|
+
agent_name = f"unnamed_agent_{len(self.agents)}"
|
149
|
+
|
150
|
+
self.logger.info(f"Adding agent '{agent_name}' to orchestrator")
|
151
|
+
self.agents[agent_name] = agent
|
152
|
+
|
132
153
|
async def generate(
|
133
154
|
self,
|
134
155
|
message: str | MessageParamT | List[MessageParamT],
|
@@ -165,19 +186,21 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
165
186
|
"""Request a structured LLM generation and return the result as a Pydantic model."""
|
166
187
|
import json
|
167
188
|
from pydantic import ValidationError
|
168
|
-
|
189
|
+
|
169
190
|
params = self.get_request_params(request_params)
|
170
191
|
result_str = await self.generate_str(message=message, request_params=params)
|
171
|
-
|
192
|
+
|
172
193
|
try:
|
173
194
|
# Directly parse JSON and create model instance
|
174
195
|
parsed_data = json.loads(result_str)
|
175
196
|
return response_model(**parsed_data)
|
176
197
|
except (json.JSONDecodeError, ValidationError) as e:
|
177
198
|
# Log the error and fall back to the original method if direct parsing fails
|
178
|
-
self.logger.error(
|
199
|
+
self.logger.error(
|
200
|
+
f"Direct JSON parsing failed: {str(e)}. Falling back to standard method."
|
201
|
+
)
|
179
202
|
self.logger.debug(f"Failed JSON content: {result_str}")
|
180
|
-
|
203
|
+
|
181
204
|
# Use AugmentedLLM's structured output handling as fallback
|
182
205
|
return await super().generate_structured(
|
183
206
|
message=result_str,
|
@@ -312,8 +335,14 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
312
335
|
context=context,
|
313
336
|
)
|
314
337
|
|
315
|
-
#
|
316
|
-
|
338
|
+
# Handle both Agent objects and AugmentedLLM objects
|
339
|
+
from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM
|
340
|
+
|
341
|
+
if isinstance(agent, AugmentedLLM):
|
342
|
+
futures.append(agent.generate_str(message=task_description))
|
343
|
+
else:
|
344
|
+
# Traditional Agent objects with _llm property
|
345
|
+
futures.append(agent._llm.generate_str(message=task_description))
|
317
346
|
|
318
347
|
# Wait for all tasks (only if we have valid futures)
|
319
348
|
results = await self.executor.execute(*futures) if futures else []
|
@@ -332,7 +361,7 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
332
361
|
task_result = TaskWithResult(
|
333
362
|
description=task_model["description"],
|
334
363
|
agent=task_model["agent"], # Track which agent produced this result
|
335
|
-
result=str(result)
|
364
|
+
result=str(result),
|
336
365
|
)
|
337
366
|
step_result.add_task_result(task_result)
|
338
367
|
task_index += 1
|
@@ -344,7 +373,7 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
344
373
|
TaskWithResult(
|
345
374
|
description=task_model["description"],
|
346
375
|
agent=task_model["agent"],
|
347
|
-
result=f"ERROR: {error_message}"
|
376
|
+
result=f"ERROR: {error_message}",
|
348
377
|
)
|
349
378
|
)
|
350
379
|
|
@@ -361,21 +390,39 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
361
390
|
"""Generate full plan considering previous results"""
|
362
391
|
import json
|
363
392
|
from pydantic import ValidationError
|
364
|
-
from mcp_agent.workflows.orchestrator.orchestrator_models import
|
393
|
+
from mcp_agent.workflows.orchestrator.orchestrator_models import (
|
394
|
+
Plan,
|
395
|
+
Step,
|
396
|
+
AgentTask,
|
397
|
+
)
|
365
398
|
|
366
399
|
params = self.get_request_params(request_params)
|
367
400
|
params = params.model_copy(update={"use_history": False})
|
368
401
|
|
402
|
+
# Debug: Print agent names before formatting
|
403
|
+
print("\n------ AGENT NAMES BEFORE FORMATTING ------")
|
404
|
+
for agent_name in self.agents.keys():
|
405
|
+
print(f"Agent name: '{agent_name}'")
|
406
|
+
print("------------------------------------------\n")
|
407
|
+
|
369
408
|
# Format agents without numeric prefixes for cleaner XML
|
370
|
-
|
371
|
-
|
372
|
-
|
409
|
+
agent_formats = []
|
410
|
+
for agent_name in self.agents.keys():
|
411
|
+
formatted = self._format_agent_info(agent_name)
|
412
|
+
agent_formats.append(formatted)
|
413
|
+
print(f"Formatted agent '{agent_name}':\n{formatted[:200]}...\n")
|
414
|
+
|
415
|
+
agents = "\n".join(agent_formats)
|
373
416
|
|
374
417
|
# Create clear plan status indicator for the template
|
375
418
|
plan_status = "Plan Status: Not Started"
|
376
419
|
if hasattr(plan_result, "is_complete"):
|
377
|
-
plan_status =
|
378
|
-
|
420
|
+
plan_status = (
|
421
|
+
"Plan Status: Complete"
|
422
|
+
if plan_result.is_complete
|
423
|
+
else "Plan Status: In Progress"
|
424
|
+
)
|
425
|
+
|
379
426
|
prompt = FULL_PLAN_PROMPT_TEMPLATE.format(
|
380
427
|
objective=objective,
|
381
428
|
plan_result=format_plan_result(plan_result),
|
@@ -388,50 +435,44 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
388
435
|
message=prompt,
|
389
436
|
request_params=params,
|
390
437
|
)
|
391
|
-
|
438
|
+
|
392
439
|
try:
|
393
440
|
# Parse JSON directly
|
394
441
|
data = json.loads(result_str)
|
395
|
-
|
442
|
+
|
396
443
|
# Create models manually to ensure agent names are preserved exactly as returned
|
397
444
|
steps = []
|
398
|
-
for step_data in data.get(
|
445
|
+
for step_data in data.get("steps", []):
|
399
446
|
tasks = []
|
400
|
-
for task_data in step_data.get(
|
447
|
+
for task_data in step_data.get("tasks", []):
|
401
448
|
# Create AgentTask directly from dict, preserving exact agent string
|
402
449
|
task = AgentTask(
|
403
|
-
description=task_data.get(
|
404
|
-
agent=task_data.get(
|
450
|
+
description=task_data.get("description", ""),
|
451
|
+
agent=task_data.get("agent", ""), # Preserve exact agent name
|
405
452
|
)
|
406
453
|
tasks.append(task)
|
407
|
-
|
454
|
+
|
408
455
|
# Create Step with the exact task objects we created
|
409
|
-
step = Step(
|
410
|
-
description=step_data.get('description', ''),
|
411
|
-
tasks=tasks
|
412
|
-
)
|
456
|
+
step = Step(description=step_data.get("description", ""), tasks=tasks)
|
413
457
|
steps.append(step)
|
414
|
-
|
458
|
+
|
415
459
|
# Create final Plan
|
416
|
-
plan = Plan(
|
417
|
-
|
418
|
-
is_complete=data.get('is_complete', False)
|
419
|
-
)
|
420
|
-
|
460
|
+
plan = Plan(steps=steps, is_complete=data.get("is_complete", False))
|
461
|
+
|
421
462
|
return plan
|
422
|
-
|
463
|
+
|
423
464
|
except (json.JSONDecodeError, ValidationError, KeyError) as e:
|
424
465
|
# Log detailed error and fall back to the original method as last resort
|
425
466
|
self.logger.error(f"Error parsing plan JSON: {str(e)}")
|
426
467
|
self.logger.debug(f"Failed JSON content: {result_str}")
|
427
|
-
|
468
|
+
|
428
469
|
# Use the normal structured parsing as fallback
|
429
470
|
plan = await self.planner.generate_structured(
|
430
471
|
message=result_str,
|
431
472
|
response_model=Plan,
|
432
473
|
request_params=params,
|
433
474
|
)
|
434
|
-
|
475
|
+
|
435
476
|
return plan
|
436
477
|
|
437
478
|
async def _get_next_step(
|
@@ -443,21 +484,29 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
443
484
|
"""Generate just the next needed step"""
|
444
485
|
import json
|
445
486
|
from pydantic import ValidationError
|
446
|
-
from mcp_agent.workflows.orchestrator.orchestrator_models import
|
447
|
-
|
487
|
+
from mcp_agent.workflows.orchestrator.orchestrator_models import (
|
488
|
+
NextStep,
|
489
|
+
AgentTask,
|
490
|
+
)
|
491
|
+
|
448
492
|
params = self.get_request_params(request_params)
|
449
493
|
params = params.model_copy(update={"use_history": False})
|
450
494
|
|
451
495
|
# Format agents without numeric prefixes for cleaner XML
|
496
|
+
# FIX: Iterate over agent names instead of agent objects
|
452
497
|
agents = "\n".join(
|
453
|
-
[self._format_agent_info(
|
498
|
+
[self._format_agent_info(agent_name) for agent_name in self.agents.keys()]
|
454
499
|
)
|
455
500
|
|
456
501
|
# Create clear plan status indicator for the template
|
457
502
|
plan_status = "Plan Status: Not Started"
|
458
503
|
if hasattr(plan_result, "is_complete"):
|
459
|
-
plan_status =
|
460
|
-
|
504
|
+
plan_status = (
|
505
|
+
"Plan Status: Complete"
|
506
|
+
if plan_result.is_complete
|
507
|
+
else "Plan Status: In Progress"
|
508
|
+
)
|
509
|
+
|
461
510
|
prompt = ITERATIVE_PLAN_PROMPT_TEMPLATE.format(
|
462
511
|
objective=objective,
|
463
512
|
plan_result=format_plan_result(plan_result),
|
@@ -470,55 +519,55 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
470
519
|
message=prompt,
|
471
520
|
request_params=params,
|
472
521
|
)
|
473
|
-
|
522
|
+
|
474
523
|
try:
|
475
524
|
# Parse JSON directly
|
476
525
|
data = json.loads(result_str)
|
477
|
-
|
526
|
+
|
478
527
|
# Create task objects manually to preserve exact agent names
|
479
528
|
tasks = []
|
480
|
-
for task_data in data.get(
|
529
|
+
for task_data in data.get("tasks", []):
|
481
530
|
# Preserve the exact agent name as specified in the JSON
|
482
531
|
task = AgentTask(
|
483
|
-
description=task_data.get(
|
484
|
-
agent=task_data.get(
|
532
|
+
description=task_data.get("description", ""),
|
533
|
+
agent=task_data.get("agent", ""),
|
485
534
|
)
|
486
535
|
tasks.append(task)
|
487
|
-
|
536
|
+
|
488
537
|
# Create step with manually constructed tasks
|
489
538
|
next_step = NextStep(
|
490
|
-
description=data.get(
|
539
|
+
description=data.get("description", ""),
|
491
540
|
tasks=tasks,
|
492
|
-
is_complete=data.get(
|
541
|
+
is_complete=data.get("is_complete", False),
|
493
542
|
)
|
494
|
-
|
543
|
+
|
495
544
|
return next_step
|
496
|
-
|
545
|
+
|
497
546
|
except (json.JSONDecodeError, ValidationError, KeyError) as e:
|
498
547
|
# Log detailed error and fall back to the original method
|
499
548
|
self.logger.error(f"Error parsing next step JSON: {str(e)}")
|
500
549
|
self.logger.debug(f"Failed JSON content: {result_str}")
|
501
|
-
|
550
|
+
|
502
551
|
# Use the normal structured parsing as fallback
|
503
552
|
next_step = await self.planner.generate_structured(
|
504
553
|
message=result_str,
|
505
554
|
response_model=NextStep,
|
506
555
|
request_params=params,
|
507
556
|
)
|
508
|
-
|
557
|
+
|
509
558
|
return next_step
|
510
559
|
|
511
560
|
def _format_server_info(self, server_name: str) -> str:
|
512
561
|
"""Format server information for display to planners using XML tags"""
|
513
562
|
from mcp_agent.workflows.llm.prompt_utils import format_server_info
|
514
|
-
|
563
|
+
|
515
564
|
server_config = self.server_registry.get_server_config(server_name)
|
516
|
-
|
565
|
+
|
517
566
|
# Get description or empty string if not available
|
518
567
|
description = ""
|
519
568
|
if server_config and server_config.description:
|
520
569
|
description = server_config.description
|
521
|
-
|
570
|
+
|
522
571
|
return format_server_info(server_name, description)
|
523
572
|
|
524
573
|
def _validate_agent_names(self, plan: Plan) -> None:
|
@@ -527,12 +576,12 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
527
576
|
This helps catch invalid agent references early.
|
528
577
|
"""
|
529
578
|
invalid_agents = []
|
530
|
-
|
579
|
+
|
531
580
|
for step in plan.steps:
|
532
581
|
for task in step.tasks:
|
533
582
|
if task.agent not in self.agents:
|
534
583
|
invalid_agents.append(task.agent)
|
535
|
-
|
584
|
+
|
536
585
|
if invalid_agents:
|
537
586
|
available_agents = ", ".join(self.agents.keys())
|
538
587
|
invalid_list = ", ".join(invalid_agents)
|
@@ -540,20 +589,17 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
540
589
|
self.logger.error(error_msg)
|
541
590
|
# We don't raise an exception here as the execution will handle invalid agents
|
542
591
|
# by logging errors for individual tasks
|
543
|
-
|
592
|
+
|
544
593
|
def _format_agent_info(self, agent_name: str) -> str:
|
545
594
|
"""Format Agent information for display to planners using XML tags"""
|
546
595
|
from mcp_agent.workflows.llm.prompt_utils import format_agent_info
|
547
|
-
|
596
|
+
|
548
597
|
agent = self.agents.get(agent_name)
|
549
598
|
if not agent:
|
599
|
+
self.logger.error(f"Agent '{agent_name}' not found in orchestrator agents")
|
550
600
|
return ""
|
551
|
-
|
552
|
-
# Get agent instruction as string
|
553
601
|
instruction = agent.instruction
|
554
|
-
|
555
|
-
instruction = instruction({})
|
556
|
-
|
602
|
+
|
557
603
|
# Get servers information
|
558
604
|
server_info = []
|
559
605
|
for server_name in agent.server_names:
|
@@ -561,7 +607,9 @@ class Orchestrator(AugmentedLLM[MessageParamT, MessageT]):
|
|
561
607
|
description = ""
|
562
608
|
if server_config and server_config.description:
|
563
609
|
description = server_config.description
|
564
|
-
|
610
|
+
|
565
611
|
server_info.append({"name": server_name, "description": description})
|
566
|
-
|
567
|
-
return format_agent_info(
|
612
|
+
|
613
|
+
return format_agent_info(
|
614
|
+
agent.name, instruction, server_info if server_info else None
|
615
|
+
)
|
File without changes
|
File without changes
|
File without changes
|