fast-agent-mcp 0.2.16__py3-none-any.whl → 0.2.17__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.2.16.dist-info → fast_agent_mcp-0.2.17.dist-info}/METADATA +4 -6
- {fast_agent_mcp-0.2.16.dist-info → fast_agent_mcp-0.2.17.dist-info}/RECORD +43 -43
- mcp_agent/agents/base_agent.py +50 -6
- mcp_agent/agents/workflow/orchestrator_agent.py +6 -7
- mcp_agent/agents/workflow/router_agent.py +70 -136
- mcp_agent/app.py +1 -124
- mcp_agent/cli/commands/setup.py +1 -1
- mcp_agent/config.py +16 -13
- mcp_agent/context.py +4 -22
- mcp_agent/core/agent_types.py +2 -2
- mcp_agent/core/direct_decorators.py +2 -2
- mcp_agent/core/direct_factory.py +2 -1
- mcp_agent/core/fastagent.py +1 -1
- mcp_agent/core/request_params.py +5 -1
- mcp_agent/executor/workflow_signal.py +0 -2
- mcp_agent/llm/augmented_llm.py +183 -57
- mcp_agent/llm/augmented_llm_passthrough.py +1 -1
- mcp_agent/llm/augmented_llm_playback.py +21 -1
- mcp_agent/llm/memory.py +3 -3
- mcp_agent/llm/model_factory.py +3 -1
- mcp_agent/llm/provider_key_manager.py +1 -0
- mcp_agent/llm/provider_types.py +2 -1
- mcp_agent/llm/providers/augmented_llm_anthropic.py +49 -10
- mcp_agent/llm/providers/augmented_llm_deepseek.py +0 -2
- mcp_agent/llm/providers/augmented_llm_google.py +30 -0
- mcp_agent/llm/providers/augmented_llm_openai.py +95 -158
- mcp_agent/llm/providers/multipart_converter_openai.py +10 -27
- mcp_agent/llm/providers/sampling_converter_openai.py +5 -6
- mcp_agent/mcp/interfaces.py +6 -1
- mcp_agent/mcp/mcp_aggregator.py +2 -8
- mcp_agent/mcp/prompt_message_multipart.py +25 -2
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +2 -2
- mcp_agent/resources/examples/in_dev/agent_build.py +1 -1
- mcp_agent/resources/examples/internal/job.py +1 -1
- mcp_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +1 -1
- mcp_agent/resources/examples/prompting/agent.py +0 -2
- mcp_agent/resources/examples/prompting/fastagent.config.yaml +2 -3
- mcp_agent/resources/examples/researcher/fastagent.config.yaml +1 -6
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -1
- mcp_agent/resources/examples/workflows/parallel.py +1 -1
- mcp_agent/executor/decorator_registry.py +0 -112
- {fast_agent_mcp-0.2.16.dist-info → fast_agent_mcp-0.2.17.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.16.dist-info → fast_agent_mcp-0.2.17.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.16.dist-info → fast_agent_mcp-0.2.17.dist-info}/licenses/LICENSE +0 -0
@@ -5,9 +5,8 @@ This provides a simplified implementation that routes messages to agents
|
|
5
5
|
by determining the best agent for a request and dispatching to it.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import TYPE_CHECKING, List, Optional, Tuple, Type
|
8
|
+
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Type
|
9
9
|
|
10
|
-
from mcp.types import TextContent
|
11
10
|
from pydantic import BaseModel
|
12
11
|
|
13
12
|
from mcp_agent.agents.agent import Agent
|
@@ -17,10 +16,12 @@ from mcp_agent.core.exceptions import AgentConfigError
|
|
17
16
|
from mcp_agent.core.prompt import Prompt
|
18
17
|
from mcp_agent.core.request_params import RequestParams
|
19
18
|
from mcp_agent.logging.logger import get_logger
|
20
|
-
from mcp_agent.mcp.interfaces import ModelT
|
19
|
+
from mcp_agent.mcp.interfaces import AugmentedLLMProtocol, ModelT
|
21
20
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
22
21
|
|
23
22
|
if TYPE_CHECKING:
|
23
|
+
from a2a_types.types import AgentCard
|
24
|
+
|
24
25
|
from mcp_agent.context import Context
|
25
26
|
|
26
27
|
logger = get_logger(__name__)
|
@@ -36,47 +37,17 @@ Follow these guidelines:
|
|
36
37
|
- Provide your confidence level (high, medium, low) and brief reasoning for your selection
|
37
38
|
"""
|
38
39
|
|
39
|
-
# Default routing instruction with placeholders for context
|
40
|
+
# Default routing instruction with placeholders for context (AgentCard JSON)
|
40
41
|
DEFAULT_ROUTING_INSTRUCTION = """
|
41
|
-
|
42
|
-
|
43
|
-
<fastagent:data>
|
42
|
+
Select from the following agents to handle the request:
|
44
43
|
<fastagent:agents>
|
44
|
+
[
|
45
45
|
{context}
|
46
|
+
]
|
46
47
|
</fastagent:agents>
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
</fastagent:request>
|
51
|
-
</fastagent:data>
|
52
|
-
|
53
|
-
Your task is to analyze the request and determine the most appropriate agent from the options above.
|
54
|
-
|
55
|
-
<fastagent:instruction>
|
56
|
-
Respond with JSON following the schema below:
|
57
|
-
{{
|
58
|
-
"type": "object",
|
59
|
-
"required": ["agent", "confidence", "reasoning"],
|
60
|
-
"properties": {{
|
61
|
-
"agent": {{
|
62
|
-
"type": "string",
|
63
|
-
"description": "The exact name of the selected agent"
|
64
|
-
}},
|
65
|
-
"confidence": {{
|
66
|
-
"type": "string",
|
67
|
-
"enum": ["high", "medium", "low"],
|
68
|
-
"description": "Your confidence level in this selection"
|
69
|
-
}},
|
70
|
-
"reasoning": {{
|
71
|
-
"type": "string",
|
72
|
-
"description": "Brief explanation for your selection"
|
73
|
-
}}
|
74
|
-
}}
|
75
|
-
}}
|
76
|
-
|
77
|
-
Supply only the JSON with no preamble. Use "reasoning" field to describe actions. NEVER EMIT CODE FENCES.
|
78
|
-
|
79
|
-
</fastagent:instruction>
|
49
|
+
You must respond with the 'name' of one of the agents listed above.
|
50
|
+
|
80
51
|
"""
|
81
52
|
|
82
53
|
|
@@ -85,18 +56,7 @@ class RoutingResponse(BaseModel):
|
|
85
56
|
|
86
57
|
agent: str
|
87
58
|
confidence: str
|
88
|
-
reasoning:
|
89
|
-
|
90
|
-
|
91
|
-
class RouterResult(BaseModel):
|
92
|
-
"""Router result with agent reference and confidence rating."""
|
93
|
-
|
94
|
-
result: BaseAgent
|
95
|
-
confidence: str
|
96
|
-
reasoning: Optional[str] = None
|
97
|
-
|
98
|
-
# Allow Agent objects to be stored without serialization
|
99
|
-
model_config = {"arbitrary_types_allowed": True}
|
59
|
+
reasoning: str | None = None
|
100
60
|
|
101
61
|
|
102
62
|
class RouterAgent(BaseAgent):
|
@@ -142,9 +102,7 @@ class RouterAgent(BaseAgent):
|
|
142
102
|
# Set up base router request parameters
|
143
103
|
base_params = {"systemPrompt": ROUTING_SYSTEM_INSTRUCTION, "use_history": False}
|
144
104
|
|
145
|
-
# Merge with provided defaults if any
|
146
105
|
if default_request_params:
|
147
|
-
# Start with defaults and override with router-specific settings
|
148
106
|
merged_params = default_request_params.model_copy(update=base_params)
|
149
107
|
else:
|
150
108
|
merged_params = RequestParams(**base_params)
|
@@ -174,32 +132,16 @@ class RouterAgent(BaseAgent):
|
|
174
132
|
except Exception as e:
|
175
133
|
logger.warning(f"Error shutting down agent: {str(e)}")
|
176
134
|
|
177
|
-
async def
|
135
|
+
async def attach_llm(
|
178
136
|
self,
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
Returns:
|
188
|
-
RouterResult containing the selected agent, or None if no suitable agent found
|
189
|
-
"""
|
190
|
-
if not self.initialized:
|
191
|
-
await self.initialize()
|
192
|
-
|
193
|
-
# Extract the request text from the last message
|
194
|
-
request = messages[-1].all_text() if messages else ""
|
195
|
-
|
196
|
-
# Determine which agent to route to
|
197
|
-
routing_result = await self._route_request(request)
|
198
|
-
|
199
|
-
if not routing_result:
|
200
|
-
logger.warning("Could not determine appropriate agent for this request")
|
201
|
-
|
202
|
-
return routing_result
|
137
|
+
llm_factory: type[AugmentedLLMProtocol] | Callable[..., AugmentedLLMProtocol],
|
138
|
+
model: str | None = None,
|
139
|
+
request_params: RequestParams | None = None,
|
140
|
+
**additional_kwargs,
|
141
|
+
) -> AugmentedLLMProtocol:
|
142
|
+
return await super().attach_llm(
|
143
|
+
llm_factory, model, request_params, verb="Routing", **additional_kwargs
|
144
|
+
)
|
203
145
|
|
204
146
|
async def generate(
|
205
147
|
self,
|
@@ -216,32 +158,21 @@ class RouterAgent(BaseAgent):
|
|
216
158
|
Returns:
|
217
159
|
The response from the selected agent
|
218
160
|
"""
|
219
|
-
routing_result = await self._get_routing_result(multipart_messages)
|
220
|
-
|
221
|
-
if not routing_result:
|
222
|
-
return PromptMessageMultipart(
|
223
|
-
role="assistant",
|
224
|
-
content=[
|
225
|
-
TextContent(
|
226
|
-
type="text", text="Could not determine appropriate agent for this request."
|
227
|
-
)
|
228
|
-
],
|
229
|
-
)
|
230
161
|
|
231
|
-
|
232
|
-
selected_agent = routing_result.result
|
162
|
+
route, warn = await self._route_request(multipart_messages[-1])
|
233
163
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
164
|
+
if not route:
|
165
|
+
return Prompt.assistant(warn or "No routing result or warning received")
|
166
|
+
|
167
|
+
# Get the selected agent
|
168
|
+
agent: Agent = self.agent_map[route.agent]
|
238
169
|
|
239
170
|
# Dispatch the request to the selected agent
|
240
|
-
return await
|
171
|
+
return await agent.generate(multipart_messages, request_params)
|
241
172
|
|
242
173
|
async def structured(
|
243
174
|
self,
|
244
|
-
|
175
|
+
multipart_messages: List[PromptMessageMultipart],
|
245
176
|
model: Type[ModelT],
|
246
177
|
request_params: Optional[RequestParams] = None,
|
247
178
|
) -> Tuple[ModelT | None, PromptMessageMultipart]:
|
@@ -256,23 +187,22 @@ class RouterAgent(BaseAgent):
|
|
256
187
|
Returns:
|
257
188
|
The parsed response from the selected agent, or None if parsing fails
|
258
189
|
"""
|
259
|
-
|
190
|
+
route, warn = await self._route_request(multipart_messages[-1])
|
260
191
|
|
261
|
-
if not
|
262
|
-
return None, Prompt.assistant(
|
192
|
+
if not route:
|
193
|
+
return None, Prompt.assistant(
|
194
|
+
warn or "No routing result or warning received (structured)"
|
195
|
+
)
|
263
196
|
|
264
197
|
# Get the selected agent
|
265
|
-
|
266
|
-
|
267
|
-
# Log the routing decision
|
268
|
-
logger.info(
|
269
|
-
f"Routing structured request to agent: {selected_agent.name} (confidence: {routing_result.confidence})"
|
270
|
-
)
|
198
|
+
agent: Agent = self.agent_map[route.agent]
|
271
199
|
|
272
200
|
# Dispatch the request to the selected agent
|
273
|
-
return await
|
201
|
+
return await agent.structured(multipart_messages, model, request_params)
|
274
202
|
|
275
|
-
async def _route_request(
|
203
|
+
async def _route_request(
|
204
|
+
self, message: PromptMessageMultipart
|
205
|
+
) -> Tuple[RoutingResponse | None, str | None]:
|
276
206
|
"""
|
277
207
|
Determine which agent to route the request to.
|
278
208
|
|
@@ -283,49 +213,53 @@ class RouterAgent(BaseAgent):
|
|
283
213
|
RouterResult containing the selected agent, or None if no suitable agent was found
|
284
214
|
"""
|
285
215
|
if not self.agents:
|
286
|
-
logger.
|
287
|
-
|
216
|
+
logger.error("No agents available for routing")
|
217
|
+
raise AgentConfigError("No agents available for routing - fatal error")
|
288
218
|
|
289
219
|
# If only one agent is available, use it directly
|
290
220
|
if len(self.agents) == 1:
|
291
|
-
return
|
292
|
-
|
293
|
-
)
|
221
|
+
return RoutingResponse(
|
222
|
+
agent=self.agents[0].name, confidence="high", reasoning="Only one agent available"
|
223
|
+
), None
|
294
224
|
|
295
225
|
# Generate agent descriptions for the context
|
296
226
|
agent_descriptions = []
|
297
|
-
for
|
298
|
-
|
299
|
-
agent_descriptions.append(
|
227
|
+
for agent in self.agents:
|
228
|
+
agent_card: AgentCard = await agent.agent_card()
|
229
|
+
agent_descriptions.append(
|
230
|
+
agent_card.model_dump_json(
|
231
|
+
include={"name", "description", "skills"}, exclude_none=True
|
232
|
+
)
|
233
|
+
)
|
300
234
|
|
301
|
-
context = "
|
235
|
+
context = ",\n".join(agent_descriptions)
|
302
236
|
|
303
237
|
# Format the routing prompt
|
304
238
|
routing_instruction = self.routing_instruction or DEFAULT_ROUTING_INSTRUCTION
|
305
|
-
|
306
|
-
|
307
|
-
# Create multipart message for the router
|
308
|
-
prompt = PromptMessageMultipart(
|
309
|
-
role="user", content=[TextContent(type="text", text=prompt_text)]
|
310
|
-
)
|
239
|
+
routing_instruction = routing_instruction.format(context=context)
|
311
240
|
|
312
|
-
# Get structured response from LLM
|
313
241
|
assert self._llm
|
242
|
+
mutated = message.model_copy(deep=True)
|
243
|
+
mutated.add_text(routing_instruction)
|
314
244
|
response, _ = await self._llm.structured(
|
315
|
-
[
|
245
|
+
[mutated],
|
246
|
+
RoutingResponse,
|
247
|
+
self._default_request_params,
|
316
248
|
)
|
317
249
|
|
250
|
+
warn: str | None = None
|
318
251
|
if not response:
|
319
|
-
|
320
|
-
|
252
|
+
warn = "No routing response received from LLM"
|
253
|
+
elif response.agent not in self.agent_map:
|
254
|
+
warn = f"A response was received, but the agent {response.agent} was not known to the Router"
|
321
255
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
256
|
+
if warn:
|
257
|
+
logger.warning(warn)
|
258
|
+
return None, warn
|
259
|
+
else:
|
260
|
+
assert response
|
261
|
+
logger.info(
|
262
|
+
f"Routing structured request to agent: {response.agent or 'error'} (confidence: {response.confidence or ''})"
|
263
|
+
)
|
328
264
|
|
329
|
-
|
330
|
-
result=selected_agent, confidence=response.confidence, reasoning=response.reasoning
|
331
|
-
)
|
265
|
+
return response, None
|
mcp_agent/app.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
from contextlib import asynccontextmanager
|
3
|
-
from
|
4
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, TypeVar
|
3
|
+
from typing import TYPE_CHECKING, Dict, Optional, Type, TypeVar
|
5
4
|
|
6
5
|
from mcp_agent.config import Settings
|
7
6
|
from mcp_agent.context import Context, cleanup_context, initialize_context
|
@@ -174,125 +173,3 @@ class MCPApp:
|
|
174
173
|
yield self
|
175
174
|
finally:
|
176
175
|
await self.cleanup()
|
177
|
-
|
178
|
-
def workflow(self, cls: Type, *args, workflow_id: str | None = None, **kwargs) -> Type:
|
179
|
-
"""
|
180
|
-
Decorator for a workflow class. By default it's a no-op,
|
181
|
-
but different executors can use this to customize behavior
|
182
|
-
for workflow registration.
|
183
|
-
|
184
|
-
Example:
|
185
|
-
If Temporal is available & we use a TemporalExecutor,
|
186
|
-
this decorator will wrap with temporal_workflow.defn.
|
187
|
-
"""
|
188
|
-
decorator_registry = self.context.decorator_registry
|
189
|
-
execution_engine = self.engine
|
190
|
-
workflow_defn_decorator = decorator_registry.get_workflow_defn_decorator(execution_engine)
|
191
|
-
|
192
|
-
if workflow_defn_decorator:
|
193
|
-
return workflow_defn_decorator(cls, *args, **kwargs)
|
194
|
-
|
195
|
-
cls._app = self
|
196
|
-
self._workflows[workflow_id or cls.__name__] = cls
|
197
|
-
|
198
|
-
# Default no-op
|
199
|
-
return cls
|
200
|
-
|
201
|
-
def workflow_run(self, fn: Callable[..., R]) -> Callable[..., R]:
|
202
|
-
"""
|
203
|
-
Decorator for a workflow's main 'run' method.
|
204
|
-
Different executors can use this to customize behavior for workflow execution.
|
205
|
-
|
206
|
-
Example:
|
207
|
-
If Temporal is in use, this gets converted to @workflow.run.
|
208
|
-
"""
|
209
|
-
|
210
|
-
decorator_registry = self.context.decorator_registry
|
211
|
-
execution_engine = self.engine
|
212
|
-
workflow_run_decorator = decorator_registry.get_workflow_run_decorator(execution_engine)
|
213
|
-
|
214
|
-
if workflow_run_decorator:
|
215
|
-
return workflow_run_decorator(fn)
|
216
|
-
|
217
|
-
# Default no-op
|
218
|
-
def wrapper(*args, **kwargs):
|
219
|
-
# no-op wrapper
|
220
|
-
return fn(*args, **kwargs)
|
221
|
-
|
222
|
-
return wrapper
|
223
|
-
|
224
|
-
def workflow_task(
|
225
|
-
self,
|
226
|
-
name: str | None = None,
|
227
|
-
schedule_to_close_timeout: timedelta | None = None,
|
228
|
-
retry_policy: Dict[str, Any] | None = None,
|
229
|
-
**kwargs: Any,
|
230
|
-
) -> Callable[[Callable[..., R]], Callable[..., R]]:
|
231
|
-
"""
|
232
|
-
Decorator to mark a function as a workflow task,
|
233
|
-
automatically registering it in the global activity registry.
|
234
|
-
|
235
|
-
Args:
|
236
|
-
name: Optional custom name for the activity
|
237
|
-
schedule_to_close_timeout: Maximum time the task can take to complete
|
238
|
-
retry_policy: Retry policy configuration
|
239
|
-
**kwargs: Additional metadata passed to the activity registration
|
240
|
-
|
241
|
-
Returns:
|
242
|
-
Decorated function that preserves async and typing information
|
243
|
-
|
244
|
-
Raises:
|
245
|
-
TypeError: If the decorated function is not async
|
246
|
-
ValueError: If the retry policy or timeout is invalid
|
247
|
-
"""
|
248
|
-
|
249
|
-
def decorator(func: Callable[..., R]) -> Callable[..., R]:
|
250
|
-
if not asyncio.iscoroutinefunction(func):
|
251
|
-
raise TypeError(f"Function {func.__name__} must be async.")
|
252
|
-
|
253
|
-
actual_name = name or f"{func.__module__}.{func.__qualname__}"
|
254
|
-
timeout = schedule_to_close_timeout or timedelta(minutes=10)
|
255
|
-
metadata = {
|
256
|
-
"activity_name": actual_name,
|
257
|
-
"schedule_to_close_timeout": timeout,
|
258
|
-
"retry_policy": retry_policy or {},
|
259
|
-
**kwargs,
|
260
|
-
}
|
261
|
-
activity_registry = self.context.task_registry
|
262
|
-
activity_registry.register(actual_name, func, metadata)
|
263
|
-
|
264
|
-
setattr(func, "is_workflow_task", True)
|
265
|
-
setattr(func, "execution_metadata", metadata)
|
266
|
-
|
267
|
-
# TODO: saqadri - determine if we need this
|
268
|
-
# Preserve metadata through partial application
|
269
|
-
# @functools.wraps(func)
|
270
|
-
# async def wrapper(*args: Any, **kwargs: Any) -> R:
|
271
|
-
# result = await func(*args, **kwargs)
|
272
|
-
# return cast(R, result) # Ensure type checking works
|
273
|
-
|
274
|
-
# # Add metadata that survives partial application
|
275
|
-
# wrapper.is_workflow_task = True # type: ignore
|
276
|
-
# wrapper.execution_metadata = metadata # type: ignore
|
277
|
-
|
278
|
-
# # Make metadata accessible through partial
|
279
|
-
# def __getattr__(name: str) -> Any:
|
280
|
-
# if name == "is_workflow_task":
|
281
|
-
# return True
|
282
|
-
# if name == "execution_metadata":
|
283
|
-
# return metadata
|
284
|
-
# raise AttributeError(f"'{func.__name__}' has no attribute '{name}'")
|
285
|
-
|
286
|
-
# wrapper.__getattr__ = __getattr__ # type: ignore
|
287
|
-
|
288
|
-
# return wrapper
|
289
|
-
|
290
|
-
return func
|
291
|
-
|
292
|
-
return decorator
|
293
|
-
|
294
|
-
def is_workflow_task(self, func: Callable[..., Any]) -> bool:
|
295
|
-
"""
|
296
|
-
Check if a function is marked as a workflow task.
|
297
|
-
This gets set for functions that are decorated with @workflow_task."""
|
298
|
-
return bool(getattr(func, "is_workflow_task", False))
|
mcp_agent/cli/commands/setup.py
CHANGED
@@ -15,7 +15,7 @@ FASTAGENT_CONFIG_TEMPLATE = """
|
|
15
15
|
# Takes format:
|
16
16
|
# <provider>.<model_string>.<reasoning_effort?> (e.g. anthropic.claude-3-5-sonnet-20241022 or openai.o3-mini.low)
|
17
17
|
# Accepts aliases for Anthropic Models: haiku, haiku3, sonnet, sonnet35, opus, opus3
|
18
|
-
# and OpenAI Models: gpt-
|
18
|
+
# and OpenAI Models: gpt-4.1, gpt-4.1-mini, o1, o1-mini, o3-mini
|
19
19
|
#
|
20
20
|
# If not specified, defaults to "haiku".
|
21
21
|
# Can be overriden with a command line switch --model=<model>, or within the Agent constructor.
|
mcp_agent/config.py
CHANGED
@@ -139,40 +139,42 @@ class DeepSeekSettings(BaseModel):
|
|
139
139
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
140
140
|
|
141
141
|
|
142
|
-
class
|
142
|
+
class GoogleSettings(BaseModel):
|
143
143
|
"""
|
144
144
|
Settings for using OpenAI models in the fast-agent application.
|
145
145
|
"""
|
146
146
|
|
147
147
|
api_key: str | None = None
|
148
|
+
# reasoning_effort: Literal["low", "medium", "high"] = "medium"
|
148
149
|
|
149
150
|
base_url: str | None = None
|
150
151
|
|
151
152
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
152
153
|
|
153
154
|
|
154
|
-
class
|
155
|
+
class GenericSettings(BaseModel):
|
155
156
|
"""
|
156
|
-
Settings for using
|
157
|
+
Settings for using OpenAI models in the fast-agent application.
|
157
158
|
"""
|
158
159
|
|
159
160
|
api_key: str | None = None
|
160
161
|
|
161
|
-
base_url: str | None = None
|
162
|
+
base_url: str | None = None
|
162
163
|
|
163
164
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
164
165
|
|
165
166
|
|
166
|
-
class
|
167
|
+
class OpenRouterSettings(BaseModel):
|
167
168
|
"""
|
168
|
-
|
169
|
+
Settings for using OpenRouter models via its OpenAI-compatible API.
|
169
170
|
"""
|
170
171
|
|
171
|
-
host: str
|
172
|
-
namespace: str = "default"
|
173
|
-
task_queue: str
|
174
172
|
api_key: str | None = None
|
175
173
|
|
174
|
+
base_url: str | None = None # Optional override, defaults handled in provider
|
175
|
+
|
176
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
177
|
+
|
176
178
|
|
177
179
|
class OpenTelemetrySettings(BaseModel):
|
178
180
|
"""
|
@@ -254,16 +256,14 @@ class Settings(BaseSettings):
|
|
254
256
|
mcp: MCPSettings | None = MCPSettings()
|
255
257
|
"""MCP config, such as MCP servers"""
|
256
258
|
|
257
|
-
execution_engine: Literal["asyncio"
|
259
|
+
execution_engine: Literal["asyncio"] = "asyncio"
|
258
260
|
"""Execution engine for the fast-agent application"""
|
259
261
|
|
260
262
|
default_model: str | None = "haiku"
|
261
263
|
"""
|
262
264
|
Default model for agents. Format is provider.model_name.<reasoning_effort>, for example openai.o3-mini.low
|
263
|
-
Aliases are provided for common models e.g. sonnet, haiku, gpt-
|
265
|
+
Aliases are provided for common models e.g. sonnet, haiku, gpt-4.1, o3-mini etc.
|
264
266
|
"""
|
265
|
-
temporal: TemporalSettings | None = None
|
266
|
-
"""Settings for Temporal workflow orchestration"""
|
267
267
|
|
268
268
|
anthropic: AnthropicSettings | None = None
|
269
269
|
"""Settings for using Anthropic models in the fast-agent application"""
|
@@ -277,6 +277,9 @@ class Settings(BaseSettings):
|
|
277
277
|
deepseek: DeepSeekSettings | None = None
|
278
278
|
"""Settings for using DeepSeek models in the fast-agent application"""
|
279
279
|
|
280
|
+
google: GoogleSettings | None = None
|
281
|
+
"""Settings for using DeepSeek models in the fast-agent application"""
|
282
|
+
|
280
283
|
openrouter: OpenRouterSettings | None = None
|
281
284
|
"""Settings for using OpenRouter models in the fast-agent application"""
|
282
285
|
|
mcp_agent/context.py
CHANGED
@@ -17,10 +17,6 @@ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapProp
|
|
17
17
|
from pydantic import BaseModel, ConfigDict
|
18
18
|
|
19
19
|
from mcp_agent.config import Settings, get_settings
|
20
|
-
from mcp_agent.executor.decorator_registry import (
|
21
|
-
DecoratorRegistry,
|
22
|
-
register_asyncio_decorators,
|
23
|
-
)
|
24
20
|
from mcp_agent.executor.executor import AsyncioExecutor, Executor
|
25
21
|
from mcp_agent.executor.task_registry import ActivityRegistry
|
26
22
|
from mcp_agent.logging.events import EventFilter
|
@@ -54,7 +50,6 @@ class Context(BaseModel):
|
|
54
50
|
# Registries
|
55
51
|
server_registry: Optional[ServerRegistry] = None
|
56
52
|
task_registry: Optional[ActivityRegistry] = None
|
57
|
-
decorator_registry: Optional[DecoratorRegistry] = None
|
58
53
|
|
59
54
|
tracer: Optional[trace.Tracer] = None
|
60
55
|
|
@@ -142,18 +137,7 @@ async def configure_executor(config: "Settings"):
|
|
142
137
|
"""
|
143
138
|
Configure the executor based on the application config.
|
144
139
|
"""
|
145
|
-
|
146
|
-
return AsyncioExecutor()
|
147
|
-
elif config.execution_engine == "temporal":
|
148
|
-
# Configure Temporal executor
|
149
|
-
from mcp_agent.executor.temporal import TemporalExecutor
|
150
|
-
|
151
|
-
executor = TemporalExecutor(config=config.temporal)
|
152
|
-
return executor
|
153
|
-
else:
|
154
|
-
# Default to asyncio executor
|
155
|
-
executor = AsyncioExecutor()
|
156
|
-
return executor
|
140
|
+
return AsyncioExecutor()
|
157
141
|
|
158
142
|
|
159
143
|
async def initialize_context(
|
@@ -180,11 +164,9 @@ async def initialize_context(
|
|
180
164
|
context.executor = await configure_executor(config)
|
181
165
|
context.task_registry = ActivityRegistry()
|
182
166
|
|
183
|
-
context.decorator_registry = DecoratorRegistry()
|
184
|
-
register_asyncio_decorators(context.decorator_registry)
|
185
|
-
|
186
167
|
# Store the tracer in context if needed
|
187
|
-
|
168
|
+
if config.otel:
|
169
|
+
context.tracer = trace.get_tracer(config.otel.service_name)
|
188
170
|
|
189
171
|
if store_globally:
|
190
172
|
global _global_context
|
@@ -234,7 +216,7 @@ def get_current_context() -> Context:
|
|
234
216
|
def get_current_config():
|
235
217
|
"""
|
236
218
|
Get the current application config.
|
237
|
-
|
219
|
+
|
238
220
|
Returns the context config if available, otherwise falls back to global settings.
|
239
221
|
"""
|
240
222
|
return get_current_context().config or get_settings()
|
mcp_agent/core/agent_types.py
CHANGED
@@ -34,8 +34,8 @@ class AgentConfig(BaseModel):
|
|
34
34
|
human_input: bool = False
|
35
35
|
agent_type: AgentType = AgentType.BASIC
|
36
36
|
|
37
|
-
@model_validator(mode=
|
38
|
-
def ensure_default_request_params(self) ->
|
37
|
+
@model_validator(mode="after")
|
38
|
+
def ensure_default_request_params(self) -> "AgentConfig":
|
39
39
|
"""Ensure default_request_params exists with proper history setting"""
|
40
40
|
if self.default_request_params is None:
|
41
41
|
self.default_request_params = RequestParams(
|
@@ -224,7 +224,7 @@ def orchestrator(
|
|
224
224
|
use_history: bool = False,
|
225
225
|
human_input: bool = False,
|
226
226
|
plan_type: Literal["full", "iterative"] = "full",
|
227
|
-
|
227
|
+
plan_iterations: int = 5,
|
228
228
|
) -> Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]:
|
229
229
|
"""
|
230
230
|
Decorator to create and register an orchestrator agent with type-safe signature.
|
@@ -260,7 +260,7 @@ def orchestrator(
|
|
260
260
|
human_input=human_input,
|
261
261
|
child_agents=agents,
|
262
262
|
plan_type=plan_type,
|
263
|
-
|
263
|
+
plan_iterations=plan_iterations,
|
264
264
|
),
|
265
265
|
)
|
266
266
|
|
mcp_agent/core/direct_factory.py
CHANGED
@@ -138,7 +138,7 @@ async def create_agents_by_type(
|
|
138
138
|
# Get common configuration
|
139
139
|
config = agent_data["config"]
|
140
140
|
|
141
|
-
|
141
|
+
# Type-specific initialization based on the Enum type
|
142
142
|
# Note: Above we compared string values from config, here we compare Enum objects directly
|
143
143
|
if agent_type == AgentType.BASIC:
|
144
144
|
# Create a basic agent
|
@@ -175,6 +175,7 @@ async def create_agents_by_type(
|
|
175
175
|
config=config,
|
176
176
|
context=app_instance.context,
|
177
177
|
agents=child_agents,
|
178
|
+
plan_iterations=agent_data.get("plan_iterations", 5),
|
178
179
|
plan_type=agent_data.get("plan_type", "full"),
|
179
180
|
)
|
180
181
|
|
mcp_agent/core/fastagent.py
CHANGED
@@ -381,7 +381,7 @@ class FastAgent:
|
|
381
381
|
handle_error(
|
382
382
|
e,
|
383
383
|
"Model Configuration Error",
|
384
|
-
"Common models: gpt-
|
384
|
+
"Common models: gpt-4.1, o3-mini, sonnet, haiku. for o3, set reasoning effort with o3-mini.high",
|
385
385
|
)
|
386
386
|
elif isinstance(e, CircularDependencyError):
|
387
387
|
handle_error(
|
mcp_agent/core/request_params.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
Request parameters definitions for LLM interactions.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import List
|
5
|
+
from typing import Any, List
|
6
6
|
|
7
7
|
from mcp import SamplingMessage
|
8
8
|
from mcp.types import CreateMessageRequestParams
|
@@ -44,3 +44,7 @@ class RequestParams(CreateMessageRequestParams):
|
|
44
44
|
Whether to allow multiple tool calls per iteration.
|
45
45
|
Also known as multi-step tool use.
|
46
46
|
"""
|
47
|
+
response_format: Any | None = None
|
48
|
+
"""
|
49
|
+
Override response format for structured calls. Prefer sending pydantic model - only use in exceptional circumstances
|
50
|
+
"""
|