fast-agent-mcp 0.2.27__py3-none-any.whl → 0.2.29__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.
@@ -8,12 +8,14 @@ from mcp_agent.core.exceptions import ModelConfigError
8
8
  from mcp_agent.core.request_params import RequestParams
9
9
  from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
10
10
  from mcp_agent.llm.augmented_llm_playback import PlaybackLLM
11
+ from mcp_agent.llm.augmented_llm_slow import SlowLLM
11
12
  from mcp_agent.llm.provider_types import Provider
12
13
  from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM
13
14
  from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM
14
15
  from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
15
16
  from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM
16
- from mcp_agent.llm.providers.augmented_llm_google import GoogleAugmentedLLM
17
+ from mcp_agent.llm.providers.augmented_llm_google_native import GoogleNativeAugmentedLLM
18
+ from mcp_agent.llm.providers.augmented_llm_google_oai import GoogleOaiAugmentedLLM
17
19
  from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
18
20
  from mcp_agent.llm.providers.augmented_llm_openrouter import OpenRouterAugmentedLLM
19
21
  from mcp_agent.llm.providers.augmented_llm_tensorzero import TensorZeroAugmentedLLM
@@ -28,9 +30,13 @@ LLMClass = Union[
28
30
  Type[OpenAIAugmentedLLM],
29
31
  Type[PassthroughLLM],
30
32
  Type[PlaybackLLM],
33
+ Type[SlowLLM],
31
34
  Type[DeepSeekAugmentedLLM],
32
35
  Type[OpenRouterAugmentedLLM],
33
36
  Type[TensorZeroAugmentedLLM],
37
+ Type[GoogleNativeAugmentedLLM],
38
+ Type[GenericAugmentedLLM],
39
+ Type[AzureOpenAIAugmentedLLM],
34
40
  ]
35
41
 
36
42
 
@@ -60,13 +66,16 @@ class ModelFactory:
60
66
  "high": ReasoningEffort.HIGH,
61
67
  }
62
68
 
63
- # TODO -- add context window size information for display/management
64
- # TODO -- add audio supporting got-4o-audio-preview
65
- # TODO -- bring model parameter configuration here
66
- # Mapping of model names to their default providers
69
+ """
70
+ TODO -- add context window size information for display/management
71
+ TODO -- add audio supporting got-4o-audio-preview
72
+ TODO -- bring model parameter configuration here
73
+ Mapping of model names to their default providers
74
+ """
67
75
  DEFAULT_PROVIDERS = {
68
76
  "passthrough": Provider.FAST_AGENT,
69
77
  "playback": Provider.FAST_AGENT,
78
+ "slow": Provider.FAST_AGENT,
70
79
  "gpt-4o": Provider.OPENAI,
71
80
  "gpt-4o-mini": Provider.OPENAI,
72
81
  "gpt-4.1": Provider.OPENAI,
@@ -91,7 +100,9 @@ class ModelFactory:
91
100
  "claude-sonnet-4-20250514": Provider.ANTHROPIC,
92
101
  "claude-sonnet-4-0": Provider.ANTHROPIC,
93
102
  "deepseek-chat": Provider.DEEPSEEK,
94
- # "deepseek-reasoner": Provider.DEEPSEEK, reinstate on release
103
+ "gemini-2.0-flash": Provider.GOOGLE,
104
+ "gemini-2.5-flash-preview-05-20": Provider.GOOGLE,
105
+ "gemini-2.5-pro-preview-05-06": Provider.GOOGLE,
95
106
  }
96
107
 
97
108
  MODEL_ALIASES = {
@@ -108,6 +119,9 @@ class ModelFactory:
108
119
  "opus3": "claude-3-opus-latest",
109
120
  "deepseekv3": "deepseek-chat",
110
121
  "deepseek": "deepseek-chat",
122
+ "gemini2": "gemini-2.0-flash",
123
+ "gemini25": "gemini-2.5-flash-preview-05-20",
124
+ "gemini25pro": "gemini-2.5-pro-preview-05-06",
111
125
  }
112
126
 
113
127
  # Mapping of providers to their LLM classes
@@ -117,7 +131,8 @@ class ModelFactory:
117
131
  Provider.FAST_AGENT: PassthroughLLM,
118
132
  Provider.DEEPSEEK: DeepSeekAugmentedLLM,
119
133
  Provider.GENERIC: GenericAugmentedLLM,
120
- Provider.GOOGLE: GoogleAugmentedLLM, # type: ignore
134
+ Provider.GOOGLE_OAI: GoogleOaiAugmentedLLM,
135
+ Provider.GOOGLE: GoogleNativeAugmentedLLM,
121
136
  Provider.OPENROUTER: OpenRouterAugmentedLLM,
122
137
  Provider.TENSORZERO: TensorZeroAugmentedLLM,
123
138
  Provider.AZURE: AzureOpenAIAugmentedLLM,
