refactor(config): extract config loading from modelgrid

This commit is contained in:
2026-04-21 12:45:16 +00:00
parent 871afedbb7
commit 0ea98caed6
3 changed files with 85 additions and 83 deletions
+4 -7
View File
@@ -1,14 +1,11 @@
import { assertEquals } from 'jsr:@std/assert@^1.0.0';
import { ModelGrid } from '../ts/modelgrid.ts';
import { ConfigManager } from '../ts/config/config-manager.ts';
import type { IModelGridConfig } from '../ts/interfaces/config.ts';
Deno.test('ModelGrid normalizes current config defaults', () => {
const modelgrid = new ModelGrid();
const normalizeConfig = (modelgrid as unknown as {
normalizeConfig: (config: Partial<IModelGridConfig>) => IModelGridConfig;
}).normalizeConfig.bind(modelgrid);
Deno.test('ConfigManager normalizes current config defaults', () => {
const configManager = new ConfigManager();
const normalized = normalizeConfig({
const normalized = configManager.normalizeConfig({
version: '1.0.0',
api: {
port: 9000,
+75
View File
@@ -0,0 +1,75 @@
import * as fs from 'node:fs/promises';
import { PATHS, VERSION } from '../constants.ts';
import type { IModelGridConfig } from '../interfaces/config.ts';
export class ConfigManager {
public async loadConfig(): Promise<IModelGridConfig> {
try {
const configContent = await fs.readFile(PATHS.CONFIG_FILE, 'utf-8');
return this.normalizeConfig(JSON.parse(configContent) as Partial<IModelGridConfig>);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
throw new Error(`Configuration file not found: ${PATHS.CONFIG_FILE}`);
}
throw error;
}
}
public async saveConfig(config: IModelGridConfig): Promise<void> {
await fs.mkdir(PATHS.CONFIG_DIR, { recursive: true });
await fs.writeFile(PATHS.CONFIG_FILE, JSON.stringify(config, null, 2));
}
public normalizeConfig(config: Partial<IModelGridConfig>): IModelGridConfig {
const filteredContainers = (config.containers || []).filter(
(container) => (container as { type?: string }).type !== 'ollama',
);
return {
version: config.version || VERSION,
api: {
port: config.api?.port || 8080,
host: config.api?.host || '0.0.0.0',
apiKeys: config.api?.apiKeys || [],
rateLimit: config.api?.rateLimit,
cors: config.api?.cors ?? true,
corsOrigins: config.api?.corsOrigins || ['*'],
},
ui: {
enabled: config.ui?.enabled ?? true,
port: config.ui?.port || 8081,
host: config.ui?.host || '0.0.0.0',
assetSource: config.ui?.assetSource === 'disk' ? 'disk' : 'bundle',
},
docker: {
networkName: config.docker?.networkName || 'modelgrid',
runtime: config.docker?.runtime || 'docker',
socketPath: config.docker?.socketPath,
},
gpus: {
autoDetect: config.gpus?.autoDetect ?? true,
assignments: config.gpus?.assignments || {},
},
containers: filteredContainers,
models: {
registryUrl: config.models?.registryUrl || 'https://list.modelgrid.com/catalog/models.json',
autoDeploy: config.models?.autoDeploy ?? true,
defaultEngine: 'vllm',
autoLoad: config.models?.autoLoad || [],
},
cluster: {
enabled: config.cluster?.enabled ?? false,
nodeName: config.cluster?.nodeName || 'modelgrid-local',
role: config.cluster?.role || 'standalone',
bindHost: config.cluster?.bindHost || '0.0.0.0',
gossipPort: config.cluster?.gossipPort || 7946,
sharedSecret: config.cluster?.sharedSecret,
advertiseUrl: config.cluster?.advertiseUrl,
controlPlaneUrl: config.cluster?.controlPlaneUrl,
heartbeatIntervalMs: config.cluster?.heartbeatIntervalMs || 5000,
seedNodes: config.cluster?.seedNodes || [],
},
checkInterval: config.checkInterval || 30000,
};
}
}
+6 -76
View File
@@ -24,7 +24,7 @@ import { ClusterHandler } from './cli/cluster-handler.ts';
import { ModelHandler } from './cli/model-handler.ts';
import { ConfigHandler } from './cli/config-handler.ts';
import { ServiceHandler } from './cli/service-handler.ts';
import * as fs from 'node:fs/promises';
import { ConfigManager } from './config/config-manager.ts';
/**
* ModelGrid - Main application coordinator
@@ -42,6 +42,7 @@ export class ModelGrid {
private clusterCoordinator?: ClusterCoordinator;
private modelRegistry: ModelRegistry;
private modelLoader?: ModelLoader;
private configManager: ConfigManager;
// CLI Handlers
private gpuHandler: GpuHandler;
@@ -60,6 +61,7 @@ export class ModelGrid {
this.containerManager = new ContainerManager();
this.clusterManager = new ClusterManager();
this.modelRegistry = new ModelRegistry();
this.configManager = new ConfigManager();
this.systemd = new Systemd();
this.daemon = new Daemon(this);
@@ -80,23 +82,8 @@ export class ModelGrid {
* Load configuration from file
*/
public async loadConfig(): Promise<void> {
try {
const configContent = await fs.readFile(PATHS.CONFIG_FILE, 'utf-8');
this.config = this.normalizeConfig(
JSON.parse(configContent) as Partial<IModelGridConfig> & {
models?: {
greenlistUrl?: string;
autoPull?: boolean;
} & Partial<IModelGridConfig['models']>;
},
);
logger.dim(`Configuration loaded from ${PATHS.CONFIG_FILE}`);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
throw new Error(`Configuration file not found: ${PATHS.CONFIG_FILE}`);
}
throw error;
}
this.config = await this.configManager.loadConfig();
logger.dim(`Configuration loaded from ${PATHS.CONFIG_FILE}`);
}
/**
@@ -107,8 +94,7 @@ export class ModelGrid {
throw new Error('No configuration to save');
}
await fs.mkdir(PATHS.CONFIG_DIR, { recursive: true });
await fs.writeFile(PATHS.CONFIG_FILE, JSON.stringify(this.config, null, 2));
await this.configManager.saveConfig(this.config);
logger.dim(`Configuration saved to ${PATHS.CONFIG_FILE}`);
}
@@ -294,62 +280,6 @@ export class ModelGrid {
logger.success('ModelGrid initialized');
}
private normalizeConfig(
config: Partial<IModelGridConfig>,
): IModelGridConfig {
const filteredContainers = (config.containers || []).filter(
(container) => (container as { type?: string }).type !== 'ollama',
);
return {
version: config.version || VERSION,
api: {
port: config.api?.port || 8080,
host: config.api?.host || '0.0.0.0',
apiKeys: config.api?.apiKeys || [],
rateLimit: config.api?.rateLimit,
cors: config.api?.cors ?? true,
corsOrigins: config.api?.corsOrigins || ['*'],
},
ui: {
enabled: config.ui?.enabled ?? true,
port: config.ui?.port || 8081,
host: config.ui?.host || '0.0.0.0',
assetSource: config.ui?.assetSource === 'disk' ? 'disk' : 'bundle',
},
docker: {
networkName: config.docker?.networkName || 'modelgrid',
runtime: config.docker?.runtime || 'docker',
socketPath: config.docker?.socketPath,
},
gpus: {
autoDetect: config.gpus?.autoDetect ?? true,
assignments: config.gpus?.assignments || {},
},
containers: filteredContainers,
models: {
registryUrl: config.models?.registryUrl ||
'https://list.modelgrid.com/catalog/models.json',
autoDeploy: config.models?.autoDeploy ?? true,
defaultEngine: 'vllm',
autoLoad: config.models?.autoLoad || [],
},
cluster: {
enabled: config.cluster?.enabled ?? false,
nodeName: config.cluster?.nodeName || 'modelgrid-local',
role: config.cluster?.role || 'standalone',
bindHost: config.cluster?.bindHost || '0.0.0.0',
gossipPort: config.cluster?.gossipPort || 7946,
sharedSecret: config.cluster?.sharedSecret,
advertiseUrl: config.cluster?.advertiseUrl,
controlPlaneUrl: config.cluster?.controlPlaneUrl,
heartbeatIntervalMs: config.cluster?.heartbeatIntervalMs || 5000,
seedNodes: config.cluster?.seedNodes || [],
},
checkInterval: config.checkInterval || 30000,
};
}
/**
* Shutdown the ModelGrid system
*/