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,511 @@
|
|
|
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
|
+
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
|
|
13
|
+
import type { TierFeatureVector } from './tier-features.js';
|
|
14
|
+
import type {
|
|
15
|
+
OutcomeSignalType,
|
|
16
|
+
RoutingDatasetRecord,
|
|
17
|
+
RoutingOutcomeRecord,
|
|
18
|
+
} from '../types/index.js';
|
|
19
|
+
|
|
20
|
+
/** Minimum labeled samples required before predictions deviate from neutral. */
|
|
21
|
+
export const MIN_TRAINING_SAMPLES = 30;
|
|
22
|
+
|
|
23
|
+
/** Neutral probability when training data is insufficient or artifact missing. */
|
|
24
|
+
export const NEUTRAL_P_SUCCESS = 0.5;
|
|
25
|
+
|
|
26
|
+
/** Outcome signals that mark a routing attempt as unsuccessful for cheap-tier training. */
|
|
27
|
+
export const FAILURE_OUTCOME_SIGNALS: readonly OutcomeSignalType[] = [
|
|
28
|
+
'model_override',
|
|
29
|
+
'feedback_bad',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/** Outcome signals reserved for future execution telemetry (failover, length, infra). */
|
|
33
|
+
export const FUTURE_FAILURE_SIGNALS = [
|
|
34
|
+
'provider_failover',
|
|
35
|
+
'stop_reason_length',
|
|
36
|
+
'infra_error',
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export const P_SUCCESS_FEATURE_NAMES = [
|
|
40
|
+
'prompt_length_norm',
|
|
41
|
+
'estimated_input_tokens_norm',
|
|
42
|
+
'triage_cyclomatic_score',
|
|
43
|
+
'requirement_reasoning',
|
|
44
|
+
'requirement_code_gen',
|
|
45
|
+
'requirement_tool_use',
|
|
46
|
+
'has_tool_context',
|
|
47
|
+
'compaction_flag',
|
|
48
|
+
'routing_latency_norm',
|
|
49
|
+
'economical_tier',
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
export type PSuccessFeatureName = (typeof P_SUCCESS_FEATURE_NAMES)[number];
|
|
53
|
+
|
|
54
|
+
export interface PSuccessFeatures {
|
|
55
|
+
readonly prompt_length_norm: number;
|
|
56
|
+
readonly estimated_input_tokens_norm: number;
|
|
57
|
+
readonly triage_cyclomatic_score: number;
|
|
58
|
+
readonly requirement_reasoning: number;
|
|
59
|
+
readonly requirement_code_gen: number;
|
|
60
|
+
readonly requirement_tool_use: number;
|
|
61
|
+
readonly has_tool_context: number;
|
|
62
|
+
readonly compaction_flag: number;
|
|
63
|
+
readonly routing_latency_norm: number;
|
|
64
|
+
readonly economical_tier: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Online inference latency budget (SP-105). */
|
|
68
|
+
export const P_SUCCESS_INFERENCE_BUDGET_MS = 5;
|
|
69
|
+
|
|
70
|
+
export const PSuccessWeightsSchema = z.object({
|
|
71
|
+
version: z.literal(1),
|
|
72
|
+
min_training_samples: z.number().int().min(0),
|
|
73
|
+
feature_names: z.array(z.enum(P_SUCCESS_FEATURE_NAMES)).length(P_SUCCESS_FEATURE_NAMES.length),
|
|
74
|
+
intercept: z.number(),
|
|
75
|
+
coefficients: z.array(z.number()).length(P_SUCCESS_FEATURE_NAMES.length),
|
|
76
|
+
trained_sample_count: z.number().int().min(0),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export interface PSuccessWeights {
|
|
80
|
+
readonly version: 1;
|
|
81
|
+
readonly min_training_samples: number;
|
|
82
|
+
readonly feature_names: readonly PSuccessFeatureName[];
|
|
83
|
+
readonly intercept: number;
|
|
84
|
+
readonly coefficients: readonly number[];
|
|
85
|
+
readonly trained_sample_count: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PSuccessPredictionResult {
|
|
89
|
+
readonly probability: number;
|
|
90
|
+
readonly elapsed_ms: number;
|
|
91
|
+
readonly within_budget: boolean;
|
|
92
|
+
readonly feature_importances: Readonly<Record<PSuccessFeatureName, number>>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface LoadPSuccessWeightsOptions {
|
|
96
|
+
readonly filePath?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class PSuccessWeightsLoaderError extends Error {
|
|
100
|
+
override readonly name = 'PSuccessWeightsLoaderError';
|
|
101
|
+
|
|
102
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
103
|
+
super(message, options);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface LabeledTrainingSample {
|
|
108
|
+
readonly request_id: string;
|
|
109
|
+
readonly features: PSuccessFeatures;
|
|
110
|
+
readonly success: boolean;
|
|
111
|
+
readonly outcome_signals: readonly OutcomeSignalType[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface DatasetExportJoinRow extends Record<string, unknown> {
|
|
115
|
+
readonly request_id: string;
|
|
116
|
+
readonly success_label: boolean | null;
|
|
117
|
+
readonly outcome_signals: readonly OutcomeSignalType[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const PROMPT_LENGTH_NORM = 8_000;
|
|
121
|
+
const TOKEN_NORM = 2_000;
|
|
122
|
+
const LATENCY_NORM_MS = 500;
|
|
123
|
+
|
|
124
|
+
function clamp01(value: number): number {
|
|
125
|
+
if (value <= 0) return 0;
|
|
126
|
+
if (value >= 1) return 1;
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sigmoid(z: number): number {
|
|
131
|
+
if (z >= 0) {
|
|
132
|
+
const expNeg = Math.exp(-z);
|
|
133
|
+
return 1 / (1 + expNeg);
|
|
134
|
+
}
|
|
135
|
+
const expPos = Math.exp(z);
|
|
136
|
+
return expPos / (1 + expPos);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function numOrZero(value: unknown): number {
|
|
140
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function boolToNum(value: unknown): number {
|
|
144
|
+
return value === true ? 1 : 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Map a dataset row to a normalized feature vector for training or inference. */
|
|
148
|
+
export function extractPSuccessFeatures(
|
|
149
|
+
record: Pick<
|
|
150
|
+
RoutingDatasetRecord,
|
|
151
|
+
| 'prompt_length_chars'
|
|
152
|
+
| 'estimated_input_tokens'
|
|
153
|
+
| 'triage_cyclomatic_score'
|
|
154
|
+
| 'requirement_reasoning'
|
|
155
|
+
| 'requirement_code_gen'
|
|
156
|
+
| 'requirement_tool_use'
|
|
157
|
+
| 'has_tool_context'
|
|
158
|
+
| 'compaction_flag'
|
|
159
|
+
| 'routing_latency_ms'
|
|
160
|
+
| 'tier'
|
|
161
|
+
>,
|
|
162
|
+
): PSuccessFeatures {
|
|
163
|
+
const promptLength = numOrZero(record.prompt_length_chars);
|
|
164
|
+
const tokens = numOrZero(record.estimated_input_tokens ?? promptLength / 4);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
prompt_length_norm: clamp01(promptLength / PROMPT_LENGTH_NORM),
|
|
168
|
+
estimated_input_tokens_norm: clamp01(tokens / TOKEN_NORM),
|
|
169
|
+
triage_cyclomatic_score: clamp01(numOrZero(record.triage_cyclomatic_score)),
|
|
170
|
+
requirement_reasoning: clamp01(numOrZero(record.requirement_reasoning)),
|
|
171
|
+
requirement_code_gen: clamp01(numOrZero(record.requirement_code_gen)),
|
|
172
|
+
requirement_tool_use: clamp01(numOrZero(record.requirement_tool_use)),
|
|
173
|
+
has_tool_context: boolToNum(record.has_tool_context),
|
|
174
|
+
compaction_flag: boolToNum(record.compaction_flag),
|
|
175
|
+
routing_latency_norm: clamp01(numOrZero(record.routing_latency_ms) / LATENCY_NORM_MS),
|
|
176
|
+
economical_tier:
|
|
177
|
+
record.tier === 'economical-cloud' || record.tier === 'zero-tier' ? 1 : 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function featuresToVector(features: PSuccessFeatures): number[] {
|
|
182
|
+
return P_SUCCESS_FEATURE_NAMES.map((name) => features[name]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Derive success label from outcome signals keyed to a request. */
|
|
186
|
+
export function deriveSuccessLabel(
|
|
187
|
+
outcomes: readonly RoutingOutcomeRecord[],
|
|
188
|
+
): { success: boolean | null; outcome_signals: readonly OutcomeSignalType[] } {
|
|
189
|
+
const signals = [...new Set(outcomes.map((entry) => entry.signal_type))];
|
|
190
|
+
|
|
191
|
+
if (signals.some((signal) => FAILURE_OUTCOME_SIGNALS.includes(signal))) {
|
|
192
|
+
return { success: false, outcome_signals: signals };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (signals.includes('feedback_good')) {
|
|
196
|
+
return { success: true, outcome_signals: signals };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (signals.length === 0) {
|
|
200
|
+
return { success: null, outcome_signals: signals };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// compaction_pin_break and other neutral signals — treat as unlabeled success.
|
|
204
|
+
return { success: true, outcome_signals: signals };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Group outcomes by request_id for export joins. */
|
|
208
|
+
export function indexOutcomesByRequestId(
|
|
209
|
+
outcomes: readonly RoutingOutcomeRecord[],
|
|
210
|
+
): Map<string, RoutingOutcomeRecord[]> {
|
|
211
|
+
const byRequest = new Map<string, RoutingOutcomeRecord[]>();
|
|
212
|
+
|
|
213
|
+
for (const outcome of outcomes) {
|
|
214
|
+
const existing = byRequest.get(outcome.request_id) ?? [];
|
|
215
|
+
existing.push(outcome);
|
|
216
|
+
byRequest.set(outcome.request_id, existing);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return byRequest;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Join dataset rows with outcome labels for training export. */
|
|
223
|
+
export function joinDatasetWithOutcomes(
|
|
224
|
+
datasetRecords: readonly RoutingDatasetRecord[],
|
|
225
|
+
outcomeRecords: readonly RoutingOutcomeRecord[],
|
|
226
|
+
): LabeledTrainingSample[] {
|
|
227
|
+
const outcomesByRequest = indexOutcomesByRequestId(outcomeRecords);
|
|
228
|
+
|
|
229
|
+
return datasetRecords.map((record) => {
|
|
230
|
+
const linked = outcomesByRequest.get(record.request_id) ?? [];
|
|
231
|
+
const { success, outcome_signals } = deriveSuccessLabel(linked);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
request_id: record.request_id,
|
|
235
|
+
features: extractPSuccessFeatures(record),
|
|
236
|
+
success: success ?? true,
|
|
237
|
+
outcome_signals,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Attach join labels to a privacy-safe export object. */
|
|
243
|
+
export function attachOutcomeLabelsToExport(
|
|
244
|
+
exportRecord: Record<string, unknown>,
|
|
245
|
+
outcomes: readonly RoutingOutcomeRecord[],
|
|
246
|
+
): DatasetExportJoinRow {
|
|
247
|
+
const { success, outcome_signals } = deriveSuccessLabel(outcomes);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
...exportRecord,
|
|
251
|
+
request_id: String(exportRecord.request_id ?? ''),
|
|
252
|
+
success_label: success,
|
|
253
|
+
outcome_signals,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function dot(a: readonly number[], b: readonly number[]): number {
|
|
258
|
+
let sum = 0;
|
|
259
|
+
for (let i = 0; i < a.length; i++) {
|
|
260
|
+
sum += a[i]! * b[i]!;
|
|
261
|
+
}
|
|
262
|
+
return sum;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Train logistic weights from labeled samples via batch gradient descent. */
|
|
266
|
+
export function trainFromLabeledSamples(
|
|
267
|
+
samples: readonly LabeledTrainingSample[],
|
|
268
|
+
options?: { readonly learningRate?: number; readonly epochs?: number },
|
|
269
|
+
): PSuccessWeights {
|
|
270
|
+
const featureDim = P_SUCCESS_FEATURE_NAMES.length;
|
|
271
|
+
const coefficients = Array.from({ length: featureDim }, () => 0);
|
|
272
|
+
let intercept = 0;
|
|
273
|
+
|
|
274
|
+
if (samples.length === 0) {
|
|
275
|
+
return {
|
|
276
|
+
version: 1,
|
|
277
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
278
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
279
|
+
intercept: 0,
|
|
280
|
+
coefficients,
|
|
281
|
+
trained_sample_count: 0,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const learningRate = options?.learningRate ?? 0.1;
|
|
286
|
+
const epochs = options?.epochs ?? 200;
|
|
287
|
+
const xs = samples.map((sample) => featuresToVector(sample.features));
|
|
288
|
+
const ys = samples.map((sample) => (sample.success ? 1 : 0));
|
|
289
|
+
|
|
290
|
+
for (let epoch = 0; epoch < epochs; epoch++) {
|
|
291
|
+
for (let i = 0; i < xs.length; i++) {
|
|
292
|
+
const x = xs[i]!;
|
|
293
|
+
const y = ys[i]!;
|
|
294
|
+
const z = intercept + dot(coefficients, x);
|
|
295
|
+
const prediction = sigmoid(z);
|
|
296
|
+
const error = prediction - y;
|
|
297
|
+
|
|
298
|
+
intercept -= learningRate * error;
|
|
299
|
+
for (let j = 0; j < coefficients.length; j++) {
|
|
300
|
+
coefficients[j]! -= learningRate * error * x[j]!;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
version: 1,
|
|
307
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
308
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
309
|
+
intercept,
|
|
310
|
+
coefficients,
|
|
311
|
+
trained_sample_count: samples.length,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Parse one JSONL export line into a labeled training sample. */
|
|
316
|
+
export function parseTrainingExportLine(line: string): LabeledTrainingSample | null {
|
|
317
|
+
const trimmed = line.trim();
|
|
318
|
+
if (trimmed.length === 0) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const parsed = JSON.parse(trimmed) as Record<string, unknown>;
|
|
323
|
+
const requestId = parsed.request_id;
|
|
324
|
+
if (typeof requestId !== 'string' || requestId.length === 0) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const successLabel = parsed.success_label;
|
|
329
|
+
let success = true;
|
|
330
|
+
if (typeof successLabel === 'boolean') {
|
|
331
|
+
success = successLabel;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const rawSignals = parsed.outcome_signals;
|
|
335
|
+
const outcome_signals = Array.isArray(rawSignals)
|
|
336
|
+
? rawSignals.filter((value): value is OutcomeSignalType => typeof value === 'string')
|
|
337
|
+
: [];
|
|
338
|
+
|
|
339
|
+
const record = parsed as unknown as RoutingDatasetRecord;
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
request_id: requestId,
|
|
343
|
+
features: extractPSuccessFeatures(record),
|
|
344
|
+
success: success === false ? false : true,
|
|
345
|
+
outcome_signals,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Train weights from privacy-safe labeled JSONL export content. */
|
|
350
|
+
export function trainFromExportJsonl(jsonl: string): PSuccessWeights {
|
|
351
|
+
const samples = jsonl
|
|
352
|
+
.split('\n')
|
|
353
|
+
.map((line) => parseTrainingExportLine(line))
|
|
354
|
+
.filter((sample): sample is LabeledTrainingSample => sample !== null);
|
|
355
|
+
|
|
356
|
+
return trainFromLabeledSamples(samples);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Predict P_success_cheap in [0, 1]; returns neutral when under-trained. */
|
|
360
|
+
export function predictPSuccessCheap(
|
|
361
|
+
features: PSuccessFeatures,
|
|
362
|
+
weights: PSuccessWeights,
|
|
363
|
+
): number {
|
|
364
|
+
if (weights.trained_sample_count < weights.min_training_samples) {
|
|
365
|
+
return NEUTRAL_P_SUCCESS;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const vector = featuresToVector(features);
|
|
369
|
+
if (vector.length !== weights.coefficients.length) {
|
|
370
|
+
return NEUTRAL_P_SUCCESS;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const z = weights.intercept + dot(weights.coefficients, vector);
|
|
374
|
+
return clamp01(sigmoid(z));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Default untrained weights artifact for operators to replace after export training. */
|
|
378
|
+
export function createDefaultPSuccessWeights(): PSuccessWeights {
|
|
379
|
+
return {
|
|
380
|
+
version: 1,
|
|
381
|
+
min_training_samples: MIN_TRAINING_SAMPLES,
|
|
382
|
+
feature_names: P_SUCCESS_FEATURE_NAMES,
|
|
383
|
+
intercept: 0,
|
|
384
|
+
coefficients: P_SUCCESS_FEATURE_NAMES.map(() => 0),
|
|
385
|
+
trained_sample_count: 0,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function formatZodIssues(error: { issues: readonly { path: readonly PropertyKey[]; message: string }[] }): string {
|
|
390
|
+
return error.issues
|
|
391
|
+
.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
|
|
392
|
+
.join('\n');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Parse and validate a P(success) weights artifact from JSON text. */
|
|
396
|
+
export function parsePSuccessWeightsJson(raw: string): PSuccessWeights {
|
|
397
|
+
let parsed: unknown;
|
|
398
|
+
try {
|
|
399
|
+
parsed = JSON.parse(raw);
|
|
400
|
+
} catch (err: unknown) {
|
|
401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
402
|
+
throw new PSuccessWeightsLoaderError(`Failed to parse JSON: ${message}`, { cause: err });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const result = PSuccessWeightsSchema.safeParse(parsed);
|
|
406
|
+
if (!result.success) {
|
|
407
|
+
throw new PSuccessWeightsLoaderError(
|
|
408
|
+
`Invalid P(success) weights artifact:\n${formatZodIssues(result.error)}`,
|
|
409
|
+
{ cause: result.error },
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return result.data;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Load P(success) weights from disk. Returns null when the artifact is missing;
|
|
418
|
+
* throws only when the file exists but is invalid.
|
|
419
|
+
*/
|
|
420
|
+
export function loadPSuccessWeights(options?: LoadPSuccessWeightsOptions): PSuccessWeights | null {
|
|
421
|
+
const filePath = options?.filePath ?? resolve('config', 'p-success-weights.json');
|
|
422
|
+
|
|
423
|
+
if (!existsSync(filePath)) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let raw: string;
|
|
428
|
+
try {
|
|
429
|
+
raw = readFileSync(filePath, 'utf8');
|
|
430
|
+
} catch (err: unknown) {
|
|
431
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
432
|
+
throw new PSuccessWeightsLoaderError(`Failed to read weights file: ${message}`, { cause: err });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return parsePSuccessWeightsJson(raw);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Resolve weights for online inference — missing or invalid artifacts fall back safely. */
|
|
439
|
+
export function resolvePSuccessWeights(options?: LoadPSuccessWeightsOptions): PSuccessWeights {
|
|
440
|
+
try {
|
|
441
|
+
return loadPSuccessWeights(options) ?? createDefaultPSuccessWeights();
|
|
442
|
+
} catch (err: unknown) {
|
|
443
|
+
console.warn('P(success) weights artifact invalid; using neutral fallback', {
|
|
444
|
+
error: err instanceof Error ? err.message : String(err),
|
|
445
|
+
});
|
|
446
|
+
return createDefaultPSuccessWeights();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Map live tier features to the P(success) feature vector (no prompt text). */
|
|
451
|
+
export function tierFeaturesToPSuccessFeatures(
|
|
452
|
+
tierFeatures: TierFeatureVector,
|
|
453
|
+
options?: { readonly routingLatencyMs?: number; readonly economicalTier?: boolean },
|
|
454
|
+
): PSuccessFeatures {
|
|
455
|
+
const routingLatencyMs = options?.routingLatencyMs ?? 0;
|
|
456
|
+
const economicalTier = options?.economicalTier ?? true;
|
|
457
|
+
|
|
458
|
+
return extractPSuccessFeatures({
|
|
459
|
+
prompt_length_chars: tierFeatures.prompt_length_chars,
|
|
460
|
+
estimated_input_tokens: tierFeatures.estimated_input_tokens,
|
|
461
|
+
triage_cyclomatic_score: tierFeatures.cyclomatic_score,
|
|
462
|
+
requirement_reasoning: tierFeatures.requirement_reasoning,
|
|
463
|
+
requirement_code_gen: tierFeatures.requirement_code_gen,
|
|
464
|
+
requirement_tool_use: tierFeatures.requirement_tool_use,
|
|
465
|
+
has_tool_context: tierFeatures.has_tool_context,
|
|
466
|
+
compaction_flag: false,
|
|
467
|
+
routing_latency_ms: routingLatencyMs,
|
|
468
|
+
tier: economicalTier ? 'economical-cloud' : 'frontier-cloud',
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Signed coefficient × feature contributions for operator explainability. */
|
|
473
|
+
export function computePSuccessFeatureImportances(
|
|
474
|
+
features: PSuccessFeatures,
|
|
475
|
+
weights: PSuccessWeights,
|
|
476
|
+
): Readonly<Record<PSuccessFeatureName, number>> {
|
|
477
|
+
const importances = {} as Record<PSuccessFeatureName, number>;
|
|
478
|
+
|
|
479
|
+
for (let i = 0; i < P_SUCCESS_FEATURE_NAMES.length; i++) {
|
|
480
|
+
const name = P_SUCCESS_FEATURE_NAMES[i]!;
|
|
481
|
+
importances[name] = weights.coefficients[i]! * features[name];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return importances;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** Predict P_success_cheap with elapsed timing guard for the online routing budget. */
|
|
488
|
+
export function predictPSuccessCheapTimed(
|
|
489
|
+
features: PSuccessFeatures,
|
|
490
|
+
weights: PSuccessWeights,
|
|
491
|
+
budgetMs: number = P_SUCCESS_INFERENCE_BUDGET_MS,
|
|
492
|
+
): PSuccessPredictionResult {
|
|
493
|
+
const start = performance.now();
|
|
494
|
+
const probability = predictPSuccessCheap(features, weights);
|
|
495
|
+
const elapsed_ms = performance.now() - start;
|
|
496
|
+
const feature_importances = computePSuccessFeatureImportances(features, weights);
|
|
497
|
+
|
|
498
|
+
if (elapsed_ms > budgetMs) {
|
|
499
|
+
console.warn('P(success) inference exceeded latency budget', {
|
|
500
|
+
elapsed_ms,
|
|
501
|
+
budget_ms: budgetMs,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
probability,
|
|
507
|
+
elapsed_ms,
|
|
508
|
+
within_budget: elapsed_ms <= budgetMs,
|
|
509
|
+
feature_importances,
|
|
510
|
+
};
|
|
511
|
+
}
|