@@ -127,48 +142,66 @@ class ModelFactory:
127
142
  # This overrides the provider-based class selection
128
143
  MODEL_SPECIFIC_CLASSES: Dict[str, LLMClass] = {
129
144
  "playback": PlaybackLLM,
145
+ "slow": SlowLLM,
130
146
  }
131
147
 
132
148
  @classmethod
133
149
  def parse_model_string(cls, model_string: str) -> ModelConfig:
134
150
  """Parse a model string into a ModelConfig object"""
135
- # Check if model string is an alias
136
151
  model_string = cls.MODEL_ALIASES.get(model_string, model_string)
137
152
  parts = model_string.split(".")
138
153
 
139
- # Start with all parts as the model name
140
- model_parts = parts.copy()
154
+ model_name_str = model_string # Default full string as model name initially
141
155
  provider = None
142
156
  reasoning_effort = None
157
+ parts_for_provider_model = []
143
158
 
144
- # Check last part for reasoning effort
159
+ # Check for reasoning effort first (last part)
145
160
  if len(parts) > 1 and parts[-1].lower() in cls.EFFORT_MAP:
146
161
  reasoning_effort = cls.EFFORT_MAP[parts[-1].lower()]
147
- model_parts = model_parts[:-1]
162
+ # Remove effort from parts list for provider/model name determination
163
+ parts_for_provider_model = parts[:-1]
164
+ else:
165
+ parts_for_provider_model = parts[:]
166
+
167
+ # Try to match longest possible provider string
168
+ identified_provider_parts = 0 # How many parts belong to the provider string
169
+
170
+ if len(parts_for_provider_model) >= 2:
171
+ potential_provider_str = f"{parts_for_provider_model[0]}.{parts_for_provider_model[1]}"
172
+ if any(p.value == potential_provider_str for p in Provider):
173
+ provider = Provider(potential_provider_str)
174
+ identified_provider_parts = 2
175
+
176
+ if provider is None and len(parts_for_provider_model) >= 1:
177
+ potential_provider_str = parts_for_provider_model[0]
178
+ if any(p.value == potential_provider_str for p in Provider):
179
+ provider = Provider(potential_provider_str)
180
+ identified_provider_parts = 1
181
+
182
+ # Construct model_name from remaining parts
183
+ if identified_provider_parts > 0:
184
+ model_name_str = ".".join(parts_for_provider_model[identified_provider_parts:])
185
+ else:
186
+ # If no provider prefix was matched, the whole string (after effort removal) is the model name
187
+ model_name_str = ".".join(parts_for_provider_model)
148
188
 
149
- # Check first part for provider
150
- if len(model_parts) > 1:
151
- potential_provider = model_parts[0]
152
- if any(provider.value == potential_provider for provider in Provider):
153
- provider = Provider(potential_provider)
154
- model_parts = model_parts[1:]
189
+ # If provider still None, try to get from DEFAULT_PROVIDERS using the model_name_str
190
+ if provider is None:
191
+ provider = cls.DEFAULT_PROVIDERS.get(model_name_str)
192
+ if provider is None:
193
+ raise ModelConfigError(
194
+ f"Unknown model or provider for: {model_string}. Model name parsed as '{model_name_str}'"
195
+ )
155
196
 
156
- if provider == Provider.TENSORZERO and not model_parts:
197
+ if provider == Provider.TENSORZERO and not model_name_str:
157
198
  raise ModelConfigError(
158
199
  f"TensorZero provider requires a function name after the provider "
159
200
  f"(e.g., tensorzero.my-function), got: {model_string}"
160
201
  )
161
- # Join remaining parts as model name
162
- model_name = ".".join(model_parts)
163
-
164
- # If no provider was found in the string, look it up in defaults
165
- if provider is None:
166
- provider = cls.DEFAULT_PROVIDERS.get(model_name)
167
- if provider is None:
168
- raise ModelConfigError(f"Unknown model: {model_name}")
169
202
 
170
203
  return ModelConfig(
171
- provider=provider, model_name=model_name, reasoning_effort=reasoning_effort
204
+ provider=provider, model_name=model_name_str, reasoning_effort=reasoning_effort
172
205
  )
173
206
 
174
207
  @classmethod
@@ -185,33 +218,37 @@ class ModelFactory:
185
218
  Returns:
186
219
  A callable that takes an agent parameter and returns an LLM instance
