Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): Backend config convention and pull model #54

Merged
merged 25 commits into from
Dec 16, 2024

Conversation

NarwhalChen
Copy link
Collaborator

@NarwhalChen NarwhalChen commented Nov 18, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a configuration management system for chat settings.
    • Added functionality to download and manage machine learning models.
    • Implemented a method to download multiple models based on configurations.
    • Enhanced dependency management with new libraries for improved functionality.
  • Bug Fixes

    • Updated dependencies to enhance functionality and improve data handling.
  • Tests

    • Added comprehensive tests for loading chat models and verifying their functionality.

@NarwhalChen NarwhalChen requested a review from Sma1lboy November 18, 2024 04:30
Copy link

coderabbitai bot commented Nov 18, 2024

Caution

Review failed

The pull request is closed.

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

backend/src/config/common-path.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-prettier".

(The package "eslint-plugin-prettier" was not found when loaded as a Node module from the directory "/backend".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-prettier@latest --save-dev

The plugin "eslint-plugin-prettier" was referenced from the config file in "backend/.eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

Walkthrough

This pull request introduces a comprehensive enhancement to the backend's model management system by adding new dependencies and implementing a robust configuration and model downloading mechanism. The changes include integrating Hugging Face libraries, Axios, and Lodash, creating a ConfigLoader for managing chat configurations, and developing a ModelDownloader to handle machine learning model retrieval and local storage. The modifications aim to streamline model loading and configuration management for the application.

Changes

File Change Summary
backend/package.json Added dependencies: @huggingface/hub, @huggingface/transformers, axios, and lodash
backend/src/config/common-path.ts Updated import statements from default to namespace imports for path and os modules
backend/src/config/config-loader.ts New file implementing ChatConfig interface and ConfigLoader class for configuration management
backend/src/main.ts Added downloadAllModels function call during bootstrap
backend/src/model/__tests__/app.e2e-spec.ts Updated import path for AppModule
backend/src/model/__tests__/loadAllChatsModels.spec.ts New test file for loading chat models
backend/src/model/model-downloader.ts New file implementing ModelDownloader singleton for model downloading
backend/src/model/utils.ts Added downloadAllModels function to download models based on configuration

Sequence Diagram

sequenceDiagram
    participant Main as Main Application
    participant ConfigLoader as Config Loader
    participant ModelDownloader as Model Downloader
    participant HuggingFace as Hugging Face

    Main->>ConfigLoader: Load Chat Configurations
    ConfigLoader-->>Main: Return Configuration
    Main->>ModelDownloader: Initialize Downloader
    ModelDownloader->>HuggingFace: Download Models
    HuggingFace-->>ModelDownloader: Return Downloaded Models
    ModelDownloader-->>Main: Models Ready
Loading

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • ZHallen122

Poem

🐰 Hop, hop, models download with glee,
Config loader dancing, setting us free!
Hugging Face magic, transformers so bright,
Backend evolving with computational might!
A rabbit's code journey, smooth and serene 🚀

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 471e9d2 and 4e59695.

⛔ Files ignored due to path filters (2)
  • backend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • backend/package.json (2 hunks)
  • backend/src/config/common-path.ts (1 hunks)
  • backend/src/config/config-loader.ts (1 hunks)
  • backend/src/main.ts (2 hunks)
  • backend/src/model/__tests__/app.e2e-spec.ts (1 hunks)
  • backend/src/model/__tests__/loadAllChatsModels.spec.ts (1 hunks)
  • backend/src/model/model-downloader.ts (1 hunks)
  • backend/src/model/utils.ts (1 hunks)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

backend/src/model/model.service.ts Outdated Show resolved Hide resolved
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name of this file should be something different, it's not file relate to nestjs, we only do nestjs convention when we doing something relate to nestjs

change to ConfigLoader also with default-config-type.d.ts

backend/src/model/model.service.ts Outdated Show resolved Hide resolved
@Sma1lboy Sma1lboy changed the title Backend config convention feat(backend): Backend config convention and pull model Nov 28, 2024
backend/src/config/ConfigLoader.ts Outdated Show resolved Hide resolved
backend/src/model/ModelDownloader.ts Outdated Show resolved Hide resolved
@Sma1lboy Sma1lboy marked this pull request as ready for review December 16, 2024 05:27
@Sma1lboy Sma1lboy merged commit cf95728 into main Dec 16, 2024
0 of 2 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (5)
backend/src/main.ts (1)

20-21: Consider graceful startup handling

The current implementation blocks server startup until all models are downloaded. This could lead to:

  1. Long startup times affecting deployment and scaling
  2. No way to handle partial model download failures

Consider:

  • Implementing a health check endpoint that reports model download status
  • Starting the server immediately and downloading models in the background
  • Adding a retry mechanism for failed model downloads
backend/src/model/model-downloader.ts (2)

16-21: Add model management and validation

The downloadModel method needs improvements in several areas:

  1. Memory management for loaded models
  2. Validation of task types
  3. Error handling for invalid models

Consider:

  • Implementing a model cache with size limits
  • Adding task type validation
  • Implementing proper cleanup for unused models
  • Adding timeout handling for downloads

Would you like me to provide a detailed implementation for any of these improvements?


23-30: Enhance local model retrieval with validation

The getLocalModel method should validate the existence of local models before attempting to load them.

Consider adding validation:

 public async getLocalModel(task: string, model: string): Promise<any> {
+  const modelPath = getModelPath(model);
+  if (!fs.existsSync(modelPath)) {
+    throw new Error(`Local model not found: ${model}`);
+  }
   const pipelineInstance = await pipeline(task as PipelineType, model, {
     local_files_only: true,
     revision: 'local',
   });
   return pipelineInstance;
 }
backend/src/config/config-loader.ts (1)

5-11: Add JSDoc documentation and consider required fields

The interface would benefit from documentation explaining the purpose of each field. Additionally, consider making the task field required since it appears to be essential for model loading based on the test file.

+/**
+ * Configuration interface for chat models
+ */
 export interface ChatConfig {
   model: string;
   endpoint?: string;
   token?: string;
   default?: boolean;
-  task?: string;
+  task: string;  // Required for model loading
 }
backend/src/model/__tests__/loadAllChatsModels.spec.ts (1)

22-36: Remove commented-out code

Remove the commented-out mock implementation as it's no longer needed and clutters the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 326e884 and c1c8a98.

⛔ Files ignored due to path filters (2)
  • backend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • backend/package.json (2 hunks)
  • backend/src/config/common-path.ts (1 hunks)
  • backend/src/config/config-loader.ts (1 hunks)
  • backend/src/main.ts (2 hunks)
  • backend/src/model/__tests__/app.e2e-spec.ts (1 hunks)
  • backend/src/model/__tests__/loadAllChatsModels.spec.ts (1 hunks)
  • backend/src/model/model-downloader.ts (1 hunks)
  • backend/src/model/utils.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • backend/src/model/tests/app.e2e-spec.ts
  • backend/src/config/common-path.ts
🧰 Additional context used
🪛 Biome (1.9.4)
backend/src/model/__tests__/loadAllChatsModels.spec.ts

[error] 12-13: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (2)
backend/src/model/utils.ts (1)

7-7: Verify the empty configuration path

The empty string path in configLoader.get<ChatConfig[]>('') seems suspicious. This might be a mistake or needs documentation explaining why an empty path is valid.

backend/src/model/__tests__/loadAllChatsModels.spec.ts (1)

52-52: Review test timeouts and consider performance optimization

The test has a very long timeout (600000ms = 10 minutes). This could indicate performance issues with model loading.

Let's analyze the model loading time:

Consider:

  1. Implementing model caching
  2. Loading models asynchronously during application startup
  3. Adding progress indicators for better test feedback

Also applies to: 85-85

✅ Verification successful

Long test timeout is justified for model download and inference

The 10-minute timeout is appropriate in this context as the test:

  1. Downloads an ML model (downloadAllModels())
  2. Loads the model into memory
  3. Performs actual inference with the model to generate text

These operations are inherently time-consuming for ML models, and the timeout isn't indicative of a performance issue but rather the nature of the test which performs real model operations. The test also includes proper error handling and logging to help diagnose any issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Measure model loading time
time node -e "
const { ModelDownloader } = require('./dist/model/model-downloader');
const downloader = ModelDownloader.getInstance();
(async () => {
  console.time('modelLoad');
  await downloader.getLocalModel(
    'text2text-generation',
    'Felladrin/onnx-flan-alpaca-base'
  );
  console.timeEnd('modelLoad');
})()
"

Length of output: 1179


Script:

#!/bin/bash
# Let's examine the test file and its context to understand the long timeout
cat backend/src/model/__tests__/loadAllChatsModels.spec.ts

Length of output: 2640

Comment on lines +10 to +18
const loadPromises = chats.map(async (chatConfig: ChatConfig) => {
const { model, task } = chatConfig;
try {
downloader.logger.log(model, task);
const pipelineInstance = await downloader.downloadModel(task, model);
} catch (error) {
downloader.logger.error(`Failed to load model ${model}:`, error.message);
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and type safety improvements

Several issues need attention in the model loading logic:

  1. The unused pipelineInstance variable
  2. No type safety for the task parameter
  3. Errors are caught but the function continues silently

Consider this improved implementation:

- const loadPromises = chats.map(async (chatConfig: ChatConfig) => {
+ type SupportedTask = 'text-generation' | 'sentiment-analysis'; // add all supported tasks
+ const loadPromises = chats.map(async (chatConfig: ChatConfig) => {
    const { model, task } = chatConfig;
+   if (!isValidTask(task)) {
+     throw new Error(`Unsupported task: ${task}`);
+   }
    try {
      downloader.logger.log(model, task);
-     const pipelineInstance = await downloader.downloadModel(task, model);
+     await downloader.downloadModel(task as SupportedTask, model);
    } catch (error) {
      downloader.logger.error(`Failed to load model ${model}:`, error.message);
+     throw error; // re-throw to indicate failure
    }
  });
- await Promise.all(loadPromises);
+ const results = await Promise.allSettled(loadPromises);
+ const failures = results.filter(r => r.status === 'rejected');
+ if (failures.length > 0) {
+   throw new Error(`Failed to load ${failures.length} models`);
+ }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +9 to +14
public static getInstance(): ModelDownloader {
if (!ModelDownloader.instance) {
ModelDownloader.instance = new ModelDownloader();
}
return ModelDownloader.instance;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve singleton implementation for thread safety

The current singleton implementation might have race conditions in a concurrent environment.

Consider using a more robust singleton pattern:

- private static instance: ModelDownloader;
- public static getInstance(): ModelDownloader {
-   if (!ModelDownloader.instance) {
-     ModelDownloader.instance = new ModelDownloader();
-   }
-   return ModelDownloader.instance;
- }
+ private static instance: ModelDownloader | null = null;
+ private static instanceLock = false;
+ public static getInstance(): ModelDownloader {
+   if (!ModelDownloader.instance) {
+     if (ModelDownloader.instanceLock) {
+       throw new Error('Instance creation in progress');
+     }
+     ModelDownloader.instanceLock = true;
+     try {
+       ModelDownloader.instance = new ModelDownloader();
+     } finally {
+       ModelDownloader.instanceLock = false;
+     }
+   }
+   return ModelDownloader.instance;
+ }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +50 to +54
validateConfig() {
if (!this.chatsConfig) {
throw new Error("Invalid configuration: 'chats' section is missing.");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance configuration validation

The current validation only checks if chatsConfig exists. Consider adding more comprehensive validation.

 validateConfig() {
   if (!this.chatsConfig) {
     throw new Error("Invalid configuration: 'chats' section is missing.");
   }
+  if (!Array.isArray(this.chatsConfig)) {
+    throw new Error("Invalid configuration: 'chats' must be an array.");
+  }
+  for (const config of this.chatsConfig) {
+    if (!config.model) {
+      throw new Error("Invalid configuration: 'model' is required.");
+    }
+    if (!config.task) {
+      throw new Error("Invalid configuration: 'task' is required.");
+    }
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validateConfig() {
if (!this.chatsConfig) {
throw new Error("Invalid configuration: 'chats' section is missing.");
}
}
validateConfig() {
if (!this.chatsConfig) {
throw new Error("Invalid configuration: 'chats' section is missing.");
}
if (!Array.isArray(this.chatsConfig)) {
throw new Error("Invalid configuration: 'chats' must be an array.");
}
for (const config of this.chatsConfig) {
if (!config.model) {
throw new Error("Invalid configuration: 'model' is required.");
}
if (!config.task) {
throw new Error("Invalid configuration: 'task' is required.");
}
}
}

Comment on lines +30 to +35
get<T>(path: string) {
if (!path) {
return this.chatsConfig as unknown as T;
}
return _.get(this.chatsConfig, path) as T;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety in get method

The generic get method could be made more type-safe by constraining the return type based on the path.

- get<T>(path: string) {
+ get<T extends keyof ChatConfig>(path: T): ChatConfig[T];
+ get<T>(path: string): T;
  if (!path) {
-   return this.chatsConfig as unknown as T;
+   return this.chatsConfig as T;
  }
  return _.get(this.chatsConfig, path) as T;
}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +23 to +28
private loadConfig() {
const file = fs.readFileSync(this.configPath, 'utf-8');

this.chatsConfig = JSON.parse(file);
console.log('Raw file content:', this.chatsConfig);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and consider async file operations

The loadConfig method uses synchronous file operations and lacks error handling. This could cause issues in production.

- private loadConfig() {
+ private loadConfig(): void {
+   try {
      const file = fs.readFileSync(this.configPath, 'utf-8');
      this.chatsConfig = JSON.parse(file);
      console.log('Raw file content:', this.chatsConfig);
+   } catch (error) {
+     console.error('Failed to load config:', error);
+     this.chatsConfig = [];
+     throw new Error(`Failed to load configuration: ${error.message}`);
+   }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private loadConfig() {
const file = fs.readFileSync(this.configPath, 'utf-8');
this.chatsConfig = JSON.parse(file);
console.log('Raw file content:', this.chatsConfig);
}
private loadConfig(): void {
try {
const file = fs.readFileSync(this.configPath, 'utf-8');
this.chatsConfig = JSON.parse(file);
console.log('Raw file content:', this.chatsConfig);
} catch (error) {
console.error('Failed to load config:', error);
this.chatsConfig = [];
throw new Error(`Failed to load configuration: ${error.message}`);
}
}

Comment on lines +29 to +30
"@huggingface/hub": "latest",
"@huggingface/transformers": "latest",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Pin Hugging Face dependencies to specific versions

Using "latest" for production dependencies is risky as it can lead to unexpected breaking changes. Consider pinning to specific versions.

- "@huggingface/hub": "latest",
- "@huggingface/transformers": "latest",
+ "@huggingface/hub": "^0.14.1",
+ "@huggingface/transformers": "^2.15.0",

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +8 to +20
const originalIsArray = Array.isArray;

Array.isArray = jest.fn((type: any): type is any[] => {
if (
type &&
type.constructor &&
(type.constructor.name === 'Float32Array' ||
type.constructor.name === 'BigInt64Array')
) {
return true;
}
return originalIsArray(type);
}) as unknown as (arg: any) => arg is any[];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid modifying global Array.isArray

Modifying the global Array.isArray is risky and could affect other tests. Consider using a more isolated approach.

- const originalIsArray = Array.isArray;
- Array.isArray = jest.fn((type: any): type is any[] => {
+ const isCustomArray = (type: any): type is any[] => {
   if (
     type &&
     type.constructor &&
     (type.constructor.name === 'Float32Array' ||
       type.constructor.name === 'BigInt64Array')
   ) {
     return true;
   }
-  return originalIsArray(type);
+  return Array.isArray(type);
- }) as unknown as (arg: any) => arg is any[];
+ };

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 12-13: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants