pi-smart-router 0.1.0
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/.pi/extensions/smart-router/command-formatters.ts +167 -0
- package/.pi/extensions/smart-router/commands.ts +242 -0
- package/.pi/extensions/smart-router/dataset-export.ts +142 -0
- package/.pi/extensions/smart-router/delegate-stream.ts +105 -0
- package/.pi/extensions/smart-router/delegation-runtime.ts +355 -0
- package/.pi/extensions/smart-router/extension-setup.ts +115 -0
- package/.pi/extensions/smart-router/fleet-bootstrap.ts +244 -0
- package/.pi/extensions/smart-router/index.ts +115 -0
- package/.pi/extensions/smart-router/package.json +3 -0
- package/.pi/extensions/smart-router/pi-model-scope.ts +55 -0
- package/.pi/extensions/smart-router/pricing-lifecycle.ts +43 -0
- package/.pi/extensions/smart-router/route-and-delegate.ts +562 -0
- package/.pi/extensions/smart-router/routing-context.ts +171 -0
- package/.pi/extensions/smart-router/routing-outcomes.ts +44 -0
- package/.pi/extensions/smart-router/session-lifecycle.ts +140 -0
- package/.pi/extensions/smart-router/stream-delegation.ts +75 -0
- package/.pi/extensions/smart-router/types.ts +90 -0
- package/.pi/extensions/smart-router/utils.ts +58 -0
- package/README.md +593 -0
- package/bin/pi-smart-router.mjs +79 -0
- package/config/.gitkeep +0 -0
- package/config/hydra-projection-weights.json.example +1169 -0
- package/config/models.yaml.example +58 -0
- package/config/p-success-weights.json.example +19 -0
- package/config/routing-calibration.json.example +1605 -0
- package/config/routing-centroids.json.example +1570 -0
- package/config/routing-clusters.yaml.example +41 -0
- package/data/contrib/example.json +25 -0
- package/dist/api/explain/router-explain.d.ts +47 -0
- package/dist/api/explain/router-explain.d.ts.map +1 -0
- package/dist/api/explain/router-explain.js +101 -0
- package/dist/api/explain/router-explain.js.map +1 -0
- package/dist/api/middleware/pi-router-middleware.d.ts +86 -0
- package/dist/api/middleware/pi-router-middleware.d.ts.map +1 -0
- package/dist/api/middleware/pi-router-middleware.js +83 -0
- package/dist/api/middleware/pi-router-middleware.js.map +1 -0
- package/dist/cli/smart-router-cli.d.ts +85 -0
- package/dist/cli/smart-router-cli.d.ts.map +1 -0
- package/dist/cli/smart-router-cli.js +293 -0
- package/dist/cli/smart-router-cli.js.map +1 -0
- package/dist/config/defaults.d.ts +7 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +33 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/models-loader.d.ts +22 -0
- package/dist/config/models-loader.d.ts.map +1 -0
- package/dist/config/models-loader.js +51 -0
- package/dist/config/models-loader.js.map +1 -0
- package/dist/config/pi-model-mapper.d.ts +40 -0
- package/dist/config/pi-model-mapper.d.ts.map +1 -0
- package/dist/config/pi-model-mapper.js +216 -0
- package/dist/config/pi-model-mapper.js.map +1 -0
- package/dist/config/routing-clusters-loader.d.ts +35 -0
- package/dist/config/routing-clusters-loader.d.ts.map +1 -0
- package/dist/config/routing-clusters-loader.js +100 -0
- package/dist/config/routing-clusters-loader.js.map +1 -0
- package/dist/domain/delegation/delegation-context.d.ts +23 -0
- package/dist/domain/delegation/delegation-context.d.ts.map +1 -0
- package/dist/domain/delegation/delegation-context.js +73 -0
- package/dist/domain/delegation/delegation-context.js.map +1 -0
- package/dist/domain/delegation/execution-ledger.d.ts +19 -0
- package/dist/domain/delegation/execution-ledger.d.ts.map +1 -0
- package/dist/domain/delegation/execution-ledger.js +23 -0
- package/dist/domain/delegation/execution-ledger.js.map +1 -0
- package/dist/domain/delegation/output-headroom.d.ts +39 -0
- package/dist/domain/delegation/output-headroom.d.ts.map +1 -0
- package/dist/domain/delegation/output-headroom.js +78 -0
- package/dist/domain/delegation/output-headroom.js.map +1 -0
- package/dist/domain/matching/cluster-matcher.d.ts +69 -0
- package/dist/domain/matching/cluster-matcher.d.ts.map +1 -0
- package/dist/domain/matching/cluster-matcher.js +294 -0
- package/dist/domain/matching/cluster-matcher.js.map +1 -0
- package/dist/domain/matching/embedding-provider.d.ts +21 -0
- package/dist/domain/matching/embedding-provider.d.ts.map +1 -0
- package/dist/domain/matching/embedding-provider.js +42 -0
- package/dist/domain/matching/embedding-provider.js.map +1 -0
- package/dist/domain/matching/hydra-input.d.ts +18 -0
- package/dist/domain/matching/hydra-input.d.ts.map +1 -0
- package/dist/domain/matching/hydra-input.js +40 -0
- package/dist/domain/matching/hydra-input.js.map +1 -0
- package/dist/domain/matching/hydra-matcher.d.ts +103 -0
- package/dist/domain/matching/hydra-matcher.d.ts.map +1 -0
- package/dist/domain/matching/hydra-matcher.js +275 -0
- package/dist/domain/matching/hydra-matcher.js.map +1 -0
- package/dist/domain/pinning/cache-economics.d.ts +53 -0
- package/dist/domain/pinning/cache-economics.d.ts.map +1 -0
- package/dist/domain/pinning/cache-economics.js +72 -0
- package/dist/domain/pinning/cache-economics.js.map +1 -0
- package/dist/domain/pinning/loop-escalation.d.ts +41 -0
- package/dist/domain/pinning/loop-escalation.d.ts.map +1 -0
- package/dist/domain/pinning/loop-escalation.js +137 -0
- package/dist/domain/pinning/loop-escalation.js.map +1 -0
- package/dist/domain/pinning/session-pinner.d.ts +81 -0
- package/dist/domain/pinning/session-pinner.d.ts.map +1 -0
- package/dist/domain/pinning/session-pinner.js +237 -0
- package/dist/domain/pinning/session-pinner.js.map +1 -0
- package/dist/domain/pinning/sub-route-policy.d.ts +41 -0
- package/dist/domain/pinning/sub-route-policy.d.ts.map +1 -0
- package/dist/domain/pinning/sub-route-policy.js +72 -0
- package/dist/domain/pinning/sub-route-policy.js.map +1 -0
- package/dist/domain/pipeline/router-pipeline.d.ts +180 -0
- package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -0
- package/dist/domain/pipeline/router-pipeline.js +807 -0
- package/dist/domain/pipeline/router-pipeline.js.map +1 -0
- package/dist/domain/pipeline/safe-default.d.ts +25 -0
- package/dist/domain/pipeline/safe-default.d.ts.map +1 -0
- package/dist/domain/pipeline/safe-default.js +41 -0
- package/dist/domain/pipeline/safe-default.js.map +1 -0
- package/dist/domain/refactor-guardrails.d.ts +48 -0
- package/dist/domain/refactor-guardrails.d.ts.map +1 -0
- package/dist/domain/refactor-guardrails.js +129 -0
- package/dist/domain/refactor-guardrails.js.map +1 -0
- package/dist/domain/routing/context-fit.d.ts +53 -0
- package/dist/domain/routing/context-fit.d.ts.map +1 -0
- package/dist/domain/routing/context-fit.js +142 -0
- package/dist/domain/routing/context-fit.js.map +1 -0
- package/dist/domain/routing/expected-cost.d.ts +63 -0
- package/dist/domain/routing/expected-cost.d.ts.map +1 -0
- package/dist/domain/routing/expected-cost.js +177 -0
- package/dist/domain/routing/expected-cost.js.map +1 -0
- package/dist/domain/routing/p-success-classifier.d.ts +130 -0
- package/dist/domain/routing/p-success-classifier.d.ts.map +1 -0
- package/dist/domain/routing/p-success-classifier.js +350 -0
- package/dist/domain/routing/p-success-classifier.js.map +1 -0
- package/dist/domain/routing/tier-features.d.ts +70 -0
- package/dist/domain/routing/tier-features.d.ts.map +1 -0
- package/dist/domain/routing/tier-features.js +174 -0
- package/dist/domain/routing/tier-features.js.map +1 -0
- package/dist/domain/routing/tool-history-guard.d.ts +32 -0
- package/dist/domain/routing/tool-history-guard.d.ts.map +1 -0
- package/dist/domain/routing/tool-history-guard.js +110 -0
- package/dist/domain/routing/tool-history-guard.js.map +1 -0
- package/dist/domain/scoring/multi-objective.d.ts +48 -0
- package/dist/domain/scoring/multi-objective.d.ts.map +1 -0
- package/dist/domain/scoring/multi-objective.js +116 -0
- package/dist/domain/scoring/multi-objective.js.map +1 -0
- package/dist/domain/triage/triage-engine.d.ts +43 -0
- package/dist/domain/triage/triage-engine.d.ts.map +1 -0
- package/dist/domain/triage/triage-engine.js +317 -0
- package/dist/domain/triage/triage-engine.js.map +1 -0
- package/dist/domain/triage/turn-envelope.d.ts +17 -0
- package/dist/domain/triage/turn-envelope.d.ts.map +1 -0
- package/dist/domain/triage/turn-envelope.js +81 -0
- package/dist/domain/triage/turn-envelope.js.map +1 -0
- package/dist/domain/types/entities.d.ts +303 -0
- package/dist/domain/types/entities.d.ts.map +1 -0
- package/dist/domain/types/entities.js +6 -0
- package/dist/domain/types/entities.js.map +1 -0
- package/dist/domain/types/index.d.ts +4 -0
- package/dist/domain/types/index.d.ts.map +1 -0
- package/dist/domain/types/index.js +2 -0
- package/dist/domain/types/index.js.map +1 -0
- package/dist/domain/types/schemas.d.ts +346 -0
- package/dist/domain/types/schemas.d.ts.map +1 -0
- package/dist/domain/types/schemas.js +241 -0
- package/dist/domain/types/schemas.js.map +1 -0
- package/dist/domain/types/store-port.d.ts +44 -0
- package/dist/domain/types/store-port.d.ts.map +1 -0
- package/dist/domain/types/store-port.js +6 -0
- package/dist/domain/types/store-port.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/gemini-provider.d.ts +28 -0
- package/dist/infra/gemini-provider.d.ts.map +1 -0
- package/dist/infra/gemini-provider.js +112 -0
- package/dist/infra/gemini-provider.js.map +1 -0
- package/dist/infra/telemetry.d.ts +51 -0
- package/dist/infra/telemetry.d.ts.map +1 -0
- package/dist/infra/telemetry.js +100 -0
- package/dist/infra/telemetry.js.map +1 -0
- package/dist/infrastructure/delegation/provider-error.d.ts +44 -0
- package/dist/infrastructure/delegation/provider-error.d.ts.map +1 -0
- package/dist/infrastructure/delegation/provider-error.js +179 -0
- package/dist/infrastructure/delegation/provider-error.js.map +1 -0
- package/dist/infrastructure/gateway/circuit-breaker.d.ts +68 -0
- package/dist/infrastructure/gateway/circuit-breaker.d.ts.map +1 -0
- package/dist/infrastructure/gateway/circuit-breaker.js +150 -0
- package/dist/infrastructure/gateway/circuit-breaker.js.map +1 -0
- package/dist/infrastructure/gateway/gateway-dispatch.d.ts +138 -0
- package/dist/infrastructure/gateway/gateway-dispatch.d.ts.map +1 -0
- package/dist/infrastructure/gateway/gateway-dispatch.js +308 -0
- package/dist/infrastructure/gateway/gateway-dispatch.js.map +1 -0
- package/dist/infrastructure/hardware/hardware-probe.d.ts +32 -0
- package/dist/infrastructure/hardware/hardware-probe.d.ts.map +1 -0
- package/dist/infrastructure/hardware/hardware-probe.js +199 -0
- package/dist/infrastructure/hardware/hardware-probe.js.map +1 -0
- package/dist/infrastructure/local/local-zero-tier.d.ts +47 -0
- package/dist/infrastructure/local/local-zero-tier.d.ts.map +1 -0
- package/dist/infrastructure/local/local-zero-tier.js +92 -0
- package/dist/infrastructure/local/local-zero-tier.js.map +1 -0
- package/dist/infrastructure/persistence/memory-store.d.ts +31 -0
- package/dist/infrastructure/persistence/memory-store.d.ts.map +1 -0
- package/dist/infrastructure/persistence/memory-store.js +87 -0
- package/dist/infrastructure/persistence/memory-store.js.map +1 -0
- package/dist/infrastructure/persistence/sqlite-store.d.ts +81 -0
- package/dist/infrastructure/persistence/sqlite-store.d.ts.map +1 -0
- package/dist/infrastructure/persistence/sqlite-store.js +658 -0
- package/dist/infrastructure/persistence/sqlite-store.js.map +1 -0
- package/dist/infrastructure/pricing/litellm-fetch.d.ts +46 -0
- package/dist/infrastructure/pricing/litellm-fetch.d.ts.map +1 -0
- package/dist/infrastructure/pricing/litellm-fetch.js +145 -0
- package/dist/infrastructure/pricing/litellm-fetch.js.map +1 -0
- package/dist/infrastructure/pricing/price-broker.d.ts +56 -0
- package/dist/infrastructure/pricing/price-broker.d.ts.map +1 -0
- package/dist/infrastructure/pricing/price-broker.js +133 -0
- package/dist/infrastructure/pricing/price-broker.js.map +1 -0
- package/dist/infrastructure/pricing/pricing-monitor.d.ts +27 -0
- package/dist/infrastructure/pricing/pricing-monitor.d.ts.map +1 -0
- package/dist/infrastructure/pricing/pricing-monitor.js +46 -0
- package/dist/infrastructure/pricing/pricing-monitor.js.map +1 -0
- package/dist/infrastructure/telemetry/dataset-limits.d.ts +13 -0
- package/dist/infrastructure/telemetry/dataset-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/dataset-limits.js +28 -0
- package/dist/infrastructure/telemetry/dataset-limits.js.map +1 -0
- package/dist/infrastructure/telemetry/dataset-recorder.d.ts +48 -0
- package/dist/infrastructure/telemetry/dataset-recorder.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/dataset-recorder.js +154 -0
- package/dist/infrastructure/telemetry/dataset-recorder.js.map +1 -0
- package/dist/infrastructure/telemetry/outcome-limits.d.ts +12 -0
- package/dist/infrastructure/telemetry/outcome-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/outcome-limits.js +28 -0
- package/dist/infrastructure/telemetry/outcome-limits.js.map +1 -0
- package/dist/infrastructure/telemetry/outcome-recorder.d.ts +29 -0
- package/dist/infrastructure/telemetry/outcome-recorder.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/outcome-recorder.js +49 -0
- package/dist/infrastructure/telemetry/outcome-recorder.js.map +1 -0
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts +115 -0
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/routing-telemetry.js +529 -0
- package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -0
- package/dist/infrastructure/telemetry/telemetry-limits.d.ts +16 -0
- package/dist/infrastructure/telemetry/telemetry-limits.d.ts.map +1 -0
- package/dist/infrastructure/telemetry/telemetry-limits.js +35 -0
- package/dist/infrastructure/telemetry/telemetry-limits.js.map +1 -0
- package/package.json +110 -0
- package/skills/router-backlog-orchestrator/SKILL.md +183 -0
- package/skills/router-backlog-orchestrator/references/github-router-issue-template.md +78 -0
- package/skills/router-backlog-orchestrator/references/packet-from-issue.md +99 -0
- package/skills/router-backlog-orchestrator/references/prioritization-rubric.md +65 -0
- package/skills/router-backlog-orchestrator/scripts/collect-backlog.sh +44 -0
- package/specs/001-build-smart-router/contracts/telemetry-contrib.schema.json +151 -0
- package/src/api/explain/.gitkeep +0 -0
- package/src/api/explain/router-explain.ts +146 -0
- package/src/api/middleware/.gitkeep +0 -0
- package/src/api/middleware/pi-router-middleware.ts +169 -0
- package/src/cli/smart-router-cli.ts +436 -0
- package/src/config/.gitkeep +0 -0
- package/src/config/defaults.ts +35 -0
- package/src/config/models-loader.ts +68 -0
- package/src/config/pi-model-mapper.ts +285 -0
- package/src/config/routing-clusters-loader.ts +148 -0
- package/src/domain/delegation/delegation-context.ts +111 -0
- package/src/domain/delegation/execution-ledger.ts +34 -0
- package/src/domain/delegation/output-headroom.ts +131 -0
- package/src/domain/matching/.gitkeep +0 -0
- package/src/domain/matching/cluster-matcher.ts +474 -0
- package/src/domain/matching/embedding-provider.ts +79 -0
- package/src/domain/matching/hydra-input.ts +53 -0
- package/src/domain/matching/hydra-matcher.ts +454 -0
- package/src/domain/pinning/.gitkeep +0 -0
- package/src/domain/pinning/cache-economics.ts +128 -0
- package/src/domain/pinning/loop-escalation.ts +181 -0
- package/src/domain/pinning/session-pinner.ts +388 -0
- package/src/domain/pinning/sub-route-policy.ts +119 -0
- package/src/domain/pipeline/.gitkeep +0 -0
- package/src/domain/pipeline/router-pipeline.ts +1139 -0
- package/src/domain/pipeline/safe-default.ts +70 -0
- package/src/domain/refactor-guardrails.ts +202 -0
- package/src/domain/routing/context-fit.ts +221 -0
- package/src/domain/routing/expected-cost.ts +320 -0
- package/src/domain/routing/p-success-classifier.ts +511 -0
- package/src/domain/routing/tier-features.ts +290 -0
- package/src/domain/routing/tool-history-guard.ts +159 -0
- package/src/domain/scoring/.gitkeep +0 -0
- package/src/domain/scoring/multi-objective.ts +175 -0
- package/src/domain/triage/.gitkeep +0 -0
- package/src/domain/triage/triage-engine.ts +373 -0
- package/src/domain/triage/turn-envelope.ts +104 -0
- package/src/domain/types/.gitkeep +0 -0
- package/src/domain/types/entities.ts +384 -0
- package/src/domain/types/index.ts +41 -0
- package/src/domain/types/schemas.ts +289 -0
- package/src/domain/types/store-port.ts +59 -0
- package/src/index.ts +103 -0
- package/src/infra/gemini-provider.ts +142 -0
- package/src/infra/telemetry.ts +169 -0
- package/src/infrastructure/delegation/provider-error.ts +251 -0
- package/src/infrastructure/gateway/.gitkeep +0 -0
- package/src/infrastructure/gateway/circuit-breaker.ts +196 -0
- package/src/infrastructure/gateway/gateway-dispatch.ts +444 -0
- package/src/infrastructure/hardware/.gitkeep +0 -0
- package/src/infrastructure/hardware/hardware-probe.ts +276 -0
- package/src/infrastructure/local/.gitkeep +0 -0
- package/src/infrastructure/local/local-zero-tier.ts +151 -0
- package/src/infrastructure/persistence/.gitkeep +0 -0
- package/src/infrastructure/persistence/memory-store.ts +122 -0
- package/src/infrastructure/persistence/sqlite-store.ts +900 -0
- package/src/infrastructure/pricing/.gitkeep +0 -0
- package/src/infrastructure/pricing/litellm-fetch.ts +215 -0
- package/src/infrastructure/pricing/price-broker.ts +183 -0
- package/src/infrastructure/pricing/pricing-monitor.ts +65 -0
- package/src/infrastructure/telemetry/.gitkeep +0 -0
- package/src/infrastructure/telemetry/dataset-limits.ts +39 -0
- package/src/infrastructure/telemetry/dataset-recorder.ts +215 -0
- package/src/infrastructure/telemetry/outcome-limits.ts +42 -0
- package/src/infrastructure/telemetry/outcome-recorder.ts +118 -0
- package/src/infrastructure/telemetry/routing-telemetry.ts +824 -0
- package/src/infrastructure/telemetry/telemetry-limits.ts +50 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expected-cost tier selection — SP-106, #68.
|
|
3
|
+
*
|
|
4
|
+
* E[cost_T] = P(success | T) × directCost(T)
|
|
5
|
+
* + (1 - P(success | T)) × E[cost_escalation]
|
|
6
|
+
*
|
|
7
|
+
* Selects the tier minimizing adjusted expected cost subject to context-fit,
|
|
8
|
+
* local readiness, and pin/cache economics (FR-008).
|
|
9
|
+
*/
|
|
10
|
+
import { resolveFrugalityCostPer1M } from '../../infrastructure/pricing/price-broker.js';
|
|
11
|
+
import { evaluateCacheEconomics, } from '../pinning/cache-economics.js';
|
|
12
|
+
import { selectLowestCostModel } from '../pinning/sub-route-policy.js';
|
|
13
|
+
/** Frontier-tier success probability when evaluating escalation terminal cost. */
|
|
14
|
+
export const FRONTIER_P_SUCCESS = 1;
|
|
15
|
+
/** Minimum per-1M-token spread required before economical tiers compete. */
|
|
16
|
+
export const MIN_PRICE_DELTA_PER_1M = 0.25;
|
|
17
|
+
function clamp01(value) {
|
|
18
|
+
if (value <= 0)
|
|
19
|
+
return 0;
|
|
20
|
+
if (value >= 1)
|
|
21
|
+
return 1;
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function directCostUsd(costPer1M, estTokens) {
|
|
25
|
+
return (estTokens / 1_000_000) * costPer1M;
|
|
26
|
+
}
|
|
27
|
+
function resolveCheapestModelForTier(fleet, tier) {
|
|
28
|
+
return selectLowestCostModel(fleet.filter((model) => model.tier === tier));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve representative per-1M cost for a tier using subscription-aware pricing (SP-096).
|
|
32
|
+
*/
|
|
33
|
+
export function resolveTierCostPer1M(tier, fleet, priceCatalog) {
|
|
34
|
+
const model = resolveCheapestModelForTier(fleet, tier);
|
|
35
|
+
if (!model) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
return resolveFrugalityCostPer1M(model, priceCatalog);
|
|
39
|
+
}
|
|
40
|
+
function resolvePSuccessForTier(tier, pSuccessCheap) {
|
|
41
|
+
if (tier === 'frontier-cloud') {
|
|
42
|
+
return FRONTIER_P_SUCCESS;
|
|
43
|
+
}
|
|
44
|
+
return clamp01(pSuccessCheap);
|
|
45
|
+
}
|
|
46
|
+
function applyCostQualityAlpha(expectedCostUsd, pSuccess, escalationCostUsd, alpha) {
|
|
47
|
+
const riskPenalty = (1 - clamp01(alpha)) * (1 - clamp01(pSuccess)) * escalationCostUsd;
|
|
48
|
+
return expectedCostUsd + riskPenalty;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compute per-tier expected routing cost under uncertainty.
|
|
52
|
+
*
|
|
53
|
+
* `priceCatalog` drives subscription virtual cost resolution; pass `costPer1M`
|
|
54
|
+
* via options when the caller already resolved tier pricing.
|
|
55
|
+
*/
|
|
56
|
+
export function computeExpectedCost(tier, pSuccess, priceCatalog, estTokens, escalationCostUsd, options) {
|
|
57
|
+
const alpha = options?.alpha ?? 1;
|
|
58
|
+
const fleet = options?.fleet ?? [];
|
|
59
|
+
const costPer1M = options?.costPer1M ?? resolveTierCostPer1M(tier, fleet, priceCatalog);
|
|
60
|
+
const direct = directCostUsd(costPer1M, estTokens);
|
|
61
|
+
const boundedPSuccess = resolvePSuccessForTier(tier, pSuccess);
|
|
62
|
+
const expectedCostUsd = boundedPSuccess * direct + (1 - boundedPSuccess) * escalationCostUsd;
|
|
63
|
+
const adjustedExpectedCostUsd = applyCostQualityAlpha(expectedCostUsd, boundedPSuccess, escalationCostUsd, alpha);
|
|
64
|
+
return {
|
|
65
|
+
tier,
|
|
66
|
+
pSuccess: boundedPSuccess,
|
|
67
|
+
costPer1M,
|
|
68
|
+
directCostUsd: direct,
|
|
69
|
+
escalationCostUsd,
|
|
70
|
+
expectedCostUsd,
|
|
71
|
+
adjustedExpectedCostUsd,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function listViableTiers(fleet, localZeroReady) {
|
|
75
|
+
const tiers = new Set();
|
|
76
|
+
for (const model of fleet) {
|
|
77
|
+
if (model.healthy === false) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (model.tier === 'zero-tier' && !localZeroReady) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
tiers.add(model.tier);
|
|
84
|
+
}
|
|
85
|
+
return [...tiers];
|
|
86
|
+
}
|
|
87
|
+
function buildEscalationCostUsd(tier, cheapDirectUsd, frontierDirectUsd) {
|
|
88
|
+
if (tier === 'frontier-cloud') {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
return cheapDirectUsd + frontierDirectUsd;
|
|
92
|
+
}
|
|
93
|
+
function hasSignificantPriceDelta(economicalCostPer1M, frontierCostPer1M) {
|
|
94
|
+
return frontierCostPer1M - economicalCostPer1M >= MIN_PRICE_DELTA_PER_1M;
|
|
95
|
+
}
|
|
96
|
+
function pickCheaperTierModel(fleet, tier) {
|
|
97
|
+
return resolveCheapestModelForTier(fleet, tier);
|
|
98
|
+
}
|
|
99
|
+
function shouldKeepPinnedTier(input, selectedTier) {
|
|
100
|
+
const pinnedModel = input.pinnedModel;
|
|
101
|
+
const sessionPin = input.sessionPin;
|
|
102
|
+
if (!pinnedModel || !sessionPin) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const candidate = pickCheaperTierModel(input.fleet, selectedTier);
|
|
106
|
+
if (!candidate || candidate.id === pinnedModel.id) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const economics = evaluateCacheEconomics(sessionPin, pinnedModel, candidate, input.estTokens, input.cacheEconomicsConfig);
|
|
110
|
+
return !economics.shouldSwitch;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Compare expected cost across context-fit-viable tiers and return argmin tier hint.
|
|
114
|
+
*/
|
|
115
|
+
export function selectTierByExpectedCost(input) {
|
|
116
|
+
const viableTiers = listViableTiers(input.fleet, input.localZeroReady);
|
|
117
|
+
const frontierCostPer1M = resolveTierCostPer1M('frontier-cloud', input.fleet, input.priceCatalog);
|
|
118
|
+
const economicalCostPer1M = Math.min(resolveTierCostPer1M('economical-cloud', input.fleet, input.priceCatalog), resolveTierCostPer1M('zero-tier', input.fleet, input.priceCatalog) || Infinity);
|
|
119
|
+
const frontierDirectUsd = directCostUsd(frontierCostPer1M, input.estTokens);
|
|
120
|
+
const priceDeltaSignificant = hasSignificantPriceDelta(economicalCostPer1M, frontierCostPer1M);
|
|
121
|
+
const tierCosts = viableTiers.map((tier) => {
|
|
122
|
+
const costPer1M = resolveTierCostPer1M(tier, input.fleet, input.priceCatalog);
|
|
123
|
+
const direct = directCostUsd(costPer1M, input.estTokens);
|
|
124
|
+
const escalationCostUsd = buildEscalationCostUsd(tier, direct, frontierDirectUsd);
|
|
125
|
+
return computeExpectedCost(tier, input.pSuccessCheap, input.priceCatalog, input.estTokens, escalationCostUsd, {
|
|
126
|
+
alpha: input.alpha,
|
|
127
|
+
costPer1M,
|
|
128
|
+
fleet: input.fleet,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
if (tierCosts.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
tierHint: null,
|
|
134
|
+
reasonCode: 'expected_cost_no_viable_tier',
|
|
135
|
+
tierCosts,
|
|
136
|
+
rationale: 'No viable tiers after context-fit and local readiness filters',
|
|
137
|
+
blockedByPinEconomics: false,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
let best = tierCosts[0];
|
|
141
|
+
for (const candidate of tierCosts.slice(1)) {
|
|
142
|
+
if (candidate.adjustedExpectedCostUsd < best.adjustedExpectedCostUsd) {
|
|
143
|
+
best = candidate;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if ((best.tier === 'economical-cloud' || best.tier === 'zero-tier') &&
|
|
147
|
+
!priceDeltaSignificant) {
|
|
148
|
+
return {
|
|
149
|
+
tierHint: null,
|
|
150
|
+
reasonCode: 'expected_cost_price_delta_insufficient',
|
|
151
|
+
tierCosts,
|
|
152
|
+
rationale: 'Economical tier expected cost is lowest but frontier–economical price delta is below threshold',
|
|
153
|
+
blockedByPinEconomics: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (shouldKeepPinnedTier(input, best.tier)) {
|
|
157
|
+
const pinnedTier = input.pinnedModel.tier;
|
|
158
|
+
return {
|
|
159
|
+
tierHint: pinnedTier,
|
|
160
|
+
reasonCode: 'expected_cost_pin_cache_economics',
|
|
161
|
+
tierCosts,
|
|
162
|
+
rationale: 'Expected-cost tier switch blocked because cache reprime exceeds projected savings (FR-008)',
|
|
163
|
+
blockedByPinEconomics: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const rationale = best.tier === 'frontier-cloud'
|
|
167
|
+
? `Frontier minimizes E[cost]=${best.adjustedExpectedCostUsd.toFixed(6)} with P(success)=${best.pSuccess.toFixed(3)}`
|
|
168
|
+
: `Economical tier minimizes E[cost]=${best.adjustedExpectedCostUsd.toFixed(6)} with P(success)=${best.pSuccess.toFixed(3)}`;
|
|
169
|
+
return {
|
|
170
|
+
tierHint: best.tier,
|
|
171
|
+
reasonCode: `expected_cost_${best.tier.replace('-', '_')}`,
|
|
172
|
+
tierCosts,
|
|
173
|
+
rationale,
|
|
174
|
+
blockedByPinEconomics: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=expected-cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expected-cost.js","sourceRoot":"","sources":["../../../src/domain/routing/expected-cost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline P(success) classifier for economical-tier routing (SP-104).
|
|
3
|
+
*
|
|
4
|
+
* Trains a lightweight logistic scorer from privacy-safe dataset export rows
|
|
5
|
+
* joined with behavioral outcome labels. No heavy ML dependencies.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { TierFeatureVector } from './tier-features.js';
|
|
9
|
+
import type { OutcomeSignalType, RoutingDatasetRecord, RoutingOutcomeRecord } from '../types/index.js';
|
|
10
|
+
/** Minimum labeled samples required before predictions deviate from neutral. */
|
|
11
|
+
export declare const MIN_TRAINING_SAMPLES = 30;
|
|
12
|
+
/** Neutral probability when training data is insufficient or artifact missing. */
|
|
13
|
+
export declare const NEUTRAL_P_SUCCESS = 0.5;
|
|
14
|
+
/** Outcome signals that mark a routing attempt as unsuccessful for cheap-tier training. */
|
|
15
|
+
export declare const FAILURE_OUTCOME_SIGNALS: readonly OutcomeSignalType[];
|
|
16
|
+
/** Outcome signals reserved for future execution telemetry (failover, length, infra). */
|
|
17
|
+
export declare const FUTURE_FAILURE_SIGNALS: readonly ["provider_failover", "stop_reason_length", "infra_error"];
|
|
18
|
+
export declare const P_SUCCESS_FEATURE_NAMES: readonly ["prompt_length_norm", "estimated_input_tokens_norm", "triage_cyclomatic_score", "requirement_reasoning", "requirement_code_gen", "requirement_tool_use", "has_tool_context", "compaction_flag", "routing_latency_norm", "economical_tier"];
|
|
19
|
+
export type PSuccessFeatureName = (typeof P_SUCCESS_FEATURE_NAMES)[number];
|
|
20
|
+
export interface PSuccessFeatures {
|
|
21
|
+
readonly prompt_length_norm: number;
|
|
22
|
+
readonly estimated_input_tokens_norm: number;
|
|
23
|
+
readonly triage_cyclomatic_score: number;
|
|
24
|
+
readonly requirement_reasoning: number;
|
|
25
|
+
readonly requirement_code_gen: number;
|
|
26
|
+
readonly requirement_tool_use: number;
|
|
27
|
+
readonly has_tool_context: number;
|
|
28
|
+
readonly compaction_flag: number;
|
|
29
|
+
readonly routing_latency_norm: number;
|
|
30
|
+
readonly economical_tier: number;
|
|
31
|
+
}
|
|
32
|
+
/** Online inference latency budget (SP-105). */
|
|
33
|
+
export declare const P_SUCCESS_INFERENCE_BUDGET_MS = 5;
|
|
34
|
+
export declare const PSuccessWeightsSchema: z.ZodObject<{
|
|
35
|
+
version: z.ZodLiteral<1>;
|
|
36
|
+
min_training_samples: z.ZodNumber;
|
|
37
|
+
feature_names: z.ZodArray<z.ZodEnum<{
|
|
38
|
+
compaction_flag: "compaction_flag";
|
|
39
|
+
has_tool_context: "has_tool_context";
|
|
40
|
+
triage_cyclomatic_score: "triage_cyclomatic_score";
|
|
41
|
+
requirement_reasoning: "requirement_reasoning";
|
|
42
|
+
requirement_code_gen: "requirement_code_gen";
|
|
43
|
+
requirement_tool_use: "requirement_tool_use";
|
|
44
|
+
prompt_length_norm: "prompt_length_norm";
|
|
45
|
+
estimated_input_tokens_norm: "estimated_input_tokens_norm";
|
|
46
|
+
routing_latency_norm: "routing_latency_norm";
|
|
47
|
+
economical_tier: "economical_tier";
|
|
48
|
+
}>>;
|
|
49
|
+
intercept: z.ZodNumber;
|
|
50
|
+
coefficients: z.ZodArray<z.ZodNumber>;
|
|
51
|
+
trained_sample_count: z.ZodNumber;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export interface PSuccessWeights {
|
|
54
|
+
readonly version: 1;
|
|
55
|
+
readonly min_training_samples: number;
|
|
56
|
+
readonly feature_names: readonly PSuccessFeatureName[];
|
|
57
|
+
readonly intercept: number;
|
|
58
|
+
readonly coefficients: readonly number[];
|
|
59
|
+
readonly trained_sample_count: number;
|
|
60
|
+
}
|
|
61
|
+
export interface PSuccessPredictionResult {
|
|
62
|
+
readonly probability: number;
|
|
63
|
+
readonly elapsed_ms: number;
|
|
64
|
+
readonly within_budget: boolean;
|
|
65
|
+
readonly feature_importances: Readonly<Record<PSuccessFeatureName, number>>;
|
|
66
|
+
}
|
|
67
|
+
export interface LoadPSuccessWeightsOptions {
|
|
68
|
+
readonly filePath?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare class PSuccessWeightsLoaderError extends Error {
|
|
71
|
+
readonly name = "PSuccessWeightsLoaderError";
|
|
72
|
+
constructor(message: string, options?: ErrorOptions);
|
|
73
|
+
}
|
|
74
|
+
export interface LabeledTrainingSample {
|
|
75
|
+
readonly request_id: string;
|
|
76
|
+
readonly features: PSuccessFeatures;
|
|
77
|
+
readonly success: boolean;
|
|
78
|
+
readonly outcome_signals: readonly OutcomeSignalType[];
|
|
79
|
+
}
|
|
80
|
+
export interface DatasetExportJoinRow extends Record<string, unknown> {
|
|
81
|
+
readonly request_id: string;
|
|
82
|
+
readonly success_label: boolean | null;
|
|
83
|
+
readonly outcome_signals: readonly OutcomeSignalType[];
|
|
84
|
+
}
|
|
85
|
+
/** Map a dataset row to a normalized feature vector for training or inference. */
|
|
86
|
+
export declare function extractPSuccessFeatures(record: Pick<RoutingDatasetRecord, 'prompt_length_chars' | 'estimated_input_tokens' | 'triage_cyclomatic_score' | 'requirement_reasoning' | 'requirement_code_gen' | 'requirement_tool_use' | 'has_tool_context' | 'compaction_flag' | 'routing_latency_ms' | 'tier'>): PSuccessFeatures;
|
|
87
|
+
export declare function featuresToVector(features: PSuccessFeatures): number[];
|
|
88
|
+
/** Derive success label from outcome signals keyed to a request. */
|
|
89
|
+
export declare function deriveSuccessLabel(outcomes: readonly RoutingOutcomeRecord[]): {
|
|
90
|
+
success: boolean | null;
|
|
91
|
+
outcome_signals: readonly OutcomeSignalType[];
|
|
92
|
+
};
|
|
93
|
+
/** Group outcomes by request_id for export joins. */
|
|
94
|
+
export declare function indexOutcomesByRequestId(outcomes: readonly RoutingOutcomeRecord[]): Map<string, RoutingOutcomeRecord[]>;
|
|
95
|
+
/** Join dataset rows with outcome labels for training export. */
|
|
96
|
+
export declare function joinDatasetWithOutcomes(datasetRecords: readonly RoutingDatasetRecord[], outcomeRecords: readonly RoutingOutcomeRecord[]): LabeledTrainingSample[];
|
|
97
|
+
/** Attach join labels to a privacy-safe export object. */
|
|
98
|
+
export declare function attachOutcomeLabelsToExport(exportRecord: Record<string, unknown>, outcomes: readonly RoutingOutcomeRecord[]): DatasetExportJoinRow;
|
|
99
|
+
/** Train logistic weights from labeled samples via batch gradient descent. */
|
|
100
|
+
export declare function trainFromLabeledSamples(samples: readonly LabeledTrainingSample[], options?: {
|
|
101
|
+
readonly learningRate?: number;
|
|
102
|
+
readonly epochs?: number;
|
|
103
|
+
}): PSuccessWeights;
|
|
104
|
+
/** Parse one JSONL export line into a labeled training sample. */
|
|
105
|
+
export declare function parseTrainingExportLine(line: string): LabeledTrainingSample | null;
|
|
106
|
+
/** Train weights from privacy-safe labeled JSONL export content. */
|
|
107
|
+
export declare function trainFromExportJsonl(jsonl: string): PSuccessWeights;
|
|
108
|
+
/** Predict P_success_cheap in [0, 1]; returns neutral when under-trained. */
|
|
109
|
+
export declare function predictPSuccessCheap(features: PSuccessFeatures, weights: PSuccessWeights): number;
|
|
110
|
+
/** Default untrained weights artifact for operators to replace after export training. */
|
|
111
|
+
export declare function createDefaultPSuccessWeights(): PSuccessWeights;
|
|
112
|
+
/** Parse and validate a P(success) weights artifact from JSON text. */
|
|
113
|
+
export declare function parsePSuccessWeightsJson(raw: string): PSuccessWeights;
|
|
114
|
+
/**
|
|
115
|
+
* Load P(success) weights from disk. Returns null when the artifact is missing;
|
|
116
|
+
* throws only when the file exists but is invalid.
|
|
117
|
+
*/
|
|
118
|
+
export declare function loadPSuccessWeights(options?: LoadPSuccessWeightsOptions): PSuccessWeights | null;
|
|
119
|
+
/** Resolve weights for online inference — missing or invalid artifacts fall back safely. */
|
|
120
|
+
export declare function resolvePSuccessWeights(options?: LoadPSuccessWeightsOptions): PSuccessWeights;
|
|
121
|
+
/** Map live tier features to the P(success) feature vector (no prompt text). */
|
|
122
|
+
export declare function tierFeaturesToPSuccessFeatures(tierFeatures: TierFeatureVector, options?: {
|
|
123
|
+
readonly routingLatencyMs?: number;
|
|
124
|
+
readonly economicalTier?: boolean;
|
|
125
|
+
}): PSuccessFeatures;
|
|
126
|
+
/** Signed coefficient × feature contributions for operator explainability. */
|
|
127
|
+
export declare function computePSuccessFeatureImportances(features: PSuccessFeatures, weights: PSuccessWeights): Readonly<Record<PSuccessFeatureName, number>>;
|
|
128
|
+
/** Predict P_success_cheap with elapsed timing guard for the online routing budget. */
|
|
129
|
+
export declare function predictPSuccessCheapTimed(features: PSuccessFeatures, weights: PSuccessWeights, budgetMs?: number): PSuccessPredictionResult;
|
|
130
|
+
//# sourceMappingURL=p-success-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"p-success-classifier.d.ts","sourceRoot":"","sources":["../../../src/domain/routing/p-success-classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline P(success) classifier for economical-tier routing (SP-104).
|
|
3
|
+
*
|
|
4
|
+
* Trains a lightweight logistic scorer from privacy-safe dataset export rows
|
|
5
|
+
* joined with behavioral outcome labels. No heavy ML dependencies.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { resolve } from 'node:path';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
/** Minimum labeled samples required before predictions deviate from neutral. */
|
|
11
|
+
export const MIN_TRAINING_SAMPLES = 30;
|
|
12
|
+
/** Neutral probability when training data is insufficient or artifact missing. */
|
|
13
|
+
export const NEUTRAL_P_SUCCESS = 0.5;
|
|
14
|
+
/** Outcome signals that mark a routing attempt as unsuccessful for cheap-tier training. */
|
|
15
|
+
export const FAILURE_OUTCOME_SIGNALS = [
|
|
16
|
+
'model_override',
|
|
17
|
+
'feedback_bad',
|
|
18
|
+
];
|
|
19
|
+
/** Outcome signals reserved for future execution telemetry (failover, length, infra). */
|
|
20
|
+
export const FUTURE_FAILURE_SIGNALS = [
|
|
21
|
+
'provider_failover',
|
|
22
|
+
'stop_reason_length',
|
|
23
|
+
'infra_error',
|
|
24
|
+
];
|
|
25
|
+
export const P_SUCCESS_FEATURE_NAMES = [
|
|
26
|
+
'prompt_length_norm',
|
|
27
|
+
'estimated_input_tokens_norm',
|
|
28
|
+
'triage_cyclomatic_score',
|
|
29
|
+
'requirement_reasoning',
|
|
30
|
+
'requirement_code_gen',
|
|
31
|
+
'requirement_tool_use',
|
|
32
|
+
'has_tool_context',
|
|
33
|
+
'compaction_flag',
|
|
34
|
+
'routing_latency_norm',
|
|
35
|
+
'economical_tier',
|
|
36
|
+
];
|
|
37
|
+
/** Online inference latency budget (SP-105). */
|
|
38
|
+
export const P_SUCCESS_INFERENCE_BUDGET_MS = 5;
|
|
39
|
+
export const PSuccessWeightsSchema = z.object({
|
|
40
|
+
version: z.literal(1),
|
|
41
|
+
min_training_samples: z.number().int().min(0),
|
|
42
|
+
feature_names: z.array(z.enum(P_SUCCESS_FEATURE_NAMES)).length(P_SUCCESS_FEATURE_NAMES.length),
|
|
43
|
+
intercept: z.number(),
|
|
44
|
+
coefficients: z.array(z.number()).length(P_SUCCESS_FEATURE_NAMES.length),
|
|
45
|
+
trained_sample_count: z.number().int().min(0),
|
|
46
|
+
});
|
|
47
|
+
export class PSuccessWeightsLoaderError extends Error {
|
|
48
|
+
name = 'PSuccessWeightsLoaderError';
|
|
49
|
+
constructor(message, options) {
|
|
50
|
+
super(message, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const PROMPT_LENGTH_NORM = 8_000;
|
|
54
|
+
const TOKEN_NORM = 2_000;
|
|
55
|
+
const LATENCY_NORM_MS = 500;
|
|
56
|
+
function clamp01(value) {
|
|
57
|
+
if (value <= 0)
|
|
58
|
+
return 0;
|
|
59
|
+
if (value >= 1)
|
|
60
|
+
return 1;
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
function sigmoid(z) {
|
|
64
|
+
if (z >= 0) {
|
|
65
|
+
const expNeg = Math.exp(-z);
|
|
66
|
+
return 1 / (1 + expNeg);
|
|
67
|
+
}
|
|
68
|
+
const expPos = Math.exp(z);
|
|
69
|
+
return expPos / (1 + expPos);
|
|
70
|
+
}
|
|
71
|
+
function numOrZero(value) {
|
|
72
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
73
|
+
}
|
|
74
|
+
function boolToNum(value) {
|
|
75
|
+
return value === true ? 1 : 0;
|
|
76
|
+
}
|
|
77
|
+
/** Map a dataset row to a normalized feature vector for training or inference. */
|
|
78
|
+
export function extractPSuccessFeatures(record) {
|
|
79
|
+
const promptLength = numOrZero(record.prompt_length_chars);
|
|
80
|
+
const tokens = numOrZero(record.estimated_input_tokens ?? promptLength / 4);
|
|
81
|
+
return {
|
|
82
|
+
prompt_length_norm: clamp01(promptLength / PROMPT_LENGTH_NORM),
|
|
83
|
+
estimated_input_tokens_norm: clamp01(tokens / TOKEN_NORM),
|
|
84
|
+
triage_cyclomatic_score: clamp01(numOrZero(record.triage_cyclomatic_score)),
|
|
85
|
+
requirement_reasoning: clamp01(numOrZero(record.requirement_reasoning)),
|
|
86
|
+
requirement_code_gen: clamp01(numOrZero(record.requirement_code_gen)),
|
|
87
|
+
requirement_tool_use: clamp01(numOrZero(record.requirement_tool_use)),
|
|
88
|
+
has_tool_context: boolToNum(record.has_tool_context),
|
|
89
|
+
compaction_flag: boolToNum(record.compaction_flag),
|
|
90
|
+
routing_latency_norm: clamp01(numOrZero(record.routing_latency_ms) / LATENCY_NORM_MS),
|
|
91
|
+
economical_tier: record.tier === 'economical-cloud' || record.tier === 'zero-tier' ? 1 : 0,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function featuresToVector(features) {
|
|
95
|
+
return P_SUCCESS_FEATURE_NAMES.map((name) => features[name]);
|
|
96
|
+
}
|
|
97
|
+
/** Derive success label from outcome signals keyed to a request. */
|
|
98
|
+
export function deriveSuccessLabel(outcomes) {
|
|
99
|
+
const signals = [...new Set(outcomes.map((entry) => entry.signal_type))];
|
|
100
|
+
if (signals.some((signal) => FAILURE_OUTCOME_SIGNALS.includes(signal))) {
|
|
101
|
+
return { success: false, outcome_signals: signals };
|
|
102
|
+
}
|
|
103
|
+
if (signals.includes('feedback_good')) {
|
|
104
|
+
return { success: true, outcome_signals: signals };
|
|
105
|
+
}
|
|
106
|
+
if (signals.length === 0) {
|
|
107
|
+
return { success: null, outcome_signals: signals };
|
|
108
|
+
}
|
|
109
|
+
// compaction_pin_break and other neutral signals — treat as unlabeled success.
|
|
110
|
+
return { success: true, outcome_signals: signals };
|
|
111
|
+
}
|
|
112
|
+
/** Group outcomes by request_id for export joins. */
|
|
113
|
+
export function indexOutcomesByRequestId(outcomes) {
|
|
114
|
+
const byRequest = new Map();
|
|
115
|
+
for (const outcome of outcomes) {
|
|
116
|
+
const existing = byRequest.get(outcome.request_id) ?? [];
|
|
117
|
+
existing.push(outcome);
|
|
118
|
+
byRequest.set(outcome.request_id, existing);
|
|
119
|
+
}
|
|
120
|
+
return byRequest;
|
|
121
|
+
}
|
|
122
|
+
/** Join dataset rows with outcome labels for training export. */
|
|
123
|
+
export function joinDatasetWithOutcomes(datasetRecords, outcomeRecords) {
|
|
124
|
+
const outcomesByRequest = indexOutcomesByRequestId(outcomeRecords);
|
|
125
|
+
return datasetRecords.map((record) => {
|
|
126
|
+
const linked = outcomesByRequest.get(record.request_id) ?? [];
|
|
127
|
+
const { success, outcome_signals } = deriveSuccessLabel(linked);
|
|
128
|
+
return {
|
|
129
|
+
request_id: record.request_id,
|
|
130
|
+
features: extractPSuccessFeatures(record),
|
|
131
|
+
success: success ?? true,
|
|
132
|
+
outcome_signals,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** Attach join labels to a privacy-safe export object. */
|
|
137
|
+
export function attachOutcomeLabelsToExport(exportRecord, outcomes) {
|
|
138
|
+
const { success, outcome_signals } = deriveSuccessLabel(outcomes);
|
|
139
|
+
return {
|
|
140
|
+
...exportRecord,
|
|
141
|
+
request_id: String(exportRecord.request_id ?? ''),
|
|
142
|
+
success_label: success,
|
|
143
|
+
outcome_signals,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function dot(a, b) {
|
|
147
|
+
let sum = 0;
|
|
148
|
+
for (let i = 0; i < a.length; i++) {
|
|
149
|
+
sum += a[i] * b[i];
|
|
150
|
+
}
|
|
151
|
+
return sum;
|
|
152
|
+
}
|
|
153
|
+
/** Train logistic weights from labeled samples via batch gradient descent. */
|
|
154
|
+
export function trainFromLabeledSamples(samples, options) {
|
|
155
|
+
const featureDim = P_SUCCESS_FEATURE_NAMES.length;
|
|
156
|
+
const coefficients = Array.from({ length: featureDim }, () => 0);
|
|
157
|
+
let intercept = 0;
|
|
158
|
+
if (samples.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
version: 1,
|
|
161
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
162
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
163
|
+
intercept: 0,
|
|
164
|
+
coefficients,
|
|
165
|
+
trained_sample_count: 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const learningRate = options?.learningRate ?? 0.1;
|
|
169
|
+
const epochs = options?.epochs ?? 200;
|
|
170
|
+
const xs = samples.map((sample) => featuresToVector(sample.features));
|
|
171
|
+
const ys = samples.map((sample) => (sample.success ? 1 : 0));
|
|
172
|
+
for (let epoch = 0; epoch < epochs; epoch++) {
|
|
173
|
+
for (let i = 0; i < xs.length; i++) {
|
|
174
|
+
const x = xs[i];
|
|
175
|
+
const y = ys[i];
|
|
176
|
+
const z = intercept + dot(coefficients, x);
|
|
177
|
+
const prediction = sigmoid(z);
|
|
178
|
+
const error = prediction - y;
|
|
179
|
+
intercept -= learningRate * error;
|
|
180
|
+
for (let j = 0; j < coefficients.length; j++) {
|
|
181
|
+
coefficients[j] -= learningRate * error * x[j];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
version: 1,
|
|
187
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
188
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
189
|
+
intercept,
|
|
190
|
+
coefficients,
|
|
191
|
+
trained_sample_count: samples.length,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Parse one JSONL export line into a labeled training sample. */
|
|
195
|
+
export function parseTrainingExportLine(line) {
|
|
196
|
+
const trimmed = line.trim();
|
|
197
|
+
if (trimmed.length === 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const parsed = JSON.parse(trimmed);
|
|
201
|
+
const requestId = parsed.request_id;
|
|
202
|
+
if (typeof requestId !== 'string' || requestId.length === 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const successLabel = parsed.success_label;
|
|
206
|
+
let success = true;
|
|
207
|
+
if (typeof successLabel === 'boolean') {
|
|
208
|
+
success = successLabel;
|
|
209
|
+
}
|
|
210
|
+
const rawSignals = parsed.outcome_signals;
|
|
211
|
+
const outcome_signals = Array.isArray(rawSignals)
|
|
212
|
+
? rawSignals.filter((value) => typeof value === 'string')
|
|
213
|
+
: [];
|
|
214
|
+
const record = parsed;
|
|
215
|
+
return {
|
|
216
|
+
request_id: requestId,
|
|
217
|
+
features: extractPSuccessFeatures(record),
|
|
218
|
+
success: success === false ? false : true,
|
|
219
|
+
outcome_signals,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Train weights from privacy-safe labeled JSONL export content. */
|
|
223
|
+
export function trainFromExportJsonl(jsonl) {
|
|
224
|
+
const samples = jsonl
|
|
225
|
+
.split('\n')
|
|
226
|
+
.map((line) => parseTrainingExportLine(line))
|
|
227
|
+
.filter((sample) => sample !== null);
|
|
228
|
+
return trainFromLabeledSamples(samples);
|
|
229
|
+
}
|
|
230
|
+
/** Predict P_success_cheap in [0, 1]; returns neutral when under-trained. */
|
|
231
|
+
export function predictPSuccessCheap(features, weights) {
|
|
232
|
+
if (weights.trained_sample_count < weights.min_training_samples) {
|
|
233
|
+
return NEUTRAL_P_SUCCESS;
|
|
234
|
+
}
|
|
235
|
+
const vector = featuresToVector(features);
|
|
236
|
+
if (vector.length !== weights.coefficients.length) {
|
|
237
|
+
return NEUTRAL_P_SUCCESS;
|
|
238
|
+
}
|
|
239
|
+
const z = weights.intercept + dot(weights.coefficients, vector);
|
|
240
|
+
return clamp01(sigmoid(z));
|
|
241
|
+
}
|
|
242
|
+
/** Default untrained weights artifact for operators to replace after export training. */
|
|
243
|
+
export function createDefaultPSuccessWeights() {
|
|
244
|
+
return {
|
|
245
|
+
version: 1,
|
|
246
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
247
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
248
|
+
intercept: 0,
|
|
249
|
+
coefficients: P_SUCCESS_FEATURE_NAMES.map(() => 0),
|
|
250
|
+
trained_sample_count: 0,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function formatZodIssues(error) {
|
|
254
|
+
return error.issues
|
|
255
|
+
.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
|
|
256
|
+
.join('\n');
|
|
257
|
+
}
|
|
258
|
+
/** Parse and validate a P(success) weights artifact from JSON text. */
|
|
259
|
+
export function parsePSuccessWeightsJson(raw) {
|
|
260
|
+
let parsed;
|
|
261
|
+
try {
|
|
262
|
+
parsed = JSON.parse(raw);
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
266
|
+
throw new PSuccessWeightsLoaderError(`Failed to parse JSON: ${message}`, { cause: err });
|
|
267
|
+
}
|
|
268
|
+
const result = PSuccessWeightsSchema.safeParse(parsed);
|
|
269
|
+
if (!result.success) {
|
|
270
|
+
throw new PSuccessWeightsLoaderError(`Invalid P(success) weights artifact:\n${formatZodIssues(result.error)}`, { cause: result.error });
|
|
271
|
+
}
|
|
272
|
+
return result.data;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Load P(success) weights from disk. Returns null when the artifact is missing;
|
|
276
|
+
* throws only when the file exists but is invalid.
|
|
277
|
+
*/
|
|
278
|
+
export function loadPSuccessWeights(options) {
|
|
279
|
+
const filePath = options?.filePath ?? resolve('config', 'p-success-weights.json');
|
|
280
|
+
if (!existsSync(filePath)) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
let raw;
|
|
284
|
+
try {
|
|
285
|
+
raw = readFileSync(filePath, 'utf8');
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
289
|
+
throw new PSuccessWeightsLoaderError(`Failed to read weights file: ${message}`, { cause: err });
|
|
290
|
+
}
|
|
291
|
+
return parsePSuccessWeightsJson(raw);
|
|
292
|
+
}
|
|
293
|
+
/** Resolve weights for online inference — missing or invalid artifacts fall back safely. */
|
|
294
|
+
export function resolvePSuccessWeights(options) {
|
|
295
|
+
try {
|
|
296
|
+
return loadPSuccessWeights(options) ?? createDefaultPSuccessWeights();
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
console.warn('P(success) weights artifact invalid; using neutral fallback', {
|
|
300
|
+
error: err instanceof Error ? err.message : String(err),
|
|
301
|
+
});
|
|
302
|
+
return createDefaultPSuccessWeights();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** Map live tier features to the P(success) feature vector (no prompt text). */
|
|
306
|
+
export function tierFeaturesToPSuccessFeatures(tierFeatures, options) {
|
|
307
|
+
const routingLatencyMs = options?.routingLatencyMs ?? 0;
|
|
308
|
+
const economicalTier = options?.economicalTier ?? true;
|
|
309
|
+
return extractPSuccessFeatures({
|
|
310
|
+
prompt_length_chars: tierFeatures.prompt_length_chars,
|
|
311
|
+
estimated_input_tokens: tierFeatures.estimated_input_tokens,
|
|
312
|
+
triage_cyclomatic_score: tierFeatures.cyclomatic_score,
|
|
313
|
+
requirement_reasoning: tierFeatures.requirement_reasoning,
|
|
314
|
+
requirement_code_gen: tierFeatures.requirement_code_gen,
|
|
315
|
+
requirement_tool_use: tierFeatures.requirement_tool_use,
|
|
316
|
+
has_tool_context: tierFeatures.has_tool_context,
|
|
317
|
+
compaction_flag: false,
|
|
318
|
+
routing_latency_ms: routingLatencyMs,
|
|
319
|
+
tier: economicalTier ? 'economical-cloud' : 'frontier-cloud',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/** Signed coefficient × feature contributions for operator explainability. */
|
|
323
|
+
export function computePSuccessFeatureImportances(features, weights) {
|
|
324
|
+
const importances = {};
|
|
325
|
+
for (let i = 0; i < P_SUCCESS_FEATURE_NAMES.length; i++) {
|
|
326
|
+
const name = P_SUCCESS_FEATURE_NAMES[i];
|
|
327
|
+
importances[name] = weights.coefficients[i] * features[name];
|
|
328
|
+
}
|
|
329
|
+
return importances;
|
|
330
|
+
}
|
|
331
|
+
/** Predict P_success_cheap with elapsed timing guard for the online routing budget. */
|
|
332
|
+
export function predictPSuccessCheapTimed(features, weights, budgetMs = P_SUCCESS_INFERENCE_BUDGET_MS) {
|
|
333
|
+
const start = performance.now();
|
|
334
|
+
const probability = predictPSuccessCheap(features, weights);
|
|
335
|
+
const elapsed_ms = performance.now() - start;
|
|
336
|
+
const feature_importances = computePSuccessFeatureImportances(features, weights);
|
|
337
|
+
if (elapsed_ms > budgetMs) {
|
|
338
|
+
console.warn('P(success) inference exceeded latency budget', {
|
|
339
|
+
elapsed_ms,
|
|
340
|
+
budget_ms: budgetMs,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
probability,
|
|
345
|
+
elapsed_ms,
|
|
346
|
+
within_budget: elapsed_ms <= budgetMs,
|
|
347
|
+
feature_importances,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=p-success-classifier.js.map
|