【Modelground】个人AI产品MVP迭代平台(3)——工程化架构设计

news2024/10/7 2:24:12

文章目录

  • 背景
  • monorepo
  • 多项目调试/打包
  • 公共静态资源服务
  • 公共模型拷贝入项目的public文件夹
  • 总结

背景

Modelground中的项目,基本都依赖Mediapipe模型,因此,有很强的需要对Mediapipe进行封装,其余项目都调用这个封装库。从架构上,这种结构的项目很容易联想到Monorepo,即多项目管理。现代包管理器对monorepo形式的仓库已有较好的支持,例如yarn、lerna等。Modelground采用的是其中的一种:pnpm

架构示意图如下:

架构示意图

monorepo

首先全局安装pnpm

npm i pnpm -g

项目初始化

pnpm init

创建pnpm-workspace.yaml,定义包目录

packages:
  - 'packages/**'

创建packages文件夹,添加第项目A和项目B

mkdir packages
cd packages
pnpm create vite A
pnpm create vite B

此时,packages中会出现名称为A和B的两个项目文件夹。

如果项目B要依赖A:

pnpm add A --filter B

此时,B项目的packages.json如下:

{
	"dependencies": {
    	"A": "workspace:^",
  },
}

这样,B打包时,A包不需要发布成npm包,B就可以将A一同打包进dist。
同理,需要给B项目添加某个依赖包C,也是如下代码:

pnpm add C --filter B

最后,统一安装整个项目的包

pnpm i

多项目调试/打包

Modelground中的项目有依赖关系,例如B依赖A,有时候我们会同时修改A和B项目的代码,如果每次都要手动启动两个项目,步骤过于繁琐,因此强需求一套自动化代码去调试某个项目前,自动开启其依赖项目。

我们首先需要一个终端的命令行选项,根目录安装inquirer

pnpm add inquirer --D -i

其次,在js文件中更方便地执行shell,需要安装execa

pnpm add execa --D -i

命令行写法:

inquirer
      .prompt([
        {
          type: "list",
          message: `选择要启动的项目:`,
          name: "mono",    // 存储答案的字段
          default: 'home',   // 默认启动项
          choices: ['home', 'fitness-count', 'ml-video', 'shooter-game', 'generate-ai'], // 想启动的项目名列表
        }
      ]).then({mono: prd} => {
      	  console.log(prd)
		  // 选择启动的项目名,例如"home"
	  })

在这里插入图片描述
启动项目的代码:

const projectServer = execa('pnpm', ['--F', prd, 'run', 'dev'], { stdio: 'pipe' }); // 等价 $pnpm --F prd run dev
projectServer.stdout.on('data', (data) => { console.log(data) }); // 监听运行输出
projectServer.stderr.on('data', (data) => { console.error(data) }); // 监听报错输出

如何在项目A启动完成后,再启动B?
一种解法是在A项目启动后,监听stdout中的输出信息,如果出现"built in",就启动B,代码如下:

let hasRun = false;
const A = execa('pnpm', ['--F', 'A', 'run', 'dev'], { stdio: 'pipe' });
A.stdout.on('data', (data) => {
        console.log(data)
        // A运行起来后,再运行当前启动项目,仅运行一次
        if (data.includes('built in') && !hasRun) {
          hasRun = true;
          const B = execa('pnpm', ['--F', 'B', 'run', 'dev'], { stdio: 'pipe' });
          B.stdout.on('data', (data) => { console.log(data) });
          B.stderr.on('data', (data) => { console.error(data) });
        }
      });
modelServer.stderr.on('data', (data) => { console.error(data) });

如果想区分不同项目的输出信息,可以安装一个chalk,可以用调整输出文字的颜色:

// 当前项目用绿色加粗
function projectTitle() {
  return chalk.green.bold('当前项目服务:');
}

// 公共静态文件用黄色加粗
function publicTitle() {
  return chalk.yellow.bold('公共模型服务:');
}

// 模型依赖用蓝色加粗
function modelTitle() {
  return chalk.blue.bold('mediapipe模型服务:');
}

// stdOut用白色
function stdOut(data) {
  return chalk.white(data);
}

// stdErr用红色
function stdErr(data) {
  return chalk.red(data);
}

// 改造上述代码
A.stdout.on('data', (data) => { console.log(projectTitle(), stdOut(data)) });
A.stderr.on('data', (data) => { console.log(projectTitle(), stdErr(data)) });

