Skip to content

Commit

Permalink
Fixed a few issues in the lm studio manager, and the frontend status
Browse files Browse the repository at this point in the history
  • Loading branch information
MarianoMolina committed Dec 10, 2024
1 parent b4255e6 commit 1756785
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 81 deletions.
13 changes: 5 additions & 8 deletions backend/src/lmStudioManager/lmStudioClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ export class LMStudioClientManager {

const fileNamespace = this.client.files;
this.messageBuilder = new MessageBuilder(new FileService(fileNamespace));

// Set up the interval for inactive model unloading
setInterval(() => this.unloadInactiveModels(), 180 * 1000);

Logger.info('LMStudioClient initialized');
}

private async handlePrediction<T extends ChatCompletionResponse | CompletionResponse>(
Expand Down Expand Up @@ -294,12 +289,14 @@ export class LMStudioClientManager {
if (modelType === 'llm') {
model = await this.client.llm.load(modelPath, {
config: finalConfig as LLMLoadModelConfig,
verbose: true
verbose: true,
identifier: modelPath
});
} else {
model = await this.client.embedding.load(modelPath, {
config: finalConfig as EmbeddingLoadModelConfig,
verbose: true
verbose: true,
identifier: modelPath
});
}

Expand All @@ -312,7 +309,7 @@ export class LMStudioClientManager {
return model;
}

private async unloadInactiveModels() {
public async unloadInactiveModels() {
try {
const now = Date.now();

Expand Down
138 changes: 116 additions & 22 deletions backend/src/lmStudioManager/lmStudioOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Logger from "../utils/logger";
import { ChatCompletionParams, CompletionParams } from "./lmStudio.types";
import { CreateEmbeddingParams } from "./lmStudio.utils";
import { IModelConfig, ModelType } from "../interfaces/model.interface";
import Model from "../models/model.model";
import { Model } from "mongoose";

interface ExtendedDownloadedModel extends DownloadedModel {
isLoaded: boolean;
Expand All @@ -14,15 +14,118 @@ interface ExtendedDownloadedModel extends DownloadedModel {
export class LMStudioRouteManager {
private lmStudioManager: LMStudioClientManager;
private requestQueue: Queue;
private cleanupInterval: NodeJS.Timeout | null;
private isInitialized: boolean;
private readonly CLEANUP_INTERVAL = 3 * 60 * 1000; // 3 minutes
private readonly INACTIVITY_THRESHOLD = 10 * 60 * 1000; // 10 minutes

constructor() {
this.lmStudioManager = new LMStudioClientManager();
this.lmStudioManager = new LMStudioClientManager(this.INACTIVITY_THRESHOLD);
this.requestQueue = new Queue({
maxQueueSize: 100,
operationTimeout: 300000, // 5 minutes
retryCount: 2,
retryDelay: 2000
});
this.cleanupInterval = null;
this.isInitialized = false;
}

/**
* Initialize the LMStudioRouteManager
* Queues initial cleanup and sets up periodic cleanup
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
Logger.warn('LMStudioRouteManager is already initialized');
return;
}

try {
Logger.info('Initializing LMStudioRouteManager');

// Queue initial cleanup
await this.requestQueue.enqueue(
async () => {
Logger.info('Performing initial cleanup');
await this.lmStudioManager.unloadAllModels();
},
'initialize-cleanup'
);

// Set up periodic cleanup
this.cleanupInterval = setInterval(() => {
// Queue periodic cleanup
this.requestQueue.enqueue(
async () => {
Logger.info('Performing periodic inactive model cleanup');
await this.lmStudioManager.unloadInactiveModels();
},
'periodic-cleanup'
).catch(error => Logger.error('Error in periodic cleanup:', error));
}, this.CLEANUP_INTERVAL);

// Set up shutdown handlers
this.setupShutdownHandlers();

this.isInitialized = true;
Logger.info('LMStudioRouteManager initialized successfully');
} catch (error) {
Logger.error('Error initializing LMStudioRouteManager:', error);
throw error;
}
}

/**
* Set up handlers for various shutdown signals
*/
private setupShutdownHandlers(): void {
const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];

signals.forEach(signal => {
process.on(signal, async () => {
Logger.info(`Received ${signal}, initiating graceful shutdown`);
await this.shutdown();
process.exit(0);
});
});

// Handle nodemon restarts
process.on('beforeExit', async () => {
Logger.info('Process beforeExit, initiating cleanup');
await this.shutdown();
});
}

/**
* Gracefully shutdown the manager
* Queues final cleanup operations
*/
async shutdown(): Promise<void> {
Logger.info('Starting LMStudioRouteManager shutdown');

try {
// Clear cleanup interval
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}

// Queue final cleanup
await this.requestQueue.enqueue(
async () => {
Logger.info('Performing final cleanup');
await this.lmStudioManager.unloadAllModels();
},
'shutdown-cleanup'
);

this.isInitialized = false;
Logger.info('LMStudioRouteManager shutdown completed');
} catch (error) {
Logger.error('Error during LMStudioRouteManager shutdown:', error);
throw error;
}
}

private async getModelConfig(modelId: string): Promise<{
Expand Down Expand Up @@ -54,14 +157,12 @@ export class LMStudioRouteManager {
keepModelInMemory: true
};

// Return appropriate config based on model type
if (modelType === ModelType.EMBEDDINGS) {
return baseConfig as EmbeddingLoadModelConfig;
}

return {
...baseConfig,
// Add any LLM-specific configurations here
} as LLMLoadModelConfig;
}

Expand All @@ -70,20 +171,15 @@ export class LMStudioRouteManager {
try {
Logger.debug('Fetching models list');

// Get all downloaded models
const downloadedModels = await this.lmStudioManager.listModels();

// Get currently loaded models for comparison
const loadedLLMs = await this.lmStudioManager.client.llm.listLoaded();
const loadedEmbeddings = await this.lmStudioManager.client.embedding.listLoaded();

// Combine loaded model lists for easier checking
const loadedModels = new Set([
...loadedLLMs.map(m => m.path),
...loadedEmbeddings.map(m => m.path)
]);

// Map downloaded models to extended interface with loaded status
return downloadedModels.map(model => ({
...model,
isLoaded: loadedModels.has(model.path)
Expand All @@ -101,7 +197,11 @@ export class LMStudioRouteManager {
const { modelPath, modelConfig, modelType } = await this.getModelConfig(modelId);

if (![ModelType.CHAT, ModelType.VISION].includes(modelType)) {
throw new Error(`Model type ${modelType} is not supported for chat completion`);
if (modelType !== ModelType.INSTRUCT) {
throw new Error(`Model type ${modelType} is not supported for chat completion`);
} else {
Logger.warn(`Model ${modelId} is not of type CHAT OR VISION, but INSTRUCT - using it anyway`);
}
}

// Update params with the model path
Expand All @@ -112,7 +212,7 @@ export class LMStudioRouteManager {

return await this.lmStudioManager.generateChatCompletion(
updatedParams,
modelConfig?.prompt_config,
modelConfig.prompt_config,
this.createModelConfig(modelConfig, modelType)
);
} catch (error) {
Expand All @@ -128,7 +228,11 @@ export class LMStudioRouteManager {
const { modelPath, modelConfig, modelType } = await this.getModelConfig(modelId);

if (modelType !== ModelType.INSTRUCT) {
throw new Error(`Model type ${modelType} is not supported for text completion`);
if (modelType === ModelType.CHAT) {
Logger.warn(`Model ${modelId} is not of type INSTRUCT, but ${modelType} - using it anyway`);
} else {
throw new Error(`Model type ${modelType} is not supported for text completion`);
}
}

const updatedParams = {
Expand Down Expand Up @@ -172,14 +276,4 @@ export class LMStudioRouteManager {
}
}, `embedding-${modelId}`);
}

// Additional utility methods
async shutdown() {
try {
await this.lmStudioManager.unloadAllModels();
} catch (error) {
Logger.error('Error during shutdown:', error);
throw error;
}
}
}
9 changes: 9 additions & 0 deletions backend/src/routes/lmStudio.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import { CreateEmbeddingParams } from '../lmStudioManager/lmStudio.utils';

const router: Router = express.Router();
const lmStudioManager = new LMStudioRouteManager();
// Initialize LMStudioRouteManager
(async () => {
try {
await lmStudioManager.initialize();
} catch (error) {
Logger.error('Failed to initialize LMStudioRouteManager:', error);
process.exit(1);
}
})();

// Error handling helper
const handleErrors = (res: Response, error: any) => {
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ services:
condition: service_healthy
workflow:
condition: service_healthy
volume-init:
condition: service_completed_successfully
volumes:
- ./frontend:/app:consistent
- /app/node_modules
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ services:
condition: service_healthy
workflow:
condition: service_healthy
volume-init:
condition: service_completed_successfully
environment:
NODE_ENV: ${NODE_ENV:-production}
PORT: ${FRONTEND_PORT_DOCKER:-4000}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useCallback, useState } from 'react';
import { Box, Typography, Select, MenuItem, Alert, IconButton, Tooltip, FormControl, InputLabel } from '@mui/material';
import { Box, Typography, Select, MenuItem, Alert, IconButton, Tooltip, FormControl } from '@mui/material';
import { AliceTask, RouteMap, TasksEndCodeRouting, TaskType } from '../../../../types/TaskTypes';
import RouteMapView from './RouteMapView';
import useStyles from './RoutingStyles';
Expand Down
55 changes: 27 additions & 28 deletions frontend/src/components/enhanced/prompt/PromptParsedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ interface PromptParsedViewProps {
onSystemChange?: (inputs: Record<string, any>) => void;
}

const PromptParsedView = ({
prompt,
systemPrompt,
initialInputs = {},
const PromptParsedView = ({
prompt,
systemPrompt,
initialInputs = {},
initialSystemInputs = {},
onChange,
onSystemChange
onSystemChange
}: PromptParsedViewProps) => {
const [inputs, setInputs] = useState<Record<string, any>>(initialInputs);
const [systemInputs, setSystemInputs] = useState<Record<string, any>>(initialSystemInputs);
Expand Down Expand Up @@ -58,29 +58,28 @@ const PromptParsedView = ({
}
};

const updateRenderedContent = () => {
try {
const rendered = renderTemplate(prompt.content, inputs, prompt.parameters);
setRenderedContent(rendered);
setError(null);
onChange?.(inputs);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error rendering template');
}

if (systemPrompt?.content) {
useEffect(() => {
const updateRenderedContent = () => {
try {
const renderedSystem = renderTemplate(systemPrompt.content, systemInputs, systemPrompt.parameters);
setRenderedSystemContent(renderedSystem);
setSystemError(null);
onSystemChange?.(systemInputs);
const rendered = renderTemplate(prompt.content, inputs, prompt.parameters);
setRenderedContent(rendered);
setError(null);
onChange?.(inputs);
} catch (err) {
setSystemError(err instanceof Error ? err.message : 'Error rendering system template');
setError(err instanceof Error ? err.message : 'Error rendering template');
}
}
};

useEffect(() => {
if (systemPrompt?.content) {
try {
const renderedSystem = renderTemplate(systemPrompt.content, systemInputs, systemPrompt.parameters);
setRenderedSystemContent(renderedSystem);
setSystemError(null);
onSystemChange?.(systemInputs);
} catch (err) {
setSystemError(err instanceof Error ? err.message : 'Error rendering system template');
}
}
};
if (prompt.content) {
updateRenderedContent();
}
Expand Down Expand Up @@ -153,9 +152,9 @@ const PromptParsedView = ({
<Card variant="outlined" className="p-4 bg-gray-50">
{systemPrompt && renderedSystemContent && (
<Box className="mb-1">
<AliceMarkdown role={RoleType.SYSTEM}>
{renderedSystemContent}
</AliceMarkdown>
<AliceMarkdown role={RoleType.SYSTEM}>
{renderedSystemContent}
</AliceMarkdown>
</Box>
)}
<AliceMarkdown role={RoleType.USER}>
Expand All @@ -180,7 +179,7 @@ const PromptParsedView = ({
</AccordionDetails>
</Accordion>
)}

<Accordion>
<AccordionSummary
expandIcon={<ExpandMore />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const TaskFlexibleView: React.FC<TaskComponentProps> = ({
setForm(defaultForm);
onChange(defaultForm);
}
// eslint-disable-next-line
}, [item?.task_type, taskType, onChange]);

const handleFieldChange = useCallback((field: keyof AliceTask, value: any) => {
Expand Down Expand Up @@ -210,6 +211,7 @@ const TaskFlexibleView: React.FC<TaskComponentProps> = ({
start_node: form.start_node,
node_end_code_routing: form.node_end_code_routing,
tasks: form.tasks
// eslint-disable-next-line
}), [form.node_end_code_routing, form.tasks, form.start_node]);

if (!form || Object.keys(form).length === 0) {
Expand Down
Loading

0 comments on commit 1756785

Please sign in to comment.