fast-agent-mcp 0.1.11__py3-none-any.whl → 0.1.13__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.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
- fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
- mcp_agent/agents/agent.py +37 -102
- mcp_agent/app.py +16 -27
- mcp_agent/cli/commands/bootstrap.py +22 -52
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +11 -26
- mcp_agent/cli/main.py +6 -9
- mcp_agent/cli/terminal.py +2 -2
- mcp_agent/config.py +1 -5
- mcp_agent/context.py +13 -26
- mcp_agent/context_dependent.py +3 -7
- mcp_agent/core/agent_app.py +46 -122
- mcp_agent/core/agent_types.py +29 -2
- mcp_agent/core/agent_utils.py +3 -5
- mcp_agent/core/decorators.py +6 -14
- mcp_agent/core/enhanced_prompt.py +25 -52
- mcp_agent/core/error_handling.py +1 -1
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/factory.py +30 -72
- mcp_agent/core/fastagent.py +48 -88
- mcp_agent/core/mcp_content.py +10 -19
- mcp_agent/core/prompt.py +8 -15
- mcp_agent/core/proxies.py +34 -25
- mcp_agent/core/request_params.py +46 -0
- mcp_agent/core/types.py +6 -6
- mcp_agent/core/validation.py +16 -16
- mcp_agent/executor/decorator_registry.py +11 -23
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +28 -74
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +17 -29
- mcp_agent/human_input/handler.py +4 -9
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +15 -17
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +24 -24
- mcp_agent/mcp/gen_client.py +4 -12
- mcp_agent/mcp/interfaces.py +107 -88
- mcp_agent/mcp/mcp_agent_client_session.py +11 -19
- mcp_agent/mcp/mcp_agent_server.py +8 -10
- mcp_agent/mcp/mcp_aggregator.py +49 -122
- mcp_agent/mcp/mcp_connection_manager.py +16 -37
- mcp_agent/mcp/prompt_message_multipart.py +12 -18
- mcp_agent/mcp/prompt_serialization.py +13 -38
- mcp_agent/mcp/prompts/prompt_load.py +99 -0
- mcp_agent/mcp/prompts/prompt_server.py +21 -128
- mcp_agent/mcp/prompts/prompt_template.py +20 -42
- mcp_agent/mcp/resource_utils.py +8 -17
- mcp_agent/mcp/sampling.py +62 -64
- mcp_agent/mcp/stdio.py +11 -8
- mcp_agent/mcp_server/__init__.py +1 -1
- mcp_agent/mcp_server/agent_server.py +10 -17
- mcp_agent/mcp_server_registry.py +13 -35
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
- mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
- mcp_agent/resources/examples/data-analysis/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +2 -1
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/prompting/__init__.py +1 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +5 -11
- mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
- mcp_agent/resources/examples/researcher/researcher.py +2 -1
- mcp_agent/resources/examples/workflows/agent_build.py +2 -1
- mcp_agent/resources/examples/workflows/chaining.py +2 -1
- mcp_agent/resources/examples/workflows/evaluator.py +2 -1
- mcp_agent/resources/examples/workflows/human_input.py +2 -1
- mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
- mcp_agent/resources/examples/workflows/parallel.py +2 -1
- mcp_agent/resources/examples/workflows/router.py +2 -1
- mcp_agent/resources/examples/workflows/sse.py +1 -1
- mcp_agent/telemetry/usage_tracking.py +2 -1
- mcp_agent/ui/console_display.py +17 -41
- mcp_agent/workflows/embedding/embedding_base.py +1 -4
- mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
- mcp_agent/workflows/embedding/embedding_openai.py +4 -13
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
- mcp_agent/workflows/llm/anthropic_utils.py +8 -29
- mcp_agent/workflows/llm/augmented_llm.py +94 -332
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +43 -76
- mcp_agent/workflows/llm/augmented_llm_openai.py +46 -100
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +42 -20
- mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
- mcp_agent/workflows/llm/memory.py +103 -0
- mcp_agent/workflows/llm/model_factory.py +9 -21
- mcp_agent/workflows/llm/openai_utils.py +1 -1
- mcp_agent/workflows/llm/prompt_utils.py +39 -27
- mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +246 -184
- mcp_agent/workflows/llm/providers/multipart_converter_openai.py +212 -202
- mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
- mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +11 -212
- mcp_agent/workflows/llm/providers/sampling_converter_openai.py +13 -215
- mcp_agent/workflows/llm/sampling_converter.py +117 -0
- mcp_agent/workflows/llm/sampling_format_converter.py +12 -29
- mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
- mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
- mcp_agent/workflows/parallel/fan_in.py +17 -47
- mcp_agent/workflows/parallel/fan_out.py +6 -12
- mcp_agent/workflows/parallel/parallel_llm.py +9 -26
- mcp_agent/workflows/router/router_base.py +29 -59
- mcp_agent/workflows/router/router_embedding.py +11 -25
- mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
- mcp_agent/workflows/router/router_embedding_openai.py +2 -2
- mcp_agent/workflows/router/router_llm.py +12 -28
- mcp_agent/workflows/swarm/swarm.py +20 -48
- mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
- mcp_agent/workflows/swarm/swarm_openai.py +2 -2
- fast_agent_mcp-0.1.11.dist-info/RECORD +0 -160
- mcp_agent/workflows/llm/llm_selector.py +0 -345
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
import contextlib
|
2
|
-
from typing import Callable, Dict, List, Optional, Type
|
2
|
+
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
3
3
|
|
4
4
|
from mcp_agent.agents.agent import Agent
|
5
5
|
from mcp_agent.context_dependent import ContextDependent
|
@@ -43,7 +43,7 @@ class FanIn(ContextDependent):
|
|
43
43
|
llm_factory: Callable[[Agent], AugmentedLLM[MessageParamT, MessageT]] = None,
|
44
44
|
context: Optional["Context"] = None,
|
45
45
|
**kwargs,
|
46
|
-
):
|
46
|
+
) -> None:
|
47
47
|
"""
|
48
48
|
Initialize the FanIn with an Agent responsible for processing multiple responses into a single aggregated one.
|
49
49
|
"""
|
@@ -67,9 +67,7 @@ class FanIn(ContextDependent):
|
|
67
67
|
Request fan-in agent generation from a list of messages from multiple sources/agents.
|
68
68
|
Internally aggregates the messages and then calls the aggregator agent to generate a response.
|
69
69
|
"""
|
70
|
-
message: (
|
71
|
-
str | MessageParamT | List[MessageParamT]
|
72
|
-
) = await self.aggregate_messages(messages)
|
70
|
+
message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
|
73
71
|
|
74
72
|
async with contextlib.AsyncExitStack() as stack:
|
75
73
|
if isinstance(self.aggregator_agent, AugmentedLLM):
|
@@ -95,9 +93,7 @@ class FanIn(ContextDependent):
|
|
95
93
|
response, which is returned as a string.
|
96
94
|
"""
|
97
95
|
|
98
|
-
message: (
|
99
|
-
str | MessageParamT | List[MessageParamT]
|
100
|
-
) = await self.aggregate_messages(messages)
|
96
|
+
message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
|
101
97
|
|
102
98
|
async with contextlib.AsyncExitStack() as stack:
|
103
99
|
if isinstance(self.aggregator_agent, AugmentedLLM):
|
@@ -107,9 +103,7 @@ class FanIn(ContextDependent):
|
|
107
103
|
ctx_agent = await stack.enter_async_context(self.aggregator_agent)
|
108
104
|
llm = await ctx_agent.attach_llm(self.llm_factory)
|
109
105
|
|
110
|
-
return await llm.generate_str(
|
111
|
-
message=message, request_params=request_params
|
112
|
-
)
|
106
|
+
return await llm.generate_str(message=message, request_params=request_params)
|
113
107
|
|
114
108
|
async def generate_structured(
|
115
109
|
self,
|
@@ -123,9 +117,7 @@ class FanIn(ContextDependent):
|
|
123
117
|
the aggregator agent to generate a response, which is returned as a Pydantic model.
|
124
118
|
"""
|
125
119
|
|
126
|
-
message: (
|
127
|
-
str | MessageParamT | List[MessageParamT]
|
128
|
-
) = await self.aggregate_messages(messages)
|
120
|
+
message: str | MessageParamT | List[MessageParamT] = await self.aggregate_messages(messages)
|
129
121
|
|
130
122
|
async with contextlib.AsyncExitStack() as stack:
|
131
123
|
if isinstance(self.aggregator_agent, AugmentedLLM):
|
@@ -141,9 +133,7 @@ class FanIn(ContextDependent):
|
|
141
133
|
request_params=request_params,
|
142
134
|
)
|
143
135
|
|
144
|
-
async def aggregate_messages(
|
145
|
-
self, messages: FanInInput
|
146
|
-
) -> str | MessageParamT | List[MessageParamT]:
|
136
|
+
async def aggregate_messages(self, messages: FanInInput) -> str | MessageParamT | List[MessageParamT]:
|
147
137
|
"""
|
148
138
|
Aggregate messages from multiple sources/agents into a single message to
|
149
139
|
use with the aggregator agent generation.
|
@@ -187,9 +177,7 @@ class FanIn(ContextDependent):
|
|
187
177
|
return await self.aggregate_agent_message_strings(messages)
|
188
178
|
|
189
179
|
else:
|
190
|
-
raise ValueError(
|
191
|
-
"Dictionary values must be either lists of messages or strings"
|
192
|
-
)
|
180
|
+
raise ValueError("Dictionary values must be either lists of messages or strings")
|
193
181
|
|
194
182
|
# Handle list inputs
|
195
183
|
elif isinstance(messages, list):
|
@@ -214,19 +202,13 @@ class FanIn(ContextDependent):
|
|
214
202
|
return await self.aggregate_message_strings(messages)
|
215
203
|
|
216
204
|
else:
|
217
|
-
raise ValueError(
|
218
|
-
"List items must be either lists of messages or strings"
|
219
|
-
)
|
205
|
+
raise ValueError("List items must be either lists of messages or strings")
|
220
206
|
|
221
207
|
else:
|
222
|
-
raise ValueError(
|
223
|
-
"Input must be either a dictionary of agent messages or a list of messages"
|
224
|
-
)
|
208
|
+
raise ValueError("Input must be either a dictionary of agent messages or a list of messages")
|
225
209
|
|
226
210
|
# Helper methods for processing different types of inputs
|
227
|
-
async def aggregate_agent_messages(
|
228
|
-
self, messages: Dict[str, List[MessageT] | List[MessageParamT]]
|
229
|
-
) -> str | MessageParamT | List[MessageParamT]:
|
211
|
+
async def aggregate_agent_messages(self, messages: Dict[str, List[MessageT] | List[MessageParamT]]) -> str | MessageParamT | List[MessageParamT]:
|
230
212
|
"""
|
231
213
|
Aggregate message lists with agent names.
|
232
214
|
|
@@ -254,9 +236,7 @@ class FanIn(ContextDependent):
|
|
254
236
|
else:
|
255
237
|
# Assume it's a Message/MessageParamT and add attribution
|
256
238
|
# TODO -- this should really unpack the text from the message
|
257
|
-
agent_message_strings.append(
|
258
|
-
f"Agent {agent_name}: {str(msg.content[0])}"
|
259
|
-
)
|
239
|
+
agent_message_strings.append(f"Agent {agent_name}: {str(msg.content[0])}")
|
260
240
|
|
261
241
|
aggregated_messages.append("\n".join(agent_message_strings))
|
262
242
|
|
@@ -279,18 +259,14 @@ class FanIn(ContextDependent):
|
|
279
259
|
return ""
|
280
260
|
|
281
261
|
# Format each agent's message with agent attribution
|
282
|
-
aggregated_messages = [
|
283
|
-
f"Agent {agent_name}: {message}" for agent_name, message in messages.items()
|
284
|
-
]
|
262
|
+
aggregated_messages = [f"Agent {agent_name}: {message}" for agent_name, message in messages.items()]
|
285
263
|
|
286
264
|
# Combine all messages with clear separation
|
287
265
|
final_message = "\n\n".join(aggregated_messages)
|
288
266
|
final_message = f"Aggregated responses from multiple Agents:\n\n{final_message}"
|
289
267
|
return final_message
|
290
268
|
|
291
|
-
async def aggregate_message_lists(
|
292
|
-
self, messages: List[List[MessageT] | List[MessageParamT]]
|
293
|
-
) -> str | MessageParamT | List[MessageParamT]:
|
269
|
+
async def aggregate_message_lists(self, messages: List[List[MessageT] | List[MessageParamT]]) -> str | MessageParamT | List[MessageParamT]:
|
294
270
|
"""
|
295
271
|
Aggregate message lists without agent names.
|
296
272
|
|
@@ -319,9 +295,7 @@ class FanIn(ContextDependent):
|
|
319
295
|
|
320
296
|
# Combine all messages with clear separation
|
321
297
|
final_message = "\n\n".join(aggregated_messages)
|
322
|
-
final_message =
|
323
|
-
f"Aggregated responses from multiple sources:\n\n{final_message}"
|
324
|
-
)
|
298
|
+
final_message = f"Aggregated responses from multiple sources:\n\n{final_message}"
|
325
299
|
return final_message
|
326
300
|
|
327
301
|
async def aggregate_message_strings(self, messages: List[str]) -> str:
|
@@ -338,13 +312,9 @@ class FanIn(ContextDependent):
|
|
338
312
|
return ""
|
339
313
|
|
340
314
|
# Format each source's message with attribution
|
341
|
-
aggregated_messages = [
|
342
|
-
f"Source {i}: {message}" for i, message in enumerate(messages, 1)
|
343
|
-
]
|
315
|
+
aggregated_messages = [f"Source {i}: {message}" for i, message in enumerate(messages, 1)]
|
344
316
|
|
345
317
|
# Combine all messages with clear separation
|
346
318
|
final_message = "\n\n".join(aggregated_messages)
|
347
|
-
final_message =
|
348
|
-
f"Aggregated responses from multiple sources:\n\n{final_message}"
|
349
|
-
)
|
319
|
+
final_message = f"Aggregated responses from multiple sources:\n\n{final_message}"
|
350
320
|
return final_message
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import contextlib
|
2
2
|
import functools
|
3
|
-
from typing import Any, Callable, Coroutine, Dict, List, Optional, Type
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional, Type
|
4
4
|
|
5
5
|
from mcp_agent.agents.agent import Agent
|
6
6
|
from mcp_agent.context_dependent import ContextDependent
|
7
|
+
from mcp_agent.logging.logger import get_logger
|
7
8
|
from mcp_agent.workflows.llm.augmented_llm import (
|
8
9
|
AugmentedLLM,
|
9
10
|
MessageParamT,
|
@@ -11,7 +12,6 @@ from mcp_agent.workflows.llm.augmented_llm import (
|
|
11
12
|
ModelT,
|
12
13
|
RequestParams,
|
13
14
|
)
|
14
|
-
from mcp_agent.logging.logger import get_logger
|
15
15
|
|
16
16
|
if TYPE_CHECKING:
|
17
17
|
from mcp_agent.context import Context
|
@@ -34,7 +34,7 @@ class FanOut(ContextDependent):
|
|
34
34
|
llm_factory: Callable[[Agent], AugmentedLLM[MessageParamT, MessageT]] = None,
|
35
35
|
context: Optional["Context"] = None,
|
36
36
|
**kwargs,
|
37
|
-
):
|
37
|
+
) -> None:
|
38
38
|
"""
|
39
39
|
Initialize the FanOut with a list of agents, functions, or LLMs.
|
40
40
|
If agents are provided, they will be wrapped in an AugmentedLLM using llm_factory if not already done so.
|
@@ -47,9 +47,7 @@ class FanOut(ContextDependent):
|
|
47
47
|
self.functions: List[Callable[[MessageParamT], MessageT]] = functions or []
|
48
48
|
|
49
49
|
if not self.agents and not self.functions:
|
50
|
-
raise ValueError(
|
51
|
-
"At least one agent or function must be provided for fan-out to work"
|
52
|
-
)
|
50
|
+
raise ValueError("At least one agent or function must be provided for fan-out to work")
|
53
51
|
|
54
52
|
if not self.llm_factory:
|
55
53
|
for agent in self.agents:
|
@@ -65,9 +63,7 @@ class FanOut(ContextDependent):
|
|
65
63
|
Request fan-out agent/function generations, and return the results as a dictionary.
|
66
64
|
The keys are the names of the agents or functions that generated the results.
|
67
65
|
"""
|
68
|
-
tasks: List[
|
69
|
-
Callable[..., List[MessageT]] | Coroutine[Any, Any, List[MessageT]]
|
70
|
-
] = []
|
66
|
+
tasks: List[Callable[..., List[MessageT]] | Coroutine[Any, Any, List[MessageT]]] = []
|
71
67
|
task_names: List[str] = []
|
72
68
|
task_results = []
|
73
69
|
|
@@ -97,9 +93,7 @@ class FanOut(ContextDependent):
|
|
97
93
|
logger.debug("Running fan-out tasks:", data=task_names)
|
98
94
|
task_results = await self.executor.execute(*tasks)
|
99
95
|
|
100
|
-
logger.debug(
|
101
|
-
"Fan-out tasks completed:", data=dict(zip(task_names, task_results))
|
102
|
-
)
|
96
|
+
logger.debug("Fan-out tasks completed:", data=dict(zip(task_names, task_results)))
|
103
97
|
return dict(zip(task_names, task_results))
|
104
98
|
|
105
99
|
async def generate_str(
|
@@ -1,5 +1,5 @@
|
|
1
|
-
from typing import Any, Callable, List, Optional, Type, TYPE_CHECKING, Union
|
2
1
|
import asyncio
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union
|
3
3
|
|
4
4
|
from mcp_agent.agents.agent import Agent
|
5
5
|
from mcp_agent.workflows.llm.augmented_llm import (
|
@@ -30,7 +30,7 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
|
|
30
30
|
context: Optional["Context"] = None,
|
31
31
|
include_request: bool = True,
|
32
32
|
**kwargs,
|
33
|
-
):
|
33
|
+
) -> None:
|
34
34
|
super().__init__(context=context, **kwargs)
|
35
35
|
self.fan_in_agent = fan_in_agent
|
36
36
|
self.fan_out_agents = fan_out_agents
|
@@ -63,14 +63,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
|
|
63
63
|
fan_in_llm = await self.ensure_llm(self.fan_in_agent)
|
64
64
|
|
65
65
|
# Run fan-out operations in parallel
|
66
|
-
responses = await asyncio.gather(
|
67
|
-
*[llm.generate(message, request_params) for llm in fan_out_llms]
|
68
|
-
)
|
66
|
+
responses = await asyncio.gather(*[llm.generate(message, request_params) for llm in fan_out_llms])
|
69
67
|
|
70
68
|
# Get message string for inclusion in formatted output
|
71
|
-
message_str = (
|
72
|
-
str(message) if isinstance(message, (str, MessageParamT)) else None
|
73
|
-
)
|
69
|
+
message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
|
74
70
|
|
75
71
|
# Run fan-in to aggregate results
|
76
72
|
result = await fan_in_llm.generate(
|
@@ -95,14 +91,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
|
|
95
91
|
fan_in_llm = await self.ensure_llm(self.fan_in_agent)
|
96
92
|
|
97
93
|
# Run fan-out operations in parallel
|
98
|
-
responses = await asyncio.gather(
|
99
|
-
*[llm.generate_str(message, request_params) for llm in fan_out_llms]
|
100
|
-
)
|
94
|
+
responses = await asyncio.gather(*[llm.generate_str(message, request_params) for llm in fan_out_llms])
|
101
95
|
|
102
96
|
# Get message string for inclusion in formatted output
|
103
|
-
message_str = (
|
104
|
-
str(message) if isinstance(message, (str, MessageParamT)) else None
|
105
|
-
)
|
97
|
+
message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
|
106
98
|
|
107
99
|
# Run fan-in to aggregate results
|
108
100
|
result = await fan_in_llm.generate_str(
|
@@ -128,17 +120,10 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
|
|
128
120
|
fan_in_llm = await self.ensure_llm(self.fan_in_agent)
|
129
121
|
|
130
122
|
# Run fan-out operations in parallel
|
131
|
-
responses = await asyncio.gather(
|
132
|
-
*[
|
133
|
-
llm.generate_structured(message, response_model, request_params)
|
134
|
-
for llm in fan_out_llms
|
135
|
-
]
|
136
|
-
)
|
123
|
+
responses = await asyncio.gather(*[llm.generate_structured(message, response_model, request_params) for llm in fan_out_llms])
|
137
124
|
|
138
125
|
# Get message string for inclusion in formatted output
|
139
|
-
message_str = (
|
140
|
-
str(message) if isinstance(message, (str, MessageParamT)) else None
|
141
|
-
)
|
126
|
+
message_str = str(message) if isinstance(message, (str, MessageParamT)) else None
|
142
127
|
|
143
128
|
# Run fan-in to aggregate results
|
144
129
|
result = await fan_in_llm.generate_structured(
|
@@ -160,7 +145,5 @@ class ParallelLLM(AugmentedLLM[MessageParamT, MessageT]):
|
|
160
145
|
|
161
146
|
for i, response in enumerate(responses):
|
162
147
|
agent_name = self.fan_out_agents[i].name
|
163
|
-
formatted.append(
|
164
|
-
f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>'
|
165
|
-
)
|
148
|
+
formatted.append(f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>')
|
166
149
|
return "\n\n".join(formatted)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from typing import Callable, Dict, Generic, List, Optional, TypeVar
|
2
|
+
from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, TypeVar
|
3
3
|
|
4
|
-
from pydantic import BaseModel, Field, ConfigDict
|
5
4
|
from mcp.server.fastmcp.tools import Tool as FastTool
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
6
6
|
|
7
7
|
from mcp_agent.agents.agent import Agent
|
8
8
|
from mcp_agent.context_dependent import ContextDependent
|
@@ -98,7 +98,7 @@ class Router(ABC, ContextDependent):
|
|
98
98
|
routing_instruction: str | None = None,
|
99
99
|
context: Optional["Context"] = None,
|
100
100
|
**kwargs,
|
101
|
-
):
|
101
|
+
) -> None:
|
102
102
|
super().__init__(context=context, **kwargs)
|
103
103
|
self.routing_instruction = routing_instruction
|
104
104
|
self.server_names = server_names or []
|
@@ -115,19 +115,13 @@ class Router(ABC, ContextDependent):
|
|
115
115
|
self.initialized: bool = False
|
116
116
|
|
117
117
|
if not self.server_names and not self.agents and not self.functions:
|
118
|
-
raise ValueError(
|
119
|
-
"At least one of mcp_servers_names, agents, or functions must be provided."
|
120
|
-
)
|
118
|
+
raise ValueError("At least one of mcp_servers_names, agents, or functions must be provided.")
|
121
119
|
|
122
120
|
if self.server_names and not self.server_registry:
|
123
|
-
raise ValueError(
|
124
|
-
"server_registry must be provided if mcp_servers_names are provided."
|
125
|
-
)
|
121
|
+
raise ValueError("server_registry must be provided if mcp_servers_names are provided.")
|
126
122
|
|
127
123
|
@abstractmethod
|
128
|
-
async def route(
|
129
|
-
self, request: str, top_k: int = 1
|
130
|
-
) -> List[RouterResult[str | Agent | Callable]]:
|
124
|
+
async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
|
131
125
|
"""
|
132
126
|
Route the input request to one or more MCP servers, agents, or functions.
|
133
127
|
If no routing decision can be made, returns an empty list.
|
@@ -138,21 +132,15 @@ class Router(ABC, ContextDependent):
|
|
138
132
|
"""
|
139
133
|
|
140
134
|
@abstractmethod
|
141
|
-
async def route_to_server(
|
142
|
-
self, request: str, top_k: int = 1
|
143
|
-
) -> List[RouterResult[str]]:
|
135
|
+
async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
|
144
136
|
"""Route the input to one or more MCP servers."""
|
145
137
|
|
146
138
|
@abstractmethod
|
147
|
-
async def route_to_agent(
|
148
|
-
self, request: str, top_k: int = 1
|
149
|
-
) -> List[RouterResult[Agent]]:
|
139
|
+
async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
|
150
140
|
"""Route the input to one or more agents."""
|
151
141
|
|
152
142
|
@abstractmethod
|
153
|
-
async def route_to_function(
|
154
|
-
self, request: str, top_k: int = 1
|
155
|
-
) -> List[RouterResult[Callable]]:
|
143
|
+
async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
|
156
144
|
"""
|
157
145
|
Route the input to one or more functions.
|
158
146
|
|
@@ -160,30 +148,20 @@ class Router(ABC, ContextDependent):
|
|
160
148
|
input: The input to route.
|
161
149
|
"""
|
162
150
|
|
163
|
-
async def initialize(self):
|
151
|
+
async def initialize(self) -> None:
|
164
152
|
"""Initialize the router categories."""
|
165
153
|
|
166
154
|
if self.initialized:
|
167
155
|
return
|
168
156
|
|
169
|
-
server_categories = [
|
170
|
-
|
171
|
-
]
|
172
|
-
self.server_categories = {
|
173
|
-
category.name: category for category in server_categories
|
174
|
-
}
|
157
|
+
server_categories = [self.get_server_category(server_name) for server_name in self.server_names]
|
158
|
+
self.server_categories = {category.name: category for category in server_categories}
|
175
159
|
|
176
160
|
agent_categories = [self.get_agent_category(agent) for agent in self.agents]
|
177
|
-
self.agent_categories = {
|
178
|
-
category.name: category for category in agent_categories
|
179
|
-
}
|
161
|
+
self.agent_categories = {category.name: category for category in agent_categories}
|
180
162
|
|
181
|
-
function_categories = [
|
182
|
-
|
183
|
-
]
|
184
|
-
self.function_categories = {
|
185
|
-
category.name: category for category in function_categories
|
186
|
-
}
|
163
|
+
function_categories = [self.get_function_category(function) for function in self.functions]
|
164
|
+
self.function_categories = {category.name: category for category in function_categories}
|
187
165
|
|
188
166
|
all_categories = server_categories + agent_categories + function_categories
|
189
167
|
|
@@ -204,15 +182,11 @@ class Router(ABC, ContextDependent):
|
|
204
182
|
)
|
205
183
|
|
206
184
|
def get_agent_category(self, agent: Agent) -> AgentRouterCategory:
|
207
|
-
agent_description = (
|
208
|
-
agent.instruction({}) if callable(agent.instruction) else agent.instruction
|
209
|
-
)
|
185
|
+
agent_description = agent.instruction({}) if callable(agent.instruction) else agent.instruction
|
210
186
|
|
211
187
|
# Just get server categories without attempting to access tools
|
212
188
|
# This is a simpler approach that avoids potential issues with uninitialized agents
|
213
|
-
server_categories = [
|
214
|
-
self.get_server_category(server_name) for server_name in agent.server_names
|
215
|
-
]
|
189
|
+
server_categories = [self.get_server_category(server_name) for server_name in agent.server_names]
|
216
190
|
|
217
191
|
return AgentRouterCategory(
|
218
192
|
category=agent,
|
@@ -230,9 +204,7 @@ class Router(ABC, ContextDependent):
|
|
230
204
|
description=tool.description,
|
231
205
|
)
|
232
206
|
|
233
|
-
def format_category(
|
234
|
-
self, category: RouterCategory, index: int | None = None
|
235
|
-
) -> str:
|
207
|
+
def format_category(self, category: RouterCategory, index: int | None = None) -> str:
|
236
208
|
"""Format a category into a readable string."""
|
237
209
|
|
238
210
|
if isinstance(category, ServerRouterCategory):
|
@@ -265,16 +237,16 @@ class Router(ABC, ContextDependent):
|
|
265
237
|
# Check if we have any content (description or tools)
|
266
238
|
has_description = bool(category.description)
|
267
239
|
has_tools = bool(category.tools)
|
268
|
-
|
240
|
+
|
269
241
|
# If no content at all, use self-closing tag
|
270
242
|
if not has_description and not has_tools:
|
271
243
|
return f'<fastagent:server-category name="{category.name}" />'
|
272
|
-
|
244
|
+
|
273
245
|
# Otherwise, build the content
|
274
246
|
description_section = ""
|
275
247
|
if has_description:
|
276
248
|
description_section = f"\n<fastagent:description>{category.description}</fastagent:description>"
|
277
|
-
|
249
|
+
|
278
250
|
# Add tools section if we have tool information
|
279
251
|
if has_tools:
|
280
252
|
tools = self._format_tools(category.tools)
|
@@ -293,11 +265,11 @@ class Router(ABC, ContextDependent):
|
|
293
265
|
# Check if we have any content (description or servers)
|
294
266
|
has_description = bool(category.description)
|
295
267
|
has_servers = bool(category.servers)
|
296
|
-
|
268
|
+
|
297
269
|
# If no content at all, use self-closing tag
|
298
270
|
if not has_description and not has_servers:
|
299
271
|
return f'<fastagent:agent-category name="{category.name}" />'
|
300
|
-
|
272
|
+
|
301
273
|
# Build description section if needed
|
302
274
|
description_section = ""
|
303
275
|
if has_description:
|
@@ -314,13 +286,13 @@ class Router(ABC, ContextDependent):
|
|
314
286
|
# Check if this server has any content
|
315
287
|
has_server_description = bool(server.description)
|
316
288
|
has_server_tools = bool(server.tools)
|
317
|
-
|
289
|
+
|
318
290
|
# Use self-closing tag if server has no content
|
319
291
|
if not has_server_description and not has_server_tools:
|
320
292
|
server_section = f'<fastagent:server name="{server.name}" />'
|
321
293
|
server_sections.append(server_section)
|
322
294
|
continue
|
323
|
-
|
295
|
+
|
324
296
|
# Build server description if needed
|
325
297
|
server_desc_section = ""
|
326
298
|
if has_server_description:
|
@@ -331,9 +303,7 @@ class Router(ABC, ContextDependent):
|
|
331
303
|
tool_items = []
|
332
304
|
for tool in server.tools:
|
333
305
|
tool_desc = tool.description if tool.description else ""
|
334
|
-
tool_items.append(
|
335
|
-
f'<fastagent:tool name="{tool.name}">{tool_desc}</fastagent:tool>'
|
336
|
-
)
|
306
|
+
tool_items.append(f'<fastagent:tool name="{tool.name}">{tool_desc}</fastagent:tool>')
|
337
307
|
|
338
308
|
tools_section = f"\n<fastagent:tools>\n{chr(10).join(tool_items)}\n</fastagent:tools>"
|
339
309
|
server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}{tools_section}
|
@@ -342,7 +312,7 @@ class Router(ABC, ContextDependent):
|
|
342
312
|
# Just description, no tools
|
343
313
|
server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}
|
344
314
|
</fastagent:server>"""
|
345
|
-
|
315
|
+
|
346
316
|
server_sections.append(server_section)
|
347
317
|
|
348
318
|
servers = "\n".join(server_sections)
|
@@ -357,11 +327,11 @@ class Router(ABC, ContextDependent):
|
|
357
327
|
"""Format a function category into a readable string."""
|
358
328
|
# Check if we have a description
|
359
329
|
has_description = bool(category.description)
|
360
|
-
|
330
|
+
|
361
331
|
# If no description, use self-closing tag
|
362
332
|
if not has_description:
|
363
333
|
return f'<fastagent:function-category name="{category.name}" />'
|
364
|
-
|
334
|
+
|
365
335
|
# Include description
|
366
336
|
return f"""<fastagent:function-category name="{category.name}">
|
367
337
|
<fastagent:description>{category.description}</fastagent:description>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Callable, List, Optional
|
1
|
+
from typing import TYPE_CHECKING, Callable, List, Optional
|
2
2
|
|
3
3
|
from numpy import mean
|
4
4
|
|
@@ -6,8 +6,8 @@ from mcp_agent.agents.agent import Agent
|
|
6
6
|
from mcp_agent.workflows.embedding.embedding_base import (
|
7
7
|
EmbeddingModel,
|
8
8
|
FloatArray,
|
9
|
-
compute_similarity_scores,
|
10
9
|
compute_confidence,
|
10
|
+
compute_similarity_scores,
|
11
11
|
)
|
12
12
|
from mcp_agent.workflows.router.router_base import (
|
13
13
|
Router,
|
@@ -56,7 +56,7 @@ class EmbeddingRouter(Router):
|
|
56
56
|
functions: List[Callable] | None = None,
|
57
57
|
context: Optional["Context"] = None,
|
58
58
|
**kwargs,
|
59
|
-
):
|
59
|
+
) -> None:
|
60
60
|
super().__init__(
|
61
61
|
server_names=server_names,
|
62
62
|
agents=agents,
|
@@ -90,7 +90,7 @@ class EmbeddingRouter(Router):
|
|
90
90
|
await instance.initialize()
|
91
91
|
return instance
|
92
92
|
|
93
|
-
async def initialize(self):
|
93
|
+
async def initialize(self) -> None:
|
94
94
|
"""Initialize by computing embeddings for all categories"""
|
95
95
|
|
96
96
|
async def create_category_with_embedding(
|
@@ -99,9 +99,7 @@ class EmbeddingRouter(Router):
|
|
99
99
|
# Get formatted text representation of category
|
100
100
|
category_text = self.format_category(category)
|
101
101
|
embedding = self._compute_embedding([category_text])
|
102
|
-
category_with_embedding = EmbeddingRouterCategory(
|
103
|
-
**category, embedding=embedding
|
104
|
-
)
|
102
|
+
category_with_embedding = EmbeddingRouterCategory(**category, embedding=embedding)
|
105
103
|
|
106
104
|
return category_with_embedding
|
107
105
|
|
@@ -129,18 +127,14 @@ class EmbeddingRouter(Router):
|
|
129
127
|
|
130
128
|
self.initialized = True
|
131
129
|
|
132
|
-
async def route(
|
133
|
-
self, request: str, top_k: int = 1
|
134
|
-
) -> List[RouterResult[str | Agent | Callable]]:
|
130
|
+
async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
|
135
131
|
"""Route the request based on embedding similarity"""
|
136
132
|
if not self.initialized:
|
137
133
|
await self.initialize()
|
138
134
|
|
139
135
|
return await self._route_with_embedding(request, top_k)
|
140
136
|
|
141
|
-
async def route_to_server(
|
142
|
-
self, request: str, top_k: int = 1
|
143
|
-
) -> List[RouterResult[str]]:
|
137
|
+
async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
|
144
138
|
"""Route specifically to server categories"""
|
145
139
|
if not self.initialized:
|
146
140
|
await self.initialize()
|
@@ -154,9 +148,7 @@ class EmbeddingRouter(Router):
|
|
154
148
|
)
|
155
149
|
return [r.result for r in results[:top_k]]
|
156
150
|
|
157
|
-
async def route_to_agent(
|
158
|
-
self, request: str, top_k: int = 1
|
159
|
-
) -> List[RouterResult[Agent]]:
|
151
|
+
async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
|
160
152
|
"""Route specifically to agent categories"""
|
161
153
|
if not self.initialized:
|
162
154
|
await self.initialize()
|
@@ -170,9 +162,7 @@ class EmbeddingRouter(Router):
|
|
170
162
|
)
|
171
163
|
return [r.result for r in results[:top_k]]
|
172
164
|
|
173
|
-
async def route_to_function(
|
174
|
-
self, request: str, top_k: int = 1
|
175
|
-
) -> List[RouterResult[Callable]]:
|
165
|
+
async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
|
176
166
|
"""Route specifically to function categories"""
|
177
167
|
if not self.initialized:
|
178
168
|
await self.initialize()
|
@@ -198,13 +188,9 @@ class EmbeddingRouter(Router):
|
|
198
188
|
if category.embedding is None:
|
199
189
|
return None
|
200
190
|
|
201
|
-
similarity = compute_similarity_scores(
|
202
|
-
request_embedding, category.embedding
|
203
|
-
)
|
191
|
+
similarity = compute_similarity_scores(request_embedding, category.embedding)
|
204
192
|
|
205
|
-
return RouterResult(
|
206
|
-
p_score=compute_confidence(similarity), result=category.category
|
207
|
-
)
|
193
|
+
return RouterResult(p_score=compute_confidence(similarity), result=category.category)
|
208
194
|
|
209
195
|
request_embedding = self._compute_embedding([request])
|
210
196
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Callable, List, Optional
|
1
|
+
from typing import TYPE_CHECKING, Callable, List, Optional
|
2
2
|
|
3
3
|
from mcp_agent.agents.agent import Agent
|
4
4
|
from mcp_agent.workflows.embedding.embedding_cohere import CohereEmbeddingModel
|
@@ -23,7 +23,7 @@ class CohereEmbeddingRouter(EmbeddingRouter):
|
|
23
23
|
embedding_model: CohereEmbeddingModel | None = None,
|
24
24
|
context: Optional["Context"] = None,
|
25
25
|
**kwargs,
|
26
|
-
):
|
26
|
+
) -> None:
|
27
27
|
embedding_model = embedding_model or CohereEmbeddingModel()
|
28
28
|
|
29
29
|
super().__init__(
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Callable, List, Optional
|
1
|
+
from typing import TYPE_CHECKING, Callable, List, Optional
|
2
2
|
|
3
3
|
from mcp_agent.agents.agent import Agent
|
4
4
|
from mcp_agent.workflows.embedding.embedding_openai import OpenAIEmbeddingModel
|
@@ -23,7 +23,7 @@ class OpenAIEmbeddingRouter(EmbeddingRouter):
|
|
23
23
|
embedding_model: OpenAIEmbeddingModel | None = None,
|
24
24
|
context: Optional["Context"] = None,
|
25
25
|
**kwargs,
|
26
|
-
):
|
26
|
+
) -> None:
|
27
27
|
embedding_model = embedding_model or OpenAIEmbeddingModel()
|
28
28
|
|
29
29
|
super().__init__(
|