fast-agent-mcp 0.1.13__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
  2. fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
  3. mcp_agent/__init__.py +75 -0
  4. mcp_agent/agents/agent.py +59 -371
  5. mcp_agent/agents/base_agent.py +522 -0
  6. mcp_agent/agents/workflow/__init__.py +1 -0
  7. mcp_agent/agents/workflow/chain_agent.py +173 -0
  8. mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
  9. mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
  10. mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +27 -11
  11. mcp_agent/agents/workflow/parallel_agent.py +182 -0
  12. mcp_agent/agents/workflow/router_agent.py +307 -0
  13. mcp_agent/app.py +3 -1
  14. mcp_agent/cli/commands/bootstrap.py +18 -7
  15. mcp_agent/cli/commands/setup.py +12 -4
  16. mcp_agent/cli/main.py +1 -1
  17. mcp_agent/cli/terminal.py +1 -1
  18. mcp_agent/config.py +24 -35
  19. mcp_agent/context.py +3 -1
  20. mcp_agent/context_dependent.py +3 -1
  21. mcp_agent/core/agent_types.py +10 -7
  22. mcp_agent/core/direct_agent_app.py +179 -0
  23. mcp_agent/core/direct_decorators.py +443 -0
  24. mcp_agent/core/direct_factory.py +476 -0
  25. mcp_agent/core/enhanced_prompt.py +15 -20
  26. mcp_agent/core/fastagent.py +151 -337
  27. mcp_agent/core/interactive_prompt.py +424 -0
  28. mcp_agent/core/mcp_content.py +19 -11
  29. mcp_agent/core/prompt.py +6 -2
  30. mcp_agent/core/validation.py +89 -16
  31. mcp_agent/executor/decorator_registry.py +6 -2
  32. mcp_agent/executor/temporal.py +35 -11
  33. mcp_agent/executor/workflow_signal.py +8 -2
  34. mcp_agent/human_input/handler.py +3 -1
  35. mcp_agent/llm/__init__.py +2 -0
  36. mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
  37. mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
  38. mcp_agent/llm/augmented_llm_playback.py +83 -0
  39. mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
  40. mcp_agent/llm/providers/__init__.py +8 -0
  41. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
  42. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
  43. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  44. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
  45. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
  46. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
  47. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
  48. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
  49. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
  50. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
  51. mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
  52. mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
  53. mcp_agent/logging/logger.py +2 -2
  54. mcp_agent/mcp/gen_client.py +9 -3
  55. mcp_agent/mcp/interfaces.py +67 -45
  56. mcp_agent/mcp/logger_textio.py +97 -0
  57. mcp_agent/mcp/mcp_agent_client_session.py +12 -4
  58. mcp_agent/mcp/mcp_agent_server.py +3 -1
  59. mcp_agent/mcp/mcp_aggregator.py +124 -93
  60. mcp_agent/mcp/mcp_connection_manager.py +21 -7
  61. mcp_agent/mcp/prompt_message_multipart.py +59 -1
  62. mcp_agent/mcp/prompt_render.py +77 -0
  63. mcp_agent/mcp/prompt_serialization.py +20 -13
  64. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  65. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  66. mcp_agent/mcp/prompts/prompt_load.py +15 -5
  67. mcp_agent/mcp/prompts/prompt_server.py +154 -87
  68. mcp_agent/mcp/prompts/prompt_template.py +26 -35
  69. mcp_agent/mcp/resource_utils.py +3 -1
  70. mcp_agent/mcp/sampling.py +24 -15
  71. mcp_agent/mcp_server/agent_server.py +8 -5
  72. mcp_agent/mcp_server_registry.py +22 -9
  73. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
  74. mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
  75. mcp_agent/resources/examples/internal/agent.py +4 -2
  76. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  77. mcp_agent/resources/examples/prompting/image_server.py +3 -1
  78. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  79. mcp_agent/ui/console_display.py +27 -7
  80. fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
  81. mcp_agent/core/agent_app.py +0 -570
  82. mcp_agent/core/agent_utils.py +0 -69
  83. mcp_agent/core/decorators.py +0 -448
  84. mcp_agent/core/factory.py +0 -422
  85. mcp_agent/core/proxies.py +0 -278
  86. mcp_agent/core/types.py +0 -22
  87. mcp_agent/eval/__init__.py +0 -0
  88. mcp_agent/mcp/stdio.py +0 -114
  89. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  90. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  91. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  92. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  93. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  94. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  95. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  96. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -189
  97. mcp_agent/resources/examples/researcher/researcher.py +0 -39
  98. mcp_agent/resources/examples/workflows/chaining.py +0 -45
  99. mcp_agent/resources/examples/workflows/evaluator.py +0 -79
  100. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  101. mcp_agent/resources/examples/workflows/human_input.py +0 -26
  102. mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
  103. mcp_agent/resources/examples/workflows/parallel.py +0 -79
  104. mcp_agent/resources/examples/workflows/router.py +0 -54
  105. mcp_agent/resources/examples/workflows/sse.py +0 -23
  106. mcp_agent/telemetry/__init__.py +0 -0
  107. mcp_agent/telemetry/usage_tracking.py +0 -19
  108. mcp_agent/workflows/__init__.py +0 -0
  109. mcp_agent/workflows/embedding/__init__.py +0 -0
  110. mcp_agent/workflows/embedding/embedding_base.py +0 -58
  111. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  112. mcp_agent/workflows/embedding/embedding_openai.py +0 -37
  113. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  114. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
  115. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  116. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
  117. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
  118. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
  119. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
  120. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
  121. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  122. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
  123. mcp_agent/workflows/llm/__init__.py +0 -0
  124. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
  125. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  126. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  127. mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
  128. mcp_agent/workflows/parallel/__init__.py +0 -0
  129. mcp_agent/workflows/parallel/fan_in.py +0 -320
  130. mcp_agent/workflows/parallel/fan_out.py +0 -181
  131. mcp_agent/workflows/parallel/parallel_llm.py +0 -149
  132. mcp_agent/workflows/router/__init__.py +0 -0
  133. mcp_agent/workflows/router/router_base.py +0 -338
  134. mcp_agent/workflows/router/router_embedding.py +0 -226
  135. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  136. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  137. mcp_agent/workflows/router/router_llm.py +0 -304
  138. mcp_agent/workflows/swarm/__init__.py +0 -0
  139. mcp_agent/workflows/swarm/swarm.py +0 -292
  140. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  141. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  142. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  143. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  144. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  145. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
  146. /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
  147. /mcp_agent/{workflows/llm → llm}/prompt_utils.py +0 -0