187
220
  """
188
- # Parse configuration up front
189
221
  config = cls.parse_model_string(model_string)
222
+
223
+ # Ensure provider is valid before trying to access PROVIDER_CLASSES with it
224
+ if (
225
+ config.provider not in cls.PROVIDER_CLASSES
226
+ and config.model_name not in cls.MODEL_SPECIFIC_CLASSES
227
+ ):
228
+ # This check is important if a provider (like old GOOGLE) is commented out from PROVIDER_CLASSES
229
+ raise ModelConfigError(
230
+ f"Provider '{config.provider}' not configured in PROVIDER_CLASSES and model '{config.model_name}' not in MODEL_SPECIFIC_CLASSES."
231
+ )
232
+
190
233
  if config.model_name in cls.MODEL_SPECIFIC_CLASSES:
191
234
  llm_class = cls.MODEL_SPECIFIC_CLASSES[config.model_name]
192
235
  else:
236
+ # This line is now safer due to the check above
193
237
  llm_class = cls.PROVIDER_CLASSES[config.provider]
194
238
 
195
- # Create a factory function matching the updated attach_llm protocol
196
239
  def factory(
197
240
  agent: Agent, request_params: Optional[RequestParams] = None, **kwargs
198
241
  ) -> AugmentedLLMProtocol:
199
- # Create base params with parsed model name
200
242
  base_params = RequestParams()
201
- base_params.model = config.model_name # Use the parsed model name, not the alias
202
-
203
- # Add reasoning effort if available
243
+ base_params.model = config.model_name
204
244
  if config.reasoning_effort:
205
245
  kwargs["reasoning_effort"] = config.reasoning_effort.value
206
-
207
- # Forward all arguments to LLM constructor
208
246
  llm_args = {
209
247
  "agent": agent,
210
248
  "model": config.model_name,
211
249
  "request_params": request_params,
212
250
  **kwargs,
213
251
  }
214
-
215
252
  llm: AugmentedLLMProtocol = llm_class(**llm_args)
216
253
  return llm
217
254
 
@@ -9,11 +9,12 @@ class Provider(Enum):
9
9
  """Supported LLM providers"""
10
10
 
11
11
  ANTHROPIC = "anthropic"
12
- OPENAI = "openai"
13
- FAST_AGENT = "fast-agent"
14
- GOOGLE = "google"
15
12
  DEEPSEEK = "deepseek"
13
+ FAST_AGENT = "fast-agent"
16
14
  GENERIC = "generic"
15
+ GOOGLE_OAI = "googleoai" # For Google through OpenAI libraries
16
+ GOOGLE = "google" # For Google GenAI native library
17
+ OPENAI = "openai"
17
18
  OPENROUTER = "openrouter"
18
19
  TENSORZERO = "tensorzero" # For TensorZero Gateway
19
20
  AZURE = "azure" # Azure OpenAI Service
@@ -1,6 +1,10 @@
1
+ from typing import List, Tuple, Type
2
+
1
3
  from mcp_agent.core.request_params import RequestParams
2
4
  from mcp_agent.llm.provider_types import Provider
3
5
  from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
6
+ from mcp_agent.mcp.interfaces import ModelT
7
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
4
8
 
5
9
  DEEPSEEK_BASE_URL = "https://api.deepseek.com"
6
10
  DEFAULT_DEEPSEEK_MODEL = "deepseekchat" # current Deepseek only has two type models
@@ -28,3 +32,48 @@ class DeepSeekAugmentedLLM(OpenAIAugmentedLLM):
28
32
  base_url = self.context.config.deepseek.base_url
29
33
 
30
34
  return base_url if base_url else DEEPSEEK_BASE_URL
35
+
36
+ async def _apply_prompt_provider_specific_structured(
37
+ self,
38
+ multipart_messages: List[PromptMessageMultipart],
39
+ model: Type[ModelT],
40
+ request_params: RequestParams | None = None,
41
+ ) -> Tuple[ModelT | None, PromptMessageMultipart]: # noqa: F821
42
+ request_params = self.get_request_params(request_params)
43
+
44
+ request_params.response_format = {"type": "json_object"}
45
+
46
+ # Get the full schema and extract just the properties
47
+ full_schema = model.model_json_schema()
48
+ properties = full_schema.get("properties", {})
49
+ required_fields = full_schema.get("required", [])
50
+
51
+ # Create a cleaner format description
52
+ format_description = "{\n"
53
+ for field_name, field_info in properties.items():
54
+ field_type = field_info.get("type", "string")
55
+ description = field_info.get("description", "")
56
+ format_description += f' "{field_name}": "{field_type}"'
57
+ if description:
58
+ format_description += f" // {description}"
59
+ if field_name in required_fields:
60
+ format_description += " // REQUIRED"
61
+ format_description += "\n"
62
+ format_description += "}"
63
+
64
+ multipart_messages[-1].add_text(
65
+ f"""YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
66
+ {format_description}
67
+
68
+ IMPORTANT RULES:
69
+ - Respond ONLY with the JSON object, no other text
70
+ - Do NOT include "properties" or "schema" wrappers
71
+ - Do NOT use code fences or markdown
72
+ - The response must be valid JSON that matches the format above
73
+ - All required fields must be included"""
74
+ )
75
+
76
+ result: PromptMessageMultipart = await self._apply_prompt_provider_specific(
77
+ multipart_messages, request_params
78
+ )
79
+ return self._structured_from_multipart(result, model)