fast-agent-mcp 0.1.12__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 (169) hide show
  1. {fast_agent_mcp-0.1.12.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 +61 -415
  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 +11 -21
  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 +15 -19
  14. mcp_agent/cli/commands/bootstrap.py +19 -38
  15. mcp_agent/cli/commands/config.py +4 -4
  16. mcp_agent/cli/commands/setup.py +7 -14
  17. mcp_agent/cli/main.py +7 -10
  18. mcp_agent/cli/terminal.py +3 -3
  19. mcp_agent/config.py +25 -40
  20. mcp_agent/context.py +12 -21
  21. mcp_agent/context_dependent.py +3 -5
  22. mcp_agent/core/agent_types.py +10 -7
  23. mcp_agent/core/direct_agent_app.py +179 -0
  24. mcp_agent/core/direct_decorators.py +443 -0
  25. mcp_agent/core/direct_factory.py +476 -0
  26. mcp_agent/core/enhanced_prompt.py +23 -55
  27. mcp_agent/core/exceptions.py +8 -8
  28. mcp_agent/core/fastagent.py +145 -371
  29. mcp_agent/core/interactive_prompt.py +424 -0
  30. mcp_agent/core/mcp_content.py +17 -17
  31. mcp_agent/core/prompt.py +6 -9
  32. mcp_agent/core/request_params.py +6 -3
  33. mcp_agent/core/validation.py +92 -18
  34. mcp_agent/executor/decorator_registry.py +9 -17
  35. mcp_agent/executor/executor.py +8 -17
  36. mcp_agent/executor/task_registry.py +2 -4
  37. mcp_agent/executor/temporal.py +19 -41
  38. mcp_agent/executor/workflow.py +3 -5
  39. mcp_agent/executor/workflow_signal.py +15 -21
  40. mcp_agent/human_input/handler.py +4 -7
  41. mcp_agent/human_input/types.py +2 -3
  42. mcp_agent/llm/__init__.py +2 -0
  43. mcp_agent/llm/augmented_llm.py +450 -0
  44. mcp_agent/llm/augmented_llm_passthrough.py +162 -0
  45. mcp_agent/llm/augmented_llm_playback.py +83 -0
  46. mcp_agent/llm/memory.py +103 -0
  47. mcp_agent/{workflows/llm → llm}/model_factory.py +22 -16
  48. mcp_agent/{workflows/llm → llm}/prompt_utils.py +1 -3
  49. mcp_agent/llm/providers/__init__.py +8 -0
  50. mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +8 -25
  51. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +56 -194
  52. mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
  53. mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +99 -190
  54. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +72 -71
  55. mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +65 -71
  56. mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +16 -44
  57. mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +4 -4
  58. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +9 -11
  59. mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +8 -12
  60. mcp_agent/{workflows/llm → llm}/sampling_converter.py +3 -31
  61. mcp_agent/llm/sampling_format_converter.py +37 -0
  62. mcp_agent/logging/events.py +1 -5
  63. mcp_agent/logging/json_serializer.py +7 -6
  64. mcp_agent/logging/listeners.py +20 -23
  65. mcp_agent/logging/logger.py +17 -19
  66. mcp_agent/logging/rich_progress.py +10 -8
  67. mcp_agent/logging/tracing.py +4 -6
  68. mcp_agent/logging/transport.py +22 -22
  69. mcp_agent/mcp/gen_client.py +1 -3
  70. mcp_agent/mcp/interfaces.py +117 -110
  71. mcp_agent/mcp/logger_textio.py +97 -0
  72. mcp_agent/mcp/mcp_agent_client_session.py +7 -7
  73. mcp_agent/mcp/mcp_agent_server.py +8 -8
  74. mcp_agent/mcp/mcp_aggregator.py +102 -143
  75. mcp_agent/mcp/mcp_connection_manager.py +20 -27
  76. mcp_agent/mcp/prompt_message_multipart.py +68 -16
  77. mcp_agent/mcp/prompt_render.py +77 -0
  78. mcp_agent/mcp/prompt_serialization.py +30 -48
  79. mcp_agent/mcp/prompts/prompt_constants.py +18 -0
  80. mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
  81. mcp_agent/mcp/prompts/prompt_load.py +109 -0
  82. mcp_agent/mcp/prompts/prompt_server.py +155 -195
  83. mcp_agent/mcp/prompts/prompt_template.py +35 -66
  84. mcp_agent/mcp/resource_utils.py +7 -14
  85. mcp_agent/mcp/sampling.py +17 -17
  86. mcp_agent/mcp_server/agent_server.py +13 -17
  87. mcp_agent/mcp_server_registry.py +13 -22
  88. mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +3 -2
  89. mcp_agent/resources/examples/in_dev/slides.py +110 -0
  90. mcp_agent/resources/examples/internal/agent.py +6 -3
  91. mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
  92. mcp_agent/resources/examples/internal/job.py +2 -1
  93. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  94. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  95. mcp_agent/resources/examples/internal/sizer.py +2 -1
  96. mcp_agent/resources/examples/internal/social.py +2 -1
  97. mcp_agent/resources/examples/prompting/agent.py +2 -1
  98. mcp_agent/resources/examples/prompting/image_server.py +4 -8
  99. mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
  100. mcp_agent/ui/console_display.py +16 -20
  101. fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
  102. mcp_agent/core/agent_app.py +0 -646
  103. mcp_agent/core/agent_utils.py +0 -71
  104. mcp_agent/core/decorators.py +0 -455
  105. mcp_agent/core/factory.py +0 -463
  106. mcp_agent/core/proxies.py +0 -269
  107. mcp_agent/core/types.py +0 -24
  108. mcp_agent/eval/__init__.py +0 -0
  109. mcp_agent/mcp/stdio.py +0 -111
  110. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
  111. mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
  112. mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
  113. mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
  114. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
  115. mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
  116. mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
  117. mcp_agent/resources/examples/researcher/researcher-imp.py +0 -190
  118. mcp_agent/resources/examples/researcher/researcher.py +0 -38
  119. mcp_agent/resources/examples/workflows/chaining.py +0 -44
  120. mcp_agent/resources/examples/workflows/evaluator.py +0 -78
  121. mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
  122. mcp_agent/resources/examples/workflows/human_input.py +0 -25
  123. mcp_agent/resources/examples/workflows/orchestrator.py +0 -73
  124. mcp_agent/resources/examples/workflows/parallel.py +0 -78
  125. mcp_agent/resources/examples/workflows/router.py +0 -53
  126. mcp_agent/resources/examples/workflows/sse.py +0 -23
  127. mcp_agent/telemetry/__init__.py +0 -0
  128. mcp_agent/telemetry/usage_tracking.py +0 -18
  129. mcp_agent/workflows/__init__.py +0 -0
  130. mcp_agent/workflows/embedding/__init__.py +0 -0
  131. mcp_agent/workflows/embedding/embedding_base.py +0 -61
  132. mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
  133. mcp_agent/workflows/embedding/embedding_openai.py +0 -46
  134. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  135. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -481
  136. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  137. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -120
  138. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -134
  139. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -45
  140. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -45
  141. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -161
  142. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
  143. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -60
  144. mcp_agent/workflows/llm/__init__.py +0 -0
  145. mcp_agent/workflows/llm/augmented_llm.py +0 -753
  146. mcp_agent/workflows/llm/augmented_llm_passthrough.py +0 -241
  147. mcp_agent/workflows/llm/augmented_llm_playback.py +0 -109
  148. mcp_agent/workflows/llm/providers/__init__.py +0 -8
  149. mcp_agent/workflows/llm/sampling_format_converter.py +0 -22
  150. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  151. mcp_agent/workflows/orchestrator/orchestrator.py +0 -578
  152. mcp_agent/workflows/parallel/__init__.py +0 -0
  153. mcp_agent/workflows/parallel/fan_in.py +0 -350
  154. mcp_agent/workflows/parallel/fan_out.py +0 -187
  155. mcp_agent/workflows/parallel/parallel_llm.py +0 -166
  156. mcp_agent/workflows/router/__init__.py +0 -0
  157. mcp_agent/workflows/router/router_base.py +0 -368
  158. mcp_agent/workflows/router/router_embedding.py +0 -240
  159. mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
  160. mcp_agent/workflows/router/router_embedding_openai.py +0 -59
  161. mcp_agent/workflows/router/router_llm.py +0 -320
  162. mcp_agent/workflows/swarm/__init__.py +0 -0
  163. mcp_agent/workflows/swarm/swarm.py +0 -320
  164. mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
  165. mcp_agent/workflows/swarm/swarm_openai.py +0 -41
  166. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
  167. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  168. {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  169. /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
@@ -0,0 +1,327 @@
1
+ """
2
+ Helper functions for working with PromptMessage and PromptMessageMultipart objects.
3
+
4
+ These utilities simplify extracting content from nested message structures
5
+ without repetitive type checking.
6
+ """
7
+
8
+ from typing import List, Optional, Union, cast
9
+
10
+ from mcp.types import (
11
+ BlobResourceContents,
12
+ EmbeddedResource,
13
+ ImageContent,
14
+ PromptMessage,
15
+ TextContent,
16
+ TextResourceContents,
17
+ )
18
+
19
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
20
+
21
+
22
+ def get_text(content: Union[TextContent, ImageContent, EmbeddedResource]) -> Optional[str]:
23
+ """
24
+ Extract text content from a content object if available.
25
+
26
+ Args:
27
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
28
+
29
+ Returns:
30
+ The text content as a string or None if not a text content
31
+ """
32
+ if isinstance(content, TextContent):
33
+ return content.text
34
+
35
+ if isinstance(content, EmbeddedResource):
36
+ if isinstance(content.resource, TextResourceContents):
37
+ return content.resource.text
38
+
39
+ return None
40
+
41
+
42
+ def get_image_data(content: Union[TextContent, ImageContent, EmbeddedResource]) -> Optional[str]:
43
+ """
44
+ Extract image data from a content object if available.
45
+
46
+ Args:
47
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
48
+
49
+ Returns:
50
+ The image data as a base64 string or None if not an image content
51
+ """
52
+ if isinstance(content, ImageContent):
53
+ return content.data
54
+
55
+ if isinstance(content, EmbeddedResource):
56
+ if isinstance(content.resource, BlobResourceContents):
57
+ # This assumes the blob might be an image, which isn't always true
58
+ # Consider checking the mimeType if needed
59
+ return content.resource.blob
60
+
61
+ return None
62
+
63
+
64
+ def get_resource_uri(content: Union[TextContent, ImageContent, EmbeddedResource]) -> Optional[str]:
65
+ """
66
+ Extract resource URI from an EmbeddedResource if available.
67
+
68
+ Args:
69
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
70
+
71
+ Returns:
72
+ The resource URI as a string or None if not an embedded resource
73
+ """
74
+ if isinstance(content, EmbeddedResource):
75
+ return str(content.resource.uri)
76
+
77
+ return None
78
+
79
+
80
+ def is_text_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
81
+ """
82
+ Check if the content is text content.
83
+
84
+ Args:
85
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
86
+
87
+ Returns:
88
+ True if the content is TextContent, False otherwise
89
+ """
90
+ return isinstance(content, TextContent)
91
+
92
+
93
+ def is_image_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
94
+ """
95
+ Check if the content is image content.
96
+
97
+ Args:
98
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
99
+
100
+ Returns:
101
+ True if the content is ImageContent, False otherwise
102
+ """
103
+ return isinstance(content, ImageContent)
104
+
105
+
106
+ def is_resource_content(content: Union[TextContent, ImageContent, EmbeddedResource]) -> bool:
107
+ """
108
+ Check if the content is an embedded resource.
109
+
110
+ Args:
111
+ content: A content object (TextContent, ImageContent, or EmbeddedResource)
112
+
113
+ Returns:
114
+ True if the content is EmbeddedResource, False otherwise
115
+ """
116
+ return isinstance(content, EmbeddedResource)
117
+
118
+
119
+ class MessageContent:
120
+ """
121
+ Helper class for working with message content in both PromptMessage and
122
+ PromptMessageMultipart objects.
123
+ """
124
+
125
+ @staticmethod
126
+ def get_all_text(message: Union[PromptMessage, PromptMessageMultipart]) -> List[str]:
127
+ """
128
+ Extract all text content from a message.
129
+
130
+ Args:
131
+ message: A PromptMessage or PromptMessageMultipart
132
+
133
+ Returns:
134
+ List of text strings from all text content parts
135
+ """
136
+ if isinstance(message, PromptMessage):
137
+ text = get_text(message.content)
138
+ return [text] if text is not None else []
139
+
140
+ result = []
141
+ for content in message.content:
142
+ text = get_text(content)
143
+ if text is not None:
144
+ result.append(text)
145
+
146
+ return result
147
+
148
+ @staticmethod
149
+ def join_text(
150
+ message: Union[PromptMessage, PromptMessageMultipart], separator: str = "\n\n"
151
+ ) -> str:
152
+ """
153
+ Join all text content in a message with a separator.
154
+
155
+ Args:
156
+ message: A PromptMessage or PromptMessageMultipart
157
+ separator: String to use as separator (default: newlines)
158
+
159
+ Returns:
160
+ Joined text string
161
+ """
162
+ return separator.join(MessageContent.get_all_text(message))
163
+
164
+ @staticmethod
165
+ def get_first_text(message: Union[PromptMessage, PromptMessageMultipart]) -> Optional[str]:
166
+ """
167
+ Get the first available text content from a message.
168
+
169
+ Args:
170
+ message: A PromptMessage or PromptMessageMultipart
171
+
172
+ Returns:
173
+ First text content or None if no text content exists
174
+ """
175
+ if isinstance(message, PromptMessage):
176
+ return get_text(message.content)
177
+
178
+ for content in message.content:
179
+ text = get_text(content)
180
+ if text is not None:
181
+ return text
182
+
183
+ return None
184
+
185
+ @staticmethod
186
+ def has_text_at_first_position(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
187
+ """
188
+ Check if a message has a TextContent at the first position.
189
+ This is a common case when dealing with messages that start with text.
190
+
191
+ Args:
192
+ message: A PromptMessage or PromptMessageMultipart
193
+
194
+ Returns:
195
+ True if the message has TextContent at first position, False otherwise
196
+ """
197
+ if isinstance(message, PromptMessage):
198
+ return isinstance(message.content, TextContent)
199
+
200
+ # For multipart messages, check if there's at least one item and the first is TextContent
201
+ return len(message.content) > 0 and isinstance(message.content[0], TextContent)
202
+
203
+ @staticmethod
204
+ def get_text_at_first_position(
205
+ message: Union[PromptMessage, PromptMessageMultipart],
206
+ ) -> Optional[str]:
207
+ """
208
+ Get the text from the first position of a message if it's TextContent.
209
+
210
+ Args:
211
+ message: A PromptMessage or PromptMessageMultipart
212
+
213
+ Returns:
214
+ The text content at the first position if it's TextContent,
215
+ None otherwise
216
+ """
217
+ if not MessageContent.has_text_at_first_position(message):
218
+ return None
219
+
220
+ if isinstance(message, PromptMessage):
221
+ return cast("TextContent", message.content).text
222
+
223
+ # Safe to cast since we've verified the first item is TextContent
224
+ return cast("TextContent", message.content[0]).text
225
+
226
+ @staticmethod
227
+ def get_all_images(message: Union[PromptMessage, PromptMessageMultipart]) -> List[str]:
228
+ """
229
+ Extract all image data from a message.
230
+
231
+ Args:
232
+ message: A PromptMessage or PromptMessageMultipart
233
+
234
+ Returns:
235
+ List of image data strings from all image content parts
236
+ """
237
+ if isinstance(message, PromptMessage):
238
+ img_data = get_image_data(message.content)
239
+ return [img_data] if img_data is not None else []
240
+
241
+ result = []
242
+ for content in message.content:
243
+ img_data = get_image_data(content)
244
+ if img_data is not None:
245
+ result.append(img_data)
246
+
247
+ return result
248
+
249
+ @staticmethod
250
+ def get_first_image(message: Union[PromptMessage, PromptMessageMultipart]) -> Optional[str]:
251
+ """
252
+ Get the first available image data from a message.
253
+
254
+ Args:
255
+ message: A PromptMessage or PromptMessageMultipart
256
+
257
+ Returns:
258
+ First image data or None if no image content exists
259
+ """
260
+ if isinstance(message, PromptMessage):
261
+ return get_image_data(message.content)
262
+
263
+ for content in message.content:
264
+ img_data = get_image_data(content)
265
+ if img_data is not None:
266
+ return img_data
267
+
268
+ return None
269
+
270
+ @staticmethod
271
+ def get_all_resources(
272
+ message: Union[PromptMessage, PromptMessageMultipart],
273
+ ) -> List[EmbeddedResource]:
274
+ """
275
+ Extract all embedded resources from a message.
276
+
277
+ Args:
278
+ message: A PromptMessage or PromptMessageMultipart
279
+
280
+ Returns:
281
+ List of EmbeddedResource objects
282
+ """
283
+ if isinstance(message, PromptMessage):
284
+ if isinstance(message.content, EmbeddedResource):
285
+ return [message.content]
286
+ return []
287
+
288
+ return [content for content in message.content if isinstance(content, EmbeddedResource)]
289
+
290
+ @staticmethod
291
+ def has_text(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
292
+ """
293
+ Check if the message has any text content.
294
+
295
+ Args:
296
+ message: A PromptMessage or PromptMessageMultipart
297
+
298
+ Returns:
299
+ True if the message has text content, False otherwise
300
+ """
301
+ return len(MessageContent.get_all_text(message)) > 0
302
+
303
+ @staticmethod
304
+ def has_images(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
305
+ """
306
+ Check if the message has any image content.
307
+
308
+ Args:
309
+ message: A PromptMessage or PromptMessageMultipart
310
+
311
+ Returns:
312
+ True if the message has image content, False otherwise
313
+ """
314
+ return len(MessageContent.get_all_images(message)) > 0
315
+
316
+ @staticmethod
317
+ def has_resources(message: Union[PromptMessage, PromptMessageMultipart]) -> bool:
318
+ """
319
+ Check if the message has any embedded resources.
320
+
321
+ Args:
322
+ message: A PromptMessage or PromptMessageMultipart
323
+
324
+ Returns:
325
+ True if the message has embedded resources, False otherwise
326
+ """
327
+ return len(MessageContent.get_all_resources(message)) > 0
@@ -0,0 +1,109 @@
1
+ from pathlib import Path
2
+ from typing import List, Literal
3
+
4
+ from mcp.server.fastmcp.prompts.base import (
5
+ AssistantMessage,
6
+ Message,
7
+ UserMessage,
8
+ )
9
+ from mcp.types import PromptMessage, TextContent
10
+
11
+ from mcp_agent.logging.logger import get_logger
12
+ from mcp_agent.mcp import mime_utils, resource_utils
13
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
14
+ from mcp_agent.mcp.prompts.prompt_template import (
15
+ PromptContent,
16
+ PromptTemplate,
17
+ PromptTemplateLoader,
18
+ )
19
+
20
+ # Define message role type
21
+ MessageRole = Literal["user", "assistant"]
22
+ logger = get_logger("prompt_load")
23
+
24
+
25
+ def cast_message_role(role: str) -> MessageRole:
26
+ """Cast a string role to a MessageRole literal type"""
27
+ if role == "user" or role == "assistant":
28
+ return role # type: ignore
29
+ # Default to user if the role is invalid
30
+ logger.warning(f"Invalid message role: {role}, defaulting to 'user'")
31
+ return "user"
32
+
33
+
34
+ def create_messages_with_resources(
35
+ content_sections: List[PromptContent], prompt_files: List[Path]
36
+ ) -> List[PromptMessage]:
37
+ """
38
+ Create a list of messages from content sections, with resources properly handled.
39
+
40
+ This implementation produces one message for each content section's text,
41
+ followed by separate messages for each resource (with the same role type
42
+ as the section they belong to).
43
+
44
+ Args:
45
+ content_sections: List of PromptContent objects
46
+ prompt_files: List of prompt files (to help locate resource files)
47
+
48
+ Returns:
49
+ List of Message objects
50
+ """
51
+
52
+ messages = []
53
+
54
+ for section in content_sections:
55
+ # Convert to our literal type for role
56
+ role = cast_message_role(section.role)
57
+
58
+ # Add the text message
59
+ messages.append(create_content_message(section.text, role))
60
+
61
+ # Add resource messages with the same role type as the section
62
+ for resource_path in section.resources:
63
+ try:
64
+ # Load resource with information about its type
65
+ resource_content, mime_type, is_binary = resource_utils.load_resource_content(
66
+ resource_path, prompt_files
67
+ )
68
+
69
+ # Create and add the resource message
70
+ resource_message = create_resource_message(
71
+ resource_path, resource_content, mime_type, is_binary, role
72
+ )
73
+ messages.append(resource_message)
74
+ except Exception as e:
75
+ logger.error(f"Error loading resource {resource_path}: {e}")
76
+
77
+ return messages
78
+
79
+
80
+ def create_content_message(text: str, role: MessageRole) -> PromptMessage:
81
+ """Create a text content message with the specified role"""
82
+ return PromptMessage(role=role, content=TextContent(type="text", text=text))
83
+
84
+
85
+ def create_resource_message(
86
+ resource_path: str, content: str, mime_type: str, is_binary: bool, role: MessageRole
87
+ ) -> Message:
88
+ """Create a resource message with the specified content and role"""
89
+ message_class = UserMessage if role == "user" else AssistantMessage
90
+
91
+ if mime_utils.is_image_mime_type(mime_type):
92
+ # For images, create an ImageContent
93
+ image_content = resource_utils.create_image_content(data=content, mime_type=mime_type)
94
+ return message_class(content=image_content)
95
+ else:
96
+ # For other resources, create an EmbeddedResource
97
+ embedded_resource = resource_utils.create_embedded_resource(
98
+ resource_path, content, mime_type, is_binary
99
+ )
100
+ return message_class(content=embedded_resource)
101
+
102
+
103
+ def load_prompt(file: Path) -> List[PromptMessage]:
104
+ template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
105
+ return create_messages_with_resources(template.content_sections, [file])
106
+
107
+
108
+ def load_prompt_multipart(file: Path) -> List[PromptMessageMultipart]:
109
+ return PromptMessageMultipart.to_multipart(load_prompt(file))