效果图:
在这里插入图片描述
同理,也可以实现多项目打包代码同步远程仓库,这里就不赘述。

公共静态资源服务

Mediapipe模型所需的预训练模型体积相对较大,由于内部采用fetch方法去请求预训练模型,因此没法放在公共依赖包中,只能放在项目的public下。但是如果每个项目都去存放一些模型,往往有重复问题,因此强需求一个公共静态资源服务,将所有的预训练模型提取到一个公共目录下。

在packages下创建一个public-assets文件夹,将公共模型都放入该文件夹下。
在这里插入图片描述
创建一个server.js,写一个简单的node文件服务。

// 引入http模块
const http = require('http');
// 引入fs模块
const fs = require('fs');
// 引入path模块
const path = require('path');

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  // 构建请求的文件路径
  const filePath = path.join(__dirname, '/', req.url === '/' ? 'index.html' : req.url);
  // 检查文件是否存在
  fs.exists(filePath, (exist) => {
    if (!exist) {
      // 如果文件不存在,返回404
      res.writeHead(404, { 'Content-Type': 'text/html' });
      res.end('404 Not Found');
      return;
    }

    // 读取文件内容
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(500, { 'Content-Type': 'text/html' });
        res.end('500 Internal Server Error');
      } else {
        // 设置响应头
        const extname = path.extname(filePath);
        let contentType = 'text/plain';
        if (extname === '.task') {
          contentType = 'application/octet-stream'
        } else if (extname === '.wasm') {
          contentType = 'application/wasm';
        } else if (extname === '.tflite') {
          contentType = 'application/octet-stream';
        }
        res.writeHead(200, {
        	'Content-Type': contentType,
        	"access-control-allow-origin": "*", // 解决不同端口跨域问题
        });
        res.end(content, 'utf-8');
      }
    });
  });
});

