fast-agent-mcp 0.2.45__py3-none-any.whl → 0.2.47__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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.47.dist-info}/METADATA +13 -13
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.47.dist-info}/RECORD +35 -30
- mcp_agent/__init__.py +40 -0
- mcp_agent/agents/workflow/iterative_planner.py +572 -0
- mcp_agent/agents/workflow/orchestrator_agent.py +3 -3
- mcp_agent/agents/workflow/orchestrator_models.py +6 -6
- mcp_agent/cli/commands/go.py +25 -4
- mcp_agent/core/__init__.py +26 -0
- mcp_agent/core/agent_types.py +1 -0
- mcp_agent/core/direct_decorators.py +168 -16
- mcp_agent/core/direct_factory.py +42 -15
- mcp_agent/core/fastagent.py +4 -0
- mcp_agent/core/mermaid_utils.py +170 -0
- mcp_agent/human_input/__init__.py +50 -0
- mcp_agent/human_input/form_fields.py +252 -0
- mcp_agent/human_input/simple_form.py +111 -0
- mcp_agent/llm/augmented_llm.py +11 -2
- mcp_agent/llm/augmented_llm_playback.py +5 -3
- mcp_agent/llm/model_database.py +2 -7
- mcp_agent/llm/providers/augmented_llm_aliyun.py +1 -1
- mcp_agent/llm/providers/augmented_llm_anthropic.py +1 -1
- mcp_agent/llm/providers/augmented_llm_deepseek.py +4 -2
- mcp_agent/llm/providers/augmented_llm_google_oai.py +1 -1
- mcp_agent/llm/providers/augmented_llm_openrouter.py +1 -1
- mcp_agent/llm/providers/augmented_llm_tensorzero.py +1 -1
- mcp_agent/llm/providers/augmented_llm_xai.py +1 -1
- mcp_agent/mcp/__init__.py +50 -0
- mcp_agent/mcp/helpers/__init__.py +23 -1
- mcp_agent/mcp/interfaces.py +13 -2
- mcp_agent/py.typed +0 -0
- mcp_agent/resources/examples/workflows/orchestrator.py +5 -2
- mcp_agent/ui/console_display.py +104 -39
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.47.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.47.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.47.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/agent_types.py
CHANGED
|
@@ -6,6 +6,7 @@ for creating agents in the DirectFastAgent framework.
|
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
8
|
from functools import wraps
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import (
|
|
10
11
|
Awaitable,
|
|
11
12
|
Callable,
|
|
@@ -21,8 +22,10 @@ from typing import (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
from mcp.client.session import ElicitationFnT
|
|
25
|
+
from pydantic import AnyUrl
|
|
24
26
|
|
|
25
27
|
from mcp_agent.agents.agent import AgentConfig
|
|
28
|
+
from mcp_agent.agents.workflow.iterative_planner import ITERATIVE_PLAN_SYSTEM_PROMPT_TEMPLATE
|
|
26
29
|
from mcp_agent.agents.workflow.router_agent import (
|
|
27
30
|
ROUTING_SYSTEM_INSTRUCTION,
|
|
28
31
|
)
|
|
@@ -85,6 +88,91 @@ class DecoratedEvaluatorOptimizerProtocol(DecoratedAgentProtocol[P, R], Protocol
|
|
|
85
88
|
_evaluator: str
|
|
86
89
|
|
|
87
90
|
|
|
91
|
+
def _fetch_url_content(url: str) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Fetch content from a URL.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
url: The URL to fetch content from
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The text content from the URL
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
requests.RequestException: If the URL cannot be fetched
|
|
103
|
+
UnicodeDecodeError: If the content cannot be decoded as UTF-8
|
|
104
|
+
"""
|
|
105
|
+
import requests
|
|
106
|
+
|
|
107
|
+
response = requests.get(url, timeout=10)
|
|
108
|
+
response.raise_for_status() # Raise exception for HTTP errors
|
|
109
|
+
return response.text
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _apply_templates(text: str) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Apply template substitutions to instruction text.
|
|
115
|
+
|
|
116
|
+
Supported templates:
|
|
117
|
+
{{currentDate}} - Current date in format "24 July 2025"
|
|
118
|
+
{{url:https://...}} - Content fetched from the specified URL
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
text: The text to process
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Text with template substitutions applied
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
requests.RequestException: If a URL in {{url:...}} cannot be fetched
|
|
128
|
+
UnicodeDecodeError: If URL content cannot be decoded as UTF-8
|
|
129
|
+
"""
|
|
130
|
+
import re
|
|
131
|
+
from datetime import datetime
|
|
132
|
+
|
|
133
|
+
# Apply {{currentDate}} template
|
|
134
|
+
current_date = datetime.now().strftime("%d %B %Y")
|
|
135
|
+
text = text.replace("{{currentDate}}", current_date)
|
|
136
|
+
|
|
137
|
+
# Apply {{url:...}} templates
|
|
138
|
+
url_pattern = re.compile(r"\{\{url:(https?://[^}]+)\}\}")
|
|
139
|
+
|
|
140
|
+
def replace_url(match):
|
|
141
|
+
url = match.group(1)
|
|
142
|
+
return _fetch_url_content(url)
|
|
143
|
+
|
|
144
|
+
text = url_pattern.sub(replace_url, text)
|
|
145
|
+
|
|
146
|
+
return text
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _resolve_instruction(instruction: str | Path | AnyUrl) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Resolve instruction from either a string, Path, or URL with template support.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
instruction: Either a string instruction, Path to a file, or URL containing the instruction
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The resolved instruction string with templates applied
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
FileNotFoundError: If the Path doesn't exist
|
|
161
|
+
PermissionError: If the Path can't be read
|
|
162
|
+
UnicodeDecodeError: If the file/URL content can't be decoded as UTF-8
|
|
163
|
+
requests.RequestException: If the URL cannot be fetched
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(instruction, Path):
|
|
166
|
+
text = instruction.read_text(encoding="utf-8")
|
|
167
|
+
elif isinstance(instruction, AnyUrl):
|
|
168
|
+
text = _fetch_url_content(str(instruction))
|
|
169
|
+
else:
|
|
170
|
+
text = instruction
|
|
171
|
+
|
|
172
|
+
# Apply template substitutions
|
|
173
|
+
return _apply_templates(text)
|
|
174
|
+
|
|
175
|
+
|
|
88
176
|
def _decorator_impl(
|
|
89
177
|
self,
|
|
90
178
|
agent_type: AgentType,
|
|
@@ -183,9 +271,9 @@ def _decorator_impl(
|
|
|
183
271
|
def agent(
|
|
184
272
|
self,
|
|
185
273
|
name: str = "default",
|
|
186
|
-
instruction_or_kwarg: Optional[str] = None,
|
|
274
|
+
instruction_or_kwarg: Optional[str | Path | AnyUrl] = None,
|
|
187
275
|
*,
|
|
188
|
-
instruction: str = "You are a helpful agent.",
|
|
276
|
+
instruction: str | Path | AnyUrl = "You are a helpful agent.",
|
|
189
277
|
servers: List[str] = [],
|
|
190
278
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
191
279
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -220,7 +308,10 @@ def agent(
|
|
|
220
308
|
Returns:
|
|
221
309
|
A decorator that registers the agent with proper type annotations
|
|
222
310
|
"""
|
|
223
|
-
|
|
311
|
+
final_instruction_raw = (
|
|
312
|
+
instruction_or_kwarg if instruction_or_kwarg is not None else instruction
|
|
313
|
+
)
|
|
314
|
+
final_instruction = _resolve_instruction(final_instruction_raw)
|
|
224
315
|
|
|
225
316
|
return _decorator_impl(
|
|
226
317
|
self,
|
|
@@ -245,9 +336,9 @@ def custom(
|
|
|
245
336
|
self,
|
|
246
337
|
cls,
|
|
247
338
|
name: str = "default",
|
|
248
|
-
instruction_or_kwarg: Optional[str] = None,
|
|
339
|
+
instruction_or_kwarg: Optional[str | Path | AnyUrl] = None,
|
|
249
340
|
*,
|
|
250
|
-
instruction: str = "You are a helpful agent.",
|
|
341
|
+
instruction: str | Path | AnyUrl = "You are a helpful agent.",
|
|
251
342
|
servers: List[str] = [],
|
|
252
343
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
253
344
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -277,7 +368,10 @@ def custom(
|
|
|
277
368
|
Returns:
|
|
278
369
|
A decorator that registers the agent with proper type annotations
|
|
279
370
|
"""
|
|
280
|
-
|
|
371
|
+
final_instruction_raw = (
|
|
372
|
+
instruction_or_kwarg if instruction_or_kwarg is not None else instruction
|
|
373
|
+
)
|
|
374
|
+
final_instruction = _resolve_instruction(final_instruction_raw)
|
|
281
375
|
|
|
282
376
|
return _decorator_impl(
|
|
283
377
|
self,
|
|
@@ -311,7 +405,7 @@ def orchestrator(
|
|
|
311
405
|
name: str,
|
|
312
406
|
*,
|
|
313
407
|
agents: List[str],
|
|
314
|
-
instruction: str = DEFAULT_INSTRUCTION_ORCHESTRATOR,
|
|
408
|
+
instruction: str | Path | AnyUrl = DEFAULT_INSTRUCTION_ORCHESTRATOR,
|
|
315
409
|
model: Optional[str] = None,
|
|
316
410
|
request_params: RequestParams | None = None,
|
|
317
411
|
use_history: bool = False,
|
|
@@ -341,6 +435,7 @@ def orchestrator(
|
|
|
341
435
|
"""
|
|
342
436
|
|
|
343
437
|
# Create final request params with plan_iterations
|
|
438
|
+
resolved_instruction = _resolve_instruction(instruction)
|
|
344
439
|
|
|
345
440
|
return cast(
|
|
346
441
|
"Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]",
|
|
@@ -348,7 +443,7 @@ def orchestrator(
|
|
|
348
443
|
self,
|
|
349
444
|
AgentType.ORCHESTRATOR,
|
|
350
445
|
name=name,
|
|
351
|
-
instruction=
|
|
446
|
+
instruction=resolved_instruction,
|
|
352
447
|
servers=[], # Orchestrators don't connect to servers directly
|
|
353
448
|
model=model,
|
|
354
449
|
use_history=use_history,
|
|
@@ -363,12 +458,65 @@ def orchestrator(
|
|
|
363
458
|
)
|
|
364
459
|
|
|
365
460
|
|
|
461
|
+
def iterative_planner(
|
|
462
|
+
self,
|
|
463
|
+
name: str,
|
|
464
|
+
*,
|
|
465
|
+
agents: List[str],
|
|
466
|
+
instruction: str | Path | AnyUrl = ITERATIVE_PLAN_SYSTEM_PROMPT_TEMPLATE,
|
|
467
|
+
model: Optional[str] = None,
|
|
468
|
+
request_params: RequestParams | None = None,
|
|
469
|
+
plan_iterations: int = -1,
|
|
470
|
+
default: bool = False,
|
|
471
|
+
api_key: str | None = None,
|
|
472
|
+
) -> Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]:
|
|
473
|
+
"""
|
|
474
|
+
Decorator to create and register an orchestrator agent with type-safe signature.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
name: Name of the orchestrator
|
|
478
|
+
agents: List of agent names this orchestrator can use
|
|
479
|
+
instruction: Base instruction for the orchestrator
|
|
480
|
+
model: Model specification string
|
|
481
|
+
use_history: Whether to maintain conversation history
|
|
482
|
+
request_params: Additional request parameters for the LLM
|
|
483
|
+
human_input: Whether to enable human input capabilities
|
|
484
|
+
plan_type: Planning approach - "full" or "iterative"
|
|
485
|
+
plan_iterations: Maximum number of planning iterations (0 for unlimited)
|
|
486
|
+
default: Whether to mark this as the default agent
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
A decorator that registers the orchestrator with proper type annotations
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# Create final request params with plan_iterations
|
|
493
|
+
resolved_instruction = _resolve_instruction(instruction)
|
|
494
|
+
|
|
495
|
+
return cast(
|
|
496
|
+
"Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]",
|
|
497
|
+
_decorator_impl(
|
|
498
|
+
self,
|
|
499
|
+
AgentType.ITERATIVE_PLANNER,
|
|
500
|
+
name=name,
|
|
501
|
+
instruction=resolved_instruction,
|
|
502
|
+
servers=[], # Orchestrators don't connect to servers directly
|
|
503
|
+
model=model,
|
|
504
|
+
use_history=False,
|
|
505
|
+
request_params=request_params,
|
|
506
|
+
child_agents=agents,
|
|
507
|
+
plan_iterations=plan_iterations,
|
|
508
|
+
default=default,
|
|
509
|
+
api_key=api_key,
|
|
510
|
+
),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
366
514
|
def router(
|
|
367
515
|
self,
|
|
368
516
|
name: str,
|
|
369
517
|
*,
|
|
370
518
|
agents: List[str],
|
|
371
|
-
instruction: Optional[str] = None,
|
|
519
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
372
520
|
servers: List[str] = [],
|
|
373
521
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
374
522
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -400,6 +548,7 @@ def router(
|
|
|
400
548
|
Returns:
|
|
401
549
|
A decorator that registers the router with proper type annotations
|
|
402
550
|
"""
|
|
551
|
+
resolved_instruction = _resolve_instruction(instruction or ROUTING_SYSTEM_INSTRUCTION)
|
|
403
552
|
|
|
404
553
|
return cast(
|
|
405
554
|
"Callable[[AgentCallable[P, R]], DecoratedRouterProtocol[P, R]]",
|
|
@@ -407,7 +556,7 @@ def router(
|
|
|
407
556
|
self,
|
|
408
557
|
AgentType.ROUTER,
|
|
409
558
|
name=name,
|
|
410
|
-
instruction=
|
|
559
|
+
instruction=resolved_instruction,
|
|
411
560
|
servers=servers,
|
|
412
561
|
model=model,
|
|
413
562
|
use_history=use_history,
|
|
@@ -429,7 +578,7 @@ def chain(
|
|
|
429
578
|
name: str,
|
|
430
579
|
*,
|
|
431
580
|
sequence: List[str],
|
|
432
|
-
instruction: Optional[str] = None,
|
|
581
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
433
582
|
cumulative: bool = False,
|
|
434
583
|
default: bool = False,
|
|
435
584
|
) -> Callable[[AgentCallable[P, R]], DecoratedChainProtocol[P, R]]:
|
|
@@ -456,6 +605,7 @@ def chain(
|
|
|
456
605
|
You are a chain that processes requests through a series of specialized agents in sequence.
|
|
457
606
|
Pass the output of each agent to the next agent in the chain.
|
|
458
607
|
"""
|
|
608
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
459
609
|
|
|
460
610
|
return cast(
|
|
461
611
|
"Callable[[AgentCallable[P, R]], DecoratedChainProtocol[P, R]]",
|
|
@@ -463,7 +613,7 @@ def chain(
|
|
|
463
613
|
self,
|
|
464
614
|
AgentType.CHAIN,
|
|
465
615
|
name=name,
|
|
466
|
-
instruction=
|
|
616
|
+
instruction=resolved_instruction,
|
|
467
617
|
sequence=sequence,
|
|
468
618
|
cumulative=cumulative,
|
|
469
619
|
default=default,
|
|
@@ -477,7 +627,7 @@ def parallel(
|
|
|
477
627
|
*,
|
|
478
628
|
fan_out: List[str],
|
|
479
629
|
fan_in: str | None = None,
|
|
480
|
-
instruction: Optional[str] = None,
|
|
630
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
481
631
|
include_request: bool = True,
|
|
482
632
|
default: bool = False,
|
|
483
633
|
) -> Callable[[AgentCallable[P, R]], DecoratedParallelProtocol[P, R]]:
|
|
@@ -499,6 +649,7 @@ def parallel(
|
|
|
499
649
|
You are a parallel processor that executes multiple agents simultaneously
|
|
500
650
|
and aggregates their results.
|
|
501
651
|
"""
|
|
652
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
502
653
|
|
|
503
654
|
return cast(
|
|
504
655
|
"Callable[[AgentCallable[P, R]], DecoratedParallelProtocol[P, R]]",
|
|
@@ -506,7 +657,7 @@ def parallel(
|
|
|
506
657
|
self,
|
|
507
658
|
AgentType.PARALLEL,
|
|
508
659
|
name=name,
|
|
509
|
-
instruction=
|
|
660
|
+
instruction=resolved_instruction,
|
|
510
661
|
servers=[], # Parallel agents don't connect to servers directly
|
|
511
662
|
fan_in=fan_in,
|
|
512
663
|
fan_out=fan_out,
|
|
@@ -522,7 +673,7 @@ def evaluator_optimizer(
|
|
|
522
673
|
*,
|
|
523
674
|
generator: str,
|
|
524
675
|
evaluator: str,
|
|
525
|
-
instruction: Optional[str] = None,
|
|
676
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
526
677
|
min_rating: str = "GOOD",
|
|
527
678
|
max_refinements: int = 3,
|
|
528
679
|
default: bool = False,
|
|
@@ -547,6 +698,7 @@ def evaluator_optimizer(
|
|
|
547
698
|
evaluated for quality, and then refined based on specific feedback until
|
|
548
699
|
it reaches an acceptable quality standard.
|
|
549
700
|
"""
|
|
701
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
550
702
|
|
|
551
703
|
return cast(
|
|
552
704
|
"Callable[[AgentCallable[P, R]], DecoratedEvaluatorOptimizerProtocol[P, R]]",
|
|
@@ -554,7 +706,7 @@ def evaluator_optimizer(
|
|
|
554
706
|
self,
|
|
555
707
|
AgentType.EVALUATOR_OPTIMIZER,
|
|
556
708
|
name=name,
|
|
557
|
-
instruction=
|
|
709
|
+
instruction=resolved_instruction,
|
|
558
710
|
servers=[], # Evaluator-optimizer doesn't connect to servers directly
|
|
559
711
|
generator=generator,
|
|
560
712
|
evaluator=evaluator,
|
mcp_agent/core/direct_factory.py
CHANGED
|
@@ -10,6 +10,7 @@ from mcp_agent.agents.workflow.evaluator_optimizer import (
|
|
|
10
10
|
EvaluatorOptimizerAgent,
|
|
11
11
|
QualityRating,
|
|
12
12
|
)
|
|
13
|
+
from mcp_agent.agents.workflow.iterative_planner import IterativePlanner
|
|
13
14
|
from mcp_agent.agents.workflow.orchestrator_agent import OrchestratorAgent
|
|
14
15
|
from mcp_agent.agents.workflow.parallel_agent import ParallelAgent
|
|
15
16
|
from mcp_agent.agents.workflow.router_agent import RouterAgent
|
|
@@ -21,9 +22,10 @@ from mcp_agent.event_progress import ProgressAction
|
|
|
21
22
|
from mcp_agent.llm.augmented_llm import RequestParams
|
|
22
23
|
from mcp_agent.llm.model_factory import ModelFactory
|
|
23
24
|
from mcp_agent.logging.logger import get_logger
|
|
25
|
+
from mcp_agent.mcp.interfaces import AgentProtocol
|
|
24
26
|
|
|
25
27
|
# Type aliases for improved readability and IDE support
|
|
26
|
-
AgentDict = Dict[str,
|
|
28
|
+
AgentDict = Dict[str, AgentProtocol]
|
|
27
29
|
AgentConfigDict = Dict[str, Dict[str, Any]]
|
|
28
30
|
T = TypeVar("T") # For generic types
|
|
29
31
|
|
|
@@ -153,7 +155,7 @@ async def create_agents_by_type(
|
|
|
153
155
|
await agent.attach_llm(
|
|
154
156
|
llm_factory,
|
|
155
157
|
request_params=config.default_request_params,
|
|
156
|
-
api_key=config.api_key
|
|
158
|
+
api_key=config.api_key,
|
|
157
159
|
)
|
|
158
160
|
result_agents[name] = agent
|
|
159
161
|
|
|
@@ -172,11 +174,11 @@ async def create_agents_by_type(
|
|
|
172
174
|
await agent.attach_llm(
|
|
173
175
|
llm_factory,
|
|
174
176
|
request_params=config.default_request_params,
|
|
175
|
-
api_key=config.api_key
|
|
177
|
+
api_key=config.api_key,
|
|
176
178
|
)
|
|
177
179
|
result_agents[name] = agent
|
|
178
180
|
|
|
179
|
-
elif agent_type == AgentType.ORCHESTRATOR:
|
|
181
|
+
elif agent_type == AgentType.ORCHESTRATOR or agent_type == AgentType.ITERATIVE_PLANNER:
|
|
180
182
|
# Get base params configured with model settings
|
|
181
183
|
base_params = (
|
|
182
184
|
config.default_request_params.model_copy()
|
|
@@ -193,24 +195,35 @@ async def create_agents_by_type(
|
|
|
193
195
|
agent = active_agents[agent_name]
|
|
194
196
|
child_agents.append(agent)
|
|
195
197
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
if AgentType.ORCHESTRATOR == agent_type:
|
|
199
|
+
# Create the orchestrator
|
|
200
|
+
orchestrator = OrchestratorAgent(
|
|
201
|
+
config=config,
|
|
202
|
+
context=app_instance.context,
|
|
203
|
+
agents=child_agents,
|
|
204
|
+
plan_iterations=agent_data.get("plan_iterations", 5),
|
|
205
|
+
plan_type=agent_data.get("plan_type", "full"),
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
orchestrator = IterativePlanner(
|
|
209
|
+
config=config,
|
|
210
|
+
context=app_instance.context,
|
|
211
|
+
agents=child_agents,
|
|
212
|
+
plan_iterations=agent_data.get("plan_iterations", 5),
|
|
213
|
+
plan_type=agent_data.get("plan_type", "full"),
|
|
214
|
+
)
|
|
204
215
|
|
|
205
216
|
# Initialize the orchestrator
|
|
206
217
|
await orchestrator.initialize()
|
|
207
218
|
|
|
208
219
|
# Attach LLM to the orchestrator
|
|
209
220
|
llm_factory = model_factory_func(model=config.model)
|
|
221
|
+
|
|
222
|
+
# print("************", config.default_request_params.instruction)
|
|
210
223
|
await orchestrator.attach_llm(
|
|
211
224
|
llm_factory,
|
|
212
225
|
request_params=config.default_request_params,
|
|
213
|
-
api_key=config.api_key
|
|
226
|
+
api_key=config.api_key,
|
|
214
227
|
)
|
|
215
228
|
|
|
216
229
|
result_agents[name] = orchestrator
|
|
@@ -274,7 +287,7 @@ async def create_agents_by_type(
|
|
|
274
287
|
await router.attach_llm(
|
|
275
288
|
llm_factory,
|
|
276
289
|
request_params=config.default_request_params,
|
|
277
|
-
api_key=config.api_key
|
|
290
|
+
api_key=config.api_key,
|
|
278
291
|
)
|
|
279
292
|
result_agents[name] = router
|
|
280
293
|
|
|
@@ -461,7 +474,6 @@ async def create_agents_in_dependency_order(
|
|
|
461
474
|
)
|
|
462
475
|
active_agents.update(evaluator_agents)
|
|
463
476
|
|
|
464
|
-
# Create orchestrator agents last since they might depend on other agents
|
|
465
477
|
if AgentType.ORCHESTRATOR.value in [agents_dict[name]["type"] for name in group]:
|
|
466
478
|
orchestrator_agents = await create_agents_by_type(
|
|
467
479
|
app_instance,
|
|
@@ -476,6 +488,21 @@ async def create_agents_in_dependency_order(
|
|
|
476
488
|
)
|
|
477
489
|
active_agents.update(orchestrator_agents)
|
|
478
490
|
|
|
491
|
+
# Create orchestrator2 agents last since they might depend on other agents
|
|
492
|
+
if AgentType.ITERATIVE_PLANNER.value in [agents_dict[name]["type"] for name in group]:
|
|
493
|
+
orchestrator2_agents = await create_agents_by_type(
|
|
494
|
+
app_instance,
|
|
495
|
+
{
|
|
496
|
+
name: agents_dict[name]
|
|
497
|
+
for name in group
|
|
498
|
+
if agents_dict[name]["type"] == AgentType.ITERATIVE_PLANNER.value
|
|
499
|
+
},
|
|
500
|
+
AgentType.ITERATIVE_PLANNER,
|
|
501
|
+
active_agents,
|
|
502
|
+
model_factory_func,
|
|
503
|
+
)
|
|
504
|
+
active_agents.update(orchestrator2_agents)
|
|
505
|
+
|
|
479
506
|
return active_agents
|
|
480
507
|
|
|
481
508
|
|
mcp_agent/core/fastagent.py
CHANGED
|
@@ -31,6 +31,9 @@ from mcp_agent.core.direct_decorators import (
|
|
|
31
31
|
from mcp_agent.core.direct_decorators import (
|
|
32
32
|
evaluator_optimizer as evaluator_optimizer_decorator,
|
|
33
33
|
)
|
|
34
|
+
from mcp_agent.core.direct_decorators import (
|
|
35
|
+
iterative_planner as orchestrator2_decorator,
|
|
36
|
+
)
|
|
34
37
|
from mcp_agent.core.direct_decorators import (
|
|
35
38
|
orchestrator as orchestrator_decorator,
|
|
36
39
|
)
|
|
@@ -249,6 +252,7 @@ class FastAgent:
|
|
|
249
252
|
agent = agent_decorator
|
|
250
253
|
custom = custom_decorator
|
|
251
254
|
orchestrator = orchestrator_decorator
|
|
255
|
+
iterative_planner = orchestrator2_decorator
|
|
252
256
|
router = router_decorator
|
|
253
257
|
chain = chain_decorator
|
|
254
258
|
parallel = parallel_decorator
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Utilities for detecting and processing Mermaid diagrams in text content."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import re
|
|
5
|
+
import zlib
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
# Mermaid chart viewer URL prefix
|
|
10
|
+
MERMAID_VIEWER_URL = "https://www.mermaidchart.com/play#"
|
|
11
|
+
# mermaid.live#pako= also works but the playground has better ux
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class MermaidDiagram:
|
|
16
|
+
"""Represents a detected Mermaid diagram."""
|
|
17
|
+
|
|
18
|
+
content: str
|
|
19
|
+
title: Optional[str] = None
|
|
20
|
+
start_pos: int = 0
|
|
21
|
+
end_pos: int = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_mermaid_diagrams(text: str) -> List[MermaidDiagram]:
|
|
25
|
+
"""
|
|
26
|
+
Extract all Mermaid diagram blocks from text content.
|
|
27
|
+
|
|
28
|
+
Handles both simple mermaid blocks and blocks with titles:
|
|
29
|
+
- ```mermaid
|
|
30
|
+
- ```mermaid title={Some Title}
|
|
31
|
+
|
|
32
|
+
Also extracts titles from within the diagram content.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
text: The text content to search for Mermaid diagrams
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
List of MermaidDiagram objects found in the text
|
|
39
|
+
"""
|
|
40
|
+
diagrams = []
|
|
41
|
+
|
|
42
|
+
# Pattern to match mermaid code blocks with optional title
|
|
43
|
+
# Matches: ```mermaid or ```mermaid title={...}
|
|
44
|
+
pattern = r"```mermaid(?:\s+title=\{([^}]+)\})?\s*\n(.*?)```"
|
|
45
|
+
|
|
46
|
+
for match in re.finditer(pattern, text, re.DOTALL):
|
|
47
|
+
title = match.group(1) # May be None if no title
|
|
48
|
+
content = match.group(2).strip()
|
|
49
|
+
|
|
50
|
+
if content: # Only add if there's actual diagram content
|
|
51
|
+
# If no title from code fence, look for title in the content
|
|
52
|
+
if not title:
|
|
53
|
+
# Look for various title patterns in mermaid diagrams
|
|
54
|
+
# pie title, graph title, etc.
|
|
55
|
+
title_patterns = [
|
|
56
|
+
r"^\s*title\s+(.+?)(?:\n|$)", # Generic title
|
|
57
|
+
r"^\s*pie\s+title\s+(.+?)(?:\n|$)", # Pie chart title
|
|
58
|
+
r"^\s*gantt\s+title\s+(.+?)(?:\n|$)", # Gantt chart title
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
for title_pattern in title_patterns:
|
|
62
|
+
title_match = re.search(title_pattern, content, re.MULTILINE)
|
|
63
|
+
if title_match:
|
|
64
|
+
title = title_match.group(1).strip()
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
diagrams.append(
|
|
68
|
+
MermaidDiagram(
|
|
69
|
+
content=content, title=title, start_pos=match.start(), end_pos=match.end()
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return diagrams
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def create_mermaid_live_link(diagram_content: str) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Create a Mermaid Live Editor link from diagram content.
|
|
79
|
+
|
|
80
|
+
The link uses pako compression (zlib) and base64 encoding.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
diagram_content: The Mermaid diagram source code
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Complete URL to Mermaid Live Editor
|
|
87
|
+
"""
|
|
88
|
+
# Create the JSON structure expected by Mermaid Live
|
|
89
|
+
# Escape newlines and quotes in the diagram content
|
|
90
|
+
escaped_content = diagram_content.replace('"', '\\"').replace("\n", "\\n")
|
|
91
|
+
json_str = f'{{"code":"{escaped_content}","mermaid":{{"theme":"default"}},"updateEditor":false,"autoSync":true,"updateDiagram":false}}'
|
|
92
|
+
|
|
93
|
+
# Compress using zlib (pako compatible)
|
|
94
|
+
compressed = zlib.compress(json_str.encode("utf-8"))
|
|
95
|
+
|
|
96
|
+
# Base64 encode
|
|
97
|
+
encoded = base64.urlsafe_b64encode(compressed).decode("utf-8")
|
|
98
|
+
|
|
99
|
+
# Remove padding characters as Mermaid Live doesn't use them
|
|
100
|
+
encoded = encoded.rstrip("=")
|
|
101
|
+
|
|
102
|
+
return f"{MERMAID_VIEWER_URL}pako:{encoded}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def format_mermaid_links(diagrams: List[MermaidDiagram]) -> List[str]:
|
|
106
|
+
"""
|
|
107
|
+
Format Mermaid diagrams as markdown links.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
diagrams: List of MermaidDiagram objects
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of formatted markdown strings
|
|
114
|
+
"""
|
|
115
|
+
links = []
|
|
116
|
+
|
|
117
|
+
for i, diagram in enumerate(diagrams, 1):
|
|
118
|
+
link = create_mermaid_live_link(diagram.content)
|
|
119
|
+
|
|
120
|
+
if diagram.title:
|
|
121
|
+
# Use the title from the diagram with number
|
|
122
|
+
markdown = f"Diagram {i} - {diagram.title}: [Open Diagram]({link})"
|
|
123
|
+
else:
|
|
124
|
+
# Use generic numbering
|
|
125
|
+
markdown = f"Diagram {i}: [Open Diagram]({link})"
|
|
126
|
+
|
|
127
|
+
links.append(markdown)
|
|
128
|
+
|
|
129
|
+
return links
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def detect_diagram_type(content: str) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Detect the type of mermaid diagram from content.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
content: The mermaid diagram source code
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Human-readable diagram type name
|
|
141
|
+
"""
|
|
142
|
+
content_lower = content.strip().lower()
|
|
143
|
+
|
|
144
|
+
# Check for common diagram types
|
|
145
|
+
if content_lower.startswith(("graph ", "flowchart ")):
|
|
146
|
+
return "Flowchart"
|
|
147
|
+
elif content_lower.startswith("sequencediagram"):
|
|
148
|
+
return "Sequence"
|
|
149
|
+
elif content_lower.startswith("pie"):
|
|
150
|
+
return "Pie Chart"
|
|
151
|
+
elif content_lower.startswith("gantt"):
|
|
152
|
+
return "Gantt Chart"
|
|
153
|
+
elif content_lower.startswith("classdiagram"):
|
|
154
|
+
return "Class Diagram"
|
|
155
|
+
elif content_lower.startswith("statediagram"):
|
|
156
|
+
return "State Diagram"
|
|
157
|
+
elif content_lower.startswith("erdiagram"):
|
|
158
|
+
return "ER Diagram"
|
|
159
|
+
elif content_lower.startswith("journey"):
|
|
160
|
+
return "User Journey"
|
|
161
|
+
elif content_lower.startswith("gitgraph"):
|
|
162
|
+
return "Git Graph"
|
|
163
|
+
elif content_lower.startswith("c4context"):
|
|
164
|
+
return "C4 Context"
|
|
165
|
+
elif content_lower.startswith("mindmap"):
|
|
166
|
+
return "Mind Map"
|
|
167
|
+
elif content_lower.startswith("timeline"):
|
|
168
|
+
return "Timeline"
|
|
169
|
+
else:
|
|
170
|
+
return "Diagram"
|