fast-agent-mcp 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
- fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
- mcp_agent/agents/agent.py +37 -102
- mcp_agent/app.py +16 -27
- mcp_agent/cli/commands/bootstrap.py +22 -52
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +11 -26
- mcp_agent/cli/main.py +6 -9
- mcp_agent/cli/terminal.py +2 -2
- mcp_agent/config.py +1 -5
- mcp_agent/context.py +13 -26
- mcp_agent/context_dependent.py +3 -7
- mcp_agent/core/agent_app.py +46 -122
- mcp_agent/core/agent_types.py +29 -2
- mcp_agent/core/agent_utils.py +3 -5
- mcp_agent/core/decorators.py +6 -14
- mcp_agent/core/enhanced_prompt.py +25 -52
- mcp_agent/core/error_handling.py +1 -1
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/factory.py +30 -72
- mcp_agent/core/fastagent.py +48 -88
- mcp_agent/core/mcp_content.py +10 -19
- mcp_agent/core/prompt.py +8 -15
- mcp_agent/core/proxies.py +34 -25
- mcp_agent/core/request_params.py +46 -0
- mcp_agent/core/types.py +6 -6
- mcp_agent/core/validation.py +16 -16
- mcp_agent/executor/decorator_registry.py +11 -23
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +28 -74
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +17 -29
- mcp_agent/human_input/handler.py +4 -9
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +15 -17
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +24 -24
- mcp_agent/mcp/gen_client.py +4 -12
- mcp_agent/mcp/interfaces.py +107 -88
- mcp_agent/mcp/mcp_agent_client_session.py +11 -19
- mcp_agent/mcp/mcp_agent_server.py +8 -10
- mcp_agent/mcp/mcp_aggregator.py +49 -122
- mcp_agent/mcp/mcp_connection_manager.py +16 -37
- mcp_agent/mcp/prompt_message_multipart.py +12 -18
- mcp_agent/mcp/prompt_serialization.py +13 -38
- mcp_agent/mcp/prompts/prompt_load.py +99 -0
- mcp_agent/mcp/prompts/prompt_server.py +21 -128
- mcp_agent/mcp/prompts/prompt_template.py +20 -42
- mcp_agent/mcp/resource_utils.py +8 -17
- mcp_agent/mcp/sampling.py +62 -64
- mcp_agent/mcp/stdio.py +11 -8
- mcp_agent/mcp_server/__init__.py +1 -1
- mcp_agent/mcp_server/agent_server.py +10 -17
- mcp_agent/mcp_server_registry.py +13 -35
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
- mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
- mcp_agent/resources/examples/data-analysis/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +2 -1
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/prompting/__init__.py +1 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +5 -11
- mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
- mcp_agent/resources/examples/researcher/researcher.py +2 -1
- mcp_agent/resources/examples/workflows/agent_build.py +2 -1
- mcp_agent/resources/examples/workflows/chaining.py +2 -1
- mcp_agent/resources/examples/workflows/evaluator.py +2 -1
- mcp_agent/resources/examples/workflows/human_input.py +2 -1
- mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
- mcp_agent/resources/examples/workflows/parallel.py +2 -1
- mcp_agent/resources/examples/workflows/router.py +2 -1
- mcp_agent/resources/examples/workflows/sse.py +1 -1
- mcp_agent/telemetry/usage_tracking.py +2 -1
- mcp_agent/ui/console_display.py +17 -41
- mcp_agent/workflows/embedding/embedding_base.py +1 -4
- mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
- mcp_agent/workflows/embedding/embedding_openai.py +4 -13
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
- mcp_agent/workflows/llm/anthropic_utils.py +8 -29
- mcp_agent/workflows/llm/augmented_llm.py +94 -332
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +43 -76
- mcp_agent/workflows/llm/augmented_llm_openai.py +46 -100
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +42 -20
- mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
- mcp_agent/workflows/llm/memory.py +103 -0
- mcp_agent/workflows/llm/model_factory.py +9 -21
- mcp_agent/workflows/llm/openai_utils.py +1 -1
- mcp_agent/workflows/llm/prompt_utils.py +39 -27
- mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +246 -184
- mcp_agent/workflows/llm/providers/multipart_converter_openai.py +212 -202
- mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
- mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +11 -212
- mcp_agent/workflows/llm/providers/sampling_converter_openai.py +13 -215
- mcp_agent/workflows/llm/sampling_converter.py +117 -0
- mcp_agent/workflows/llm/sampling_format_converter.py +12 -29
- mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
- mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
- mcp_agent/workflows/parallel/fan_in.py +17 -47
- mcp_agent/workflows/parallel/fan_out.py +6 -12
- mcp_agent/workflows/parallel/parallel_llm.py +9 -26
- mcp_agent/workflows/router/router_base.py +29 -59
- mcp_agent/workflows/router/router_embedding.py +11 -25
- mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
- mcp_agent/workflows/router/router_embedding_openai.py +2 -2
- mcp_agent/workflows/router/router_llm.py +12 -28
- mcp_agent/workflows/swarm/swarm.py +20 -48
- mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
- mcp_agent/workflows/swarm/swarm_openai.py +2 -2
- fast_agent_mcp-0.1.11.dist-info/RECORD +0 -160
- mcp_agent/workflows/llm/llm_selector.py +0 -345
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,345 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from difflib import SequenceMatcher
|
3
|
-
from importlib import resources
|
4
|
-
from typing import Dict, List
|
5
|
-
|
6
|
-
from numpy import average
|
7
|
-
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
|
8
|
-
|
9
|
-
from mcp.types import ModelHint, ModelPreferences
|
10
|
-
|
11
|
-
|
12
|
-
class ModelBenchmarks(BaseModel):
|
13
|
-
"""
|
14
|
-
Performance benchmarks for comparing different models.
|
15
|
-
"""
|
16
|
-
|
17
|
-
__pydantic_extra__: dict[str, float] = Field(
|
18
|
-
init=False
|
19
|
-
) # Enforces that extra fields are floats
|
20
|
-
|
21
|
-
quality_score: float | None = None
|
22
|
-
"""A blended quality score for the model."""
|
23
|
-
|
24
|
-
mmlu_score: float | None = None
|
25
|
-
gsm8k_score: float | None = None
|
26
|
-
bbh_score: float | None = None
|
27
|
-
|
28
|
-
model_config = ConfigDict(extra="allow")
|
29
|
-
|
30
|
-
|
31
|
-
class ModelLatency(BaseModel):
|
32
|
-
"""
|
33
|
-
Latency benchmarks for comparing different models.
|
34
|
-
"""
|
35
|
-
|
36
|
-
time_to_first_token_ms: float = Field(gt=0)
|
37
|
-
"""
|
38
|
-
Median Time to first token in milliseconds.
|
39
|
-
"""
|
40
|
-
|
41
|
-
tokens_per_second: float = Field(gt=0)
|
42
|
-
"""
|
43
|
-
Median output tokens per second.
|
44
|
-
"""
|
45
|
-
|
46
|
-
|
47
|
-
class ModelCost(BaseModel):
|
48
|
-
"""
|
49
|
-
Cost benchmarks for comparing different models.
|
50
|
-
"""
|
51
|
-
|
52
|
-
blended_cost_per_1m: float | None = None
|
53
|
-
"""
|
54
|
-
Blended cost mixing input/output cost per 1M tokens.
|
55
|
-
"""
|
56
|
-
|
57
|
-
input_cost_per_1m: float | None = None
|
58
|
-
"""
|
59
|
-
Cost per 1M input tokens.
|
60
|
-
"""
|
61
|
-
|
62
|
-
output_cost_per_1m: float | None = None
|
63
|
-
"""
|
64
|
-
Cost per 1M output tokens.
|
65
|
-
"""
|
66
|
-
|
67
|
-
|
68
|
-
class ModelMetrics(BaseModel):
|
69
|
-
"""
|
70
|
-
Model metrics for comparing different models.
|
71
|
-
"""
|
72
|
-
|
73
|
-
cost: ModelCost
|
74
|
-
speed: ModelLatency
|
75
|
-
intelligence: ModelBenchmarks
|
76
|
-
|
77
|
-
|
78
|
-
class ModelInfo(BaseModel):
|
79
|
-
"""
|
80
|
-
LLM metadata, including performance benchmarks.
|
81
|
-
"""
|
82
|
-
|
83
|
-
name: str
|
84
|
-
description: str | None = None
|
85
|
-
provider: str
|
86
|
-
metrics: ModelMetrics
|
87
|
-
|
88
|
-
|
89
|
-
class ModelSelector:
|
90
|
-
"""
|
91
|
-
A heuristic-based selector to choose the best model from a list of models.
|
92
|
-
|
93
|
-
Because LLMs can vary along multiple dimensions, choosing the "best" model is
|
94
|
-
rarely straightforward. Different models excel in different areas—some are
|
95
|
-
faster but less capable, others are more capable but more expensive, and so
|
96
|
-
on.
|
97
|
-
|
98
|
-
MCP's ModelPreferences interface allows servers to express their priorities across multiple
|
99
|
-
dimensions to help clients make an appropriate selection for their use case.
|
100
|
-
"""
|
101
|
-
|
102
|
-
def __init__(
|
103
|
-
self,
|
104
|
-
models: List[ModelInfo] = None,
|
105
|
-
benchmark_weights: Dict[str, float] | None = None,
|
106
|
-
):
|
107
|
-
if not models:
|
108
|
-
self.models = load_default_models()
|
109
|
-
else:
|
110
|
-
self.models = models
|
111
|
-
|
112
|
-
if benchmark_weights:
|
113
|
-
self.benchmark_weights = benchmark_weights
|
114
|
-
else:
|
115
|
-
# Defaults for how much to value each benchmark metric (must add to 1)
|
116
|
-
self.benchmark_weights = {"mmlu": 0.4, "gsm8k": 0.3, "bbh": 0.3}
|
117
|
-
|
118
|
-
if abs(sum(self.benchmark_weights.values()) - 1.0) > 1e-6:
|
119
|
-
raise ValueError("Benchmark weights must sum to 1.0")
|
120
|
-
|
121
|
-
self.max_values = self._calculate_max_scores(self.models)
|
122
|
-
self.models_by_provider = self._models_by_provider(self.models)
|
123
|
-
|
124
|
-
def select_best_model(
|
125
|
-
self, model_preferences: ModelPreferences, provider: str | None = None
|
126
|
-
) -> ModelInfo:
|
127
|
-
"""
|
128
|
-
Select the best model from a given list of models based on the given model preferences.
|
129
|
-
"""
|
130
|
-
|
131
|
-
models: List[ModelInfo] = []
|
132
|
-
if provider:
|
133
|
-
models = self.models_by_provider[provider]
|
134
|
-
else:
|
135
|
-
models = self.models
|
136
|
-
|
137
|
-
if not models:
|
138
|
-
raise ValueError(f"No models available for selection. Provider={provider}")
|
139
|
-
|
140
|
-
candidate_models = models
|
141
|
-
# First check the model hints
|
142
|
-
if model_preferences.hints:
|
143
|
-
candidate_models = []
|
144
|
-
for model in models:
|
145
|
-
for hint in model_preferences.hints:
|
146
|
-
if self._check_model_hint(model, hint):
|
147
|
-
candidate_models.append(model)
|
148
|
-
|
149
|
-
if not candidate_models:
|
150
|
-
# If no hints match, we'll use all models and let the benchmark weights decide
|
151
|
-
candidate_models = models
|
152
|
-
|
153
|
-
scores = []
|
154
|
-
|
155
|
-
# Next, we'll use the benchmark weights to decide the best model
|
156
|
-
for model in candidate_models:
|
157
|
-
cost_score = self._calculate_cost_score(
|
158
|
-
model, model_preferences, max_cost=self.max_values["max_cost"]
|
159
|
-
)
|
160
|
-
speed_score = self._calculate_speed_score(
|
161
|
-
model,
|
162
|
-
max_tokens_per_second=self.max_values["max_tokens_per_second"],
|
163
|
-
max_time_to_first_token_ms=self.max_values[
|
164
|
-
"max_time_to_first_token_ms"
|
165
|
-
],
|
166
|
-
)
|
167
|
-
intelligence_score = self._calculate_intelligence_score(
|
168
|
-
model, self.max_values
|
169
|
-
)
|
170
|
-
|
171
|
-
model_score = (
|
172
|
-
(model_preferences.costPriority or 0) * cost_score
|
173
|
-
+ (model_preferences.speedPriority or 0) * speed_score
|
174
|
-
+ (model_preferences.intelligencePriority or 0) * intelligence_score
|
175
|
-
)
|
176
|
-
scores.append((model_score, model))
|
177
|
-
|
178
|
-
return max(scores, key=lambda x: x[0])[1]
|
179
|
-
|
180
|
-
def _models_by_provider(
|
181
|
-
self, models: List[ModelInfo]
|
182
|
-
) -> Dict[str, List[ModelInfo]]:
|
183
|
-
"""
|
184
|
-
Group models by provider.
|
185
|
-
"""
|
186
|
-
provider_models: Dict[str, List[ModelInfo]] = {}
|
187
|
-
for model in models:
|
188
|
-
if model.provider not in provider_models:
|
189
|
-
provider_models[model.provider] = []
|
190
|
-
provider_models[model.provider].append(model)
|
191
|
-
return provider_models
|
192
|
-
|
193
|
-
def _check_model_hint(self, model: ModelInfo, hint: ModelHint) -> bool:
|
194
|
-
"""
|
195
|
-
Check if a model matches a specific hint.
|
196
|
-
"""
|
197
|
-
|
198
|
-
name_match = True
|
199
|
-
if hint.name:
|
200
|
-
name_match = _fuzzy_match(hint.name, model.name)
|
201
|
-
|
202
|
-
provider_match = True
|
203
|
-
provider: str | None = getattr(hint, "provider", None)
|
204
|
-
if provider:
|
205
|
-
provider_match = _fuzzy_match(provider, model.provider)
|
206
|
-
|
207
|
-
# This can be extended to check for more hints
|
208
|
-
return name_match and provider_match
|
209
|
-
|
210
|
-
def _calculate_total_cost(self, model: ModelInfo, io_ratio: float = 3.0) -> float:
|
211
|
-
"""
|
212
|
-
Calculate a single cost metric of a model based on input/output token costs,
|
213
|
-
and a ratio of input to output tokens.
|
214
|
-
|
215
|
-
Args:
|
216
|
-
model: The model to calculate the cost for.
|
217
|
-
io_ratio: The estimated ratio of input to output tokens. Defaults to 3.0.
|
218
|
-
"""
|
219
|
-
|
220
|
-
if model.metrics.cost.blended_cost_per_1m is not None:
|
221
|
-
return model.metrics.cost.blended_cost_per_1m
|
222
|
-
|
223
|
-
input_cost = model.metrics.cost.input_cost_per_1m
|
224
|
-
output_cost = model.metrics.cost.output_cost_per_1m
|
225
|
-
|
226
|
-
total_cost = (input_cost * io_ratio + output_cost) / (1 + io_ratio)
|
227
|
-
return total_cost
|
228
|
-
|
229
|
-
def _calculate_cost_score(
|
230
|
-
self,
|
231
|
-
model: ModelInfo,
|
232
|
-
model_preferences: ModelPreferences,
|
233
|
-
max_cost: float,
|
234
|
-
) -> float:
|
235
|
-
"""Normalized 0->1 cost score for a model."""
|
236
|
-
total_cost = self._calculate_total_cost(model, model_preferences)
|
237
|
-
return 1 - (total_cost / max_cost)
|
238
|
-
|
239
|
-
def _calculate_intelligence_score(
|
240
|
-
self, model: ModelInfo, max_values: Dict[str, float]
|
241
|
-
) -> float:
|
242
|
-
"""
|
243
|
-
Return a normalized 0->1 intelligence score for a model based on its benchmark metrics.
|
244
|
-
"""
|
245
|
-
scores = []
|
246
|
-
weights = []
|
247
|
-
|
248
|
-
benchmark_dict: Dict[str, float] = model.metrics.intelligence.model_dump()
|
249
|
-
use_weights = True
|
250
|
-
for bench, score in benchmark_dict.items():
|
251
|
-
key = f"max_{bench}"
|
252
|
-
if score is not None and key in max_values:
|
253
|
-
scores.append(score / max_values[key])
|
254
|
-
if bench in self.benchmark_weights:
|
255
|
-
weights.append(self.benchmark_weights[bench])
|
256
|
-
else:
|
257
|
-
# If a benchmark doesn't have a weight, don't use weights at all, we'll just average the scores
|
258
|
-
use_weights = False
|
259
|
-
|
260
|
-
if not scores:
|
261
|
-
return 0
|
262
|
-
elif use_weights:
|
263
|
-
return average(scores, weights=weights)
|
264
|
-
else:
|
265
|
-
return average(scores)
|
266
|
-
|
267
|
-
def _calculate_speed_score(
|
268
|
-
self,
|
269
|
-
model: ModelInfo,
|
270
|
-
max_tokens_per_second: float,
|
271
|
-
max_time_to_first_token_ms: float,
|
272
|
-
) -> float:
|
273
|
-
"""Normalized 0->1 cost score for a model."""
|
274
|
-
|
275
|
-
time_to_first_token_score = 1 - (
|
276
|
-
model.metrics.speed.time_to_first_token_ms / max_time_to_first_token_ms
|
277
|
-
)
|
278
|
-
|
279
|
-
tokens_per_second_score = (
|
280
|
-
model.metrics.speed.tokens_per_second / max_tokens_per_second
|
281
|
-
)
|
282
|
-
|
283
|
-
latency_score = average(
|
284
|
-
[time_to_first_token_score, tokens_per_second_score], weights=[0.4, 0.6]
|
285
|
-
)
|
286
|
-
return latency_score
|
287
|
-
|
288
|
-
def _calculate_max_scores(self, models: List[ModelInfo]) -> Dict[str, float]:
|
289
|
-
"""
|
290
|
-
Of all the models, calculate the maximum value for each benchmark metric.
|
291
|
-
"""
|
292
|
-
max_dict: Dict[str, float] = {}
|
293
|
-
|
294
|
-
max_dict["max_cost"] = max(self._calculate_total_cost(m) for m in models)
|
295
|
-
max_dict["max_tokens_per_second"] = max(
|
296
|
-
max(m.metrics.speed.tokens_per_second for m in models), 1e-6
|
297
|
-
)
|
298
|
-
max_dict["max_time_to_first_token_ms"] = max(
|
299
|
-
max(m.metrics.speed.time_to_first_token_ms for m in models), 1e-6
|
300
|
-
)
|
301
|
-
|
302
|
-
# Find the maximum value for each model performance benchmark
|
303
|
-
for model in models:
|
304
|
-
benchmark_dict: Dict[str, float] = model.metrics.intelligence.model_dump()
|
305
|
-
for bench, score in benchmark_dict.items():
|
306
|
-
if score is None:
|
307
|
-
continue
|
308
|
-
|
309
|
-
key = f"max_{bench}"
|
310
|
-
if key in max_dict:
|
311
|
-
max_dict[key] = max(max_dict[key], score)
|
312
|
-
else:
|
313
|
-
max_dict[key] = score
|
314
|
-
|
315
|
-
return max_dict
|
316
|
-
|
317
|
-
|
318
|
-
def load_default_models() -> List[ModelInfo]:
|
319
|
-
"""
|
320
|
-
We use ArtificialAnalysis benchmarks for determining the best model.
|
321
|
-
"""
|
322
|
-
with (
|
323
|
-
resources.files("mcp_agent.data")
|
324
|
-
.joinpath("artificial_analysis_llm_benchmarks.json")
|
325
|
-
.open() as file
|
326
|
-
):
|
327
|
-
data = json.load(file) # Array of ModelInfo objects
|
328
|
-
adapter = TypeAdapter(List[ModelInfo])
|
329
|
-
return adapter.validate_python(data)
|
330
|
-
|
331
|
-
|
332
|
-
def _fuzzy_match(str1: str, str2: str, threshold: float = 0.8) -> bool:
|
333
|
-
"""
|
334
|
-
Fuzzy match two strings
|
335
|
-
|
336
|
-
Args:
|
337
|
-
str1: First string to compare
|
338
|
-
str2: Second string to compare
|
339
|
-
threshold: Minimum similarity ratio to consider a match (0.0 to 1.0)
|
340
|
-
|
341
|
-
Returns:
|
342
|
-
bool: True if strings match above threshold, False otherwise
|
343
|
-
"""
|
344
|
-
sequence_ratio = SequenceMatcher(None, str1.lower(), str2.lower()).ratio()
|
345
|
-
return sequence_ratio >= threshold
|
File without changes
|
File without changes
|
File without changes
|