@@ -1,338 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, TypeVar
3
-
4
- from mcp.server.fastmcp.tools import Tool as FastTool
5
- from pydantic import BaseModel, ConfigDict, Field
6
-
7
- from mcp_agent.agents.agent import Agent
8
- from mcp_agent.context_dependent import ContextDependent
9
- from mcp_agent.logging.logger import get_logger
10
-
11
- if TYPE_CHECKING:
12
- from mcp_agent.context import Context
13
-
14
- logger = get_logger(__name__)
15
-
16
- ResultT = TypeVar("ResultT", bound=str | Agent | Callable)
17
-
18
-
19
- class RouterResult(BaseModel, Generic[ResultT]):
20
- """A class that represents the result of a Router.route request"""
21
-
22
- result: ResultT
23
- """The router returns an MCP server name, an Agent, or a function to route the input to."""
24
-
25
- p_score: float | None = None
26
- """
27
- The probability score (i.e. 0->1) of the routing decision.
28
- This is optional and may only be provided if the router is probabilistic (e.g. a probabilistic binary classifier).
29
- """
30
-
31
- model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
32
-
33
-
34
- class RouterCategory(BaseModel):
35
- """
36
- A class that represents a category of routing.
37
- Used to collect information the router needs to decide.
38
- """
39
-
40
- name: str
41
- """The name of the category"""
42
-
43
- description: str | None = None
44
- """A description of the category"""
45
-
46
- category: str | Agent | Callable
47
- """The class to route to"""
48
-
49
- model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
50
-
51
-
52
- class ServerRouterCategory(RouterCategory):
53
- """A class that represents a category of routing to an MCP server"""
54
-
55
- tools: List[FastTool] = Field(default_factory=list)
56
-
57
-
58
- class AgentRouterCategory(RouterCategory):
59
- """A class that represents a category of routing to an agent"""
60
-
61
- servers: List[ServerRouterCategory] = Field(default_factory=list)
62
-
63
-
64
- class Router(ABC, ContextDependent):
65
- """
66
- Routing classifies an input and directs it to one or more specialized followup tasks.
67
- This class helps to route an input to a specific MCP server,
68
- an Agent (an aggregation of MCP servers), or a function (any Callable).
69
-
70
- When to use this workflow:
71
- - This workflow allows for separation of concerns, and building more specialized prompts.
72
-
73
- - Routing works well for complex tasks where there are distinct categories that
74
- are better handled separately, and where classification can be handled accurately,
75
- either by an LLM or a more traditional classification model/algorithm.
76
-
77
- Examples where routing is useful:
78
- - Directing different types of customer service queries
79
- (general questions, refund requests, technical support)
80
- into different downstream processes, prompts, and tools.
81
-
82
- - Routing easy/common questions to smaller models like Claude 3.5 Haiku
83
- and hard/unusual questions to more capable models like Claude 3.5 Sonnet
84
- to optimize cost and speed.
85
-
86
- Args:
87
- routing_instruction: A string that tells the router how to route the input.
88
- mcp_servers_names: A list of server names to route the input to.
89
- agents: A list of agents to route the input to.
90
- functions: A list of functions to route the input to.
91
- """
92
-
93
- def __init__(
94
- self,
95
- server_names: List[str] | None = None,
96
- agents: List[Agent] | None = None,
97
- functions: List[Callable] | None = None,
98
- routing_instruction: str | None = None,
99
- context: Optional["Context"] = None,
100
- **kwargs,
101
- ) -> None:
102
- super().__init__(context=context, **kwargs)
103
- self.routing_instruction = routing_instruction
104
- self.server_names = server_names or []
105
- self.agents = agents or []
106
- self.functions = functions or []
107
- self.server_registry = self.context.server_registry
108
-
109
- # A dict of categories to route to, keyed by category name.
110
- # These are populated in the initialize method.
111
- self.server_categories: Dict[str, ServerRouterCategory] = {}
112
- self.agent_categories: Dict[str, AgentRouterCategory] = {}
113
- self.function_categories: Dict[str, RouterCategory] = {}
114
- self.categories: Dict[str, RouterCategory] = {}
115
- self.initialized: bool = False
116
-
117
- if not self.server_names and not self.agents and not self.functions:
118
- raise ValueError("At least one of mcp_servers_names, agents, or functions must be provided.")
119
-
120
- if self.server_names and not self.server_registry:
121
- raise ValueError("server_registry must be provided if mcp_servers_names are provided.")
122
-
123
- @abstractmethod
124
- async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
125
- """
126
- Route the input request to one or more MCP servers, agents, or functions.
127
- If no routing decision can be made, returns an empty list.
128
-
129
- Args:
130
- request: The input to route.
131
- top_k: The maximum number of top routing results to return. May return fewer.
132
- """
133
-
134
- @abstractmethod
135
- async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
136
- """Route the input to one or more MCP servers."""
137
-
138
- @abstractmethod
139
- async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
140
- """Route the input to one or more agents."""
141
-
142
- @abstractmethod
143
- async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
144
- """
145
- Route the input to one or more functions.
146
-
147
- Args:
148
- input: The input to route.
149
- """
150
-
151
- async def initialize(self) -> None:
152
- """Initialize the router categories."""
153
-
154
- if self.initialized:
155
- return
156
-
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}
159
-
160
- agent_categories = [self.get_agent_category(agent) for agent in self.agents]
161
- self.agent_categories = {category.name: category for category in agent_categories}
162
-
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}
165
-
166
- all_categories = server_categories + agent_categories + function_categories
167
-
168
- self.categories = {category.name: category for category in all_categories}
169
- self.initialized = True
170
-
171
- def get_server_category(self, server_name: str) -> ServerRouterCategory:
172
- server_config = self.server_registry.get_server_config(server_name)
173
-
174
- # TODO: saqadri - Currently we only populate the server name and description.
175
- # To make even more high fidelity routing decisions, we can populate the
176
- # tools, resources and prompts that the server has access to.
177
- return ServerRouterCategory(
178
- category=server_name,
179
- name=server_config.name if server_config else server_name,
180
- description=server_config.description,
181
- tools=[], # Empty list to avoid validation errors
182
- )
183
-
184
- def get_agent_category(self, agent: Agent) -> AgentRouterCategory:
185
- agent_description = agent.instruction({}) if callable(agent.instruction) else agent.instruction
186
-
187
- # Just get server categories without attempting to access tools
188
- # This is a simpler approach that avoids potential issues with uninitialized agents
189
- server_categories = [self.get_server_category(server_name) for server_name in agent.server_names]
190
-
191
- return AgentRouterCategory(
192
- category=agent,
193
- name=agent.name,
194
- description=agent_description,
195
- servers=server_categories,
196
- )
197
-
198
- def get_function_category(self, function: Callable) -> RouterCategory:
199
- tool = FastTool.from_function(function)
200
-
201
- return RouterCategory(
202
- category=function,
203
- name=tool.name,
204
- description=tool.description,
205
- )
206
-
207
- def format_category(self, category: RouterCategory, index: int | None = None) -> str:
208
- """Format a category into a readable string."""
209
-
210
- if isinstance(category, ServerRouterCategory):
211
- category_str = self._format_server_category(category)
212
- elif isinstance(category, AgentRouterCategory):
213
- category_str = self._format_agent_category(category)
214
- else:
215
- category_str = self._format_function_category(category)
216
-
217
- return category_str
218
-
219
- def _format_tools(self, tools: List[FastTool]) -> str:
220
- """Format a list of tools into a readable string."""
221
- if not tools:
222
- # Return a note about tools within XML tags to maintain structure
223
- return '<fastagent:tool name="info">No tool information available</fastagent:tool>'
224
-
225
- tool_descriptions = []
226
- for tool in tools:
227
- # Access tool name and description safely
228
- tool_name = getattr(tool, "name", "unnamed-tool")
229
- tool_description = getattr(tool, "description", "No description available")
230
- desc = f'<fastagent:tool name="{tool_name}">{tool_description}</fastagent:tool>'
231
- tool_descriptions.append(desc)
232
-
233
- return "\n".join(tool_descriptions)
234
-
235
- def _format_server_category(self, category: ServerRouterCategory) -> str:
236
- """Format a server category into a readable string."""
237
- # Check if we have any content (description or tools)
238
- has_description = bool(category.description)
239
- has_tools = bool(category.tools)
240
-
241
- # If no content at all, use self-closing tag
242
- if not has_description and not has_tools:
243
- return f'<fastagent:server-category name="{category.name}" />'
244
-
245
- # Otherwise, build the content
246
- description_section = ""
247
- if has_description:
248
- description_section = f"\n<fastagent:description>{category.description}</fastagent:description>"
249
-
250
- # Add tools section if we have tool information
251
- if has_tools:
252
- tools = self._format_tools(category.tools)
253
- return f"""<fastagent:server-category name="{category.name}">{description_section}
254
- <fastagent:tools>
255
- {tools}
256
- </fastagent:tools>
257
- </fastagent:server-category>"""
258
- else:
259
- # Just description, no tools
260
- return f"""<fastagent:server-category name="{category.name}">{description_section}
261
- </fastagent:server-category>"""
262
-
263
- def _format_agent_category(self, category: AgentRouterCategory) -> str:
264
- """Format an agent category into a readable string."""
265
- # Check if we have any content (description or servers)
266
- has_description = bool(category.description)
267
- has_servers = bool(category.servers)
268
-
269
- # If no content at all, use self-closing tag
270
- if not has_description and not has_servers:
271
- return f'<fastagent:agent-category name="{category.name}" />'
272
-
273
- # Build description section if needed
274
- description_section = ""
275
- if has_description:
276
- description_section = f"\n<fastagent:description>{category.description}</fastagent:description>"
277
-
278
- # Handle the case with no servers
279
- if not has_servers:
280
- return f"""<fastagent:agent-category name="{category.name}">{description_section}
281
- </fastagent:agent-category>"""
282
-
283
- # Format servers with proper XML tags and include their tools
284
- server_sections = []
285
- for server in category.servers:
286
- # Check if this server has any content
287
- has_server_description = bool(server.description)
288
- has_server_tools = bool(server.tools)
289
-
290
- # Use self-closing tag if server has no content
291
- if not has_server_description and not has_server_tools:
292
- server_section = f'<fastagent:server name="{server.name}" />'
293
- server_sections.append(server_section)
294
- continue
295
-
296
- # Build server description if needed
297
- server_desc_section = ""
298
- if has_server_description:
299
- server_desc_section = f"\n<fastagent:description>{server.description}</fastagent:description>"
300
-
301
- # Format server tools if available
302
- if has_server_tools:
303
- tool_items = []
304
- for tool in server.tools:
305
- tool_desc = tool.description if tool.description else ""
306
- tool_items.append(f'<fastagent:tool name="{tool.name}">{tool_desc}</fastagent:tool>')
307
-
308
- tools_section = f"\n<fastagent:tools>\n{chr(10).join(tool_items)}\n</fastagent:tools>"
309
- server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}{tools_section}
310
- </fastagent:server>"""
311
- else:
312
- # Just description, no tools
313
- server_section = f"""<fastagent:server name="{server.name}">{server_desc_section}
314
- </fastagent:server>"""
315
-
316
- server_sections.append(server_section)
317
-
318
- servers = "\n".join(server_sections)
319
-
320
- return f"""<fastagent:agent-category name="{category.name}">{description_section}
321
- <fastagent:servers>
322
- {servers}
323
- </fastagent:servers>
324
- </fastagent:agent-category>"""
325
-
326
- def _format_function_category(self, category: RouterCategory) -> str:
327
- """Format a function category into a readable string."""
328
- # Check if we have a description
329
- has_description = bool(category.description)
330
-
331
- # If no description, use self-closing tag
332
- if not has_description:
333
- return f'<fastagent:function-category name="{category.name}" />'
334
-
335
- # Include description
336
- return f"""<fastagent:function-category name="{category.name}">
337
- <fastagent:description>{category.description}</fastagent:description>
338
- </fastagent:function-category>"""
@@ -1,226 +0,0 @@
1
- from typing import TYPE_CHECKING, Callable, List, Optional
2
-
3
- from numpy import mean
4
-
5
- from mcp_agent.agents.agent import Agent
6
- from mcp_agent.workflows.embedding.embedding_base import (
7
- EmbeddingModel,
8
- FloatArray,
9
- compute_confidence,
10
- compute_similarity_scores,
11
- )
12
- from mcp_agent.workflows.router.router_base import (
13
- Router,
14
- RouterCategory,
15
- RouterResult,
16
- )
17
-
18
- if TYPE_CHECKING:
19
- from mcp_agent.context import Context
20
-
21
-
22
- class EmbeddingRouterCategory(RouterCategory):
23
- """A category for embedding-based routing"""
24
-
25
- embedding: FloatArray | None = None
26
- """Pre-computed embedding for this category"""
27
-
28
-
29
- class EmbeddingRouter(Router):
30
- """
31
- A router that uses embedding similarity to route requests to appropriate categories.
32
- This class helps to route an input to a specific MCP server, an Agent (an aggregation of MCP servers),
33
- or a function (any Callable).
34
-
35
- Features:
36
- - Semantic similarity based routing using embeddings
37
- - Flexible embedding model support
38
- - Support for formatting and combining category metadata
39
-
40
- Example usage:
41
- # Initialize router with embedding model
42
- router = EmbeddingRouter(
43
- embedding_model=OpenAIEmbeddingModel(model="text-embedding-3-small"),
44
- mcp_servers_names=["customer_service", "tech_support"],
45
- )
46
-
47
- # Route a request
48
- results = await router.route("My laptop keeps crashing")
49
- """
50
-
51
- def __init__(
52
- self,
53
- embedding_model: EmbeddingModel,
54
- server_names: List[str] | None = None,
55
- agents: List[Agent] | None = None,
56
- functions: List[Callable] | None = None,
57
- context: Optional["Context"] = None,
58
- **kwargs,
59
- ) -> None:
60
- super().__init__(
61
- server_names=server_names,
62
- agents=agents,
63
- functions=functions,
64
- context=context,
65
- **kwargs,
66
- )
67
-
68
- self.embedding_model = embedding_model
69
-
70
- @classmethod
71
- async def create(
72
- cls,
73
- embedding_model: EmbeddingModel,
74
- server_names: List[str] | None = None,
75
- agents: List[Agent] | None = None,
76
- functions: List[Callable] | None = None,
77
- context: Optional["Context"] = None,
78
- ) -> "EmbeddingRouter":
79
- """
80
- Factory method to create and initialize a router.
81
- Use this instead of constructor since we need async initialization.
82
- """
83
- instance = cls(
84
- embedding_model=embedding_model,
85
- server_names=server_names,
86
- agents=agents,
87
- functions=functions,
88
- context=context,
89
- )
90
- await instance.initialize()
91
- return instance
92
-
93
- async def initialize(self) -> None:
94
- """Initialize by computing embeddings for all categories"""
95
-
96
- async def create_category_with_embedding(
97
- category: RouterCategory,
98
- ) -> EmbeddingRouterCategory:
99
- # Get formatted text representation of category
100
- category_text = self.format_category(category)
101
- embedding = self._compute_embedding([category_text])
102
- category_with_embedding = EmbeddingRouterCategory(**category, embedding=embedding)
103
-
104
- return category_with_embedding
105
-
106
- if self.initialized:
107
- return
108
-
109
- # Create categories for servers, agents, and functions
110
- await super().initialize()
111
- self.initialized = False # We are not initialized yet
112
-
113
- for name, category in self.server_categories.items():
114
- category_with_embedding = await create_category_with_embedding(category)
115
- self.server_categories[name] = category_with_embedding
116
- self.categories[name] = category_with_embedding
117
-
118
- for name, category in self.agent_categories.items():
119
- category_with_embedding = await create_category_with_embedding(category)
120
- self.agent_categories[name] = category_with_embedding
121
- self.categories[name] = category_with_embedding
122
-
123
- for name, category in self.function_categories.items():
124
- category_with_embedding = await create_category_with_embedding(category)
125
- self.function_categories[name] = category_with_embedding
126
- self.categories[name] = category_with_embedding
127
-
128
- self.initialized = True
129
-
130
- async def route(self, request: str, top_k: int = 1) -> List[RouterResult[str | Agent | Callable]]:
131
- """Route the request based on embedding similarity"""
132
- if not self.initialized:
133
- await self.initialize()
134
-
135
- return await self._route_with_embedding(request, top_k)
136
-
137
- async def route_to_server(self, request: str, top_k: int = 1) -> List[RouterResult[str]]:
138
- """Route specifically to server categories"""
139
- if not self.initialized:
140
- await self.initialize()
141
-
142
- results = await self._route_with_embedding(
143
- request,
144
- top_k,
145
- include_servers=True,
146
- include_agents=False,
147
- include_functions=False,
148
- )
149
- return [r.result for r in results[:top_k]]
150
-
151
- async def route_to_agent(self, request: str, top_k: int = 1) -> List[RouterResult[Agent]]:
152
- """Route specifically to agent categories"""
153
- if not self.initialized:
154
- await self.initialize()
155
-
156
- results = await self._route_with_embedding(
157
- request,
158
- top_k,
159
- include_servers=False,
160
- include_agents=True,
161
- include_functions=False,
162
- )
163
- return [r.result for r in results[:top_k]]
164
-
165
- async def route_to_function(self, request: str, top_k: int = 1) -> List[RouterResult[Callable]]:
166
- """Route specifically to function categories"""
167
- if not self.initialized:
168
- await self.initialize()
169
-
170
- results = await self._route_with_embedding(
171
- request,
172
- top_k,
173
- include_servers=False,
174
- include_agents=False,
175
- include_functions=True,
176
- )
177
- return [r.result for r in results[:top_k]]
178
-
179
- async def _route_with_embedding(
180
- self,
181
- request: str,
182
- top_k: int = 1,
183
- include_servers: bool = True,
184
- include_agents: bool = True,
185
- include_functions: bool = True,
186
- ) -> List[RouterResult]:
187
- def create_result(category: RouterCategory, request_embedding):
188
- if category.embedding is None:
189
- return None
190
-
191
- similarity = compute_similarity_scores(request_embedding, category.embedding)
192
-
193
- return RouterResult(p_score=compute_confidence(similarity), result=category.category)
194
-
195
- request_embedding = self._compute_embedding([request])
196
-
197
- results: List[RouterResult] = []
198
- if include_servers:
199
- for _, category in self.server_categories.items():
200
- result = create_result(category, request_embedding)
201
- if result:
202
- results.append(result)
203
-
204
- if include_agents:
205
- for _, category in self.agent_categories.items():
206
- result = create_result(category, request_embedding)
207
- if result:
208
- results.append(result)
209
-
210
- if include_functions:
211
- for _, category in self.function_categories.items():
212
- result = create_result(category, request_embedding)
213
- if result:
214
- results.append(result)
215
-
216
- results.sort(key=lambda x: x.p_score, reverse=True)
217
- return results[:top_k]
218
-
219
- async def _compute_embedding(self, data: List[str]):
220
- # Get embedding for the provided text
221
- embeddings = await self.embedding_model.embed(data)
222
-
223
- # Use mean pooling to combine embeddings
224
- embedding = mean(embeddings, axis=0)
225
-
226
- return embedding
@@ -1,59 +0,0 @@
1
- from typing import TYPE_CHECKING, Callable, List, Optional
2
-
3
- from mcp_agent.agents.agent import Agent
4
- from mcp_agent.workflows.embedding.embedding_cohere import CohereEmbeddingModel
5
- from mcp_agent.workflows.router.router_embedding import EmbeddingRouter
6
-
7
- if TYPE_CHECKING:
8
- from mcp_agent.context import Context
9
-
10
-
11
- class CohereEmbeddingRouter(EmbeddingRouter):
12
- """
13
- A router that uses Cohere embedding similarity to route requests to appropriate categories.
14
- This class helps to route an input to a specific MCP server, an Agent (an aggregation of MCP servers),
15
- or a function (any Callable).
16
- """
17
-
18
- def __init__(
19
- self,
20
- server_names: List[str] | None = None,
21
- agents: List[Agent] | None = None,
22
- functions: List[Callable] | None = None,
23
- embedding_model: CohereEmbeddingModel | None = None,
24
- context: Optional["Context"] = None,
25
- **kwargs,
26
- ) -> None:
27
- embedding_model = embedding_model or CohereEmbeddingModel()
28
-
29
- super().__init__(
30
- embedding_model=embedding_model,
31
- server_names=server_names,
32
- agents=agents,
33
- functions=functions,
34
- context=context,
35
- **kwargs,
36
- )
37
-
38
- @classmethod
39
- async def create(
40
- cls,
41
- embedding_model: CohereEmbeddingModel | None = None,
42
- server_names: List[str] | None = None,
43
- agents: List[Agent] | None = None,
44
- functions: List[Callable] | None = None,
45
- context: Optional["Context"] = None,
46
- ) -> "CohereEmbeddingRouter":
47
- """
48
- Factory method to create and initialize a router.
49
- Use this instead of constructor since we need async initialization.
50
- """
51
- instance = cls(
52
- server_names=server_names,
53
- agents=agents,
54
- functions=functions,
55
- embedding_model=embedding_model,
56
- context=context,
57
- )
58
- await instance.initialize()
59
- return instance
@@ -1,59 +0,0 @@
1
- from typing import TYPE_CHECKING, Callable, List, Optional
2
-
3
- from mcp_agent.agents.agent import Agent
4
- from mcp_agent.workflows.embedding.embedding_openai import OpenAIEmbeddingModel
5
- from mcp_agent.workflows.router.router_embedding import EmbeddingRouter
6
-
7
- if TYPE_CHECKING:
8
- from mcp_agent.context import Context
9
-
10
-
11
- class OpenAIEmbeddingRouter(EmbeddingRouter):
12
- """
13
- A router that uses OpenAI embedding similarity to route requests to appropriate categories.
14
- This class helps to route an input to a specific MCP server, an Agent (an aggregation of MCP servers),
15
- or a function (any Callable).
16
- """
17
-
18
- def __init__(
19
- self,
20
- server_names: List[str] | None = None,
21
- agents: List[Agent] | None = None,
22
- functions: List[Callable] | None = None,
23
- embedding_model: OpenAIEmbeddingModel | None = None,
24
- context: Optional["Context"] = None,
25
- **kwargs,
26
- ) -> None:
27
- embedding_model = embedding_model or OpenAIEmbeddingModel()
28
-
29
- super().__init__(
30
- embedding_model=embedding_model,
31
- server_names=server_names,
32
- agents=agents,
33
- functions=functions,
34
- context=context,
35
- **kwargs,
36
- )
37
-
38
- @classmethod
39
- async def create(
40
- cls,
41
- embedding_model: OpenAIEmbeddingModel | None = None,
42
- server_names: List[str] | None = None,
43
- agents: List[Agent] | None = None,
44
- functions: List[Callable] | None = None,
45
- context: Optional["Context"] = None,
46
- ) -> "OpenAIEmbeddingRouter":
47
- """
48
- Factory method to create and initialize a router.
49
- Use this instead of constructor since we need async initialization.
50
- """
51
- instance = cls(
52
- server_names=server_names,
53
- agents=agents,
54
- functions=functions,
55
- embedding_model=embedding_model,
56
- context=context,
57
- )
58
- await instance.initialize()
59
- return instance