diff --git a/README.md b/README.md index 55ca756..8ba409f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # ainize-js -A Typescript JS for the Ainize, a system for running AI services on the AI Network. +A Typescript JS for the Ainize, a system for running AI models on the AI Network. ## Requirements + node >= 16 ## usage + ### Install + ```bash npm install @ainize-team/ainize-js @@ -14,59 +17,75 @@ yarn install @ainize-team/ainize-js ``` ### Import -```typescript -import Ainize from '@ainize-team/ainize-js' -const ainize = new Ainize(); -``` CHAIN_ID + - 0: AI Network test net - 1: AI Network main net +```typescript + +import Ainize from '@ainize-team/ainize-js' +const ainize = new Ainize(); +``` + ### Login + You should login to ainize with AI Network account before deploy the container. + ```typescript ainize.login(); ``` You can also login using [AIN Wallet](https://chromewebstore.google.com/detail/ain-wallet/hbdheoebpgogdkagfojahleegjfkhkpl) on the web. + ```typescript ainize.loginWithSigner(); ``` + This feature is supported from AIN Wallet version 2.0.5 or later. -### Deploy -You can deploy your AI service to ainize. +### Using model + +You can use a model using `ainize.getModel()`. + ```typescript -const service = await ainize.deploy(); +const model = await ainize.getModel(); ``` -CONFIGURATION -- serviceName: The name you want to deploying service. -- billingConfig: Billing configuration required for service usage. - - depositAddress: The address for receiving AIN deposits. - - costPerToken: Cost per token for service usage. - - minCost: Minimum cost. - - maxCost: Maximum cost. (optional) -You can stop or run your service container. Only service deployer can use this. +You should deposit AIN to AI model for credit before using model. + ```typescript -service.stop(); -service.run(); +await model.chargeCredit(); +const balance = await model.getCreditBalance(); ``` -### Using Service -You can use a service using `ainize.getService()`. +If you have enough credit, you can use the model. + ```typescript -const service = await ainize.getService(); +const result = await model.request(); ``` -You should deposit AIN to credit before using service. +### Deploy + +You can deploy your AI model to ainize. Anyone can use your AI model with AIN token. + +CONFIGURATION(JSON) + +- modelName: The name you want to deploying model. +- billingConfig: Billing configuration required for model usage. + - depositAddress: The address for receiving AIN deposits. + - costPerToken: Cost per token for model usage. + - minCost: Minimum cost. + - maxCost: Maximum cost. (optional) + ```typescript -await service.chargeCredit(); -const balance = await service.getCreditBalance(); +const model = await ainize.deploy(); ``` -If you have enough credit, you can use the service. +You can stop or run your model container. Only model deployer can use this. + ```typescript -const result = await service.request(); -``` +model.stop(); +model.run(); +``` diff --git a/src/ainize.ts b/src/ainize.ts index 24d19bc..0fc0c5e 100644 --- a/src/ainize.ts +++ b/src/ainize.ts @@ -3,7 +3,7 @@ import Middleware from "./middlewares/middleware"; import { DEFAULT_BILLING_CONFIG, Path } from "./constants"; import Handler from "./handlers/handler"; import AppController from "./controllers/appController"; -import Service from "./service"; +import Model from "./model"; import { deployConfig } from "./types/type"; import AinModule from "./ain"; import Internal from "./internal"; @@ -79,12 +79,12 @@ export default class Ainize { // FIXME(yoojin): add config type and change param type. /** - * Deploy AI service container. - * @param {deployConfig} deployConfig Set configuration for setting container. serviceName, billingConfig, etc. - * @returns {Service} Deployed service object. + * Deploy AI model container. + * @param {deployConfig} deployConfig Set configuration for setting container. modelName, billingConfig, etc. + * @returns {Model} Deployed model object. */ // TODO(yoojin, woojae): Deploy container, advanced. - async deploy({serviceName, billingConfig, serviceUrl}: deployConfig): Promise { + async deploy({modelName, billingConfig, modelUrl}: deployConfig): Promise { // TODO(yoojin, woojae): Add container deploy logic. const result = await new Promise(async (resolve, reject) => { const deployer = await this.ain.getAddress(); @@ -94,31 +94,31 @@ export default class Ainize { depositAddress: deployer, }; } - // NOTE(yoojin): For test. We make fixed url on service. - if (!serviceUrl) { - serviceUrl = `https://${serviceName}.ainetwork.xyz`; + // NOTE(yoojin): For test. We make fixed url on model. + if (!modelUrl) { + modelUrl = `https://${modelName}.ainetwork.xyz`; } - serviceUrl = serviceUrl.replace(/\/$/, ''); - const servicePath = Path.app(serviceName).status(); - await this.handler.subscribe(servicePath, resolve); - await this.appController.createApp({ appName: serviceName, serviceUrl, billingConfig }); + modelUrl = modelUrl.replace(/\/$/, ''); + const modelPath = Path.app(modelName).status(); + await this.handler.subscribe(modelPath, resolve); + await this.appController.createApp({ appName: modelName, modelUrl, billingConfig }); }); - console.log(`${serviceName} deploy success!`); - return this.getService(serviceName); + console.log(`${modelName} deploy success!`); + return this.getModel(modelName); } /** - * Get deployed service. - * @param serviceName - * @returns {Service} Deployed service object. + * Get deployed model. + * @param modelName + * @returns {Model} Deployed model object. */ - async getService(serviceName: string): Promise { - const servicePath = Path.app(serviceName).root(); - const serviceData = await this.ain.getValue(servicePath, { is_shallow: true }); - if(!serviceData) { - throw new Error("Service not found"); + async getModel(modelName: string): Promise { + const modelPath = Path.app(modelName).root(); + const modelData = await this.ain.getValue(modelPath, { is_shallow: true }); + if(!modelData) { + throw new Error("Model not found"); } - return new Service(serviceName); + return new Model(modelName); } test() { diff --git a/src/constants.ts b/src/constants.ts index f96358f..119cc67 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -19,11 +19,11 @@ export const Path = { deposit: () => `${Path.app(appName).root()}/deposit`, depositOfUser: (userAddress: string) => `${Path.app(appName).deposit()}/${userAddress}`, billingConfig: () => `${Path.app(appName).root()}/billingConfig`, - service: () => `${Path.app(appName).root()}/service`, - userOfService: (userAddress: string) => - `${Path.app(appName).service()}/${userAddress}`, + model: () => `${Path.app(appName).root()}/model`, + userOfModel: (userAddress: string) => + `${Path.app(appName).model()}/${userAddress}`, requestKey: (userAddress: string, requestKey: string) => - `${Path.app(appName).userOfService(userAddress)}/${requestKey}`, + `${Path.app(appName).userOfModel(userAddress)}/${requestKey}`, request: (userAddress: string, requestKey: string) => `${Path.app(appName).requestKey(userAddress, requestKey)}/request`, response: (userAddress: string, requestKey: string) => diff --git a/src/controllers/appController.ts b/src/controllers/appController.ts index 09b1114..23d185f 100644 --- a/src/controllers/appController.ts +++ b/src/controllers/appController.ts @@ -15,7 +15,7 @@ export default class AppController { return AppController.instance; } - async createApp({ appName, serviceUrl, billingConfig }: createAppConfig) { + async createApp({ appName, modelUrl, billingConfig }: createAppConfig) { const setRuleOps: SetOperation[] = []; const setFunctionOps: SetOperation[] = []; const setBillingConfigOps: SetOperation[] = [] ; @@ -29,7 +29,7 @@ export default class AppController { } const depositPath = `${Path.app(appName).depositOfUser("$userAddress")}/$transferKey` - const depositUrl = `${serviceUrl}/deposit`; + const depositUrl = `${modelUrl}/deposit`; const depositParam: setTriggerFunctionParm = { ref: depositPath, function_id: "deposit-trigger", @@ -40,17 +40,17 @@ export default class AppController { const depositFuncOp = buildSetOperation("SET_FUNCTION", depositParam.ref, depositValue); setFunctionOps.push(depositFuncOp); - const serviceFuncPath = Path.app(appName).request("$userAddress", "$requestKey") - const serviceFuncUrl = `${serviceUrl}/service`; - const serviceFuncParam: setTriggerFunctionParm = { - ref: serviceFuncPath, - function_id: "service-trigger", + const modelFuncPath = Path.app(appName).request("$userAddress", "$requestKey") + const modelFuncUrl = `${modelUrl}/model`; + const modelFuncParam: setTriggerFunctionParm = { + ref: modelFuncPath, + function_id: "model-trigger", function_type: "REST", - function_url: serviceFuncUrl + function_url: modelFuncUrl } - const serviceFuncValue = this.buildSetFunctionValue(serviceFuncParam); - const serviceFuncOp = buildSetOperation("SET_FUNCTION", serviceFuncParam.ref, serviceFuncValue); - setFunctionOps.push(serviceFuncOp); + const modelFuncValue = this.buildSetFunctionValue(modelFuncParam); + const modelFuncOp = buildSetOperation("SET_FUNCTION", modelFuncParam.ref, modelFuncValue); + setFunctionOps.push(modelFuncOp); const configOp = this.buildSetAppBillingConfigOp(appName, billingConfig); setBillingConfigOps.push(configOp); diff --git a/src/controllers/serviceController.ts b/src/controllers/modelController.ts similarity index 55% rename from src/controllers/serviceController.ts rename to src/controllers/modelController.ts index c08889d..2529af7 100644 --- a/src/controllers/serviceController.ts +++ b/src/controllers/modelController.ts @@ -6,37 +6,37 @@ import { buildSetOperation, buildTxBody } from "../utils/builder"; import Handler from "../handlers/handler"; import { ContainerStatus, creditHistories } from "../types/type"; -export default class ServiceController { - private static instance: ServiceController | undefined; +export default class ModelController { + private static instance: ModelController | undefined; private ain = AinModule.getInstance(); private handler = Handler.getInstance(); static getInstance() { - if(!ServiceController.instance){ - ServiceController.instance = new ServiceController(); + if(!ModelController.instance){ + ModelController.instance = new ModelController(); } - return ServiceController.instance; + return ModelController.instance; } - async checkRunning(serviceName: string): Promise { - const isRunning = await this.isRunning(serviceName); + async checkRunning(modelName: string): Promise { + const isRunning = await this.isRunning(modelName); if (!isRunning) { - throw new Error('Service is not running.'); + throw new Error('Model is not running.'); } } - async isRunning(serviceName: string): Promise { - const runningStatus = await this.ain.getValue(Path.app(serviceName).status()); + async isRunning(modelName: string): Promise { + const runningStatus = await this.ain.getValue(Path.app(modelName).status()); return runningStatus === ContainerStatus.RUNNING ? true : false; } // TODO(woojae): implement this - async getInformation(serviceName: string): Promise { - return await 'information of service'; + async getInformation(modelName: string): Promise { + return await 'information of model'; } // FIXME(yoojin): Temporary deprecated. Need new pricing rules. - private async calculateCost(serviceName: string, requestData: string): Promise { - const billingConfig = await this.ain.getValue(Path.app(serviceName).billingConfig()); + private async calculateCost(modelName: string, requestData: string): Promise { + const billingConfig = await this.ain.getValue(Path.app(modelName).billingConfig()); const token = requestData.split(' ').length; let cost = token * billingConfig.costPerToken; if (billingConfig.minCost && cost < billingConfig.minCost) { @@ -47,45 +47,45 @@ export default class ServiceController { return cost; } - async chargeCredit(serviceName: string, amount: number): Promise { - await this.checkRunning(serviceName); + async chargeCredit(modelName: string, amount: number): Promise { + await this.checkRunning(modelName); const transferKey = Date.now(); const userAddress = await this.ain.getAddress(); - const depositAddress = await this.getDepositAddress(serviceName); + const depositAddress = await this.getDepositAddress(modelName); const op_list: SetOperation[] = [ getTransferOp(userAddress, depositAddress, transferKey.toString(), amount), - getRequestDepositOp(serviceName, userAddress, transferKey.toString(), amount) + getRequestDepositOp(modelName, userAddress, transferKey.toString(), amount) ] const txBody = buildTxBody(op_list, transferKey); return this.ain.sendTransaction(txBody); } // TODO(woojae): implement this - async withdrawCredit(serviceName: string, amount: number) { + async withdrawCredit(modelName: string, amount: number) { return await true; } - async getCreditBalance(serviceName: string): Promise { + async getCreditBalance(modelName: string): Promise { const userAddress = await this.ain.getAddress(); - const balancePath = Path.app(serviceName).balanceOfUser(userAddress); + const balancePath = Path.app(modelName).balanceOfUser(userAddress); return await this.ain.getValue(balancePath) as number | 0; } - async getCreditHistory(serviceName: string): Promise { + async getCreditHistory(modelName: string): Promise { const userAddress = await this.ain.getAddress(); - const creditHistoryPath = Path.app(serviceName).historyOfUser(userAddress); + const creditHistoryPath = Path.app(modelName).historyOfUser(userAddress); return await this.ain.getValue(creditHistoryPath) as creditHistories; } - async request(serviceName: string, requestData: any, requestKey?: string) : Promise { - this.checkRunning(serviceName); + async request(modelName: string, requestData: any, requestKey?: string) : Promise { + this.checkRunning(modelName); const result = await new Promise(async (resolve, reject) => { requestKey = requestKey || Date.now().toString(); try { const requesterAddress = await this.ain.getAddress(); - const responsePath = Path.app(serviceName).response(requesterAddress, requestKey.toString()); + const responsePath = Path.app(modelName).response(requesterAddress, requestKey.toString()); await this.handler.subscribe(responsePath, resolve); - const requestPath = Path.app(serviceName).request(requesterAddress, requestKey); + const requestPath = Path.app(modelName).request(requesterAddress, requestKey); const requestOp = buildSetOperation("SET_VALUE", requestPath, requestData); const txBody = buildTxBody(requestOp); await this.ain.sendTransaction(txBody); @@ -97,27 +97,27 @@ export default class ServiceController { return result; } - async run(serviceName: string): Promise { - const statusPath = Path.app(serviceName).status(); + async run(modelName: string): Promise { + const statusPath = Path.app(modelName).status(); const statusOp = buildSetOperation("SET_VALUE", statusPath, ContainerStatus.RUNNING); const txBody = buildTxBody(statusOp); await this.ain.sendTransaction(txBody); } - async stop(serviceName: string): Promise { - const statusPath = Path.app(serviceName).status(); + async stop(modelName: string): Promise { + const statusPath = Path.app(modelName).status(); const statusOp = buildSetOperation("SET_VALUE", statusPath, ContainerStatus.STOP); const txBody = buildTxBody(statusOp); await this.ain.sendTransaction(txBody); } // TODO:(woojae): implement this - async changeServiceInfo(serviceName: string, config: any): Promise { + async changeModelInfo(modelName: string, config: any): Promise { await true; } - private async getDepositAddress(serviceName: string): Promise { - return (await this.ain.getValue(Path.app(serviceName).billingConfig())).depositAddress; + private async getDepositAddress(modelName: string): Promise { + return (await this.ain.getValue(Path.app(modelName).billingConfig())).depositAddress; } checkLoggedIn(): void { @@ -126,12 +126,12 @@ export default class ServiceController { } } - async isAdmin(serviceName: string): Promise { + async isAdmin(modelName: string): Promise { this.checkLoggedIn(); - const adminPath = `/manage_app/${serviceName}/config/admin`; + const adminPath = `/manage_app/${modelName}/config/admin`; const adminList = await this.ain.getValue(adminPath); if(!adminList[(await this.ain.getAddress())]) { - throw new Error('You are not a service admin.'); + throw new Error('You are not a model admin.'); } } } \ No newline at end of file diff --git a/src/internal.ts b/src/internal.ts index 93482ef..9934108 100644 --- a/src/internal.ts +++ b/src/internal.ts @@ -4,7 +4,7 @@ import { getChangeBalanceOp, getResponseOp, getWriteHistoryOp } from "./utils/op import { HISTORY_TYPE, RESPONSE_STATUS, deposit, request, response } from "./types/type"; import { buildTxBody } from "./utils/builder"; import AinModule from "./ain"; -import { extractDataFromDepositRequest, extractDataFromServiceRequest } from "./utils/extractor"; +import { extractDataFromDepositRequest, extractDataFromModelRequest } from "./utils/extractor"; export default class Internal { private ain = AinModule.getInstance(); @@ -21,7 +21,7 @@ export default class Internal { } async handleRequest(req: Request, cost: number, status: RESPONSE_STATUS, responseData: string) { - const { requesterAddress, requestKey, appName } = this.getDataFromServiceRequest(req); + const { requesterAddress, requestKey, appName } = this.getDataFromModelRequest(req); const ops: SetOperation[] = []; const responseOp = getResponseOp(appName, requesterAddress, requestKey, status, responseData, cost); ops.push(responseOp); @@ -35,8 +35,8 @@ export default class Internal { return await this.ain.sendTransaction(txBody); } - getDataFromServiceRequest(req: Request) { - return extractDataFromServiceRequest(req); + getDataFromModelRequest(req: Request) { + return extractDataFromModelRequest(req); } private getDataFromDepositRequest(req: Request) { diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 0000000..991c1a6 --- /dev/null +++ b/src/model.ts @@ -0,0 +1,129 @@ +import ModelController from "./controllers/modelController"; + +export default class Model { + modelName: string; + private modelController: ModelController; + + constructor(modelName: string) { + this.modelName = modelName; + this.modelController = ModelController.getInstance(); + } + + /** + * Gets whether the model is running or not. + * @returns {Promise} + */ + async isRunning() { + return await this.modelController.isRunning(this.modelName); + } + + /** + * Get model information. not implemented yet. + * @returns {string} model information. + */ + async getInformation() { + return await this.modelController.getInformation(this.modelName); + } + + /** + * Calculate estimated cost for given request data. + * @param {string} rerquestData string data for request to model. + * @returns {number} Estimated cost. + */ + async calculateCost(requestData: string) { + // FIXME(yoojin): Temporary deprecated. Need new pricing rules. + // return await this.modelController.calculateCost(this.modelName, requestData); + return 0; + } + + /** + * Charge credit to model. + * @param {number} amount Amount of credit to charge. + * @returns {string} Transaction hash. + */ + async chargeCredit(amount: number) { + this.checkLoggedIn(); + return await this.modelController.chargeCredit(this.modelName, amount); + } + + /** + * Withdraw credit from model. + * @param {number} amount Amount of credit to withdraw. + * @returns {string} Transaction hash. + */ + async withdrawCredit(amount: number) { + this.checkLoggedIn(); + return await this.modelController.withdrawCredit( + this.modelName, + amount + ); + } + + /** + * Get credit balance of model. + * @returns {number} Amount of credit balance. + */ + async getCreditBalance() { + this.checkLoggedIn(); + return await this.modelController.getCreditBalance(this.modelName); + } + + /** + * Get credit history of model. + * @returns {creditHistories} Histories of credit deposit and usage. + */ + async getCreditHistory() { + this.checkLoggedIn(); + return await this.modelController.getCreditHistory(this.modelName); + } + + /** + * Use model with given request data. + * @param {string} requestData String data for request to model. + * @returns {string} Response data from model. + */ + async request(requestData: any, requestKey?: string) { + this.checkLoggedIn(); + return await this.modelController.request( + this.modelName, + requestData, + requestKey + ); + } + + /** + * Change status of AI model container to Running. Need to be admin. Not implemented yet. + */ + async run() { + await this.isAdmin(); + return await this.modelController.run(this.modelName); + } + + /** + * Change status of AI model container to Stopped. Need to be admin. Not implemented yet. + */ + async stop() { + await this.isAdmin(); + return await this.modelController.stop(this.modelName); + } + + /** + * Change model configuration. Need to be admin. Not implemented yet. + * @param {any} config Configuration to change. Not implemented yet. + */ + async changemodelInfo(config: any) { + await this.isAdmin(); + return await this.modelController.changeModelInfo( + this.modelName, + config + ); + } + + private async isAdmin() { + return this.modelController.isAdmin(this.modelName); + } + + private checkLoggedIn() { + return this.modelController.checkLoggedIn(); + } +} diff --git a/src/service.ts b/src/service.ts deleted file mode 100644 index 8e7524c..0000000 --- a/src/service.ts +++ /dev/null @@ -1,119 +0,0 @@ -import ServiceController from "./controllers/serviceController"; - -export default class Service { - serviceName: string; - private serviceController: ServiceController; - - constructor(serviceName: string) { - this.serviceName = serviceName; - this.serviceController = ServiceController.getInstance(); - } - - /** - * Gets whether the service is running or not. - * @returns {Promise} - */ - async isRunning() { - return await this.serviceController.isRunning(this.serviceName); - } - - /** - * Get service information. not implemented yet. - * @returns {string} Service information. - */ - async getInformation() { - return await this.serviceController.getInformation(this.serviceName); - } - - /** - * Calculate estimated cost for given request data. - * @param {string} rerquestData string data for request to service. - * @returns {number} Estimated cost. - */ - async calculateCost (requestData: string) { - // FIXME(yoojin): Temporary deprecated. Need new pricing rules. - // return await this.serviceController.calculateCost(this.serviceName, requestData); - return 0; - } - - /** - * Charge credit to service. - * @param {number} amount Amount of credit to charge. - * @returns {string} Transaction hash. - */ - async chargeCredit(amount: number) { - this.checkLoggedIn(); - return await this.serviceController.chargeCredit(this.serviceName, amount); - } - - /** - * Withdraw credit from service. - * @param {number} amount Amount of credit to withdraw. - * @returns {string} Transaction hash. - */ - async withdrawCredit(amount: number) { - this.checkLoggedIn(); - return await this.serviceController.withdrawCredit(this.serviceName, amount); - } - - /** - * Get credit balance of service. - * @returns {number} Amount of credit balance. - */ - async getCreditBalance() { - this.checkLoggedIn(); - return await this.serviceController.getCreditBalance(this.serviceName); - } - - /** - * Get credit history of service. - * @returns {creditHistories} Histories of credit deposit and usage. - */ - async getCreditHistory() { - this.checkLoggedIn(); - return await this.serviceController.getCreditHistory(this.serviceName); - } - - /** - * Use service with given request data. - * @param {string} requestData String data for request to service. - * @returns {string} Response data from service. - */ - async request(requestData: any, requestKey?: string) { - this.checkLoggedIn(); - return await this.serviceController.request(this.serviceName, requestData, requestKey); - } - - /** - * Change status of AI service container to Running. Need to be admin. Not implemented yet. - */ - async run() { - await this.isAdmin(); - return await this.serviceController.run(this.serviceName); - } - - /** - * Change status of AI service container to Stopped. Need to be admin. Not implemented yet. - */ - async stop() { - await this.isAdmin(); - return await this.serviceController.stop(this.serviceName); - } - - /** - * Change service configuration. Need to be admin. Not implemented yet. - * @param {any} config Configuration to change. Not implemented yet. - */ - async changeServiceInfo(config: any) { - await this.isAdmin(); - return await this.serviceController.changeServiceInfo(this.serviceName, config); - } - - private async isAdmin() { - return this.serviceController.isAdmin(this.serviceName); - } - - private checkLoggedIn() { - return this.serviceController.checkLoggedIn(); - } -} diff --git a/src/types/type.ts b/src/types/type.ts index ae28990..f6b7650 100644 --- a/src/types/type.ts +++ b/src/types/type.ts @@ -81,15 +81,15 @@ export type deposit = { }; export type deployConfig = { - serviceName: string, + modelName: string, billingConfig?: appBillingConfig, - serviceUrl?: string, // NOTE(yoojin): Remove when container deploy logic was available. + modelUrl?: string, // NOTE(yoojin): Remove when container deploy logic was available. }; export type createAppConfig = { appName: string, billingConfig: appBillingConfig, - serviceUrl: string, + modelUrl: string, }; export enum ContainerStatus { diff --git a/src/utils/extractor.ts b/src/utils/extractor.ts index 87c7686..41aaaba 100644 --- a/src/utils/extractor.ts +++ b/src/utils/extractor.ts @@ -1,9 +1,9 @@ import { Request } from "express"; import { deposit, request } from "../types/type"; -export const extractDataFromServiceRequest = (req:Request) => { +export const extractDataFromModelRequest = (req:Request) => { if(!req.body.valuePath[1] || !req.body.valuePath[3] || !req.body.valuePath[4] || !req.body.value) { - throw new Error("Not from service request"); + throw new Error("Not from model request"); } const requestData: request = { appName: req.body.valuePath[1],