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,246 @@
|
|
|
1
|
+
"""This is the component that handles processing all streaming outputs from LLM"""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from solace_ai_connector.components.component_base import ComponentBase
|
|
6
|
+
from solace_ai_connector.common.log import log
|
|
7
|
+
from solace_ai_connector.common.message import Message
|
|
8
|
+
from ...common.utils import parse_orchestrator_response
|
|
9
|
+
from ...services.history_service import HistoryService
|
|
10
|
+
from ...services.file_service import FileService
|
|
11
|
+
from ...orchestrator.orchestrator_main import (
|
|
12
|
+
ORCHESTRATOR_HISTORY_IDENTIFIER,
|
|
13
|
+
ORCHESTRATOR_HISTORY_CONFIG,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
info = {
|
|
17
|
+
"class_name": "OrchestratorStreamingOutputComponent",
|
|
18
|
+
"description": ("This component handles all streaming outputs from LLM"),
|
|
19
|
+
"config_parameters": [],
|
|
20
|
+
"input_schema": {
|
|
21
|
+
# A streaming output object - it doesn't have a fixed schema
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": True,
|
|
24
|
+
},
|
|
25
|
+
"output_schema": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"text": {"type": "string"},
|
|
31
|
+
"streaming": {"type": "boolean"},
|
|
32
|
+
"first_chunk": {"type": "boolean"},
|
|
33
|
+
"last_chunk": {"type": "boolean"},
|
|
34
|
+
"uuid": {"type": "string"},
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"text",
|
|
38
|
+
"streaming",
|
|
39
|
+
"first_chunk",
|
|
40
|
+
"last_chunk",
|
|
41
|
+
"uuid",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OrchestratorStreamingOutputComponent(ComponentBase):
|
|
49
|
+
"""This is the component that handles processing all streaming outputs from LLM"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, **kwargs):
|
|
52
|
+
super().__init__(info, **kwargs)
|
|
53
|
+
self._response_state = {}
|
|
54
|
+
self.history = HistoryService(
|
|
55
|
+
ORCHESTRATOR_HISTORY_CONFIG, identifier=ORCHESTRATOR_HISTORY_IDENTIFIER
|
|
56
|
+
)
|
|
57
|
+
self.file_service = FileService()
|
|
58
|
+
|
|
59
|
+
def invoke(self, message: Message, data):
|
|
60
|
+
"""Handle streaming outputs from LLM"""
|
|
61
|
+
if not data:
|
|
62
|
+
log.error("No data received from LLM")
|
|
63
|
+
self.discard_current_message()
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
# response complete messages also go through this flow
|
|
67
|
+
# to maintain the order of messages with the streamed responses
|
|
68
|
+
if data.get("response_complete"):
|
|
69
|
+
return [data]
|
|
70
|
+
|
|
71
|
+
user_properties = message.get_user_properties()
|
|
72
|
+
stimulus_uuid = user_properties.get("stimulus_uuid")
|
|
73
|
+
session_id = user_properties.get("session_id")
|
|
74
|
+
text = data.get("content")
|
|
75
|
+
response_uuid = data.get("response_uuid")
|
|
76
|
+
first_chunk = data.get("first_chunk")
|
|
77
|
+
last_chunk = data.get("last_chunk")
|
|
78
|
+
|
|
79
|
+
if first_chunk:
|
|
80
|
+
response_state = self.add_response_state(response_uuid)
|
|
81
|
+
else:
|
|
82
|
+
response_state = self.get_response_state(response_uuid)
|
|
83
|
+
if not response_state:
|
|
84
|
+
# Just discard the data
|
|
85
|
+
self.discard_current_message()
|
|
86
|
+
return None
|
|
87
|
+
if last_chunk:
|
|
88
|
+
self.delete_response_state(response_uuid)
|
|
89
|
+
if stimulus_uuid:
|
|
90
|
+
self.history.store_history(stimulus_uuid, "assistant", text)
|
|
91
|
+
|
|
92
|
+
obj = parse_orchestrator_response(text, last_chunk=last_chunk)
|
|
93
|
+
|
|
94
|
+
if not obj or isinstance(obj, str) or not obj.get("content"):
|
|
95
|
+
log.debug("Error parsing LLM output: %s", obj)
|
|
96
|
+
self.discard_current_message()
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
content = obj.get("content")
|
|
100
|
+
status_updates = obj.get("status_updates")
|
|
101
|
+
send_last_status_update = obj.get("send_last_status_update")
|
|
102
|
+
|
|
103
|
+
outputs = []
|
|
104
|
+
full_text = ""
|
|
105
|
+
last_text_output_idx = None
|
|
106
|
+
if content and isinstance(content, list):
|
|
107
|
+
for item_idx, item in enumerate(content):
|
|
108
|
+
output, item_text = self.process_content_item(
|
|
109
|
+
item_idx,
|
|
110
|
+
len(content),
|
|
111
|
+
item,
|
|
112
|
+
response_state,
|
|
113
|
+
response_uuid,
|
|
114
|
+
last_chunk,
|
|
115
|
+
session_id
|
|
116
|
+
)
|
|
117
|
+
if item_text:
|
|
118
|
+
full_text += item_text
|
|
119
|
+
if output:
|
|
120
|
+
outputs.append(output)
|
|
121
|
+
if output.get("text"):
|
|
122
|
+
last_text_output_idx = len(outputs) - 1
|
|
123
|
+
|
|
124
|
+
if status_updates and isinstance(status_updates, list):
|
|
125
|
+
last_status_update_idx = response_state.get("last_status_update_idx", -1)
|
|
126
|
+
if (
|
|
127
|
+
last_status_update_idx < len(status_updates) - 1
|
|
128
|
+
or send_last_status_update
|
|
129
|
+
):
|
|
130
|
+
user_properties = message.get_user_properties()
|
|
131
|
+
stimulus_uuid = user_properties.get("stimulus_uuid")
|
|
132
|
+
if not stimulus_uuid:
|
|
133
|
+
log.error("No stimulus_uuid found in user_properties")
|
|
134
|
+
stimulus_uuid = response_uuid
|
|
135
|
+
output = {
|
|
136
|
+
"status_update": True,
|
|
137
|
+
"text": status_updates[-1],
|
|
138
|
+
"uuid": stimulus_uuid + "-status",
|
|
139
|
+
"streaming": True,
|
|
140
|
+
}
|
|
141
|
+
outputs.append(output)
|
|
142
|
+
response_state["last_status_update_idx"] = len(status_updates) - 1
|
|
143
|
+
|
|
144
|
+
if not outputs:
|
|
145
|
+
self.discard_current_message()
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
chunk, next_chunk_index = self.get_current_chunk(
|
|
149
|
+
full_text, response_state.get("previous_chunk_index", 0)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Add the chunk to the last output
|
|
153
|
+
if chunk:
|
|
154
|
+
if last_text_output_idx is not None:
|
|
155
|
+
outputs[last_text_output_idx]["chunk"] = chunk
|
|
156
|
+
response_state["previous_chunk_index"] = next_chunk_index
|
|
157
|
+
|
|
158
|
+
return outputs
|
|
159
|
+
|
|
160
|
+
def process_content_item(
|
|
161
|
+
self, item_idx, num_items, item, response_state, response_uuid, last_chunk, session_id
|
|
162
|
+
):
|
|
163
|
+
"""Process a content item"""
|
|
164
|
+
streaming_content_idx = response_state.get("streaming_content_idx", 0)
|
|
165
|
+
|
|
166
|
+
if item_idx < streaming_content_idx:
|
|
167
|
+
if (
|
|
168
|
+
item.get("type") == "text"
|
|
169
|
+
and not item.get("status_update")
|
|
170
|
+
and not item.get("response_complete")
|
|
171
|
+
):
|
|
172
|
+
return None, item.get("body")
|
|
173
|
+
return None, ""
|
|
174
|
+
|
|
175
|
+
streaming_started = response_state.get("streaming_started", False)
|
|
176
|
+
last_chunk = last_chunk or item_idx < (num_items - 1)
|
|
177
|
+
first_chunk = not streaming_started or item_idx > streaming_content_idx
|
|
178
|
+
|
|
179
|
+
output = {
|
|
180
|
+
"streaming": True,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if item.get("type") == "text":
|
|
184
|
+
output["text"] = item.get("body")
|
|
185
|
+
elif item.get("type") == "file":
|
|
186
|
+
file_info = item.get("body", {})
|
|
187
|
+
if file_info.get("url") or file_info.get("data"):
|
|
188
|
+
# If there is no url but there is data, we need to store the data in the file service
|
|
189
|
+
if not file_info.get("url") and file_info.get("data"):
|
|
190
|
+
file_meta = self.file_service.upload_from_buffer(
|
|
191
|
+
file_info["data"],
|
|
192
|
+
file_info["name"],
|
|
193
|
+
session_id,
|
|
194
|
+
data_source="Orchestrator created data",
|
|
195
|
+
)
|
|
196
|
+
if not session_id:
|
|
197
|
+
log.error("No session_id found in user_properties")
|
|
198
|
+
file_info.update(file_meta)
|
|
199
|
+
|
|
200
|
+
output["files"] = [file_info]
|
|
201
|
+
else:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
output["first_chunk"] = first_chunk
|
|
205
|
+
output["last_chunk"] = last_chunk
|
|
206
|
+
output["uuid"] = response_uuid + "-" + str(item_idx)
|
|
207
|
+
response_state["streaming_content_idx"] = item_idx
|
|
208
|
+
response_state["streaming_started"] = True
|
|
209
|
+
return output, output.get("text") or ""
|
|
210
|
+
|
|
211
|
+
def get_current_chunk(self, full_text, previous_chunk_index):
|
|
212
|
+
"""Use the previous_chunk_index to get the current chunk of text from the full_text"""
|
|
213
|
+
|
|
214
|
+
# The chunk is the text from the previous_chunk_index to the end of the full_text
|
|
215
|
+
chunk = full_text[previous_chunk_index:]
|
|
216
|
+
return chunk, len(full_text)
|
|
217
|
+
|
|
218
|
+
def age_out_response_state(self):
|
|
219
|
+
"""Remove any responses that have been around for too long"""
|
|
220
|
+
# Loop through all states and remove any that are too old
|
|
221
|
+
current_time = datetime.now()
|
|
222
|
+
for response_uuid in list(self._response_state.keys()):
|
|
223
|
+
response_state = self._response_state[response_uuid]
|
|
224
|
+
delta = current_time - response_state["create_time"]
|
|
225
|
+
if delta.total_seconds() > 60:
|
|
226
|
+
del self._response_state[response_uuid]
|
|
227
|
+
|
|
228
|
+
def get_response_state(self, response_uuid):
|
|
229
|
+
"""Get the state of a response"""
|
|
230
|
+
return self._response_state.get(response_uuid)
|
|
231
|
+
|
|
232
|
+
def add_response_state(self, response_uuid):
|
|
233
|
+
"""Add a new response state"""
|
|
234
|
+
response_state = {
|
|
235
|
+
"create_time": datetime.now(),
|
|
236
|
+
"streaming_content_idx": 0,
|
|
237
|
+
"previous_chunk_index": 0,
|
|
238
|
+
}
|
|
239
|
+
self._response_state[response_uuid] = response_state
|
|
240
|
+
self.age_out_response_state()
|
|
241
|
+
return response_state
|
|
242
|
+
|
|
243
|
+
def delete_response_state(self, response_uuid):
|
|
244
|
+
"""Delete a response state"""
|
|
245
|
+
if response_uuid in self._response_state:
|
|
246
|
+
del self._response_state[response_uuid]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from ..services.middleware_service.middleware_service import MiddlewareService
|
|
3
|
+
import threading
|
|
4
|
+
from solace_ai_connector.common.log import log
|
|
5
|
+
from ..common.action_response import ActionResponse
|
|
6
|
+
from ..common.time import TEN_MINUTES, THIRTY_MINUTES
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ORCHESTRATOR_HISTORY_IDENTIFIER = "orchestrator"
|
|
10
|
+
|
|
11
|
+
ORCHESTRATOR_HISTORY_CONFIG = {
|
|
12
|
+
"type": "memory",
|
|
13
|
+
"time_to_live": THIRTY_MINUTES,
|
|
14
|
+
"expiration_check_interval": TEN_MINUTES,
|
|
15
|
+
"history_policy": {
|
|
16
|
+
"max_turns": 30,
|
|
17
|
+
"max_characters": 0,
|
|
18
|
+
"enforce_alternate_message_roles": False,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OrchestratorState:
|
|
24
|
+
"""Singleton object to store orchestrator state"""
|
|
25
|
+
|
|
26
|
+
_instance = None
|
|
27
|
+
_lock = threading.Lock()
|
|
28
|
+
_config = None
|
|
29
|
+
_session_state = {}
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def set_config(cls, config):
|
|
33
|
+
cls._config = config
|
|
34
|
+
|
|
35
|
+
def __new__(cls):
|
|
36
|
+
with cls._lock:
|
|
37
|
+
if cls._instance is None:
|
|
38
|
+
cls._instance = super(OrchestratorState, cls).__new__(cls)
|
|
39
|
+
return cls._instance
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
if not hasattr(self, "registered_agents"):
|
|
43
|
+
self.registered_agents = {}
|
|
44
|
+
|
|
45
|
+
def register_agent(self, agent):
|
|
46
|
+
with self._lock:
|
|
47
|
+
agent_name = agent.get("agent_name")
|
|
48
|
+
agent["state"] = "closed"
|
|
49
|
+
if agent_name not in self.registered_agents:
|
|
50
|
+
self.registered_agents[agent_name] = agent
|
|
51
|
+
|
|
52
|
+
# Reset its TTL
|
|
53
|
+
self.registered_agents[agent_name][
|
|
54
|
+
"expire_time"
|
|
55
|
+
] = datetime.now() + timedelta(
|
|
56
|
+
milliseconds=self._config.get("agent_ttl_ms")
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def get_registered_agents(self):
|
|
60
|
+
with self._lock:
|
|
61
|
+
return self.registered_agents
|
|
62
|
+
|
|
63
|
+
def get_agent_action(self, agent_name, action_name):
|
|
64
|
+
with self._lock:
|
|
65
|
+
agent = self.registered_agents.get(agent_name)
|
|
66
|
+
if not agent:
|
|
67
|
+
return None
|
|
68
|
+
actions = agent.get("actions")
|
|
69
|
+
if not actions:
|
|
70
|
+
return None
|
|
71
|
+
for action in actions:
|
|
72
|
+
if action is None:
|
|
73
|
+
continue
|
|
74
|
+
for action_obj_name, action_obj in action.items():
|
|
75
|
+
if action_obj_name == action_name:
|
|
76
|
+
return action_obj
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def age_out_agents(self):
|
|
80
|
+
with self._lock:
|
|
81
|
+
now = datetime.now()
|
|
82
|
+
for agent_name, agent in self.registered_agents.items():
|
|
83
|
+
if agent.get("expire_time") < now:
|
|
84
|
+
log.warning("Agent %s has expired. Removing.", agent_name)
|
|
85
|
+
del self.registered_agents[agent_name]
|
|
86
|
+
|
|
87
|
+
def delete_agent(self, agent_name):
|
|
88
|
+
with self._lock:
|
|
89
|
+
if agent_name in self.registered_agents:
|
|
90
|
+
del self.registered_agents[agent_name]
|
|
91
|
+
|
|
92
|
+
def get_session_state(self, session_id):
|
|
93
|
+
if not session_id in self._session_state:
|
|
94
|
+
self._session_state[session_id] = {}
|
|
95
|
+
return self._session_state[session_id]
|
|
96
|
+
|
|
97
|
+
def get_agent_state(self, session_id):
|
|
98
|
+
session_state = self.get_session_state(session_id)
|
|
99
|
+
if "agent_state" not in session_state:
|
|
100
|
+
session_state["agent_state"] = {
|
|
101
|
+
"global": {"agent_name": "global", "state": "open"}
|
|
102
|
+
}
|
|
103
|
+
return session_state["agent_state"]
|
|
104
|
+
|
|
105
|
+
def set_agent_state(self, session_id, agent_state):
|
|
106
|
+
session_state = self.get_session_state(session_id)
|
|
107
|
+
session_state["agent_state"] = agent_state
|
|
108
|
+
|
|
109
|
+
def get_current_subject_starting_id(self, session_id):
|
|
110
|
+
session_state = self.get_session_state(session_id)
|
|
111
|
+
return session_state.get("current_subject_starting_id")
|
|
112
|
+
|
|
113
|
+
def set_current_subject_starting_id(self, session_id, current_subject_starting_id):
|
|
114
|
+
session_state = self.get_session_state(session_id)
|
|
115
|
+
session_state["current_subject_starting_id"] = current_subject_starting_id
|
|
116
|
+
|
|
117
|
+
def update_agent_state(
|
|
118
|
+
self, agent_name: str, new_state: str, session_id
|
|
119
|
+
) -> ActionResponse:
|
|
120
|
+
"""
|
|
121
|
+
Handle an app state change. Return whether or not
|
|
122
|
+
"""
|
|
123
|
+
with self._lock:
|
|
124
|
+
if agent_name == "global":
|
|
125
|
+
return None
|
|
126
|
+
old_state = "closed"
|
|
127
|
+
conversation_agent_state = self.get_agent_state(session_id)
|
|
128
|
+
if agent_name in conversation_agent_state:
|
|
129
|
+
old_state = conversation_agent_state[agent_name].get("state", "closed")
|
|
130
|
+
conversation_agent_state[agent_name] = {
|
|
131
|
+
"agent_name": agent_name,
|
|
132
|
+
"state": new_state,
|
|
133
|
+
}
|
|
134
|
+
self.set_agent_state(session_id, conversation_agent_state)
|
|
135
|
+
if old_state == "closed" and new_state == "open":
|
|
136
|
+
return ActionResponse(
|
|
137
|
+
invoke_model_again=True,
|
|
138
|
+
)
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def get_agents_and_actions(self, user_properties: dict) -> dict:
|
|
142
|
+
session_id = user_properties.get("session_id", "")
|
|
143
|
+
result = {}
|
|
144
|
+
middleware_service = MiddlewareService()
|
|
145
|
+
|
|
146
|
+
for agent_name, agent in self.registered_agents.items():
|
|
147
|
+
actions = agent.get("actions", [])
|
|
148
|
+
filtered_actions = middleware_service.get("filter_action")(user_properties, actions)
|
|
149
|
+
|
|
150
|
+
if filtered_actions:
|
|
151
|
+
agent_state = self.get_agent_state(session_id).get(agent_name, {})
|
|
152
|
+
state = agent_state.get("state", "closed")
|
|
153
|
+
|
|
154
|
+
result[agent_name] = {
|
|
155
|
+
"description": agent.get("description"),
|
|
156
|
+
"state": state,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if state == "open":
|
|
160
|
+
if agent.get("detailed_description"):
|
|
161
|
+
result[agent_name]["description"] = agent.get(
|
|
162
|
+
"detailed_description"
|
|
163
|
+
)
|
|
164
|
+
result[agent_name]["actions"] = filtered_actions
|
|
165
|
+
|
|
166
|
+
return result
|