solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (172) hide show
  1. solace_agent_mesh/__init__.py +0 -3
  2. solace_agent_mesh/agents/__init__.py +0 -0
  3. solace_agent_mesh/agents/base_agent_component.py +224 -0
  4. solace_agent_mesh/agents/global/__init__.py +0 -0
  5. solace_agent_mesh/agents/global/actions/__init__.py +0 -0
  6. solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
  7. solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
  8. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
  9. solace_agent_mesh/agents/global/actions/create_file.py +70 -0
  10. solace_agent_mesh/agents/global/actions/error_action.py +45 -0
  11. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
  12. solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
  13. solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
  14. solace_agent_mesh/agents/global/global_agent_component.py +38 -0
  15. solace_agent_mesh/agents/image_processing/__init__.py +0 -0
  16. solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
  17. solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
  18. solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
  19. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
  20. solace_agent_mesh/agents/slack/__init__.py +1 -0
  21. solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
  22. solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
  23. solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
  24. solace_agent_mesh/agents/web_request/__init__.py +0 -0
  25. solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
  26. solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
  27. solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
  28. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
  29. solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
  30. solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
  31. solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
  32. solace_agent_mesh/cli/__init__.py +1 -0
  33. solace_agent_mesh/cli/commands/__init__.py +0 -0
  34. solace_agent_mesh/cli/commands/add/__init__.py +3 -0
  35. solace_agent_mesh/cli/commands/add/add.py +88 -0
  36. solace_agent_mesh/cli/commands/add/agent.py +110 -0
  37. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
  38. solace_agent_mesh/cli/commands/add/gateway.py +221 -0
  39. solace_agent_mesh/cli/commands/build.py +631 -0
  40. solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
  41. solace_agent_mesh/cli/commands/chat/chat.py +361 -0
  42. solace_agent_mesh/cli/commands/config.py +29 -0
  43. solace_agent_mesh/cli/commands/init/__init__.py +3 -0
  44. solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
  45. solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
  46. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
  47. solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
  48. solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
  49. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
  50. solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
  51. solace_agent_mesh/cli/commands/init/init.py +114 -0
  52. solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
  53. solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
  54. solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
  55. solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
  56. solace_agent_mesh/cli/commands/plugin/add.py +98 -0
  57. solace_agent_mesh/cli/commands/plugin/build.py +217 -0
  58. solace_agent_mesh/cli/commands/plugin/create.py +117 -0
  59. solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
  60. solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
  61. solace_agent_mesh/cli/commands/run.py +68 -0
  62. solace_agent_mesh/cli/commands/visualizer.py +138 -0
  63. solace_agent_mesh/cli/config.py +81 -0
  64. solace_agent_mesh/cli/main.py +306 -0
  65. solace_agent_mesh/cli/utils.py +246 -0
  66. solace_agent_mesh/common/__init__.py +0 -0
  67. solace_agent_mesh/common/action.py +91 -0
  68. solace_agent_mesh/common/action_list.py +37 -0
  69. solace_agent_mesh/common/action_response.py +327 -0
  70. solace_agent_mesh/common/constants.py +3 -0
  71. solace_agent_mesh/common/mysql_database.py +40 -0
  72. solace_agent_mesh/common/postgres_database.py +79 -0
  73. solace_agent_mesh/common/prompt_templates.py +30 -0
  74. solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
  75. solace_agent_mesh/common/stimulus_utils.py +152 -0
  76. solace_agent_mesh/common/time.py +24 -0
  77. solace_agent_mesh/common/utils.py +638 -0
  78. solace_agent_mesh/configs/agent_global.yaml +74 -0
  79. solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
  80. solace_agent_mesh/configs/agent_slack.yaml +64 -0
  81. solace_agent_mesh/configs/agent_web_request.yaml +75 -0
  82. solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
  83. solace_agent_mesh/configs/error_catcher.yaml +56 -0
  84. solace_agent_mesh/configs/monitor.yaml +0 -0
  85. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
  86. solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
  87. solace_agent_mesh/configs/orchestrator.yaml +241 -0
  88. solace_agent_mesh/configs/service_embedding.yaml +81 -0
  89. solace_agent_mesh/configs/service_llm.yaml +265 -0
  90. solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
  91. solace_agent_mesh/gateway/__init__.py +0 -0
  92. solace_agent_mesh/gateway/components/__init__.py +0 -0
  93. solace_agent_mesh/gateway/components/gateway_base.py +41 -0
  94. solace_agent_mesh/gateway/components/gateway_input.py +265 -0
  95. solace_agent_mesh/gateway/components/gateway_output.py +289 -0
  96. solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
  97. solace_agent_mesh/gateway/identity/identity_base.py +10 -0
  98. solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
  99. solace_agent_mesh/gateway/identity/no_identity.py +9 -0
  100. solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
  101. solace_agent_mesh/monitors/base_monitor_component.py +26 -0
  102. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
  103. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
  104. solace_agent_mesh/orchestrator/__init__.py +0 -0
  105. solace_agent_mesh/orchestrator/action_manager.py +225 -0
  106. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  107. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
  108. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
  109. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
  110. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
  111. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
  112. solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
  113. solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
  114. solace_agent_mesh/services/__init__.py +0 -0
  115. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
  116. solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
  117. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
  118. solace_agent_mesh/services/common/__init__.py +4 -0
  119. solace_agent_mesh/services/common/auto_expiry.py +45 -0
  120. solace_agent_mesh/services/common/singleton.py +18 -0
  121. solace_agent_mesh/services/file_service/__init__.py +14 -0
  122. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  123. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
  124. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
  125. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
  126. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
  127. solace_agent_mesh/services/file_service/file_service.py +432 -0
  128. solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
  129. solace_agent_mesh/services/file_service/file_transformations.py +131 -0
  130. solace_agent_mesh/services/file_service/file_utils.py +322 -0
  131. solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
  132. solace_agent_mesh/services/history_service/__init__.py +3 -0
  133. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  134. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
  135. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
  136. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
  137. solace_agent_mesh/services/history_service/history_service.py +139 -0
  138. solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
  139. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
  140. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  141. solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
  142. solace_agent_mesh/templates/action.py +38 -0
  143. solace_agent_mesh/templates/agent.py +29 -0
  144. solace_agent_mesh/templates/agent.yaml +70 -0
  145. solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
  146. solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
  147. solace_agent_mesh/templates/gateway-flows.yaml +81 -0
  148. solace_agent_mesh/templates/gateway-header.yaml +16 -0
  149. solace_agent_mesh/templates/gateway_base.py +15 -0
  150. solace_agent_mesh/templates/gateway_input.py +98 -0
  151. solace_agent_mesh/templates/gateway_output.py +71 -0
  152. solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
  153. solace_agent_mesh/templates/rest-api-default-config.yaml +23 -0
  154. solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
  155. solace_agent_mesh/templates/slack-default-config.yaml +9 -0
  156. solace_agent_mesh/templates/slack-flows.yaml +90 -0
  157. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
  158. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
  159. solace_agent_mesh/templates/web-default-config.yaml +5 -0
  160. solace_agent_mesh/templates/web-flows.yaml +86 -0
  161. solace_agent_mesh/tools/__init__.py +0 -0
  162. solace_agent_mesh/tools/components/__init__.py +0 -0
  163. solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
  164. solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
  165. solace_agent_mesh/tools/config/runtime_config.py +26 -0
  166. solace_agent_mesh-0.1.0.dist-info/METADATA +179 -0
  167. solace_agent_mesh-0.1.0.dist-info/RECORD +170 -0
  168. solace_agent_mesh-0.1.0.dist-info/entry_points.txt +3 -0
  169. solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.0.dist-info/licenses/LICENSE +1 -2
  170. solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
  171. solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
  172. {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,177 @@
1
+ """Slack Post Message action."""
2
+
3
+ from typing import Dict, List, Union
4
+
5
+ from solace_ai_connector.common.log import log
6
+ from ....common.action import Action
7
+ from ....common.time import FIVE_MINUTES
8
+ from ....common.action_response import ActionResponse
9
+ from ....services.file_service import FileService, FS_PROTOCOL
10
+
11
+
12
+ class PostMessage(Action):
13
+ """Action for posting messages to Slack channels."""
14
+
15
+ def __init__(self, **kwargs):
16
+ """Initialize the action with its configuration."""
17
+ super().__init__(
18
+ {
19
+ "name": "post_message",
20
+ "prompt_directive": "Post a message to a Slack channel",
21
+ "params": [
22
+ {
23
+ "name": "channel",
24
+ "desc": "The Slack channel to post to (include # prefix)",
25
+ "type": "string",
26
+ },
27
+ {
28
+ "name": "thread_correlation_id",
29
+ "desc": "Optional correlation ID to group messages in a thread",
30
+ "type": "string",
31
+ },
32
+ {
33
+ "name": "text",
34
+ "desc": "Text message to post",
35
+ "type": "string",
36
+ },
37
+ {
38
+ "name": "blocks",
39
+ "desc": "Slack blocks for formatted message",
40
+ "type": "array",
41
+ },
42
+ {
43
+ "name": "files",
44
+ "desc": f"Array of file URLs ({FS_PROTOCOL}:// or inline files with <file><data/></file> tags)",
45
+ "type": "array",
46
+ },
47
+ {
48
+ "name": "last_post_to_thread",
49
+ "desc": "If True, clears the thread correlation cache after posting",
50
+ "type": "bool",
51
+ },
52
+ ],
53
+ "required_scopes": ["slack:post_message:create"],
54
+ },
55
+ **kwargs,
56
+ )
57
+
58
+ def _process_files(
59
+ self, files: List[str], session_id: str
60
+ ) -> List[Dict[str, Union[str, bytes]]]:
61
+ """Process file URLs into uploadable content.
62
+
63
+ Args:
64
+ files: List of file URLs to process
65
+ session_id: Current session ID for file service
66
+
67
+ Returns:
68
+ List of dicts containing file data and titles
69
+ """
70
+ if not files:
71
+ return []
72
+
73
+ file_service = FileService()
74
+ processed_files = []
75
+
76
+ for file_url in files:
77
+ try:
78
+ # Download file content
79
+ content = file_service.download_to_buffer(file_url, session_id)
80
+
81
+ # Extract filename from URL
82
+ filename = file_url.split("/")[-1]
83
+ processed_files.append(
84
+ {
85
+ "content": content,
86
+ "title": filename,
87
+ }
88
+ )
89
+ except Exception as e:
90
+ log.error("Failed to process file %s: %s", file_url, str(e))
91
+
92
+ return processed_files
93
+
94
+ def invoke(self, params: Dict, meta: Dict = {}) -> ActionResponse:
95
+ """Post message to Slack channel.
96
+
97
+ Args:
98
+ params: Action parameters including channel, message, etc.
99
+ meta: Additional metadata including session_id
100
+
101
+ Returns:
102
+ ActionResponse with success/error message
103
+ """
104
+ try:
105
+ # Get Slack client from agent
106
+ slack_client = self.get_agent().slack_client
107
+
108
+ channel = params.get("channel", "").strip()
109
+
110
+ # Remove the # prefix if present
111
+ if channel.startswith("#"):
112
+ channel = channel[1:]
113
+
114
+ # Get thread_ts from correlation id if provided
115
+ thread_correlation_id = params.get("thread_correlation_id")
116
+ thread_ts = None
117
+ if thread_correlation_id:
118
+ cache_key = f"slack_agent:thread_correlation:{thread_correlation_id}"
119
+ thread_ts = self.get_agent().cache_service.get_data(cache_key)
120
+
121
+ # Prepare message arguments
122
+ msg_args = {
123
+ "channel": channel,
124
+ "text": params.get("text", ""),
125
+ "blocks": params.get("blocks"),
126
+ "thread_ts": thread_ts,
127
+ }
128
+
129
+ # Send message
130
+ response = slack_client.chat_postMessage(**msg_args)
131
+
132
+ # Handle thread correlation caching
133
+ if thread_correlation_id:
134
+ cache_key = f"slack_agent:thread_correlation:{thread_correlation_id}"
135
+
136
+ # Clear cache if this is the last post
137
+ if params.get("last_post_to_thread"):
138
+ self.get_agent().cache_service.remove_data(cache_key)
139
+ # Store thread_ts if this is a new thread
140
+ elif not thread_ts:
141
+ thread_cache_ttl = self.get_agent().get_config("thread_cache_ttl")
142
+ self.get_agent().cache_service.add_data(
143
+ cache_key,
144
+ response["ts"],
145
+ expiry=thread_cache_ttl,
146
+ component=self.get_agent(),
147
+ )
148
+ msg_args["thread_ts"] = response["ts"]
149
+
150
+ # Process any files
151
+ files = params.get("files", [])
152
+ if files:
153
+ processed_files = self._process_files(files, meta.get("session_id"))
154
+ for file_data in processed_files:
155
+ slack_client.files_upload_v2(
156
+ channel=channel,
157
+ content=file_data["content"],
158
+ title=file_data["title"],
159
+ thread_ts=msg_args["thread_ts"],
160
+ )
161
+
162
+ # Return thread_correlation_id in message for reference
163
+ return ActionResponse(
164
+ message=(
165
+ f"Successfully posted message to {channel}. "
166
+ f"Thread correlation ID: {thread_correlation_id or 'none'}"
167
+ + (
168
+ f" (thread_ts: {response['ts']})"
169
+ if not thread_ts
170
+ else f" (replied to existing thread)"
171
+ )
172
+ )
173
+ )
174
+
175
+ except Exception as e:
176
+ log.error("Failed to post Slack message: %s", str(e))
177
+ return ActionResponse(message=f"Failed to post message to Slack: {str(e)}")
@@ -0,0 +1,59 @@
1
+ """Slack agent for posting messages to Slack channels."""
2
+
3
+ import copy
4
+ from slack_sdk import WebClient
5
+
6
+ from ..base_agent_component import agent_info, BaseAgentComponent
7
+ from .actions.post_message import PostMessage
8
+ from ...common.time import FIVE_MINUTES
9
+
10
+ info = copy.deepcopy(agent_info)
11
+ info.update(
12
+ {
13
+ "agent_name": "slack",
14
+ "class_name": "SlackAgentComponent",
15
+ "description": "Slack messaging agent for posting messages and files to channels",
16
+ "config_parameters": [
17
+ {
18
+ "name": "slack_bot_token",
19
+ "required": True,
20
+ "description": "Slack bot user OAuth token",
21
+ "type": "string",
22
+ },
23
+ {
24
+ "name": "thread_cache_ttl",
25
+ "required": False,
26
+ "description": f"Time-to-live in seconds for thread correlation cache (default: {FIVE_MINUTES} seconds)",
27
+ "type": "integer",
28
+ "default": FIVE_MINUTES,
29
+ },
30
+ ],
31
+ }
32
+ )
33
+
34
+
35
+ class SlackAgentComponent(BaseAgentComponent):
36
+ """Component for handling Slack messaging operations."""
37
+
38
+ info = info
39
+ actions = [PostMessage]
40
+
41
+ def __init__(self, module_info={}, **kwargs):
42
+ """Initialize the Slack agent component.
43
+
44
+ Args:
45
+ module_info: Optional module configuration.
46
+ **kwargs: Additional keyword arguments.
47
+
48
+ Raises:
49
+ ValueError: If required Slack configuration is missing.
50
+ """
51
+ super().__init__(module_info, **kwargs)
52
+
53
+ # Get Slack configuration
54
+ self.slack_bot_token = self.get_config("slack_bot_token")
55
+ if not self.slack_bot_token:
56
+ raise ValueError("Slack bot token is required")
57
+
58
+ # Initialize Slack client
59
+ self.slack_client = WebClient(token=self.slack_bot_token)
File without changes
@@ -0,0 +1,84 @@
1
+ """Web Search action"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ import requests
5
+ from duckduckgo_search import DDGS
6
+
7
+
8
+ from ....common.action import Action
9
+ from ....common.action_response import ActionResponse
10
+ from ....services.file_service import FileService
11
+
12
+
13
+ class DoImageSearch(Action):
14
+ def __init__(self, **kwargs):
15
+ super().__init__(
16
+ {
17
+ "name": "do_image_search",
18
+ "prompt_directive": "Search the web for images using a search engine.",
19
+ "params": [
20
+ {
21
+ "name": "query",
22
+ "desc": "The search query to use",
23
+ "type": "string",
24
+ },
25
+ {
26
+ "name": "max_results",
27
+ "desc": "Maximum number of results to show, defaults to 3 if not present",
28
+ "type": "int",
29
+ },
30
+ {
31
+ "name": "size",
32
+ "desc": (
33
+ "Size of the image to search for. ",
34
+ "Can be one of: small, medium, large, wallpaper or None. Defaults to None if not present",
35
+ ),
36
+ "type": "string",
37
+ },
38
+ ],
39
+ "required_scopes": ["web_request:do_image_search:read"],
40
+ },
41
+ **kwargs,
42
+ )
43
+
44
+ def image_search(self, keyword, size, max_results) -> list:
45
+ """
46
+ Returns a list of dictionaries that represent image results
47
+ """
48
+ return DDGS().images(
49
+ keywords=keyword,
50
+ region="wt-wt",
51
+ safesearch="off",
52
+ size=str(size) or None,
53
+ color=None,
54
+ type_image=None,
55
+ layout=None,
56
+ license_image=None,
57
+ max_results=int(max_results),
58
+ )
59
+
60
+ def invoke(self, params, meta={}) -> ActionResponse:
61
+ query = params.get("query")
62
+ max_results = params.get("max_results", 3)
63
+ size = params.get("size")
64
+ results = []
65
+ if not query:
66
+ return ActionResponse(message="ERROR: Query not found!")
67
+ results = self.image_search(query, size, max_results) # append text results to results
68
+ file_service = FileService()
69
+ image_metas = []
70
+ for result in results:
71
+ try:
72
+ received_image = requests.get(result.get("image"), timeout=10).content
73
+ log.debug(f"Received image: {result.get('image')}")
74
+ image_metas.append(
75
+ file_service.upload_from_buffer(
76
+ received_image,
77
+ result.get("title"),
78
+ meta.get("session_id"),
79
+ data_source="Web Request Agent - Image Search Action",
80
+ )
81
+ )
82
+ except Exception as e:
83
+ log.error(f"Failed to download image: {result.get('image')} because {e}")
84
+ return ActionResponse(files=image_metas)
@@ -0,0 +1,47 @@
1
+ """Web Search action"""
2
+
3
+ from duckduckgo_search import DDGS
4
+
5
+
6
+ from ....common.action import Action
7
+ from ....common.action_response import ActionResponse
8
+
9
+
10
+ class DoNewsSearch(Action):
11
+ def __init__(self, **kwargs):
12
+ super().__init__(
13
+ {
14
+ "name": "do_news_search",
15
+ "prompt_directive": "Search the web for news and current events using a search engine.",
16
+ "params": [
17
+ {
18
+ "name": "query",
19
+ "desc": "The search query to use",
20
+ "type": "string",
21
+ },
22
+ {
23
+ "name": "max_results",
24
+ "desc": "Maximum number of results to show, defaults to 5 if not present",
25
+ "type": "int",
26
+ },
27
+ ],
28
+ "required_scopes": ["web_request:do_news_search:read"],
29
+ },
30
+ **kwargs,
31
+ )
32
+
33
+ def news_search(self, keyword, max_results) -> list:
34
+ """
35
+ Returns a list of dictionaries that represent news results
36
+ """
37
+ return DDGS().news(keywords=keyword, region="wt-wt", max_results=int(max_results))
38
+
39
+ def invoke(self, params, meta={}) -> ActionResponse:
40
+ query = params.get("query")
41
+ max_results = params.get("max_results", 5)
42
+ results = []
43
+ if not query:
44
+ return ActionResponse(message="ERROR: Query not found!")
45
+ results = self.news_search(query, max_results) # append text results to results
46
+
47
+ return ActionResponse(message=results)
@@ -0,0 +1,34 @@
1
+ """Web Request action"""
2
+
3
+ from duckduckgo_search import DDGS
4
+
5
+ from ....common.action import Action
6
+ from ....common.action_response import ActionResponse
7
+
8
+
9
+ class DoSuggestionSearch(Action):
10
+
11
+ def __init__(self, **kwargs):
12
+ super().__init__(
13
+ {
14
+ "name": "do_suggestion_search",
15
+ "prompt_directive": "Browse the web and get suggestions for the search query especially on new or unfamiliar topics.",
16
+ "params": [
17
+ {
18
+ "name": "query",
19
+ "desc": "The search query to use",
20
+ "type": "string",
21
+ }
22
+ ],
23
+ "required_scopes": ["web_request:do_suggestion_search:read"],
24
+ },
25
+ **kwargs,
26
+ )
27
+
28
+ def invoke(self, params, meta={}) -> ActionResponse:
29
+ query = params.get("query")
30
+ if not query:
31
+ return ActionResponse(message="ERROR: Query not found!")
32
+ content = DDGS().suggestions(query)
33
+
34
+ return ActionResponse(message=content)
@@ -0,0 +1,134 @@
1
+ """Web Request action"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ import requests
5
+ from bs4 import BeautifulSoup
6
+ import html2text
7
+
8
+
9
+ from ....common.action import Action
10
+ from ....common.action_response import ActionResponse
11
+
12
+
13
+ class DoWebRequest(Action):
14
+
15
+ def __init__(self, **kwargs):
16
+ super().__init__(
17
+ {
18
+ "name": "do_web_request",
19
+ "prompt_directive": "Fetch content from a URL and process it according to a specified LLM prompt. Returns processed content from a single web request. For comprehensive data gathering, multiple requests may be needed to follow relevant links.",
20
+ "params": [
21
+ {
22
+ "name": "url",
23
+ "desc": "URL to fetch",
24
+ "type": "string",
25
+ },
26
+ {
27
+ "name": "llm_prompt",
28
+ "desc": "Text prompt to direct the LLM on how to process the fetched content. If this parameter is not provided, the fetched content will be returned as is.",
29
+ "type": "string",
30
+ },
31
+ ],
32
+ "required_scopes": ["web_request:do_web_request:read"],
33
+ "examples": [
34
+ """ <example>
35
+ <example_docstring>
36
+ This is an example of a user requesting to fetch information from the web. The web_request agent is open so invoke the do_web_request action to fetch the content from the url and process the information according to the llm_prompt.
37
+ </example_docstring>
38
+ <example_stimulus>
39
+ <{tp}stimulus starting_id="10"/>
40
+ What is the weather in Ottawa?
41
+ </{tp}stimulus>
42
+ <{tp}stimulus_metadata>
43
+ local_time: 2024-11-06 12:33:12 EST-0500 (Wednesday)
44
+ </{tp}stimulus_metadata>
45
+ </example_stimulus>
46
+ <example_response>
47
+ <{tp}reasoning>
48
+ - User is asking for current weather information in Ottawa
49
+ - We need to fetch up-to-date weather data
50
+ - Use the web_request agent to get the latest weather information
51
+ - Plan to use the Environment Canada website for accurate local weather data
52
+ </{tp}reasoning>
53
+
54
+ Certainly! I\'ll fetch the current weather information for Ottawa for you right away.
55
+
56
+ <{tp}invoke_action agent="web_request" action="do_web_request">
57
+ <{tp}parameter name="url">https://weather.gc.ca/city/pages/on-118_metric_e.html</{tp}parameter>
58
+ <{tp}parameter name="llm_prompt">Extract the current temperature, weather conditions, and any important weather alerts or warnings for Ottawa from the webpage. Format the response as a bulleted list with emoji where appropriate.</{tp}parameter>
59
+ </{tp}invoke_action>
60
+
61
+ <{tp}status_update>Retrieving the latest weather data for Ottawa...</{tp}status_update>'
62
+ </example_response>
63
+ </example>
64
+ """
65
+ ],
66
+ },
67
+ **kwargs,
68
+ )
69
+
70
+ def get_system_prompt(self):
71
+ return """
72
+ The assistant is a professional web researcher. It will take the URL and request below and
73
+ will diligently extract the requested information from that URL. Sometimes the page does
74
+ not have the information requested, but it might be on a different page that is linked to
75
+ from this page. In that case the assistant will indicate the page doesn't have the information
76
+ but it will provide other links to try for the information.
77
+
78
+ The assistant will also include a section of 'interesting links' that it found on the page
79
+ that might be useful for further research. These links must be from the page and not from
80
+ the assistant's own knowledge.
81
+
82
+ The assistant will respond in markdown format.
83
+ """
84
+
85
+ def get_user_prompt(self, url, content, prompt):
86
+ return f"""
87
+ <page_url>{url}</page_url>
88
+ <research_request>
89
+ {prompt}
90
+ </research_request>
91
+ <page_content_markdown>
92
+ {content}
93
+ </page_content_markdown>
94
+ """
95
+
96
+ def invoke(self, params, meta={}) -> ActionResponse:
97
+ url = params.get("url")
98
+ prompt = params.get("llm_prompt")
99
+
100
+ if not url:
101
+ return ActionResponse(message="URL is required")
102
+
103
+ # Fetch the content from the URL
104
+ try:
105
+ response = requests.get(url, timeout=30)
106
+ response.raise_for_status()
107
+ except Exception as e:
108
+ return ActionResponse(message=f"Failed to fetch content from URL: {e}")
109
+
110
+ # Use beautiful soup to extract the text content from the HTML
111
+ soup = BeautifulSoup(response.content, "html.parser")
112
+
113
+ # Convert the HTML content to Markdown using html2text
114
+ h = html2text.HTML2Text()
115
+ h.ignore_links = False # Ensure that links are preserved
116
+ h.ignore_images = False # Ignore images
117
+ content = h.handle(str(soup))
118
+
119
+ # If a prompt is provided, send the content to an LLM model
120
+ if prompt:
121
+ system_prompt = self.get_system_prompt()
122
+ user_prompt = self.get_user_prompt(url, content, prompt)
123
+
124
+ messages = [
125
+ {"role": "system", "content": system_prompt},
126
+ {"role": "user", "content": user_prompt},
127
+ ]
128
+
129
+ agent = self.get_agent()
130
+ response = agent.do_llm_service_request(messages=messages)
131
+ content = response.get("content")
132
+
133
+ # Code to create the image using the provided content
134
+ return ActionResponse(message=content)
@@ -0,0 +1,69 @@
1
+ """File Request action"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ import requests
5
+ from io import BytesIO
6
+
7
+
8
+ from ....common.action import Action
9
+ from ....common.action_response import ActionResponse
10
+ from ....services.file_service import FileService, FS_PROTOCOL
11
+
12
+
13
+ class DownloadFile(Action):
14
+
15
+ def __init__(self, **kwargs):
16
+ super().__init__(
17
+ {
18
+ "name": "download_external_file",
19
+ "prompt_directive": f"Given a http/https external URL of a file (image, video, etc.), fetch the content from the URL and add the file to the {FS_PROTOCOL} file service so it can be used in the system.",
20
+ "params": [
21
+ {
22
+ "name": "url",
23
+ "desc": "URL of file",
24
+ "type": "string",
25
+ },
26
+ {
27
+ "name": "local_file_name",
28
+ "desc": "Local file name to use for the file - make sure it has the correct extension",
29
+ "type": "string",
30
+ },
31
+ ],
32
+ "required_scopes": ["web_request:download_file:write"],
33
+ },
34
+ **kwargs,
35
+ )
36
+
37
+ def invoke(self, params, meta={}) -> ActionResponse:
38
+ url = params.get("url")
39
+ local_file_name = params.get("local_file_name")
40
+
41
+ if not url:
42
+ return ActionResponse(message="URL is required")
43
+
44
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"}
45
+
46
+ # Fetch the file from the URL and write it in chunks
47
+ files = []
48
+ try:
49
+ with requests.get(url, headers=headers, stream=True, timeout=30) as file_response:
50
+ file_response.raise_for_status()
51
+ byte_io = BytesIO()
52
+ for chunk in file_response.iter_content(chunk_size=8192):
53
+ if chunk: # Filter out keep-alive new chunks
54
+ byte_io.write(chunk)
55
+ byte_io.seek(0)
56
+ file_service = FileService()
57
+ meta = file_service.upload_from_buffer(
58
+ byte_io.read(),
59
+ local_file_name,
60
+ meta.get("session_id"),
61
+ data_source="Web Request Agent - Download File Action",
62
+ )
63
+ files.append(meta)
64
+ except requests.exceptions.HTTPError as e:
65
+ return ActionResponse(message=f"Failed to fetch file from URL: {e}")
66
+ except Exception as e:
67
+ return ActionResponse(message=f"An error occurred: {e}")
68
+
69
+ return ActionResponse(files=files)
@@ -0,0 +1,33 @@
1
+ """Responsible for processing the actions from the Web Request Agent"""
2
+
3
+ import copy
4
+
5
+ from ..base_agent_component import (
6
+ agent_info,
7
+ BaseAgentComponent,
8
+ )
9
+
10
+ from .actions.do_web_request import DoWebRequest
11
+ from .actions.download_file import DownloadFile
12
+ from .actions.do_image_search import DoImageSearch
13
+ from .actions.do_news_search import DoNewsSearch
14
+ from .actions.do_suggestion_search import DoSuggestionSearch
15
+
16
+ info = copy.deepcopy(agent_info)
17
+ info["agent_name"] = "web_request"
18
+ info["class_name"] = "WebRequestAgentComponent"
19
+ info["description"] = (
20
+ "Web request agent that is able to download and retrieve info from urls. ",
21
+ "It can also search the web for news, images and suggestion results with the DuckDuckGo search engine.",
22
+ )
23
+
24
+
25
+ class WebRequestAgentComponent(BaseAgentComponent):
26
+ info = info
27
+ actions = [
28
+ DoWebRequest,
29
+ DownloadFile,
30
+ DoImageSearch,
31
+ DoNewsSearch,
32
+ DoSuggestionSearch,
33
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,3 @@
1
+ from .add import add_command
2
+
3
+ __all__ = ["add_command"]