mcp-efficiency-engine 0.1.3 → 0.1.5
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/.github/workflows/release-npm.yml +16 -7
- package/ARCHITECTURE.md +15 -0
- package/FINAL_USAGE_GUIDE.md +24 -1
- package/README.md +6 -0
- package/bin/mcpee.js +38 -0
- package/package.json +2 -2
- package/scripts/ops/publish-langsmith-kpis.py +295 -0
- package/scripts/ops/trace-command.py +69 -0
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
- name: Setup Node.js
|
|
27
27
|
uses: actions/setup-node@v4
|
|
28
28
|
with:
|
|
29
|
-
node-version:
|
|
29
|
+
node-version: 22
|
|
30
30
|
registry-url: https://registry.npmjs.org
|
|
31
31
|
|
|
32
32
|
- name: Resolve publish version
|
|
@@ -94,14 +94,23 @@ jobs:
|
|
|
94
94
|
env:
|
|
95
95
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
96
96
|
|
|
97
|
-
- name:
|
|
97
|
+
- name: Record package.json sync requirement
|
|
98
98
|
if: steps.version.outputs.changed == 'true'
|
|
99
99
|
run: |
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
echo "package.json was bumped in CI to publish an available npm version (${{ steps.version.outputs.tag }})."
|
|
101
|
+
echo "Repository sync is skipped because main is protected and requires PR-based updates."
|
|
102
|
+
echo "Open a follow-up PR to align package.json with npm published version."
|
|
103
|
+
|
|
104
|
+
- name: Release summary
|
|
105
|
+
run: |
|
|
106
|
+
echo "## NPM Release" >> "$GITHUB_STEP_SUMMARY"
|
|
107
|
+
echo "- mode: ${{ steps.version.outputs.mode }}" >> "$GITHUB_STEP_SUMMARY"
|
|
108
|
+
echo "- published: ${{ steps.version.outputs.tag }}" >> "$GITHUB_STEP_SUMMARY"
|
|
109
|
+
if [ "${{ steps.version.outputs.changed }}" = "true" ]; then
|
|
110
|
+
echo "- package.json sync: skipped (protected branch, requires PR)" >> "$GITHUB_STEP_SUMMARY"
|
|
111
|
+
else
|
|
112
|
+
echo "- package.json sync: not required" >> "$GITHUB_STEP_SUMMARY"
|
|
113
|
+
fi
|
|
105
114
|
|
|
106
115
|
- name: Create GitHub release
|
|
107
116
|
uses: softprops/action-gh-release@v2
|
package/ARCHITECTURE.md
CHANGED
|
@@ -7,21 +7,27 @@ flowchart TD
|
|
|
7
7
|
O --> A[Agents Layer]
|
|
8
8
|
|
|
9
9
|
A --> DEV[backend]
|
|
10
|
+
A --> FE[frontend-agent]
|
|
10
11
|
A --> LEG[legacy]
|
|
11
12
|
A --> DBA[dba]
|
|
13
|
+
A --> UX[ux-ui]
|
|
12
14
|
A --> RAG[rag-local]
|
|
13
15
|
A --> AZ[rag-azure]
|
|
14
16
|
A --> IOT[iot]
|
|
15
17
|
A --> COM[community-manager]
|
|
18
|
+
A --> WIKI[wiki-agent]
|
|
16
19
|
A --> SNAP[snapshot]
|
|
17
20
|
|
|
18
21
|
DEV --> TS[Token Saver Policy]
|
|
22
|
+
FE --> TS
|
|
19
23
|
LEG --> TS
|
|
20
24
|
DBA --> TS
|
|
25
|
+
UX --> TS
|
|
21
26
|
RAG --> TS
|
|
22
27
|
AZ --> TS
|
|
23
28
|
IOT --> TS
|
|
24
29
|
COM --> TS
|
|
30
|
+
WIKI --> TS
|
|
25
31
|
SNAP --> TS
|
|
26
32
|
|
|
27
33
|
TS --> CG[CodeGraph]
|
|
@@ -55,3 +61,12 @@ Token Saver optimiza qué contexto se usa.
|
|
|
55
61
|
Routing decide qué motor se usa.
|
|
56
62
|
Observability mide si todo funciona.
|
|
57
63
|
```
|
|
64
|
+
|
|
65
|
+
## Routing base (resumen)
|
|
66
|
+
|
|
67
|
+
- `backend` y `frontend-agent` -> `CodeGraph`
|
|
68
|
+
- `legacy` -> `GitNexus`
|
|
69
|
+
- `dba`, `ux-ui`, `rag-local`, `community-manager` -> `Graphify`
|
|
70
|
+
- `rag-azure` -> `Azure RAG Builder`
|
|
71
|
+
- `wiki-agent` -> `CodeGraph` (fallback `Graphify`)
|
|
72
|
+
- `snapshot` -> `Repomix`
|
package/FINAL_USAGE_GUIDE.md
CHANGED
|
@@ -85,7 +85,30 @@ Esto significa:
|
|
|
85
85
|
- las fuentes se conservan cuando sean obligatorias,
|
|
86
86
|
- el sistema solo aumenta detalle si el usuario o el caso lo requiere.
|
|
87
87
|
|
|
88
|
-
## 7.
|
|
88
|
+
## 7. Instalación desde npm (rápido)
|
|
89
|
+
|
|
90
|
+
Instalación recomendada en proyecto host:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
npm install mcp-efficiency-engine
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Si el entorno bloquea scripts de npm (`allow-scripts`), ejecutar manualmente:
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
npx mcp-efficiency-engine install
|
|
100
|
+
npx mcp-efficiency-engine validate -PortableMode
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Validación de contenido publicado en npm:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
npm pack --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Debe listar, como mínimo, `bin/`, `scripts/`, `.github/`, `README.md`, `AGENTS.md`, `ARCHITECTURE.md` y `FINAL_USAGE_GUIDE.md`.
|
|
110
|
+
|
|
111
|
+
## 8. Telemetry Engine
|
|
89
112
|
|
|
90
113
|
El sistema incluye un engine de telemetría desacoplado con collector único.
|
|
91
114
|
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ Contrato global en `AGENTS.md`:
|
|
|
118
118
|
- `rag-azure` -> `Azure RAG Builder`
|
|
119
119
|
- `iot` -> `GitNexus/CodeGraph + Graphify`
|
|
120
120
|
- `community-manager` -> `Graphify`
|
|
121
|
+
- `wiki-agent` -> `CodeGraph` (fallback `Graphify`)
|
|
121
122
|
- `snapshot` -> `Repomix`
|
|
122
123
|
|
|
123
124
|
## Motores Y Herramientas
|
|
@@ -252,6 +253,11 @@ Comportamiento del hook:
|
|
|
252
253
|
- `scripts/learning/iteration-value-report.py`
|
|
253
254
|
- `scripts/ops/publish-langsmith-kpis.py` (best effort)
|
|
254
255
|
|
|
256
|
+
Notas operativas recientes:
|
|
257
|
+
|
|
258
|
+
- `mcpee` envuelve scripts PowerShell con `scripts/ops/trace-command.py` para emitir trazas por comando (`mcpee.*`) en el collector.
|
|
259
|
+
- `scripts/ops/publish-langsmith-kpis.py` agrega snapshots locales de flujos, coste y tokens antes de publicar KPI runs en LangSmith.
|
|
260
|
+
|
|
255
261
|
Artefactos/resultados:
|
|
256
262
|
|
|
257
263
|
- AutoDocs actualizado en `autodocs/generated` y `autodocs/site`
|
package/bin/mcpee.js
CHANGED
|
@@ -60,6 +60,7 @@ function resolveInvocationCandidates() {
|
|
|
60
60
|
function runPowerShellScript(scriptRelativePath, forwardedArgs) {
|
|
61
61
|
const executionRoot = resolveExecutionRoot(scriptRelativePath);
|
|
62
62
|
const scriptPath = path.join(executionRoot, scriptRelativePath);
|
|
63
|
+
const tracerScriptPath = path.join(repoRoot, "scripts", "ops", "trace-command.py");
|
|
63
64
|
if (!fs.existsSync(scriptPath)) {
|
|
64
65
|
process.stderr.write(`Script no encontrado: ${scriptRelativePath}\n`);
|
|
65
66
|
return 1;
|
|
@@ -67,6 +68,43 @@ function runPowerShellScript(scriptRelativePath, forwardedArgs) {
|
|
|
67
68
|
|
|
68
69
|
const shellCandidates = resolveInvocationCandidates();
|
|
69
70
|
for (const shellCommand of shellCandidates) {
|
|
71
|
+
if (fs.existsSync(tracerScriptPath)) {
|
|
72
|
+
const operation = `mcpee.${path.basename(scriptRelativePath, path.extname(scriptRelativePath))}`;
|
|
73
|
+
const tracedResult = spawnSync(
|
|
74
|
+
"py",
|
|
75
|
+
[
|
|
76
|
+
"-3",
|
|
77
|
+
tracerScriptPath,
|
|
78
|
+
"--operation",
|
|
79
|
+
operation,
|
|
80
|
+
"--session-id",
|
|
81
|
+
"mcpee-cli",
|
|
82
|
+
"--cwd",
|
|
83
|
+
executionRoot,
|
|
84
|
+
"--",
|
|
85
|
+
shellCommand,
|
|
86
|
+
"-NoProfile",
|
|
87
|
+
"-ExecutionPolicy",
|
|
88
|
+
"Bypass",
|
|
89
|
+
"-File",
|
|
90
|
+
scriptPath,
|
|
91
|
+
...forwardedArgs,
|
|
92
|
+
],
|
|
93
|
+
{
|
|
94
|
+
cwd: repoRoot,
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (!tracedResult.error || tracedResult.error.code !== "ENOENT") {
|
|
100
|
+
if (tracedResult.error) {
|
|
101
|
+
process.stderr.write(`${tracedResult.error.message}\n`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
return tracedResult.status ?? 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
70
108
|
const result = spawnSync(
|
|
71
109
|
shellCommand,
|
|
72
110
|
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath, ...forwardedArgs],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-efficiency-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Motor de orquestacion para agentes MCP con routing por dominio y bootstrap portable.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"hi": "node ./bin/mcpee.js hi",
|
|
52
52
|
"bye": "node ./bin/mcpee.js bye",
|
|
53
53
|
"intake": "node ./bin/mcpee.js intake",
|
|
54
|
-
"langsmith:kpis": "py -3 ./scripts/ops/publish-langsmith-kpis.py",
|
|
54
|
+
"langsmith:kpis": "py -3 ./scripts/ops/trace-command.py --operation mcpee.langsmith-kpis --session-id mcpee-cli --cwd . -- py -3 ./scripts/ops/publish-langsmith-kpis.py",
|
|
55
55
|
"pack:check": "npm pack --dry-run"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
import uuid
|
|
7
|
+
from collections import Counter
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
@@ -25,6 +26,28 @@ def _read_json(path: Path) -> dict[str, Any]:
|
|
|
25
26
|
return payload if isinstance(payload, dict) else {}
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
30
|
+
if not path.exists():
|
|
31
|
+
return []
|
|
32
|
+
rows: list[dict[str, Any]] = []
|
|
33
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
34
|
+
raw = line.strip()
|
|
35
|
+
if not raw:
|
|
36
|
+
continue
|
|
37
|
+
try:
|
|
38
|
+
obj = json.loads(raw)
|
|
39
|
+
except Exception:
|
|
40
|
+
continue
|
|
41
|
+
if isinstance(obj, dict):
|
|
42
|
+
rows.append(obj)
|
|
43
|
+
return rows
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
47
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
|
|
28
51
|
def _iso_now() -> datetime:
|
|
29
52
|
return datetime.now(timezone.utc)
|
|
30
53
|
|
|
@@ -48,6 +71,161 @@ def _flatten_kpis(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
48
71
|
return flat
|
|
49
72
|
|
|
50
73
|
|
|
74
|
+
def _build_telemetry_snapshot(repo_root: Path) -> dict[str, Any]:
|
|
75
|
+
traces_path = repo_root / ".telemetry" / "traces.jsonl"
|
|
76
|
+
metrics_path = repo_root / ".telemetry" / "metrics.jsonl"
|
|
77
|
+
traces = _read_jsonl(traces_path)
|
|
78
|
+
metrics = _read_jsonl(metrics_path)
|
|
79
|
+
|
|
80
|
+
execution_ids: set[str] = set()
|
|
81
|
+
operations = Counter()
|
|
82
|
+
sessions = Counter()
|
|
83
|
+
engines = Counter()
|
|
84
|
+
agents = Counter()
|
|
85
|
+
warnings = 0
|
|
86
|
+
errors = 0
|
|
87
|
+
fallback_count = 0
|
|
88
|
+
grounded_count = 0
|
|
89
|
+
routing_count = 0
|
|
90
|
+
durations: list[float] = []
|
|
91
|
+
exporter_failures = Counter()
|
|
92
|
+
|
|
93
|
+
for row in traces:
|
|
94
|
+
context = row.get("context", {}) if isinstance(row.get("context"), dict) else {}
|
|
95
|
+
payload = row.get("payload", {}) if isinstance(row.get("payload"), dict) else {}
|
|
96
|
+
event_name = str(row.get("event_name", ""))
|
|
97
|
+
level = str(row.get("level", "INFO")).upper()
|
|
98
|
+
|
|
99
|
+
execution_id = str(context.get("execution_id", "")).strip()
|
|
100
|
+
if execution_id:
|
|
101
|
+
execution_ids.add(execution_id)
|
|
102
|
+
|
|
103
|
+
session_id = str(context.get("session_id", "")).strip()
|
|
104
|
+
if session_id:
|
|
105
|
+
sessions[session_id] += 1
|
|
106
|
+
|
|
107
|
+
if level == "WARNING":
|
|
108
|
+
warnings += 1
|
|
109
|
+
if level == "ERROR":
|
|
110
|
+
errors += 1
|
|
111
|
+
|
|
112
|
+
if event_name == "ExecutionStarted":
|
|
113
|
+
op = str(payload.get("operation", "")).strip() or "unknown"
|
|
114
|
+
operations[op] += 1
|
|
115
|
+
|
|
116
|
+
if event_name == "RoutingResolved":
|
|
117
|
+
routing_count += 1
|
|
118
|
+
engine = str(payload.get("engine", "")).strip() or "unknown"
|
|
119
|
+
agent = str(payload.get("agent", "")).strip() or "unknown"
|
|
120
|
+
engines[engine] += 1
|
|
121
|
+
agents[agent] += 1
|
|
122
|
+
if bool(payload.get("fallback", False)):
|
|
123
|
+
fallback_count += 1
|
|
124
|
+
if bool(payload.get("grounded", False)):
|
|
125
|
+
grounded_count += 1
|
|
126
|
+
|
|
127
|
+
if event_name == "SpanFinished":
|
|
128
|
+
try:
|
|
129
|
+
durations.append(float(payload.get("duration_ms") or 0.0))
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
if event_name == "WarningGenerated" and str(payload.get("warning", "")) == "exporter_failed":
|
|
134
|
+
exporter = str(payload.get("exporter", "unknown")).strip() or "unknown"
|
|
135
|
+
exporter_failures[exporter] += 1
|
|
136
|
+
|
|
137
|
+
metric_totals = {
|
|
138
|
+
"tokens_input": 0.0,
|
|
139
|
+
"tokens_output": 0.0,
|
|
140
|
+
"total_tokens": 0.0,
|
|
141
|
+
"estimated_cost": 0.0,
|
|
142
|
+
"efficiency_score_samples": 0,
|
|
143
|
+
"efficiency_score_avg": 0.0,
|
|
144
|
+
}
|
|
145
|
+
efficiency_sum = 0.0
|
|
146
|
+
for row in metrics:
|
|
147
|
+
payload = row.get("payload", {}) if isinstance(row.get("payload"), dict) else {}
|
|
148
|
+
if str(row.get("event_name", "")) != "MetricCalculated":
|
|
149
|
+
continue
|
|
150
|
+
name = str(payload.get("name", "")).strip()
|
|
151
|
+
try:
|
|
152
|
+
value = float(payload.get("value") or 0.0)
|
|
153
|
+
except Exception:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
if name in {"tokens_input", "tokens_output", "total_tokens", "estimated_cost"}:
|
|
157
|
+
metric_totals[name] += value
|
|
158
|
+
elif name == "efficiency_score":
|
|
159
|
+
efficiency_sum += value
|
|
160
|
+
metric_totals["efficiency_score_samples"] += 1
|
|
161
|
+
|
|
162
|
+
samples = int(metric_totals["efficiency_score_samples"])
|
|
163
|
+
if samples > 0:
|
|
164
|
+
metric_totals["efficiency_score_avg"] = round(efficiency_sum / samples, 4)
|
|
165
|
+
|
|
166
|
+
avg_duration = round(sum(durations) / len(durations), 4) if durations else 0.0
|
|
167
|
+
p95_duration = 0.0
|
|
168
|
+
if durations:
|
|
169
|
+
sorted_d = sorted(durations)
|
|
170
|
+
idx = max(0, min(len(sorted_d) - 1, int(round(0.95 * (len(sorted_d) - 1)))))
|
|
171
|
+
p95_duration = round(sorted_d[idx], 4)
|
|
172
|
+
|
|
173
|
+
fallback_rate = round((fallback_count / routing_count), 4) if routing_count else 0.0
|
|
174
|
+
grounded_rate = round((grounded_count / routing_count), 4) if routing_count else 0.0
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"timestamp": _iso_now().isoformat(),
|
|
178
|
+
"source": {
|
|
179
|
+
"traces": str(traces_path),
|
|
180
|
+
"metrics": str(metrics_path),
|
|
181
|
+
},
|
|
182
|
+
"totals": {
|
|
183
|
+
"trace_records": len(traces),
|
|
184
|
+
"metric_records": len(metrics),
|
|
185
|
+
"executions": len(execution_ids),
|
|
186
|
+
"warnings": warnings,
|
|
187
|
+
"errors": errors,
|
|
188
|
+
},
|
|
189
|
+
"kpis": {
|
|
190
|
+
"routing_fallback_rate": fallback_rate,
|
|
191
|
+
"routing_grounded_rate": grounded_rate,
|
|
192
|
+
"avg_span_duration_ms": avg_duration,
|
|
193
|
+
"p95_span_duration_ms": p95_duration,
|
|
194
|
+
"tokens_input": int(metric_totals["tokens_input"]),
|
|
195
|
+
"tokens_output": int(metric_totals["tokens_output"]),
|
|
196
|
+
"total_tokens": int(metric_totals["total_tokens"]),
|
|
197
|
+
"total_cost_usd": round(float(metric_totals["estimated_cost"]), 6),
|
|
198
|
+
"efficiency_score_avg": metric_totals["efficiency_score_avg"],
|
|
199
|
+
},
|
|
200
|
+
"breakdown": {
|
|
201
|
+
"operation": dict(operations),
|
|
202
|
+
"session": dict(sessions),
|
|
203
|
+
"engine": dict(engines),
|
|
204
|
+
"agent": dict(agents),
|
|
205
|
+
"exporter_failures": dict(exporter_failures),
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _build_chat_usage_snapshot(repo_root: Path) -> dict[str, Any]:
|
|
211
|
+
chat_path = repo_root / "observability" / "evals" / "chat-token-usage-report.json"
|
|
212
|
+
payload = _read_json(chat_path)
|
|
213
|
+
totals = payload.get("totals", {}) if isinstance(payload.get("totals", {}), dict) else {}
|
|
214
|
+
budget = payload.get("budget", {}) if isinstance(payload.get("budget", {}), dict) else {}
|
|
215
|
+
return {
|
|
216
|
+
"timestamp": payload.get("timestamp"),
|
|
217
|
+
"source": str(chat_path),
|
|
218
|
+
"kpis": {
|
|
219
|
+
"input_tokens": int(totals.get("input_tokens", 0) or 0),
|
|
220
|
+
"output_tokens": int(totals.get("output_tokens", 0) or 0),
|
|
221
|
+
"total_tokens": int(totals.get("total_tokens", 0) or 0),
|
|
222
|
+
"copilot_credits": float(totals.get("copilot_credits", 0.0) or 0.0),
|
|
223
|
+
"budget_utilization_rate": float(budget.get("budget_utilization_rate", 0.0) or 0.0),
|
|
224
|
+
},
|
|
225
|
+
"raw": payload,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
51
229
|
def _publish_run(
|
|
52
230
|
client: Any,
|
|
53
231
|
*,
|
|
@@ -104,12 +282,18 @@ def _publish_run(
|
|
|
104
282
|
|
|
105
283
|
|
|
106
284
|
def _publish_feedback_metrics(client: Any, *, run_id: str, metrics: dict[str, Any], source: str) -> None:
|
|
285
|
+
min_score = -99999.9999
|
|
286
|
+
max_score = 99999.9999
|
|
107
287
|
for key, raw_value in metrics.items():
|
|
108
288
|
try:
|
|
109
289
|
value = float(raw_value)
|
|
110
290
|
except Exception:
|
|
111
291
|
continue
|
|
112
292
|
|
|
293
|
+
if value < min_score or value > max_score:
|
|
294
|
+
# LangSmith feedback score has bounded range.
|
|
295
|
+
continue
|
|
296
|
+
|
|
113
297
|
try:
|
|
114
298
|
client.create_feedback(
|
|
115
299
|
run_id=run_id,
|
|
@@ -152,9 +336,38 @@ def main() -> int:
|
|
|
152
336
|
|
|
153
337
|
learning_path = repo_root / "observability" / "evals" / "learning-loop-report.json"
|
|
154
338
|
value_path = repo_root / "observability" / "evals" / "iteration-value-report.json"
|
|
339
|
+
telemetry_snapshot_path = repo_root / "observability" / "evals" / "telemetry-flow-cost-token-report.json"
|
|
340
|
+
chat_snapshot_path = repo_root / "observability" / "evals" / "chat-token-usage-report.json"
|
|
155
341
|
|
|
156
342
|
learning = _read_json(learning_path)
|
|
157
343
|
value = _read_json(value_path)
|
|
344
|
+
telemetry_snapshot = _build_telemetry_snapshot(repo_root)
|
|
345
|
+
_write_json(telemetry_snapshot_path, telemetry_snapshot)
|
|
346
|
+
chat_snapshot = _build_chat_usage_snapshot(repo_root)
|
|
347
|
+
|
|
348
|
+
project_traceability_path = (
|
|
349
|
+
repo_root
|
|
350
|
+
/ "projects"
|
|
351
|
+
/ "techriders"
|
|
352
|
+
/ "analysis_mcpee"
|
|
353
|
+
/ f"telemetry-recovery-{datetime.now(timezone.utc).strftime('%Y-%m-%d')}.json"
|
|
354
|
+
)
|
|
355
|
+
unified_snapshot = {
|
|
356
|
+
"timestamp": _iso_now().isoformat(),
|
|
357
|
+
"host_project": host_project,
|
|
358
|
+
"telemetry_scope": telemetry_scope,
|
|
359
|
+
"sources": {
|
|
360
|
+
"learning": str(learning_path),
|
|
361
|
+
"iteration_value": str(value_path),
|
|
362
|
+
"telemetry_snapshot": str(telemetry_snapshot_path),
|
|
363
|
+
"chat_usage": str(chat_snapshot_path),
|
|
364
|
+
},
|
|
365
|
+
"learning": learning,
|
|
366
|
+
"iteration_value": value,
|
|
367
|
+
"telemetry": telemetry_snapshot,
|
|
368
|
+
"chat_usage": chat_snapshot,
|
|
369
|
+
}
|
|
370
|
+
_write_json(project_traceability_path, unified_snapshot)
|
|
158
371
|
|
|
159
372
|
published = 0
|
|
160
373
|
|
|
@@ -288,10 +501,92 @@ def main() -> int:
|
|
|
288
501
|
_publish_feedback_metrics(client, run_id=run_id, metrics=alignment_feedback, source="alignment")
|
|
289
502
|
published += 1
|
|
290
503
|
|
|
504
|
+
telemetry_kpis = telemetry_snapshot.get("kpis", {}) if isinstance(telemetry_snapshot.get("kpis", {}), dict) else {}
|
|
505
|
+
if telemetry_kpis:
|
|
506
|
+
telemetry_flat_kpis = _flatten_kpis(telemetry_kpis)
|
|
507
|
+
run_id = _publish_run(
|
|
508
|
+
client,
|
|
509
|
+
project=cfg.langsmith.project,
|
|
510
|
+
name="KPI::TelemetryFlowCostToken",
|
|
511
|
+
inputs={
|
|
512
|
+
"source_file": str(telemetry_snapshot_path),
|
|
513
|
+
"timestamp": telemetry_snapshot.get("timestamp"),
|
|
514
|
+
},
|
|
515
|
+
outputs={
|
|
516
|
+
"kpis": telemetry_kpis,
|
|
517
|
+
**telemetry_flat_kpis,
|
|
518
|
+
"totals": telemetry_snapshot.get("totals", {}),
|
|
519
|
+
"breakdown": telemetry_snapshot.get("breakdown", {}),
|
|
520
|
+
},
|
|
521
|
+
tags=[
|
|
522
|
+
"mcpee",
|
|
523
|
+
"kpi",
|
|
524
|
+
"telemetry",
|
|
525
|
+
"cost-token-flow",
|
|
526
|
+
"dashboard",
|
|
527
|
+
f"scope:{telemetry_scope}",
|
|
528
|
+
f"host:{host_slug}",
|
|
529
|
+
],
|
|
530
|
+
metadata={
|
|
531
|
+
"kpi_source": "telemetry-flow-cost-token",
|
|
532
|
+
"kpi_kind": "snapshot",
|
|
533
|
+
"host_project": host_project,
|
|
534
|
+
"host_project_slug": host_slug,
|
|
535
|
+
"telemetry_scope": telemetry_scope,
|
|
536
|
+
**telemetry_flat_kpis,
|
|
537
|
+
},
|
|
538
|
+
total_cost=float(telemetry_kpis.get("total_cost_usd", 0.0) or 0.0),
|
|
539
|
+
total_tokens=int(telemetry_kpis.get("total_tokens", 0) or 0),
|
|
540
|
+
run_type="llm",
|
|
541
|
+
)
|
|
542
|
+
_publish_feedback_metrics(client, run_id=run_id, metrics=telemetry_flat_kpis, source="telemetry-flow-cost-token")
|
|
543
|
+
published += 1
|
|
544
|
+
|
|
545
|
+
chat_kpis = chat_snapshot.get("kpis", {}) if isinstance(chat_snapshot.get("kpis", {}), dict) else {}
|
|
546
|
+
if chat_kpis:
|
|
547
|
+
chat_flat_kpis = _flatten_kpis(chat_kpis)
|
|
548
|
+
run_id = _publish_run(
|
|
549
|
+
client,
|
|
550
|
+
project=cfg.langsmith.project,
|
|
551
|
+
name="KPI::ChatTokenUsage",
|
|
552
|
+
inputs={
|
|
553
|
+
"source_file": str(chat_snapshot_path),
|
|
554
|
+
"timestamp": chat_snapshot.get("timestamp"),
|
|
555
|
+
},
|
|
556
|
+
outputs={
|
|
557
|
+
"kpis": chat_kpis,
|
|
558
|
+
**chat_flat_kpis,
|
|
559
|
+
"budget": (chat_snapshot.get("raw", {}) or {}).get("budget", {}),
|
|
560
|
+
},
|
|
561
|
+
tags=[
|
|
562
|
+
"mcpee",
|
|
563
|
+
"kpi",
|
|
564
|
+
"chat-usage",
|
|
565
|
+
"cost-token-flow",
|
|
566
|
+
"dashboard",
|
|
567
|
+
f"scope:{telemetry_scope}",
|
|
568
|
+
f"host:{host_slug}",
|
|
569
|
+
],
|
|
570
|
+
metadata={
|
|
571
|
+
"kpi_source": "chat-token-usage",
|
|
572
|
+
"kpi_kind": "snapshot",
|
|
573
|
+
"host_project": host_project,
|
|
574
|
+
"host_project_slug": host_slug,
|
|
575
|
+
"telemetry_scope": telemetry_scope,
|
|
576
|
+
**chat_flat_kpis,
|
|
577
|
+
},
|
|
578
|
+
total_tokens=int(chat_kpis.get("total_tokens", 0) or 0),
|
|
579
|
+
run_type="llm",
|
|
580
|
+
)
|
|
581
|
+
_publish_feedback_metrics(client, run_id=run_id, metrics=chat_flat_kpis, source="chat-token-usage")
|
|
582
|
+
published += 1
|
|
583
|
+
|
|
291
584
|
print(
|
|
292
585
|
f"Published KPI runs: {published} to project '{cfg.langsmith.project}' "
|
|
293
586
|
f"(host_project='{host_project}', scope='{telemetry_scope}')."
|
|
294
587
|
)
|
|
588
|
+
print(f"Local telemetry snapshot: {telemetry_snapshot_path}")
|
|
589
|
+
print(f"Project traceability snapshot: {project_traceability_path}")
|
|
295
590
|
if published == 0:
|
|
296
591
|
print("No local KPI report JSON files were found in observability/evals.")
|
|
297
592
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _bootstrap_repo_root() -> Path:
|
|
10
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
11
|
+
if str(repo_root) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(repo_root))
|
|
13
|
+
return repo_root
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _parse_args() -> argparse.Namespace:
|
|
17
|
+
parser = argparse.ArgumentParser(description="Run a command wrapped with telemetry execution/span.")
|
|
18
|
+
parser.add_argument("--operation", required=True, help="Telemetry operation name.")
|
|
19
|
+
parser.add_argument("--session-id", default="mcpee-cli", help="Telemetry session identifier.")
|
|
20
|
+
parser.add_argument("--cwd", default=".", help="Working directory for the command.")
|
|
21
|
+
parser.add_argument("command", nargs=argparse.REMAINDER, help="Command to execute after --.")
|
|
22
|
+
return parser.parse_args()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_command(raw: list[str]) -> list[str]:
|
|
26
|
+
cmd = list(raw)
|
|
27
|
+
if cmd and cmd[0] == "--":
|
|
28
|
+
cmd = cmd[1:]
|
|
29
|
+
return cmd
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> int:
|
|
33
|
+
args = _parse_args()
|
|
34
|
+
command = _normalize_command(args.command)
|
|
35
|
+
if not command:
|
|
36
|
+
print("trace-command.py: missing command to execute", file=sys.stderr)
|
|
37
|
+
return 2
|
|
38
|
+
|
|
39
|
+
repo_root = _bootstrap_repo_root()
|
|
40
|
+
from telemetry import build_telemetry_collector
|
|
41
|
+
|
|
42
|
+
collector = build_telemetry_collector(repo_root)
|
|
43
|
+
cwd = Path(args.cwd).resolve()
|
|
44
|
+
exit_code = 0
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with collector.start_execution(operation=args.operation, session_id=args.session_id, model="mcpee-cli"):
|
|
48
|
+
with collector.start_span(
|
|
49
|
+
name="subprocess",
|
|
50
|
+
kind="TOOL",
|
|
51
|
+
attributes={
|
|
52
|
+
"cwd": str(cwd),
|
|
53
|
+
"command": command[0],
|
|
54
|
+
"args_count": len(command) - 1,
|
|
55
|
+
},
|
|
56
|
+
):
|
|
57
|
+
completed = subprocess.run(command, cwd=str(cwd), check=False)
|
|
58
|
+
exit_code = int(completed.returncode)
|
|
59
|
+
collector.record_metric("subprocess_exit_code", float(exit_code), unit="code")
|
|
60
|
+
if exit_code != 0:
|
|
61
|
+
raise RuntimeError(f"command failed with exit code {exit_code}")
|
|
62
|
+
except RuntimeError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
return exit_code
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|