// 设置监听端口
const port = 5180;
server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/`);
});

通过 node server.js 就可以开启该文件服务。

我们在每个项目中新建环境变量文件.env.development

VITE_MODEL_PATH=http://localhost:5180/

在实际请求模型时,通过vite的环境变量就可以取到该变量import.meta.env.VITE_MODEL_PATH

同理,在正式环境时,模型的请求地址就变成了项目的路由,如果是根路由,设定.env.production

VITE_MODEL_PATH=/

这样,不同环境下就能正常获取模型文件。

公共模型拷贝入项目的public文件夹

简单说,就是利用shell的cp命令,拷贝文件,直接上build代码:

import inquirer from "inquirer";
import { execaCommand } from "execa";

// 各项目所需的模型文件
const copyConfig = {
  'shooter-game': {
    'packages/public-assets/wasm/vision_wasm_internal.js': 'packages/shooter-game/dist/wasm',
    'packages/public-assets/wasm/vision_wasm_internal.wasm': 'packages/shooter-game/dist/wasm',
    'packages/public-assets/models/ObjectDetection/rim_ball_model_v1.tflite': 'packages/shooter-game/dist/models/ObjectDetection'
  },
  'ml-video': {
    "packages/public-assets/models/ObjectDetection/rim_ball_model_v1.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/ObjectDetection/efficientdet_lite0.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/ObjectDetection/efficientdet_lite2.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/PoseLandMarker/pose_landmarker_full.task": 'packages/ml-video/dist/models/PoseLandMarker',
    "packages/public-assets/models/PoseLandMarker/pose_landmarker_lite.task": 'packages/ml-video/dist/models/PoseLandMarker',
    "packages/public-assets/models/HandLandMarker/hand_landmarker.task": 'packages/ml-video/dist/models/HandLandMarker',
    "packages/public-assets/models/FaceLandMarker/face_landmarker.task": 'packages/ml-video/dist/models/FaceLandMarker',
    "packages/public-assets/wasm/vision_wasm_internal.js": 'packages/ml-video/dist/wasm',
    "packages/public-assets/wasm/vision_wasm_internal.wasm": 'packages/ml-video/dist/wasm'
  }
}

async function run() {
  try {
    const { mono: prd } = await inquirer
      .prompt([
        {
          type: "list",
          message: `选择要构建的项目:`,
          name: "mono",    // 存储答案的字段
          default: 'home',   // 默认启动项
          choices: ['home', 'fitness-count', 'mediapipe-model-core', 'ml-video', 'shooter-game', 'generate-ai'],
        }
      ]);
    // 先打包,有了dist文件夹再拷贝文件
    let result = await execaCommand(`pnpm --filter ${prd} run build`, { stdio: "inherit" });
    
    const copy = copyConfig[prd];
    if (!copy) return;
    
    const pa = Object.entries(copy);
    for (let i = 0; i < pa.length; i++) {
      const from = pa[i][0]; // 待拷贝的文件
      const to = pa[i][1]; // 目标文件夹
      await execaCommand(`mkdir -p ${to}`); // 如果没有该文件夹,先新建
      await execaCommand(`cp ${from} ${to}`); // 执行拷贝
    }
  } catch (err) {
    console.error(err);
  }
}

run();

总结

这套架构是我在开发Modelground过程中,逐渐摸索出来的比较成熟的架构。很多坑都是过程中发现并解决,并不是一开始就能考虑到的。

总结而言,依赖monorepo多项目管理模式,实现项目依赖,并行开发。通过流水线模式,简化项目启动流程。通过公共模型服务,减少冗余静态文件复制动作,在打包时统一拷贝。

以上,就是Modelground的工程化架构设计内容,极大减少了本人开发耗时,可以将精力集中在构思创意上。

欢迎访问Modelground体验已有模型https://tryiscool.space

在这里插入图片描述
如果本文对你有帮助,希望能得到你的三连+订阅Modelground专栏,鼓励我持续产出,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1796082.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文字生成视频!又一王炸!!!(且免费使用!)

VIVA王炸 开场 “ 生成令人惊叹的AI视频&#xff0c;再加上4K视频增强和初学者友好的自动提示优化&#xff0c;为您提供无与伦比的视频创作体验。” 直抒胸臆 自从sora的出现&#xff0c;开启了人工智能的有一个阶段。VIVA是现在唯数不多的与OpenAI的sora互相抗衡。也是为数…

Orange Pi AI Pro 开箱 记录

香橙派 AIpro&#xff08;OrangePi AIpro&#xff09;是一款面向AI开发的强大开发板&#xff0c;提供了高性能和多功能的开发环境。我将结合自己的开发经验&#xff0c;详细介绍这款开发板的性能、适用场景及使用体验。 一、产品概述 香橙派 AIpro配备了强大的硬件配置&#…

String类知识

目录 一、String存在意义 二、字符串为何不可变 三、String类常用方法 1、字符串构造 2、String对象的比较 3、字符串查找 4、转化 &#xff08;1&#xff09;数值和字符转化 &#xff08;2&#xff09;大小写转换 &#xff08;3&#xff09;字符串转数组 &#xff08;4&…

不同类型红酒的保存期限与品质变化

云仓酒庄雷盛红酒&#xff0c;以其多样的品种和与众不同的风味吸引了无数葡萄酒爱好者。然而&#xff0c;不同类型和风格的红酒在保存过程中&#xff0c;其期限和品质变化也各不相同。本文将深入探讨这个问题&#xff0c;以帮助消费者更好地理解和欣赏云仓酒庄雷盛红酒的多样性…

智慧互联网医院系统开发指南:从源码到在线问诊APP

近期&#xff0c;互联网医院系统的热度非常高&#xff0c;很多人跟小编提问如何开发&#xff0c;今天小编将从零开始为大家详解互联网医院系统源码&#xff0c;以及在线问诊APP开发技术。 一、需求分析与系统设计 1.1 需求分析 用户管理 预约挂号 在线问诊 电子病历 药品…

0606 作业

#include <stdio.h> #include <string.h>typedef struct usr{char unm[21];char pwd[21]; }user;int main(int argc, const char *argv[]) {FILE* userfilefopen("./user_tible.txt","r");printf("输入username:");user u;scanf(&qu…

主流的单片机语言是 C 吗?是的话为啥不是 C++?

是c&#xff0c;而且可以预见在很长很长一段时间&#xff0c;没有巨大变革的情况下都会是c 商业项目开发光讨论语言特性优劣问题&#xff0c;是非常片面的&#xff0c;所以要看待为什么是c&#xff0c;最主要仍然是从收益和成本上来看。 刚好我有一些资料&#xff0c;是我根据…

【postgresql初级使用】初识触发器,在数据行发生变化时自动执行用户行为,也可以SQL级别触发,特别是视图上可以有触发器了

初识触发器 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 初识触发器概…

深入解析手机信息恢复,2个技巧,做数据安全守护者

在现代社会&#xff0c;手机就像我们的第六感一样&#xff0c;随时能够帮助我们搞定难题。但是&#xff0c;有时候手机也会闹个小脾气&#xff0c;比如误删信息、系统崩溃和硬件故障等&#xff0c;这些问题可了不得&#xff01;它们会让我们无法访问那些重要的数据&#xff0c;…

【机器学习】逻辑回归:原理、应用与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 逻辑回归&#xff1a;原理、应用与实践引言1. 逻辑回归基础1.1 基本概念1.2 Sig…

Mysql8安装教程与配置(超详细图文)

MySQL 8.0 是 MySQL 数据库的一个重大更新版本&#xff0c;它引入了许多新特性和改进&#xff0c;旨在提高性能、安全性和易用性。 1.下载MySQL 安装包 注&#xff1a;本文使用的是压缩版进行安装。 &#xff08;1&#xff09;从网盘下载安装文件 点击此处直接下载 &#…

网络层-IP协议 二

一、网段划分 为了进行组网,把一个IP地址,分成了两个部分: 网络号 主机号 例如:192.168.2.100 这个IP地址中,前面一部分 : 192.168.2就是我们的网络号 后面一部分 100就是我们的主机号. 家用宽带来说,一般默认就是前面三个字节是网络号,主机号的范围就表示局域网中可以有…

开机弹窗找不到opencl.dll怎么办,教你几种有效的修复方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到opencl.dll文件”。这个问题可能会影响到我们的正常使用&#xff0c;因此了解其原因和解决方法是非常必要的。本文将从多个方面对“找不到opencl.dll文件”这一问题进行详细分析和解…

某h5st逆向分析

具体网址经过了base64处理 aHR0cHM6Ly9zby5tLmpkLmNvbS93YXJlL3NlYXJjaC5hY3Rpb24/a2V5d29yZD0lRTklOTklQTQlRTYlQjklQkYlRTYlOUMlQkEmc2VhcmNoRnJvbT1ob21lJnNmPTE1JmFzPTA 要做的是一个搜索的功能具体如图所示。 这里发现携带的参数中存在一个token还有一个加密参数&#x…

【网络安全的神秘世界】Kali火狐浏览器汉化教程

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 打开火狐浏览器 进入设置后&#xff0c;搜索language 打开之后选择添加其他语言&#xff0c;简体中文在最后一个 Add后点击ok即可

代码签名证书:软件安全的守护神

在数字化日益普及的今天&#xff0c;软件安全问题愈发受到人们的关注。而在这其中&#xff0c;一个常被提及但可能不为大众所熟知的名词——“代码签名证书”&#xff0c;实际上在软件安全领域扮演着举足轻重的角色。今天&#xff0c;我们就来聊聊代码签名证书对软件安全到底有…

SVM模型实现城镇居民月平均消费数据分类

SVM模型实现城镇居民月平均消费数据分类 一、SVM支持向量机简介二、数据集介绍三、SVM建模流程及分析一、SVM支持向量机简介 支持向量机是由感知机发展而来的机器学习算法,属于监督学习算法。支持向量机具有完备的理论基础,算法通过对样本进行求解,得到最大边距的超平面,并…

485数据采集模块

在工业自动化与智能化的浪潮中&#xff0c;数据采集作为整个系统的基础和核心&#xff0c;其准确性和实时性直接关系到生产效率和产品质量。而485数据采集模块&#xff0c;作为连接现场设备与上位机的重要桥梁&#xff0c;其性能与稳定性对于整个系统的运行至关重要。HiWoo Box…

浪潮电脑文件消失怎么恢复?原来有这五种方法

无论是工作、学习还是娱乐&#xff0c;电脑都扮演着举足轻重的角色。然而&#xff0c;在使用电脑的过程中&#xff0c;我们有时会遇到一些令人头疼的问题&#xff0c;比如文件突然消失。对于使用浪潮电脑的用户来说&#xff0c;文件消失可能是一个令人焦虑的问题。本文将为您详…

如何通俗易懂地理解大模型参数?

大型语言模型 (LLM) 的大小是通过参数数量来衡量的。举几个典型例子&#xff0c;GPT-3 有 1750 亿个参数&#xff0c;1750亿也可称为175B&#xff08;1B 10亿&#xff09;&#xff0c;Meta最新开源的Llama3 参数数量在 80 亿到 700 亿之间&#xff0c;智谱公司最新开源的GLM4-…