fast-agent-mcp 0.0.14__py3-none-any.whl → 0.0.15__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.14.dist-info → fast_agent_mcp-0.0.15.dist-info}/METADATA +51 -30
- {fast_agent_mcp-0.0.14.dist-info → fast_agent_mcp-0.0.15.dist-info}/RECORD +17 -10
- mcp_agent/core/agent_app.py +163 -0
- mcp_agent/core/agent_types.py +16 -0
- mcp_agent/core/agent_utils.py +65 -0
- mcp_agent/core/error_handling.py +23 -0
- mcp_agent/core/fastagent.py +220 -306
- mcp_agent/core/proxies.py +127 -0
- mcp_agent/core/types.py +22 -0
- mcp_agent/resources/examples/internal/social.py +66 -0
- mcp_agent/resources/examples/workflows/parallel.py +4 -0
- mcp_agent/workflows/llm/augmented_llm.py +75 -1
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +49 -18
- mcp_agent/workflows/parallel/parallel_llm.py +32 -7
- {fast_agent_mcp-0.0.14.dist-info → fast_agent_mcp-0.0.15.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.0.14.dist-info → fast_agent_mcp-0.0.15.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.0.14.dist-info → fast_agent_mcp-0.0.15.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/fastagent.py
CHANGED
@@ -10,291 +10,57 @@ from typing import (
|
|
10
10
|
Callable,
|
11
11
|
TypeVar,
|
12
12
|
Any,
|
13
|
-
Union,
|
14
|
-
TypeAlias,
|
15
13
|
Literal,
|
16
14
|
)
|
17
|
-
from enum import Enum
|
18
15
|
import yaml
|
19
16
|
import argparse
|
20
17
|
from contextlib import asynccontextmanager
|
21
18
|
|
22
|
-
from mcp_agent.core.exceptions import (
|
23
|
-
AgentConfigError,
|
24
|
-
CircularDependencyError,
|
25
|
-
ModelConfigError,
|
26
|
-
PromptExitError,
|
27
|
-
ServerConfigError,
|
28
|
-
ProviderKeyError,
|
29
|
-
ServerInitializationError,
|
30
|
-
)
|
31
|
-
|
32
19
|
from mcp_agent.app import MCPApp
|
33
20
|
from mcp_agent.agents.agent import Agent, AgentConfig
|
34
21
|
from mcp_agent.context_dependent import ContextDependent
|
22
|
+
from mcp_agent.config import Settings
|
35
23
|
from mcp_agent.event_progress import ProgressAction
|
36
|
-
from mcp_agent.workflows.orchestrator.orchestrator import Orchestrator
|
37
|
-
from mcp_agent.workflows.parallel.parallel_llm import ParallelLLM
|
38
24
|
from mcp_agent.workflows.evaluator_optimizer.evaluator_optimizer import (
|
39
25
|
EvaluatorOptimizerLLM,
|
40
26
|
QualityRating,
|
41
27
|
)
|
42
|
-
from mcp_agent.workflows.router.router_llm import LLMRouter
|
43
|
-
from mcp_agent.config import Settings
|
44
|
-
from rich import print
|
45
|
-
from mcp_agent.progress_display import progress_display
|
46
|
-
from mcp_agent.workflows.llm.model_factory import ModelFactory
|
47
28
|
from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM, RequestParams
|
29
|
+
from mcp_agent.workflows.llm.model_factory import ModelFactory
|
30
|
+
from mcp_agent.workflows.orchestrator.orchestrator import Orchestrator
|
31
|
+
from mcp_agent.workflows.parallel.parallel_llm import ParallelLLM
|
32
|
+
from mcp_agent.workflows.router.router_llm import LLMRouter
|
48
33
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
34
|
+
from mcp_agent.core.agent_app import AgentApp
|
35
|
+
from mcp_agent.core.agent_types import AgentType
|
36
|
+
from mcp_agent.core.agent_utils import unwrap_proxy, get_agent_instances, log_agent_load
|
37
|
+
from mcp_agent.core.error_handling import handle_error
|
38
|
+
from mcp_agent.core.proxies import (
|
39
|
+
BaseAgentProxy,
|
40
|
+
LLMAgentProxy,
|
41
|
+
WorkflowProxy,
|
42
|
+
RouterProxy,
|
43
|
+
ChainProxy,
|
44
|
+
)
|
45
|
+
from mcp_agent.core.types import AgentOrWorkflow, ProxyDict
|
46
|
+
from mcp_agent.core.exceptions import (
|
47
|
+
AgentConfigError,
|
48
|
+
CircularDependencyError,
|
49
|
+
ModelConfigError,
|
50
|
+
PromptExitError,
|
51
|
+
ServerConfigError,
|
52
|
+
ProviderKeyError,
|
53
|
+
ServerInitializationError,
|
54
|
+
)
|
62
55
|
|
63
|
-
|
64
|
-
|
65
|
-
PARALLEL = "parallel"
|
66
|
-
EVALUATOR_OPTIMIZER = "evaluator_optimizer"
|
67
|
-
ROUTER = "router"
|
68
|
-
CHAIN = "chain"
|
56
|
+
# TODO -- reinstate once Windows&Python 3.13 platform issues are fixed
|
57
|
+
# import readline # noqa: F401
|
69
58
|
|
59
|
+
from rich import print
|
70
60
|
|
71
61
|
T = TypeVar("T") # For the wrapper classes
|
72
62
|
|
73
63
|
|
74
|
-
class BaseAgentProxy:
|
75
|
-
"""Base class for all proxy types"""
|
76
|
-
|
77
|
-
def __init__(self, app: MCPApp, name: str):
|
78
|
-
self._app = app
|
79
|
-
self._name = name
|
80
|
-
|
81
|
-
async def __call__(self, message: Optional[str] = None) -> str:
|
82
|
-
"""Allow: agent.researcher('message')"""
|
83
|
-
return await self.send(message)
|
84
|
-
|
85
|
-
async def send(self, message: Optional[str] = None) -> str:
|
86
|
-
"""Allow: agent.researcher.send('message')"""
|
87
|
-
if message is None:
|
88
|
-
return await self.prompt()
|
89
|
-
return await self.generate_str(message)
|
90
|
-
|
91
|
-
async def prompt(self, default_prompt: str = "") -> str:
|
92
|
-
"""Allow: agent.researcher.prompt()"""
|
93
|
-
return await self._app.prompt(self._name, default_prompt)
|
94
|
-
|
95
|
-
async def generate_str(self, message: str) -> str:
|
96
|
-
"""Generate response for a message - must be implemented by subclasses"""
|
97
|
-
raise NotImplementedError("Subclasses must implement generate_str")
|
98
|
-
|
99
|
-
|
100
|
-
class AgentProxy(BaseAgentProxy):
|
101
|
-
"""Legacy proxy for individual agent operations"""
|
102
|
-
|
103
|
-
async def generate_str(self, message: str) -> str:
|
104
|
-
return await self._app.send(self._name, message)
|
105
|
-
|
106
|
-
|
107
|
-
class LLMAgentProxy(BaseAgentProxy):
|
108
|
-
"""Proxy for regular agents that use _llm.generate_str()"""
|
109
|
-
|
110
|
-
def __init__(self, app: MCPApp, name: str, agent: Agent):
|
111
|
-
super().__init__(app, name)
|
112
|
-
self._agent = agent
|
113
|
-
|
114
|
-
async def generate_str(self, message: str) -> str:
|
115
|
-
return await self._agent._llm.generate_str(message)
|
116
|
-
|
117
|
-
|
118
|
-
class WorkflowProxy(BaseAgentProxy):
|
119
|
-
"""Proxy for workflow types that implement generate_str() directly"""
|
120
|
-
|
121
|
-
def __init__(self, app: MCPApp, name: str, workflow: WorkflowType):
|
122
|
-
super().__init__(app, name)
|
123
|
-
self._workflow = workflow
|
124
|
-
|
125
|
-
async def generate_str(self, message: str) -> str:
|
126
|
-
return await self._workflow.generate_str(message)
|
127
|
-
|
128
|
-
|
129
|
-
class RouterProxy(BaseAgentProxy):
|
130
|
-
"""Proxy for LLM Routers"""
|
131
|
-
|
132
|
-
def __init__(self, app: MCPApp, name: str, workflow: WorkflowType):
|
133
|
-
super().__init__(app, name)
|
134
|
-
self._workflow = workflow
|
135
|
-
|
136
|
-
async def generate_str(self, message: str) -> str:
|
137
|
-
results = await self._workflow.route(message)
|
138
|
-
if not results:
|
139
|
-
return "No appropriate route found for the request."
|
140
|
-
|
141
|
-
# Get the top result
|
142
|
-
top_result = results[0]
|
143
|
-
if isinstance(top_result.result, Agent):
|
144
|
-
# Agent route - delegate to the agent
|
145
|
-
agent = top_result.result
|
146
|
-
|
147
|
-
return await agent._llm.generate_str(message)
|
148
|
-
elif isinstance(top_result.result, str):
|
149
|
-
# Server route - use the router directly
|
150
|
-
return "Tool call requested by router - not yet supported"
|
151
|
-
|
152
|
-
return f"Routed to: {top_result.result} ({top_result.confidence}): {top_result.reasoning}"
|
153
|
-
|
154
|
-
|
155
|
-
class ChainProxy(BaseAgentProxy):
|
156
|
-
"""Proxy for chained agent operations"""
|
157
|
-
|
158
|
-
def __init__(
|
159
|
-
self, app: MCPApp, name: str, sequence: List[str], agent_proxies: ProxyDict
|
160
|
-
):
|
161
|
-
super().__init__(app, name)
|
162
|
-
self._sequence = sequence
|
163
|
-
self._agent_proxies = agent_proxies
|
164
|
-
|
165
|
-
async def generate_str(self, message: str) -> str:
|
166
|
-
"""Chain message through a sequence of agents"""
|
167
|
-
current_message = message
|
168
|
-
|
169
|
-
for agent_name in self._sequence:
|
170
|
-
proxy = self._agent_proxies[agent_name]
|
171
|
-
current_message = await proxy.generate_str(current_message)
|
172
|
-
|
173
|
-
return current_message
|
174
|
-
|
175
|
-
|
176
|
-
class AgentApp:
|
177
|
-
"""Main application wrapper"""
|
178
|
-
|
179
|
-
def __init__(self, app: MCPApp, agents: ProxyDict):
|
180
|
-
self._app = app
|
181
|
-
self._agents = agents
|
182
|
-
# Optional: set default agent for direct calls
|
183
|
-
self._default = next(iter(agents)) if agents else None
|
184
|
-
|
185
|
-
async def send(self, agent_name: str, message: Optional[str]) -> str:
|
186
|
-
"""Core message sending"""
|
187
|
-
if agent_name not in self._agents:
|
188
|
-
raise ValueError(f"No agent named '{agent_name}'")
|
189
|
-
|
190
|
-
if not message or "" == message:
|
191
|
-
return await self.prompt(agent_name)
|
192
|
-
|
193
|
-
proxy = self._agents[agent_name]
|
194
|
-
return await proxy.generate_str(message)
|
195
|
-
|
196
|
-
async def prompt(self, agent_name: Optional[str] = None, default: str = "") -> str:
|
197
|
-
"""
|
198
|
-
Interactive prompt for sending messages with advanced features.
|
199
|
-
|
200
|
-
Args:
|
201
|
-
agent_name: Optional target agent name (uses default if not specified)
|
202
|
-
default: Default message to use when user presses enter
|
203
|
-
"""
|
204
|
-
from .enhanced_prompt import get_enhanced_input, handle_special_commands
|
205
|
-
|
206
|
-
agent = agent_name or self._default
|
207
|
-
|
208
|
-
if agent not in self._agents:
|
209
|
-
raise ValueError(f"No agent named '{agent}'")
|
210
|
-
|
211
|
-
# Pass all available agent names for auto-completion
|
212
|
-
available_agents = list(self._agents.keys())
|
213
|
-
|
214
|
-
# Create agent_types dictionary mapping agent names to their types
|
215
|
-
agent_types = {}
|
216
|
-
for name, proxy in self._agents.items():
|
217
|
-
# Determine agent type based on the proxy type
|
218
|
-
if isinstance(proxy, LLMAgentProxy):
|
219
|
-
# Convert AgentType.BASIC.value ("agent") to "Agent"
|
220
|
-
agent_types[name] = "Agent"
|
221
|
-
elif isinstance(proxy, RouterProxy):
|
222
|
-
agent_types[name] = "Router"
|
223
|
-
elif isinstance(proxy, ChainProxy):
|
224
|
-
agent_types[name] = "Chain"
|
225
|
-
elif isinstance(proxy, WorkflowProxy):
|
226
|
-
# For workflow proxies, check the workflow type
|
227
|
-
workflow = proxy._workflow
|
228
|
-
if isinstance(workflow, Orchestrator):
|
229
|
-
agent_types[name] = "Orchestrator"
|
230
|
-
elif isinstance(workflow, ParallelLLM):
|
231
|
-
agent_types[name] = "Parallel"
|
232
|
-
elif isinstance(workflow, EvaluatorOptimizerLLM):
|
233
|
-
agent_types[name] = "Evaluator"
|
234
|
-
else:
|
235
|
-
agent_types[name] = "Workflow"
|
236
|
-
|
237
|
-
result = ""
|
238
|
-
while True:
|
239
|
-
with progress_display.paused():
|
240
|
-
# Use the enhanced input method with advanced features
|
241
|
-
user_input = await get_enhanced_input(
|
242
|
-
agent_name=agent,
|
243
|
-
default=default,
|
244
|
-
show_default=(default != ""),
|
245
|
-
show_stop_hint=True,
|
246
|
-
multiline=False, # Default to single-line mode
|
247
|
-
available_agent_names=available_agents,
|
248
|
-
syntax=None, # Can enable syntax highlighting for code input
|
249
|
-
agent_types=agent_types, # Pass agent types for display
|
250
|
-
)
|
251
|
-
|
252
|
-
# Handle special commands
|
253
|
-
command_result = await handle_special_commands(user_input, self)
|
254
|
-
|
255
|
-
# Check if we should switch agents
|
256
|
-
if (
|
257
|
-
isinstance(command_result, dict)
|
258
|
-
and "switch_agent" in command_result
|
259
|
-
):
|
260
|
-
agent = command_result["switch_agent"]
|
261
|
-
continue
|
262
|
-
|
263
|
-
# Skip further processing if command was handled
|
264
|
-
if command_result:
|
265
|
-
continue
|
266
|
-
|
267
|
-
if user_input.upper() == "STOP":
|
268
|
-
return result
|
269
|
-
if user_input == "":
|
270
|
-
continue
|
271
|
-
|
272
|
-
result = await self.send(agent, user_input)
|
273
|
-
|
274
|
-
return result
|
275
|
-
|
276
|
-
def __getattr__(self, name: str) -> AgentProxy:
|
277
|
-
"""Support: agent.researcher"""
|
278
|
-
if name not in self._agents:
|
279
|
-
raise AttributeError(f"No agent named '{name}'")
|
280
|
-
return AgentProxy(self, name)
|
281
|
-
|
282
|
-
def __getitem__(self, name: str) -> AgentProxy:
|
283
|
-
"""Support: agent['researcher']"""
|
284
|
-
if name not in self._agents:
|
285
|
-
raise KeyError(f"No agent named '{name}'")
|
286
|
-
return AgentProxy(self, name)
|
287
|
-
|
288
|
-
async def __call__(
|
289
|
-
self, message: Optional[str] = "", agent_name: Optional[str] = None
|
290
|
-
) -> str:
|
291
|
-
"""Support: agent('message')"""
|
292
|
-
target = agent_name or self._default
|
293
|
-
if not target:
|
294
|
-
raise ValueError("No default agent available")
|
295
|
-
return await self.send(target, message)
|
296
|
-
|
297
|
-
|
298
64
|
class FastAgent(ContextDependent):
|
299
65
|
"""
|
300
66
|
A decorator-based interface for MCP Agent applications.
|
@@ -318,11 +84,27 @@ class FastAgent(ContextDependent):
|
|
318
84
|
"--model",
|
319
85
|
help="Override the default model for all agents. Precedence is default < config_file < command line < constructor",
|
320
86
|
)
|
87
|
+
parser.add_argument(
|
88
|
+
"--agent",
|
89
|
+
help="Specify the agent to send a message to (used with --message)",
|
90
|
+
)
|
91
|
+
parser.add_argument(
|
92
|
+
"-m", "--message",
|
93
|
+
help="Message to send to the specified agent (requires --agent)",
|
94
|
+
)
|
95
|
+
parser.add_argument(
|
96
|
+
"--quiet", action="store_true",
|
97
|
+
help="Disable progress display, tool and message logging for cleaner output",
|
98
|
+
)
|
321
99
|
self.args = parser.parse_args()
|
100
|
+
|
101
|
+
# Quiet mode will be handled in _load_config()
|
322
102
|
|
323
103
|
self.name = name
|
324
104
|
self.config_path = config_path
|
325
105
|
self._load_config()
|
106
|
+
|
107
|
+
# Create the MCPApp with the config
|
326
108
|
self.app = MCPApp(
|
327
109
|
name=name,
|
328
110
|
settings=Settings(**self.config) if hasattr(self, "config") else None,
|
@@ -350,7 +132,7 @@ class FastAgent(ContextDependent):
|
|
350
132
|
AgentType.EVALUATOR_OPTIMIZER.value,
|
351
133
|
AgentType.CHAIN.value,
|
352
134
|
]:
|
353
|
-
self.
|
135
|
+
log_agent_load(self.app, name)
|
354
136
|
if agent_type == AgentType.BASIC.value:
|
355
137
|
if not isinstance(instance, Agent):
|
356
138
|
raise TypeError(
|
@@ -397,7 +179,7 @@ class FastAgent(ContextDependent):
|
|
397
179
|
"""Load configuration from YAML file, properly handling without dotenv processing"""
|
398
180
|
if self.config_path:
|
399
181
|
with open(self.config_path) as f:
|
400
|
-
self.config = yaml.safe_load(f)
|
182
|
+
self.config = yaml.safe_load(f) or {}
|
401
183
|
|
402
184
|
def _validate_server_references(self) -> None:
|
403
185
|
"""
|
@@ -638,6 +420,7 @@ class FastAgent(ContextDependent):
|
|
638
420
|
def agent(
|
639
421
|
self,
|
640
422
|
name: str = "Agent",
|
423
|
+
instruction_or_kwarg: str = None,
|
641
424
|
*,
|
642
425
|
instruction: str = "You are a helpful agent.",
|
643
426
|
servers: List[str] = [],
|
@@ -651,13 +434,26 @@ class FastAgent(ContextDependent):
|
|
651
434
|
|
652
435
|
Args:
|
653
436
|
name: Name of the agent
|
654
|
-
|
437
|
+
instruction_or_kwarg: Optional positional parameter for instruction
|
438
|
+
instruction: Base instruction for the agent (keyword arg)
|
655
439
|
servers: List of server names the agent should connect to
|
656
440
|
model: Model specification string (highest precedence)
|
657
441
|
use_history: Whether to maintain conversation history
|
658
442
|
request_params: Additional request parameters for the LLM
|
659
443
|
human_input: Whether to enable human input capabilities
|
444
|
+
|
445
|
+
The instruction can be provided either as a second positional argument
|
446
|
+
or as a keyword argument. Positional argument takes precedence when both are provided.
|
447
|
+
|
448
|
+
Usage:
|
449
|
+
@fast.agent("agent_name", "Your instruction here") # Using positional arg
|
450
|
+
@fast.agent("agent_name", instruction="Your instruction here") # Using keyword arg
|
660
451
|
"""
|
452
|
+
# Use positional argument if provided, otherwise use keyword argument
|
453
|
+
final_instruction = (
|
454
|
+
instruction_or_kwarg if instruction_or_kwarg is not None else instruction
|
455
|
+
)
|
456
|
+
|
661
457
|
decorator = self._create_decorator(
|
662
458
|
AgentType.BASIC,
|
663
459
|
default_name="Agent",
|
@@ -665,7 +461,7 @@ class FastAgent(ContextDependent):
|
|
665
461
|
default_use_history=True,
|
666
462
|
)(
|
667
463
|
name=name,
|
668
|
-
instruction=
|
464
|
+
instruction=final_instruction,
|
669
465
|
servers=servers,
|
670
466
|
model=model,
|
671
467
|
use_history=use_history,
|
@@ -726,25 +522,47 @@ class FastAgent(ContextDependent):
|
|
726
522
|
def parallel(
|
727
523
|
self,
|
728
524
|
name: str,
|
729
|
-
fan_in: str,
|
730
525
|
fan_out: List[str],
|
526
|
+
fan_in: Optional[str] = None,
|
731
527
|
instruction: str = "",
|
732
528
|
model: str | None = None,
|
733
529
|
use_history: bool = True,
|
734
530
|
request_params: Optional[Dict] = None,
|
531
|
+
include_request: bool = True,
|
735
532
|
) -> Callable:
|
736
533
|
"""
|
737
534
|
Decorator to create and register a parallel executing agent.
|
738
535
|
|
739
536
|
Args:
|
740
537
|
name: Name of the parallel executing agent
|
741
|
-
fan_in: Name of collecting agent
|
742
538
|
fan_out: List of parallel execution agents
|
539
|
+
fan_in: Optional name of collecting agent. If not provided, a passthrough agent
|
540
|
+
will be created automatically with the name "{name}_fan_in"
|
743
541
|
instruction: Optional instruction for the parallel agent
|
744
542
|
model: Model specification string
|
745
543
|
use_history: Whether to maintain conversation history
|
746
544
|
request_params: Additional request parameters for the LLM
|
545
|
+
include_request: Whether to include the original request in the fan-in message
|
747
546
|
"""
|
547
|
+
# If fan_in is not provided, create a passthrough agent with a derived name
|
548
|
+
if fan_in is None:
|
549
|
+
passthrough_name = f"{name}_fan_in"
|
550
|
+
|
551
|
+
# Register the passthrough agent directly in self.agents
|
552
|
+
self.agents[passthrough_name] = {
|
553
|
+
"config": AgentConfig(
|
554
|
+
name=passthrough_name,
|
555
|
+
instruction=f"Passthrough fan-in for {name}",
|
556
|
+
servers=[],
|
557
|
+
use_history=use_history,
|
558
|
+
),
|
559
|
+
"type": AgentType.BASIC.value, # Using BASIC type since we're just attaching a PassthroughLLM
|
560
|
+
"func": lambda x: x, # Simple passthrough function (never actually called)
|
561
|
+
}
|
562
|
+
|
563
|
+
# Use this passthrough as the fan-in
|
564
|
+
fan_in = passthrough_name
|
565
|
+
|
748
566
|
decorator = self._create_decorator(
|
749
567
|
AgentType.PARALLEL,
|
750
568
|
default_instruction="",
|
@@ -758,6 +576,7 @@ class FastAgent(ContextDependent):
|
|
758
576
|
model=model,
|
759
577
|
use_history=use_history,
|
760
578
|
request_params=request_params,
|
579
|
+
include_request=include_request,
|
761
580
|
)
|
762
581
|
return decorator
|
763
582
|
|
@@ -848,6 +667,7 @@ class FastAgent(ContextDependent):
|
|
848
667
|
model: str | None = None,
|
849
668
|
use_history: bool = True,
|
850
669
|
request_params: Optional[Dict] = None,
|
670
|
+
continue_with_final: bool = True,
|
851
671
|
) -> Callable:
|
852
672
|
"""
|
853
673
|
Decorator to create and register a chain of agents.
|
@@ -860,6 +680,7 @@ class FastAgent(ContextDependent):
|
|
860
680
|
model: Model specification string (not used directly in chain)
|
861
681
|
use_history: Whether to maintain conversation history
|
862
682
|
request_params: Additional request parameters
|
683
|
+
continue_with_final: When using prompt(), whether to continue with the final agent after processing chain (default: True)
|
863
684
|
"""
|
864
685
|
# Support both parameter names
|
865
686
|
agent_sequence = sequence or agents
|
@@ -884,6 +705,37 @@ class FastAgent(ContextDependent):
|
|
884
705
|
model=model,
|
885
706
|
use_history=use_history,
|
886
707
|
request_params=request_params,
|
708
|
+
continue_with_final=continue_with_final,
|
709
|
+
)
|
710
|
+
return decorator
|
711
|
+
|
712
|
+
def passthrough(
|
713
|
+
self,
|
714
|
+
name: str = "Passthrough",
|
715
|
+
use_history: bool = True,
|
716
|
+
**kwargs
|
717
|
+
) -> Callable:
|
718
|
+
"""
|
719
|
+
Decorator to create and register a passthrough agent.
|
720
|
+
A passthrough agent simply returns any input message without modification.
|
721
|
+
|
722
|
+
This is useful for parallel workflows where no fan-in aggregation is needed
|
723
|
+
(the fan-in agent can be a passthrough that simply returns the combined outputs).
|
724
|
+
|
725
|
+
Args:
|
726
|
+
name: Name of the passthrough agent
|
727
|
+
use_history: Whether to maintain conversation history
|
728
|
+
**kwargs: Additional parameters (ignored, for compatibility)
|
729
|
+
"""
|
730
|
+
decorator = self._create_decorator(
|
731
|
+
AgentType.BASIC, # Using BASIC agent type since we'll use a regular agent with PassthroughLLM
|
732
|
+
default_name="Passthrough",
|
733
|
+
default_instruction="Passthrough agent that returns input without modification",
|
734
|
+
default_use_history=use_history,
|
735
|
+
wrapper_needed=True,
|
736
|
+
)(
|
737
|
+
name=name,
|
738
|
+
use_history=use_history,
|
887
739
|
)
|
888
740
|
return decorator
|
889
741
|
|
@@ -920,19 +772,42 @@ class FastAgent(ContextDependent):
|
|
920
772
|
|
921
773
|
# Type-specific initialization
|
922
774
|
if agent_type == AgentType.BASIC:
|
923
|
-
#
|
924
|
-
|
925
|
-
|
926
|
-
#
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
agent
|
933
|
-
|
934
|
-
|
935
|
-
|
775
|
+
# Get the agent name for special handling
|
776
|
+
agent_name = agent_data["config"].name
|
777
|
+
|
778
|
+
# Check if this is an agent that should use the PassthroughLLM
|
779
|
+
if agent_name.endswith("_fan_in") or agent_name.startswith("passthrough"):
|
780
|
+
# Import here to avoid circular imports
|
781
|
+
from mcp_agent.workflows.llm.augmented_llm import PassthroughLLM
|
782
|
+
|
783
|
+
# Create basic agent with configuration
|
784
|
+
agent = Agent(config=config, context=agent_app.context)
|
785
|
+
|
786
|
+
# Set up a PassthroughLLM directly
|
787
|
+
async with agent:
|
788
|
+
agent._llm = PassthroughLLM(
|
789
|
+
name=f"{config.name}_llm",
|
790
|
+
context=agent_app.context,
|
791
|
+
agent=agent,
|
792
|
+
default_request_params=config.default_request_params,
|
793
|
+
)
|
794
|
+
|
795
|
+
# Store the agent
|
796
|
+
instance = agent
|
797
|
+
else:
|
798
|
+
# Standard basic agent with LLM
|
799
|
+
agent = Agent(config=config, context=agent_app.context)
|
800
|
+
|
801
|
+
# Set up LLM with proper configuration
|
802
|
+
async with agent:
|
803
|
+
llm_factory = self._get_model_factory(
|
804
|
+
model=config.model,
|
805
|
+
request_params=config.default_request_params,
|
806
|
+
)
|
807
|
+
agent._llm = await agent.attach_llm(llm_factory)
|
808
|
+
|
809
|
+
# Store the agent
|
810
|
+
instance = agent
|
936
811
|
|
937
812
|
elif agent_type == AgentType.ORCHESTRATOR:
|
938
813
|
# Get base params configured with model settings
|
@@ -1049,17 +924,17 @@ class FastAgent(ContextDependent):
|
|
1049
924
|
elif agent_type == AgentType.CHAIN:
|
1050
925
|
# Get the sequence from either parameter
|
1051
926
|
sequence = agent_data.get("sequence", agent_data.get("agents", []))
|
1052
|
-
|
927
|
+
|
1053
928
|
# Auto-generate instruction if not provided or if it's just the default
|
1054
929
|
default_instruction = f"Chain of agents: {', '.join(sequence)}"
|
1055
|
-
|
930
|
+
|
1056
931
|
# If user provided a custom instruction, use that
|
1057
932
|
# Otherwise, generate a description based on the sequence and their servers
|
1058
933
|
if config.instruction == default_instruction:
|
1059
934
|
# Get all agent names in the sequence
|
1060
935
|
agent_names = []
|
1061
936
|
all_servers = set()
|
1062
|
-
|
937
|
+
|
1063
938
|
# Collect information about the agents and their servers
|
1064
939
|
for agent_name in sequence:
|
1065
940
|
if agent_name in active_agents:
|
@@ -1073,15 +948,26 @@ class FastAgent(ContextDependent):
|
|
1073
948
|
elif hasattr(agent_proxy, "_workflow"):
|
1074
949
|
# For WorkflowProxy
|
1075
950
|
agent_names.append(agent_name)
|
1076
|
-
|
951
|
+
|
1077
952
|
# Generate a better description
|
1078
953
|
if agent_names:
|
1079
|
-
server_part =
|
954
|
+
server_part = (
|
955
|
+
f" with access to servers: {', '.join(sorted(all_servers))}"
|
956
|
+
if all_servers
|
957
|
+
else ""
|
958
|
+
)
|
1080
959
|
config.instruction = f"Sequence of agents: {', '.join(agent_names)}{server_part}."
|
1081
|
-
|
960
|
+
|
1082
961
|
# Create a ChainProxy without needing a new instance
|
1083
962
|
# Just pass the agent proxies and sequence
|
1084
963
|
instance = ChainProxy(self.app, name, sequence, active_agents)
|
964
|
+
# Set continue_with_final behavior from configuration
|
965
|
+
instance._continue_with_final = agent_data.get(
|
966
|
+
"continue_with_final", True
|
967
|
+
)
|
968
|
+
|
969
|
+
# We removed the AgentType.PASSTHROUGH case
|
970
|
+
# Passthrough agents are now created as BASIC agents with a special LLM
|
1085
971
|
|
1086
972
|
elif agent_type == AgentType.PARALLEL:
|
1087
973
|
# Get fan-out agents (could be basic agents or other parallels)
|
@@ -1104,6 +990,7 @@ class FastAgent(ContextDependent):
|
|
1104
990
|
context=agent_app.context,
|
1105
991
|
llm_factory=llm_factory,
|
1106
992
|
default_request_params=config.default_request_params,
|
993
|
+
include_request=agent_data.get("include_request", True),
|
1107
994
|
)
|
1108
995
|
|
1109
996
|
else:
|
@@ -1337,9 +1224,7 @@ class FastAgent(ContextDependent):
|
|
1337
1224
|
Returns:
|
1338
1225
|
The underlying Agent or workflow instance
|
1339
1226
|
"""
|
1340
|
-
|
1341
|
-
return proxy._agent
|
1342
|
-
return proxy._workflow
|
1227
|
+
return unwrap_proxy(proxy)
|
1343
1228
|
|
1344
1229
|
def _get_agent_instances(
|
1345
1230
|
self, agent_names: List[str], active_agents: ProxyDict
|
@@ -1354,7 +1239,7 @@ class FastAgent(ContextDependent):
|
|
1354
1239
|
Returns:
|
1355
1240
|
List of unwrapped agent/workflow instances
|
1356
1241
|
"""
|
1357
|
-
return
|
1242
|
+
return get_agent_instances(agent_names, active_agents)
|
1358
1243
|
|
1359
1244
|
@asynccontextmanager
|
1360
1245
|
async def run(self):
|
@@ -1364,14 +1249,30 @@ class FastAgent(ContextDependent):
|
|
1364
1249
|
"""
|
1365
1250
|
active_agents = {}
|
1366
1251
|
had_error = False
|
1252
|
+
|
1253
|
+
# Handle quiet mode by disabling logger settings after initialization
|
1254
|
+
quiet_mode = hasattr(self, "args") and self.args.quiet
|
1255
|
+
|
1367
1256
|
try:
|
1368
1257
|
async with self.app.run() as agent_app:
|
1258
|
+
# Apply quiet mode directly to the context's config if needed
|
1259
|
+
if quiet_mode and hasattr(agent_app.context, "config") and hasattr(agent_app.context.config, "logger"):
|
1260
|
+
# Apply after initialization but before agents are created
|
1261
|
+
agent_app.context.config.logger.progress_display = False
|
1262
|
+
agent_app.context.config.logger.show_chat = False
|
1263
|
+
agent_app.context.config.logger.show_tools = False
|
1264
|
+
|
1265
|
+
# Directly disable the progress display singleton
|
1266
|
+
from mcp_agent.progress_display import progress_display
|
1267
|
+
progress_display.stop() # This will stop and hide the display
|
1268
|
+
|
1369
1269
|
# Pre-flight validation
|
1370
1270
|
self._validate_server_references()
|
1371
1271
|
self._validate_workflow_references()
|
1372
1272
|
|
1373
1273
|
# Create all types of agents in dependency order
|
1374
1274
|
active_agents = await self._create_basic_agents(agent_app)
|
1275
|
+
|
1375
1276
|
orchestrators = await self._create_orchestrators(
|
1376
1277
|
agent_app, active_agents
|
1377
1278
|
)
|
@@ -1395,6 +1296,30 @@ class FastAgent(ContextDependent):
|
|
1395
1296
|
|
1396
1297
|
# Create wrapper with all agents
|
1397
1298
|
wrapper = AgentApp(agent_app, active_agents)
|
1299
|
+
|
1300
|
+
# Handle direct message sending if --agent and --message are provided
|
1301
|
+
if self.args.agent and self.args.message:
|
1302
|
+
agent_name = self.args.agent
|
1303
|
+
message = self.args.message
|
1304
|
+
|
1305
|
+
if agent_name not in active_agents:
|
1306
|
+
available_agents = ", ".join(active_agents.keys())
|
1307
|
+
print(f"\n\nError: Agent '{agent_name}' not found. Available agents: {available_agents}")
|
1308
|
+
raise SystemExit(1)
|
1309
|
+
|
1310
|
+
try:
|
1311
|
+
# Get response
|
1312
|
+
response = await wrapper[agent_name].send(message)
|
1313
|
+
|
1314
|
+
# Only print the response in quiet mode
|
1315
|
+
if self.args.quiet:
|
1316
|
+
print(f"{response}")
|
1317
|
+
|
1318
|
+
raise SystemExit(0)
|
1319
|
+
except Exception as e:
|
1320
|
+
print(f"\n\nError sending message to agent '{agent_name}': {str(e)}")
|
1321
|
+
raise SystemExit(1)
|
1322
|
+
|
1398
1323
|
yield wrapper
|
1399
1324
|
|
1400
1325
|
except ServerConfigError as e:
|
@@ -1481,19 +1406,8 @@ class FastAgent(ContextDependent):
|
|
1481
1406
|
error_type: Type of error to display
|
1482
1407
|
suggestion: Optional suggestion message to display
|
1483
1408
|
"""
|
1484
|
-
|
1485
|
-
print(getattr(e, "message", str(e)))
|
1486
|
-
if hasattr(e, "details") and e.details:
|
1487
|
-
print("\nDetails:")
|
1488
|
-
print(e.details)
|
1489
|
-
if suggestion:
|
1490
|
-
print(f"\n{suggestion}")
|
1409
|
+
handle_error(e, error_type, suggestion)
|
1491
1410
|
|
1492
1411
|
def _log_agent_load(self, agent_name: str) -> None:
|
1493
|
-
|
1494
|
-
|
1495
|
-
data={
|
1496
|
-
"progress_action": ProgressAction.LOADED,
|
1497
|
-
"agent_name": agent_name,
|
1498
|
-
},
|
1499
|
-
)
|
1412
|
+
# Using the imported function
|
1413
|
+
log_agent_load(self.app, agent_name)
|