solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.1__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.
- solace_agent_mesh/__init__.py +0 -3
- solace_agent_mesh/agents/__init__.py +0 -0
- solace_agent_mesh/agents/base_agent_component.py +224 -0
- solace_agent_mesh/agents/global/__init__.py +0 -0
- solace_agent_mesh/agents/global/actions/__init__.py +0 -0
- solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
- solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
- solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
- solace_agent_mesh/agents/global/actions/create_file.py +70 -0
- solace_agent_mesh/agents/global/actions/error_action.py +45 -0
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
- solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
- solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
- solace_agent_mesh/agents/global/global_agent_component.py +38 -0
- solace_agent_mesh/agents/image_processing/__init__.py +0 -0
- solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
- solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
- solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
- solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
- solace_agent_mesh/agents/slack/__init__.py +1 -0
- solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
- solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
- solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
- solace_agent_mesh/agents/web_request/__init__.py +0 -0
- solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
- solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
- solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
- solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
- solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
- solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
- solace_agent_mesh/assets/web-visualizer/assets/index-C5awueeJ.js +109 -0
- solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +1 -0
- solace_agent_mesh/assets/web-visualizer/index.html +14 -0
- solace_agent_mesh/assets/web-visualizer/vite.svg +1 -0
- solace_agent_mesh/cli/__init__.py +1 -0
- solace_agent_mesh/cli/commands/__init__.py +0 -0
- solace_agent_mesh/cli/commands/add/__init__.py +3 -0
- solace_agent_mesh/cli/commands/add/add.py +88 -0
- solace_agent_mesh/cli/commands/add/agent.py +110 -0
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
- solace_agent_mesh/cli/commands/add/gateway.py +221 -0
- solace_agent_mesh/cli/commands/build.py +631 -0
- solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
- solace_agent_mesh/cli/commands/chat/chat.py +361 -0
- solace_agent_mesh/cli/commands/config.py +29 -0
- solace_agent_mesh/cli/commands/init/__init__.py +3 -0
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
- solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
- solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
- solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
- solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
- solace_agent_mesh/cli/commands/init/init.py +114 -0
- solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
- solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
- solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
- solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
- solace_agent_mesh/cli/commands/plugin/add.py +98 -0
- solace_agent_mesh/cli/commands/plugin/build.py +217 -0
- solace_agent_mesh/cli/commands/plugin/create.py +117 -0
- solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
- solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
- solace_agent_mesh/cli/commands/run.py +68 -0
- solace_agent_mesh/cli/commands/visualizer.py +138 -0
- solace_agent_mesh/cli/config.py +81 -0
- solace_agent_mesh/cli/main.py +306 -0
- solace_agent_mesh/cli/utils.py +246 -0
- solace_agent_mesh/common/__init__.py +0 -0
- solace_agent_mesh/common/action.py +91 -0
- solace_agent_mesh/common/action_list.py +37 -0
- solace_agent_mesh/common/action_response.py +327 -0
- solace_agent_mesh/common/constants.py +3 -0
- solace_agent_mesh/common/mysql_database.py +40 -0
- solace_agent_mesh/common/postgres_database.py +79 -0
- solace_agent_mesh/common/prompt_templates.py +30 -0
- solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
- solace_agent_mesh/common/stimulus_utils.py +152 -0
- solace_agent_mesh/common/time.py +24 -0
- solace_agent_mesh/common/utils.py +638 -0
- solace_agent_mesh/configs/agent_global.yaml +74 -0
- solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
- solace_agent_mesh/configs/agent_slack.yaml +64 -0
- solace_agent_mesh/configs/agent_web_request.yaml +75 -0
- solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
- solace_agent_mesh/configs/error_catcher.yaml +56 -0
- solace_agent_mesh/configs/monitor.yaml +0 -0
- solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
- solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
- solace_agent_mesh/configs/orchestrator.yaml +241 -0
- solace_agent_mesh/configs/service_embedding.yaml +81 -0
- solace_agent_mesh/configs/service_llm.yaml +265 -0
- solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
- solace_agent_mesh/gateway/__init__.py +0 -0
- solace_agent_mesh/gateway/components/__init__.py +0 -0
- solace_agent_mesh/gateway/components/gateway_base.py +41 -0
- solace_agent_mesh/gateway/components/gateway_input.py +265 -0
- solace_agent_mesh/gateway/components/gateway_output.py +289 -0
- solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
- solace_agent_mesh/gateway/identity/identity_base.py +10 -0
- solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
- solace_agent_mesh/gateway/identity/no_identity.py +9 -0
- solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
- solace_agent_mesh/monitors/base_monitor_component.py +26 -0
- solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
- solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
- solace_agent_mesh/orchestrator/__init__.py +0 -0
- solace_agent_mesh/orchestrator/action_manager.py +225 -0
- solace_agent_mesh/orchestrator/components/__init__.py +0 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
- solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
- solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
- solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
- solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
- solace_agent_mesh/services/common/__init__.py +4 -0
- solace_agent_mesh/services/common/auto_expiry.py +45 -0
- solace_agent_mesh/services/common/singleton.py +18 -0
- solace_agent_mesh/services/file_service/__init__.py +14 -0
- solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
- solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
- solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
- solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
- solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
- solace_agent_mesh/services/file_service/file_service.py +432 -0
- solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
- solace_agent_mesh/services/file_service/file_transformations.py +131 -0
- solace_agent_mesh/services/file_service/file_utils.py +322 -0
- solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
- solace_agent_mesh/services/history_service/__init__.py +3 -0
- solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
- solace_agent_mesh/services/history_service/history_service.py +139 -0
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
- solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
- solace_agent_mesh/services/middleware_service/__init__.py +0 -0
- solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
- solace_agent_mesh/templates/action.py +38 -0
- solace_agent_mesh/templates/agent.py +29 -0
- solace_agent_mesh/templates/agent.yaml +70 -0
- solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
- solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
- solace_agent_mesh/templates/gateway-flows.yaml +81 -0
- solace_agent_mesh/templates/gateway-header.yaml +16 -0
- solace_agent_mesh/templates/gateway_base.py +15 -0
- solace_agent_mesh/templates/gateway_input.py +98 -0
- solace_agent_mesh/templates/gateway_output.py +71 -0
- solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
- solace_agent_mesh/templates/rest-api-default-config.yaml +24 -0
- solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
- solace_agent_mesh/templates/slack-default-config.yaml +9 -0
- solace_agent_mesh/templates/slack-flows.yaml +90 -0
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
- solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
- solace_agent_mesh/templates/web-default-config.yaml +5 -0
- solace_agent_mesh/templates/web-flows.yaml +86 -0
- solace_agent_mesh/tools/__init__.py +0 -0
- solace_agent_mesh/tools/components/__init__.py +0 -0
- solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
- solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
- solace_agent_mesh/tools/config/runtime_config.py +26 -0
- solace_agent_mesh-0.1.1.dist-info/METADATA +179 -0
- solace_agent_mesh-0.1.1.dist-info/RECORD +174 -0
- solace_agent_mesh-0.1.1.dist-info/entry_points.txt +3 -0
- solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.1.dist-info/licenses/LICENSE +1 -2
- solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
- solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
- {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.1.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
|
|
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
|
+
]
|