mcp-efficiency-engine 0.1.0 → 0.1.2
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/.githooks/post-commit +20 -0
- package/.github/workflows/autodocs-sync.yml +2 -15
- package/.github/workflows/release-npm.yml +111 -0
- package/FINAL_USAGE_GUIDE.md +78 -0
- package/README.md +231 -0
- package/autodocs/README.md +6 -0
- package/bin/install-host.js +26 -1
- package/observability/metrics.md +33 -0
- package/observability/scoring-rubric.md +20 -0
- package/orchestrator/wiki/graph_consolidator.py +60 -1
- package/orchestrator/wiki/incremental_engine.py +23 -1
- package/orchestrator/wiki/plugin_manager.py +87 -22
- package/package.json +10 -2
- package/requirements.txt +2 -0
- package/scripts/intake/resolve-routing.py +124 -87
- package/scripts/intake/validate-database-routing.py +13 -4
- package/scripts/intake/validate-rag-routing.py +4 -3
- package/scripts/learning/learning-loop-report.py +28 -10
- package/scripts/ops/bye.ps1 +30 -0
- package/scripts/ops/hi.ps1 +13 -0
- package/scripts/ops/llm-token-probe.py +103 -39
- package/scripts/ops/post-commit-refresh.ps1 +110 -0
- package/scripts/ops/publish-langsmith-kpis.py +302 -0
- package/scripts/ops/telemetry-benchmark.py +184 -0
- package/scripts/setup/install-project-hooks.ps1 +33 -0
- package/scripts/setup/setup-prerequisites.ps1 +42 -15
- package/scripts/wiki/compiler_main.py +108 -76
- package/scripts/wiki/providers/repo_content_provider.py +51 -0
- package/scripts/wiki/validator.py +40 -15
- package/telemetry/__init__.py +4 -0
- package/telemetry/bootstrap.py +47 -0
- package/telemetry/collector/engine.py +261 -0
- package/telemetry/config.json +20 -0
- package/telemetry/config.py +131 -0
- package/telemetry/context/propagation.py +85 -0
- package/telemetry/events/catalog.py +24 -0
- package/telemetry/events/factory.py +42 -0
- package/telemetry/exporters/base.py +16 -0
- package/telemetry/exporters/console/exporter.py +30 -0
- package/telemetry/exporters/json/exporter.py +34 -0
- package/telemetry/exporters/langsmith/exporter.py +415 -0
- package/telemetry/metrics/aggregator.py +60 -0
- package/telemetry/scoring/efficiency.py +68 -0
- package/telemetry/tracing/span.py +43 -0
- package/observability/evals/chat-token-usage-report.json +0 -64
- package/observability/evals/chat-token-usage-report.md +0 -33
- package/observability/evals/iteration-value-report.json +0 -76
- package/observability/evals/iteration-value-report.md +0 -39
- package/observability/evals/learning-loop-report.json +0 -268
- package/observability/evals/learning-loop-report.md +0 -37
- package/observability/evals/repo-intake-eval-cases.md +0 -23
- package/observability/evals/routing-eval-cases.json +0 -68
- package/observability/evals/routing-eval-cases.md +0 -25
- package/observability/evals/routing-eval-report.json +0 -72
- package/observability/logs/copilot-usage-ingest-report.json +0 -13
- package/observability/logs/iteration-metrics.jsonl +0 -75
- package/observability/logs/learning-feedback.jsonl +0 -152
- package/observability/logs/routing-decisions.jsonl +0 -333
- package/observability/logs/session/agent-pipeline-preflight-20260701-004146.log +0 -8
- package/observability/logs/session/agent-pipeline-preflight-20260701-005554.log +0 -8
- package/observability/logs/session/agent-pipeline-preflight-20260701-010351.log +0 -8
- package/observability/logs/session/agent-pipeline-preflight-20260704-145829.log +0 -8
- package/observability/logs/session/agent-pipeline-preflight-20260704-150021.log +0 -8
- package/observability/logs/session/agent-pipeline-preflight-20260704-154328.log +0 -8
- package/observability/logs/session/bye-20260629-221513.json +0 -39
- package/observability/logs/session/bye-20260629-221545.json +0 -59
- package/observability/logs/session/bye-20260629-222007.json +0 -52
- package/observability/logs/session/bye-20260629-222133.json +0 -59
- package/observability/logs/session/bye-20260629-222412.json +0 -45
- package/observability/logs/session/bye-20260629-223224.json +0 -52
- package/observability/logs/session/bye-20260629-224050.json +0 -59
- package/observability/logs/session/bye-20260630-105732.json +0 -24
- package/observability/logs/session/bye-20260630-211536.json +0 -87
- package/observability/logs/session/bye-20260630-223423.json +0 -87
- package/observability/logs/session/bye-20260630-232534.json +0 -87
- package/observability/logs/session/bye-20260701-003638.json +0 -126
- package/observability/logs/session/bye-20260701-003849.json +0 -126
- package/observability/logs/session/bye-20260701-004315.json +0 -138
- package/observability/logs/session/bye-20260701-004559.json +0 -76
- package/observability/logs/session/bye-20260701-005913.json +0 -138
- package/observability/logs/session/bye-20260701-010753.json +0 -138
- package/observability/logs/session/bye-20260703-194106.json +0 -24
- package/observability/logs/session/bye-20260703-233443.json +0 -76
- package/observability/logs/session/bye-20260703-233653.json +0 -76
- package/observability/logs/session/bye-20260704-150317.json +0 -152
- package/observability/logs/session/bye-20260704-150929.json +0 -75
- package/observability/logs/session/bye-20260704-151050.json +0 -75
- package/observability/logs/session/bye-20260705-115458.json +0 -165
- package/observability/logs/session/codegraph-status-20260701-004228.log +0 -39
- package/observability/logs/session/codegraph-status-20260701-004310.log +0 -39
- package/observability/logs/session/codegraph-status-20260701-005637.log +0 -39
- package/observability/logs/session/codegraph-status-20260701-005908.log +0 -39
- package/observability/logs/session/codegraph-status-20260701-010444.log +0 -39
- package/observability/logs/session/codegraph-status-20260701-010747.log +0 -39
- package/observability/logs/session/codegraph-status-20260704-150137.log +0 -26
- package/observability/logs/session/codegraph-status-20260704-150313.log +0 -28
- package/observability/logs/session/codegraph-status-20260704-154445.log +0 -26
- package/observability/logs/session/codegraph-status-20260705-115454.log +0 -27
- package/observability/logs/session/codegraph-sync-20260701-004211.log +0 -88
- package/observability/logs/session/codegraph-sync-20260701-004241.log +0 -82
- package/observability/logs/session/codegraph-sync-20260701-005620.log +0 -89
- package/observability/logs/session/codegraph-sync-20260701-005840.log +0 -88
- package/observability/logs/session/codegraph-sync-20260701-010419.log +0 -93
- package/observability/logs/session/codegraph-sync-20260701-010609.log +0 -90
- package/observability/logs/session/codegraph-sync-20260704-145856.log +0 -70
- package/observability/logs/session/codegraph-sync-20260704-150047.log +0 -53
- package/observability/logs/session/codegraph-sync-20260704-150204.log +0 -44
- package/observability/logs/session/codegraph-sync-20260704-154353.log +0 -26
- package/observability/logs/session/codegraph-sync-20260705-115337.log +0 -32
- package/observability/logs/session/hi-20260629-221459.json +0 -38
- package/observability/logs/session/hi-20260629-221945.json +0 -59
- package/observability/logs/session/hi-20260629-222108.json +0 -66
- package/observability/logs/session/hi-20260629-222352.json +0 -45
- package/observability/logs/session/hi-20260629-225719.json +0 -59
- package/observability/logs/session/hi-20260629-233722.json +0 -52
- package/observability/logs/session/hi-20260629-233913.json +0 -198
- package/observability/logs/session/hi-20260630-110156.json +0 -177
- package/observability/logs/session/hi-20260630-191243.json +0 -184
- package/observability/logs/session/hi-20260630-230050.json +0 -234
- package/observability/logs/session/hi-20260630-230523.json +0 -227
- package/observability/logs/session/hi-20260630-230855.json +0 -227
- package/observability/logs/session/hi-20260630-231638.json +0 -227
- package/observability/logs/session/hi-20260630-231756.json +0 -219
- package/observability/logs/session/hi-20260701-002842.json +0 -234
- package/observability/logs/session/hi-20260701-003458.json +0 -282
- package/observability/logs/session/hi-20260701-004233.json +0 -290
- package/observability/logs/session/hi-20260701-004554.json +0 -236
- package/observability/logs/session/hi-20260701-005641.json +0 -290
- package/observability/logs/session/hi-20260701-010450.json +0 -290
- package/observability/logs/session/hi-20260703-233854.json +0 -312
- package/observability/logs/session/hi-20260703-234448.json +0 -318
- package/observability/logs/session/hi-20260704-145907.json +0 -353
- package/observability/logs/session/hi-20260704-150141.json +0 -392
- package/observability/logs/session/hi-20260704-154448.json +0 -392
- package/observability/logs/session/iteration-value-refresh-20260701-004309.log +0 -3
- package/observability/logs/session/iteration-value-refresh-20260701-005907.log +0 -3
- package/observability/logs/session/iteration-value-refresh-20260701-010747.log +0 -3
- package/observability/logs/session/iteration-value-refresh-20260704-150312.log +0 -3
- package/observability/logs/session/iteration-value-refresh-20260705-115453.log +0 -3
- package/observability/logs/session/learning-loop-refresh-20260701-004309.log +0 -3
- package/observability/logs/session/learning-loop-refresh-20260701-005906.log +0 -3
- package/observability/logs/session/learning-loop-refresh-20260701-010745.log +0 -3
- package/observability/logs/session/learning-loop-refresh-20260704-150309.log +0 -3
- package/observability/logs/session/learning-loop-refresh-20260705-115450.log +0 -3
- package/observability/logs/session/project-notes-refresh-20260701-004227.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260701-004308.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260701-005636.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260701-005906.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260701-010443.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260701-010744.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260704-150133.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260704-150309.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260704-154442.log +0 -1
- package/observability/logs/session/project-notes-refresh-20260705-115449.log +0 -1
- package/observability/logs/session/repo-intake-20260701-004218.log +0 -6
- package/observability/logs/session/repo-intake-20260701-005626.log +0 -6
- package/observability/logs/session/repo-intake-20260701-010433.log +0 -6
- package/observability/logs/session/repo-intake-20260704-150053.log +0 -6
- package/observability/logs/session/repo-intake-20260704-154404.log +0 -6
- package/observability/logs/session/repo-intake-refresh-20260701-004247.log +0 -6
- package/observability/logs/session/repo-intake-refresh-20260701-005847.log +0 -6
- package/observability/logs/session/repo-intake-refresh-20260701-010615.log +0 -6
- package/observability/logs/session/repo-intake-refresh-20260704-150209.log +0 -6
- package/observability/logs/session/repo-intake-refresh-20260705-115342.log +0 -6
- package/observability/logs/session/repo-registry-validation-20260701-004234.log +0 -2
- package/observability/logs/session/repo-registry-validation-20260701-004556.log +0 -2
- package/observability/logs/session/repo-registry-validation-20260701-005833.log +0 -2
- package/observability/logs/session/repo-registry-validation-20260701-010601.log +0 -2
- package/observability/logs/session/repo-registry-validation-20260704-150155.log +0 -2
- package/observability/logs/session/repo-registry-validation-20260705-115323.log +0 -2
- package/observability/logs/session/repomix-refresh-20260701-003823.log +0 -4236
- package/observability/logs/session/repomix-refresh-20260701-004251.log +0 -4275
- package/observability/logs/session/repomix-refresh-20260701-005850.log +0 -4248
- package/observability/logs/session/repomix-refresh-20260701-010619.log +0 -6531
- package/observability/logs/session/repomix-refresh-20260704-150239.log +0 -652
- package/observability/logs/session/repomix-refresh-20260705-115419.log +0 -178
- package/observability/logs/session/routing-evals-20260701-004221.log +0 -2
- package/observability/logs/session/routing-evals-20260701-004235.log +0 -2
- package/observability/logs/session/routing-evals-20260701-005629.log +0 -2
- package/observability/logs/session/routing-evals-20260701-005834.log +0 -2
- package/observability/logs/session/routing-evals-20260701-010437.log +0 -2
- package/observability/logs/session/routing-evals-20260701-010603.log +0 -2
- package/observability/logs/session/routing-evals-20260704-150125.log +0 -2
- package/observability/logs/session/routing-evals-20260704-150157.log +0 -2
- package/observability/logs/session/routing-evals-20260704-154434.log +0 -2
- package/observability/logs/session/routing-evals-20260705-115327.log +0 -2
- package/observability/logs/session/sibling-discovery-refresh-20260701-004250.log +0 -3
- package/observability/logs/session/sibling-discovery-refresh-20260701-005850.log +0 -3
- package/observability/logs/session/sibling-discovery-refresh-20260701-010618.log +0 -3
- package/observability/logs/session/sibling-discovery-refresh-20260704-150238.log +0 -3
- package/observability/logs/session/sibling-discovery-refresh-20260705-115418.log +0 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from datetime import timezone
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
from uuid import uuid5
|
|
8
|
+
from uuid import NAMESPACE_URL
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LangSmithExporter:
|
|
13
|
+
name = "langsmith"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
api_key: str,
|
|
19
|
+
project: str,
|
|
20
|
+
endpoint: str | None = None,
|
|
21
|
+
workspace_id: str | None = None,
|
|
22
|
+
high_signal_only: bool = True,
|
|
23
|
+
min_span_duration_ms: float = 100.0,
|
|
24
|
+
emit_execution_summary: bool = True,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.api_key = api_key.strip()
|
|
27
|
+
self.project = project.strip()
|
|
28
|
+
self.endpoint = (endpoint or "").strip() or None
|
|
29
|
+
self.workspace_id = (workspace_id or "").strip() or None
|
|
30
|
+
self.high_signal_only = bool(high_signal_only)
|
|
31
|
+
self.min_span_duration_ms = max(0.0, float(min_span_duration_ms))
|
|
32
|
+
self.emit_execution_summary = bool(emit_execution_summary)
|
|
33
|
+
self._client: Any | None = None
|
|
34
|
+
self._usage_by_execution: dict[str, dict[str, float]] = defaultdict(dict)
|
|
35
|
+
self._execution_state: dict[str, dict[str, Any]] = defaultdict(dict)
|
|
36
|
+
if not self.api_key or not self.project:
|
|
37
|
+
raise ValueError("LangSmith exporter requires api_key and project")
|
|
38
|
+
|
|
39
|
+
def _update_execution_state(self, record: dict[str, Any], timestamp: datetime) -> None:
|
|
40
|
+
context = record.get("context", {})
|
|
41
|
+
payload = record.get("payload", {})
|
|
42
|
+
execution_id = str(context.get("execution_id") or "")
|
|
43
|
+
if not execution_id:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
state = self._execution_state[execution_id]
|
|
47
|
+
state.setdefault("warnings", 0)
|
|
48
|
+
state.setdefault("errors", 0)
|
|
49
|
+
state.setdefault("operation", payload.get("operation"))
|
|
50
|
+
state.setdefault("provider", context.get("provider"))
|
|
51
|
+
state.setdefault("model", context.get("model"))
|
|
52
|
+
|
|
53
|
+
event_name = str(record.get("event_name", ""))
|
|
54
|
+
record_type = str(record.get("type", "event"))
|
|
55
|
+
level = str(record.get("level", "INFO")).upper()
|
|
56
|
+
|
|
57
|
+
if event_name == "ExecutionStarted":
|
|
58
|
+
started_at = payload.get("started_at") or record.get("timestamp")
|
|
59
|
+
state["started_at"] = self._parse_timestamp(started_at)
|
|
60
|
+
if payload.get("operation"):
|
|
61
|
+
state["operation"] = payload.get("operation")
|
|
62
|
+
elif event_name == "RoutingResolved":
|
|
63
|
+
if payload.get("agent"):
|
|
64
|
+
state["agent"] = payload.get("agent")
|
|
65
|
+
if payload.get("engine"):
|
|
66
|
+
state["engine"] = payload.get("engine")
|
|
67
|
+
elif event_name in {"WarningGenerated"} or level == "WARNING":
|
|
68
|
+
state["warnings"] = int(state.get("warnings", 0)) + 1
|
|
69
|
+
elif event_name in {"ExceptionThrown"} or level == "ERROR":
|
|
70
|
+
state["errors"] = int(state.get("errors", 0)) + 1
|
|
71
|
+
if payload.get("error"):
|
|
72
|
+
state["last_error"] = payload.get("error")
|
|
73
|
+
|
|
74
|
+
if record_type == "span":
|
|
75
|
+
status = str(payload.get("status", "OK")).upper()
|
|
76
|
+
if status != "OK":
|
|
77
|
+
state["errors"] = int(state.get("errors", 0)) + 1
|
|
78
|
+
state["last_span_duration_ms"] = payload.get("duration_ms")
|
|
79
|
+
|
|
80
|
+
def _build_execution_summary(self, execution_id: str, context: dict[str, Any], timestamp: datetime) -> dict[str, Any]:
|
|
81
|
+
state = self._execution_state.get(execution_id, {})
|
|
82
|
+
started_at = state.get("started_at")
|
|
83
|
+
if isinstance(started_at, datetime):
|
|
84
|
+
duration_ms = max(0.0, (timestamp - started_at).total_seconds() * 1000.0)
|
|
85
|
+
else:
|
|
86
|
+
duration_ms = 0.0
|
|
87
|
+
|
|
88
|
+
warnings = int(state.get("warnings", 0))
|
|
89
|
+
errors = int(state.get("errors", 0))
|
|
90
|
+
status = "error" if errors > 0 else "ok"
|
|
91
|
+
|
|
92
|
+
usage = self._usage_by_execution.get(execution_id, {})
|
|
93
|
+
usage_summary = {
|
|
94
|
+
"input_tokens": int(usage.get("tokens_input", 0.0)),
|
|
95
|
+
"output_tokens": int(usage.get("tokens_output", 0.0)),
|
|
96
|
+
"total_tokens": int(usage.get("total_tokens", 0.0)),
|
|
97
|
+
"total_cost": float(usage.get("estimated_cost", 0.0)),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
summary = {
|
|
101
|
+
"name": "ExecutionSummary",
|
|
102
|
+
"run_type": "chain",
|
|
103
|
+
"inputs": {
|
|
104
|
+
"execution_id": execution_id,
|
|
105
|
+
"operation": state.get("operation"),
|
|
106
|
+
"agent": state.get("agent"),
|
|
107
|
+
"engine": state.get("engine"),
|
|
108
|
+
},
|
|
109
|
+
"outputs": {
|
|
110
|
+
"status": status,
|
|
111
|
+
"duration_ms": duration_ms,
|
|
112
|
+
"warning_count": warnings,
|
|
113
|
+
"error_count": errors,
|
|
114
|
+
"usage_metadata": usage_summary,
|
|
115
|
+
"provider": context.get("provider") or state.get("provider"),
|
|
116
|
+
"model": context.get("model") or state.get("model"),
|
|
117
|
+
},
|
|
118
|
+
"extra": {
|
|
119
|
+
"metadata": {
|
|
120
|
+
"execution_id": execution_id,
|
|
121
|
+
"telemetry_kind": "execution_summary",
|
|
122
|
+
"last_error": state.get("last_error"),
|
|
123
|
+
"usage_metadata": usage_summary,
|
|
124
|
+
"ls_provider": context.get("provider") or state.get("provider"),
|
|
125
|
+
"ls_model_name": context.get("model") or state.get("model"),
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"id": self._to_uuid(f"{execution_id}:execution-summary", salt="run"),
|
|
129
|
+
"start_time": started_at if isinstance(started_at, datetime) else timestamp,
|
|
130
|
+
"end_time": timestamp,
|
|
131
|
+
"total_tokens": usage_summary["total_tokens"],
|
|
132
|
+
"total_cost": usage_summary["total_cost"],
|
|
133
|
+
"tags": ["telemetry", "execution", "summary"],
|
|
134
|
+
}
|
|
135
|
+
return summary
|
|
136
|
+
|
|
137
|
+
def _is_high_signal_record(self, record: dict[str, Any]) -> bool:
|
|
138
|
+
record_type = str(record.get("type", "event"))
|
|
139
|
+
event_name = str(record.get("event_name", "TelemetryEvent"))
|
|
140
|
+
payload = record.get("payload", {})
|
|
141
|
+
level = str(record.get("level", "INFO")).upper()
|
|
142
|
+
|
|
143
|
+
if level in {"ERROR", "WARNING"}:
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
if record_type == "metric":
|
|
147
|
+
# Emit only the per-execution summary for usage/cost to avoid noise.
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
if record_type == "span":
|
|
151
|
+
duration_ms = float(payload.get("duration_ms") or 0.0)
|
|
152
|
+
status = str(payload.get("status", "OK")).upper()
|
|
153
|
+
return status != "OK" or duration_ms >= self.min_span_duration_ms
|
|
154
|
+
|
|
155
|
+
return event_name in {
|
|
156
|
+
"ExecutionStarted",
|
|
157
|
+
"ExecutionFinished",
|
|
158
|
+
"RoutingResolved",
|
|
159
|
+
"ExceptionThrown",
|
|
160
|
+
"WarningGenerated",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def _to_uuid(self, value: Any, *, salt: str = "") -> str | None:
|
|
164
|
+
if value is None:
|
|
165
|
+
return None
|
|
166
|
+
raw = str(value).strip()
|
|
167
|
+
if not raw:
|
|
168
|
+
return None
|
|
169
|
+
try:
|
|
170
|
+
return str(UUID(raw))
|
|
171
|
+
except Exception:
|
|
172
|
+
try:
|
|
173
|
+
return str(UUID(hex=raw))
|
|
174
|
+
except Exception:
|
|
175
|
+
return str(uuid5(NAMESPACE_URL, f"mcpee:{salt}:{raw}"))
|
|
176
|
+
|
|
177
|
+
def _parse_timestamp(self, value: Any) -> datetime:
|
|
178
|
+
if isinstance(value, datetime):
|
|
179
|
+
return value.astimezone(timezone.utc)
|
|
180
|
+
if isinstance(value, str) and value:
|
|
181
|
+
try:
|
|
182
|
+
return datetime.fromisoformat(value).astimezone(timezone.utc)
|
|
183
|
+
except Exception:
|
|
184
|
+
pass
|
|
185
|
+
return datetime.now(timezone.utc)
|
|
186
|
+
|
|
187
|
+
def _build_usage_summary(self, execution_id: str, context: dict[str, Any], timestamp: datetime) -> dict[str, Any]:
|
|
188
|
+
usage = self._usage_by_execution.get(execution_id, {})
|
|
189
|
+
model = context.get("model")
|
|
190
|
+
provider = context.get("provider")
|
|
191
|
+
input_tokens = int(usage.get("tokens_input", 0.0))
|
|
192
|
+
output_tokens = int(usage.get("tokens_output", 0.0))
|
|
193
|
+
total_tokens = int(usage.get("total_tokens", 0.0))
|
|
194
|
+
total_cost = float(usage.get("estimated_cost", 0.0))
|
|
195
|
+
usage_metadata = {
|
|
196
|
+
"input_tokens": input_tokens,
|
|
197
|
+
"output_tokens": output_tokens,
|
|
198
|
+
"total_tokens": total_tokens,
|
|
199
|
+
"total_cost": total_cost,
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
"name": "UsageSummary",
|
|
203
|
+
"run_type": "llm",
|
|
204
|
+
"inputs": {
|
|
205
|
+
"execution_id": execution_id,
|
|
206
|
+
"provider": provider,
|
|
207
|
+
"model": model,
|
|
208
|
+
},
|
|
209
|
+
"outputs": {
|
|
210
|
+
"model": model,
|
|
211
|
+
"provider": provider,
|
|
212
|
+
"usage_metadata": usage_metadata,
|
|
213
|
+
"status": "recorded",
|
|
214
|
+
},
|
|
215
|
+
"extra": {
|
|
216
|
+
"metadata": {
|
|
217
|
+
"execution_id": execution_id,
|
|
218
|
+
"trace_id": context.get("trace_id"),
|
|
219
|
+
"provider": provider,
|
|
220
|
+
"model": model,
|
|
221
|
+
"ls_provider": provider,
|
|
222
|
+
"ls_model_name": model,
|
|
223
|
+
"usage_metadata": usage_metadata,
|
|
224
|
+
"telemetry_kind": "usage_summary",
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"id": self._to_uuid(f"{execution_id}:usage-summary", salt="run"),
|
|
228
|
+
"start_time": timestamp,
|
|
229
|
+
"end_time": timestamp,
|
|
230
|
+
"prompt_tokens": input_tokens,
|
|
231
|
+
"completion_tokens": output_tokens,
|
|
232
|
+
"total_tokens": total_tokens,
|
|
233
|
+
"total_cost": total_cost,
|
|
234
|
+
"tags": ["telemetry", "usage", "cost"],
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
def _run_payload(self, record: dict[str, Any], *, index: int) -> dict[str, Any]:
|
|
238
|
+
event_name = str(record.get("event_name", "TelemetryEvent"))
|
|
239
|
+
context = record.get("context", {})
|
|
240
|
+
payload = record.get("payload", {})
|
|
241
|
+
record_type = str(record.get("type", "event"))
|
|
242
|
+
level = str(record.get("level", "INFO"))
|
|
243
|
+
execution_id = str(context.get("execution_id") or "")
|
|
244
|
+
provider = context.get("provider")
|
|
245
|
+
model = context.get("model")
|
|
246
|
+
timestamp = self._parse_timestamp(record.get("timestamp"))
|
|
247
|
+
|
|
248
|
+
inputs: dict[str, Any] = {"event": event_name, "type": record_type}
|
|
249
|
+
outputs: dict[str, Any] = {"status": "recorded", "level": level}
|
|
250
|
+
run_type = "chain"
|
|
251
|
+
prompt_tokens: int | None = None
|
|
252
|
+
completion_tokens: int | None = None
|
|
253
|
+
total_tokens: int | None = None
|
|
254
|
+
total_cost: float | None = None
|
|
255
|
+
|
|
256
|
+
if record_type == "metric":
|
|
257
|
+
metric_name = str(payload.get("name", "unknown"))
|
|
258
|
+
metric_value = payload.get("value")
|
|
259
|
+
metric_unit = payload.get("unit")
|
|
260
|
+
inputs = {
|
|
261
|
+
"metric": metric_name,
|
|
262
|
+
"value": metric_value,
|
|
263
|
+
"unit": metric_unit,
|
|
264
|
+
"execution_id": execution_id,
|
|
265
|
+
}
|
|
266
|
+
outputs = {
|
|
267
|
+
"metric": metric_name,
|
|
268
|
+
"value": metric_value,
|
|
269
|
+
"unit": metric_unit,
|
|
270
|
+
"provider": provider,
|
|
271
|
+
"model": model,
|
|
272
|
+
"status": "recorded",
|
|
273
|
+
}
|
|
274
|
+
if metric_name in {"tokens_input", "tokens_output", "total_tokens", "estimated_cost"} and execution_id:
|
|
275
|
+
self._usage_by_execution[execution_id][metric_name] = float(metric_value or 0.0)
|
|
276
|
+
run_type = "llm"
|
|
277
|
+
if metric_name == "tokens_input":
|
|
278
|
+
prompt_tokens = int(float(metric_value or 0.0))
|
|
279
|
+
elif metric_name == "tokens_output":
|
|
280
|
+
completion_tokens = int(float(metric_value or 0.0))
|
|
281
|
+
elif metric_name == "total_tokens":
|
|
282
|
+
total_tokens = int(float(metric_value or 0.0))
|
|
283
|
+
elif metric_name == "estimated_cost":
|
|
284
|
+
total_cost = float(metric_value or 0.0)
|
|
285
|
+
elif record_type == "span":
|
|
286
|
+
run_type = "tool"
|
|
287
|
+
inputs = {
|
|
288
|
+
"span": payload.get("name"),
|
|
289
|
+
"kind": payload.get("kind"),
|
|
290
|
+
"execution_id": execution_id,
|
|
291
|
+
}
|
|
292
|
+
outputs = {
|
|
293
|
+
"status": payload.get("status", "recorded"),
|
|
294
|
+
"duration_ms": payload.get("duration_ms"),
|
|
295
|
+
"error": payload.get("error"),
|
|
296
|
+
"provider": provider,
|
|
297
|
+
"model": model,
|
|
298
|
+
}
|
|
299
|
+
else:
|
|
300
|
+
inputs = {
|
|
301
|
+
"event": event_name,
|
|
302
|
+
"operation": payload.get("operation"),
|
|
303
|
+
"execution_id": execution_id,
|
|
304
|
+
}
|
|
305
|
+
outputs = {
|
|
306
|
+
"status": payload.get("status", "recorded"),
|
|
307
|
+
"provider": provider,
|
|
308
|
+
"model": model,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
metadata = {
|
|
312
|
+
"execution_id": context.get("execution_id"),
|
|
313
|
+
"trace_id": context.get("trace_id"),
|
|
314
|
+
"span_id": context.get("span_id"),
|
|
315
|
+
"parent_span_id": context.get("parent_span_id"),
|
|
316
|
+
"request_id": context.get("request_id"),
|
|
317
|
+
"session_id": context.get("session_id"),
|
|
318
|
+
"correlation_id": context.get("correlation_id"),
|
|
319
|
+
"user": context.get("user"),
|
|
320
|
+
"provider": provider,
|
|
321
|
+
"model": model,
|
|
322
|
+
"ls_provider": provider,
|
|
323
|
+
"ls_model_name": model,
|
|
324
|
+
"event_name": event_name,
|
|
325
|
+
"type": record_type,
|
|
326
|
+
"level": level,
|
|
327
|
+
"payload": payload,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if prompt_tokens is not None or completion_tokens is not None or total_tokens is not None or total_cost is not None:
|
|
331
|
+
metadata["usage_metadata"] = {
|
|
332
|
+
"input_tokens": prompt_tokens or 0,
|
|
333
|
+
"output_tokens": completion_tokens or 0,
|
|
334
|
+
"total_tokens": total_tokens or 0,
|
|
335
|
+
"total_cost": total_cost or 0.0,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
run_id = self._to_uuid(
|
|
339
|
+
f"{execution_id or 'no-exec'}:{context.get('span_id') or 'no-span'}:{event_name}:{record_type}:{index}",
|
|
340
|
+
salt="run",
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
run_payload: dict[str, Any] = {
|
|
344
|
+
"name": event_name,
|
|
345
|
+
"run_type": run_type,
|
|
346
|
+
"inputs": inputs,
|
|
347
|
+
"outputs": outputs,
|
|
348
|
+
"project_name": self.project,
|
|
349
|
+
"extra": {"metadata": metadata},
|
|
350
|
+
"id": run_id,
|
|
351
|
+
"start_time": timestamp,
|
|
352
|
+
"end_time": timestamp,
|
|
353
|
+
"tags": ["telemetry", record_type],
|
|
354
|
+
"_usage_ready": record_type == "metric" and str(payload.get("name", "")) == "estimated_cost" and bool(execution_id),
|
|
355
|
+
"_execution_id": execution_id,
|
|
356
|
+
"_context": context,
|
|
357
|
+
}
|
|
358
|
+
if prompt_tokens is not None:
|
|
359
|
+
run_payload["prompt_tokens"] = prompt_tokens
|
|
360
|
+
if completion_tokens is not None:
|
|
361
|
+
run_payload["completion_tokens"] = completion_tokens
|
|
362
|
+
if total_tokens is not None:
|
|
363
|
+
run_payload["total_tokens"] = total_tokens
|
|
364
|
+
if total_cost is not None:
|
|
365
|
+
run_payload["total_cost"] = total_cost
|
|
366
|
+
return run_payload
|
|
367
|
+
|
|
368
|
+
def _get_client(self) -> Any:
|
|
369
|
+
if self._client is not None:
|
|
370
|
+
return self._client
|
|
371
|
+
try:
|
|
372
|
+
from langsmith import Client # type: ignore
|
|
373
|
+
except Exception as exc:
|
|
374
|
+
raise RuntimeError("langsmith SDK is not installed") from exc
|
|
375
|
+
|
|
376
|
+
kwargs: dict[str, Any] = {"api_key": self.api_key}
|
|
377
|
+
if self.endpoint:
|
|
378
|
+
kwargs["api_url"] = self.endpoint
|
|
379
|
+
if self.workspace_id:
|
|
380
|
+
kwargs["workspace_id"] = self.workspace_id
|
|
381
|
+
self._client = Client(**kwargs)
|
|
382
|
+
return self._client
|
|
383
|
+
|
|
384
|
+
def export(self, records: list[dict]) -> None:
|
|
385
|
+
client = self._get_client()
|
|
386
|
+
for index, record in enumerate(records):
|
|
387
|
+
run = self._run_payload(record, index=index)
|
|
388
|
+
usage_ready = bool(run.pop("_usage_ready", False))
|
|
389
|
+
execution_id = str(run.pop("_execution_id", "") or "")
|
|
390
|
+
context = run.pop("_context", {})
|
|
391
|
+
timestamp = run["end_time"]
|
|
392
|
+
|
|
393
|
+
self._update_execution_state(record, timestamp)
|
|
394
|
+
|
|
395
|
+
should_emit = True
|
|
396
|
+
if self.high_signal_only:
|
|
397
|
+
should_emit = self._is_high_signal_record(record)
|
|
398
|
+
|
|
399
|
+
if should_emit:
|
|
400
|
+
client.create_run(**run)
|
|
401
|
+
|
|
402
|
+
if usage_ready and execution_id:
|
|
403
|
+
summary = self._build_usage_summary(execution_id, context, run["end_time"])
|
|
404
|
+
client.create_run(project_name=self.project, **summary)
|
|
405
|
+
|
|
406
|
+
event_name = str(record.get("event_name", ""))
|
|
407
|
+
if self.emit_execution_summary and execution_id and event_name == "ExecutionFinished":
|
|
408
|
+
execution_summary = self._build_execution_summary(execution_id, context, timestamp)
|
|
409
|
+
client.create_run(project_name=self.project, **execution_summary)
|
|
410
|
+
|
|
411
|
+
def flush(self) -> None:
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
def shutdown(self) -> None:
|
|
415
|
+
return
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class MetricsSnapshot:
|
|
8
|
+
execution_time_ms: float = 0.0
|
|
9
|
+
tool_duration_ms: float = 0.0
|
|
10
|
+
average_duration_ms: float = 0.0
|
|
11
|
+
parallel_tasks: int = 0
|
|
12
|
+
sequential_tasks: int = 0
|
|
13
|
+
retry_count: int = 0
|
|
14
|
+
error_count: int = 0
|
|
15
|
+
warning_count: int = 0
|
|
16
|
+
llm_calls: int = 0
|
|
17
|
+
provider_calls: int = 0
|
|
18
|
+
mcp_calls: int = 0
|
|
19
|
+
tool_invocations: int = 0
|
|
20
|
+
tokens_input: int = 0
|
|
21
|
+
tokens_output: int = 0
|
|
22
|
+
total_tokens: int = 0
|
|
23
|
+
estimated_cost: float = 0.0
|
|
24
|
+
cache_hits: int = 0
|
|
25
|
+
cache_misses: int = 0
|
|
26
|
+
compression_ratio: float = 0.0
|
|
27
|
+
parallel_efficiency: float = 0.0
|
|
28
|
+
prompt_reduction: float = 0.0
|
|
29
|
+
context_reduction: float = 0.0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class MetricsAggregator:
|
|
34
|
+
durations: list[float] = field(default_factory=list)
|
|
35
|
+
snapshot: MetricsSnapshot = field(default_factory=MetricsSnapshot)
|
|
36
|
+
|
|
37
|
+
def add_duration(self, value_ms: float) -> None:
|
|
38
|
+
safe = max(0.0, float(value_ms))
|
|
39
|
+
self.durations.append(safe)
|
|
40
|
+
self.snapshot.tool_duration_ms += safe
|
|
41
|
+
if self.durations:
|
|
42
|
+
self.snapshot.average_duration_ms = self.snapshot.tool_duration_ms / len(self.durations)
|
|
43
|
+
|
|
44
|
+
def add_tokens(self, input_tokens: int, output_tokens: int) -> None:
|
|
45
|
+
self.snapshot.tokens_input += max(0, int(input_tokens))
|
|
46
|
+
self.snapshot.tokens_output += max(0, int(output_tokens))
|
|
47
|
+
self.snapshot.total_tokens = self.snapshot.tokens_input + self.snapshot.tokens_output
|
|
48
|
+
|
|
49
|
+
def add_cost(self, estimated_cost: float) -> None:
|
|
50
|
+
self.snapshot.estimated_cost += max(0.0, float(estimated_cost))
|
|
51
|
+
|
|
52
|
+
def bump(self, metric_name: str, value: int = 1) -> None:
|
|
53
|
+
if not hasattr(self.snapshot, metric_name):
|
|
54
|
+
return
|
|
55
|
+
current = getattr(self.snapshot, metric_name)
|
|
56
|
+
if isinstance(current, int):
|
|
57
|
+
setattr(self.snapshot, metric_name, max(0, current + int(value)))
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> dict[str, float | int]:
|
|
60
|
+
return self.snapshot.__dict__.copy()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEFAULT_WEIGHTS = {
|
|
7
|
+
"time": 0.18,
|
|
8
|
+
"cost": 0.16,
|
|
9
|
+
"tokens": 0.14,
|
|
10
|
+
"cache": 0.12,
|
|
11
|
+
"parallelism": 0.10,
|
|
12
|
+
"reuse": 0.10,
|
|
13
|
+
"calls": 0.10,
|
|
14
|
+
"compression": 0.10,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _clamp(value: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
19
|
+
return max(lo, min(hi, value))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _safe_ratio(numerator: float, denominator: float) -> float:
|
|
23
|
+
if denominator <= 0:
|
|
24
|
+
return 0.0
|
|
25
|
+
return numerator / denominator
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def compute_efficiency_score(metrics: dict[str, Any], weights: dict[str, float] | None = None) -> float:
|
|
29
|
+
w = dict(DEFAULT_WEIGHTS)
|
|
30
|
+
if weights:
|
|
31
|
+
w.update(weights)
|
|
32
|
+
|
|
33
|
+
duration = float(metrics.get("execution_time_ms", 0.0))
|
|
34
|
+
cost = float(metrics.get("estimated_cost", 0.0))
|
|
35
|
+
total_tokens = float(metrics.get("total_tokens", 0.0))
|
|
36
|
+
cache_hits = float(metrics.get("cache_hits", 0.0))
|
|
37
|
+
cache_misses = float(metrics.get("cache_misses", 0.0))
|
|
38
|
+
parallel_eff = float(metrics.get("parallel_efficiency", 0.0))
|
|
39
|
+
prompt_reduction = float(metrics.get("prompt_reduction", 0.0))
|
|
40
|
+
context_reduction = float(metrics.get("context_reduction", 0.0))
|
|
41
|
+
calls = float(
|
|
42
|
+
metrics.get("tool_invocations", 0)
|
|
43
|
+
+ metrics.get("provider_calls", 0)
|
|
44
|
+
+ metrics.get("llm_calls", 0)
|
|
45
|
+
+ metrics.get("mcp_calls", 0)
|
|
46
|
+
)
|
|
47
|
+
compression = float(metrics.get("compression_ratio", 0.0))
|
|
48
|
+
|
|
49
|
+
time_score = 1.0 - _clamp(duration / 120000.0)
|
|
50
|
+
cost_score = 1.0 - _clamp(cost / 2.0)
|
|
51
|
+
token_score = 1.0 - _clamp(total_tokens / 200000.0)
|
|
52
|
+
cache_score = _clamp(_safe_ratio(cache_hits, cache_hits + cache_misses))
|
|
53
|
+
parallel_score = _clamp(parallel_eff)
|
|
54
|
+
reuse_score = _clamp((prompt_reduction + context_reduction) / 2.0)
|
|
55
|
+
call_score = 1.0 - _clamp(calls / 200.0)
|
|
56
|
+
compression_score = _clamp(compression)
|
|
57
|
+
|
|
58
|
+
weighted = (
|
|
59
|
+
time_score * w["time"]
|
|
60
|
+
+ cost_score * w["cost"]
|
|
61
|
+
+ token_score * w["tokens"]
|
|
62
|
+
+ cache_score * w["cache"]
|
|
63
|
+
+ parallel_score * w["parallelism"]
|
|
64
|
+
+ reuse_score * w["reuse"]
|
|
65
|
+
+ call_score * w["calls"]
|
|
66
|
+
+ compression_score * w["compression"]
|
|
67
|
+
)
|
|
68
|
+
return round(_clamp(weighted) * 100.0, 2)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def utc_now() -> str:
|
|
9
|
+
return datetime.now(timezone.utc).isoformat()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SpanRecord:
|
|
14
|
+
name: str
|
|
15
|
+
kind: str
|
|
16
|
+
span_id: str
|
|
17
|
+
parent_span_id: str | None
|
|
18
|
+
trace_id: str
|
|
19
|
+
execution_id: str
|
|
20
|
+
started_at: str
|
|
21
|
+
ended_at: str | None = None
|
|
22
|
+
duration_ms: float | None = None
|
|
23
|
+
status: str = "UNSET"
|
|
24
|
+
attributes: dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
events: list[dict[str, Any]] = field(default_factory=list)
|
|
26
|
+
error: str | None = None
|
|
27
|
+
|
|
28
|
+
def add_event(self, name: str, attributes: dict[str, Any] | None = None) -> None:
|
|
29
|
+
self.events.append(
|
|
30
|
+
{
|
|
31
|
+
"name": name,
|
|
32
|
+
"timestamp": utc_now(),
|
|
33
|
+
"attributes": attributes or {},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def end(self, *, status: str = "OK", error: str | None = None) -> None:
|
|
38
|
+
self.ended_at = utc_now()
|
|
39
|
+
self.status = status
|
|
40
|
+
self.error = error
|
|
41
|
+
start_dt = datetime.fromisoformat(self.started_at)
|
|
42
|
+
end_dt = datetime.fromisoformat(self.ended_at)
|
|
43
|
+
self.duration_ms = max(0.0, (end_dt - start_dt).total_seconds() * 1000.0)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"timestamp": "2026-07-05T09:54:53.886533+00:00",
|
|
3
|
-
"source": "observability/logs/iteration-metrics.jsonl",
|
|
4
|
-
"filters": {
|
|
5
|
-
"source": "copilot-session",
|
|
6
|
-
"from": "",
|
|
7
|
-
"to": ""
|
|
8
|
-
},
|
|
9
|
-
"totals": {
|
|
10
|
-
"events": 36,
|
|
11
|
-
"input_tokens": 3893402,
|
|
12
|
-
"output_tokens": 126892,
|
|
13
|
-
"total_tokens": 4020294,
|
|
14
|
-
"copilot_credits": 816.9227
|
|
15
|
-
},
|
|
16
|
-
"breakdown": {
|
|
17
|
-
"model": {
|
|
18
|
-
"copilot/auto": {
|
|
19
|
-
"count": 36,
|
|
20
|
-
"input_tokens": 3893402,
|
|
21
|
-
"output_tokens": 126892,
|
|
22
|
-
"total_tokens": 4020294,
|
|
23
|
-
"copilot_credits": 816.9227
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"day": {
|
|
27
|
-
"2026-07-03": {
|
|
28
|
-
"count": 19,
|
|
29
|
-
"input_tokens": 2219137,
|
|
30
|
-
"output_tokens": 67846,
|
|
31
|
-
"total_tokens": 2286983,
|
|
32
|
-
"copilot_credits": 492.07088600000003
|
|
33
|
-
},
|
|
34
|
-
"2026-07-04": {
|
|
35
|
-
"count": 9,
|
|
36
|
-
"input_tokens": 876490,
|
|
37
|
-
"output_tokens": 21560,
|
|
38
|
-
"total_tokens": 898050,
|
|
39
|
-
"copilot_credits": 160.161499
|
|
40
|
-
},
|
|
41
|
-
"2026-07-05": {
|
|
42
|
-
"count": 8,
|
|
43
|
-
"input_tokens": 797775,
|
|
44
|
-
"output_tokens": 37486,
|
|
45
|
-
"total_tokens": 835261,
|
|
46
|
-
"copilot_credits": 164.690315
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"license": {
|
|
51
|
-
"plan": "business",
|
|
52
|
-
"seats": 1,
|
|
53
|
-
"included_credits_per_user_month": 19.0,
|
|
54
|
-
"included_credits_total_month": 19.0,
|
|
55
|
-
"official_plans_url": "https://github.com/features/copilot/plans?ref_cta=View+pricing+and+plans&ref_loc=hero&ref_page=%2Ffeatures_copilot_copilot_business&plans=business"
|
|
56
|
-
},
|
|
57
|
-
"budget": {
|
|
58
|
-
"used_copilot_credits": 816.9227,
|
|
59
|
-
"budget_utilization_rate": 42.995932,
|
|
60
|
-
"overage_credits": 797.9227,
|
|
61
|
-
"equivalent_seats_used": 42.995932,
|
|
62
|
-
"percent_of_one_seat": 4299.59
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Chat Token Usage Report
|
|
2
|
-
|
|
3
|
-
- timestamp: 2026-07-05T09:54:53.886533+00:00
|
|
4
|
-
- source: observability/logs/iteration-metrics.jsonl
|
|
5
|
-
- filter.source: copilot-session
|
|
6
|
-
- filter.from:
|
|
7
|
-
- filter.to:
|
|
8
|
-
- plan: business
|
|
9
|
-
- seats: 1
|
|
10
|
-
- official_plans_url: https://github.com/features/copilot/plans?ref_cta=View+pricing+and+plans&ref_loc=hero&ref_page=%2Ffeatures_copilot_copilot_business&plans=business
|
|
11
|
-
|
|
12
|
-
## Totals
|
|
13
|
-
|
|
14
|
-
- events: 36
|
|
15
|
-
- input_tokens: 3893402
|
|
16
|
-
- output_tokens: 126892
|
|
17
|
-
- total_tokens: 4020294
|
|
18
|
-
- copilot_credits: 816.9227
|
|
19
|
-
- included_credits_total_month: 19.0
|
|
20
|
-
- budget_utilization_rate: 42.995932
|
|
21
|
-
- overage_credits: 797.9227
|
|
22
|
-
- equivalent_seats_used: 42.995932
|
|
23
|
-
- percent_of_one_seat: 4299.59
|
|
24
|
-
|
|
25
|
-
## Breakdown by Model
|
|
26
|
-
|
|
27
|
-
- copilot/auto: count=36, input=3893402, output=126892, total=4020294, credits=816.9227
|
|
28
|
-
|
|
29
|
-
## Breakdown by Day
|
|
30
|
-
|
|
31
|
-
- 2026-07-03: count=19, input=2219137, output=67846, total=2286983, credits=492.070886
|
|
32
|
-
- 2026-07-04: count=9, input=876490, output=21560, total=898050, credits=160.161499
|
|
33
|
-
- 2026-07-05: count=8, input=797775, output=37486, total=835261, credits=164.690315
|