create-windy 0.4.1 → 0.5.1
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.
- package/dist/cli.js +292 -125
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
- package/template/apps/agent-server/adk_web_agents/services.py +25 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
- package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
- package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
- package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
- package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
- package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
- package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
- package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
- package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
- package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
- package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
- package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
- package/template/apps/agent-server/tests/test_report_store.py +286 -0
- package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
- package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
- package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
- package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
- package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
- package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
- package/template/apps/server/src/agent/debug-session-id.ts +37 -0
- package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
- package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
- package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
- package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
- package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
- package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
- package/template/apps/server/src/agent/run-authorization.ts +32 -1
- package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +5 -0
- package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
- package/template/apps/server/src/agent/run-facade.test.ts +78 -0
- package/template/apps/server/src/agent/run-facade.ts +33 -3
- package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
- package/template/apps/server/src/agent/run-input-verification.ts +41 -0
- package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
- package/template/apps/server/src/agent/tool-host.test.ts +1 -0
- package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
- package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
- package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
- package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
- package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
- package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
- package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
- package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
- package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
- package/template/apps/server/src/license/runtime-service.test.ts +6 -0
- package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
- package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
- package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
- package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
- package/template/apps/server/src/module-host/initialize.ts +6 -197
- package/template/apps/server/src/module-host/request-context.ts +1 -0
- package/template/apps/server/src/module-host/scoped-host.ts +214 -0
- package/template/apps/server/src/module-host.ts +1 -0
- package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
- package/template/apps/server/src/runtime-adk-debug.ts +8 -2
- package/template/apps/server/src/system/operations.test.ts +54 -0
- package/template/apps/server/src/system/operations.ts +23 -1
- package/template/apps/server/src/system/seed.test.ts +4 -0
- package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
- package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
- package/template/apps/web/src/layout/AppSidebar.vue +6 -1
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
- package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
- package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
- package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
- package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
- package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
- package/template/apps/web/src/router/error-navigation.ts +22 -0
- package/template/apps/web/src/router/index.ts +12 -23
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
- package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
- package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
- package/template/apps/web/src/services/operations-api.ts +28 -0
- package/template/docker-compose.agent-debug.yml +1 -1
- package/template/docs/architecture/ai-runtime.md +2 -1
- package/template/docs/architecture/module-host.md +3 -0
- package/template/docs/platform/agent-run-attestation.md +65 -0
- package/template/docs/platform/agent-runtime-observability.md +135 -0
- package/template/docs/platform/agent-runtime.md +47 -3
- package/template/docs/platform/operations-overview.md +13 -0
- package/template/docs/platform/platform-authentication.md +4 -3
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
- package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
- package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
- package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
- package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
- package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
- package/template/packages/database/drizzle/meta/_journal.json +21 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
- package/template/packages/database/src/schema/agent-runtime.ts +114 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +10 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
- package/template/packages/modules/src/system-api-control-plane.ts +17 -1
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +8 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
- package/template/packages/modules/src/system-module-catalog.ts +15 -4
- package/template/packages/modules/src/system-modules-access.test.ts +69 -0
- package/template/packages/modules/src/system-modules.ts +13 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
- package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
- package/template/packages/server-sdk/src/module-host.ts +5 -0
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/ai-runtime-report.ts +134 -0
- package/template/scripts/lib/postgres-regression.ts +201 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from psycopg import AsyncConnection
|
|
6
|
+
from psycopg_pool import AsyncConnectionPool
|
|
7
|
+
|
|
8
|
+
from .postgres_report_queries import (
|
|
9
|
+
attempt_filter_clause,
|
|
10
|
+
filter_clause,
|
|
11
|
+
)
|
|
12
|
+
from .report_types import ReportFilter, iso
|
|
13
|
+
|
|
14
|
+
type DatabaseConnection = AsyncConnection[dict[str, Any]]
|
|
15
|
+
type DatabasePool = AsyncConnectionPool[DatabaseConnection]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def summary(
|
|
19
|
+
pool: DatabasePool,
|
|
20
|
+
query: ReportFilter,
|
|
21
|
+
retention_days: int,
|
|
22
|
+
generated_at: float,
|
|
23
|
+
) -> dict[str, object]:
|
|
24
|
+
run_clause, run_parameters = filter_clause(query, generated_at, "r")
|
|
25
|
+
attempt_clause, attempt_parameters = attempt_filter_clause(
|
|
26
|
+
query,
|
|
27
|
+
generated_at,
|
|
28
|
+
)
|
|
29
|
+
async with pool.connection() as connection:
|
|
30
|
+
run_row = await fetch_one(
|
|
31
|
+
connection,
|
|
32
|
+
f"""
|
|
33
|
+
select count(*) as total,
|
|
34
|
+
count(*) filter (where r.status = 'completed') as completed,
|
|
35
|
+
count(*) filter (where r.status = 'failed') as failed,
|
|
36
|
+
count(*) filter (where r.status = 'cancelled') as cancelled,
|
|
37
|
+
min(r.created_at) as available_from
|
|
38
|
+
from agent_runtime_report_runs r {run_clause}
|
|
39
|
+
""",
|
|
40
|
+
run_parameters,
|
|
41
|
+
)
|
|
42
|
+
attempt_row = await fetch_one(
|
|
43
|
+
connection,
|
|
44
|
+
f"""
|
|
45
|
+
select count(*) as attempts,
|
|
46
|
+
min(a.started_at) as available_from,
|
|
47
|
+
count(*) filter (where a.cache_status = 'hit') as cache_hit,
|
|
48
|
+
count(*) filter (where a.cache_status = 'miss') as cache_miss,
|
|
49
|
+
count(*) filter (where a.cache_status = 'unknown') as cache_unknown,
|
|
50
|
+
count(*) filter (
|
|
51
|
+
where a.total_tokens is not null
|
|
52
|
+
) as known_total_tokens,
|
|
53
|
+
sum(a.input_tokens) as input_tokens,
|
|
54
|
+
sum(a.output_tokens) as output_tokens,
|
|
55
|
+
sum(a.cached_input_tokens) as cached_input_tokens,
|
|
56
|
+
sum(a.reasoning_tokens) as reasoning_tokens,
|
|
57
|
+
sum(a.total_tokens) as total_tokens,
|
|
58
|
+
coalesce(sum(a.input_tokens) filter (
|
|
59
|
+
where a.input_tokens is not null
|
|
60
|
+
and a.cached_input_tokens is not null
|
|
61
|
+
), 0) as paired_input_tokens,
|
|
62
|
+
coalesce(sum(a.cached_input_tokens) filter (
|
|
63
|
+
where a.input_tokens is not null
|
|
64
|
+
and a.cached_input_tokens is not null
|
|
65
|
+
), 0) as paired_cached_tokens
|
|
66
|
+
from agent_runtime_report_attempts a
|
|
67
|
+
join agent_runtime_report_runs r on r.run_id = a.run_id
|
|
68
|
+
{attempt_clause}
|
|
69
|
+
""",
|
|
70
|
+
attempt_parameters,
|
|
71
|
+
)
|
|
72
|
+
model_rows = await fetch_all(
|
|
73
|
+
connection,
|
|
74
|
+
f"""
|
|
75
|
+
select a.deployment_key,
|
|
76
|
+
coalesce(a.reported_model, a.requested_model) as model,
|
|
77
|
+
count(*) as attempts,
|
|
78
|
+
sum(a.input_tokens) as input_tokens,
|
|
79
|
+
sum(a.output_tokens) as output_tokens,
|
|
80
|
+
sum(a.cached_input_tokens) as cached_input_tokens,
|
|
81
|
+
sum(a.reasoning_tokens) as reasoning_tokens,
|
|
82
|
+
sum(a.total_tokens) as total_tokens,
|
|
83
|
+
count(*) filter (
|
|
84
|
+
where a.usage_source = 'unknown'
|
|
85
|
+
) as unknown_usage_count
|
|
86
|
+
from agent_runtime_report_attempts a
|
|
87
|
+
join agent_runtime_report_runs r on r.run_id = a.run_id
|
|
88
|
+
{attempt_clause}
|
|
89
|
+
group by a.deployment_key,
|
|
90
|
+
coalesce(a.reported_model, a.requested_model)
|
|
91
|
+
order by a.deployment_key,
|
|
92
|
+
coalesce(a.reported_model, a.requested_model)
|
|
93
|
+
""",
|
|
94
|
+
attempt_parameters,
|
|
95
|
+
)
|
|
96
|
+
activity_rows = await fetch_all(
|
|
97
|
+
connection,
|
|
98
|
+
f"""
|
|
99
|
+
with run_activity as (
|
|
100
|
+
select to_char(
|
|
101
|
+
to_timestamp(r.created_at) at time zone 'UTC',
|
|
102
|
+
'YYYY-MM-DD'
|
|
103
|
+
) as date,
|
|
104
|
+
count(*) as runs,
|
|
105
|
+
count(*) filter (
|
|
106
|
+
where r.status = 'completed'
|
|
107
|
+
) as completed,
|
|
108
|
+
count(*) filter (where r.status = 'failed') as failed
|
|
109
|
+
from agent_runtime_report_runs r
|
|
110
|
+
{run_clause}
|
|
111
|
+
group by 1
|
|
112
|
+
),
|
|
113
|
+
attempt_activity as (
|
|
114
|
+
select to_char(
|
|
115
|
+
to_timestamp(a.started_at) at time zone 'UTC',
|
|
116
|
+
'YYYY-MM-DD'
|
|
117
|
+
) as date,
|
|
118
|
+
count(*) as attempts,
|
|
119
|
+
count(a.total_tokens) as known_token_attempts,
|
|
120
|
+
sum(a.total_tokens) as total_tokens
|
|
121
|
+
from agent_runtime_report_attempts a
|
|
122
|
+
join agent_runtime_report_runs r on r.run_id = a.run_id
|
|
123
|
+
{attempt_clause}
|
|
124
|
+
group by 1
|
|
125
|
+
),
|
|
126
|
+
activity_dates as (
|
|
127
|
+
select date from run_activity
|
|
128
|
+
union
|
|
129
|
+
select date from attempt_activity
|
|
130
|
+
)
|
|
131
|
+
select d.date,
|
|
132
|
+
coalesce(ra.runs, 0) as runs,
|
|
133
|
+
coalesce(ra.completed, 0) as completed,
|
|
134
|
+
coalesce(ra.failed, 0) as failed,
|
|
135
|
+
coalesce(aa.attempts, 0) as attempts,
|
|
136
|
+
aa.total_tokens
|
|
137
|
+
from activity_dates d
|
|
138
|
+
left join run_activity ra on ra.date = d.date
|
|
139
|
+
left join attempt_activity aa on aa.date = d.date
|
|
140
|
+
order by d.date
|
|
141
|
+
""",
|
|
142
|
+
(*run_parameters, *attempt_parameters),
|
|
143
|
+
)
|
|
144
|
+
return present_summary(
|
|
145
|
+
run_row,
|
|
146
|
+
attempt_row,
|
|
147
|
+
model_rows,
|
|
148
|
+
activity_rows,
|
|
149
|
+
retention_days,
|
|
150
|
+
generated_at,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def present_summary(
|
|
155
|
+
run: dict[str, Any],
|
|
156
|
+
attempt: dict[str, Any],
|
|
157
|
+
models: list[dict[str, Any]],
|
|
158
|
+
activity: list[dict[str, Any]],
|
|
159
|
+
retention_days: int,
|
|
160
|
+
generated_at: float,
|
|
161
|
+
) -> dict[str, object]:
|
|
162
|
+
total = integer(run, "total")
|
|
163
|
+
completed = integer(run, "completed")
|
|
164
|
+
failed = integer(run, "failed")
|
|
165
|
+
cancelled = integer(run, "cancelled")
|
|
166
|
+
attempts = integer(attempt, "attempts")
|
|
167
|
+
cache_hit = integer(attempt, "cache_hit")
|
|
168
|
+
cache_miss = integer(attempt, "cache_miss")
|
|
169
|
+
paired_input = integer(attempt, "paired_input_tokens")
|
|
170
|
+
available_values = [
|
|
171
|
+
value
|
|
172
|
+
for value in (
|
|
173
|
+
optional_float(run, "available_from"),
|
|
174
|
+
optional_float(attempt, "available_from"),
|
|
175
|
+
)
|
|
176
|
+
if value is not None
|
|
177
|
+
]
|
|
178
|
+
return {
|
|
179
|
+
"generatedAt": iso(generated_at),
|
|
180
|
+
"reportingAvailableFrom": iso(
|
|
181
|
+
min(available_values) if available_values else None
|
|
182
|
+
),
|
|
183
|
+
"retentionDays": retention_days,
|
|
184
|
+
"runs": {
|
|
185
|
+
"total": total,
|
|
186
|
+
"completed": completed,
|
|
187
|
+
"failed": failed,
|
|
188
|
+
"cancelled": cancelled,
|
|
189
|
+
"inProgress": total - completed - failed - cancelled,
|
|
190
|
+
"successRate": (
|
|
191
|
+
completed / (completed + failed) if completed + failed else None
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
"cache": {
|
|
195
|
+
"hit": cache_hit,
|
|
196
|
+
"miss": cache_miss,
|
|
197
|
+
"unknown": integer(attempt, "cache_unknown"),
|
|
198
|
+
"hitRate": (
|
|
199
|
+
cache_hit / (cache_hit + cache_miss) if cache_hit + cache_miss else None
|
|
200
|
+
),
|
|
201
|
+
"tokenRate": (
|
|
202
|
+
integer(attempt, "paired_cached_tokens") / paired_input
|
|
203
|
+
if paired_input > 0
|
|
204
|
+
else None
|
|
205
|
+
),
|
|
206
|
+
"coverageRate": ((cache_hit + cache_miss) / attempts if attempts else 0),
|
|
207
|
+
},
|
|
208
|
+
"tokens": {
|
|
209
|
+
"input": aggregate_token(attempt, "input_tokens", attempts),
|
|
210
|
+
"output": aggregate_token(attempt, "output_tokens", attempts),
|
|
211
|
+
"cachedInput": aggregate_token(
|
|
212
|
+
attempt,
|
|
213
|
+
"cached_input_tokens",
|
|
214
|
+
attempts,
|
|
215
|
+
),
|
|
216
|
+
"reasoning": aggregate_token(
|
|
217
|
+
attempt,
|
|
218
|
+
"reasoning_tokens",
|
|
219
|
+
attempts,
|
|
220
|
+
),
|
|
221
|
+
"total": aggregate_token(attempt, "total_tokens", attempts),
|
|
222
|
+
"coverageRate": (
|
|
223
|
+
integer(attempt, "known_total_tokens") / attempts if attempts else 0
|
|
224
|
+
),
|
|
225
|
+
},
|
|
226
|
+
"byModel": [present_model(row) for row in models],
|
|
227
|
+
"activity": [present_activity(row) for row in activity],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def present_model(row: dict[str, Any]) -> dict[str, object]:
|
|
232
|
+
return {
|
|
233
|
+
"deploymentKey": text(row, "deployment_key"),
|
|
234
|
+
"model": text(row, "model"),
|
|
235
|
+
"attempts": integer(row, "attempts"),
|
|
236
|
+
"inputTokens": optional_integer(row, "input_tokens"),
|
|
237
|
+
"outputTokens": optional_integer(row, "output_tokens"),
|
|
238
|
+
"cachedInputTokens": optional_integer(row, "cached_input_tokens"),
|
|
239
|
+
"reasoningTokens": optional_integer(row, "reasoning_tokens"),
|
|
240
|
+
"totalTokens": optional_integer(row, "total_tokens"),
|
|
241
|
+
"unknownUsageCount": integer(row, "unknown_usage_count"),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def present_activity(row: dict[str, Any]) -> dict[str, object]:
|
|
246
|
+
attempts = integer(row, "attempts")
|
|
247
|
+
known_attempts = integer(row, "known_token_attempts")
|
|
248
|
+
return {
|
|
249
|
+
"date": text(row, "date"),
|
|
250
|
+
"runs": integer(row, "runs"),
|
|
251
|
+
"completed": integer(row, "completed"),
|
|
252
|
+
"failed": integer(row, "failed"),
|
|
253
|
+
"totalTokens": (
|
|
254
|
+
optional_integer(row, "total_tokens")
|
|
255
|
+
if attempts and known_attempts == attempts
|
|
256
|
+
else None
|
|
257
|
+
),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
async def fetch_one(
|
|
262
|
+
connection: DatabaseConnection,
|
|
263
|
+
query: str,
|
|
264
|
+
parameters: tuple[object, ...],
|
|
265
|
+
) -> dict[str, Any]:
|
|
266
|
+
row = await (await connection.execute(query, parameters)).fetchone()
|
|
267
|
+
if row is None:
|
|
268
|
+
raise RuntimeError("AI Runtime 报表聚合查询未返回结果")
|
|
269
|
+
return row
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def fetch_all(
|
|
273
|
+
connection: DatabaseConnection,
|
|
274
|
+
query: str,
|
|
275
|
+
parameters: tuple[object, ...],
|
|
276
|
+
) -> list[dict[str, Any]]:
|
|
277
|
+
return list(await (await connection.execute(query, parameters)).fetchall())
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def integer(row: dict[str, Any], key: str) -> int:
|
|
281
|
+
return int(row[key])
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def optional_integer(row: dict[str, Any], key: str) -> int | None:
|
|
285
|
+
value = row[key]
|
|
286
|
+
return int(value) if value is not None else None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def aggregate_token(
|
|
290
|
+
row: dict[str, Any],
|
|
291
|
+
key: str,
|
|
292
|
+
attempt_count: int,
|
|
293
|
+
) -> int | None:
|
|
294
|
+
value = optional_integer(row, key)
|
|
295
|
+
return value if value is not None else (0 if attempt_count == 0 else None)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def text(row: dict[str, Any], key: str) -> str:
|
|
299
|
+
return str(row[key])
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def optional_float(row: dict[str, Any], key: str) -> float | None:
|
|
303
|
+
value = row[key]
|
|
304
|
+
return float(value) if value is not None else None
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
from typing import Literal, cast
|
|
6
|
+
|
|
7
|
+
from .contracts import StartRunRequest
|
|
8
|
+
from .report_store import AgentRuntimeReportStore
|
|
9
|
+
from .report_types import (
|
|
10
|
+
AttemptReport,
|
|
11
|
+
AttemptTerminalOutcome,
|
|
12
|
+
NonTerminalRunStatus,
|
|
13
|
+
UsageFacts,
|
|
14
|
+
)
|
|
15
|
+
from .store import CanonicalEvent, RunRecord, RunStore
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def ensure_run_report_safely(
|
|
21
|
+
store: AgentRuntimeReportStore,
|
|
22
|
+
run_id: str,
|
|
23
|
+
request: StartRunRequest,
|
|
24
|
+
) -> None:
|
|
25
|
+
try:
|
|
26
|
+
await store.ensure_run(run_id, request, time.time())
|
|
27
|
+
except Exception:
|
|
28
|
+
reporting_failure(store, "run_ensure", run_id)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def start_run_report_safely(
|
|
32
|
+
store: AgentRuntimeReportStore,
|
|
33
|
+
run_id: str,
|
|
34
|
+
) -> None:
|
|
35
|
+
try:
|
|
36
|
+
await store.start_run(run_id, time.time())
|
|
37
|
+
except Exception:
|
|
38
|
+
reporting_failure(store, "run_start", run_id)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def finish_run_report_safely(
|
|
42
|
+
store: AgentRuntimeReportStore,
|
|
43
|
+
run_id: str,
|
|
44
|
+
status: Literal["completed", "failed", "cancelled"],
|
|
45
|
+
safe_error_code: str | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Keep a reporting outage from changing the canonical Run terminal state."""
|
|
48
|
+
try:
|
|
49
|
+
await store.finish_run(
|
|
50
|
+
run_id,
|
|
51
|
+
status,
|
|
52
|
+
time.time(),
|
|
53
|
+
safe_error_code,
|
|
54
|
+
)
|
|
55
|
+
except Exception:
|
|
56
|
+
reporting_failure(
|
|
57
|
+
store,
|
|
58
|
+
"run_terminal",
|
|
59
|
+
run_id,
|
|
60
|
+
{"report_status": status},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def sync_terminal_from_canonical(
|
|
65
|
+
report_store: AgentRuntimeReportStore,
|
|
66
|
+
canonical_store: RunStore,
|
|
67
|
+
run_id: str,
|
|
68
|
+
fallback_safe_error_code: str | None = None,
|
|
69
|
+
) -> RunRecord | None:
|
|
70
|
+
record = await canonical_store.get(run_id)
|
|
71
|
+
if record is None or not record.terminal:
|
|
72
|
+
return None
|
|
73
|
+
status = cast(Literal["completed", "failed", "cancelled"], record.status)
|
|
74
|
+
safe_error_code = None
|
|
75
|
+
if status == "failed":
|
|
76
|
+
safe_error_code = canonical_error_code(record) or fallback_safe_error_code
|
|
77
|
+
await finish_run_report_safely(
|
|
78
|
+
report_store,
|
|
79
|
+
run_id,
|
|
80
|
+
status,
|
|
81
|
+
safe_error_code,
|
|
82
|
+
)
|
|
83
|
+
return record
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def sync_canonical_event(
|
|
87
|
+
report_store: AgentRuntimeReportStore,
|
|
88
|
+
canonical_store: RunStore,
|
|
89
|
+
run_id: str,
|
|
90
|
+
event: CanonicalEvent | None,
|
|
91
|
+
fallback_safe_error_code: str | None = None,
|
|
92
|
+
) -> RunRecord | None:
|
|
93
|
+
event_type = event.get("type") if event else None
|
|
94
|
+
if event is None or event_type in {
|
|
95
|
+
"run.completed",
|
|
96
|
+
"run.failed",
|
|
97
|
+
"run.cancelled",
|
|
98
|
+
}:
|
|
99
|
+
return await sync_terminal_from_canonical(
|
|
100
|
+
report_store,
|
|
101
|
+
canonical_store,
|
|
102
|
+
run_id,
|
|
103
|
+
fallback_safe_error_code,
|
|
104
|
+
)
|
|
105
|
+
if isinstance(event_type, str):
|
|
106
|
+
await sync_status(report_store, run_id, event_type)
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def append_and_sync_canonical_event(
|
|
111
|
+
report_store: AgentRuntimeReportStore,
|
|
112
|
+
canonical_store: RunStore,
|
|
113
|
+
run_id: str,
|
|
114
|
+
event_type: str,
|
|
115
|
+
fields: dict[str, object] | None = None,
|
|
116
|
+
fallback_safe_error_code: str | None = None,
|
|
117
|
+
) -> RunRecord | None:
|
|
118
|
+
event = await canonical_store.append(run_id, event_type, fields)
|
|
119
|
+
return await sync_canonical_event(
|
|
120
|
+
report_store,
|
|
121
|
+
canonical_store,
|
|
122
|
+
run_id,
|
|
123
|
+
event,
|
|
124
|
+
fallback_safe_error_code,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def sync_status(
|
|
129
|
+
store: AgentRuntimeReportStore,
|
|
130
|
+
run_id: str,
|
|
131
|
+
event_type: str,
|
|
132
|
+
) -> None:
|
|
133
|
+
status: NonTerminalRunStatus
|
|
134
|
+
if event_type == "approval.required":
|
|
135
|
+
status = "awaiting-approval"
|
|
136
|
+
elif event_type == "approval.resolved":
|
|
137
|
+
status = "running"
|
|
138
|
+
else:
|
|
139
|
+
return
|
|
140
|
+
try:
|
|
141
|
+
await store.set_run_status(run_id, status, time.time())
|
|
142
|
+
except Exception:
|
|
143
|
+
reporting_failure(
|
|
144
|
+
store,
|
|
145
|
+
"run_status",
|
|
146
|
+
run_id,
|
|
147
|
+
{"report_status": status},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def canonical_error_code(record: RunRecord) -> str | None:
|
|
152
|
+
for event in reversed(record.events):
|
|
153
|
+
if event.get("type") != "run.failed":
|
|
154
|
+
continue
|
|
155
|
+
error = event.get("error")
|
|
156
|
+
if isinstance(error, dict):
|
|
157
|
+
code = error.get("code")
|
|
158
|
+
if isinstance(code, str):
|
|
159
|
+
return code
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def start_attempt_report_safely(
|
|
164
|
+
store: AgentRuntimeReportStore,
|
|
165
|
+
run_id: str,
|
|
166
|
+
tool_round: int,
|
|
167
|
+
deployment_key: str,
|
|
168
|
+
requested_model: str,
|
|
169
|
+
) -> AttemptReport | None:
|
|
170
|
+
try:
|
|
171
|
+
return await store.start_attempt(
|
|
172
|
+
run_id,
|
|
173
|
+
tool_round,
|
|
174
|
+
deployment_key,
|
|
175
|
+
requested_model,
|
|
176
|
+
time.time(),
|
|
177
|
+
)
|
|
178
|
+
except Exception:
|
|
179
|
+
reporting_failure(store, "attempt_start", run_id)
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def finish_attempt_report_safely(
|
|
184
|
+
store: AgentRuntimeReportStore,
|
|
185
|
+
attempt: AttemptReport | None,
|
|
186
|
+
outcome: AttemptTerminalOutcome,
|
|
187
|
+
usage: UsageFacts,
|
|
188
|
+
*,
|
|
189
|
+
reported_model: str | None = None,
|
|
190
|
+
safe_error_code: str | None = None,
|
|
191
|
+
) -> None:
|
|
192
|
+
if attempt is None:
|
|
193
|
+
return
|
|
194
|
+
try:
|
|
195
|
+
await store.finish_attempt(
|
|
196
|
+
attempt.attempt_id,
|
|
197
|
+
outcome,
|
|
198
|
+
usage,
|
|
199
|
+
time.time(),
|
|
200
|
+
reported_model,
|
|
201
|
+
safe_error_code,
|
|
202
|
+
)
|
|
203
|
+
except Exception:
|
|
204
|
+
reporting_failure(store, "attempt_terminal", attempt.run_id)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def reporting_failure(
|
|
208
|
+
store: AgentRuntimeReportStore,
|
|
209
|
+
stage: str,
|
|
210
|
+
run_id: str,
|
|
211
|
+
extra: dict[str, str] | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
store.metrics.observe_reporting_failure(stage)
|
|
214
|
+
logger.exception(
|
|
215
|
+
"Agent Runtime 报表写入失败",
|
|
216
|
+
extra={"run_id": run_id, "report_stage": stage, **(extra or {})},
|
|
217
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from threading import Lock
|
|
5
|
+
|
|
6
|
+
from .report_types import AttemptTerminalOutcome, UsageFacts
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentRuntimeMetrics:
|
|
10
|
+
"""In-process Prometheus counters with deliberately low-cardinality labels."""
|
|
11
|
+
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._values: defaultdict[tuple[str, ...], int] = defaultdict(int)
|
|
14
|
+
self._lock = Lock()
|
|
15
|
+
|
|
16
|
+
def observe_run(self, outcome: str) -> None:
|
|
17
|
+
with self._lock:
|
|
18
|
+
self._values[("runs", outcome)] += 1
|
|
19
|
+
|
|
20
|
+
def observe_reporting_failure(self, stage: str) -> None:
|
|
21
|
+
with self._lock:
|
|
22
|
+
self._values[("reporting_failures", stage)] += 1
|
|
23
|
+
|
|
24
|
+
def observe_attempt(
|
|
25
|
+
self,
|
|
26
|
+
outcome: AttemptTerminalOutcome,
|
|
27
|
+
usage: UsageFacts,
|
|
28
|
+
) -> None:
|
|
29
|
+
with self._lock:
|
|
30
|
+
self._values[("attempts", outcome)] += 1
|
|
31
|
+
self._values[("cache", usage.cache_status)] += 1
|
|
32
|
+
self._values[("usage", usage.source)] += 1
|
|
33
|
+
for kind, value in (
|
|
34
|
+
("input", usage.input_tokens),
|
|
35
|
+
("output", usage.output_tokens),
|
|
36
|
+
("cached_input", usage.cached_input_tokens),
|
|
37
|
+
("reasoning", usage.reasoning_tokens),
|
|
38
|
+
("total", usage.total_tokens),
|
|
39
|
+
):
|
|
40
|
+
if value is not None:
|
|
41
|
+
self._values[("tokens", kind, usage.source)] += value
|
|
42
|
+
|
|
43
|
+
def render(self) -> str:
|
|
44
|
+
with self._lock:
|
|
45
|
+
values = dict(self._values)
|
|
46
|
+
lines = [
|
|
47
|
+
"# HELP southwind_agent_runtime_runs_total Terminal Agent runs.",
|
|
48
|
+
"# TYPE southwind_agent_runtime_runs_total counter",
|
|
49
|
+
]
|
|
50
|
+
lines.extend(
|
|
51
|
+
metric(
|
|
52
|
+
"southwind_agent_runtime_runs_total",
|
|
53
|
+
{"outcome": outcome},
|
|
54
|
+
values.get(("runs", outcome), 0),
|
|
55
|
+
)
|
|
56
|
+
for outcome in ("completed", "failed", "cancelled")
|
|
57
|
+
)
|
|
58
|
+
lines.extend(
|
|
59
|
+
(
|
|
60
|
+
"# HELP southwind_agent_runtime_attempts_total Provider attempts.",
|
|
61
|
+
"# TYPE southwind_agent_runtime_attempts_total counter",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
lines.extend(
|
|
65
|
+
metric(
|
|
66
|
+
"southwind_agent_runtime_attempts_total",
|
|
67
|
+
{"outcome": outcome},
|
|
68
|
+
values.get(("attempts", outcome), 0),
|
|
69
|
+
)
|
|
70
|
+
for outcome in (
|
|
71
|
+
"completed",
|
|
72
|
+
"failed",
|
|
73
|
+
"cancelled",
|
|
74
|
+
"blocked",
|
|
75
|
+
"abandoned",
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
lines.extend(
|
|
79
|
+
(
|
|
80
|
+
"# HELP southwind_agent_runtime_cache_attempts_total Cache facts.",
|
|
81
|
+
"# TYPE southwind_agent_runtime_cache_attempts_total counter",
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
lines.extend(
|
|
85
|
+
metric(
|
|
86
|
+
"southwind_agent_runtime_cache_attempts_total",
|
|
87
|
+
{"status": status},
|
|
88
|
+
values.get(("cache", status), 0),
|
|
89
|
+
)
|
|
90
|
+
for status in ("hit", "miss", "unknown")
|
|
91
|
+
)
|
|
92
|
+
lines.extend(
|
|
93
|
+
(
|
|
94
|
+
"# HELP southwind_agent_runtime_usage_attempts_total Usage coverage.",
|
|
95
|
+
"# TYPE southwind_agent_runtime_usage_attempts_total counter",
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
lines.extend(
|
|
99
|
+
metric(
|
|
100
|
+
"southwind_agent_runtime_usage_attempts_total",
|
|
101
|
+
{"source": source},
|
|
102
|
+
values.get(("usage", source), 0),
|
|
103
|
+
)
|
|
104
|
+
for source in ("provider-reported", "estimated", "unknown")
|
|
105
|
+
)
|
|
106
|
+
lines.extend(
|
|
107
|
+
(
|
|
108
|
+
"# HELP southwind_agent_runtime_reporting_failures_total "
|
|
109
|
+
"Internal reporting write failures.",
|
|
110
|
+
"# TYPE southwind_agent_runtime_reporting_failures_total counter",
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
lines.extend(
|
|
114
|
+
metric(
|
|
115
|
+
"southwind_agent_runtime_reporting_failures_total",
|
|
116
|
+
{"stage": stage},
|
|
117
|
+
values.get(("reporting_failures", stage), 0),
|
|
118
|
+
)
|
|
119
|
+
for stage in (
|
|
120
|
+
"run_ensure",
|
|
121
|
+
"run_start",
|
|
122
|
+
"run_status",
|
|
123
|
+
"run_terminal",
|
|
124
|
+
"attempt_start",
|
|
125
|
+
"attempt_terminal",
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
lines.extend(
|
|
129
|
+
(
|
|
130
|
+
"# HELP southwind_agent_runtime_tokens_total Reported token usage.",
|
|
131
|
+
"# TYPE southwind_agent_runtime_tokens_total counter",
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
for key, value in sorted(values.items()):
|
|
135
|
+
if len(key) == 3 and key[0] == "tokens":
|
|
136
|
+
_, kind, source = key
|
|
137
|
+
lines.append(
|
|
138
|
+
metric(
|
|
139
|
+
"southwind_agent_runtime_tokens_total",
|
|
140
|
+
{"kind": kind, "source": source},
|
|
141
|
+
value,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
return "\n".join(lines) + "\n"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def metric(name: str, labels: dict[str, str], value: int) -> str:
|
|
148
|
+
encoded = ",".join(f'{key}="{item}"' for key, item in labels.items())
|
|
149
|
+
return f"{name}{{{encoded}}} {value}"
|