solace-agent-mesh 1.4.11__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/adk_llm.txt +3 -4
- solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
- solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +1 -0
- solace_agent_mesh/agent/adk/callbacks.py +51 -2
- solace_agent_mesh/agent/adk/models/lite_llm.py +1 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +1 -2
- solace_agent_mesh/agent/adk/services.py +30 -15
- solace_agent_mesh/agent/adk/setup.py +4 -0
- solace_agent_mesh/agent/agent_llm.txt +1 -1
- solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +2 -13
- solace_agent_mesh/agent/protocol/protocol_llm.txt +15 -2
- solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
- solace_agent_mesh/agent/sac/component.py +51 -21
- solace_agent_mesh/agent/sac/sac_llm.txt +15 -1
- solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +73 -0
- solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +148 -154
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +274 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +1 -1
- solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/0e682baa.d54b8668.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/483cef9a.bf9398af.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.dc155742.js → main.0c149855.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/{runtime~main.0d2ff2b6.js → runtime~main.c66557e4.js} +1 -1
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +8 -4
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +7 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1760032255022.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1760032255022.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-j1LW-wlq.js → authCallback-DwrxZE0E.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-B9p_nFNA.js → client-DarGQzyw.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-CZbpmwfA.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-C__uuUkB.js +339 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CS5YMf8a.js → vendor-BKIeiHj_.js} +80 -70
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/a2a_llm.txt +1 -1
- solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +1 -1
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +23 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +93 -15
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +23 -0
- solace_agent_mesh/common/common_llm.txt +24 -39
- solace_agent_mesh/common/common_llm_detail.txt +2562 -0
- solace_agent_mesh/common/data_parts.py +9 -1
- solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
- solace_agent_mesh/common/sac/sac_llm.txt +1 -1
- solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
- solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
- solace_agent_mesh/common/services/identity_service.py +7 -4
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +4 -2
- solace_agent_mesh/common/services/services_llm.txt +57 -6
- solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
- solace_agent_mesh/common/utils/utils_llm.txt +75 -87
- solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
- solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
- solace_agent_mesh/gateway/base/app.py +1 -1
- solace_agent_mesh/gateway/base/base_llm.txt +1 -1
- solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
- solace_agent_mesh/gateway/base/component.py +1 -1
- solace_agent_mesh/gateway/gateway_llm.txt +242 -235
- solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +295 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +10 -1
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +155 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +1 -1
- solace_agent_mesh/gateway/http_sse/app.py +148 -2
- solace_agent_mesh/gateway/http_sse/component.py +368 -60
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +46 -6
- solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +108 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +1 -1
- solace_agent_mesh/gateway/http_sse/dependencies.py +116 -26
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +172 -172
- solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
- solace_agent_mesh/gateway/http_sse/main.py +146 -41
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +3 -12
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +103 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +5 -3
- solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +263 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -16
- solace_agent_mesh/gateway/http_sse/repository/entities/task.py +25 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +73 -18
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -5
- solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
- solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +266 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +3 -3
- solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
- solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +32 -0
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +340 -0
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +4 -53
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +173 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/config.py +26 -4
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +346 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +3 -3
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +83 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +2 -10
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +5 -3
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +107 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +1 -15
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +37 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +255 -204
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +220 -40
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +168 -42
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +272 -0
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +241 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +0 -80
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +177 -13
- solace_agent_mesh/gateway/http_sse/services/session_service.py +151 -84
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +317 -0
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +25 -14
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +285 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +7 -0
- solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
- solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +32 -0
- solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
- solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
- solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
- {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/RECORD +179 -131
- solace_agent_mesh/agent/adk/invocation_monitor.py +0 -295
- solace_agent_mesh/assets/docs/assets/js/0e682baa.d054e1d8.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/483cef9a.4736f2d8.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1759514789087.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1759514789087.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-ChRwcV89.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-DnnE01OM.js +0 -339
- solace_agent_mesh/gateway/http_sse/repository/entities/message.py +0 -41
- solace_agent_mesh/gateway/http_sse/repository/message_repository.py +0 -84
- solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +0 -45
- /solace_agent_mesh/assets/docs/assets/js/{main.dc155742.js.LICENSE.txt → main.0c149855.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service for managing automatic cleanup of old data based on retention policies.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Callable, Dict
|
|
7
|
+
|
|
8
|
+
from solace_ai_connector.common.log import log
|
|
9
|
+
from sqlalchemy.orm import Session as DBSession
|
|
10
|
+
|
|
11
|
+
from ..repository.feedback_repository import FeedbackRepository
|
|
12
|
+
from ..repository.task_repository import TaskRepository
|
|
13
|
+
from ..shared import now_epoch_ms
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DataRetentionService:
|
|
17
|
+
"""
|
|
18
|
+
Service for automatically cleaning up old tasks, task events, and feedback
|
|
19
|
+
based on configurable retention policies.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Validation constants
|
|
23
|
+
MIN_RETENTION_DAYS = 1
|
|
24
|
+
MIN_CLEANUP_INTERVAL_HOURS = 1
|
|
25
|
+
MIN_BATCH_SIZE = 1
|
|
26
|
+
MAX_BATCH_SIZE = 10000
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self, session_factory: Callable[[], DBSession] | None, config: Dict[str, Any]
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Initialize the DataRetentionService.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
session_factory: Factory function to create database sessions
|
|
36
|
+
config: Configuration dictionary with retention settings
|
|
37
|
+
"""
|
|
38
|
+
self.session_factory = session_factory
|
|
39
|
+
self.config = config
|
|
40
|
+
self.log_identifier = "[DataRetentionService]"
|
|
41
|
+
|
|
42
|
+
# Validate and store configuration
|
|
43
|
+
self._validate_config()
|
|
44
|
+
|
|
45
|
+
log.info(
|
|
46
|
+
"%s Initialized with task_retention=%d days, feedback_retention=%d days, "
|
|
47
|
+
"cleanup_interval=%d hours, batch_size=%d",
|
|
48
|
+
self.log_identifier,
|
|
49
|
+
self.config.get("task_retention_days"),
|
|
50
|
+
self.config.get("feedback_retention_days"),
|
|
51
|
+
self.config.get("cleanup_interval_hours"),
|
|
52
|
+
self.config.get("batch_size"),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def _validate_config(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Validates configuration values and applies safe defaults if needed.
|
|
58
|
+
Logs warnings for invalid values.
|
|
59
|
+
"""
|
|
60
|
+
# Validate task retention days
|
|
61
|
+
task_retention = self.config.get("task_retention_days", 90)
|
|
62
|
+
if task_retention < self.MIN_RETENTION_DAYS:
|
|
63
|
+
log.warning(
|
|
64
|
+
"%s task_retention_days (%d) is below minimum (%d days). Using minimum.",
|
|
65
|
+
self.log_identifier,
|
|
66
|
+
task_retention,
|
|
67
|
+
self.MIN_RETENTION_DAYS,
|
|
68
|
+
)
|
|
69
|
+
self.config["task_retention_days"] = self.MIN_RETENTION_DAYS
|
|
70
|
+
else:
|
|
71
|
+
self.config["task_retention_days"] = task_retention
|
|
72
|
+
|
|
73
|
+
# Validate feedback retention days
|
|
74
|
+
feedback_retention = self.config.get("feedback_retention_days", 90)
|
|
75
|
+
if feedback_retention < self.MIN_RETENTION_DAYS:
|
|
76
|
+
log.warning(
|
|
77
|
+
"%s feedback_retention_days (%d) is below minimum (%d days). Using minimum.",
|
|
78
|
+
self.log_identifier,
|
|
79
|
+
feedback_retention,
|
|
80
|
+
self.MIN_RETENTION_DAYS,
|
|
81
|
+
)
|
|
82
|
+
self.config["feedback_retention_days"] = self.MIN_RETENTION_DAYS
|
|
83
|
+
else:
|
|
84
|
+
self.config["feedback_retention_days"] = feedback_retention
|
|
85
|
+
|
|
86
|
+
# Validate cleanup interval
|
|
87
|
+
cleanup_interval = self.config.get("cleanup_interval_hours", 24)
|
|
88
|
+
if cleanup_interval < self.MIN_CLEANUP_INTERVAL_HOURS:
|
|
89
|
+
log.warning(
|
|
90
|
+
"%s cleanup_interval_hours (%d) is below minimum (%d hours). Using minimum.",
|
|
91
|
+
self.log_identifier,
|
|
92
|
+
cleanup_interval,
|
|
93
|
+
self.MIN_CLEANUP_INTERVAL_HOURS,
|
|
94
|
+
)
|
|
95
|
+
self.config["cleanup_interval_hours"] = self.MIN_CLEANUP_INTERVAL_HOURS
|
|
96
|
+
else:
|
|
97
|
+
self.config["cleanup_interval_hours"] = cleanup_interval
|
|
98
|
+
|
|
99
|
+
# Validate batch size
|
|
100
|
+
batch_size = self.config.get("batch_size", 1000)
|
|
101
|
+
if batch_size < self.MIN_BATCH_SIZE:
|
|
102
|
+
log.warning(
|
|
103
|
+
"%s batch_size (%d) is below minimum (%d). Using minimum.",
|
|
104
|
+
self.log_identifier,
|
|
105
|
+
batch_size,
|
|
106
|
+
self.MIN_BATCH_SIZE,
|
|
107
|
+
)
|
|
108
|
+
self.config["batch_size"] = self.MIN_BATCH_SIZE
|
|
109
|
+
elif batch_size > self.MAX_BATCH_SIZE:
|
|
110
|
+
log.warning(
|
|
111
|
+
"%s batch_size (%d) exceeds maximum (%d). Using maximum.",
|
|
112
|
+
self.log_identifier,
|
|
113
|
+
batch_size,
|
|
114
|
+
self.MAX_BATCH_SIZE,
|
|
115
|
+
)
|
|
116
|
+
self.config["batch_size"] = self.MAX_BATCH_SIZE
|
|
117
|
+
else:
|
|
118
|
+
self.config["batch_size"] = batch_size
|
|
119
|
+
|
|
120
|
+
def cleanup_old_data(self) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Main orchestration method for cleaning up old data.
|
|
123
|
+
Calls cleanup methods for tasks and feedback.
|
|
124
|
+
"""
|
|
125
|
+
if not self.config.get("enabled", True):
|
|
126
|
+
log.warning(
|
|
127
|
+
"%s Data retention cleanup is disabled via configuration.",
|
|
128
|
+
self.log_identifier,
|
|
129
|
+
)
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if not self.session_factory:
|
|
133
|
+
log.warning(
|
|
134
|
+
"%s No database session factory available. Skipping cleanup.",
|
|
135
|
+
self.log_identifier,
|
|
136
|
+
)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
log.info("%s Starting data retention cleanup...", self.log_identifier)
|
|
140
|
+
start_time = time.time()
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Cleanup old tasks
|
|
144
|
+
task_retention_days = self.config.get("task_retention_days")
|
|
145
|
+
tasks_deleted = self._cleanup_old_tasks(task_retention_days)
|
|
146
|
+
|
|
147
|
+
# Cleanup old feedback
|
|
148
|
+
feedback_retention_days = self.config.get("feedback_retention_days")
|
|
149
|
+
feedback_deleted = self._cleanup_old_feedback(feedback_retention_days)
|
|
150
|
+
|
|
151
|
+
elapsed_time = time.time() - start_time
|
|
152
|
+
log.info(
|
|
153
|
+
"%s Cleanup completed. Tasks deleted: %d, Feedback deleted: %d, Time taken: %.2f seconds",
|
|
154
|
+
self.log_identifier,
|
|
155
|
+
tasks_deleted,
|
|
156
|
+
feedback_deleted,
|
|
157
|
+
elapsed_time,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
log.error(
|
|
162
|
+
"%s Error during data retention cleanup: %s",
|
|
163
|
+
self.log_identifier,
|
|
164
|
+
e,
|
|
165
|
+
exc_info=True,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def _cleanup_old_tasks(self, retention_days: int) -> int:
|
|
169
|
+
"""
|
|
170
|
+
Deletes tasks (and their events via cascade) older than the retention period.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
retention_days: Number of days to retain tasks
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Total number of tasks deleted
|
|
177
|
+
"""
|
|
178
|
+
log.info(
|
|
179
|
+
"%s Cleaning up tasks older than %d days...",
|
|
180
|
+
self.log_identifier,
|
|
181
|
+
retention_days,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Calculate cutoff time in milliseconds
|
|
185
|
+
cutoff_time_ms = now_epoch_ms() - (retention_days * 24 * 60 * 60 * 1000)
|
|
186
|
+
batch_size = self.config.get("batch_size")
|
|
187
|
+
|
|
188
|
+
db = self.session_factory()
|
|
189
|
+
try:
|
|
190
|
+
repo = TaskRepository(db)
|
|
191
|
+
total_deleted = repo.delete_tasks_older_than(cutoff_time_ms, batch_size)
|
|
192
|
+
|
|
193
|
+
if total_deleted == 0:
|
|
194
|
+
log.info(
|
|
195
|
+
"%s No tasks found older than %d days.",
|
|
196
|
+
self.log_identifier,
|
|
197
|
+
retention_days,
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
log.info(
|
|
201
|
+
"%s Deleted %d tasks older than %d days.",
|
|
202
|
+
self.log_identifier,
|
|
203
|
+
total_deleted,
|
|
204
|
+
retention_days,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return total_deleted
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
log.error(
|
|
211
|
+
"%s Error cleaning up old tasks: %s",
|
|
212
|
+
self.log_identifier,
|
|
213
|
+
e,
|
|
214
|
+
exc_info=True,
|
|
215
|
+
)
|
|
216
|
+
db.rollback()
|
|
217
|
+
return 0
|
|
218
|
+
finally:
|
|
219
|
+
db.close()
|
|
220
|
+
|
|
221
|
+
def _cleanup_old_feedback(self, retention_days: int) -> int:
|
|
222
|
+
"""
|
|
223
|
+
Deletes feedback records older than the retention period.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
retention_days: Number of days to retain feedback
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Total number of feedback records deleted
|
|
230
|
+
"""
|
|
231
|
+
log.info(
|
|
232
|
+
"%s Cleaning up feedback older than %d days...",
|
|
233
|
+
self.log_identifier,
|
|
234
|
+
retention_days,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Calculate cutoff time in milliseconds
|
|
238
|
+
cutoff_time_ms = now_epoch_ms() - (retention_days * 24 * 60 * 60 * 1000)
|
|
239
|
+
batch_size = self.config.get("batch_size")
|
|
240
|
+
|
|
241
|
+
db = self.session_factory()
|
|
242
|
+
try:
|
|
243
|
+
repo = FeedbackRepository(db)
|
|
244
|
+
total_deleted = repo.delete_feedback_older_than(cutoff_time_ms, batch_size)
|
|
245
|
+
|
|
246
|
+
if total_deleted == 0:
|
|
247
|
+
log.info(
|
|
248
|
+
"%s No feedback found older than %d days.",
|
|
249
|
+
self.log_identifier,
|
|
250
|
+
retention_days,
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
log.info(
|
|
254
|
+
"%s Deleted %d feedback records older than %d days.",
|
|
255
|
+
self.log_identifier,
|
|
256
|
+
total_deleted,
|
|
257
|
+
retention_days,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return total_deleted
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
log.error(
|
|
264
|
+
"%s Error cleaning up old feedback: %s",
|
|
265
|
+
self.log_identifier,
|
|
266
|
+
e,
|
|
267
|
+
exc_info=True,
|
|
268
|
+
)
|
|
269
|
+
db.rollback()
|
|
270
|
+
return 0
|
|
271
|
+
finally:
|
|
272
|
+
db.close()
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service layer for handling user feedback on chat messages.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import TYPE_CHECKING, Callable
|
|
8
|
+
|
|
9
|
+
from solace_ai_connector.common.log import log
|
|
10
|
+
from sqlalchemy.orm import Session as DBSession
|
|
11
|
+
|
|
12
|
+
from ..repository.entities import Feedback
|
|
13
|
+
from ..repository.feedback_repository import FeedbackRepository
|
|
14
|
+
from ..shared import now_epoch_ms
|
|
15
|
+
from ..utils.stim_utils import create_stim_from_task_data
|
|
16
|
+
|
|
17
|
+
# The FeedbackPayload is defined in the router, this creates a forward reference
|
|
18
|
+
# which is resolved at runtime.
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..routers.feedback import FeedbackPayload
|
|
21
|
+
from ..component import WebUIBackendComponent
|
|
22
|
+
from ..repository.interfaces import ITaskRepository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FeedbackService:
|
|
26
|
+
"""Handles the business logic for processing user feedback."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
session_factory: Callable[[], DBSession] | None,
|
|
31
|
+
component: "WebUIBackendComponent",
|
|
32
|
+
task_repo: "ITaskRepository",
|
|
33
|
+
):
|
|
34
|
+
"""Initializes the FeedbackService."""
|
|
35
|
+
self.session_factory = session_factory
|
|
36
|
+
self.component = component
|
|
37
|
+
self.task_repo = task_repo
|
|
38
|
+
if self.session_factory:
|
|
39
|
+
log.info("FeedbackService initialized with database persistence.")
|
|
40
|
+
else:
|
|
41
|
+
log.info(
|
|
42
|
+
"FeedbackService initialized without database persistence (logging only)."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def process_feedback(self, payload: "FeedbackPayload", user_id: str):
|
|
46
|
+
"""
|
|
47
|
+
Processes and stores the feedback. If a repository is configured,
|
|
48
|
+
it saves to the database. Otherwise, it logs the feedback.
|
|
49
|
+
Also publishes feedback to a Solace topic if configured.
|
|
50
|
+
Additionally updates the corresponding task's metadata with the feedback.
|
|
51
|
+
"""
|
|
52
|
+
if self.session_factory:
|
|
53
|
+
task_id = getattr(payload, "task_id", None)
|
|
54
|
+
if not task_id:
|
|
55
|
+
log.error(
|
|
56
|
+
"Feedback payload is missing 'task_id'. Cannot save to database. Payload: %s",
|
|
57
|
+
payload.model_dump_json(by_alias=True),
|
|
58
|
+
)
|
|
59
|
+
# We can still try to publish the event without saving to DB
|
|
60
|
+
else:
|
|
61
|
+
feedback_entity = Feedback(
|
|
62
|
+
id=str(uuid.uuid4()),
|
|
63
|
+
session_id=payload.session_id,
|
|
64
|
+
task_id=task_id,
|
|
65
|
+
user_id=user_id,
|
|
66
|
+
rating=payload.feedback_type,
|
|
67
|
+
comment=payload.feedback_text,
|
|
68
|
+
created_time=now_epoch_ms(),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
db = self.session_factory()
|
|
72
|
+
try:
|
|
73
|
+
repo = FeedbackRepository(db)
|
|
74
|
+
repo.save(feedback_entity)
|
|
75
|
+
db.commit()
|
|
76
|
+
log.info(
|
|
77
|
+
"Feedback from user '%s' for task '%s' saved to database.",
|
|
78
|
+
user_id,
|
|
79
|
+
task_id,
|
|
80
|
+
)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
log.exception(
|
|
83
|
+
"Failed to save feedback for user '%s' to database: %s",
|
|
84
|
+
user_id,
|
|
85
|
+
e,
|
|
86
|
+
)
|
|
87
|
+
db.rollback()
|
|
88
|
+
finally:
|
|
89
|
+
db.close()
|
|
90
|
+
|
|
91
|
+
# Update task metadata with feedback
|
|
92
|
+
self._update_task_metadata_with_feedback(
|
|
93
|
+
task_id, user_id, payload.feedback_type, payload.feedback_text
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
log.warning(
|
|
97
|
+
"Feedback received but no database repository is configured. "
|
|
98
|
+
"Logging feedback only. Payload: %s",
|
|
99
|
+
payload.model_dump_json(by_alias=True),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# --- New event publishing logic ---
|
|
103
|
+
try:
|
|
104
|
+
await self._publish_feedback_event(payload, user_id)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
log.error(
|
|
107
|
+
"Failed to publish feedback event for user '%s': %s", user_id, e
|
|
108
|
+
)
|
|
109
|
+
# Do not re-raise, as the primary operation (DB save) may have succeeded.
|
|
110
|
+
|
|
111
|
+
def _update_task_metadata_with_feedback(
|
|
112
|
+
self, task_id: str, user_id: str, feedback_type: str, feedback_text: str | None
|
|
113
|
+
):
|
|
114
|
+
"""
|
|
115
|
+
Update the task's metadata with feedback information.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
task_id: The task ID to update
|
|
119
|
+
user_id: The user ID who submitted feedback
|
|
120
|
+
feedback_type: Type of feedback ("up" or "down")
|
|
121
|
+
feedback_text: Optional feedback text
|
|
122
|
+
"""
|
|
123
|
+
if not self.session_factory:
|
|
124
|
+
log.debug(
|
|
125
|
+
"No session factory available, skipping task metadata update for task %s",
|
|
126
|
+
task_id
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
db = self.session_factory()
|
|
131
|
+
try:
|
|
132
|
+
from ..repository.chat_task_repository import ChatTaskRepository
|
|
133
|
+
|
|
134
|
+
task_repo = ChatTaskRepository(db)
|
|
135
|
+
task = task_repo.find_by_id(task_id, user_id)
|
|
136
|
+
|
|
137
|
+
if task:
|
|
138
|
+
# Update feedback in task metadata
|
|
139
|
+
task.add_feedback(feedback_type, feedback_text)
|
|
140
|
+
task_repo.save(task)
|
|
141
|
+
db.commit()
|
|
142
|
+
log.info(
|
|
143
|
+
"Updated task metadata with feedback for task '%s' by user '%s'",
|
|
144
|
+
task_id,
|
|
145
|
+
user_id
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
log.warning(
|
|
149
|
+
"Task '%s' not found for user '%s', cannot update task metadata with feedback",
|
|
150
|
+
task_id,
|
|
151
|
+
user_id
|
|
152
|
+
)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
log.warning(
|
|
155
|
+
"Failed to update task metadata with feedback for task '%s': %s",
|
|
156
|
+
task_id,
|
|
157
|
+
e
|
|
158
|
+
)
|
|
159
|
+
db.rollback()
|
|
160
|
+
# Don't re-raise - feedback was already saved to feedback table
|
|
161
|
+
finally:
|
|
162
|
+
db.close()
|
|
163
|
+
|
|
164
|
+
async def _publish_feedback_event(self, payload: "FeedbackPayload", user_id: str):
|
|
165
|
+
"""Publishes the feedback as an event to the message broker if configured."""
|
|
166
|
+
log_id = f"[FeedbackPublisher:{payload.task_id}]"
|
|
167
|
+
config = self.component.get_config("feedback_publishing", {})
|
|
168
|
+
|
|
169
|
+
if not config.get("enabled", False):
|
|
170
|
+
log.debug("%s Feedback publishing is disabled. Skipping.", log_id)
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
# Construct base payload
|
|
174
|
+
event_payload = {
|
|
175
|
+
"feedback": {
|
|
176
|
+
"task_id": payload.task_id,
|
|
177
|
+
"session_id": payload.session_id,
|
|
178
|
+
"feedback_type": payload.feedback_type,
|
|
179
|
+
"feedback_text": payload.feedback_text,
|
|
180
|
+
"user_id": user_id,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
include_task_info = config.get("include_task_info", "none")
|
|
185
|
+
task_summary_data = None
|
|
186
|
+
|
|
187
|
+
if include_task_info == "summary":
|
|
188
|
+
log.debug("%s Including task summary.", log_id)
|
|
189
|
+
task_summary_data = self.task_repo.find_by_id(payload.task_id)
|
|
190
|
+
if task_summary_data:
|
|
191
|
+
event_payload["task_summary"] = task_summary_data.model_dump()
|
|
192
|
+
|
|
193
|
+
elif include_task_info == "stim":
|
|
194
|
+
log.debug("%s Including task stim data.", log_id)
|
|
195
|
+
task_with_events = self.task_repo.find_by_id_with_events(payload.task_id)
|
|
196
|
+
if task_with_events:
|
|
197
|
+
task, events = task_with_events
|
|
198
|
+
stim_data = create_stim_from_task_data(task, events)
|
|
199
|
+
event_payload["task_stim_data"] = stim_data
|
|
200
|
+
|
|
201
|
+
# Check payload size
|
|
202
|
+
max_size = config.get("max_payload_size_bytes", 9000000)
|
|
203
|
+
try:
|
|
204
|
+
payload_bytes = json.dumps(event_payload).encode("utf-8")
|
|
205
|
+
if len(payload_bytes) > max_size:
|
|
206
|
+
log.warning(
|
|
207
|
+
"%s Stim payload size (%d bytes) exceeds limit (%d bytes). Falling back to summary.",
|
|
208
|
+
log_id,
|
|
209
|
+
len(payload_bytes),
|
|
210
|
+
max_size,
|
|
211
|
+
)
|
|
212
|
+
# Fallback to summary
|
|
213
|
+
del event_payload["task_stim_data"]
|
|
214
|
+
task_summary_data = self.task_repo.find_by_id(payload.task_id)
|
|
215
|
+
if task_summary_data:
|
|
216
|
+
event_payload[
|
|
217
|
+
"task_summary"
|
|
218
|
+
] = task_summary_data.model_dump()
|
|
219
|
+
event_payload["truncation_details"] = {
|
|
220
|
+
"strategy": "fallback_to_summary",
|
|
221
|
+
"reason": "payload_too_large",
|
|
222
|
+
}
|
|
223
|
+
except Exception as e:
|
|
224
|
+
log.error("%s Error checking payload size: %s", log_id, e)
|
|
225
|
+
# If we can't check size, better to not send a potentially huge message
|
|
226
|
+
if "task_stim_data" in event_payload:
|
|
227
|
+
del event_payload["task_stim_data"]
|
|
228
|
+
|
|
229
|
+
# Publish the event
|
|
230
|
+
topic = config.get("topic", "sam/feedback/v1")
|
|
231
|
+
try:
|
|
232
|
+
log.info("%s Publishing feedback event to topic '%s'", log_id, topic)
|
|
233
|
+
self.component.publish_a2a(topic, event_payload)
|
|
234
|
+
except Exception as e:
|
|
235
|
+
log.error(
|
|
236
|
+
"%s Failed to publish feedback event to topic '%s': %s",
|
|
237
|
+
log_id,
|
|
238
|
+
topic,
|
|
239
|
+
e,
|
|
240
|
+
)
|
|
241
|
+
# Don't re-raise, this is a non-critical operation.
|
|
@@ -9,86 +9,6 @@ from solace_ai_connector.common.log import log
|
|
|
9
9
|
from ....common.services.identity_service import BaseIdentityService
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class PeopleService:
|
|
13
|
-
"""
|
|
14
|
-
Provides methods for searching and retrieving user information,
|
|
15
|
-
acting as a layer on top of the configured IdentityService.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, identity_service: Optional[BaseIdentityService]):
|
|
19
|
-
"""
|
|
20
|
-
Initializes the PeopleService.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
identity_service: An instance of a configured BaseIdentityService, or None.
|
|
24
|
-
"""
|
|
25
|
-
self._identity_service = identity_service
|
|
26
|
-
self.log_identifier = "[PeopleService]"
|
|
27
|
-
log.info(
|
|
28
|
-
"%s Initialized with Identity Service: %s",
|
|
29
|
-
self.log_identifier,
|
|
30
|
-
identity_service is not None,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
async def search_for_users(
|
|
34
|
-
self, query: str, limit: int = 10
|
|
35
|
-
) -> List[Dict[str, Any]]:
|
|
36
|
-
"""
|
|
37
|
-
Searches for users via the identity service.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
query: The search query string.
|
|
41
|
-
limit: The maximum number of results to return.
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
|
-
A list of user profile dictionaries.
|
|
45
|
-
"""
|
|
46
|
-
if not self._identity_service:
|
|
47
|
-
log.warning(
|
|
48
|
-
"%s Search requested but no identity service is configured.",
|
|
49
|
-
self.log_identifier,
|
|
50
|
-
)
|
|
51
|
-
return []
|
|
52
|
-
|
|
53
|
-
if not query or len(query) < 2:
|
|
54
|
-
return []
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
log.debug(
|
|
58
|
-
"%s Searching for users with query: '%s', limit: %d",
|
|
59
|
-
self.log_identifier,
|
|
60
|
-
query,
|
|
61
|
-
limit,
|
|
62
|
-
)
|
|
63
|
-
results = await self._identity_service.search_users(query, limit)
|
|
64
|
-
log.info(
|
|
65
|
-
"%s Found %d users for query: '%s'",
|
|
66
|
-
self.log_identifier,
|
|
67
|
-
len(results),
|
|
68
|
-
query,
|
|
69
|
-
)
|
|
70
|
-
return results
|
|
71
|
-
except Exception as e:
|
|
72
|
-
log.exception(
|
|
73
|
-
"%s Error during user search for query '%s': %s",
|
|
74
|
-
self.log_identifier,
|
|
75
|
-
query,
|
|
76
|
-
e,
|
|
77
|
-
)
|
|
78
|
-
return []
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"""
|
|
82
|
-
Service layer for handling people-related operations, such as searching for users.
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
from typing import Any, Dict, List, Optional
|
|
86
|
-
|
|
87
|
-
from solace_ai_connector.common.log import log
|
|
88
|
-
|
|
89
|
-
from ....common.services.identity_service import BaseIdentityService
|
|
90
|
-
|
|
91
|
-
|
|
92
12
|
class PeopleService:
|
|
93
13
|
"""
|
|
94
14
|
Provides methods for searching and retrieving user information,
|