solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.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/callbacks.py +0 -5
- solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +40 -1
- solace_agent_mesh/agent/proxies/__init__.py +0 -0
- solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
- solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
- solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/base/app.py +99 -0
- solace_agent_mesh/agent/proxies/base/component.py +619 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
- solace_agent_mesh/agent/sac/app.py +9 -3
- solace_agent_mesh/agent/sac/component.py +160 -8
- solace_agent_mesh/agent/tools/audio_tools.py +125 -8
- solace_agent_mesh/agent/tools/web_tools.py +10 -5
- solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/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/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
- solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
- 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/__init__.py +24 -0
- solace_agent_mesh/common/a2a/artifact.py +39 -0
- solace_agent_mesh/common/a2a/events.py +29 -0
- solace_agent_mesh/common/a2a/message.py +68 -0
- solace_agent_mesh/common/a2a/protocol.py +73 -1
- solace_agent_mesh/common/agent_registry.py +83 -3
- solace_agent_mesh/common/constants.py +3 -1
- solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/evaluation/evaluator.py +128 -104
- solace_agent_mesh/evaluation/message_organizer.py +116 -110
- solace_agent_mesh/evaluation/report_data_processor.py +84 -86
- solace_agent_mesh/evaluation/report_generator.py +73 -79
- solace_agent_mesh/evaluation/run.py +421 -235
- solace_agent_mesh/evaluation/shared/__init__.py +92 -0
- solace_agent_mesh/evaluation/shared/constants.py +47 -0
- solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
- solace_agent_mesh/evaluation/shared/helpers.py +35 -0
- solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
- solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
- solace_agent_mesh/evaluation/subscriber.py +111 -232
- solace_agent_mesh/evaluation/summary_builder.py +227 -117
- solace_agent_mesh/gateway/base/app.py +1 -1
- solace_agent_mesh/gateway/base/component.py +8 -1
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/component.py +98 -2
- solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
- solace_agent_mesh/gateway/http_sse/main.py +2 -1
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
- solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
- solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
- solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
- solace_agent_mesh/templates/logging_config_template.ini +10 -6
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
- solace_agent_mesh/templates/shared_config.yaml +40 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
- solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
- solace_agent_mesh/evaluation/config_loader.py +0 -657
- solace_agent_mesh/evaluation/test_case_loader.py +0 -714
- /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -272,7 +272,7 @@ class BaseGatewayApp(App):
|
|
|
272
272
|
broker_config["queue_name"] = (
|
|
273
273
|
f"{self.namespace.strip('/')}/q/gdk/gateway/{self.gateway_id}"
|
|
274
274
|
)
|
|
275
|
-
broker_config["temporary_queue"] = True
|
|
275
|
+
broker_config["temporary_queue"] = modified_app_info.get("broker", {}).get("temporary_queue", True)
|
|
276
276
|
log.debug(
|
|
277
277
|
"Injected broker settings for gateway '%s': %s",
|
|
278
278
|
self.gateway_id,
|
|
@@ -282,6 +282,9 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
282
282
|
"user_id_for_a2a", user_identity.get("id")
|
|
283
283
|
)
|
|
284
284
|
|
|
285
|
+
system_purpose = self.get_config("system_purpose", "")
|
|
286
|
+
response_format = self.get_config("response_format", "")
|
|
287
|
+
|
|
285
288
|
if not a2a_session_id:
|
|
286
289
|
a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
|
|
287
290
|
log.warning(
|
|
@@ -291,7 +294,11 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
291
294
|
)
|
|
292
295
|
external_request_context["a2a_session_id"] = a2a_session_id
|
|
293
296
|
|
|
294
|
-
a2a_metadata = {
|
|
297
|
+
a2a_metadata = {
|
|
298
|
+
"agent_name": target_agent_name,
|
|
299
|
+
"system_purpose": system_purpose,
|
|
300
|
+
"response_format": response_format,
|
|
301
|
+
}
|
|
295
302
|
invoked_artifacts = external_request_context.get("invoked_with_artifacts")
|
|
296
303
|
if invoked_artifacts:
|
|
297
304
|
a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
|
solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""add performance indexes for query optimization
|
|
2
|
+
|
|
3
|
+
Revision ID: 20251015_session_idx
|
|
4
|
+
Revises: 98882922fa59
|
|
5
|
+
Create Date: 2025-10-15
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
revision: str = "20251015_session_idx"
|
|
15
|
+
down_revision: str | Sequence[str] | None = "98882922fa59"
|
|
16
|
+
branch_labels: str | Sequence[str] | None = None
|
|
17
|
+
depends_on: str | Sequence[str] | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
"""Add composite indexes for optimal query performance."""
|
|
22
|
+
|
|
23
|
+
op.create_index("ix_sessions_user_id", "sessions", ["user_id"], unique=False)
|
|
24
|
+
|
|
25
|
+
op.create_index(
|
|
26
|
+
"ix_sessions_user_updated",
|
|
27
|
+
"sessions",
|
|
28
|
+
["user_id", sa.text("updated_time DESC")],
|
|
29
|
+
unique=False,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
op.create_index(
|
|
33
|
+
"ix_chat_tasks_session_user_created",
|
|
34
|
+
"chat_tasks",
|
|
35
|
+
["session_id", "user_id", "created_time"],
|
|
36
|
+
unique=False,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
op.create_index(
|
|
40
|
+
"ix_tasks_user_start_time",
|
|
41
|
+
"tasks",
|
|
42
|
+
["user_id", sa.text("start_time DESC")],
|
|
43
|
+
unique=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
op.create_index(
|
|
47
|
+
"ix_task_events_task_created",
|
|
48
|
+
"task_events",
|
|
49
|
+
["task_id", "created_time"],
|
|
50
|
+
unique=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
op.drop_index("ix_tasks_initial_request_text", table_name="tasks")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def downgrade() -> None:
|
|
57
|
+
"""Remove performance indexes."""
|
|
58
|
+
|
|
59
|
+
op.create_index(
|
|
60
|
+
op.f("ix_tasks_initial_request_text"),
|
|
61
|
+
"tasks",
|
|
62
|
+
["initial_request_text"],
|
|
63
|
+
unique=False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
op.drop_index("ix_task_events_task_created", table_name="task_events")
|
|
67
|
+
op.drop_index("ix_tasks_user_start_time", table_name="tasks")
|
|
68
|
+
op.drop_index("ix_chat_tasks_session_user_created", table_name="chat_tasks")
|
|
69
|
+
op.drop_index("ix_sessions_user_updated", table_name="sessions")
|
|
70
|
+
op.drop_index("ix_sessions_user_id", table_name="sessions")
|
|
@@ -146,6 +146,27 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
146
146
|
timer_id=self._sse_cleanup_timer_id,
|
|
147
147
|
interval_ms=cleanup_interval_sec * 1000,
|
|
148
148
|
)
|
|
149
|
+
|
|
150
|
+
# Set up health check timer for agent registry
|
|
151
|
+
from ...common.constants import HEALTH_CHECK_INTERVAL_SECONDS
|
|
152
|
+
self.health_check_timer_id = f"agent_health_check_{self.gateway_id}"
|
|
153
|
+
health_check_interval_seconds = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
154
|
+
if health_check_interval_seconds > 0:
|
|
155
|
+
log.info(
|
|
156
|
+
"%s Scheduling agent health check every %d seconds.",
|
|
157
|
+
self.log_identifier,
|
|
158
|
+
health_check_interval_seconds,
|
|
159
|
+
)
|
|
160
|
+
self.add_timer(
|
|
161
|
+
delay_ms=health_check_interval_seconds * 1000,
|
|
162
|
+
timer_id=self.health_check_timer_id,
|
|
163
|
+
interval_ms=health_check_interval_seconds * 1000,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
log.warning(
|
|
167
|
+
"%s Agent health check interval not configured or invalid, health checks will not run periodically.",
|
|
168
|
+
self.log_identifier,
|
|
169
|
+
)
|
|
149
170
|
|
|
150
171
|
session_config = self._resolve_session_config()
|
|
151
172
|
if session_config.get("type") == "sql":
|
|
@@ -265,6 +286,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
265
286
|
log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
|
|
266
287
|
self.sse_event_buffer.cleanup_stale_buffers()
|
|
267
288
|
return
|
|
289
|
+
elif event.data.get("timer_id") == self.health_check_timer_id:
|
|
290
|
+
log.debug("%s Agent health check timer triggered.", self.log_identifier)
|
|
291
|
+
self._check_agent_health()
|
|
292
|
+
return
|
|
268
293
|
|
|
269
294
|
if timer_id == self._data_retention_timer_id:
|
|
270
295
|
log.debug("%s Data retention cleanup timer triggered.", self.log_identifier)
|
|
@@ -354,7 +379,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
354
379
|
),
|
|
355
380
|
"retry_interval": main_broker_config.get("retry_interval"),
|
|
356
381
|
"retry_count": main_broker_config.get("retry_count"),
|
|
357
|
-
"temporary_queue": True,
|
|
382
|
+
"temporary_queue": main_broker_config.get("temporary_queue", True),
|
|
358
383
|
},
|
|
359
384
|
}
|
|
360
385
|
|
|
@@ -486,7 +511,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
486
511
|
),
|
|
487
512
|
"retry_interval": main_broker_config.get("retry_interval"),
|
|
488
513
|
"retry_count": main_broker_config.get("retry_count"),
|
|
489
|
-
"temporary_queue": True,
|
|
514
|
+
"temporary_queue": main_broker_config.get("temporary_queue", True),
|
|
490
515
|
},
|
|
491
516
|
}
|
|
492
517
|
|
|
@@ -1404,6 +1429,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1404
1429
|
self.data_retention_service = None
|
|
1405
1430
|
log.info("%s Data retention service cleaned up.", self.log_identifier)
|
|
1406
1431
|
|
|
1432
|
+
self.cancel_timer(self.health_check_timer_id)
|
|
1407
1433
|
log.info("%s Cleaning up visualization resources...", self.log_identifier)
|
|
1408
1434
|
if self._visualization_message_queue:
|
|
1409
1435
|
self._visualization_message_queue.put(None)
|
|
@@ -1729,6 +1755,76 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1729
1755
|
|
|
1730
1756
|
def get_agent_registry(self) -> AgentRegistry:
|
|
1731
1757
|
return self.agent_registry
|
|
1758
|
+
|
|
1759
|
+
def _check_agent_health(self):
|
|
1760
|
+
"""
|
|
1761
|
+
Checks the health of peer agents and de-registers unresponsive ones.
|
|
1762
|
+
This is called periodically by the health check timer.
|
|
1763
|
+
Uses TTL-based expiration to determine if an agent is unresponsive.
|
|
1764
|
+
"""
|
|
1765
|
+
|
|
1766
|
+
log.debug("%s Performing agent health check...", self.log_identifier)
|
|
1767
|
+
|
|
1768
|
+
# Get TTL from configuration or use default from constants
|
|
1769
|
+
from ...common.constants import HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
|
|
1770
|
+
ttl_seconds = self.get_config("agent_health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
|
|
1771
|
+
health_check_interval = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
1772
|
+
|
|
1773
|
+
log.debug(
|
|
1774
|
+
"%s Health check configuration: interval=%d seconds, TTL=%d seconds",
|
|
1775
|
+
self.log_identifier,
|
|
1776
|
+
health_check_interval,
|
|
1777
|
+
ttl_seconds
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
# Validate configuration values
|
|
1781
|
+
if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
|
|
1782
|
+
log.error(
|
|
1783
|
+
"%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
|
|
1784
|
+
self.log_identifier,
|
|
1785
|
+
ttl_seconds,
|
|
1786
|
+
health_check_interval
|
|
1787
|
+
)
|
|
1788
|
+
raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
|
|
1789
|
+
|
|
1790
|
+
# Get all agent names from the registry
|
|
1791
|
+
agent_names = self.agent_registry.get_agent_names()
|
|
1792
|
+
total_agents = len(agent_names)
|
|
1793
|
+
agents_to_deregister = []
|
|
1794
|
+
|
|
1795
|
+
log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
|
|
1796
|
+
|
|
1797
|
+
for agent_name in agent_names:
|
|
1798
|
+
# Check if the agent's TTL has expired
|
|
1799
|
+
is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
|
|
1800
|
+
|
|
1801
|
+
if is_expired:
|
|
1802
|
+
log.warning(
|
|
1803
|
+
"%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
|
|
1804
|
+
self.log_identifier,
|
|
1805
|
+
agent_name,
|
|
1806
|
+
time_since_last_seen,
|
|
1807
|
+
ttl_seconds
|
|
1808
|
+
)
|
|
1809
|
+
agents_to_deregister.append(agent_name)
|
|
1810
|
+
|
|
1811
|
+
# De-register unresponsive agents
|
|
1812
|
+
for agent_name in agents_to_deregister:
|
|
1813
|
+
self._deregister_agent(agent_name)
|
|
1814
|
+
|
|
1815
|
+
log.info(
|
|
1816
|
+
"%s Agent health check completed. Total agents: %d, De-registered: %d",
|
|
1817
|
+
self.log_identifier,
|
|
1818
|
+
total_agents,
|
|
1819
|
+
len(agents_to_deregister)
|
|
1820
|
+
)
|
|
1821
|
+
|
|
1822
|
+
def _deregister_agent(self, agent_name: str):
|
|
1823
|
+
"""
|
|
1824
|
+
De-registers an agent from the registry and publishes a de-registration event.
|
|
1825
|
+
"""
|
|
1826
|
+
# Remove from registry
|
|
1827
|
+
self.agent_registry.remove_agent(agent_name)
|
|
1732
1828
|
|
|
1733
1829
|
def get_sse_manager(self) -> SSEManager:
|
|
1734
1830
|
return self.sse_manager
|
|
@@ -243,10 +243,10 @@ def get_people_service(
|
|
|
243
243
|
return PeopleService(identity_service=identity_service)
|
|
244
244
|
|
|
245
245
|
|
|
246
|
-
def get_task_repository(
|
|
246
|
+
def get_task_repository() -> ITaskRepository:
|
|
247
247
|
"""FastAPI dependency to get an instance of TaskRepository."""
|
|
248
248
|
log.debug("[Dependencies] get_task_repository called")
|
|
249
|
-
return TaskRepository(
|
|
249
|
+
return TaskRepository()
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
def get_feedback_service(
|
|
@@ -525,9 +525,9 @@ def get_session_validator(
|
|
|
525
525
|
try:
|
|
526
526
|
db = SessionLocal()
|
|
527
527
|
try:
|
|
528
|
-
session_repository = SessionRepository(
|
|
528
|
+
session_repository = SessionRepository()
|
|
529
529
|
session_domain = session_repository.find_user_session(
|
|
530
|
-
session_id, user_id
|
|
530
|
+
db, session_id, user_id
|
|
531
531
|
)
|
|
532
532
|
return session_domain is not None
|
|
533
533
|
finally:
|
|
@@ -97,7 +97,8 @@ async def _get_user_info(
|
|
|
97
97
|
|
|
98
98
|
def _extract_user_identifier(user_info: dict) -> str:
|
|
99
99
|
user_identifier = (
|
|
100
|
-
user_info.get("
|
|
100
|
+
user_info.get("user_id") # internal /user_info endpoint format maps identifier to user_id
|
|
101
|
+
or user_info.get("sub")
|
|
101
102
|
or user_info.get("client_id")
|
|
102
103
|
or user_info.get("username")
|
|
103
104
|
or user_info.get("oid")
|
|
@@ -16,12 +16,9 @@ from .models import ChatTaskModel
|
|
|
16
16
|
class ChatTaskRepository(IChatTaskRepository):
|
|
17
17
|
"""SQLAlchemy implementation of chat task repository."""
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
self.db = db
|
|
21
|
-
|
|
22
|
-
def save(self, task: ChatTask) -> ChatTask:
|
|
19
|
+
def save(self, session: DBSession, task: ChatTask) -> ChatTask:
|
|
23
20
|
"""Save or update a chat task (upsert)."""
|
|
24
|
-
existing =
|
|
21
|
+
existing = session.query(ChatTaskModel).filter(
|
|
25
22
|
ChatTaskModel.id == task.id
|
|
26
23
|
).first()
|
|
27
24
|
|
|
@@ -43,12 +40,12 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
43
40
|
created_time=task.created_time,
|
|
44
41
|
updated_time=task.updated_time
|
|
45
42
|
)
|
|
46
|
-
|
|
43
|
+
session.add(model)
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
session.flush()
|
|
49
46
|
|
|
50
47
|
# Reload to get updated values
|
|
51
|
-
model =
|
|
48
|
+
model = session.query(ChatTaskModel).filter(
|
|
52
49
|
ChatTaskModel.id == task.id
|
|
53
50
|
).first()
|
|
54
51
|
|
|
@@ -56,11 +53,12 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
56
53
|
|
|
57
54
|
def find_by_session(
|
|
58
55
|
self,
|
|
56
|
+
session: DBSession,
|
|
59
57
|
session_id: SessionId,
|
|
60
58
|
user_id: UserId
|
|
61
59
|
) -> List[ChatTask]:
|
|
62
60
|
"""Find all tasks for a session."""
|
|
63
|
-
models =
|
|
61
|
+
models = session.query(ChatTaskModel).filter(
|
|
64
62
|
ChatTaskModel.session_id == session_id,
|
|
65
63
|
ChatTaskModel.user_id == user_id
|
|
66
64
|
).order_by(ChatTaskModel.created_time.asc()).all()
|
|
@@ -69,23 +67,24 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
69
67
|
|
|
70
68
|
def find_by_id(
|
|
71
69
|
self,
|
|
70
|
+
session: DBSession,
|
|
72
71
|
task_id: str,
|
|
73
72
|
user_id: UserId
|
|
74
73
|
) -> Optional[ChatTask]:
|
|
75
74
|
"""Find a specific task."""
|
|
76
|
-
model =
|
|
75
|
+
model = session.query(ChatTaskModel).filter(
|
|
77
76
|
ChatTaskModel.id == task_id,
|
|
78
77
|
ChatTaskModel.user_id == user_id
|
|
79
78
|
).first()
|
|
80
79
|
|
|
81
80
|
return self._model_to_entity(model) if model else None
|
|
82
81
|
|
|
83
|
-
def delete_by_session(self, session_id: SessionId) -> bool:
|
|
82
|
+
def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
|
|
84
83
|
"""Delete all tasks for a session."""
|
|
85
|
-
result =
|
|
84
|
+
result = session.query(ChatTaskModel).filter(
|
|
86
85
|
ChatTaskModel.session_id == session_id
|
|
87
86
|
).delete()
|
|
88
|
-
|
|
87
|
+
session.flush()
|
|
89
88
|
return result > 0
|
|
90
89
|
|
|
91
90
|
def _model_to_entity(self, model: ChatTaskModel) -> ChatTask:
|
|
@@ -12,10 +12,7 @@ from .models import FeedbackModel
|
|
|
12
12
|
class FeedbackRepository(IFeedbackRepository):
|
|
13
13
|
"""SQLAlchemy implementation of feedback repository."""
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
self.db = db
|
|
17
|
-
|
|
18
|
-
def save(self, feedback: Feedback) -> Feedback:
|
|
15
|
+
def save(self, session: DBSession, feedback: Feedback) -> Feedback:
|
|
19
16
|
"""Save feedback."""
|
|
20
17
|
model = FeedbackModel(
|
|
21
18
|
id=feedback.id,
|
|
@@ -26,12 +23,12 @@ class FeedbackRepository(IFeedbackRepository):
|
|
|
26
23
|
comment=feedback.comment,
|
|
27
24
|
created_time=feedback.created_time,
|
|
28
25
|
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
session.add(model)
|
|
27
|
+
session.flush()
|
|
28
|
+
session.refresh(model)
|
|
32
29
|
return self._model_to_entity(model)
|
|
33
30
|
|
|
34
|
-
def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
|
|
31
|
+
def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
|
|
35
32
|
"""
|
|
36
33
|
Delete feedback records older than the cutoff time.
|
|
37
34
|
Uses batch deletion to avoid long-running transactions.
|
|
@@ -44,36 +41,36 @@ class FeedbackRepository(IFeedbackRepository):
|
|
|
44
41
|
Total number of feedback records deleted
|
|
45
42
|
"""
|
|
46
43
|
total_deleted = 0
|
|
47
|
-
|
|
44
|
+
|
|
48
45
|
while True:
|
|
49
46
|
# Find a batch of feedback IDs to delete
|
|
50
47
|
feedback_ids_to_delete = (
|
|
51
|
-
|
|
48
|
+
session.query(FeedbackModel.id)
|
|
52
49
|
.filter(FeedbackModel.created_time < cutoff_time_ms)
|
|
53
50
|
.limit(batch_size)
|
|
54
51
|
.all()
|
|
55
52
|
)
|
|
56
|
-
|
|
53
|
+
|
|
57
54
|
if not feedback_ids_to_delete:
|
|
58
55
|
break
|
|
59
|
-
|
|
56
|
+
|
|
60
57
|
# Extract IDs from the result tuples
|
|
61
58
|
ids = [feedback_id[0] for feedback_id in feedback_ids_to_delete]
|
|
62
|
-
|
|
59
|
+
|
|
63
60
|
# Delete this batch
|
|
64
61
|
deleted_count = (
|
|
65
|
-
|
|
62
|
+
session.query(FeedbackModel)
|
|
66
63
|
.filter(FeedbackModel.id.in_(ids))
|
|
67
64
|
.delete(synchronize_session=False)
|
|
68
65
|
)
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
|
|
67
|
+
session.commit()
|
|
71
68
|
total_deleted += deleted_count
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
# If we deleted fewer than batch_size, we're done
|
|
74
71
|
if deleted_count < batch_size:
|
|
75
72
|
break
|
|
76
|
-
|
|
73
|
+
|
|
77
74
|
return total_deleted
|
|
78
75
|
|
|
79
76
|
def _model_to_entity(self, model: FeedbackModel) -> Feedback:
|
|
@@ -4,8 +4,10 @@ Repository interfaces defining contracts for data access.
|
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
from sqlalchemy.orm import Session as DBSession
|
|
7
8
|
|
|
8
|
-
from ..shared.
|
|
9
|
+
from ..shared.pagination import PaginationParams
|
|
10
|
+
from ..shared.types import SessionId, UserId
|
|
9
11
|
from .entities import Feedback, Session, Task, TaskEvent
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
@@ -14,33 +16,33 @@ if TYPE_CHECKING:
|
|
|
14
16
|
|
|
15
17
|
class ISessionRepository(ABC):
|
|
16
18
|
"""Interface for session data access operations."""
|
|
17
|
-
|
|
19
|
+
|
|
18
20
|
@abstractmethod
|
|
19
21
|
def find_by_user(
|
|
20
|
-
self, user_id: UserId, pagination: PaginationParams | None = None
|
|
22
|
+
self, session: DBSession, user_id: UserId, pagination: PaginationParams | None = None
|
|
21
23
|
) -> list[Session]:
|
|
22
24
|
"""Find all sessions for a specific user."""
|
|
23
25
|
pass
|
|
24
26
|
|
|
25
27
|
@abstractmethod
|
|
26
|
-
def count_by_user(self, user_id: UserId) -> int:
|
|
28
|
+
def count_by_user(self, session: DBSession, user_id: UserId) -> int:
|
|
27
29
|
"""Count total sessions for a specific user."""
|
|
28
30
|
pass
|
|
29
31
|
|
|
30
32
|
@abstractmethod
|
|
31
33
|
def find_user_session(
|
|
32
|
-
self, session_id: SessionId, user_id: UserId
|
|
34
|
+
self, session: DBSession, session_id: SessionId, user_id: UserId
|
|
33
35
|
) -> Session | None:
|
|
34
36
|
"""Find a specific session belonging to a user."""
|
|
35
37
|
pass
|
|
36
38
|
|
|
37
39
|
@abstractmethod
|
|
38
|
-
def save(self, session: Session) -> Session:
|
|
40
|
+
def save(self, session: DBSession, session_obj: Session) -> Session:
|
|
39
41
|
"""Save or update a session."""
|
|
40
42
|
pass
|
|
41
43
|
|
|
42
44
|
@abstractmethod
|
|
43
|
-
def delete(self, session_id: SessionId, user_id: UserId) -> bool:
|
|
45
|
+
def delete(self, session: DBSession, session_id: SessionId, user_id: UserId) -> bool:
|
|
44
46
|
"""Delete a session belonging to a user."""
|
|
45
47
|
pass
|
|
46
48
|
|
|
@@ -49,28 +51,31 @@ class ITaskRepository(ABC):
|
|
|
49
51
|
"""Interface for task data access operations."""
|
|
50
52
|
|
|
51
53
|
@abstractmethod
|
|
52
|
-
def save_task(self, task: Task) -> Task:
|
|
54
|
+
def save_task(self, session: DBSession, task: Task) -> Task:
|
|
53
55
|
"""Create or update a task."""
|
|
54
56
|
pass
|
|
55
57
|
|
|
56
58
|
@abstractmethod
|
|
57
|
-
def save_event(self, event: TaskEvent) -> TaskEvent:
|
|
59
|
+
def save_event(self, session: DBSession, event: TaskEvent) -> TaskEvent:
|
|
58
60
|
"""Save a task event."""
|
|
59
61
|
pass
|
|
60
62
|
|
|
61
63
|
@abstractmethod
|
|
62
|
-
def find_by_id(self, task_id: str) -> Task | None:
|
|
64
|
+
def find_by_id(self, session: DBSession, task_id: str) -> Task | None:
|
|
63
65
|
"""Find a task by its ID."""
|
|
64
66
|
pass
|
|
65
67
|
|
|
66
68
|
@abstractmethod
|
|
67
|
-
def find_by_id_with_events(
|
|
69
|
+
def find_by_id_with_events(
|
|
70
|
+
self, session: DBSession, task_id: str
|
|
71
|
+
) -> tuple[Task, list[TaskEvent]] | None:
|
|
68
72
|
"""Find a task with all its events."""
|
|
69
73
|
pass
|
|
70
74
|
|
|
71
75
|
@abstractmethod
|
|
72
76
|
def search(
|
|
73
77
|
self,
|
|
78
|
+
session: DBSession,
|
|
74
79
|
user_id: UserId,
|
|
75
80
|
start_date: int | None = None,
|
|
76
81
|
end_date: int | None = None,
|
|
@@ -81,7 +86,7 @@ class ITaskRepository(ABC):
|
|
|
81
86
|
pass
|
|
82
87
|
|
|
83
88
|
@abstractmethod
|
|
84
|
-
def delete_tasks_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
|
|
89
|
+
def delete_tasks_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
|
|
85
90
|
"""Delete tasks older than cutoff time using batch deletion."""
|
|
86
91
|
pass
|
|
87
92
|
|
|
@@ -90,12 +95,12 @@ class IFeedbackRepository(ABC):
|
|
|
90
95
|
"""Interface for feedback data access operations."""
|
|
91
96
|
|
|
92
97
|
@abstractmethod
|
|
93
|
-
def save(self, feedback: Feedback) -> Feedback:
|
|
98
|
+
def save(self, session: DBSession, feedback: Feedback) -> Feedback:
|
|
94
99
|
"""Save feedback."""
|
|
95
100
|
pass
|
|
96
101
|
|
|
97
102
|
@abstractmethod
|
|
98
|
-
def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
|
|
103
|
+
def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
|
|
99
104
|
"""Delete feedback older than cutoff time using batch deletion."""
|
|
100
105
|
pass
|
|
101
106
|
|
|
@@ -104,21 +109,23 @@ class IChatTaskRepository(ABC):
|
|
|
104
109
|
"""Interface for chat task data access operations."""
|
|
105
110
|
|
|
106
111
|
@abstractmethod
|
|
107
|
-
def save(self, task: "ChatTask") -> "ChatTask":
|
|
112
|
+
def save(self, session: DBSession, task: "ChatTask") -> "ChatTask":
|
|
108
113
|
"""Save or update a chat task (upsert)."""
|
|
109
114
|
pass
|
|
110
115
|
|
|
111
116
|
@abstractmethod
|
|
112
|
-
def find_by_session(
|
|
117
|
+
def find_by_session(
|
|
118
|
+
self, session: DBSession, session_id: SessionId, user_id: UserId
|
|
119
|
+
) -> list["ChatTask"]:
|
|
113
120
|
"""Find all tasks for a session."""
|
|
114
121
|
pass
|
|
115
122
|
|
|
116
123
|
@abstractmethod
|
|
117
|
-
def find_by_id(self, task_id: str, user_id: UserId) -> Optional["ChatTask"]:
|
|
124
|
+
def find_by_id(self, session: DBSession, task_id: str, user_id: UserId) -> Optional["ChatTask"]:
|
|
118
125
|
"""Find a specific task."""
|
|
119
126
|
pass
|
|
120
127
|
|
|
121
128
|
@abstractmethod
|
|
122
|
-
def delete_by_session(self, session_id: SessionId) -> bool:
|
|
129
|
+
def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
|
|
123
130
|
"""Delete all tasks for a session."""
|
|
124
131
|
pass
|