Files
boardroom-map/startos/actions/configure-models.ts
T
Jonathan KirkwoodandClaude Fable 5 b1d7aed9f4 Implement BDEF v1.1 grading: scoring core, per-deck pipeline, ledger, dashboard, StartOS layer
- Deterministic scoring.py (quant 60 / qual 40 / flags -15, profitability heaviest)
- Per-company JSON ledger with forecast-target chaining deck N-1 -> N
- Single-shot sandbox agent with guided-JSON fallback ladder (no tool loop)
- Portfolio dashboard with sparklines, KPI hit rates, BDEF category bars
- 48 unit tests green; endpoints smoke-tested; npm check+build green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:15:12 -05:00

160 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { sdk } from '../sdk'
import { configFile } from '../file-models/config'
const { InputSpec, Value, List } = sdk
const inputSpec = InputSpec.of({
models: Value.list(
List.obj(
{
name: 'Model Catalog',
description:
'The local models this service can serve on your Sparks. Each grader ' +
'references one of these by its alias. The job runner loads models in ' +
'waves so you can run a panel across more models than fit in GPU memory ' +
'at once.',
default: [],
minLength: 1,
maxLength: 16,
},
{
uniqueBy: 'alias',
displayAs: '{{alias}} → {{hfModel}}',
spec: InputSpec.of({
alias: Value.text({
name: 'Alias',
description: 'Short name graders use to pick this model (e.g. "qwen-32b").',
required: true,
default: null,
placeholder: 'grader-a',
patterns: [
{ regex: '^[a-z0-9][a-z0-9-]{0,30}$',
description: 'Lowercase letters, numbers, dashes (max 31 chars).' },
],
}),
hfModel: Value.text({
name: 'Hugging Face Model ID',
description: 'The model vLLM serves. Must be present in the Spark HF cache for air-gapped mode.',
required: true,
default: null,
placeholder: 'Qwen/Qwen3-32B-FP8',
}),
spark: Value.select({
name: 'Served On',
description:
'Which Spark serves this model. Air-gapped grading mode requires the ' +
'head (primary) Spark; the secondary is used only in local-services mode.',
default: 'primary',
values: { primary: 'Primary (head) Spark', secondary: 'Secondary Spark' },
}),
port: Value.number({
name: 'vLLM Port',
description: 'Host port the vLLM container for this model listens on. Unique per Spark.',
required: true,
default: 8001,
integer: true,
min: 1,
max: 65535,
}),
}),
},
),
),
gpuMemoryUtilization: Value.text({
name: 'GPU Memory Utilization',
description: 'vLLM --gpu-memory-utilization (01). Lower it if you co-resident multiple models per Spark.',
required: true,
default: '0.85',
}),
maxModelLen: Value.number({
name: 'Max Model Length',
description: 'vLLM --max-model-len (context window). Deck text is chunked to fit.',
required: true,
default: 32768,
integer: true,
min: 2048,
}),
toolCallParser: Value.text({
name: 'Tool-Call Parser',
description:
'vLLM tool-call parser for the grader\'s read-file tool loop. Match the ' +
'served model family (Qwen3 → "hermes"). Empty disables native tool-calling.',
required: false,
default: 'hermes',
}),
maxConcurrentModels: Value.number({
name: 'Max Co-resident Models (head Spark)',
description:
'How many distinct models may load on the head Spark at once. The job runner ' +
'loads models in waves so it never exceeds this. 1 is safest.',
required: true,
default: 1,
integer: true,
min: 1,
max: 8,
}),
proxyPort: Value.number({
name: 'Model Proxy Port',
description: 'Port for the on-Spark LiteLLM router that exposes every model alias on one endpoint.',
required: true,
default: 4000,
integer: true,
min: 1,
max: 65535,
}),
})
export const configureModels = sdk.Action.withInput(
'configure-models',
async ({ effects }) => ({
name: 'Configure Models',
description: 'Define the local model catalog served on your Sparks and the serving knobs.',
warning: null,
allowedStatuses: 'any',
group: 'Setup',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const cfg = await configFile.read().const(effects)
if (!cfg) return {}
return {
models: cfg.models,
gpuMemoryUtilization: cfg.gpuMemoryUtilization,
maxModelLen: cfg.maxModelLen,
toolCallParser: cfg.toolCallParser,
maxConcurrentModels: cfg.maxConcurrentModels,
proxyPort: cfg.proxyPort,
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
models: input.models,
gpuMemoryUtilization: input.gpuMemoryUtilization,
maxModelLen: input.maxModelLen,
toolCallParser: input.toolCallParser ?? '',
maxConcurrentModels: input.maxConcurrentModels,
proxyPort: input.proxyPort,
})
return {
version: '1',
title: 'Models Configured',
message:
'Saved ' + input.models.length + ' model(s). Make sure each is present in ' +
'the Spark HF cache for air-gapped runs, then set "Configure Graders".',
result: {
type: 'single',
value: input.models.map((m) => m.alias).join(', '),
copyable: false,
qr: false,
masked: false,
},
}
},
)