mirror of
https://github.com/langgenius/dify.git
synced 2026-06-18 15:51:14 +08:00
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:
parent
f992ede836
commit
e970cbde0f
@ -1,11 +1,12 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from flask import request
|
from flask import abort, request
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.app.app import (
|
from controllers.console.app.app import (
|
||||||
AppDetailWithSite,
|
AppDetailWithSite,
|
||||||
AppListQuery,
|
AppListQuery,
|
||||||
@ -27,14 +28,22 @@ from fields.agent_fields import (
|
|||||||
AgentConfigSnapshotDetailResponse,
|
AgentConfigSnapshotDetailResponse,
|
||||||
AgentConfigSnapshotListResponse,
|
AgentConfigSnapshotListResponse,
|
||||||
AgentInviteOptionsResponse,
|
AgentInviteOptionsResponse,
|
||||||
|
AgentLogListResponse,
|
||||||
AgentPublishedReferenceResponse,
|
AgentPublishedReferenceResponse,
|
||||||
AgentRosterListResponse,
|
AgentRosterListResponse,
|
||||||
|
AgentStatisticSummaryEnvelopeResponse,
|
||||||
)
|
)
|
||||||
|
from libs.datetime_utils import parse_time_range
|
||||||
from libs.helper import dump_response
|
from libs.helper import dump_response
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import Account
|
from models import Account
|
||||||
from models.model import IconType
|
from models.model import IconType
|
||||||
from services.agent.errors import AgentNotFoundError
|
from services.agent.errors import AgentNotFoundError
|
||||||
|
from services.agent.observability_service import (
|
||||||
|
AgentLogQueryParams,
|
||||||
|
AgentObservabilityService,
|
||||||
|
AgentStatisticsQueryParams,
|
||||||
|
)
|
||||||
from services.agent.roster_service import AgentRosterService
|
from services.agent.roster_service import AgentRosterService
|
||||||
from services.app_service import AppListParams, AppService, CreateAppParams
|
from services.app_service import AppListParams, AppService, CreateAppParams
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
from services.enterprise.enterprise_service import EnterpriseService
|
||||||
@ -63,11 +72,49 @@ class AgentAppUpdatePayload(UpdateAppPayload):
|
|||||||
role: str | None = Field(default=None, description="Agent role", max_length=255)
|
role: str | None = Field(default=None, description="Agent role", max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentLogsQuery(BaseModel):
|
||||||
|
page: int = Field(default=1, ge=1, description="Page number")
|
||||||
|
limit: int = Field(default=20, ge=1, le=100, description="Page size")
|
||||||
|
keyword: str | None = Field(default=None, description="Search query, answer, or conversation name")
|
||||||
|
status: str | None = Field(default=None, description="Filter by success, failed, or paused")
|
||||||
|
source: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger",
|
||||||
|
)
|
||||||
|
start: str | None = Field(default=None, description="Start date (YYYY-MM-DD HH:MM)")
|
||||||
|
end: str | None = Field(default=None, description="End date (YYYY-MM-DD HH:MM)")
|
||||||
|
|
||||||
|
@field_validator("keyword", "status", "source", "start", "end", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def empty_string_to_none(cls, value: str | None) -> str | None:
|
||||||
|
if value == "":
|
||||||
|
return None
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class AgentStatisticsQuery(BaseModel):
|
||||||
|
source: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger",
|
||||||
|
)
|
||||||
|
start: str | None = Field(default=None, description="Start date (YYYY-MM-DD HH:MM)")
|
||||||
|
end: str | None = Field(default=None, description="End date (YYYY-MM-DD HH:MM)")
|
||||||
|
|
||||||
|
@field_validator("source", "start", "end", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def empty_string_to_none(cls, value: str | None) -> str | None:
|
||||||
|
if value == "":
|
||||||
|
return None
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
register_schema_models(
|
register_schema_models(
|
||||||
console_ns,
|
console_ns,
|
||||||
AgentAppCreatePayload,
|
AgentAppCreatePayload,
|
||||||
AgentAppUpdatePayload,
|
AgentAppUpdatePayload,
|
||||||
AgentInviteOptionsQuery,
|
AgentInviteOptionsQuery,
|
||||||
|
AgentLogsQuery,
|
||||||
|
AgentStatisticsQuery,
|
||||||
AgentIdPath,
|
AgentIdPath,
|
||||||
AppListQuery,
|
AppListQuery,
|
||||||
UpdateAppPayload,
|
UpdateAppPayload,
|
||||||
@ -80,8 +127,10 @@ register_response_schema_models(
|
|||||||
AgentConfigSnapshotDetailResponse,
|
AgentConfigSnapshotDetailResponse,
|
||||||
AgentConfigSnapshotListResponse,
|
AgentConfigSnapshotListResponse,
|
||||||
AgentInviteOptionsResponse,
|
AgentInviteOptionsResponse,
|
||||||
|
AgentLogListResponse,
|
||||||
AgentPublishedReferenceResponse,
|
AgentPublishedReferenceResponse,
|
||||||
AgentRosterListResponse,
|
AgentRosterListResponse,
|
||||||
|
AgentStatisticSummaryEnvelopeResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -136,7 +185,19 @@ def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def _resolve_agent_app_model(*, tenant_id: str, agent_id: UUID):
|
def _resolve_agent_app_model(*, tenant_id: str, agent_id: UUID):
|
||||||
return _agent_roster_service().get_agent_app_model(tenant_id=tenant_id, agent_id=str(agent_id))
|
return resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_observability_service() -> AgentObservabilityService:
|
||||||
|
return AgentObservabilityService(db.session)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_observability_time_range(start: str | None, end: str | None, account: Account):
|
||||||
|
timezone = account.timezone or "UTC"
|
||||||
|
try:
|
||||||
|
return parse_time_range(start, end, timezone)
|
||||||
|
except ValueError as exc:
|
||||||
|
abort(400, description=str(exc))
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agent")
|
@console_ns.route("/agent")
|
||||||
@ -267,6 +328,65 @@ class AgentInviteOptionsApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/logs")
|
||||||
|
class AgentLogsApi(Resource):
|
||||||
|
@console_ns.doc(params=query_params_from_model(AgentLogsQuery))
|
||||||
|
@console_ns.response(200, "Agent logs", console_ns.models[AgentLogListResponse.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = _resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
query = AgentLogsQuery.model_validate(request.args.to_dict(flat=True))
|
||||||
|
start, end = _parse_observability_time_range(query.start, query.end, current_user)
|
||||||
|
try:
|
||||||
|
payload = _agent_observability_service().list_logs(
|
||||||
|
app=app_model,
|
||||||
|
params=AgentLogQueryParams(
|
||||||
|
page=query.page,
|
||||||
|
limit=query.limit,
|
||||||
|
keyword=query.keyword,
|
||||||
|
status=query.status,
|
||||||
|
source=query.source,
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
abort(400, description=str(exc))
|
||||||
|
return dump_response(AgentLogListResponse, payload)
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/statistics/summary")
|
||||||
|
class AgentStatisticsSummaryApi(Resource):
|
||||||
|
@console_ns.doc(params=query_params_from_model(AgentStatisticsQuery))
|
||||||
|
@console_ns.response(
|
||||||
|
200,
|
||||||
|
"Agent monitoring summary and chart data",
|
||||||
|
console_ns.models[AgentStatisticSummaryEnvelopeResponse.__name__],
|
||||||
|
)
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = _resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
query = AgentStatisticsQuery.model_validate(request.args.to_dict(flat=True))
|
||||||
|
timezone = current_user.timezone or "UTC"
|
||||||
|
start, end = _parse_observability_time_range(query.start, query.end, current_user)
|
||||||
|
try:
|
||||||
|
payload = _agent_observability_service().get_statistics_summary(
|
||||||
|
app=app_model,
|
||||||
|
params=AgentStatisticsQueryParams(source=query.source, start=start, end=end, timezone=timezone),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
abort(400, description=str(exc))
|
||||||
|
return dump_response(AgentStatisticSummaryEnvelopeResponse, payload)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agent/<uuid:agent_id>/versions")
|
@console_ns.route("/agent/<uuid:agent_id>/versions")
|
||||||
class AgentRosterVersionsApi(Resource):
|
class AgentRosterVersionsApi(Resource):
|
||||||
@console_ns.response(200, "Agent versions", console_ns.models[AgentConfigSnapshotListResponse.__name__])
|
@console_ns.response(200, "Agent versions", console_ns.models[AgentConfigSnapshotListResponse.__name__])
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import Annotated, Literal
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
|
from libs.helper import to_timestamp
|
||||||
from models.agent import (
|
from models.agent import (
|
||||||
AgentConfigRevisionOperation,
|
AgentConfigRevisionOperation,
|
||||||
AgentIconType,
|
AgentIconType,
|
||||||
@ -105,6 +107,114 @@ class AgentInviteOptionsResponse(ResponseModel):
|
|||||||
has_more: bool
|
has_more: bool
|
||||||
|
|
||||||
|
|
||||||
|
class AgentLogItemResponse(ResponseModel):
|
||||||
|
id: str
|
||||||
|
message_id: str
|
||||||
|
conversation_id: str
|
||||||
|
conversation_name: str | None = None
|
||||||
|
query: str
|
||||||
|
answer: str
|
||||||
|
status: str
|
||||||
|
error: str | None = None
|
||||||
|
source: str | None = None
|
||||||
|
from_source: str | None = None
|
||||||
|
from_end_user_id: str | None = None
|
||||||
|
from_account_id: str | None = None
|
||||||
|
message_tokens: int
|
||||||
|
answer_tokens: int
|
||||||
|
total_tokens: int
|
||||||
|
total_price: str
|
||||||
|
currency: str
|
||||||
|
latency: float
|
||||||
|
created_at: int | None = None
|
||||||
|
updated_at: int | None = None
|
||||||
|
|
||||||
|
@field_validator("created_at", "updated_at", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_timestamp(cls, value: datetime | int | None) -> int | None:
|
||||||
|
return to_timestamp(value)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentLogListResponse(ResponseModel):
|
||||||
|
data: list[AgentLogItemResponse]
|
||||||
|
page: int
|
||||||
|
limit: int
|
||||||
|
total: int
|
||||||
|
has_more: bool
|
||||||
|
|
||||||
|
|
||||||
|
class AgentStatisticSummaryResponse(ResponseModel):
|
||||||
|
total_messages: int
|
||||||
|
total_conversations: int
|
||||||
|
total_end_users: int
|
||||||
|
total_tokens: int
|
||||||
|
total_price: str
|
||||||
|
currency: str
|
||||||
|
average_session_interactions: float
|
||||||
|
average_response_time: float
|
||||||
|
tokens_per_second: float
|
||||||
|
user_satisfaction_rate: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDailyMessageStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
message_count: int
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDailyConversationStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
conversation_count: int
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDailyEndUserStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
terminal_count: int
|
||||||
|
|
||||||
|
|
||||||
|
class AgentTokenUsageStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
token_count: int
|
||||||
|
total_price: str
|
||||||
|
currency: str
|
||||||
|
|
||||||
|
|
||||||
|
class AgentAverageSessionInteractionStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
interactions: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentAverageResponseTimeStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
latency: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentTokensPerSecondStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
tps: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentUserSatisfactionRateStatisticResponse(ResponseModel):
|
||||||
|
date: str
|
||||||
|
rate: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentStatisticChartsResponse(ResponseModel):
|
||||||
|
daily_messages: list[AgentDailyMessageStatisticResponse] = Field(default_factory=list)
|
||||||
|
daily_conversations: list[AgentDailyConversationStatisticResponse] = Field(default_factory=list)
|
||||||
|
daily_end_users: list[AgentDailyEndUserStatisticResponse] = Field(default_factory=list)
|
||||||
|
token_usage: list[AgentTokenUsageStatisticResponse] = Field(default_factory=list)
|
||||||
|
average_session_interactions: list[AgentAverageSessionInteractionStatisticResponse] = Field(default_factory=list)
|
||||||
|
average_response_time: list[AgentAverageResponseTimeStatisticResponse] = Field(default_factory=list)
|
||||||
|
tokens_per_second: list[AgentTokensPerSecondStatisticResponse] = Field(default_factory=list)
|
||||||
|
user_satisfaction_rate: list[AgentUserSatisfactionRateStatisticResponse] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentStatisticSummaryEnvelopeResponse(ResponseModel):
|
||||||
|
source: str
|
||||||
|
summary: AgentStatisticSummaryResponse
|
||||||
|
charts: AgentStatisticChartsResponse
|
||||||
|
|
||||||
|
|
||||||
class AgentConfigRevisionResponse(ResponseModel):
|
class AgentConfigRevisionResponse(ResponseModel):
|
||||||
id: str
|
id: str
|
||||||
previous_snapshot_id: str | None = None
|
previous_snapshot_id: str | None = None
|
||||||
|
|||||||
@ -638,6 +638,26 @@ Commit an uploaded file into the Agent App drive under files/<name>
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 201 | File committed into the agent drive | **application/json**: [AgentDriveFileCommitResponse](#agentdrivefilecommitresponse)<br> |
|
| 201 | File committed into the agent drive | **application/json**: [AgentDriveFileCommitResponse](#agentdrivefilecommitresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/logs
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| end | query | End date (YYYY-MM-DD HH:MM) | No | string |
|
||||||
|
| keyword | query | Search query, answer, or conversation name | No | string |
|
||||||
|
| limit | query | Page size | No | integer, <br>**Default:** 20 |
|
||||||
|
| page | query | Page number | No | integer, <br>**Default:** 1 |
|
||||||
|
| source | query | Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger | No | string |
|
||||||
|
| start | query | Start date (YYYY-MM-DD HH:MM) | No | string |
|
||||||
|
| status | query | Filter by success, failed, or paused | No | string |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent logs | **application/json**: [AgentLogListResponse](#agentloglistresponse)<br> |
|
||||||
|
|
||||||
### [GET] /agent/{agent_id}/messages/{message_id}
|
### [GET] /agent/{agent_id}/messages/{message_id}
|
||||||
Get Agent App message details by ID
|
Get Agent App message details by ID
|
||||||
|
|
||||||
@ -790,6 +810,22 @@ Infer CLI tool + ENV suggestions from a standardized Agent App skill
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Inference result (draft suggestions, nothing persisted) | **application/json**: [SkillToolInferenceResult](#skilltoolinferenceresult)<br> |
|
| 200 | Inference result (draft suggestions, nothing persisted) | **application/json**: [SkillToolInferenceResult](#skilltoolinferenceresult)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/statistics/summary
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| end | query | End date (YYYY-MM-DD HH:MM) | No | string |
|
||||||
|
| source | query | Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger | No | string |
|
||||||
|
| start | query | Start date (YYYY-MM-DD HH:MM) | No | string |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent monitoring summary and chart data | **application/json**: [AgentStatisticSummaryEnvelopeResponse](#agentstatisticsummaryenveloperesponse)<br> |
|
||||||
|
|
||||||
### [GET] /agent/{agent_id}/versions
|
### [GET] /agent/{agent_id}/versions
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
@ -11318,6 +11354,20 @@ default (the config form sends the full desired feature state on save).
|
|||||||
| role | string | Agent role | No |
|
| role | string | Agent role | No |
|
||||||
| use_icon_as_answer_icon | boolean | Use icon as answer icon | No |
|
| use_icon_as_answer_icon | boolean | Use icon as answer icon | No |
|
||||||
|
|
||||||
|
#### AgentAverageResponseTimeStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| latency | number | | Yes |
|
||||||
|
|
||||||
|
#### AgentAverageSessionInteractionStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| interactions | number | | Yes |
|
||||||
|
|
||||||
#### AgentCliToolAuthorizationStatus
|
#### AgentCliToolAuthorizationStatus
|
||||||
|
|
||||||
Authorization state for Agent-scoped CLI tools.
|
Authorization state for Agent-scoped CLI tools.
|
||||||
@ -11558,6 +11608,27 @@ Audit operation recorded for Agent Soul version/revision changes.
|
|||||||
| version | integer | | Yes |
|
| version | integer | | Yes |
|
||||||
| version_note | string | | No |
|
| version_note | string | | No |
|
||||||
|
|
||||||
|
#### AgentDailyConversationStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| conversation_count | integer | | Yes |
|
||||||
|
| date | string | | Yes |
|
||||||
|
|
||||||
|
#### AgentDailyEndUserStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| terminal_count | integer | | Yes |
|
||||||
|
|
||||||
|
#### AgentDailyMessageStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| message_count | integer | | Yes |
|
||||||
|
|
||||||
#### AgentDriveDeleteFileByAgentQuery
|
#### AgentDriveDeleteFileByAgentQuery
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -11797,6 +11868,41 @@ the current roster/workflow APIs scoped to Dify Agent.
|
|||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| AgentKnowledgeQueryMode | string | | |
|
| AgentKnowledgeQueryMode | string | | |
|
||||||
|
|
||||||
|
#### AgentLogItemResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| answer | string | | Yes |
|
||||||
|
| answer_tokens | integer | | Yes |
|
||||||
|
| conversation_id | string | | Yes |
|
||||||
|
| conversation_name | string | | No |
|
||||||
|
| created_at | integer | | No |
|
||||||
|
| currency | string | | Yes |
|
||||||
|
| error | string | | No |
|
||||||
|
| from_account_id | string | | No |
|
||||||
|
| from_end_user_id | string | | No |
|
||||||
|
| from_source | string | | No |
|
||||||
|
| id | string | | Yes |
|
||||||
|
| latency | number | | Yes |
|
||||||
|
| message_id | string | | Yes |
|
||||||
|
| message_tokens | integer | | Yes |
|
||||||
|
| query | string | | Yes |
|
||||||
|
| source | string | | No |
|
||||||
|
| status | string | | Yes |
|
||||||
|
| total_price | string | | Yes |
|
||||||
|
| total_tokens | integer | | Yes |
|
||||||
|
| updated_at | integer | | No |
|
||||||
|
|
||||||
|
#### AgentLogListResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| data | [ [AgentLogItemResponse](#agentlogitemresponse) ] | | Yes |
|
||||||
|
| has_more | boolean | | Yes |
|
||||||
|
| limit | integer | | Yes |
|
||||||
|
| page | integer | | Yes |
|
||||||
|
| total | integer | | Yes |
|
||||||
|
|
||||||
#### AgentLogMetaResponse
|
#### AgentLogMetaResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -11824,6 +11930,18 @@ the current roster/workflow APIs scoped to Dify Agent.
|
|||||||
| iterations | [ [AgentIterationLogResponse](#agentiterationlogresponse) ] | | Yes |
|
| iterations | [ [AgentIterationLogResponse](#agentiterationlogresponse) ] | | Yes |
|
||||||
| meta | [AgentLogMetaResponse](#agentlogmetaresponse) | | Yes |
|
| meta | [AgentLogMetaResponse](#agentlogmetaresponse) | | Yes |
|
||||||
|
|
||||||
|
#### AgentLogsQuery
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| end | string | End date (YYYY-MM-DD HH:MM) | No |
|
||||||
|
| keyword | string | Search query, answer, or conversation name | No |
|
||||||
|
| limit | integer, <br>**Default:** 20 | Page size | No |
|
||||||
|
| page | integer, <br>**Default:** 1 | Page number | No |
|
||||||
|
| source | string | Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger | No |
|
||||||
|
| start | string | Start date (YYYY-MM-DD HH:MM) | No |
|
||||||
|
| status | string | Filter by success, failed, or paused | No |
|
||||||
|
|
||||||
#### AgentMemoryArtifactConfig
|
#### AgentMemoryArtifactConfig
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -12197,6 +12315,50 @@ Origin that created or imported the Agent.
|
|||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| AgentSource | string | Origin that created or imported the Agent. | |
|
| AgentSource | string | Origin that created or imported the Agent. | |
|
||||||
|
|
||||||
|
#### AgentStatisticChartsResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| average_response_time | [ [AgentAverageResponseTimeStatisticResponse](#agentaverageresponsetimestatisticresponse) ] | | No |
|
||||||
|
| average_session_interactions | [ [AgentAverageSessionInteractionStatisticResponse](#agentaveragesessioninteractionstatisticresponse) ] | | No |
|
||||||
|
| daily_conversations | [ [AgentDailyConversationStatisticResponse](#agentdailyconversationstatisticresponse) ] | | No |
|
||||||
|
| daily_end_users | [ [AgentDailyEndUserStatisticResponse](#agentdailyenduserstatisticresponse) ] | | No |
|
||||||
|
| daily_messages | [ [AgentDailyMessageStatisticResponse](#agentdailymessagestatisticresponse) ] | | No |
|
||||||
|
| token_usage | [ [AgentTokenUsageStatisticResponse](#agenttokenusagestatisticresponse) ] | | No |
|
||||||
|
| tokens_per_second | [ [AgentTokensPerSecondStatisticResponse](#agenttokenspersecondstatisticresponse) ] | | No |
|
||||||
|
| user_satisfaction_rate | [ [AgentUserSatisfactionRateStatisticResponse](#agentusersatisfactionratestatisticresponse) ] | | No |
|
||||||
|
|
||||||
|
#### AgentStatisticSummaryEnvelopeResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| charts | [AgentStatisticChartsResponse](#agentstatisticchartsresponse) | | Yes |
|
||||||
|
| source | string | | Yes |
|
||||||
|
| summary | [AgentStatisticSummaryResponse](#agentstatisticsummaryresponse) | | Yes |
|
||||||
|
|
||||||
|
#### AgentStatisticSummaryResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| average_response_time | number | | Yes |
|
||||||
|
| average_session_interactions | number | | Yes |
|
||||||
|
| currency | string | | Yes |
|
||||||
|
| tokens_per_second | number | | Yes |
|
||||||
|
| total_conversations | integer | | Yes |
|
||||||
|
| total_end_users | integer | | Yes |
|
||||||
|
| total_messages | integer | | Yes |
|
||||||
|
| total_price | string | | Yes |
|
||||||
|
| total_tokens | integer | | Yes |
|
||||||
|
| user_satisfaction_rate | number | | Yes |
|
||||||
|
|
||||||
|
#### AgentStatisticsQuery
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| end | string | End date (YYYY-MM-DD HH:MM) | No |
|
||||||
|
| source | string | Filter by all, console/explore, api/service-api, web-app, debugger, openapi, or trigger | No |
|
||||||
|
| start | string | Start date (YYYY-MM-DD HH:MM) | No |
|
||||||
|
|
||||||
#### AgentStatus
|
#### AgentStatus
|
||||||
|
|
||||||
Soft lifecycle state for Agent records.
|
Soft lifecycle state for Agent records.
|
||||||
@ -12239,6 +12401,22 @@ Soft lifecycle state for Agent records.
|
|||||||
| tool_input | string | | No |
|
| tool_input | string | | No |
|
||||||
| tool_labels | [JSONValue](#jsonvalue) | | Yes |
|
| tool_labels | [JSONValue](#jsonvalue) | | Yes |
|
||||||
|
|
||||||
|
#### AgentTokenUsageStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| currency | string | | Yes |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| token_count | integer | | Yes |
|
||||||
|
| total_price | string | | Yes |
|
||||||
|
|
||||||
|
#### AgentTokensPerSecondStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| tps | number | | Yes |
|
||||||
|
|
||||||
#### AgentToolCallResponse
|
#### AgentToolCallResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -12253,6 +12431,13 @@ Soft lifecycle state for Agent records.
|
|||||||
| tool_output | object | | Yes |
|
| tool_output | object | | Yes |
|
||||||
| tool_parameters | object | | Yes |
|
| tool_parameters | object | | Yes |
|
||||||
|
|
||||||
|
#### AgentUserSatisfactionRateStatisticResponse
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| date | string | | Yes |
|
||||||
|
| rate | number | | Yes |
|
||||||
|
|
||||||
#### AllowedExtensionsResponse
|
#### AllowedExtensionsResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|
|||||||
300
api/services/agent/observability_service.py
Normal file
300
api/services/agent/observability_service.py
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import func, or_, select
|
||||||
|
|
||||||
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
|
from libs.helper import convert_datetime_to_date, escape_like_pattern, to_timestamp
|
||||||
|
from models.enums import MessageStatus
|
||||||
|
from models.model import App, Conversation, Message
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AgentLogQueryParams:
|
||||||
|
page: int = 1
|
||||||
|
limit: int = 20
|
||||||
|
keyword: str | None = None
|
||||||
|
status: str | None = None
|
||||||
|
source: str | None = None
|
||||||
|
start: datetime | None = None
|
||||||
|
end: datetime | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AgentStatisticsQueryParams:
|
||||||
|
source: str | None = None
|
||||||
|
start: datetime | None = None
|
||||||
|
end: datetime | None = None
|
||||||
|
timezone: str = "UTC"
|
||||||
|
|
||||||
|
|
||||||
|
class AgentObservabilityService:
|
||||||
|
_SOURCE_ALIASES: dict[str, InvokeFrom] = {
|
||||||
|
"api": InvokeFrom.SERVICE_API,
|
||||||
|
"service-api": InvokeFrom.SERVICE_API,
|
||||||
|
"service_api": InvokeFrom.SERVICE_API,
|
||||||
|
"console": InvokeFrom.EXPLORE,
|
||||||
|
"explore": InvokeFrom.EXPLORE,
|
||||||
|
"explore-app": InvokeFrom.EXPLORE,
|
||||||
|
"explore_app": InvokeFrom.EXPLORE,
|
||||||
|
"web": InvokeFrom.WEB_APP,
|
||||||
|
"web-app": InvokeFrom.WEB_APP,
|
||||||
|
"web_app": InvokeFrom.WEB_APP,
|
||||||
|
"debugger": InvokeFrom.DEBUGGER,
|
||||||
|
"dev": InvokeFrom.DEBUGGER,
|
||||||
|
"openapi": InvokeFrom.OPENAPI,
|
||||||
|
"trigger": InvokeFrom.TRIGGER,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, session: Any):
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_source(cls, source: str | None) -> InvokeFrom | None:
|
||||||
|
if not source or source == "all":
|
||||||
|
return None
|
||||||
|
normalized = source.strip().lower()
|
||||||
|
if not normalized or normalized == "all":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return cls._SOURCE_ALIASES[normalized]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ValueError(f"Unsupported source: {source}") from exc
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _message_status(message: Message) -> str:
|
||||||
|
if message.error or message.status == MessageStatus.ERROR:
|
||||||
|
return "failed"
|
||||||
|
if message.status == MessageStatus.PAUSED:
|
||||||
|
return "paused"
|
||||||
|
return "success"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _total_tokens(message: Message) -> int:
|
||||||
|
return int(message.message_tokens or 0) + int(message.answer_tokens or 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def serialize_log_message(cls, message: Message, conversation: Conversation | None = None) -> dict[str, Any]:
|
||||||
|
invoke_from = message.invoke_from.value if message.invoke_from else None
|
||||||
|
return {
|
||||||
|
"id": message.id,
|
||||||
|
"message_id": message.id,
|
||||||
|
"conversation_id": message.conversation_id,
|
||||||
|
"conversation_name": conversation.name if conversation else None,
|
||||||
|
"query": message.query,
|
||||||
|
"answer": message.answer,
|
||||||
|
"status": cls._message_status(message),
|
||||||
|
"error": message.error,
|
||||||
|
"source": invoke_from,
|
||||||
|
"from_source": message.from_source.value if message.from_source else None,
|
||||||
|
"from_end_user_id": message.from_end_user_id,
|
||||||
|
"from_account_id": message.from_account_id,
|
||||||
|
"message_tokens": int(message.message_tokens or 0),
|
||||||
|
"answer_tokens": int(message.answer_tokens or 0),
|
||||||
|
"total_tokens": cls._total_tokens(message),
|
||||||
|
"total_price": str(message.total_price or Decimal(0)),
|
||||||
|
"currency": message.currency,
|
||||||
|
"latency": float(message.provider_response_latency or 0),
|
||||||
|
"created_at": to_timestamp(message.created_at),
|
||||||
|
"updated_at": to_timestamp(message.updated_at),
|
||||||
|
}
|
||||||
|
|
||||||
|
def list_logs(self, *, app: App, params: AgentLogQueryParams) -> dict[str, Any]:
|
||||||
|
source = self.resolve_source(params.source)
|
||||||
|
stmt = (
|
||||||
|
select(Message, Conversation)
|
||||||
|
.join(Conversation, Conversation.id == Message.conversation_id)
|
||||||
|
.where(Message.app_id == app.id, Conversation.app_id == app.id)
|
||||||
|
)
|
||||||
|
stmt = self._apply_source_filter(stmt, source)
|
||||||
|
|
||||||
|
if params.start:
|
||||||
|
stmt = stmt.where(Message.created_at >= params.start)
|
||||||
|
if params.end:
|
||||||
|
stmt = stmt.where(Message.created_at < params.end)
|
||||||
|
if params.keyword:
|
||||||
|
escaped_keyword = escape_like_pattern(params.keyword)
|
||||||
|
pattern = f"%{escaped_keyword}%"
|
||||||
|
stmt = stmt.where(
|
||||||
|
or_(
|
||||||
|
Message.query.ilike(pattern, escape="\\"),
|
||||||
|
Message.answer.ilike(pattern, escape="\\"),
|
||||||
|
Conversation.name.ilike(pattern, escape="\\"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if params.status:
|
||||||
|
stmt = self._apply_status_filter(stmt, params.status)
|
||||||
|
|
||||||
|
total = self._session.scalar(select(func.count()).select_from(stmt.subquery())) or 0
|
||||||
|
rows = list(
|
||||||
|
self._session.execute(
|
||||||
|
stmt.order_by(Message.created_at.desc(), Message.id.desc())
|
||||||
|
.offset((params.page - 1) * params.limit)
|
||||||
|
.limit(params.limit)
|
||||||
|
).all()
|
||||||
|
)
|
||||||
|
data = []
|
||||||
|
for message, conversation in rows:
|
||||||
|
data.append(self.serialize_log_message(message, conversation))
|
||||||
|
return {
|
||||||
|
"data": data,
|
||||||
|
"page": params.page,
|
||||||
|
"limit": params.limit,
|
||||||
|
"total": total,
|
||||||
|
"has_more": params.page * params.limit < total,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _apply_source_filter(cls, stmt, source: InvokeFrom | None):
|
||||||
|
if source is None:
|
||||||
|
return stmt.where(Message.invoke_from != InvokeFrom.DEBUGGER)
|
||||||
|
return stmt.where(Message.invoke_from == source)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _apply_status_filter(stmt, status: str):
|
||||||
|
normalized = status.strip().lower()
|
||||||
|
if normalized in {"success", "normal"}:
|
||||||
|
return stmt.where(Message.error.is_(None), Message.status == MessageStatus.NORMAL)
|
||||||
|
if normalized in {"failed", "error"}:
|
||||||
|
return stmt.where(or_(Message.error.is_not(None), Message.status == MessageStatus.ERROR))
|
||||||
|
if normalized == "paused":
|
||||||
|
return stmt.where(Message.status == MessageStatus.PAUSED)
|
||||||
|
raise ValueError(f"Unsupported status: {status}")
|
||||||
|
|
||||||
|
def get_statistics_summary(self, *, app: App, params: AgentStatisticsQueryParams) -> dict[str, Any]:
|
||||||
|
source = self.resolve_source(params.source)
|
||||||
|
rows = self._load_daily_statistics(app=app, params=params, source=source)
|
||||||
|
charts = self._build_charts(rows)
|
||||||
|
summary = self._build_summary(rows)
|
||||||
|
return {
|
||||||
|
"source": source.value if source else "all",
|
||||||
|
"summary": summary,
|
||||||
|
"charts": charts,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _load_daily_statistics(
|
||||||
|
self, *, app: App, params: AgentStatisticsQueryParams, source: InvokeFrom | None
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
converted_created_at = convert_datetime_to_date("m.created_at")
|
||||||
|
source_condition = "AND m.invoke_from != :debugger" if source is None else "AND m.invoke_from = :source"
|
||||||
|
sql_query = f"""SELECT
|
||||||
|
{converted_created_at} AS date,
|
||||||
|
COUNT(m.id) AS message_count,
|
||||||
|
COUNT(DISTINCT m.conversation_id) AS conversation_count,
|
||||||
|
COUNT(DISTINCT m.from_end_user_id) AS end_user_count,
|
||||||
|
COALESCE(SUM(COALESCE(m.message_tokens, 0) + COALESCE(m.answer_tokens, 0)), 0) AS token_count,
|
||||||
|
COALESCE(SUM(COALESCE(m.total_price, 0)), 0) AS total_price,
|
||||||
|
COALESCE(AVG(m.provider_response_latency), 0) AS avg_latency,
|
||||||
|
COALESCE(SUM(m.provider_response_latency), 0) AS latency_sum,
|
||||||
|
COALESCE(SUM(m.answer_tokens), 0) AS answer_tokens,
|
||||||
|
COUNT(mf.id) AS like_count
|
||||||
|
FROM messages m
|
||||||
|
LEFT JOIN message_feedbacks mf
|
||||||
|
ON mf.message_id = m.id AND mf.rating = 'like'
|
||||||
|
WHERE
|
||||||
|
m.app_id = :app_id
|
||||||
|
{source_condition}"""
|
||||||
|
args: dict[str, Any] = {
|
||||||
|
"tz": params.timezone,
|
||||||
|
"app_id": app.id,
|
||||||
|
"debugger": InvokeFrom.DEBUGGER,
|
||||||
|
}
|
||||||
|
if source is not None:
|
||||||
|
args["source"] = source
|
||||||
|
if params.start:
|
||||||
|
sql_query += " AND m.created_at >= :start"
|
||||||
|
args["start"] = params.start
|
||||||
|
if params.end:
|
||||||
|
sql_query += " AND m.created_at < :end"
|
||||||
|
args["end"] = params.end
|
||||||
|
sql_query += " GROUP BY date ORDER BY date"
|
||||||
|
|
||||||
|
return [dict(row._mapping) for row in self._session.execute(sa.text(sql_query), args).all()]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_charts(rows: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
|
||||||
|
messages = []
|
||||||
|
conversations = []
|
||||||
|
end_users = []
|
||||||
|
token_usage = []
|
||||||
|
average_session_interactions = []
|
||||||
|
average_response_time = []
|
||||||
|
tokens_per_second = []
|
||||||
|
user_satisfaction_rate = []
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
date = str(row["date"])
|
||||||
|
message_count = int(row["message_count"] or 0)
|
||||||
|
conversation_count = int(row["conversation_count"] or 0)
|
||||||
|
token_count = int(row["token_count"] or 0)
|
||||||
|
total_price = row["total_price"] or Decimal(0)
|
||||||
|
avg_latency = float(row["avg_latency"] or 0)
|
||||||
|
latency_sum = float(row["latency_sum"] or 0)
|
||||||
|
answer_tokens = int(row["answer_tokens"] or 0)
|
||||||
|
like_count = int(row["like_count"] or 0)
|
||||||
|
|
||||||
|
messages.append({"date": date, "message_count": message_count})
|
||||||
|
conversations.append({"date": date, "conversation_count": conversation_count})
|
||||||
|
end_users.append({"date": date, "terminal_count": int(row["end_user_count"] or 0)})
|
||||||
|
token_usage.append(
|
||||||
|
{
|
||||||
|
"date": date,
|
||||||
|
"token_count": token_count,
|
||||||
|
"total_price": str(total_price),
|
||||||
|
"currency": "USD",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
average_session_interactions.append(
|
||||||
|
{
|
||||||
|
"date": date,
|
||||||
|
"interactions": round(message_count / conversation_count, 2) if conversation_count else 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
average_response_time.append({"date": date, "latency": round(avg_latency * 1000, 4)})
|
||||||
|
tokens_per_second.append({"date": date, "tps": round(answer_tokens / latency_sum, 4) if latency_sum else 0})
|
||||||
|
user_satisfaction_rate.append(
|
||||||
|
{"date": date, "rate": round(like_count * 100 / message_count, 2) if message_count else 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"daily_messages": messages,
|
||||||
|
"daily_conversations": conversations,
|
||||||
|
"daily_end_users": end_users,
|
||||||
|
"token_usage": token_usage,
|
||||||
|
"average_session_interactions": average_session_interactions,
|
||||||
|
"average_response_time": average_response_time,
|
||||||
|
"tokens_per_second": tokens_per_second,
|
||||||
|
"user_satisfaction_rate": user_satisfaction_rate,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_summary(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
total_messages = sum(int(row["message_count"] or 0) for row in rows)
|
||||||
|
total_conversations = sum(int(row["conversation_count"] or 0) for row in rows)
|
||||||
|
total_end_users = sum(int(row["end_user_count"] or 0) for row in rows)
|
||||||
|
total_tokens = sum(int(row["token_count"] or 0) for row in rows)
|
||||||
|
total_price = sum(Decimal(str(row["total_price"] or 0)) for row in rows)
|
||||||
|
total_answer_tokens = sum(int(row["answer_tokens"] or 0) for row in rows)
|
||||||
|
total_latency = sum(float(row["latency_sum"] or 0) for row in rows)
|
||||||
|
weighted_latency = sum(float(row["avg_latency"] or 0) * int(row["message_count"] or 0) for row in rows)
|
||||||
|
total_likes = sum(int(row["like_count"] or 0) for row in rows)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_messages": total_messages,
|
||||||
|
"total_conversations": total_conversations,
|
||||||
|
"total_end_users": total_end_users,
|
||||||
|
"total_tokens": total_tokens,
|
||||||
|
"total_price": str(total_price),
|
||||||
|
"currency": "USD",
|
||||||
|
"average_session_interactions": round(total_messages / total_conversations, 2)
|
||||||
|
if total_conversations
|
||||||
|
else 0,
|
||||||
|
"average_response_time": round((weighted_latency / total_messages) * 1000, 4) if total_messages else 0,
|
||||||
|
"tokens_per_second": round(total_answer_tokens / total_latency, 4) if total_latency else 0,
|
||||||
|
"user_satisfaction_rate": round(total_likes * 100 / total_messages, 2) if total_messages else 0,
|
||||||
|
}
|
||||||
@ -87,7 +87,7 @@ def _add_app_log(session: Session, scope: _TestScope, workflow_run: WorkflowRun)
|
|||||||
session.commit()
|
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(
|
pause = WorkflowPause(
|
||||||
workflow_id=workflow_run.workflow_id,
|
workflow_id=workflow_run.workflow_id,
|
||||||
workflow_run_id=workflow_run.id,
|
workflow_run_id=workflow_run.id,
|
||||||
|
|||||||
@ -23,8 +23,10 @@ from controllers.console.agent.roster import (
|
|||||||
AgentAppApi,
|
AgentAppApi,
|
||||||
AgentAppListApi,
|
AgentAppListApi,
|
||||||
AgentInviteOptionsApi,
|
AgentInviteOptionsApi,
|
||||||
|
AgentLogsApi,
|
||||||
AgentRosterVersionDetailApi,
|
AgentRosterVersionDetailApi,
|
||||||
AgentRosterVersionsApi,
|
AgentRosterVersionsApi,
|
||||||
|
AgentStatisticsSummaryApi,
|
||||||
)
|
)
|
||||||
from controllers.console.app import completion as completion_controller
|
from controllers.console.app import completion as completion_controller
|
||||||
from controllers.console.app import message as message_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>/feedbacks",
|
||||||
"/agent/<uuid:agent_id>/chat-messages/<uuid:message_id>/suggested-questions",
|
"/agent/<uuid:agent_id>/chat-messages/<uuid:message_id>/suggested-questions",
|
||||||
"/agent/<uuid:agent_id>/messages/<uuid:message_id>",
|
"/agent/<uuid:agent_id>/messages/<uuid:message_id>",
|
||||||
|
"/agent/<uuid:agent_id>/logs",
|
||||||
|
"/agent/<uuid:agent_id>/statistics/summary",
|
||||||
"/agent/invite-options",
|
"/agent/invite-options",
|
||||||
):
|
):
|
||||||
assert route in paths
|
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
|
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(
|
def test_workflow_composer_get_put_validate_candidates_impact_and_save(
|
||||||
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||||
) -> None:
|
) -> 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,
|
||||||
|
}
|
||||||
@ -29,6 +29,9 @@ import {
|
|||||||
zGetAgentByAgentIdDriveFilesPreviewResponse,
|
zGetAgentByAgentIdDriveFilesPreviewResponse,
|
||||||
zGetAgentByAgentIdDriveFilesQuery,
|
zGetAgentByAgentIdDriveFilesQuery,
|
||||||
zGetAgentByAgentIdDriveFilesResponse,
|
zGetAgentByAgentIdDriveFilesResponse,
|
||||||
|
zGetAgentByAgentIdLogsPath,
|
||||||
|
zGetAgentByAgentIdLogsQuery,
|
||||||
|
zGetAgentByAgentIdLogsResponse,
|
||||||
zGetAgentByAgentIdMessagesByMessageIdPath,
|
zGetAgentByAgentIdMessagesByMessageIdPath,
|
||||||
zGetAgentByAgentIdMessagesByMessageIdResponse,
|
zGetAgentByAgentIdMessagesByMessageIdResponse,
|
||||||
zGetAgentByAgentIdPath,
|
zGetAgentByAgentIdPath,
|
||||||
@ -41,6 +44,9 @@ import {
|
|||||||
zGetAgentByAgentIdSandboxFilesReadQuery,
|
zGetAgentByAgentIdSandboxFilesReadQuery,
|
||||||
zGetAgentByAgentIdSandboxFilesReadResponse,
|
zGetAgentByAgentIdSandboxFilesReadResponse,
|
||||||
zGetAgentByAgentIdSandboxFilesResponse,
|
zGetAgentByAgentIdSandboxFilesResponse,
|
||||||
|
zGetAgentByAgentIdStatisticsSummaryPath,
|
||||||
|
zGetAgentByAgentIdStatisticsSummaryQuery,
|
||||||
|
zGetAgentByAgentIdStatisticsSummaryResponse,
|
||||||
zGetAgentByAgentIdVersionsByVersionIdPath,
|
zGetAgentByAgentIdVersionsByVersionIdPath,
|
||||||
zGetAgentByAgentIdVersionsByVersionIdResponse,
|
zGetAgentByAgentIdVersionsByVersionIdResponse,
|
||||||
zGetAgentByAgentIdVersionsPath,
|
zGetAgentByAgentIdVersionsPath,
|
||||||
@ -391,10 +397,27 @@ export const files2 = {
|
|||||||
post: post5,
|
post: post5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const get9 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdLogs',
|
||||||
|
path: '/agent/{agent_id}/logs',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({ params: zGetAgentByAgentIdLogsPath, query: zGetAgentByAgentIdLogsQuery.optional() }),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdLogsResponse)
|
||||||
|
|
||||||
|
export const logs = {
|
||||||
|
get: get9,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Agent App message details by ID
|
* Get Agent App message details by ID
|
||||||
*/
|
*/
|
||||||
export const get9 = oc
|
export const get10 = oc
|
||||||
.route({
|
.route({
|
||||||
description: 'Get Agent App message details by ID',
|
description: 'Get Agent App message details by ID',
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
@ -407,7 +430,7 @@ export const get9 = oc
|
|||||||
.output(zGetAgentByAgentIdMessagesByMessageIdResponse)
|
.output(zGetAgentByAgentIdMessagesByMessageIdResponse)
|
||||||
|
|
||||||
export const byMessageId2 = {
|
export const byMessageId2 = {
|
||||||
get: get9,
|
get: get10,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messages = {
|
export const messages = {
|
||||||
@ -417,7 +440,7 @@ export const messages = {
|
|||||||
/**
|
/**
|
||||||
* List workflow apps that reference this Agent App's bound Agent (read-only)
|
* List workflow apps that reference this Agent App's bound Agent (read-only)
|
||||||
*/
|
*/
|
||||||
export const get10 = oc
|
export const get11 = oc
|
||||||
.route({
|
.route({
|
||||||
description: 'List workflow apps that reference this Agent App\'s bound Agent (read-only)',
|
description: 'List workflow apps that reference this Agent App\'s bound Agent (read-only)',
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
@ -430,13 +453,13 @@ export const get10 = oc
|
|||||||
.output(zGetAgentByAgentIdReferencingWorkflowsResponse)
|
.output(zGetAgentByAgentIdReferencingWorkflowsResponse)
|
||||||
|
|
||||||
export const referencingWorkflows = {
|
export const referencingWorkflows = {
|
||||||
get: get10,
|
get: get11,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a text/binary preview file in an Agent App conversation sandbox
|
* Read a text/binary preview file in an Agent App conversation sandbox
|
||||||
*/
|
*/
|
||||||
export const get11 = oc
|
export const get12 = oc
|
||||||
.route({
|
.route({
|
||||||
description: 'Read a text/binary preview file in an Agent App conversation sandbox',
|
description: 'Read a text/binary preview file in an Agent App conversation sandbox',
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
@ -454,7 +477,7 @@ export const get11 = oc
|
|||||||
.output(zGetAgentByAgentIdSandboxFilesReadResponse)
|
.output(zGetAgentByAgentIdSandboxFilesReadResponse)
|
||||||
|
|
||||||
export const read = {
|
export const read = {
|
||||||
get: get11,
|
get: get12,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -484,7 +507,7 @@ export const upload = {
|
|||||||
/**
|
/**
|
||||||
* List a directory in an Agent App conversation sandbox
|
* List a directory in an Agent App conversation sandbox
|
||||||
*/
|
*/
|
||||||
export const get12 = oc
|
export const get13 = oc
|
||||||
.route({
|
.route({
|
||||||
description: 'List a directory in an Agent App conversation sandbox',
|
description: 'List a directory in an Agent App conversation sandbox',
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
@ -502,7 +525,7 @@ export const get12 = oc
|
|||||||
.output(zGetAgentByAgentIdSandboxFilesResponse)
|
.output(zGetAgentByAgentIdSandboxFilesResponse)
|
||||||
|
|
||||||
export const files3 = {
|
export const files3 = {
|
||||||
get: get12,
|
get: get13,
|
||||||
read,
|
read,
|
||||||
upload,
|
upload,
|
||||||
}
|
}
|
||||||
@ -596,7 +619,31 @@ export const skills = {
|
|||||||
bySlug,
|
bySlug,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const get13 = oc
|
export const get14 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdStatisticsSummary',
|
||||||
|
path: '/agent/{agent_id}/statistics/summary',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdStatisticsSummaryPath,
|
||||||
|
query: zGetAgentByAgentIdStatisticsSummaryQuery.optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdStatisticsSummaryResponse)
|
||||||
|
|
||||||
|
export const summary = {
|
||||||
|
get: get14,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const statistics = {
|
||||||
|
summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get15 = oc
|
||||||
.route({
|
.route({
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -608,10 +655,10 @@ export const get13 = oc
|
|||||||
.output(zGetAgentByAgentIdVersionsByVersionIdResponse)
|
.output(zGetAgentByAgentIdVersionsByVersionIdResponse)
|
||||||
|
|
||||||
export const byVersionId = {
|
export const byVersionId = {
|
||||||
get: get13,
|
get: get15,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const get14 = oc
|
export const get16 = oc
|
||||||
.route({
|
.route({
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -623,7 +670,7 @@ export const get14 = oc
|
|||||||
.output(zGetAgentByAgentIdVersionsResponse)
|
.output(zGetAgentByAgentIdVersionsResponse)
|
||||||
|
|
||||||
export const versions = {
|
export const versions = {
|
||||||
get: get14,
|
get: get16,
|
||||||
byVersionId,
|
byVersionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,7 +686,7 @@ export const delete3 = oc
|
|||||||
.input(z.object({ params: zDeleteAgentByAgentIdPath }))
|
.input(z.object({ params: zDeleteAgentByAgentIdPath }))
|
||||||
.output(zDeleteAgentByAgentIdResponse)
|
.output(zDeleteAgentByAgentIdResponse)
|
||||||
|
|
||||||
export const get15 = oc
|
export const get17 = oc
|
||||||
.route({
|
.route({
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -663,7 +710,7 @@ export const put2 = oc
|
|||||||
|
|
||||||
export const byAgentId = {
|
export const byAgentId = {
|
||||||
delete: delete3,
|
delete: delete3,
|
||||||
get: get15,
|
get: get17,
|
||||||
put: put2,
|
put: put2,
|
||||||
chatMessages,
|
chatMessages,
|
||||||
composer,
|
composer,
|
||||||
@ -671,14 +718,16 @@ export const byAgentId = {
|
|||||||
features,
|
features,
|
||||||
feedbacks,
|
feedbacks,
|
||||||
files: files2,
|
files: files2,
|
||||||
|
logs,
|
||||||
messages,
|
messages,
|
||||||
referencingWorkflows,
|
referencingWorkflows,
|
||||||
sandbox,
|
sandbox,
|
||||||
skills,
|
skills,
|
||||||
|
statistics,
|
||||||
versions,
|
versions,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const get16 = oc
|
export const get18 = oc
|
||||||
.route({
|
.route({
|
||||||
inputStructure: 'detailed',
|
inputStructure: 'detailed',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -702,7 +751,7 @@ export const post10 = oc
|
|||||||
.output(zPostAgentResponse)
|
.output(zPostAgentResponse)
|
||||||
|
|
||||||
export const agent = {
|
export const agent = {
|
||||||
get: get16,
|
get: get18,
|
||||||
post: post10,
|
post: post10,
|
||||||
inviteOptions,
|
inviteOptions,
|
||||||
byAgentId,
|
byAgentId,
|
||||||
|
|||||||
@ -169,6 +169,14 @@ export type AgentDriveFileCommitResponse = {
|
|||||||
file: AgentDriveFileResponse
|
file: AgentDriveFileResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AgentLogListResponse = {
|
||||||
|
data: Array<AgentLogItemResponse>
|
||||||
|
has_more: boolean
|
||||||
|
limit: number
|
||||||
|
page: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
export type MessageDetailResponse = {
|
export type MessageDetailResponse = {
|
||||||
agent_thoughts?: Array<AgentThought>
|
agent_thoughts?: Array<AgentThought>
|
||||||
annotation?: ConversationAnnotation | null
|
annotation?: ConversationAnnotation | null
|
||||||
@ -242,6 +250,12 @@ export type SkillToolInferenceResult = {
|
|||||||
reason?: string | null
|
reason?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AgentStatisticSummaryEnvelopeResponse = {
|
||||||
|
charts: AgentStatisticChartsResponse
|
||||||
|
source: string
|
||||||
|
summary: AgentStatisticSummaryResponse
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentConfigSnapshotListResponse = {
|
export type AgentConfigSnapshotListResponse = {
|
||||||
data: Array<AgentConfigSnapshotSummaryResponse>
|
data: Array<AgentConfigSnapshotSummaryResponse>
|
||||||
}
|
}
|
||||||
@ -530,6 +544,29 @@ export type AgentDriveFileResponse = {
|
|||||||
size?: number | null
|
size?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AgentLogItemResponse = {
|
||||||
|
answer: string
|
||||||
|
answer_tokens: number
|
||||||
|
conversation_id: string
|
||||||
|
conversation_name?: string | null
|
||||||
|
created_at?: number | null
|
||||||
|
currency: string
|
||||||
|
error?: string | null
|
||||||
|
from_account_id?: string | null
|
||||||
|
from_end_user_id?: string | null
|
||||||
|
from_source?: string | null
|
||||||
|
id: string
|
||||||
|
latency: number
|
||||||
|
message_id: string
|
||||||
|
message_tokens: number
|
||||||
|
query: string
|
||||||
|
source?: string | null
|
||||||
|
status: string
|
||||||
|
total_price: string
|
||||||
|
total_tokens: number
|
||||||
|
updated_at?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentThought = {
|
export type AgentThought = {
|
||||||
chain_id?: string | null
|
chain_id?: string | null
|
||||||
created_at?: number | null
|
created_at?: number | null
|
||||||
@ -644,6 +681,30 @@ export type CliToolSuggestion = {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AgentStatisticChartsResponse = {
|
||||||
|
average_response_time?: Array<AgentAverageResponseTimeStatisticResponse>
|
||||||
|
average_session_interactions?: Array<AgentAverageSessionInteractionStatisticResponse>
|
||||||
|
daily_conversations?: Array<AgentDailyConversationStatisticResponse>
|
||||||
|
daily_end_users?: Array<AgentDailyEndUserStatisticResponse>
|
||||||
|
daily_messages?: Array<AgentDailyMessageStatisticResponse>
|
||||||
|
token_usage?: Array<AgentTokenUsageStatisticResponse>
|
||||||
|
tokens_per_second?: Array<AgentTokensPerSecondStatisticResponse>
|
||||||
|
user_satisfaction_rate?: Array<AgentUserSatisfactionRateStatisticResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentStatisticSummaryResponse = {
|
||||||
|
average_response_time: number
|
||||||
|
average_session_interactions: number
|
||||||
|
currency: string
|
||||||
|
tokens_per_second: number
|
||||||
|
total_conversations: number
|
||||||
|
total_end_users: number
|
||||||
|
total_messages: number
|
||||||
|
total_price: string
|
||||||
|
total_tokens: number
|
||||||
|
user_satisfaction_rate: number
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentConfigRevisionResponse = {
|
export type AgentConfigRevisionResponse = {
|
||||||
created_at?: number | null
|
created_at?: number | null
|
||||||
created_by?: string | null
|
created_by?: string | null
|
||||||
@ -953,6 +1014,48 @@ export type EnvSuggestion = {
|
|||||||
secret_likely?: boolean
|
secret_likely?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AgentAverageResponseTimeStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
latency: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentAverageSessionInteractionStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
interactions: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentDailyConversationStatisticResponse = {
|
||||||
|
conversation_count: number
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentDailyEndUserStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
terminal_count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentDailyMessageStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
message_count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentTokenUsageStatisticResponse = {
|
||||||
|
currency: string
|
||||||
|
date: string
|
||||||
|
token_count: number
|
||||||
|
total_price: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentTokensPerSecondStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
tps: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentUserSatisfactionRateStatisticResponse = {
|
||||||
|
date: string
|
||||||
|
rate: number
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentConfigRevisionOperation
|
export type AgentConfigRevisionOperation
|
||||||
= | 'create_version'
|
= | 'create_version'
|
||||||
| 'save_current_version'
|
| 'save_current_version'
|
||||||
@ -1717,6 +1820,30 @@ export type PostAgentByAgentIdFilesResponses = {
|
|||||||
export type PostAgentByAgentIdFilesResponse
|
export type PostAgentByAgentIdFilesResponse
|
||||||
= PostAgentByAgentIdFilesResponses[keyof PostAgentByAgentIdFilesResponses]
|
= PostAgentByAgentIdFilesResponses[keyof PostAgentByAgentIdFilesResponses]
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdLogsData = {
|
||||||
|
body?: never
|
||||||
|
path: {
|
||||||
|
agent_id: string
|
||||||
|
}
|
||||||
|
query?: {
|
||||||
|
end?: string
|
||||||
|
keyword?: string
|
||||||
|
limit?: number
|
||||||
|
page?: number
|
||||||
|
source?: string
|
||||||
|
start?: string
|
||||||
|
status?: string
|
||||||
|
}
|
||||||
|
url: '/agent/{agent_id}/logs'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdLogsResponses = {
|
||||||
|
200: AgentLogListResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdLogsResponse
|
||||||
|
= GetAgentByAgentIdLogsResponses[keyof GetAgentByAgentIdLogsResponses]
|
||||||
|
|
||||||
export type GetAgentByAgentIdMessagesByMessageIdData = {
|
export type GetAgentByAgentIdMessagesByMessageIdData = {
|
||||||
body?: never
|
body?: never
|
||||||
path: {
|
path: {
|
||||||
@ -1886,6 +2013,26 @@ export type PostAgentByAgentIdSkillsBySlugInferToolsResponses = {
|
|||||||
export type PostAgentByAgentIdSkillsBySlugInferToolsResponse
|
export type PostAgentByAgentIdSkillsBySlugInferToolsResponse
|
||||||
= PostAgentByAgentIdSkillsBySlugInferToolsResponses[keyof PostAgentByAgentIdSkillsBySlugInferToolsResponses]
|
= PostAgentByAgentIdSkillsBySlugInferToolsResponses[keyof PostAgentByAgentIdSkillsBySlugInferToolsResponses]
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdStatisticsSummaryData = {
|
||||||
|
body?: never
|
||||||
|
path: {
|
||||||
|
agent_id: string
|
||||||
|
}
|
||||||
|
query?: {
|
||||||
|
end?: string
|
||||||
|
source?: string
|
||||||
|
start?: string
|
||||||
|
}
|
||||||
|
url: '/agent/{agent_id}/statistics/summary'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdStatisticsSummaryResponses = {
|
||||||
|
200: AgentStatisticSummaryEnvelopeResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAgentByAgentIdStatisticsSummaryResponse
|
||||||
|
= GetAgentByAgentIdStatisticsSummaryResponses[keyof GetAgentByAgentIdStatisticsSummaryResponses]
|
||||||
|
|
||||||
export type GetAgentByAgentIdVersionsData = {
|
export type GetAgentByAgentIdVersionsData = {
|
||||||
body?: never
|
body?: never
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
@ -321,6 +321,43 @@ export const zAgentDriveFileCommitResponse = z.object({
|
|||||||
file: zAgentDriveFileResponse,
|
file: zAgentDriveFileResponse,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentLogItemResponse
|
||||||
|
*/
|
||||||
|
export const zAgentLogItemResponse = z.object({
|
||||||
|
answer: z.string(),
|
||||||
|
answer_tokens: z.int(),
|
||||||
|
conversation_id: z.string(),
|
||||||
|
conversation_name: z.string().nullish(),
|
||||||
|
created_at: z.int().nullish(),
|
||||||
|
currency: z.string(),
|
||||||
|
error: z.string().nullish(),
|
||||||
|
from_account_id: z.string().nullish(),
|
||||||
|
from_end_user_id: z.string().nullish(),
|
||||||
|
from_source: z.string().nullish(),
|
||||||
|
id: z.string(),
|
||||||
|
latency: z.number(),
|
||||||
|
message_id: z.string(),
|
||||||
|
message_tokens: z.int(),
|
||||||
|
query: z.string(),
|
||||||
|
source: z.string().nullish(),
|
||||||
|
status: z.string(),
|
||||||
|
total_price: z.string(),
|
||||||
|
total_tokens: z.int(),
|
||||||
|
updated_at: z.int().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentLogListResponse
|
||||||
|
*/
|
||||||
|
export const zAgentLogListResponse = z.object({
|
||||||
|
data: z.array(zAgentLogItemResponse),
|
||||||
|
has_more: z.boolean(),
|
||||||
|
limit: z.int(),
|
||||||
|
page: z.int(),
|
||||||
|
total: z.int(),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AgentThought
|
* AgentThought
|
||||||
*/
|
*/
|
||||||
@ -458,6 +495,22 @@ export const zAgentSkillUploadResponse = z.object({
|
|||||||
skill: zAgentSkillRefConfig,
|
skill: zAgentSkillRefConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentStatisticSummaryResponse
|
||||||
|
*/
|
||||||
|
export const zAgentStatisticSummaryResponse = z.object({
|
||||||
|
average_response_time: z.number(),
|
||||||
|
average_session_interactions: z.number(),
|
||||||
|
currency: z.string(),
|
||||||
|
tokens_per_second: z.number(),
|
||||||
|
total_conversations: z.int(),
|
||||||
|
total_end_users: z.int(),
|
||||||
|
total_messages: z.int(),
|
||||||
|
total_price: z.string(),
|
||||||
|
total_tokens: z.int(),
|
||||||
|
user_satisfaction_rate: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModelConfigPartial
|
* ModelConfigPartial
|
||||||
*/
|
*/
|
||||||
@ -885,6 +938,97 @@ export const zSkillToolInferenceResult = z.object({
|
|||||||
reason: z.string().nullish(),
|
reason: z.string().nullish(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentAverageResponseTimeStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentAverageResponseTimeStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
latency: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentAverageSessionInteractionStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentAverageSessionInteractionStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
interactions: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentDailyConversationStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentDailyConversationStatisticResponse = z.object({
|
||||||
|
conversation_count: z.int(),
|
||||||
|
date: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentDailyEndUserStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentDailyEndUserStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
terminal_count: z.int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentDailyMessageStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentDailyMessageStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
message_count: z.int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentTokenUsageStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentTokenUsageStatisticResponse = z.object({
|
||||||
|
currency: z.string(),
|
||||||
|
date: z.string(),
|
||||||
|
token_count: z.int(),
|
||||||
|
total_price: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentTokensPerSecondStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentTokensPerSecondStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
tps: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentUserSatisfactionRateStatisticResponse
|
||||||
|
*/
|
||||||
|
export const zAgentUserSatisfactionRateStatisticResponse = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
rate: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentStatisticChartsResponse
|
||||||
|
*/
|
||||||
|
export const zAgentStatisticChartsResponse = z.object({
|
||||||
|
average_response_time: z.array(zAgentAverageResponseTimeStatisticResponse).optional(),
|
||||||
|
average_session_interactions: z
|
||||||
|
.array(zAgentAverageSessionInteractionStatisticResponse)
|
||||||
|
.optional(),
|
||||||
|
daily_conversations: z.array(zAgentDailyConversationStatisticResponse).optional(),
|
||||||
|
daily_end_users: z.array(zAgentDailyEndUserStatisticResponse).optional(),
|
||||||
|
daily_messages: z.array(zAgentDailyMessageStatisticResponse).optional(),
|
||||||
|
token_usage: z.array(zAgentTokenUsageStatisticResponse).optional(),
|
||||||
|
tokens_per_second: z.array(zAgentTokensPerSecondStatisticResponse).optional(),
|
||||||
|
user_satisfaction_rate: z.array(zAgentUserSatisfactionRateStatisticResponse).optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentStatisticSummaryEnvelopeResponse
|
||||||
|
*/
|
||||||
|
export const zAgentStatisticSummaryEnvelopeResponse = z.object({
|
||||||
|
charts: zAgentStatisticChartsResponse,
|
||||||
|
source: z.string(),
|
||||||
|
summary: zAgentStatisticSummaryResponse,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AgentConfigRevisionOperation
|
* AgentConfigRevisionOperation
|
||||||
*
|
*
|
||||||
@ -2106,6 +2250,25 @@ export const zPostAgentByAgentIdFilesPath = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zPostAgentByAgentIdFilesResponse = zAgentDriveFileCommitResponse
|
export const zPostAgentByAgentIdFilesResponse = zAgentDriveFileCommitResponse
|
||||||
|
|
||||||
|
export const zGetAgentByAgentIdLogsPath = z.object({
|
||||||
|
agent_id: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zGetAgentByAgentIdLogsQuery = z.object({
|
||||||
|
end: z.string().optional(),
|
||||||
|
keyword: z.string().optional(),
|
||||||
|
limit: z.int().gte(1).lte(100).optional().default(20),
|
||||||
|
page: z.int().gte(1).optional().default(1),
|
||||||
|
source: z.string().optional(),
|
||||||
|
start: z.string().optional(),
|
||||||
|
status: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent logs
|
||||||
|
*/
|
||||||
|
export const zGetAgentByAgentIdLogsResponse = zAgentLogListResponse
|
||||||
|
|
||||||
export const zGetAgentByAgentIdMessagesByMessageIdPath = z.object({
|
export const zGetAgentByAgentIdMessagesByMessageIdPath = z.object({
|
||||||
agent_id: z.string(),
|
agent_id: z.string(),
|
||||||
message_id: z.string(),
|
message_id: z.string(),
|
||||||
@ -2202,6 +2365,21 @@ export const zPostAgentByAgentIdSkillsBySlugInferToolsPath = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zPostAgentByAgentIdSkillsBySlugInferToolsResponse = zSkillToolInferenceResult
|
export const zPostAgentByAgentIdSkillsBySlugInferToolsResponse = zSkillToolInferenceResult
|
||||||
|
|
||||||
|
export const zGetAgentByAgentIdStatisticsSummaryPath = z.object({
|
||||||
|
agent_id: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zGetAgentByAgentIdStatisticsSummaryQuery = z.object({
|
||||||
|
end: z.string().optional(),
|
||||||
|
source: z.string().optional(),
|
||||||
|
start: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent monitoring summary and chart data
|
||||||
|
*/
|
||||||
|
export const zGetAgentByAgentIdStatisticsSummaryResponse = zAgentStatisticSummaryEnvelopeResponse
|
||||||
|
|
||||||
export const zGetAgentByAgentIdVersionsPath = z.object({
|
export const zGetAgentByAgentIdVersionsPath = z.object({
|
||||||
agent_id: z.string(),
|
agent_id: z.string(),
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user