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.

Files changed (189) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +3 -4
  2. solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
  3. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
  4. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +1 -0
  5. solace_agent_mesh/agent/adk/callbacks.py +51 -2
  6. solace_agent_mesh/agent/adk/models/lite_llm.py +1 -0
  7. solace_agent_mesh/agent/adk/models/models_llm.txt +1 -2
  8. solace_agent_mesh/agent/adk/services.py +30 -15
  9. solace_agent_mesh/agent/adk/setup.py +4 -0
  10. solace_agent_mesh/agent/agent_llm.txt +1 -1
  11. solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
  12. solace_agent_mesh/agent/protocol/event_handlers.py +2 -13
  13. solace_agent_mesh/agent/protocol/protocol_llm.txt +15 -2
  14. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
  15. solace_agent_mesh/agent/sac/component.py +51 -21
  16. solace_agent_mesh/agent/sac/sac_llm.txt +15 -1
  17. solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
  18. solace_agent_mesh/agent/sac/task_execution_context.py +73 -0
  19. solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
  20. solace_agent_mesh/agent/tools/tools_llm.txt +148 -154
  21. solace_agent_mesh/agent/tools/tools_llm_detail.txt +274 -0
  22. solace_agent_mesh/agent/utils/utils_llm.txt +1 -1
  23. solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
  24. solace_agent_mesh/assets/docs/404.html +3 -3
  25. solace_agent_mesh/assets/docs/assets/js/0e682baa.d54b8668.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/483cef9a.bf9398af.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/{main.dc155742.js → main.0c149855.js} +2 -2
  28. solace_agent_mesh/assets/docs/assets/js/{runtime~main.0d2ff2b6.js → runtime~main.c66557e4.js} +1 -1
  29. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/rbac-setup-guilde/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +8 -4
  32. 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
  33. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/litellm_models/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  50. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +7 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  57. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  60. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  61. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  62. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  63. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
  64. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  65. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
  66. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  67. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  68. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  69. solace_agent_mesh/assets/docs/lunr-index-1760032255022.json +1 -0
  70. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  71. solace_agent_mesh/assets/docs/search-doc-1760032255022.json +1 -0
  72. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  73. solace_agent_mesh/cli/__init__.py +1 -1
  74. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-j1LW-wlq.js → authCallback-DwrxZE0E.js} +1 -1
  75. solace_agent_mesh/client/webui/frontend/static/assets/{client-B9p_nFNA.js → client-DarGQzyw.js} +1 -1
  76. solace_agent_mesh/client/webui/frontend/static/assets/main-CZbpmwfA.css +1 -0
  77. solace_agent_mesh/client/webui/frontend/static/assets/main-C__uuUkB.js +339 -0
  78. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CS5YMf8a.js → vendor-BKIeiHj_.js} +80 -70
  79. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  80. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  81. solace_agent_mesh/common/a2a/a2a_llm.txt +1 -1
  82. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
  83. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +1 -1
  84. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
  85. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +23 -0
  86. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +93 -15
  87. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +23 -0
  88. solace_agent_mesh/common/common_llm.txt +24 -39
  89. solace_agent_mesh/common/common_llm_detail.txt +2562 -0
  90. solace_agent_mesh/common/data_parts.py +9 -1
  91. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
  92. solace_agent_mesh/common/sac/sac_llm.txt +1 -1
  93. solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
  94. solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
  95. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
  96. solace_agent_mesh/common/services/identity_service.py +7 -4
  97. solace_agent_mesh/common/services/providers/local_file_identity_service.py +4 -2
  98. solace_agent_mesh/common/services/services_llm.txt +57 -6
  99. solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
  100. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
  101. solace_agent_mesh/common/utils/utils_llm.txt +75 -87
  102. solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
  103. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
  104. solace_agent_mesh/gateway/base/app.py +1 -1
  105. solace_agent_mesh/gateway/base/base_llm.txt +1 -1
  106. solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
  107. solace_agent_mesh/gateway/base/component.py +1 -1
  108. solace_agent_mesh/gateway/gateway_llm.txt +242 -235
  109. solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
  110. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +295 -0
  111. solace_agent_mesh/gateway/http_sse/alembic/env.py +10 -1
  112. solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
  113. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +155 -0
  114. solace_agent_mesh/gateway/http_sse/alembic.ini +1 -1
  115. solace_agent_mesh/gateway/http_sse/app.py +148 -2
  116. solace_agent_mesh/gateway/http_sse/component.py +368 -60
  117. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +46 -6
  118. solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +108 -0
  119. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +1 -1
  120. solace_agent_mesh/gateway/http_sse/dependencies.py +116 -26
  121. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +172 -172
  122. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
  123. solace_agent_mesh/gateway/http_sse/main.py +146 -41
  124. solace_agent_mesh/gateway/http_sse/repository/__init__.py +3 -12
  125. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +103 -0
  126. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +5 -3
  127. solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
  128. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +263 -0
  129. solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
  130. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -16
  131. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +25 -0
  132. solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
  133. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +81 -0
  134. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +73 -18
  135. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -5
  136. solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
  137. solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
  138. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +266 -0
  139. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +3 -3
  140. solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
  141. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +32 -0
  142. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +340 -0
  143. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +4 -53
  144. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +173 -0
  145. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1 -1
  146. solace_agent_mesh/gateway/http_sse/routers/config.py +26 -4
  147. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +346 -0
  148. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +3 -3
  149. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +83 -0
  150. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +2 -10
  151. solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
  152. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +5 -3
  153. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +107 -0
  154. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +1 -15
  155. solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
  156. solace_agent_mesh/gateway/http_sse/routers/feedback.py +37 -0
  157. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +255 -204
  158. solace_agent_mesh/gateway/http_sse/routers/sessions.py +220 -40
  159. solace_agent_mesh/gateway/http_sse/routers/tasks.py +168 -42
  160. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +272 -0
  161. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +241 -0
  162. solace_agent_mesh/gateway/http_sse/services/people_service.py +0 -80
  163. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +177 -13
  164. solace_agent_mesh/gateway/http_sse/services/session_service.py +151 -84
  165. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +317 -0
  166. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +25 -14
  167. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +285 -0
  168. solace_agent_mesh/gateway/http_sse/shared/types.py +7 -0
  169. solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
  170. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +32 -0
  171. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
  172. solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
  173. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
  174. {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/METADATA +1 -1
  175. {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/RECORD +179 -131
  176. solace_agent_mesh/agent/adk/invocation_monitor.py +0 -295
  177. solace_agent_mesh/assets/docs/assets/js/0e682baa.d054e1d8.js +0 -1
  178. solace_agent_mesh/assets/docs/assets/js/483cef9a.4736f2d8.js +0 -1
  179. solace_agent_mesh/assets/docs/lunr-index-1759514789087.json +0 -1
  180. solace_agent_mesh/assets/docs/search-doc-1759514789087.json +0 -1
  181. solace_agent_mesh/client/webui/frontend/static/assets/main-ChRwcV89.css +0 -1
  182. solace_agent_mesh/client/webui/frontend/static/assets/main-DnnE01OM.js +0 -339
  183. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +0 -41
  184. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +0 -84
  185. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +0 -45
  186. /solace_agent_mesh/assets/docs/assets/js/{main.dc155742.js.LICENSE.txt → main.0c149855.js.LICENSE.txt} +0 -0
  187. {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/WHEEL +0 -0
  188. {solace_agent_mesh-1.4.11.dist-info → solace_agent_mesh-1.5.0.dist-info}/entry_points.txt +0 -0
  189. {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,