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,265 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import base64
|
|
3
|
+
import json
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from solace_ai_connector.common.message import Message
|
|
7
|
+
from solace_ai_connector.common.log import log
|
|
8
|
+
from ...services.file_service import FileService
|
|
9
|
+
from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
|
|
10
|
+
from .gateway_base import GatewayBase
|
|
11
|
+
|
|
12
|
+
info = {
|
|
13
|
+
"class_name": "GatewayInput",
|
|
14
|
+
"description": (
|
|
15
|
+
"This component handles requests from users and forms the "
|
|
16
|
+
"appropriate prompt for the next component in the flow."
|
|
17
|
+
),
|
|
18
|
+
"config_parameters": [
|
|
19
|
+
{
|
|
20
|
+
"name": "gateway_config",
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"gateway_id": {"type": "string"},
|
|
24
|
+
"system_purpose": {"type": "string"},
|
|
25
|
+
"interaction_type": {"type": "string"},
|
|
26
|
+
"history_class": {"type": "string"},
|
|
27
|
+
"history_config": {"type": "object"},
|
|
28
|
+
"identity_key_field": {"type": "string", "default": DEFAULT_IDENTITY_KEY_FIELD},
|
|
29
|
+
"identity": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"type": {"type": "string"},
|
|
33
|
+
"module": {"type": "string"},
|
|
34
|
+
"configuration": {"type": "object"},
|
|
35
|
+
},
|
|
36
|
+
"description": "Identity component configuration including module and class names.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
"description": "Gateway configuration including originators and their configurations.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
"input_schema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"event": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"text": {"type": "string"},
|
|
49
|
+
"files": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"name": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
},
|
|
57
|
+
"content": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
},
|
|
60
|
+
"mime_type": {"type": "string"},
|
|
61
|
+
"url": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
},
|
|
64
|
+
"size": {
|
|
65
|
+
"type": "number",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
"required": ["event"],
|
|
74
|
+
},
|
|
75
|
+
"output_schema": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"files": {
|
|
79
|
+
"type": "array",
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {
|
|
83
|
+
"name": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
},
|
|
86
|
+
"content": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
},
|
|
89
|
+
"mime_type": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
},
|
|
92
|
+
"url": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
},
|
|
95
|
+
"file_size": {
|
|
96
|
+
"type": "number",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
"text": {"type": "string"},
|
|
102
|
+
"interface_properties": {"type": "object"},
|
|
103
|
+
"history": {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"items": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"role": {"type": "string"},
|
|
109
|
+
"content": {"type": "string"},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
"required": ["session_id", "clear_history", "messages"],
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
DEFAULT_SYSTEM_PURPOSE = "You are participating in the chat bot framework."
|
|
119
|
+
DEFAULT_INTERACTION_TYPE = "interactive" # vs "autonomous"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class GatewayInput(GatewayBase):
|
|
123
|
+
"""This component handles incoming stimuli and prepares the context for processing."""
|
|
124
|
+
|
|
125
|
+
def __init__(self, **kwargs):
|
|
126
|
+
super().__init__(info, **kwargs)
|
|
127
|
+
self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE)
|
|
128
|
+
self.interaction_type = self.get_config(
|
|
129
|
+
"interaction_type", DEFAULT_INTERACTION_TYPE
|
|
130
|
+
)
|
|
131
|
+
self.identity_component = self._initialize_identity_component()
|
|
132
|
+
|
|
133
|
+
def _authenticate_user(self, _user_properties: Dict[str, Any]) -> bool:
|
|
134
|
+
# Implement actual authentication logic here
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
def invoke(self, message: Message, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
138
|
+
user_properties = message.get_user_properties() or {}
|
|
139
|
+
copied_data = data.copy()
|
|
140
|
+
|
|
141
|
+
errors = []
|
|
142
|
+
available_files = []
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
if not self._authenticate_user(user_properties):
|
|
146
|
+
log.error("User authentication failed")
|
|
147
|
+
raise PermissionError("User authentication failed")
|
|
148
|
+
|
|
149
|
+
# Get identity info
|
|
150
|
+
identity_field = self.identity_component.get_identity_field()
|
|
151
|
+
identity_value = user_properties.get(identity_field)
|
|
152
|
+
if not identity_value:
|
|
153
|
+
log.error("Identity field '%s' not found", identity_field)
|
|
154
|
+
raise ValueError(f"Identity field '{identity_field}' not found")
|
|
155
|
+
|
|
156
|
+
user_info = self.identity_component.get_user_info(identity_value)
|
|
157
|
+
|
|
158
|
+
session_id = user_properties.get("session_id")
|
|
159
|
+
|
|
160
|
+
# De-clutter the data and user_properties by moving some properties to a nested sub-object
|
|
161
|
+
top_level_data_properties = {"text", "files"}
|
|
162
|
+
self.demote_interface_properties(copied_data, top_level_data_properties)
|
|
163
|
+
|
|
164
|
+
top_level_user_properties = {
|
|
165
|
+
"input_type",
|
|
166
|
+
"session_id",
|
|
167
|
+
"response_format_prompt",
|
|
168
|
+
}
|
|
169
|
+
self.demote_interface_properties(user_properties, top_level_user_properties)
|
|
170
|
+
|
|
171
|
+
prompt = data.get("text", "")
|
|
172
|
+
files = data.get("files", [])
|
|
173
|
+
|
|
174
|
+
attached_files = []
|
|
175
|
+
if len(files) > 0:
|
|
176
|
+
file_service = FileService()
|
|
177
|
+
for file in files:
|
|
178
|
+
content = file["content"]
|
|
179
|
+
if type(content) == str:
|
|
180
|
+
try:
|
|
181
|
+
byte_buffer = base64.b64decode(content)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
byte_buffer = content.encode("utf-8")
|
|
184
|
+
elif type(content) == bytes:
|
|
185
|
+
byte_buffer = content
|
|
186
|
+
else:
|
|
187
|
+
log.error(
|
|
188
|
+
"Invalid content type for file %s: %s",
|
|
189
|
+
file["name"],
|
|
190
|
+
type(content),
|
|
191
|
+
)
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
file_metadata = file_service.upload_from_buffer(
|
|
195
|
+
byte_buffer,
|
|
196
|
+
file["name"],
|
|
197
|
+
session_id,
|
|
198
|
+
file_size=file["size"],
|
|
199
|
+
data_source=f"User provided file - {self.gateway_id} Gateway",
|
|
200
|
+
)
|
|
201
|
+
attached_files.append(file_metadata)
|
|
202
|
+
copied_data["files"] = attached_files
|
|
203
|
+
|
|
204
|
+
copied_data["history"] = []
|
|
205
|
+
if self.use_history:
|
|
206
|
+
prompt = data.get("text", "")
|
|
207
|
+
self.history_instance.store_history(session_id, "user", prompt)
|
|
208
|
+
|
|
209
|
+
for file in attached_files:
|
|
210
|
+
self.history_instance.store_file(session_id, file)
|
|
211
|
+
|
|
212
|
+
# retrieve all files for the session
|
|
213
|
+
available_files = self.history_instance.get_files(session_id)
|
|
214
|
+
|
|
215
|
+
# Add history to the data
|
|
216
|
+
copied_data["history"] = self.history_instance.get_history(session_id)
|
|
217
|
+
|
|
218
|
+
available_files = json.dumps(available_files)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
log.error("Error processing input: %s", e)
|
|
221
|
+
errors.append(str(e))
|
|
222
|
+
|
|
223
|
+
stimulus_uuid = self.gateway_id + str(uuid4())
|
|
224
|
+
|
|
225
|
+
user_properties.update(
|
|
226
|
+
{
|
|
227
|
+
"gateway_id": self.gateway_id,
|
|
228
|
+
"system_purpose": self.system_purpose,
|
|
229
|
+
"interaction_type": self.interaction_type,
|
|
230
|
+
"available_files": available_files,
|
|
231
|
+
"stimulus_uuid": stimulus_uuid,
|
|
232
|
+
"user_info": user_info,
|
|
233
|
+
"identity": identity_value,
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
copied_data["user_info"] = user_info
|
|
237
|
+
copied_data["errors"] = errors
|
|
238
|
+
message.set_user_properties(user_properties)
|
|
239
|
+
message.set_payload(copied_data)
|
|
240
|
+
|
|
241
|
+
return copied_data
|
|
242
|
+
|
|
243
|
+
def demote_interface_properties(
|
|
244
|
+
self, dict_properties: Dict[str, Any], top_level_properties: set
|
|
245
|
+
):
|
|
246
|
+
"""
|
|
247
|
+
Demotes specified properties from the top level of a dictionary to a nested dictionary under the key 'interface_properties'.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
dict_properties (Dict[str, Any]): The dictionary containing the properties.
|
|
251
|
+
top_level_properties (set): A set of property names that should remain at the top level.
|
|
252
|
+
|
|
253
|
+
Modifies:
|
|
254
|
+
dict_properties: Adds a new key 'interface_properties' containing the properties that are not in top_level_properties.
|
|
255
|
+
Removes the demoted properties from the top level of dict_properties.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
interface_properties = {
|
|
259
|
+
k: v for k, v in dict_properties.items() if k not in top_level_properties
|
|
260
|
+
}
|
|
261
|
+
dict_properties["interface_properties"] = interface_properties
|
|
262
|
+
|
|
263
|
+
# Remove keys from user_properties that are in interface_user_properties
|
|
264
|
+
for key in interface_properties:
|
|
265
|
+
dict_properties.pop(key, None)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from solace_ai_connector.common.message import Message
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
|
|
5
|
+
from .gateway_base import GatewayBase
|
|
6
|
+
from ...services.file_service import FileService
|
|
7
|
+
from ...common.utils import files_to_block_text
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
info = {
|
|
11
|
+
"class_name": "GatewayOutput",
|
|
12
|
+
"description": (
|
|
13
|
+
"This component handles stimuli from users and forms the "
|
|
14
|
+
"appropriate prompt for the next component in the flow."
|
|
15
|
+
),
|
|
16
|
+
"config_parameters": [
|
|
17
|
+
{
|
|
18
|
+
"name": "gateway_config",
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"gateway_id": {"type": "string"},
|
|
22
|
+
"system_purpose": {"type": "string"},
|
|
23
|
+
"interaction_type": {"type": "string"},
|
|
24
|
+
"default_originator_scopes": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {"type": "string"},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
"description": "Gateway configuration including originators and their configurations.",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
"input_schema": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"event": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"text": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
},
|
|
41
|
+
"files": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"name": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
},
|
|
49
|
+
"data": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
},
|
|
52
|
+
"mime_type": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
},
|
|
55
|
+
"url": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
},
|
|
58
|
+
"file_size": {
|
|
59
|
+
"type": "number",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"first_chunk": {
|
|
65
|
+
"type": "boolean",
|
|
66
|
+
},
|
|
67
|
+
"last_chunk": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
},
|
|
70
|
+
"uuid": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
},
|
|
73
|
+
"chunk": {
|
|
74
|
+
"type": "text",
|
|
75
|
+
},
|
|
76
|
+
"status_update": {
|
|
77
|
+
"type": "boolean",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"output_schema": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"event": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"text": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
},
|
|
92
|
+
"files": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"name": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
},
|
|
100
|
+
"data": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
},
|
|
103
|
+
"mime_type": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
},
|
|
106
|
+
"url": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
},
|
|
109
|
+
"file_size": {
|
|
110
|
+
"type": "number",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
"first_chunk": {
|
|
116
|
+
"type": "boolean",
|
|
117
|
+
},
|
|
118
|
+
"last_chunk": {
|
|
119
|
+
"type": "boolean",
|
|
120
|
+
},
|
|
121
|
+
"uuid": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
},
|
|
124
|
+
"chunk": {
|
|
125
|
+
"type": "text",
|
|
126
|
+
},
|
|
127
|
+
"status_update": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class GatewayOutput(GatewayBase):
|
|
138
|
+
"""This is the component that handles incoming stimuli"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, **kwargs):
|
|
141
|
+
super().__init__(info, **kwargs)
|
|
142
|
+
self.default_agent_scopes = self.get_config("default_agent_scopes", [])
|
|
143
|
+
self.originators = self.get_config("originators", [])
|
|
144
|
+
|
|
145
|
+
def _resolve_text_content(self, data: dict, session_id: str) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Resolve all AMFS URLs in text and chunk fields of the data dictionary.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
data (dict): The data dictionary containing text and chunk fields
|
|
151
|
+
session_id (str): The session ID for file service operations
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
None: Modifies the data dictionary in place
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
file_service = FileService()
|
|
158
|
+
text_output_response = data.get("text", "") or ""
|
|
159
|
+
data["text"] = file_service.resolve_all_resolvable_urls(
|
|
160
|
+
text_output_response, session_id
|
|
161
|
+
)
|
|
162
|
+
chunk_output_response = data.get("chunk", "") or ""
|
|
163
|
+
data["chunk"] = file_service.resolve_all_resolvable_urls(
|
|
164
|
+
chunk_output_response, session_id
|
|
165
|
+
)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
log.error(f"Failed to resolve URLs in text: {e}")
|
|
168
|
+
|
|
169
|
+
def invoke(self, message: Message, data) -> Message:
|
|
170
|
+
file_service = FileService()
|
|
171
|
+
user_properties = message.get_user_properties()
|
|
172
|
+
session_id = user_properties.get("session_id")
|
|
173
|
+
files = data.get("files", [])
|
|
174
|
+
|
|
175
|
+
# Extract the interface queue ID
|
|
176
|
+
interface_properties = user_properties.get("interface_properties", [])
|
|
177
|
+
server_input_id = (
|
|
178
|
+
interface_properties and interface_properties.get("server_input_id", "")
|
|
179
|
+
if interface_properties
|
|
180
|
+
else ""
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if self.use_history and session_id:
|
|
184
|
+
topic = message.get_topic()
|
|
185
|
+
content = data.get("text") or ""
|
|
186
|
+
|
|
187
|
+
file_text_blocks = files_to_block_text(files)
|
|
188
|
+
if file_text_blocks:
|
|
189
|
+
content += file_text_blocks
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
"/streamingResponse/" in topic
|
|
193
|
+
and data.get("last_chunk")
|
|
194
|
+
and "text" in data
|
|
195
|
+
):
|
|
196
|
+
if content:
|
|
197
|
+
self.history_instance.store_history(
|
|
198
|
+
session_id, "assistant", content
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
for file in files:
|
|
202
|
+
self.history_instance.store_history(
|
|
203
|
+
session_id, "assistant", f'\n[Returned file: {{name: {file.get("name")}, url: {file.get("url")}}}]\n'
|
|
204
|
+
)
|
|
205
|
+
self.history_instance.store_file(session_id, file)
|
|
206
|
+
|
|
207
|
+
clear_history_tuple = user_properties.get("clear_gateway_history", [])
|
|
208
|
+
if clear_history_tuple and clear_history_tuple[0]:
|
|
209
|
+
keep_depth = clear_history_tuple[1]
|
|
210
|
+
self.history_instance.clear_history(session_id, keep_depth)
|
|
211
|
+
|
|
212
|
+
if files:
|
|
213
|
+
downloaded_files = []
|
|
214
|
+
for file in files:
|
|
215
|
+
output_file = {
|
|
216
|
+
"name": file.get("name"),
|
|
217
|
+
}
|
|
218
|
+
# inline file
|
|
219
|
+
if file.get("data"):
|
|
220
|
+
data_content = file_service.resolve_all_resolvable_urls(
|
|
221
|
+
file.get("data"), session_id
|
|
222
|
+
)
|
|
223
|
+
inline_data = base64.b64encode(data_content.encode()).decode()
|
|
224
|
+
output_file["content"] = inline_data
|
|
225
|
+
output_file["mime_type"] = file.get("mime_type", "text/plain")
|
|
226
|
+
elif file.get("url"):
|
|
227
|
+
url = file.get("url")
|
|
228
|
+
try:
|
|
229
|
+
resolved_content = file_service.resolve_url(url, session_id)
|
|
230
|
+
buffer_content = (
|
|
231
|
+
resolved_content
|
|
232
|
+
if type(resolved_content) == bytes
|
|
233
|
+
else resolved_content.encode()
|
|
234
|
+
)
|
|
235
|
+
output_file["content"] = base64.b64encode(
|
|
236
|
+
buffer_content
|
|
237
|
+
).decode("utf-8")
|
|
238
|
+
|
|
239
|
+
# If the file name or mime type is not provided, try to get it from the resolved URL
|
|
240
|
+
if not output_file.get("name") or not output_file.get(
|
|
241
|
+
"mime_type"
|
|
242
|
+
):
|
|
243
|
+
metadata = file_service.get_metadata(url)
|
|
244
|
+
output_file["name"] = output_file.get(
|
|
245
|
+
"name"
|
|
246
|
+
) or metadata.get("name")
|
|
247
|
+
output_file["mime_type"] = output_file.get(
|
|
248
|
+
"mime_type"
|
|
249
|
+
) or metadata.get("mime_type")
|
|
250
|
+
except Exception as e:
|
|
251
|
+
log.error(f"Failed to download file {file.get('name')}: {e}")
|
|
252
|
+
continue
|
|
253
|
+
else:
|
|
254
|
+
log.error(f"No file content found for {file.get('name')}")
|
|
255
|
+
continue
|
|
256
|
+
downloaded_files.append(output_file)
|
|
257
|
+
|
|
258
|
+
data["files"] = downloaded_files
|
|
259
|
+
data["server_input_id"] = server_input_id
|
|
260
|
+
|
|
261
|
+
# Promote the interface properties back to the top level of the user_properties
|
|
262
|
+
self.promote_interface_properties(user_properties)
|
|
263
|
+
message.set_user_properties(user_properties)
|
|
264
|
+
|
|
265
|
+
# Resolve inline URLs in text and chunk fields
|
|
266
|
+
self._resolve_text_content(data, session_id)
|
|
267
|
+
|
|
268
|
+
message.set_payload(data)
|
|
269
|
+
return {"payload": data}
|
|
270
|
+
|
|
271
|
+
def promote_interface_properties(self, user_properties):
|
|
272
|
+
"""
|
|
273
|
+
Updates the user_properties with the values from the
|
|
274
|
+
"interface_properties" sub-object, if it exists.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
user_properties (dict): A dictionary that may contain an
|
|
278
|
+
"interface_properties" key with a sub-dictionary
|
|
279
|
+
of properties to be merged into the main dictionary.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
None: The function modifies the user_properties dictionary in place.
|
|
283
|
+
"""
|
|
284
|
+
interface_properties = user_properties.get("interface_properties", {})
|
|
285
|
+
if isinstance(interface_properties, dict):
|
|
286
|
+
for key, value in interface_properties.items():
|
|
287
|
+
user_properties[key] = value
|
|
288
|
+
|
|
289
|
+
user_properties.pop("interface_properties", None)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from .identity_base import IdentityBase
|
|
3
|
+
from ...services.bamboo_hr_service.bamboo_hr import BambooHR
|
|
4
|
+
|
|
5
|
+
class BambooHRIdentity(IdentityBase):
|
|
6
|
+
def __init__(self, config: dict):
|
|
7
|
+
super().__init__(config)
|
|
8
|
+
self.bamboo_hr_service = BambooHR(config)
|
|
9
|
+
|
|
10
|
+
def get_user_info(self, identity: str) -> dict:
|
|
11
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", identity):
|
|
12
|
+
raise ValueError("Invalid email address")
|
|
13
|
+
|
|
14
|
+
employee_summary = self.bamboo_hr_service.get_employee_summary(identity)
|
|
15
|
+
if employee_summary:
|
|
16
|
+
return employee_summary
|
|
17
|
+
|
|
18
|
+
return {"identity": identity}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
from ...gateway.identity.passthru_identity import PassthruIdentity
|
|
4
|
+
from ...gateway.identity.bamboohr_identity import BambooHRIdentity
|
|
5
|
+
from ...gateway.identity.no_identity import NoIdentity
|
|
6
|
+
from ...gateway.identity.identity_base import IdentityBase
|
|
7
|
+
from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
|
|
8
|
+
|
|
9
|
+
IDENTITY_PROVIDERS = {
|
|
10
|
+
"passthru": PassthruIdentity,
|
|
11
|
+
"bamboohr": BambooHRIdentity,
|
|
12
|
+
"none": NoIdentity,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
DEFAULT_PROVIDER = "none"
|
|
16
|
+
|
|
17
|
+
class IdentityProvider:
|
|
18
|
+
identity_provider: IdentityBase
|
|
19
|
+
|
|
20
|
+
def __init__(self, config=None):
|
|
21
|
+
self.config = config or {}
|
|
22
|
+
self.provider_type = self.config.get("type", DEFAULT_PROVIDER)
|
|
23
|
+
self.identity_field = self.config.get("key_field", DEFAULT_IDENTITY_KEY_FIELD)
|
|
24
|
+
if self.provider_type not in IDENTITY_PROVIDERS and not self.config.get("module_path"):
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"Unsupported identity provider type: {self.provider_type}. No module_path provided."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
provider_configuration = self.config.get("configuration", {})
|
|
30
|
+
|
|
31
|
+
if self.provider_type in IDENTITY_PROVIDERS:
|
|
32
|
+
# Load built-in identity provider
|
|
33
|
+
self.identity_provider = IDENTITY_PROVIDERS[self.provider_type](provider_configuration)
|
|
34
|
+
else:
|
|
35
|
+
try:
|
|
36
|
+
# Load the provider from the module path
|
|
37
|
+
module_name = self.provider_type
|
|
38
|
+
module_path = self.config.get("module_path")
|
|
39
|
+
module = importlib.import_module(module_path, package=__package__)
|
|
40
|
+
identity_class = getattr(module, module_name)
|
|
41
|
+
if not issubclass(identity_class, IdentityBase):
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"Identity provider class {identity_class} does not inherit from IdentityBase"
|
|
44
|
+
)
|
|
45
|
+
self.identity_provider = identity_class(provider_configuration)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
raise ImportError("Unable to load component: " + str(e)) from e
|
|
48
|
+
|
|
49
|
+
def get_identity_field(self) -> str:
|
|
50
|
+
"""Returns the configured field name to use for identity lookup"""
|
|
51
|
+
return self.identity_field
|
|
52
|
+
|
|
53
|
+
def get_user_info(self, identity: str) -> dict:
|
|
54
|
+
"""
|
|
55
|
+
Get user information using the configured identity provider.
|
|
56
|
+
|
|
57
|
+
:param identity: The user identity string (usually email)
|
|
58
|
+
:return: Dictionary containing user information
|
|
59
|
+
"""
|
|
60
|
+
return self.identity_provider.get_user_info(identity)
|