@peakinfer/cli 1.0.133
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/.claude/settings.local.json +8 -0
- package/.env.example +6 -0
- package/.github/workflows/peakinfer.yml +64 -0
- package/CHANGELOG.md +31 -0
- package/LICENSE +190 -0
- package/README.md +335 -0
- package/data/inferencemax.json +274 -0
- package/dist/agent-analyzer.d.ts +45 -0
- package/dist/agent-analyzer.d.ts.map +1 -0
- package/dist/agent-analyzer.js +374 -0
- package/dist/agent-analyzer.js.map +1 -0
- package/dist/agent.d.ts +76 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +965 -0
- package/dist/agent.js.map +1 -0
- package/dist/agents/correlation-analyzer.d.ts +34 -0
- package/dist/agents/correlation-analyzer.d.ts.map +1 -0
- package/dist/agents/correlation-analyzer.js +261 -0
- package/dist/agents/correlation-analyzer.js.map +1 -0
- package/dist/agents/index.d.ts +91 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +111 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/runtime-analyzer.d.ts +38 -0
- package/dist/agents/runtime-analyzer.d.ts.map +1 -0
- package/dist/agents/runtime-analyzer.js +244 -0
- package/dist/agents/runtime-analyzer.js.map +1 -0
- package/dist/analysis-types.d.ts +500 -0
- package/dist/analysis-types.d.ts.map +1 -0
- package/dist/analysis-types.js +11 -0
- package/dist/analysis-types.js.map +1 -0
- package/dist/analytics.d.ts +25 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +94 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analyzer.d.ts +48 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +547 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/artifacts.d.ts +44 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +165 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/benchmarks/index.d.ts +88 -0
- package/dist/benchmarks/index.d.ts.map +1 -0
- package/dist/benchmarks/index.js +205 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ci.d.ts +19 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +253 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +249 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/demo.d.ts +15 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +106 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/export.d.ts +14 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +209 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/history.d.ts +15 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +389 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/template.d.ts +14 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +341 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/validate-map.d.ts +12 -0
- package/dist/commands/validate-map.d.ts.map +1 -0
- package/dist/commands/validate-map.js +274 -0
- package/dist/commands/validate-map.js.map +1 -0
- package/dist/commands/whatif.d.ts +17 -0
- package/dist/commands/whatif.d.ts.map +1 -0
- package/dist/commands/whatif.js +206 -0
- package/dist/commands/whatif.js.map +1 -0
- package/dist/comparison.d.ts +38 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +223 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/connectors/helicone.d.ts +9 -0
- package/dist/connectors/helicone.d.ts.map +1 -0
- package/dist/connectors/helicone.js +106 -0
- package/dist/connectors/helicone.js.map +1 -0
- package/dist/connectors/index.d.ts +37 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +65 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/langsmith.d.ts +9 -0
- package/dist/connectors/langsmith.d.ts.map +1 -0
- package/dist/connectors/langsmith.js +122 -0
- package/dist/connectors/langsmith.js.map +1 -0
- package/dist/connectors/types.d.ts +83 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +98 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/cost-estimator.d.ts +46 -0
- package/dist/cost-estimator.d.ts.map +1 -0
- package/dist/cost-estimator.js +104 -0
- package/dist/cost-estimator.js.map +1 -0
- package/dist/costs.d.ts +57 -0
- package/dist/costs.d.ts.map +1 -0
- package/dist/costs.js +251 -0
- package/dist/costs.js.map +1 -0
- package/dist/counterfactuals.d.ts +29 -0
- package/dist/counterfactuals.d.ts.map +1 -0
- package/dist/counterfactuals.js +448 -0
- package/dist/counterfactuals.js.map +1 -0
- package/dist/enhancement-prompts.d.ts +41 -0
- package/dist/enhancement-prompts.d.ts.map +1 -0
- package/dist/enhancement-prompts.js +88 -0
- package/dist/enhancement-prompts.js.map +1 -0
- package/dist/envelopes.d.ts +20 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/envelopes.js +790 -0
- package/dist/envelopes.js.map +1 -0
- package/dist/format-normalizer.d.ts +71 -0
- package/dist/format-normalizer.d.ts.map +1 -0
- package/dist/format-normalizer.js +1331 -0
- package/dist/format-normalizer.js.map +1 -0
- package/dist/history.d.ts +79 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +313 -0
- package/dist/history.js.map +1 -0
- package/dist/html.d.ts +11 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +463 -0
- package/dist/html.js.map +1 -0
- package/dist/impact.d.ts +42 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +443 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +5 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +271 -0
- package/dist/insights.js.map +1 -0
- package/dist/joiner.d.ts +9 -0
- package/dist/joiner.d.ts.map +1 -0
- package/dist/joiner.js +247 -0
- package/dist/joiner.js.map +1 -0
- package/dist/orchestrator.d.ts +34 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +827 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pdf.d.ts +26 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +84 -0
- package/dist/pdf.js.map +1 -0
- package/dist/prediction.d.ts +33 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/prediction.js +316 -0
- package/dist/prediction.js.map +1 -0
- package/dist/prompts/loader.d.ts +38 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +60 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/renderer.d.ts +64 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +923 -0
- package/dist/renderer.js.map +1 -0
- package/dist/runid.d.ts +57 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +199 -0
- package/dist/runid.js.map +1 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +366 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +426 -0
- package/dist/scanner.js.map +1 -0
- package/dist/templates.d.ts +120 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +429 -0
- package/dist/templates.js.map +1 -0
- package/dist/tools/index.d.ts +153 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +177 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +3647 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +703 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/docs/demo-guide.md +423 -0
- package/docs/events-format.md +295 -0
- package/docs/inferencemap-spec.md +344 -0
- package/docs/migration-v2.md +293 -0
- package/fixtures/demo/precomputed.json +142 -0
- package/fixtures/demo-project/README.md +52 -0
- package/fixtures/demo-project/ai-service.ts +65 -0
- package/fixtures/demo-project/sample-events.jsonl +15 -0
- package/fixtures/demo-project/src/ai-service.ts +128 -0
- package/fixtures/demo-project/src/llm-client.ts +155 -0
- package/package.json +65 -0
- package/prompts/agent-analyzer.yaml +47 -0
- package/prompts/ci-gate.yaml +98 -0
- package/prompts/correlation-analyzer.yaml +178 -0
- package/prompts/format-normalizer.yaml +46 -0
- package/prompts/peak-performance.yaml +180 -0
- package/prompts/pr-comment.yaml +111 -0
- package/prompts/runtime-analyzer.yaml +189 -0
- package/prompts/unified-analyzer.yaml +241 -0
- package/schemas/inference-map.v0.1.json +215 -0
- package/scripts/benchmark.ts +394 -0
- package/scripts/demo-v1.5.sh +158 -0
- package/scripts/sync-from-site.sh +197 -0
- package/scripts/validate-sync.sh +178 -0
- package/src/agent-analyzer.ts +481 -0
- package/src/agent.ts +1232 -0
- package/src/agents/correlation-analyzer.ts +353 -0
- package/src/agents/index.ts +235 -0
- package/src/agents/runtime-analyzer.ts +343 -0
- package/src/analysis-types.ts +558 -0
- package/src/analytics.ts +100 -0
- package/src/analyzer.ts +692 -0
- package/src/artifacts.ts +218 -0
- package/src/benchmarks/index.ts +309 -0
- package/src/cli.ts +503 -0
- package/src/commands/ci.ts +336 -0
- package/src/commands/config.ts +288 -0
- package/src/commands/demo.ts +175 -0
- package/src/commands/export.ts +297 -0
- package/src/commands/history.ts +425 -0
- package/src/commands/template.ts +385 -0
- package/src/commands/validate-map.ts +324 -0
- package/src/commands/whatif.ts +272 -0
- package/src/comparison.ts +283 -0
- package/src/config.ts +188 -0
- package/src/connectors/helicone.ts +164 -0
- package/src/connectors/index.ts +93 -0
- package/src/connectors/langsmith.ts +179 -0
- package/src/connectors/types.ts +180 -0
- package/src/cost-estimator.ts +146 -0
- package/src/costs.ts +347 -0
- package/src/counterfactuals.ts +516 -0
- package/src/enhancement-prompts.ts +118 -0
- package/src/envelopes.ts +814 -0
- package/src/format-normalizer.ts +1486 -0
- package/src/history.ts +400 -0
- package/src/html.ts +512 -0
- package/src/impact.ts +522 -0
- package/src/index.ts +83 -0
- package/src/insights.ts +341 -0
- package/src/joiner.ts +289 -0
- package/src/orchestrator.ts +1015 -0
- package/src/pdf.ts +110 -0
- package/src/prediction.ts +392 -0
- package/src/prompts/loader.ts +88 -0
- package/src/renderer.ts +1045 -0
- package/src/runid.ts +261 -0
- package/src/runtime.ts +450 -0
- package/src/scanner.ts +508 -0
- package/src/templates.ts +561 -0
- package/src/tools/index.ts +214 -0
- package/src/types.ts +873 -0
- package/src/version.ts +24 -0
- package/templates/context-accumulation.yaml +23 -0
- package/templates/cost-concentration.yaml +20 -0
- package/templates/dead-code.yaml +20 -0
- package/templates/latency-explainer.yaml +23 -0
- package/templates/optimizations/ab-testing-framework.yaml +74 -0
- package/templates/optimizations/api-gateway-optimization.yaml +81 -0
- package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
- package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
- package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
- package/templates/optimizations/comprehensive-apm.yaml +76 -0
- package/templates/optimizations/context-window-optimization.yaml +91 -0
- package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
- package/templates/optimizations/distributed-training-optimization.yaml +77 -0
- package/templates/optimizations/document-analysis-edge.yaml +77 -0
- package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
- package/templates/optimizations/domain-specific-distillation.yaml +78 -0
- package/templates/optimizations/error-handling-optimization.yaml +76 -0
- package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
- package/templates/optimizations/long-context-memory-management.yaml +78 -0
- package/templates/optimizations/max-tokens-optimization.yaml +76 -0
- package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
- package/templates/optimizations/multi-framework-resilience.yaml +75 -0
- package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
- package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
- package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
- package/templates/optimizations/quality-monitoring.yaml +74 -0
- package/templates/optimizations/realtime-budget-controls.yaml +74 -0
- package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
- package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
- package/templates/optimizations/smart-model-routing.yaml +96 -0
- package/templates/optimizations/streaming-batch-selection.yaml +167 -0
- package/templates/optimizations/system-prompt-optimization.yaml +75 -0
- package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
- package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
- package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
- package/templates/overpowered-extraction.yaml +32 -0
- package/templates/overpowered-model.yaml +31 -0
- package/templates/prompt-bloat.yaml +24 -0
- package/templates/retry-explosion.yaml +28 -0
- package/templates/schema/insight.schema.json +113 -0
- package/templates/schema/optimization.schema.json +180 -0
- package/templates/streaming-drift.yaml +30 -0
- package/templates/throughput-gap.yaml +21 -0
- package/templates/token-underutilization.yaml +28 -0
- package/templates/untested-fallback.yaml +21 -0
- package/tests/accuracy/drift-detection.test.ts +184 -0
- package/tests/accuracy/false-positives.test.ts +166 -0
- package/tests/accuracy/templates.test.ts +205 -0
- package/tests/action/commands.test.ts +125 -0
- package/tests/action/comments.test.ts +347 -0
- package/tests/cli.test.ts +203 -0
- package/tests/comparison.test.ts +309 -0
- package/tests/correlation-analyzer.test.ts +534 -0
- package/tests/counterfactuals.test.ts +347 -0
- package/tests/fixtures/events/missing-id.jsonl +1 -0
- package/tests/fixtures/events/missing-input.jsonl +1 -0
- package/tests/fixtures/events/missing-latency.jsonl +1 -0
- package/tests/fixtures/events/missing-model.jsonl +1 -0
- package/tests/fixtures/events/missing-output.jsonl +1 -0
- package/tests/fixtures/events/missing-provider.jsonl +1 -0
- package/tests/fixtures/events/missing-ts.jsonl +1 -0
- package/tests/fixtures/events/valid.csv +3 -0
- package/tests/fixtures/events/valid.json +1 -0
- package/tests/fixtures/events/valid.jsonl +2 -0
- package/tests/fixtures/events/with-callsite.jsonl +1 -0
- package/tests/fixtures/events/with-intent.jsonl +1 -0
- package/tests/fixtures/events/wrong-type.jsonl +1 -0
- package/tests/fixtures/repos/empty/.gitkeep +0 -0
- package/tests/fixtures/repos/hybrid-router/router.py +35 -0
- package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
- package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
- package/tests/fixtures/repos/saas-openai/client.py +26 -0
- package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
- package/tests/github-action.test.ts +292 -0
- package/tests/insights.test.ts +878 -0
- package/tests/joiner.test.ts +168 -0
- package/tests/performance/action-latency.test.ts +132 -0
- package/tests/performance/benchmark.test.ts +189 -0
- package/tests/performance/cli-latency.test.ts +102 -0
- package/tests/pr-comment.test.ts +313 -0
- package/tests/prediction.test.ts +296 -0
- package/tests/runtime-analyzer.test.ts +375 -0
- package/tests/runtime.test.ts +205 -0
- package/tests/scanner.test.ts +122 -0
- package/tests/template-conformance.test.ts +526 -0
- package/tests/unit/cost-calculator.test.ts +303 -0
- package/tests/unit/credits.test.ts +180 -0
- package/tests/unit/inference-map.test.ts +276 -0
- package/tests/unit/schema.test.ts +300 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What-If Command (v1.6 - GAP 5)
|
|
3
|
+
*
|
|
4
|
+
* CLI command for counterfactual analysis:
|
|
5
|
+
* - Model swap: What if we used a different model?
|
|
6
|
+
* - Streaming: What if we enabled/disabled streaming?
|
|
7
|
+
* - Batching: What if we batched requests?
|
|
8
|
+
* - Provider: What if we used a different provider?
|
|
9
|
+
*
|
|
10
|
+
* Leverages existing src/counterfactuals.ts module.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { existsSync, readFileSync } from 'fs';
|
|
15
|
+
import { listRuns, loadRun } from '../history.js';
|
|
16
|
+
import {
|
|
17
|
+
generateCounterfactuals,
|
|
18
|
+
rankCounterfactuals,
|
|
19
|
+
formatCounterfactualSummary,
|
|
20
|
+
} from '../counterfactuals.js';
|
|
21
|
+
import type {
|
|
22
|
+
InferenceMap,
|
|
23
|
+
Counterfactual,
|
|
24
|
+
CounterfactualResult,
|
|
25
|
+
} from '../types.js';
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// TYPES
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
interface WhatIfOptions {
|
|
32
|
+
model?: string;
|
|
33
|
+
provider?: string;
|
|
34
|
+
streaming?: string;
|
|
35
|
+
batchSize?: string;
|
|
36
|
+
run?: string;
|
|
37
|
+
priority?: 'latency' | 'cost' | 'balanced';
|
|
38
|
+
output?: string;
|
|
39
|
+
limit?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// HELPERS
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load inference map from history or file
|
|
48
|
+
*/
|
|
49
|
+
function loadInferenceMap(runId?: string): InferenceMap | null {
|
|
50
|
+
if (runId) {
|
|
51
|
+
// Check if it's a file path
|
|
52
|
+
if (existsSync(runId) && runId.endsWith('.json')) {
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(runId, 'utf-8');
|
|
55
|
+
return JSON.parse(content);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Load from history
|
|
62
|
+
const run = loadRun(runId);
|
|
63
|
+
return run?.data?.inferenceMap || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load latest run
|
|
67
|
+
const runs = listRuns();
|
|
68
|
+
if (runs.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const latestRun = loadRun(runs[0].runId);
|
|
73
|
+
return latestRun?.data?.inferenceMap || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Filter counterfactuals by type/criteria
|
|
78
|
+
*/
|
|
79
|
+
function filterCounterfactuals(
|
|
80
|
+
result: CounterfactualResult,
|
|
81
|
+
options: WhatIfOptions
|
|
82
|
+
): Counterfactual[] {
|
|
83
|
+
let filtered = result.counterfactuals;
|
|
84
|
+
|
|
85
|
+
// Filter by type based on options
|
|
86
|
+
if (options.model) {
|
|
87
|
+
filtered = filtered.filter(c =>
|
|
88
|
+
c.type === 'model_swap' &&
|
|
89
|
+
c.proposedState.model?.toLowerCase().includes(options.model!.toLowerCase())
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.provider) {
|
|
94
|
+
filtered = filtered.filter(c =>
|
|
95
|
+
c.proposedState.provider?.toLowerCase().includes(options.provider!.toLowerCase())
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (options.streaming !== undefined) {
|
|
100
|
+
filtered = filtered.filter(c => c.type === 'streaming_enable');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (options.batchSize) {
|
|
104
|
+
filtered = filtered.filter(c => c.type === 'batch_optimization');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return filtered;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format counterfactual for display
|
|
112
|
+
*/
|
|
113
|
+
function formatCounterfactual(cf: Counterfactual, index: number): string {
|
|
114
|
+
const lines: string[] = [];
|
|
115
|
+
|
|
116
|
+
// Header with number and headline
|
|
117
|
+
lines.push(`\n${index + 1}. ${cf.headline}`);
|
|
118
|
+
lines.push('─'.repeat(50));
|
|
119
|
+
|
|
120
|
+
// Type badge
|
|
121
|
+
const typeBadges: Record<string, string> = {
|
|
122
|
+
model_swap: 'Model Swap',
|
|
123
|
+
batch_optimization: 'Batching',
|
|
124
|
+
cache_addition: 'Caching',
|
|
125
|
+
streaming_enable: 'Streaming',
|
|
126
|
+
provider_change: 'Provider',
|
|
127
|
+
};
|
|
128
|
+
lines.push(` Type: ${typeBadges[cf.type] || cf.type}`);
|
|
129
|
+
|
|
130
|
+
// Current vs Proposed
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push(' Current → Proposed:');
|
|
133
|
+
if (cf.currentState.model) {
|
|
134
|
+
lines.push(` Model: ${cf.currentState.model} → ${cf.proposedState.model || 'same'}`);
|
|
135
|
+
}
|
|
136
|
+
if (cf.currentState.provider || cf.proposedState.provider) {
|
|
137
|
+
lines.push(` Provider: ${cf.currentState.provider || '-'} → ${cf.proposedState.provider || 'same'}`);
|
|
138
|
+
}
|
|
139
|
+
if (cf.currentState.pattern || cf.proposedState.pattern) {
|
|
140
|
+
lines.push(` Pattern: ${cf.currentState.pattern || '-'} → ${cf.proposedState.pattern || 'same'}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Impact
|
|
144
|
+
lines.push('');
|
|
145
|
+
lines.push(' Impact:');
|
|
146
|
+
if (cf.impact.latencyDeltaPercent !== 0) {
|
|
147
|
+
const sign = cf.impact.latencyDeltaPercent < 0 ? '' : '+';
|
|
148
|
+
lines.push(` Latency: ${sign}${cf.impact.latencyDeltaPercent}% (${sign}${cf.impact.latencyDelta}ms)`);
|
|
149
|
+
}
|
|
150
|
+
if (cf.impact.costDeltaPercent !== 0) {
|
|
151
|
+
const sign = cf.impact.costDeltaPercent < 0 ? '' : '+';
|
|
152
|
+
lines.push(` Cost: ${sign}${cf.impact.costDeltaPercent}%`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Effort and confidence
|
|
156
|
+
lines.push(` Effort: ${cf.effort} Confidence: ${cf.confidence}`);
|
|
157
|
+
|
|
158
|
+
// Tradeoffs
|
|
159
|
+
if (cf.impact.tradeoffs.length > 0) {
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push(' Tradeoffs:');
|
|
162
|
+
for (const tradeoff of cf.impact.tradeoffs.slice(0, 3)) {
|
|
163
|
+
lines.push(` • ${tradeoff}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Affected points
|
|
168
|
+
if (cf.affectedPoints.length > 0) {
|
|
169
|
+
lines.push(`\n Affects ${cf.affectedPoints.length} inference point${cf.affectedPoints.length !== 1 ? 's' : ''}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return lines.join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Display counterfactual results
|
|
177
|
+
*/
|
|
178
|
+
function displayResults(counterfactuals: Counterfactual[], summary: CounterfactualResult['summary']): void {
|
|
179
|
+
if (counterfactuals.length === 0) {
|
|
180
|
+
console.log('\nNo optimization opportunities found matching your criteria.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log('\nWhat-If Analysis Results');
|
|
185
|
+
console.log('═'.repeat(50));
|
|
186
|
+
console.log(formatCounterfactualSummary({ counterfactuals, summary, generatedAt: new Date().toISOString() }));
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < counterfactuals.length; i++) {
|
|
189
|
+
console.log(formatCounterfactual(counterfactuals[i], i));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// COMMAND
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Register what-if command
|
|
201
|
+
*/
|
|
202
|
+
export function registerWhatIfCommand(program: Command): void {
|
|
203
|
+
program
|
|
204
|
+
.command('whatif')
|
|
205
|
+
.description('run counterfactual analysis ("what if" scenarios)')
|
|
206
|
+
.option('--model <model>', 'what if we used this model?')
|
|
207
|
+
.option('--provider <provider>', 'what if we used this provider?')
|
|
208
|
+
.option('--streaming <bool>', 'what if streaming was enabled/disabled?')
|
|
209
|
+
.option('--batch-size <n>', 'what if we batched requests?')
|
|
210
|
+
.option('--run <runId>', 'run ID or inference-map.json path (default: latest)')
|
|
211
|
+
.option('--priority <type>', 'ranking priority: latency, cost, balanced (default)', 'balanced')
|
|
212
|
+
.option('--output <format>', 'output format: text (default) or json', 'text')
|
|
213
|
+
.option('--limit <n>', 'limit number of results', parseInt)
|
|
214
|
+
.action(async (options: WhatIfOptions) => {
|
|
215
|
+
try {
|
|
216
|
+
// Load inference map
|
|
217
|
+
const inferenceMap = loadInferenceMap(options.run);
|
|
218
|
+
|
|
219
|
+
if (!inferenceMap) {
|
|
220
|
+
console.error('No analysis data found.');
|
|
221
|
+
console.log('\nRun "peakinfer analyze ." first, or specify:');
|
|
222
|
+
console.log(' --run <runId> Run ID from history');
|
|
223
|
+
console.log(' --run <file> Path to inference-map.json');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (inferenceMap.callsites.length === 0) {
|
|
228
|
+
console.error('No inference points found in analysis.');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Generate counterfactuals
|
|
233
|
+
const result = generateCounterfactuals(inferenceMap);
|
|
234
|
+
|
|
235
|
+
// Filter based on options
|
|
236
|
+
let counterfactuals = filterCounterfactuals(result, options);
|
|
237
|
+
|
|
238
|
+
// Rank by priority
|
|
239
|
+
if (options.priority && ['latency', 'cost', 'balanced'].includes(options.priority)) {
|
|
240
|
+
counterfactuals = rankCounterfactuals(
|
|
241
|
+
{ ...result, counterfactuals },
|
|
242
|
+
options.priority as 'latency' | 'cost' | 'balanced'
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Apply limit
|
|
247
|
+
if (options.limit && options.limit > 0) {
|
|
248
|
+
counterfactuals = counterfactuals.slice(0, options.limit);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Output
|
|
252
|
+
if (options.output === 'json') {
|
|
253
|
+
console.log(JSON.stringify({
|
|
254
|
+
counterfactuals,
|
|
255
|
+
summary: result.summary,
|
|
256
|
+
generatedAt: new Date().toISOString(),
|
|
257
|
+
}, null, 2));
|
|
258
|
+
} else {
|
|
259
|
+
displayResults(counterfactuals, result.summary);
|
|
260
|
+
|
|
261
|
+
// Show hints for more specific queries
|
|
262
|
+
if (!options.model && !options.provider && !options.streaming && !options.batchSize) {
|
|
263
|
+
console.log('Tip: Use --model, --provider, --streaming, or --batch-size to filter results.');
|
|
264
|
+
console.log('Example: peakinfer whatif --model gpt-4o-mini --priority cost');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Error:', error instanceof Error ? error.message : 'What-if analysis failed');
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Historical Comparison Module (v1.5)
|
|
3
|
+
*
|
|
4
|
+
* Compares current analysis with previous runs to surface:
|
|
5
|
+
* - New inference points (pre-deploy validation)
|
|
6
|
+
* - Removed inference points (cleanup validation)
|
|
7
|
+
* - Changed configurations (drift detection)
|
|
8
|
+
*
|
|
9
|
+
* Enables tracking changes over time for informed deployment decisions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
Callsite,
|
|
14
|
+
Insight,
|
|
15
|
+
ComparisonResult,
|
|
16
|
+
ChangedInferencePoint,
|
|
17
|
+
FieldChange,
|
|
18
|
+
HistoryManifest,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// TYPES
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export interface CompareOptions {
|
|
26
|
+
/** Specific run ID to compare against (default: latest) */
|
|
27
|
+
baseRunId?: string;
|
|
28
|
+
/** Include insight comparison (default: true) */
|
|
29
|
+
compareInsights?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AnalysisSnapshot {
|
|
33
|
+
runId: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
callsites: Callsite[];
|
|
36
|
+
insights?: Insight[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// HELPERS
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a unique key for an inference point based on file:line.
|
|
45
|
+
* Used for matching inference points across runs.
|
|
46
|
+
*/
|
|
47
|
+
function getCallsiteKey(callsite: Callsite): string {
|
|
48
|
+
return `${callsite.file}:${callsite.line}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compare two values for equality (deep comparison for objects).
|
|
53
|
+
*/
|
|
54
|
+
function valuesEqual(a: unknown, b: unknown): boolean {
|
|
55
|
+
if (a === b) return true;
|
|
56
|
+
if (a === null || b === null) return a === b;
|
|
57
|
+
if (typeof a !== typeof b) return false;
|
|
58
|
+
if (typeof a === 'object') {
|
|
59
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find changes between two inference points.
|
|
66
|
+
*/
|
|
67
|
+
function findCallsiteChanges(before: Callsite, after: Callsite): FieldChange[] {
|
|
68
|
+
const changes: FieldChange[] = [];
|
|
69
|
+
const fieldsToCompare: (keyof Callsite)[] = [
|
|
70
|
+
'provider', 'model', 'framework', 'runtime', 'confidence'
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const field of fieldsToCompare) {
|
|
74
|
+
if (!valuesEqual(before[field], after[field])) {
|
|
75
|
+
changes.push({
|
|
76
|
+
field,
|
|
77
|
+
before: before[field],
|
|
78
|
+
after: after[field],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Compare patterns object
|
|
84
|
+
const patternKeys = new Set([
|
|
85
|
+
...Object.keys(before.patterns || {}),
|
|
86
|
+
...Object.keys(after.patterns || {}),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
for (const pattern of patternKeys) {
|
|
90
|
+
const beforeVal = (before.patterns as Record<string, unknown>)?.[pattern];
|
|
91
|
+
const afterVal = (after.patterns as Record<string, unknown>)?.[pattern];
|
|
92
|
+
if (!valuesEqual(beforeVal, afterVal)) {
|
|
93
|
+
changes.push({
|
|
94
|
+
field: `patterns.${pattern}`,
|
|
95
|
+
before: beforeVal,
|
|
96
|
+
after: afterVal,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return changes;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Compare insights between runs to find new/resolved issues.
|
|
106
|
+
*/
|
|
107
|
+
function compareInsights(
|
|
108
|
+
baseInsights: Insight[],
|
|
109
|
+
currentInsights: Insight[]
|
|
110
|
+
): { newCritical: number; resolvedCritical: number; newWarnings: number; resolvedWarnings: number } {
|
|
111
|
+
const baseIds = new Set(baseInsights.map(i => i.id || `${i.headline}:${i.location}`));
|
|
112
|
+
const currentIds = new Set(currentInsights.map(i => i.id || `${i.headline}:${i.location}`));
|
|
113
|
+
|
|
114
|
+
// New insights (in current but not in base)
|
|
115
|
+
const newInsights = currentInsights.filter(i => {
|
|
116
|
+
const id = i.id || `${i.headline}:${i.location}`;
|
|
117
|
+
return !baseIds.has(id);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Resolved insights (in base but not in current)
|
|
121
|
+
const resolvedInsights = baseInsights.filter(i => {
|
|
122
|
+
const id = i.id || `${i.headline}:${i.location}`;
|
|
123
|
+
return !currentIds.has(id);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
newCritical: newInsights.filter(i => i.severity === 'critical').length,
|
|
128
|
+
resolvedCritical: resolvedInsights.filter(i => i.severity === 'critical').length,
|
|
129
|
+
newWarnings: newInsights.filter(i => i.severity === 'warning').length,
|
|
130
|
+
resolvedWarnings: resolvedInsights.filter(i => i.severity === 'warning').length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// PUBLIC API
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compare two analysis snapshots.
|
|
140
|
+
*/
|
|
141
|
+
export function compareSnapshots(
|
|
142
|
+
baseline: AnalysisSnapshot,
|
|
143
|
+
current: AnalysisSnapshot,
|
|
144
|
+
options: CompareOptions = {}
|
|
145
|
+
): ComparisonResult {
|
|
146
|
+
const { compareInsights: shouldCompareInsights = true } = options;
|
|
147
|
+
|
|
148
|
+
// Build lookup maps
|
|
149
|
+
const baseMap = new Map<string, Callsite>();
|
|
150
|
+
for (const cs of baseline.callsites) {
|
|
151
|
+
baseMap.set(getCallsiteKey(cs), cs);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const currentMap = new Map<string, Callsite>();
|
|
155
|
+
for (const cs of current.callsites) {
|
|
156
|
+
currentMap.set(getCallsiteKey(cs), cs);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Find added, removed, and changed inference points
|
|
160
|
+
const added: Callsite[] = [];
|
|
161
|
+
const removed: Callsite[] = [];
|
|
162
|
+
const changed: ChangedInferencePoint[] = [];
|
|
163
|
+
|
|
164
|
+
// Check current callsites against baseline
|
|
165
|
+
for (const [key, currentCs] of currentMap) {
|
|
166
|
+
const baseCs = baseMap.get(key);
|
|
167
|
+
if (!baseCs) {
|
|
168
|
+
// New inference point
|
|
169
|
+
added.push(currentCs);
|
|
170
|
+
} else {
|
|
171
|
+
// Check for changes
|
|
172
|
+
const changes = findCallsiteChanges(baseCs, currentCs);
|
|
173
|
+
if (changes.length > 0) {
|
|
174
|
+
changed.push({ point: currentCs, changes });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check for removed callsites
|
|
180
|
+
for (const [key, baseCs] of baseMap) {
|
|
181
|
+
if (!currentMap.has(key)) {
|
|
182
|
+
removed.push(baseCs);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build result
|
|
187
|
+
const result: ComparisonResult = {
|
|
188
|
+
baseRunId: baseline.runId,
|
|
189
|
+
baseTimestamp: baseline.timestamp,
|
|
190
|
+
currentRunId: current.runId,
|
|
191
|
+
currentTimestamp: current.timestamp,
|
|
192
|
+
added,
|
|
193
|
+
removed,
|
|
194
|
+
changed,
|
|
195
|
+
metrics: {
|
|
196
|
+
totalBefore: baseline.callsites.length,
|
|
197
|
+
totalAfter: current.callsites.length,
|
|
198
|
+
addedCount: added.length,
|
|
199
|
+
removedCount: removed.length,
|
|
200
|
+
changedCount: changed.length,
|
|
201
|
+
netChange: added.length - removed.length,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Add insight deltas if requested
|
|
206
|
+
if (shouldCompareInsights && baseline.insights && current.insights) {
|
|
207
|
+
result.insightDeltas = compareInsights(baseline.insights, current.insights);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Format a comparison result as a human-readable summary.
|
|
215
|
+
* Provides concise, actionable summary for pre-deploy review.
|
|
216
|
+
*/
|
|
217
|
+
export function formatComparisonSummary(comparison: ComparisonResult): string {
|
|
218
|
+
const lines: string[] = [];
|
|
219
|
+
|
|
220
|
+
// Header with delta
|
|
221
|
+
const delta = comparison.metrics.netChange;
|
|
222
|
+
const deltaStr = delta > 0 ? `+${delta}` : delta.toString();
|
|
223
|
+
lines.push(`Comparing with run from ${new Date(comparison.baseTimestamp).toLocaleDateString()}`);
|
|
224
|
+
lines.push(`Inference points: ${comparison.metrics.totalBefore} → ${comparison.metrics.totalAfter} (${deltaStr})`);
|
|
225
|
+
lines.push('');
|
|
226
|
+
|
|
227
|
+
// Changes summary
|
|
228
|
+
if (comparison.metrics.addedCount > 0) {
|
|
229
|
+
lines.push(` + ${comparison.metrics.addedCount} new inference point${comparison.metrics.addedCount !== 1 ? 's' : ''}`);
|
|
230
|
+
}
|
|
231
|
+
if (comparison.metrics.removedCount > 0) {
|
|
232
|
+
lines.push(` - ${comparison.metrics.removedCount} removed inference point${comparison.metrics.removedCount !== 1 ? 's' : ''}`);
|
|
233
|
+
}
|
|
234
|
+
if (comparison.metrics.changedCount > 0) {
|
|
235
|
+
lines.push(` ~ ${comparison.metrics.changedCount} modified inference point${comparison.metrics.changedCount !== 1 ? 's' : ''}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Insight deltas (if available)
|
|
239
|
+
if (comparison.insightDeltas) {
|
|
240
|
+
const { newCritical, resolvedCritical, newWarnings, resolvedWarnings } = comparison.insightDeltas;
|
|
241
|
+
if (newCritical > 0) {
|
|
242
|
+
lines.push(` ! ${newCritical} new critical issue${newCritical !== 1 ? 's' : ''}`);
|
|
243
|
+
}
|
|
244
|
+
if (resolvedCritical > 0) {
|
|
245
|
+
lines.push(` [OK] ${resolvedCritical} critical issue${resolvedCritical !== 1 ? 's' : ''} resolved`);
|
|
246
|
+
}
|
|
247
|
+
if (newWarnings > 0) {
|
|
248
|
+
lines.push(` ! ${newWarnings} new warning${newWarnings !== 1 ? 's' : ''}`);
|
|
249
|
+
}
|
|
250
|
+
if (resolvedWarnings > 0) {
|
|
251
|
+
lines.push(` [OK] ${resolvedWarnings} warning${resolvedWarnings !== 1 ? 's' : ''} resolved`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// No changes case
|
|
256
|
+
if (comparison.metrics.addedCount === 0 &&
|
|
257
|
+
comparison.metrics.removedCount === 0 &&
|
|
258
|
+
comparison.metrics.changedCount === 0) {
|
|
259
|
+
lines.push(' No changes detected');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if comparison has significant changes that warrant attention.
|
|
267
|
+
* Used to highlight important changes in the output.
|
|
268
|
+
*/
|
|
269
|
+
export function hasSignificantChanges(comparison: ComparisonResult): boolean {
|
|
270
|
+
// Any added/removed/changed inference points are significant
|
|
271
|
+
if (comparison.metrics.addedCount > 0 ||
|
|
272
|
+
comparison.metrics.removedCount > 0 ||
|
|
273
|
+
comparison.metrics.changedCount > 0) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// New critical insights are significant
|
|
278
|
+
if (comparison.insightDeltas?.newCritical && comparison.insightDeltas.newCritical > 0) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return false;
|
|
283
|
+
}
|