Files
boardroom-map/startos/actions/configure-models.ts
T

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 reviewer ' +
'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 reviewers use to pick this model (e.g. "qwen-32b").',
required: true,
default: null,
placeholder: 'reviewer-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 review 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). Documents are 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 reviewer\'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: null,
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 Reviewers".',
result: {
type: 'single',
value: input.models.map((m) => m.alias).join(', '),
copyable: false,
qr: false,
masked: false,
},
}
},
)