feat: add agent roster observability APIs (#37566)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
zyssyz123
2026-06-17 05:22:52 +00:00
committed by GitHub
co-authored by autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
parent f992ede836
commit e970cbde0f
10 changed files with 1339 additions and 21 deletions
@@ -87,7 +87,7 @@ def _add_app_log(session: Session, scope: _TestScope, workflow_run: WorkflowRun)
session.commit()
def _add_pause_with_reason(session: Session, scope: _TestScope, workflow_run: WorkflowRun) -> WorkflowPause:
def _add_pause_with_reason(session: Session, _scope: _TestScope, workflow_run: WorkflowRun) -> WorkflowPause:
pause = WorkflowPause(
workflow_id=workflow_run.workflow_id,
workflow_run_id=workflow_run.id,
@@ -23,8 +23,10 @@ from controllers.console.agent.roster import (
AgentAppApi,
AgentAppListApi,
AgentInviteOptionsApi,
AgentLogsApi,
AgentRosterVersionDetailApi,
AgentRosterVersionsApi,
AgentStatisticsSummaryApi,
)
from controllers.console.app import completion as completion_controller
from controllers.console.app import message as message_controller
@@ -148,6 +150,8 @@ def test_agent_v2_console_routes_are_agent_id_first() -> None:
"/agent/<uuid:agent_id>/feedbacks",
"/agent/<uuid:agent_id>/chat-messages/<uuid:message_id>/suggested-questions",
"/agent/<uuid:agent_id>/messages/<uuid:message_id>",
"/agent/<uuid:agent_id>/logs",
"/agent/<uuid:agent_id>/statistics/summary",
"/agent/invite-options",
):
assert route in paths
@@ -371,6 +375,108 @@ def test_agent_versions_call_services(app: Flask, monkeypatch: pytest.MonkeyPatc
assert version_detail["agent_id"] == agent_id
def test_agent_observability_routes_resolve_app_from_agent_id(
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
) -> None:
agent_id = "00000000-0000-0000-0000-000000000001"
app_model = SimpleNamespace(id="app-1")
captured: dict[str, object] = {}
class FakeObservabilityService:
def list_logs(self, *, app, params):
captured["logs"] = {"app": app, "params": params}
return {
"data": [
{
"id": "message-1",
"message_id": "message-1",
"conversation_id": "conversation-1",
"conversation_name": "Debug",
"query": "hello",
"answer": "hi",
"status": "success",
"error": None,
"source": "explore",
"from_source": "console",
"from_end_user_id": None,
"from_account_id": account_id,
"message_tokens": 1,
"answer_tokens": 2,
"total_tokens": 3,
"total_price": "0",
"currency": "USD",
"latency": 1.2,
"created_at": 1,
"updated_at": 2,
}
],
"page": 2,
"limit": 5,
"total": 6,
"has_more": False,
}
def get_statistics_summary(self, *, app, params):
captured["statistics"] = {"app": app, "params": params}
return {
"source": "all",
"summary": {
"total_messages": 1,
"total_conversations": 1,
"total_end_users": 1,
"total_tokens": 3,
"total_price": "0",
"currency": "USD",
"average_session_interactions": 1,
"average_response_time": 1200,
"tokens_per_second": 2,
"user_satisfaction_rate": 100,
},
"charts": {
"daily_messages": [{"date": "2026-06-17", "message_count": 1}],
"daily_conversations": [{"date": "2026-06-17", "conversation_count": 1}],
"daily_end_users": [{"date": "2026-06-17", "terminal_count": 1}],
"token_usage": [{"date": "2026-06-17", "token_count": 3, "total_price": "0", "currency": "USD"}],
"average_session_interactions": [{"date": "2026-06-17", "interactions": 1}],
"average_response_time": [{"date": "2026-06-17", "latency": 1200}],
"tokens_per_second": [{"date": "2026-06-17", "tps": 2}],
"user_satisfaction_rate": [{"date": "2026-06-17", "rate": 100}],
},
}
monkeypatch.setattr(roster_controller, "_resolve_agent_app_model", lambda **kwargs: app_model)
monkeypatch.setattr(roster_controller, "_agent_observability_service", lambda: FakeObservabilityService())
account = SimpleNamespace(id=account_id, timezone="UTC")
with app.test_request_context(
"/console/api/agent/00000000-0000-0000-0000-000000000001/logs"
"?page=2&limit=5&keyword=hello&status=success&source=console"
):
logs = unwrap(AgentLogsApi.get)(AgentLogsApi(), "tenant-1", account, agent_id)
assert logs["data"][0]["id"] == "message-1"
logs_call = cast(dict[str, object], captured["logs"])
assert logs_call["app"] is app_model
logs_params = cast(Any, logs_call["params"])
assert logs_params.page == 2
assert logs_params.limit == 5
assert logs_params.keyword == "hello"
assert logs_params.status == "success"
assert logs_params.source == "console"
with app.test_request_context(
"/console/api/agent/00000000-0000-0000-0000-000000000001/statistics/summary?source=api"
):
statistics = unwrap(AgentStatisticsSummaryApi.get)(AgentStatisticsSummaryApi(), "tenant-1", account, agent_id)
assert statistics["summary"]["total_messages"] == 1
stats_call = cast(dict[str, object], captured["statistics"])
assert stats_call["app"] is app_model
stats_params = cast(Any, stats_call["params"])
assert stats_params.source == "api"
assert stats_params.timezone == "UTC"
def test_workflow_composer_get_put_validate_candidates_impact_and_save(
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
) -> None:
@@ -0,0 +1,123 @@
from datetime import UTC, datetime
from decimal import Decimal
from types import SimpleNamespace
import pytest
from core.app.entities.app_invoke_entities import InvokeFrom
from models.enums import ConversationFromSource, MessageStatus
from services.agent.observability_service import AgentObservabilityService
def test_resolve_source_accepts_frontend_aliases() -> None:
assert AgentObservabilityService.resolve_source(None) is None
assert AgentObservabilityService.resolve_source("all") is None
assert AgentObservabilityService.resolve_source("console") == InvokeFrom.EXPLORE
assert AgentObservabilityService.resolve_source("api") == InvokeFrom.SERVICE_API
assert AgentObservabilityService.resolve_source("web_app") == InvokeFrom.WEB_APP
with pytest.raises(ValueError, match="Unsupported source"):
AgentObservabilityService.resolve_source("unknown")
def test_serialize_log_message_returns_frontend_log_shape() -> None:
created_at = datetime(2026, 6, 17, 1, 2, 3, tzinfo=UTC)
updated_at = datetime(2026, 6, 17, 1, 3, 3, tzinfo=UTC)
message = SimpleNamespace(
id="message-1",
conversation_id="conversation-1",
query="hello",
answer="hi",
error=None,
status=MessageStatus.NORMAL,
invoke_from=InvokeFrom.EXPLORE,
from_source=ConversationFromSource.CONSOLE,
from_end_user_id=None,
from_account_id="account-1",
message_tokens=3,
answer_tokens=4,
total_price=Decimal("0.0001"),
currency="USD",
provider_response_latency=1.25,
created_at=created_at,
updated_at=updated_at,
)
conversation = SimpleNamespace(name="Debug conversation")
payload = AgentObservabilityService.serialize_log_message(message, conversation) # type: ignore[arg-type]
assert payload == {
"id": "message-1",
"message_id": "message-1",
"conversation_id": "conversation-1",
"conversation_name": "Debug conversation",
"query": "hello",
"answer": "hi",
"status": "success",
"error": None,
"source": "explore",
"from_source": "console",
"from_end_user_id": None,
"from_account_id": "account-1",
"message_tokens": 3,
"answer_tokens": 4,
"total_tokens": 7,
"total_price": "0.0001",
"currency": "USD",
"latency": 1.25,
"created_at": int(created_at.timestamp()),
"updated_at": int(updated_at.timestamp()),
}
def test_build_charts_and_summary_match_monitoring_metrics() -> None:
rows = [
{
"date": "2026-06-16",
"message_count": 2,
"conversation_count": 1,
"end_user_count": 1,
"token_count": 30,
"total_price": Decimal("0.003"),
"avg_latency": 1.5,
"latency_sum": 3,
"answer_tokens": 12,
"like_count": 1,
},
{
"date": "2026-06-17",
"message_count": 1,
"conversation_count": 1,
"end_user_count": 1,
"token_count": 20,
"total_price": Decimal("0.002"),
"avg_latency": 2,
"latency_sum": 2,
"answer_tokens": 8,
"like_count": 1,
},
]
charts = AgentObservabilityService._build_charts(rows)
summary = AgentObservabilityService._build_summary(rows)
assert charts["token_usage"] == [
{"date": "2026-06-16", "token_count": 30, "total_price": "0.003", "currency": "USD"},
{"date": "2026-06-17", "token_count": 20, "total_price": "0.002", "currency": "USD"},
]
assert charts["average_response_time"] == [
{"date": "2026-06-16", "latency": 1500.0},
{"date": "2026-06-17", "latency": 2000.0},
]
assert summary == {
"total_messages": 3,
"total_conversations": 2,
"total_end_users": 2,
"total_tokens": 50,
"total_price": "0.005",
"currency": "USD",
"average_session_interactions": 1.5,
"average_response_time": 1666.6667,
"tokens_per_second": 4.0,
"user_satisfaction_rate": 66.67,
}