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
mcp_agent/core/validation.py
CHANGED
@@ -2,17 +2,22 @@
|
|
2
2
|
Validation utilities for FastAgent configuration and dependencies.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import Dict, List
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
|
6
7
|
from mcp_agent.core.agent_types import AgentType
|
8
|
+
from mcp_agent.core.exceptions import (
|
9
|
+
AgentConfigError,
|
10
|
+
CircularDependencyError,
|
11
|
+
ServerConfigError,
|
12
|
+
)
|
7
13
|
from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM
|
8
|
-
from mcp_agent.core.exceptions import ServerConfigError, AgentConfigError, CircularDependencyError
|
9
14
|
|
10
15
|
|
11
16
|
def validate_server_references(context, agents: Dict[str, Dict[str, Any]]) -> None:
|
12
17
|
"""
|
13
18
|
Validate that all server references in agent configurations exist in config.
|
14
19
|
Raises ServerConfigError if any referenced servers are not defined.
|
15
|
-
|
20
|
+
|
16
21
|
Args:
|
17
22
|
context: Application context
|
18
23
|
agents: Dictionary of agent configurations
|
@@ -39,7 +44,7 @@ def validate_workflow_references(agents: Dict[str, Dict[str, Any]]) -> None:
|
|
39
44
|
Validate that all workflow references point to valid agents/workflows.
|
40
45
|
Also validates that referenced agents have required configuration.
|
41
46
|
Raises AgentConfigError if any validation fails.
|
42
|
-
|
47
|
+
|
43
48
|
Args:
|
44
49
|
agents: Dictionary of agent configurations
|
45
50
|
"""
|
@@ -133,11 +138,11 @@ def validate_workflow_references(agents: Dict[str, Dict[str, Any]]) -> None:
|
|
133
138
|
|
134
139
|
|
135
140
|
def get_dependencies(
|
136
|
-
name: str,
|
141
|
+
name: str,
|
137
142
|
agents: Dict[str, Dict[str, Any]],
|
138
|
-
visited: set,
|
139
|
-
path: set,
|
140
|
-
agent_type: AgentType = None
|
143
|
+
visited: set,
|
144
|
+
path: set,
|
145
|
+
agent_type: AgentType = None,
|
141
146
|
) -> List[str]:
|
142
147
|
"""
|
143
148
|
Get dependencies for an agent in topological order.
|
@@ -184,9 +189,7 @@ def get_dependencies(
|
|
184
189
|
# Get dependencies from sequence agents
|
185
190
|
sequence = config.get("sequence", config.get("agents", []))
|
186
191
|
for agent_name in sequence:
|
187
|
-
deps.extend(
|
188
|
-
get_dependencies(agent_name, agents, visited, path, agent_type)
|
189
|
-
)
|
192
|
+
deps.extend(get_dependencies(agent_name, agents, visited, path, agent_type))
|
190
193
|
|
191
194
|
# Add this agent after its dependencies
|
192
195
|
deps.append(name)
|
@@ -197,10 +200,7 @@ def get_dependencies(
|
|
197
200
|
|
198
201
|
|
199
202
|
def get_parallel_dependencies(
|
200
|
-
name: str,
|
201
|
-
agents: Dict[str, Dict[str, Any]],
|
202
|
-
visited: set,
|
203
|
-
path: set
|
203
|
+
name: str, agents: Dict[str, Dict[str, Any]], visited: set, path: set
|
204
204
|
) -> List[str]:
|
205
205
|
"""
|
206
206
|
Get dependencies for a parallel agent in topological order.
|
@@ -218,4 +218,4 @@ def get_parallel_dependencies(
|
|
218
218
|
Raises:
|
219
219
|
CircularDependencyError: If circular dependency detected
|
220
220
|
"""
|
221
|
-
return get_dependencies(name, agents, visited, path, AgentType.PARALLEL)
|
221
|
+
return get_dependencies(name, agents, visited, path, AgentType.PARALLEL)
|
@@ -11,17 +11,15 @@ R = TypeVar("R")
|
|
11
11
|
class DecoratorRegistry:
|
12
12
|
"""Centralized decorator management with validation and metadata."""
|
13
13
|
|
14
|
-
def __init__(self):
|
14
|
+
def __init__(self) -> None:
|
15
15
|
self._workflow_defn_decorators: Dict[str, Callable[[Type], Type]] = {}
|
16
|
-
self._workflow_run_decorators: Dict[
|
17
|
-
str, Callable[[Callable[..., R]], Callable[..., R]]
|
18
|
-
] = {}
|
16
|
+
self._workflow_run_decorators: Dict[str, Callable[[Callable[..., R]], Callable[..., R]]] = {}
|
19
17
|
|
20
18
|
def register_workflow_defn_decorator(
|
21
19
|
self,
|
22
20
|
executor_name: str,
|
23
21
|
decorator: Callable[[Type], Type],
|
24
|
-
):
|
22
|
+
) -> None:
|
25
23
|
"""
|
26
24
|
Registers a workflow definition decorator for a given executor.
|
27
25
|
|
@@ -48,7 +46,7 @@ class DecoratorRegistry:
|
|
48
46
|
self,
|
49
47
|
executor_name: str,
|
50
48
|
decorator: Callable[[Callable[..., R]], Callable[..., R]],
|
51
|
-
):
|
49
|
+
) -> None:
|
52
50
|
"""
|
53
51
|
Registers a workflow run decorator for a given executor.
|
54
52
|
|
@@ -62,9 +60,7 @@ class DecoratorRegistry:
|
|
62
60
|
)
|
63
61
|
self._workflow_run_decorators[executor_name] = decorator
|
64
62
|
|
65
|
-
def get_workflow_run_decorator(
|
66
|
-
self, executor_name: str
|
67
|
-
) -> Callable[[Callable[..., R]], Callable[..., R]]:
|
63
|
+
def get_workflow_run_decorator(self, executor_name: str) -> Callable[[Callable[..., R]], Callable[..., R]]:
|
68
64
|
"""
|
69
65
|
Retrieves a workflow run decorator for a given executor.
|
70
66
|
|
@@ -88,18 +84,14 @@ def default_workflow_run(fn: Callable[..., R]) -> Callable[..., R]:
|
|
88
84
|
return wrapper
|
89
85
|
|
90
86
|
|
91
|
-
def register_asyncio_decorators(decorator_registry: DecoratorRegistry):
|
87
|
+
def register_asyncio_decorators(decorator_registry: DecoratorRegistry) -> None:
|
92
88
|
"""Registers default asyncio decorators."""
|
93
89
|
executor_name = "asyncio"
|
94
|
-
decorator_registry.register_workflow_defn_decorator(
|
95
|
-
|
96
|
-
)
|
97
|
-
decorator_registry.register_workflow_run_decorator(
|
98
|
-
executor_name, default_workflow_run
|
99
|
-
)
|
90
|
+
decorator_registry.register_workflow_defn_decorator(executor_name, default_workflow_defn)
|
91
|
+
decorator_registry.register_workflow_run_decorator(executor_name, default_workflow_run)
|
100
92
|
|
101
93
|
|
102
|
-
def register_temporal_decorators(decorator_registry: DecoratorRegistry):
|
94
|
+
def register_temporal_decorators(decorator_registry: DecoratorRegistry) -> None:
|
103
95
|
"""Registers Temporal decorators if Temporal SDK is available."""
|
104
96
|
try:
|
105
97
|
import temporalio.workflow as temporal_workflow
|
@@ -112,9 +104,5 @@ def register_temporal_decorators(decorator_registry: DecoratorRegistry):
|
|
112
104
|
return
|
113
105
|
|
114
106
|
executor_name = "temporal"
|
115
|
-
decorator_registry.register_workflow_defn_decorator(
|
116
|
-
|
117
|
-
)
|
118
|
-
decorator_registry.register_workflow_run_decorator(
|
119
|
-
executor_name, temporal_workflow.run
|
120
|
-
)
|
107
|
+
decorator_registry.register_workflow_defn_decorator(executor_name, temporal_workflow.defn)
|
108
|
+
decorator_registry.register_workflow_run_decorator(executor_name, temporal_workflow.run)
|
mcp_agent/executor/executor.py
CHANGED
@@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
|
|
4
4
|
from contextlib import asynccontextmanager
|
5
5
|
from datetime import timedelta
|
6
6
|
from typing import (
|
7
|
+
TYPE_CHECKING,
|
7
8
|
Any,
|
8
9
|
AsyncIterator,
|
9
10
|
Callable,
|
@@ -13,7 +14,6 @@ from typing import (
|
|
13
14
|
Optional,
|
14
15
|
Type,
|
15
16
|
TypeVar,
|
16
|
-
TYPE_CHECKING,
|
17
17
|
)
|
18
18
|
|
19
19
|
from pydantic import BaseModel, ConfigDict
|
@@ -56,7 +56,7 @@ class Executor(ABC, ContextDependent):
|
|
56
56
|
signal_bus: SignalHandler = None,
|
57
57
|
context: Optional["Context"] = None,
|
58
58
|
**kwargs,
|
59
|
-
):
|
59
|
+
) -> None:
|
60
60
|
super().__init__(context=context, **kwargs)
|
61
61
|
self.execution_engine = engine
|
62
62
|
|
@@ -127,9 +127,7 @@ class Executor(ABC, ContextDependent):
|
|
127
127
|
|
128
128
|
return results
|
129
129
|
|
130
|
-
async def validate_task(
|
131
|
-
self, task: Callable[..., R] | Coroutine[Any, Any, R]
|
132
|
-
) -> None:
|
130
|
+
async def validate_task(self, task: Callable[..., R] | Coroutine[Any, Any, R]) -> None:
|
133
131
|
"""Validate a task before execution."""
|
134
132
|
if not (asyncio.iscoroutine(task) or asyncio.iscoroutinefunction(task)):
|
135
133
|
raise TypeError(f"Task must be async: {task}")
|
@@ -164,7 +162,7 @@ class Executor(ABC, ContextDependent):
|
|
164
162
|
|
165
163
|
# Notify any callbacks that the workflow is about to be paused waiting for a signal
|
166
164
|
if self.context.signal_notification:
|
167
|
-
self.context.signal_notification(
|
165
|
+
await self.context.signal_notification(
|
168
166
|
signal_name=signal_name,
|
169
167
|
request_id=request_id,
|
170
168
|
workflow_id=workflow_id,
|
@@ -188,15 +186,13 @@ class AsyncioExecutor(Executor):
|
|
188
186
|
self,
|
189
187
|
config: ExecutorConfig | None = None,
|
190
188
|
signal_bus: SignalHandler | None = None,
|
191
|
-
):
|
189
|
+
) -> None:
|
192
190
|
signal_bus = signal_bus or AsyncioSignalHandler()
|
193
191
|
super().__init__(engine="asyncio", config=config, signal_bus=signal_bus)
|
194
192
|
|
195
193
|
self._activity_semaphore: asyncio.Semaphore | None = None
|
196
194
|
if self.config.max_concurrent_activities is not None:
|
197
|
-
self._activity_semaphore = asyncio.Semaphore(
|
198
|
-
self.config.max_concurrent_activities
|
199
|
-
)
|
195
|
+
self._activity_semaphore = asyncio.Semaphore(self.config.max_concurrent_activities)
|
200
196
|
|
201
197
|
async def _execute_task(
|
202
198
|
self, task: Callable[..., R] | Coroutine[Any, Any, R], **kwargs: Any
|
@@ -253,16 +249,11 @@ class AsyncioExecutor(Executor):
|
|
253
249
|
# TODO: saqadri - validate if async with self.execution_context() is needed here
|
254
250
|
async with self.execution_context():
|
255
251
|
# Create futures for all tasks
|
256
|
-
futures = [
|
257
|
-
asyncio.create_task(self._execute_task(task, **kwargs))
|
258
|
-
for task in tasks
|
259
|
-
]
|
252
|
+
futures = [asyncio.create_task(self._execute_task(task, **kwargs)) for task in tasks]
|
260
253
|
pending = set(futures)
|
261
254
|
|
262
255
|
while pending:
|
263
|
-
done, pending = await asyncio.wait(
|
264
|
-
pending, return_when=asyncio.FIRST_COMPLETED
|
265
|
-
)
|
256
|
+
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
266
257
|
for future in done:
|
267
258
|
yield await future
|
268
259
|
|
@@ -10,13 +10,11 @@ from typing import Any, Callable, Dict, List
|
|
10
10
|
class ActivityRegistry:
|
11
11
|
"""Centralized task/activity management with validation and metadata."""
|
12
12
|
|
13
|
-
def __init__(self):
|
13
|
+
def __init__(self) -> None:
|
14
14
|
self._activities: Dict[str, Callable] = {}
|
15
15
|
self._metadata: Dict[str, Dict[str, Any]] = {}
|
16
16
|
|
17
|
-
def register(
|
18
|
-
self, name: str, func: Callable, metadata: Dict[str, Any] | None = None
|
19
|
-
):
|
17
|
+
def register(self, name: str, func: Callable, metadata: Dict[str, Any] | None = None) -> None:
|
20
18
|
if name in self._activities:
|
21
19
|
raise ValueError(f"Activity '{name}' is already registered.")
|
22
20
|
self._activities[name] = func
|
mcp_agent/executor/temporal.py
CHANGED
@@ -9,6 +9,7 @@ import asyncio
|
|
9
9
|
import functools
|
10
10
|
import uuid
|
11
11
|
from typing import (
|
12
|
+
TYPE_CHECKING,
|
12
13
|
Any,
|
13
14
|
AsyncIterator,
|
14
15
|
Callable,
|
@@ -16,11 +17,10 @@ from typing import (
|
|
16
17
|
Dict,
|
17
18
|
List,
|
18
19
|
Optional,
|
19
|
-
TYPE_CHECKING,
|
20
20
|
)
|
21
21
|
|
22
22
|
from pydantic import ConfigDict
|
23
|
-
from temporalio import activity,
|
23
|
+
from temporalio import activity, exceptions, workflow
|
24
24
|
from temporalio.client import Client as TemporalClient
|
25
25
|
from temporalio.worker import Worker
|
26
26
|
|
@@ -43,9 +43,7 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
43
43
|
|
44
44
|
async def wait_for_signal(self, signal, timeout_seconds=None) -> SignalValueT:
|
45
45
|
if not workflow._Runtime.current():
|
46
|
-
raise RuntimeError(
|
47
|
-
"TemporalSignalHandler.wait_for_signal must be called from within a workflow"
|
48
|
-
)
|
46
|
+
raise RuntimeError("TemporalSignalHandler.wait_for_signal must be called from within a workflow")
|
49
47
|
|
50
48
|
unique_signal_name = f"{signal.name}_{uuid.uuid4()}"
|
51
49
|
registration = SignalRegistration(
|
@@ -59,22 +57,18 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
59
57
|
|
60
58
|
# Define the signal handler for this specific registration
|
61
59
|
@workflow.signal(name=unique_signal_name)
|
62
|
-
def signal_handler(value: SignalValueT):
|
60
|
+
def signal_handler(value: SignalValueT) -> None:
|
63
61
|
container["value"] = value
|
64
62
|
container["completed"] = True
|
65
63
|
|
66
64
|
async with self._lock:
|
67
65
|
# Register both the signal registration and handler atomically
|
68
66
|
self._pending_signals.setdefault(signal.name, []).append(registration)
|
69
|
-
self._handlers.setdefault(signal.name, []).append(
|
70
|
-
(unique_signal_name, signal_handler)
|
71
|
-
)
|
67
|
+
self._handlers.setdefault(signal.name, []).append((unique_signal_name, signal_handler))
|
72
68
|
|
73
69
|
try:
|
74
70
|
# Wait for signal with optional timeout
|
75
|
-
await workflow.wait_condition(
|
76
|
-
lambda: container["completed"], timeout=timeout_seconds
|
77
|
-
)
|
71
|
+
await workflow.wait_condition(lambda: container["completed"], timeout=timeout_seconds)
|
78
72
|
|
79
73
|
return container["value"]
|
80
74
|
except asyncio.TimeoutError as exc:
|
@@ -83,21 +77,13 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
83
77
|
async with self._lock:
|
84
78
|
# Remove ourselves from _pending_signals
|
85
79
|
if signal.name in self._pending_signals:
|
86
|
-
self._pending_signals[signal.name] = [
|
87
|
-
sr
|
88
|
-
for sr in self._pending_signals[signal.name]
|
89
|
-
if sr.unique_name != unique_signal_name
|
90
|
-
]
|
80
|
+
self._pending_signals[signal.name] = [sr for sr in self._pending_signals[signal.name] if sr.unique_name != unique_signal_name]
|
91
81
|
if not self._pending_signals[signal.name]:
|
92
82
|
del self._pending_signals[signal.name]
|
93
83
|
|
94
84
|
# Remove ourselves from _handlers
|
95
85
|
if signal.name in self._handlers:
|
96
|
-
self._handlers[signal.name] = [
|
97
|
-
h
|
98
|
-
for h in self._handlers[signal.name]
|
99
|
-
if h[0] != unique_signal_name
|
100
|
-
]
|
86
|
+
self._handlers[signal.name] = [h for h in self._handlers[signal.name] if h[0] != unique_signal_name]
|
101
87
|
if not self._handlers[signal.name]:
|
102
88
|
del self._handlers[signal.name]
|
103
89
|
|
@@ -110,7 +96,7 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
110
96
|
|
111
97
|
# Create the actual handler that will be registered with Temporal
|
112
98
|
@workflow.signal(name=unique_signal_name)
|
113
|
-
async def wrapped(signal_value: SignalValueT):
|
99
|
+
async def wrapped(signal_value: SignalValueT) -> None:
|
114
100
|
# Create a signal object to pass to the handler
|
115
101
|
signal = Signal(
|
116
102
|
name=signal_name,
|
@@ -123,19 +109,15 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
123
109
|
func(signal)
|
124
110
|
|
125
111
|
# Register the handler under the original signal name
|
126
|
-
self._handlers.setdefault(signal_name, []).append(
|
127
|
-
(unique_signal_name, wrapped)
|
128
|
-
)
|
112
|
+
self._handlers.setdefault(signal_name, []).append((unique_signal_name, wrapped))
|
129
113
|
return func
|
130
114
|
|
131
115
|
return decorator
|
132
116
|
|
133
|
-
async def signal(self, signal):
|
117
|
+
async def signal(self, signal) -> None:
|
134
118
|
self.validate_signal(signal)
|
135
119
|
|
136
|
-
workflow_handle = workflow.get_external_workflow_handle(
|
137
|
-
workflow_id=signal.workflow_id
|
138
|
-
)
|
120
|
+
workflow_handle = workflow.get_external_workflow_handle(workflow_id=signal.workflow_id)
|
139
121
|
|
140
122
|
# Send the signal to all registrations of this signal
|
141
123
|
async with self._lock:
|
@@ -146,30 +128,22 @@ class TemporalSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
146
128
|
registration = pending_signal.registration
|
147
129
|
if registration.workflow_id == signal.workflow_id:
|
148
130
|
# Only signal for registrations of that workflow
|
149
|
-
signal_tasks.append(
|
150
|
-
workflow_handle.signal(
|
151
|
-
registration.unique_name, signal.payload
|
152
|
-
)
|
153
|
-
)
|
131
|
+
signal_tasks.append(workflow_handle.signal(registration.unique_name, signal.payload))
|
154
132
|
else:
|
155
133
|
continue
|
156
134
|
|
157
135
|
# Notify any registered handler functions
|
158
136
|
if signal.name in self._handlers:
|
159
137
|
for unique_name, _ in self._handlers[signal.name]:
|
160
|
-
signal_tasks.append(
|
161
|
-
workflow_handle.signal(unique_name, signal.payload)
|
162
|
-
)
|
138
|
+
signal_tasks.append(workflow_handle.signal(unique_name, signal.payload))
|
163
139
|
|
164
140
|
await asyncio.gather(*signal_tasks, return_exceptions=True)
|
165
141
|
|
166
|
-
def validate_signal(self, signal):
|
142
|
+
def validate_signal(self, signal) -> None:
|
167
143
|
super().validate_signal(signal)
|
168
144
|
# Add TemporalSignalHandler-specific validation
|
169
145
|
if signal.workflow_id is None:
|
170
|
-
raise ValueError(
|
171
|
-
"No workflow_id provided on Signal. That is required for Temporal signals"
|
172
|
-
)
|
146
|
+
raise ValueError("No workflow_id provided on Signal. That is required for Temporal signals")
|
173
147
|
|
174
148
|
|
175
149
|
class TemporalExecutorConfig(ExecutorConfig, TemporalSettings):
|
@@ -188,7 +162,7 @@ class TemporalExecutor(Executor):
|
|
188
162
|
client: TemporalClient | None = None,
|
189
163
|
context: Optional["Context"] = None,
|
190
164
|
**kwargs,
|
191
|
-
):
|
165
|
+
) -> None:
|
192
166
|
signal_bus = signal_bus or TemporalSignalHandler()
|
193
167
|
super().__init__(
|
194
168
|
engine="temporal",
|
@@ -197,17 +171,13 @@ class TemporalExecutor(Executor):
|
|
197
171
|
context=context,
|
198
172
|
**kwargs,
|
199
173
|
)
|
200
|
-
self.config: TemporalExecutorConfig = (
|
201
|
-
config or self.context.config.temporal or TemporalExecutorConfig()
|
202
|
-
)
|
174
|
+
self.config: TemporalExecutorConfig = config or self.context.config.temporal or TemporalExecutorConfig()
|
203
175
|
self.client = client
|
204
176
|
self._worker = None
|
205
177
|
self._activity_semaphore = None
|
206
178
|
|
207
179
|
if config.max_concurrent_activities is not None:
|
208
|
-
self._activity_semaphore = asyncio.Semaphore(
|
209
|
-
self.config.max_concurrent_activities
|
210
|
-
)
|
180
|
+
self._activity_semaphore = asyncio.Semaphore(self.config.max_concurrent_activities)
|
211
181
|
|
212
182
|
@staticmethod
|
213
183
|
def wrap_as_activity(
|
@@ -234,9 +204,7 @@ class TemporalExecutor(Executor):
|
|
234
204
|
|
235
205
|
return wrapped_activity
|
236
206
|
|
237
|
-
async def _execute_task_as_async(
|
238
|
-
self, task: Callable[..., R] | Coroutine[Any, Any, R], **kwargs: Any
|
239
|
-
) -> R | BaseException:
|
207
|
+
async def _execute_task_as_async(self, task: Callable[..., R] | Coroutine[Any, Any, R], **kwargs: Any) -> R | BaseException:
|
240
208
|
async def run_task(task: Callable[..., R] | Coroutine[Any, Any, R]) -> R:
|
241
209
|
try:
|
242
210
|
if asyncio.iscoroutine(task):
|
@@ -269,15 +237,11 @@ class TemporalExecutor(Executor):
|
|
269
237
|
else:
|
270
238
|
return await run_task(task)
|
271
239
|
|
272
|
-
async def _execute_task(
|
273
|
-
self, task: Callable[..., R] | Coroutine[Any, Any, R], **kwargs: Any
|
274
|
-
) -> R | BaseException:
|
240
|
+
async def _execute_task(self, task: Callable[..., R] | Coroutine[Any, Any, R], **kwargs: Any) -> R | BaseException:
|
275
241
|
func = task.func if isinstance(task, functools.partial) else task
|
276
242
|
is_workflow_task = getattr(func, "is_workflow_task", False)
|
277
243
|
if not is_workflow_task:
|
278
|
-
return await asyncio.create_task(
|
279
|
-
self._execute_task_as_async(task, **kwargs)
|
280
|
-
)
|
244
|
+
return await asyncio.create_task(self._execute_task_as_async(task, **kwargs))
|
281
245
|
|
282
246
|
execution_metadata: Dict[str, Any] = getattr(func, "execution_metadata", {})
|
283
247
|
|
@@ -286,9 +250,7 @@ class TemporalExecutor(Executor):
|
|
286
250
|
if not activity_name:
|
287
251
|
activity_name = f"{func.__module__}.{func.__qualname__}"
|
288
252
|
|
289
|
-
schedule_to_close = execution_metadata.get(
|
290
|
-
"schedule_to_close_timeout", self.config.timeout_seconds
|
291
|
-
)
|
253
|
+
schedule_to_close = execution_metadata.get("schedule_to_close_timeout", self.config.timeout_seconds)
|
292
254
|
|
293
255
|
retry_policy = execution_metadata.get("retry_policy", None)
|
294
256
|
|
@@ -319,9 +281,7 @@ class TemporalExecutor(Executor):
|
|
319
281
|
) -> List[R | BaseException]:
|
320
282
|
# Must be called from within a workflow
|
321
283
|
if not workflow._Runtime.current():
|
322
|
-
raise RuntimeError(
|
323
|
-
"TemporalExecutor.execute must be called from within a workflow"
|
324
|
-
)
|
284
|
+
raise RuntimeError("TemporalExecutor.execute must be called from within a workflow")
|
325
285
|
|
326
286
|
# TODO: saqadri - validate if async with self.execution_context() is needed here
|
327
287
|
async with self.execution_context():
|
@@ -336,9 +296,7 @@ class TemporalExecutor(Executor):
|
|
336
296
|
**kwargs: Any,
|
337
297
|
) -> AsyncIterator[R | BaseException]:
|
338
298
|
if not workflow._Runtime.current():
|
339
|
-
raise RuntimeError(
|
340
|
-
"TemporalExecutor.execute_streaming must be called from within a workflow"
|
341
|
-
)
|
299
|
+
raise RuntimeError("TemporalExecutor.execute_streaming must be called from within a workflow")
|
342
300
|
|
343
301
|
# TODO: saqadri - validate if async with self.execution_context() is needed here
|
344
302
|
async with self.execution_context():
|
@@ -347,9 +305,7 @@ class TemporalExecutor(Executor):
|
|
347
305
|
pending = set(futures)
|
348
306
|
|
349
307
|
while pending:
|
350
|
-
done, pending = await workflow.wait(
|
351
|
-
pending, return_when=asyncio.FIRST_COMPLETED
|
352
|
-
)
|
308
|
+
done, pending = await workflow.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
353
309
|
for future in done:
|
354
310
|
try:
|
355
311
|
result = await future
|
@@ -368,7 +324,7 @@ class TemporalExecutor(Executor):
|
|
368
324
|
|
369
325
|
return self.client
|
370
326
|
|
371
|
-
async def start_worker(self):
|
327
|
+
async def start_worker(self) -> None:
|
372
328
|
"""
|
373
329
|
Start a worker in this process, auto-registering all tasks
|
374
330
|
from the global registry. Also picks up any classes decorated
|
@@ -398,8 +354,6 @@ class TemporalExecutor(Executor):
|
|
398
354
|
activities=activities,
|
399
355
|
workflows=[], # We'll auto-load by Python scanning or let the user specify
|
400
356
|
)
|
401
|
-
print(
|
402
|
-
f"Starting Temporal Worker on task queue '{self.config.task_queue}' with {len(activities)} activities."
|
403
|
-
)
|
357
|
+
print(f"Starting Temporal Worker on task queue '{self.config.task_queue}' with {len(activities)} activities.")
|
404
358
|
|
405
359
|
await self._worker.run()
|
mcp_agent/executor/workflow.py
CHANGED
@@ -62,7 +62,7 @@ class Workflow(ABC, Generic[T]):
|
|
62
62
|
name: str | None = None,
|
63
63
|
metadata: Dict[str, Any] | None = None,
|
64
64
|
**kwargs: Any,
|
65
|
-
):
|
65
|
+
) -> None:
|
66
66
|
self.executor = executor
|
67
67
|
self.name = name or self.__class__.__name__
|
68
68
|
self.init_kwargs = kwargs
|
@@ -80,7 +80,7 @@ class Workflow(ABC, Generic[T]):
|
|
80
80
|
Main workflow implementation. Must be overridden by subclasses.
|
81
81
|
"""
|
82
82
|
|
83
|
-
async def update_state(self, **kwargs):
|
83
|
+
async def update_state(self, **kwargs) -> None:
|
84
84
|
"""Syntactic sugar to update workflow state."""
|
85
85
|
for key, value in kwargs.items():
|
86
86
|
self.state[key] = value
|
@@ -93,9 +93,7 @@ class Workflow(ABC, Generic[T]):
|
|
93
93
|
Convenience method for human input. Uses `human_input` signal
|
94
94
|
so we can unify local (console input) and Temporal signals.
|
95
95
|
"""
|
96
|
-
return await self.executor.wait_for_signal(
|
97
|
-
"human_input", description=description
|
98
|
-
)
|
96
|
+
return await self.executor.wait_for_signal("human_input", description=description)
|
99
97
|
|
100
98
|
|
101
99
|
# ############################
|