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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
  2. fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
  3. mcp_agent/__init__.py +75 -0
  4. mcp_agent/agents/agent.py +59 -371
  5. mcp_agent/agents/base_agent.py +522 -0
  6. mcp_agent/agents/workflow/__init__.py +1 -0
  7. mcp_agent/agents/workflow/chain_agent.py +173 -0
  8. mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
  9. mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
  10. mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +27 -11
  11. mcp_agent/agents/workflow/parallel_agent.py +182 -0
  12. mcp_agent/agents/workflow/router_agent.py +307 -0
  13. mcp_agent/app.py +3 -1
  14. mcp_agent/cli/commands/bootstrap.py +18 -7
  15. mcp_agent/cli/commands/setup.py +12 -4
  16. mcp_agent/cli/main.py +1 -1
  17. mcp_agent/cli/terminal.py +1 -1
  18. mcp_agent/config.py +24 -35
  19. mcp_agent/context.py +3 -1
  20. mcp_agent/context_dependent.py +3 -1
  21. mcp_agent/core/agent_types.py +10 -7
  22. mcp_agent/core/direct_agent_app.py +179 -0
  23. mcp_agent/core/direct_decorators.py +443 -0
  24. mcp_agent/core/direct_factory.py +476 -0
  25. mcp_agent/core/enhanced_prompt.py +15 -20
  26. mcp_agent/core/fastagent.py +151 -337
  27. mcp_agent/core/interactive_prompt.py +424 -0
  28. mcp_agent/core/mcp_content.py +19 -11
  29. mcp_agent/core/prompt.py +6 -2
  30. mcp_agent/core/validation.py +89 -16
  31. mcp_agent/executor/decorator_registry.py +6 -2
  32. mcp_agent/executor/temporal.py +35 -11
  33. mcp_agent/executor/workflow_signal.py +8 -2
  34. mcp_agent/human_input/handler.py +3 -1
  35. mcp_agent/llm/__init__.py +2 -0
  36. mcp_agent/{workflows/llm → llm}/augmented_llm.py +131 -256
  37. mcp_agent/{workflows/llm → llm}/augmented_llm_passthrough.py +35 -107
  38. mcp_agent/llm/augmented_llm_playback.py +83 -0
  39. mcp_agent/{workflows/llm → llm}/model_factory.py +26 -8
  40. mcp_agent/llm/providers/__init__.py +8 -0
  41. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +5 -1
  42. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +37 -141
  43. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  44. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +112 -148
  45. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +78 -35
  46. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +73 -44
  47. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +18 -4
  48. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +3 -3
  49. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +3 -3
  50. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +3 -3
  51. mcp_agent/{workflows/llm → llm}/sampling_converter.py +0 -21
  52. mcp_agent/{workflows/llm → llm}/sampling_format_converter.py +16 -1
  53. mcp_agent/logging/logger.py +2 -2
  54. mcp_agent/mcp/gen_client.py +9 -3
  55. mcp_agent/mcp/interfaces.py +67 -45
  56. mcp_agent/mcp/logger_textio.py +97 -0
  57. mcp_agent/mcp/mcp_agent_client_session.py +12 -4
  58. mcp_agent/mcp/mcp_agent_server.py +3 -1
  59. mcp_agent/mcp/mcp_aggregator.py +124 -93
  60. mcp_agent/mcp/mcp_connection_manager.py +21 -7
  61. mcp_agent/mcp/prompt_message_multipart.py +59 -1
  62. mcp_agent/mcp/prompt_render.py +77 -0
  63. mcp_agent/mcp/prompt_serialization.py +20 -13
  64. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  65. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  66. mcp_agent/mcp/prompts/prompt_load.py +15 -5
  67. mcp_agent/mcp/prompts/prompt_server.py +154 -87
  68. mcp_agent/mcp/prompts/prompt_template.py +26 -35
  69. mcp_agent/mcp/resource_utils.py +3 -1
  70. mcp_agent/mcp/sampling.py +24 -15
  71. mcp_agent/mcp_server/agent_server.py +8 -5
  72. mcp_agent/mcp_server_registry.py +22 -9
  73. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +1 -1
  74. mcp_agent/resources/examples/{data-analysis → in_dev}/slides.py +1 -1
  75. mcp_agent/resources/examples/internal/agent.py +4 -2
  76. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  77. mcp_agent/resources/examples/prompting/image_server.py +3 -1
  78. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  79. mcp_agent/ui/console_display.py +27 -7
  80. fast_agent_mcp-0.1.13.dist-info/RECORD +0 -164
  81. mcp_agent/core/agent_app.py +0 -570
  82. mcp_agent/core/agent_utils.py +0 -69
  83. mcp_agent/core/decorators.py +0 -448
  84. mcp_agent/core/factory.py +0 -422
  85. mcp_agent/core/proxies.py +0 -278
  86. mcp_agent/core/types.py +0 -22
  87. mcp_agent/eval/__init__.py +0 -0
  88. mcp_agent/mcp/stdio.py +0 -114
  89. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  90. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  91. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  92. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  93. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  94. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  95. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  96. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -189
  97. mcp_agent/resources/examples/researcher/researcher.py +0 -39
  98. mcp_agent/resources/examples/workflows/chaining.py +0 -45
  99. mcp_agent/resources/examples/workflows/evaluator.py +0 -79
  100. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  101. mcp_agent/resources/examples/workflows/human_input.py +0 -26
  102. mcp_agent/resources/examples/workflows/orchestrator.py +0 -74
  103. mcp_agent/resources/examples/workflows/parallel.py +0 -79
  104. mcp_agent/resources/examples/workflows/router.py +0 -54
  105. mcp_agent/resources/examples/workflows/sse.py +0 -23
  106. mcp_agent/telemetry/__init__.py +0 -0
  107. mcp_agent/telemetry/usage_tracking.py +0 -19
  108. mcp_agent/workflows/__init__.py +0 -0
  109. mcp_agent/workflows/embedding/__init__.py +0 -0
  110. mcp_agent/workflows/embedding/embedding_base.py +0 -58
  111. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  112. mcp_agent/workflows/embedding/embedding_openai.py +0 -37
  113. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  114. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -447
  115. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  116. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -117
  117. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -130
  118. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -41
  119. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -41
  120. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -150
  121. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  122. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -58
  123. mcp_agent/workflows/llm/__init__.py +0 -0
  124. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -111
  125. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  126. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  127. mcp_agent/workflows/orchestrator/orchestrator.py +0 -535
  128. mcp_agent/workflows/parallel/__init__.py +0 -0
  129. mcp_agent/workflows/parallel/fan_in.py +0 -320
  130. mcp_agent/workflows/parallel/fan_out.py +0 -181
  131. mcp_agent/workflows/parallel/parallel_llm.py +0 -149
  132. mcp_agent/workflows/router/__init__.py +0 -0
  133. mcp_agent/workflows/router/router_base.py +0 -338
  134. mcp_agent/workflows/router/router_embedding.py +0 -226
  135. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  136. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  137. mcp_agent/workflows/router/router_llm.py +0 -304
  138. mcp_agent/workflows/swarm/__init__.py +0 -0
  139. mcp_agent/workflows/swarm/swarm.py +0 -292
  140. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  141. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  142. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  143. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  144. {fast_agent_mcp-0.1.13.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  145. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
  146. /mcp_agent/{workflows/llm → llm}/memory.py +0 -0
  147. /mcp_agent/{workflows/llm → llm}/prompt_utils.py +0 -0
@@ -1,19 +1,19 @@
1
1
  import json # Import at the module level
2
- from typing import Any, List, Optional, Type, Union
2
+ from typing import Any, List, Optional, Union
3
3
 
4
- from mcp import GetPromptResult
5
4
  from mcp.types import PromptMessage
6
- from pydantic_core import from_json
7
5
 
8
- from mcp_agent.logging.logger import get_logger
9
- from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
10
- from mcp_agent.workflows.llm.augmented_llm import (
6
+ from mcp_agent.core.prompt import Prompt
7
+ from mcp_agent.llm.augmented_llm import (
11
8
  AugmentedLLM,
12
9
  MessageParamT,
13
- MessageT,
14
- ModelT,
15
10
  RequestParams,
16
11
  )
12
+ from mcp_agent.logging.logger import get_logger
13
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
14
+
15
+ CALL_TOOL_INDICATOR = "***CALL_TOOL"
16
+ FIXED_RESPONSE_INDICATOR = "***FIXED_RESPONSE"
17
17
 
18
18
 
19
19
  class PassthroughLLM(AugmentedLLM):
@@ -25,21 +25,12 @@ class PassthroughLLM(AugmentedLLM):
25
25
  parallel workflow where no fan-in aggregation is needed.
26
26
  """
27
27
 
28
- def __init__(self, name: str = "Passthrough", context=None, **kwargs) -> None:
29
- super().__init__(name=name, context=context, **kwargs)
28
+ def __init__(self, name: str = "Passthrough", **kwargs: dict[str, Any]) -> None:
29
+ super().__init__(name=name, **kwargs)
30
30
  self.provider = "fast-agent"
31
- # Initialize logger - keep it simple without name reference
32
31
  self.logger = get_logger(__name__)
33
32
  self._messages = [PromptMessage]
34
-
35
- async def generate(
36
- self,
37
- message: Union[str, MessageParamT, List[MessageParamT]],
38
- request_params: Optional[RequestParams] = None,
39
- ) -> Union[List[MessageT], Any]:
40
- """Simply return the input message as is."""
41
- # Return in the format expected by the caller
42
- return [message] if isinstance(message, list) else message
33
+ self._fixed_response: str | None = None
43
34
 
44
35
  async def generate_str(
45
36
  self,
@@ -143,92 +134,29 @@ class PassthroughLLM(AugmentedLLM):
143
134
 
144
135
  return "\n".join(result_text)
145
136
 
146
- async def generate_structured(
147
- self,
148
- message: Union[str, MessageParamT, List[MessageParamT]],
149
- response_model: Type[ModelT],
150
- request_params: Optional[RequestParams] = None,
151
- ) -> ModelT:
152
- """
153
- Return the input message as the requested model type.
154
- This is a best-effort implementation - it may fail if the
155
- message cannot be converted to the requested model.
156
- """
157
- if isinstance(message, response_model):
158
- return message
159
- elif isinstance(message, dict):
160
- return response_model(**message)
161
- elif isinstance(message, str):
162
- return response_model.model_validate(from_json(message, allow_partial=True))
163
-
164
- async def generate_prompt(self, prompt: "PromptMessageMultipart", request_params: RequestParams | None) -> str:
165
- # Check if this prompt contains a tool call command
166
- if prompt.content and prompt.content[0].text and prompt.content[0].text.startswith("***CALL_TOOL "):
167
- return await self._call_tool_and_return_result(prompt.content[0].text)
168
-
169
- # Process all parts of the PromptMessageMultipart
170
- parts_text = []
171
- for part in prompt.content:
172
- parts_text.append(str(part))
173
-
174
- # If no parts found, return empty string
175
- if not parts_text:
176
- return ""
177
-
178
- # Join all parts and process with generate_str
179
- return await self.generate_str("\n".join(parts_text), request_params)
180
-
181
- async def apply_prompt(
137
+ async def _apply_prompt_provider_specific(
182
138
  self,
183
139
  multipart_messages: List["PromptMessageMultipart"],
184
- request_params: Optional[RequestParams] = None,
185
- ) -> str:
186
- """
187
- Apply a list of PromptMessageMultipart messages directly to the LLM.
188
- In PassthroughLLM, this returns a concatenated string of all message content.
189
-
190
- Args:
191
- multipart_messages: List of PromptMessageMultipart objects
192
- request_params: Optional parameters to configure the LLM request
193
-
194
- Returns:
195
- String representation of all message content concatenated together
196
- """
197
- # Generate and concatenate result from all messages
198
- result = ""
199
- for prompt in multipart_messages:
200
- result += await self.generate_prompt(prompt, request_params) + "\n"
201
-
202
- return result
203
-
204
- async def apply_prompt_template(self, prompt_result: GetPromptResult, prompt_name: str) -> str:
205
- """
206
- Apply a prompt template by adding it to the conversation history.
207
- For PassthroughLLM, this returns all content concatenated together.
208
-
209
- Args:
210
- prompt_result: The GetPromptResult containing prompt messages
211
- prompt_name: The name of the prompt being applied
212
-
213
- Returns:
214
- String representation of all message content concatenated together
215
- """
216
- prompt_messages: List[PromptMessage] = prompt_result.messages
217
-
218
- # Extract arguments if they were stored in the result
219
- arguments = getattr(prompt_result, "arguments", None)
220
-
221
- # Display information about the loaded prompt
222
- await self.show_prompt_loaded(
223
- prompt_name=prompt_name,
224
- description=prompt_result.description,
225
- message_count=len(prompt_messages),
226
- arguments=arguments,
227
- )
228
- self._messages = prompt_messages
229
-
230
- # Convert prompt messages to multipart format
231
- multipart_messages = PromptMessageMultipart.to_multipart(prompt_messages)
232
-
233
- # Use apply_prompt to handle the multipart messages
234
- return await self.apply_prompt(multipart_messages)
140
+ request_params: RequestParams | None = None,
141
+ ) -> PromptMessageMultipart:
142
+ last_message = multipart_messages[-1]
143
+
144
+ # TODO -- improve when we support Audio/Multimodal gen
145
+ if self.is_tool_call(last_message):
146
+ return Prompt.assistant(await self.generate_str(last_message.first_text()))
147
+
148
+ if last_message.first_text().startswith(FIXED_RESPONSE_INDICATOR):
149
+ self._fixed_response = (
150
+ last_message.first_text().split(FIXED_RESPONSE_INDICATOR, 1)[1].strip()
151
+ )
152
+
153
+ if self._fixed_response:
154
+ await self.show_assistant_message(self._fixed_response)
155
+ return Prompt.assistant(self._fixed_response)
156
+ else:
157
+ concatenated: str = "\n".join(message.all_text() for message in multipart_messages)
158
+ await self.show_assistant_message(concatenated)
159
+ return Prompt.assistant(concatenated)
160
+
161
+ def is_tool_call(self, message: PromptMessageMultipart) -> bool:
162
+ return message.first_text().startswith(CALL_TOOL_INDICATOR)
@@ -0,0 +1,83 @@
1
+ from typing import Any, List
2
+
3
+ from mcp_agent.core.prompt import Prompt
4
+ from mcp_agent.llm.augmented_llm import RequestParams
5
+ from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
6
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
7
+ from mcp_agent.mcp.prompts.prompt_helpers import MessageContent
8
+
9
+
10
+ class PlaybackLLM(PassthroughLLM):
11
+ """
12
+ A specialized LLM implementation that plays back assistant messages when loaded with prompts.
13
+
14
+ Unlike the PassthroughLLM which simply passes through messages without modification,
15
+ PlaybackLLM is designed to simulate a conversation by playing back prompt messages
16
+ in sequence when loaded with prompts through apply_prompt_template.
17
+
18
+ After apply_prompts has been called, each call to generate_str returns the next
19
+ "ASSISTANT" message in the loaded messages. If no messages are set or all messages have
20
+ been played back, it returns a message indicating that messages are exhausted.
21
+ """
22
+
23
+ def __init__(self, name: str = "Playback", **kwargs: dict[str, Any]) -> None:
24
+ super().__init__(name=name, **kwargs)
25
+ self._messages: List[PromptMessageMultipart] = []
26
+ self._current_index = -1
27
+ self._overage = -1
28
+
29
+ def _get_next_assistant_message(self) -> PromptMessageMultipart:
30
+ """
31
+ Get the next assistant message from the loaded messages.
32
+ Increments the current message index and skips user messages.
33
+ """
34
+ # Find next assistant message
35
+ while self._current_index < len(self._messages):
36
+ message = self._messages[self._current_index]
37
+ self._current_index += 1
38
+ if "assistant" != message.role:
39
+ continue
40
+
41
+ return message
42
+
43
+ self._overage += 1
44
+ return Prompt.assistant(
45
+ f"MESSAGES EXHAUSTED (list size {len(self._messages)}) ({self._overage} overage)"
46
+ )
47
+
48
+ async def generate(
49
+ self,
50
+ multipart_messages: List[PromptMessageMultipart],
51
+ request_params: RequestParams | None = None,
52
+ ) -> PromptMessageMultipart:
53
+ """
54
+ Handle playback of messages in two modes:
55
+ 1. First call: store messages for playback and return "HISTORY LOADED"
56
+ 2. Subsequent calls: return the next assistant message
57
+ """
58
+ # If this is the first call (initialization) or we're loading a prompt template
59
+ # with multiple messages (comes from apply_prompt)
60
+ if -1 == self._current_index:
61
+ if len(multipart_messages) > 1:
62
+ self._messages = multipart_messages
63
+ else:
64
+ self._messages.extend(multipart_messages)
65
+
66
+ # Reset the index to the beginning for proper playback
67
+ self._current_index = 0
68
+
69
+ await self.show_assistant_message(
70
+ message_text=f"HISTORY LOADED ({len(self._messages)} messages)",
71
+ title="ASSISTANT/PLAYBACK",
72
+ )
73
+
74
+ # In PlaybackLLM, we always return "HISTORY LOADED" on initialization,
75
+ # regardless of the prompt content. The next call will return messages.
76
+ return Prompt.assistant("HISTORY LOADED")
77
+
78
+ response = self._get_next_assistant_message()
79
+ await self.show_assistant_message(
80
+ message_text=MessageContent.get_first_text(response), title="ASSISTANT/PLAYBACK"
81
+ )
82
+
83
+ return response
@@ -5,11 +5,15 @@ from typing import Callable, Dict, Optional, Type, Union
5
5
  from mcp_agent.agents.agent import Agent
6
6
  from mcp_agent.core.exceptions import ModelConfigError
7
7
  from mcp_agent.core.request_params import RequestParams
8
+ from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
9
+ from mcp_agent.llm.augmented_llm_playback import PlaybackLLM
10
+ from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM
11
+ from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
12
+ from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
8
13
  from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
9
- from mcp_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM
10
- from mcp_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM
11
- from mcp_agent.workflows.llm.augmented_llm_passthrough import PassthroughLLM
12
- from mcp_agent.workflows.llm.augmented_llm_playback import PlaybackLLM
14
+
15
+ # from mcp_agent.workflows.llm.augmented_llm_deepseek import DeekSeekAugmentedLLM
16
+
13
17
 
14
18
  # Type alias for LLM classes
15
19
  LLMClass = Union[
@@ -17,6 +21,7 @@ LLMClass = Union[
17
21
  Type[OpenAIAugmentedLLM],
18
22
  Type[PassthroughLLM],
19
23
  Type[PlaybackLLM],
24
+ Type[DeepSeekAugmentedLLM],
20
25
  ]
21
26
 
22
27
 
@@ -26,6 +31,7 @@ class Provider(Enum):
26
31
  ANTHROPIC = auto()
27
32
  OPENAI = auto()
28
33
  FAST_AGENT = auto()
34
+ DEEPSEEK = auto()
29
35
 
30
36
 
31
37
  class ReasoningEffort(Enum):
@@ -53,6 +59,7 @@ class ModelFactory:
53
59
  "anthropic": Provider.ANTHROPIC,
54
60
  "openai": Provider.OPENAI,
55
61
  "fast-agent": Provider.FAST_AGENT,
62
+ "deepseek": Provider.DEEPSEEK,
56
63
  }
57
64
 
58
65
  # Mapping of effort strings to enum values
@@ -85,6 +92,8 @@ class ModelFactory:
85
92
  "claude-3-7-sonnet-latest": Provider.ANTHROPIC,
86
93
  "claude-3-opus-20240229": Provider.ANTHROPIC,
87
94
  "claude-3-opus-latest": Provider.ANTHROPIC,
95
+ "deepseek-chat": Provider.DEEPSEEK,
96
+ # "deepseek-reasoner": Provider.DEEPSEEK, reinstate on release
88
97
  }
89
98
 
90
99
  MODEL_ALIASES = {
@@ -97,6 +106,8 @@ class ModelFactory:
97
106
  "haiku35": "claude-3-5-haiku-latest",
98
107
  "opus": "claude-3-opus-latest",
99
108
  "opus3": "claude-3-opus-latest",
109
+ "deepseekv3": "deepseek-chat",
110
+ "deepseek": "deepseek-chat",
100
111
  }
101
112
 
102
113
  # Mapping of providers to their LLM classes
@@ -104,6 +115,7 @@ class ModelFactory:
104
115
  Provider.ANTHROPIC: AnthropicAugmentedLLM,
105
116
  Provider.OPENAI: OpenAIAugmentedLLM,
106
117
  Provider.FAST_AGENT: PassthroughLLM,
118
+ Provider.DEEPSEEK: DeepSeekAugmentedLLM,
107
119
  }
108
120
 
109
121
  # Mapping of special model names to their specific LLM classes
@@ -145,10 +157,14 @@ class ModelFactory:
145
157
  if provider is None:
146
158
  raise ModelConfigError(f"Unknown model: {model_name}")
147
159
 
148
- return ModelConfig(provider=provider, model_name=model_name, reasoning_effort=reasoning_effort)
160
+ return ModelConfig(
161
+ provider=provider, model_name=model_name, reasoning_effort=reasoning_effort
162
+ )
149
163
 
150
164
  @classmethod
151
- def create_factory(cls, model_string: str, request_params: Optional[RequestParams] = None) -> Callable[..., AugmentedLLMProtocol]:
165
+ def create_factory(
166
+ cls, model_string: str, request_params: Optional[RequestParams] = None
167
+ ) -> Callable[..., AugmentedLLMProtocol]:
152
168
  """
153
169
  Creates a factory function that follows the attach_llm protocol.
154
170
 
@@ -177,7 +193,9 @@ class ModelFactory:
177
193
  params_dict = factory_params.model_dump()
178
194
  params_dict.update(kwargs["default_request_params"].model_dump(exclude_unset=True))
179
195
  factory_params = RequestParams(**params_dict)
180
- factory_params.model = config.model_name # Ensure parsed model name isn't overwritten
196
+ factory_params.model = (
197
+ config.model_name
198
+ ) # Ensure parsed model name isn't overwritten
181
199
 
182
200
  # Forward all keyword arguments to LLM constructor
183
201
  llm_args = {
@@ -196,7 +214,7 @@ class ModelFactory:
196
214
  if key not in ["agent", "default_request_params", "name"]:
197
215
  llm_args[key] = value
198
216
 
199
- llm = llm_class(**llm_args)
217
+ llm: AugmentedLLMProtocol = llm_class(**llm_args)
200
218
  return llm
201
219
 
202
220
  return factory
@@ -0,0 +1,8 @@
1
+ from mcp_agent.llm.providers.sampling_converter_anthropic import (
2
+ AnthropicSamplingConverter,
3
+ )
4
+ from mcp_agent.llm.providers.sampling_converter_openai import (
5
+ OpenAISamplingConverter,
6
+ )
7
+
8
+ __all__ = ["AnthropicSamplingConverter", "OpenAISamplingConverter"]
@@ -47,7 +47,11 @@ def anthropic_message_param_to_prompt_message_multipart(
47
47
  text = block.get("text", "")
48
48
 
49
49
  # Check if this is a resource marker
50
- if text and (text.startswith("[Resource:") or text.startswith("[Binary Resource:")) and "\n" in text:
50
+ if (
51
+ text
52
+ and (text.startswith("[Resource:") or text.startswith("[Binary Resource:"))
53
+ and "\n" in text
54
+ ):
51
55
  header, content_text = text.split("\n", 1)
52
56
  if "MIME:" in header:
53
57
  mime_match = header.split("MIME:", 1)[1].split("]")[0].strip()
@@ -1,18 +1,18 @@
1
1
  import os
2
2
  from typing import TYPE_CHECKING, List, Type
3
3
 
4
- from mcp_agent.workflows.llm.providers.multipart_converter_anthropic import (
4
+ from mcp_agent.core.prompt import Prompt
5
+ from mcp_agent.llm.providers.multipart_converter_anthropic import (
5
6
  AnthropicConverter,
6
7
  )
7
- from mcp_agent.workflows.llm.providers.sampling_converter_anthropic import (
8
+ from mcp_agent.llm.providers.sampling_converter_anthropic import (
8
9
  AnthropicSamplingConverter,
9
10
  )
11
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
10
12
 
11
13
  if TYPE_CHECKING:
12
14
  from mcp import ListToolsResult
13
15
 
14
- from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
15
-
16
16
 
17
17
  from anthropic import Anthropic, AuthenticationError
18
18
  from anthropic.types import (
@@ -32,12 +32,12 @@ from pydantic_core import from_json
32
32
  from rich.text import Text
33
33
 
34
34
  from mcp_agent.core.exceptions import ProviderKeyError
35
- from mcp_agent.logging.logger import get_logger
36
- from mcp_agent.workflows.llm.augmented_llm import (
35
+ from mcp_agent.llm.augmented_llm import (
37
36
  AugmentedLLM,
38
37
  ModelT,
39
38
  RequestParams,
40
39
  )
40
+ from mcp_agent.logging.logger import get_logger
41
41
 
42
42
  DEFAULT_ANTHROPIC_MODEL = "claude-3-7-sonnet-latest"
43
43
 
@@ -69,9 +69,12 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
69
69
  use_history=True,
70
70
  )
71
71
 
72
- async def generate(
72
+ def _base_url(self) -> str:
73
+ return self.context.config.anthropic.base_url if self.context.config.anthropic else None
74
+
75
+ async def generate_internal(
73
76
  self,
74
- message,
77
+ message_param,
75
78
  request_params: RequestParams | None = None,
76
79
  ):
77
80
  """
@@ -80,8 +83,12 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
80
83
  """
81
84
 
82
85
  api_key = self._api_key(self.context.config)
86
+ base_url = self._base_url()
87
+ if base_url and base_url.endswith("/v1"):
88
+ base_url = base_url.rstrip("/v1")
89
+
83
90
  try:
84
- anthropic = Anthropic(api_key=api_key)
91
+ anthropic = Anthropic(api_key=api_key, base_url=base_url)
85
92
  messages: List[MessageParam] = []
86
93
  params = self.get_request_params(request_params)
87
94
  except AuthenticationError as e:
@@ -94,12 +101,7 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
94
101
  # if use_history is True
95
102
  messages.extend(self.history.get(include_history=params.use_history))
96
103
 
97
- if isinstance(message, str):
98
- messages.append({"role": "user", "content": message})
99
- elif isinstance(message, list):
100
- messages.extend(message)
101
- else:
102
- messages.append(message)
104
+ messages.append(message_param)
103
105
 
104
106
  tool_list: ListToolsResult = await self.aggregator.list_tools()
105
107
  available_tools: List[ToolParam] = [
@@ -112,13 +114,11 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
112
114
  ]
113
115
 
114
116
  responses: List[Message] = []
115
- model = await self.select_model(params)
116
- chat_turn = (len(messages) + 1) // 2
117
- self.show_user_message(str(message), model, chat_turn)
117
+
118
+ model = self.default_request_params.model
118
119
 
119
120
  for i in range(params.max_iterations):
120
- chat_turn = (len(messages) + 1) // 2
121
- self._log_chat_progress(chat_turn, model=model)
121
+ self._log_chat_progress(self.chat_turn(), model=model)
122
122
  arguments = {
123
123
  "model": model,
124
124
  "messages": messages,
@@ -191,7 +191,9 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
191
191
  break
192
192
  elif response.stop_reason == "stop_sequence":
193
193
  # We have reached a stop sequence
194
- self.logger.debug(f"Iteration {i}: Stopping because finish_reason is 'stop_sequence'")
194
+ self.logger.debug(
195
+ f"Iteration {i}: Stopping because finish_reason is 'stop_sequence'"
196
+ )
195
197
  break
196
198
  elif response.stop_reason == "max_tokens":
197
199
  # We have reached the max tokens limit
@@ -246,7 +248,9 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
246
248
  params=CallToolRequestParams(name=tool_name, arguments=tool_args),
247
249
  )
248
250
  # TODO -- support MCP isError etc.
249
- result = await self.call_tool(request=tool_call_request, tool_call_id=tool_use_id)
251
+ result = await self.call_tool(
252
+ request=tool_call_request, tool_call_id=tool_use_id
253
+ )
250
254
  self.show_tool_result(result)
251
255
 
252
256
  # Add each result to our collection
@@ -293,7 +297,7 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
293
297
 
294
298
  async def generate_str(
295
299
  self,
296
- message,
300
+ message_param,
297
301
  request_params: RequestParams | None = None,
298
302
  ) -> str:
299
303
  """
@@ -301,16 +305,10 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
301
305
  The default implementation uses Claude as the LLM.
302
306
  Override this method to use a different LLM.
303
307
 
304
- Special commands:
305
- - "***SAVE_HISTORY <filename.md>" - Saves the conversation history to the specified file
306
- in MCP prompt format with user/assistant delimiters.
307
308
  """
308
- # Check if this is a special command to save history
309
- if isinstance(message, str) and message.startswith("***SAVE_HISTORY "):
310
- return await self._save_history_to_file(message)
311
309
 
312
- responses: List[Message] = await self.generate(
313
- message=message,
310
+ responses: List[Message] = await self.generate_internal(
311
+ message_param=message_param,
314
312
  request_params=request_params,
315
313
  )
316
314
 
@@ -333,96 +331,32 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
333
331
  # Join all collected text
334
332
  return "\n".join(final_text)
335
333
 
336
- async def generate_prompt(self, prompt: "PromptMessageMultipart", request_params: RequestParams | None) -> str:
337
- return await self.generate_str(AnthropicConverter.convert_to_anthropic(prompt), request_params)
338
-
339
- async def _apply_prompt_template_provider_specific(
334
+ async def _apply_prompt_provider_specific(
340
335
  self,
341
336
  multipart_messages: List["PromptMessageMultipart"],
342
337
  request_params: RequestParams | None = None,
343
- ) -> str:
344
- """
345
- Anthropic-specific implementation of apply_prompt_template that handles
346
- multimodal content natively.
347
-
348
- Args:
349
- multipart_messages: List of PromptMessageMultipart objects parsed from the prompt template
350
-
351
- Returns:
352
- String representation of the assistant's response if generated,
353
- or the last assistant message in the prompt
354
- """
338
+ ) -> PromptMessageMultipart:
355
339
  # Check the last message role
356
340
  last_message = multipart_messages[-1]
357
341
 
358
342
  # Add all previous messages to history (or all messages if last is from assistant)
359
- messages_to_add = multipart_messages[:-1] if last_message.role == "user" else multipart_messages
343
+ messages_to_add = (
344
+ multipart_messages[:-1] if last_message.role == "user" else multipart_messages
345
+ )
360
346
  converted = []
361
347
  for msg in messages_to_add:
362
348
  converted.append(AnthropicConverter.convert_to_anthropic(msg))
349
+
363
350
  self.history.extend(converted, is_prompt=True)
364
351
 
365
352
  if last_message.role == "user":
366
- # For user messages: Generate response to the last one
367
353
  self.logger.debug("Last message in prompt is from user, generating assistant response")
368
354
  message_param = AnthropicConverter.convert_to_anthropic(last_message)
369
- return await self.generate_str(message_param, request_params)
355
+ return Prompt.assistant(await self.generate_str(message_param, request_params))
370
356
  else:
371
357
  # For assistant messages: Return the last message content as text
372
358
  self.logger.debug("Last message in prompt is from assistant, returning it directly")
373
- return str(last_message)
374
-
375
- async def _save_history_to_file(self, command: str) -> str:
376
- """
377
- Save the conversation history to a file in MCP prompt format.
378
-
379
- Args:
380
- command: The command string, expected format: "***SAVE_HISTORY <filename.md>"
381
-
382
- Returns:
383
- Success or error message
384
- """
385
- try:
386
- # Extract the filename from the command
387
- parts = command.split(" ", 1)
388
- if len(parts) != 2 or not parts[1].strip():
389
- return "Error: Invalid format. Expected '***SAVE_HISTORY <filename.md>'"
390
-
391
- filename = parts[1].strip()
392
-
393
- # Get all messages from history
394
- messages = self.history.get(include_history=True)
395
-
396
- # Import required utilities
397
- from mcp_agent.mcp.prompt_serialization import (
398
- multipart_messages_to_delimited_format,
399
- )
400
- from mcp_agent.workflows.llm.anthropic_utils import (
401
- anthropic_message_param_to_prompt_message_multipart,
402
- )
403
-
404
- # Convert message params to PromptMessageMultipart objects
405
- multipart_messages = []
406
- for msg in messages:
407
- multipart_messages.append(anthropic_message_param_to_prompt_message_multipart(msg))
408
-
409
- # Convert to delimited format
410
- delimited_content = multipart_messages_to_delimited_format(
411
- multipart_messages,
412
- user_delimiter="---USER",
413
- assistant_delimiter="---ASSISTANT",
414
- )
415
-
416
- # Write to file
417
- with open(filename, "w", encoding="utf-8") as f:
418
- f.write("\n\n".join(delimited_content))
419
-
420
- self.logger.info(f"Saved conversation history to {filename}")
421
- return f"Done. Saved conversation history to {filename}"
422
-
423
- except Exception as e:
424
- self.logger.error(f"Error saving history: {str(e)}")
425
- return f"Error saving history: {str(e)}"
359
+ return last_message
426
360
 
427
361
  async def generate_structured(
428
362
  self,
@@ -461,41 +395,3 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
461
395
  )
462
396
 
463
397
  return MessageParam(role="assistant", content=content, **kwargs)
464
-
465
- def message_param_str(self, message: MessageParam) -> str:
466
- """Convert an input message to a string representation."""
467
-
468
- if message.get("content"):
469
- content = message["content"]
470
- if isinstance(content, str):
471
- return content
472
- else:
473
- final_text: List[str] = []
474
- for block in content:
475
- if block.text:
476
- final_text.append(str(block.text))
477
- else:
478
- final_text.append(str(block))
479
-
480
- return "\n".join(final_text)
481
-
482
- return str(message)
483
-
484
- def message_str(self, message: Message) -> str:
485
- """Convert an output message to a string representation."""
486
- content = message.content
487
-
488
- if content:
489
- if isinstance(content, list):
490
- final_text: List[str] = []
491
- for block in content:
492
- if block.text:
493
- final_text.append(str(block.text))
494
- else:
495
- final_text.append(str(block))
496
-
497
- return "\n".join(final_text)
498
- else:
499
- return str(content)
500
-
501
- return str(message)