From 1cf7c162efe526da2bff1247cdedb5cbf359bf56 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Wed, 20 Nov 2024 16:58:43 +0200 Subject: [PATCH 01/41] docs: set prompt example in swagger --- proxy-router/docs/docs.go | 26 ++++++++++++++++++- proxy-router/docs/swagger.json | 26 ++++++++++++++++++- proxy-router/docs/swagger.yaml | 18 ++++++++++++- .../internal/proxyapi/controller_http.go | 8 +++--- proxy-router/internal/proxyapi/requests.go | 8 ++++++ 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index 39284b93..42c985f9 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -1190,7 +1190,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "type": "string" + "$ref": "#/definitions/proxyapi.ChatCompletionRequestSwaggerExample" } } ], @@ -1652,6 +1652,30 @@ const docTemplate = `{ } } }, + "proxyapi.ChatCompletionRequestSwaggerExample": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "content": { + "type": "string", + "example": "tell me a joke" + }, + "role": { + "type": "string", + "example": "user" + } + } + } + }, + "stream": { + "type": "boolean" + } + } + }, "proxyapi.InitiateSessionReq": { "type": "object", "required": [ diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index 5220d08e..4f18c145 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -1183,7 +1183,7 @@ "in": "body", "required": true, "schema": { - "type": "string" + "$ref": "#/definitions/proxyapi.ChatCompletionRequestSwaggerExample" } } ], @@ -1645,6 +1645,30 @@ } } }, + "proxyapi.ChatCompletionRequestSwaggerExample": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "content": { + "type": "string", + "example": "tell me a joke" + }, + "role": { + "type": "string", + "example": "user" + } + } + } + }, + "stream": { + "type": "boolean" + } + } + }, "proxyapi.InitiateSessionReq": { "type": "object", "required": [ diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index 807b45b5..618ad9cb 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -152,6 +152,22 @@ definitions: - timestamp - user type: object + proxyapi.ChatCompletionRequestSwaggerExample: + properties: + messages: + items: + properties: + content: + example: tell me a joke + type: string + role: + example: user + type: string + type: object + type: array + stream: + type: boolean + type: object proxyapi.InitiateSessionReq: properties: bidId: @@ -1340,7 +1356,7 @@ paths: name: prompt required: true schema: - type: string + $ref: '#/definitions/proxyapi.ChatCompletionRequestSwaggerExample' produces: - text/event-stream responses: diff --git a/proxy-router/internal/proxyapi/controller_http.go b/proxy-router/internal/proxyapi/controller_http.go index 5e3a03a6..9db0fef5 100644 --- a/proxy-router/internal/proxyapi/controller_http.go +++ b/proxy-router/internal/proxyapi/controller_http.go @@ -86,10 +86,10 @@ func (s *ProxyController) InitiateSession(ctx *gin.Context) { // @Description Send prompt to a local or remote model based on session id in header // @Tags chat // @Produce text/event-stream -// @Param session_id header string false "Session ID" format(hex32) -// @Param model_id header string false "Model ID" format(hex32) -// @Param chat_id header string false "Chat ID" format(hex32) -// @Param prompt body string true "Prompt" +// @Param session_id header string false "Session ID" format(hex32) +// @Param model_id header string false "Model ID" format(hex32) +// @Param chat_id header string false "Chat ID" format(hex32) +// @Param prompt body proxyapi.ChatCompletionRequestSwaggerExample true "Prompt" // @Success 200 {object} string // @Router /v1/chat/completions [post] func (c *ProxyController) Prompt(ctx *gin.Context) { diff --git a/proxy-router/internal/proxyapi/requests.go b/proxy-router/internal/proxyapi/requests.go index 93bc4299..bf8fc47f 100644 --- a/proxy-router/internal/proxyapi/requests.go +++ b/proxy-router/internal/proxyapi/requests.go @@ -40,3 +40,11 @@ type UpdateChatTitleReq struct { type ResultResponse struct { Result bool `json:"result"` } + +type ChatCompletionRequestSwaggerExample struct { + Stream bool `json:"stream"` + Messages []struct { + Role string `json:"role" example:"user"` + Content string `json:"content" example:"tell me a joke"` + } `json:"messages"` +} From c8af450ca365fb8de5ccc9a1d54167854bd617fc Mon Sep 17 00:00:00 2001 From: shev Date: Wed, 20 Nov 2024 16:18:53 +0100 Subject: [PATCH 02/41] fix: apply retry logic for both api calls of polling cycle --- proxy-router/internal/lib/ethclient.go | 8 +- .../contracts/log_watcher_polling.go | 162 ++++++++++-------- .../contracts/log_watcher_subscription.go | 14 +- proxy-router/internal/storages/storage.go | 7 +- 4 files changed, 111 insertions(+), 80 deletions(-) diff --git a/proxy-router/internal/lib/ethclient.go b/proxy-router/internal/lib/ethclient.go index 640ddd84..7fa5f2f8 100644 --- a/proxy-router/internal/lib/ethclient.go +++ b/proxy-router/internal/lib/ethclient.go @@ -1,6 +1,8 @@ package lib import ( + "fmt" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/lumerintoken" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/marketplace" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/modelregistry" @@ -31,11 +33,7 @@ type EVMError struct { } func (e EVMError) Error() string { - idBytes := e.Abi.ID.Bytes() - if len(idBytes) > 4 { - idBytes = idBytes[:4] - } - return "EVM error: " + e.Abi.Sig + " " + common.BytesToHash(idBytes).Hex() + return fmt.Sprintf("EVM error: %s %+v", e.Abi.Sig, e.Args) } // TryConvertGethError attempts to convert geth error to an EVMError, otherwise just returns original error diff --git a/proxy-router/internal/repositories/contracts/log_watcher_polling.go b/proxy-router/internal/repositories/contracts/log_watcher_polling.go index c43808e0..0b773bf2 100644 --- a/proxy-router/internal/repositories/contracts/log_watcher_polling.go +++ b/proxy-router/internal/repositories/contracts/log_watcher_polling.go @@ -3,6 +3,7 @@ package contracts import ( "context" "errors" + "fmt" "math/big" "time" @@ -10,7 +11,6 @@ import ( "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" ) var ( @@ -47,94 +47,116 @@ func (w *LogWatcherPolling) Watch(ctx context.Context, contractAddr common.Addre return lib.NewSubscription(func(quit <-chan struct{}) error { defer close(sink) - for { - currentBlock, err := w.client.HeaderByNumber(ctx, nil) + for { // infinite polling loop + nextFrom, err := w.pollReconnect(ctx, quit, nextFromBlock, contractAddr, mapper, sink) if err != nil { return err } + nextFromBlock = nextFrom + } + }, sink), nil +} - if nextFromBlock == nil { - nextFromBlock = new(big.Int).Set(currentBlock.Number) - } +func (w *LogWatcherPolling) pollReconnect(ctx context.Context, quit <-chan struct{}, nextFromBlock *big.Int, contractAddr common.Address, mapper EventMapper, sink chan interface{}) (*big.Int, error) { + var lastErr error + for i := 0; i < w.maxReconnects || w.maxReconnects == 0; i++ { + // for any of those cases, we should stop retrying + select { + case <-quit: + return nextFromBlock, SubClosedError + case <-ctx.Done(): + return nextFromBlock, ctx.Err() + default: + } + newNextFromBlock, err := w.pollChanges(ctx, nextFromBlock, quit, contractAddr, mapper, sink) + if err == nil { + return newNextFromBlock, nil + } + maxReconnects := fmt.Sprintf("%d", w.maxReconnects) + if w.maxReconnects == 0 { + maxReconnects = "∞" + } + w.log.Warnf("polling error, retrying (%d/%s): %s", i, maxReconnects, err) + lastErr = err - // if we poll too often, we might be behind the chain, so we wait for the next block - if currentBlock.Number.Cmp(nextFromBlock) < 0 { - select { - case <-quit: - return SubClosedError - case <-ctx.Done(): - return ctx.Err() - case <-time.After(w.pollInterval): - } - continue - } + // retry delay + select { + case <-quit: + return nextFromBlock, SubClosedError + case <-ctx.Done(): + return nextFromBlock, ctx.Err() + case <-time.After(w.pollInterval): + } + } - query := ethereum.FilterQuery{ - Addresses: []common.Address{contractAddr}, - FromBlock: nextFromBlock, - ToBlock: currentBlock.Number, - } - sub, err := w.filterLogsRetry(ctx, query, quit) - if err != nil { - return err - } + err := fmt.Errorf("polling error, retries exhausted (%d), stopping: %s", w.maxReconnects, lastErr) + w.log.Warnf(err.Error()) + return nextFromBlock, err +} - for _, log := range sub { - if log.Removed { - continue - } - event, err := mapper(log) - if err != nil { - w.log.Debugf("error mapping event: %s", err) - continue // mapper error, retry won't help, but we can continue - } - - select { - case <-quit: - return SubClosedError - case <-ctx.Done(): - return ctx.Err() - case sink <- event: - } - } +func (w *LogWatcherPolling) pollChanges(ctx context.Context, nextFromBlock *big.Int, quit <-chan struct{}, contractAddr common.Address, mapper EventMapper, sink chan interface{}) (*big.Int, error) { + currentBlock, err := w.client.HeaderByNumber(ctx, nil) + if err != nil { + return nil, err + } - nextFromBlock.Add(currentBlock.Number, big.NewInt(1)) + if nextFromBlock == nil { + nextFromBlock = new(big.Int).Set(currentBlock.Number) + } - select { - case <-quit: - return SubClosedError - case <-ctx.Done(): - return ctx.Err() - case <-time.After(w.pollInterval): - } + // if we poll too often, we might be behind the chain, so we wait for the next block + // mapper error, retry won't help, but we can continue + if currentBlock.Number.Cmp(nextFromBlock) < 0 { + select { + case <-quit: + return nextFromBlock, SubClosedError + case <-ctx.Done(): + return nextFromBlock, ctx.Err() + case <-time.After(w.pollInterval): } - }, sink), nil -} + return nextFromBlock, nil + } -func (w *LogWatcherPolling) filterLogsRetry(ctx context.Context, query ethereum.FilterQuery, quit <-chan struct{}) ([]types.Log, error) { - var lastErr error + query := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddr}, + FromBlock: nextFromBlock, + ToBlock: currentBlock.Number, + } - for attempts := 0; attempts < w.maxReconnects; attempts++ { - logs, err := w.client.FilterLogs(ctx, query) - if err == nil { - if attempts > 0 { - w.log.Warnf("subscription successfully reconnected after error: %s", lastErr) - } + w.log.Debugf("=====> calling poll from %s to %s", query.FromBlock.String(), query.ToBlock.String()) + sub, err := w.client.FilterLogs(ctx, query) + if err != nil { + return nextFromBlock, err + } - return logs, nil + for _, log := range sub { + if log.Removed { + continue + } + event, err := mapper(log) + if err != nil { + w.log.Debugf("error mapping event, skipping: %s", err) + continue } - - w.log.Debugf("subscription error: %s, retrying in %s", err, w.pollInterval.String()) - lastErr = err select { case <-quit: - return nil, SubClosedError + return nextFromBlock, SubClosedError case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(w.pollInterval): + return nextFromBlock, ctx.Err() + case sink <- event: } } - return nil, lastErr + nextFromBlock.Add(currentBlock.Number, big.NewInt(1)) + + select { + case <-quit: + return nextFromBlock, SubClosedError + case <-ctx.Done(): + return nextFromBlock, ctx.Err() + case <-time.After(w.pollInterval): + } + + return nextFromBlock, nil } diff --git a/proxy-router/internal/repositories/contracts/log_watcher_subscription.go b/proxy-router/internal/repositories/contracts/log_watcher_subscription.go index 42c7fb6c..c5629a60 100644 --- a/proxy-router/internal/repositories/contracts/log_watcher_subscription.go +++ b/proxy-router/internal/repositories/contracts/log_watcher_subscription.go @@ -2,6 +2,7 @@ package contracts import ( "context" + "fmt" "math/big" i "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/interfaces" @@ -89,9 +90,15 @@ func (w *LogWatcherSubscription) Watch(ctx context.Context, contractAddr common. func (w *LogWatcherSubscription) subscribeFilterLogsRetry(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { var lastErr error - for attempts := 0; attempts < w.maxReconnects; attempts++ { + for attempts := 0; attempts < w.maxReconnects || w.maxReconnects == 0; attempts++ { sub, err := w.client.SubscribeFilterLogs(ctx, query, ch) if err != nil { + maxReconnects := fmt.Sprintf("%d", w.maxReconnects) + if w.maxReconnects == 0 { + maxReconnects = "∞" + } + w.log.Warnf("subscription error, retrying (%d/%s): %s", attempts, maxReconnects, err) + lastErr = err continue } @@ -102,5 +109,8 @@ func (w *LogWatcherSubscription) subscribeFilterLogsRetry(ctx context.Context, q return sub, nil } - return nil, lastErr + err := fmt.Errorf("subscription error, retries exhausted (%d), stopping: %s", w.maxReconnects, lastErr) + w.log.Warnf(err.Error()) + + return nil, err } diff --git a/proxy-router/internal/storages/storage.go b/proxy-router/internal/storages/storage.go index 84789bee..bdc5ed78 100644 --- a/proxy-router/internal/storages/storage.go +++ b/proxy-router/internal/storages/storage.go @@ -12,17 +12,18 @@ type Storage struct { } func NewStorage(log lib.ILogger, path string) *Storage { + badgerLogger := NewBadgerLogger(log) if err := os.Mkdir(path, os.ModePerm); err != nil { - log.Warn(err) + badgerLogger.Debugf("%s", err) } opts := badger.DefaultOptions(path) - opts.Logger = NewBadgerLogger(log) + opts.Logger = badgerLogger db, err := badger.Open(opts) - if err != nil { log.Fatal(err) } + return &Storage{db} } From 396b44e0c2484ce2806678e5c3bb3b4fbad22340 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Wed, 20 Nov 2024 10:39:12 -0500 Subject: [PATCH 03/41] update documentation for new ui name and added testnet/mainnet .env examples --- docs/02-provider-setup.md | 7 +++-- docs/03-provider-offer.md | 18 +++++++---- docs/04-consumer-setup.md | 4 +-- docs/04a-consumer-setup-source.md | 6 ++-- docs/mac-boot-strap.md | 4 +-- docs/proxy-router-api-direct.md | 6 ++-- docs/proxy-router.all.env | 30 +++++++++++-------- proxy-router/.env.main.example | 19 ++++++++++++ proxy-router/.env.main.example.win | 19 ++++++++++++ .../{.env.example => .env.test.example} | 4 +-- ....env.example.win => .env.test.example.win} | 4 +-- readme.md | 7 +++-- 12 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 proxy-router/.env.main.example create mode 100644 proxy-router/.env.main.example.win rename proxy-router/{.env.example => .env.test.example} (79%) rename proxy-router/{.env.example.win => .env.test.example.win} (79%) diff --git a/docs/02-provider-setup.md b/docs/02-provider-setup.md index ebac0b76..a63569e1 100644 --- a/docs/02-provider-setup.md +++ b/docs/02-provider-setup.md @@ -8,7 +8,7 @@ * If your local model is listening on a different port locally, you will need to modify the `models-config.json` file to match the correct port * You have an existing funded wallet with MOR and ETH and also have the `private key` for the wallet (this will be needed for the .env file configuration) * You have created an Alchemy or Infura free account and have a private API key for the Arbitrum Sepolia testnet (wss://arb-sepolia.g.alchemy.com/v2/) -* Your proxy-router must have a publicly accessible endpoint for the provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` - this will be used when creating the provider on the blockchain +* Your proxy-router must have a **publicly accessible endpoint** for the provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` - this will be used when creating the provider on the blockchain ## Installation & Configuration Steps: 1. Obtain the software: @@ -28,7 +28,10 @@ 1. Environment configuration * In most cases, to start with, the default .env file will work for the proxy-router...in some cases you will want to modify the .env file with advanced capability (log entries, private keys, private endpoints, etc) - * Change the name of the `.env.example` or `.env.example.win` file to `.env` and edit the file with the appropriate values + * Choose which chain and environment you want to work with (MAIN or TEST) and select the right example .env file + * Mainnet: `env.main.example` for Max/Unix or `env.main.example.win` for Windows + * Testnet: `env.test.example` for Max/Unix or `env.test.example.win` for Windows + * Change the name of the desired file to `.env` * Please see [proxy-router.all.env](proxy-router.all.env) for more information on the key values needed in the .env file 1. **(OPTIONAL)** - External Provider or Pass through diff --git a/docs/03-provider-offer.md b/docs/03-provider-offer.md index 03b631bd..f5cd687a 100644 --- a/docs/03-provider-offer.md +++ b/docs/03-provider-offer.md @@ -2,8 +2,8 @@ # Creating Provider, Model and Bid on the Blockchain: **Needed information (samples):** -* Provider/Owner: `0x9E26Fea97F7d644BAf62d0e20e4d4b8F836C166c` # Your ERC-20 Wallet with saMOR & saETH -* Endpoint: `server.domain.com:3333` # Internet publicly accessible server/node access point +* Provider/Owner: `0x9E26Fea97F7d644BAf62d0e20e4d4b8F836C166c` # Your ERC-20 Wallet with MOR & ETH +* Endpoint: `server.domain.com:3333` # Internet **publicly accessible** server/node access point * Model ID: `0xe1e6e3e77148d140065ef2cd4fba7f4ae59c90e1639184b6df5c84` # Random 32byte/hex that you generate * ipfcCID: `0xc2d3a5e4f9b7c1a2c8f0b1d5e6c78492fa7bcd34e9a3b9c9e18f25d3be47a1f6` # Another 32byte/hex random for future use * Model Name: `CapybaraHermes-v2.5-Mistral-7b` # Human Readable name for the model @@ -16,7 +16,7 @@ 1. Authorize Diamond Contract to spend on the Provider's behalf 1. http://localhost:8082/swagger/index.html#/transactions/post_blockchain_approve 1. Spender Address = Diamond Contract - 1. Authorized Amount = remember that this is in the form `1*10^18` so make sure there's enough MOR 1ranted to cover the contract fees + 1. Authorized Amount = remember that this is in the form `1*10^18` so make sure there's enough MOR granted to cover the contract fees 1. The Diamond Contract is now authorized to spend MOR on provider's behalf 1. Create Provider in the Diamond contract via swagger api: @@ -24,7 +24,7 @@ 1. http://localhost:8082/swagger/index.html#/providers/post_blockchain_providers 1. Enter required fields: 1. addStake = Amount of stake for provider to risk - Stake can be 0 now - 1. Endpoint = Your publicly accessible endpoint for the proxy-router provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` + 1. Endpoint = Your **publicly accessible endpoint** for the proxy-router provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` 1. Create Model in the contract: 1. Go to http://localhost:8082/swagger/index.html#/models/post_blockchain_models and enter @@ -35,12 +35,18 @@ 1. Owner: Provider Wallet Address 1. name: Human Readable model like "Llama 2.0" or "Mistral 2.5" or "Collective Cognition 1.1" 1. tags: array of tag strings for the model - 1. Capture the `modelID` from the JSON response + 1. Capture the `modelID` from the JSON response + **NOTE** The returned `modelID` is a combination of your requested modelID and your providerID and will be required to update your models-config.json file AND when offering bids + +1. Update the models-config.json file with the new modelID and restart the proxy-router + 1. Navigate to the proxy-router directory and open the `models-config.json` file + 1. Add the new modelID to the JSON array of models + 1. Save the file and restart the proxy-router 1. Offer Model Bid in the contract: 1. Navigate to http://localhost:8082/swagger/index.html#/bids/post_blockchain_bids and enter 1. modelID: Model ID Created in last step: - 1. pricePerSecond: this is in 1*10^18 format so 100000000000 should make 5 minutes for the session 1round 37.5 saMOR + 1. pricePerSecond: this is in 1*10^18 format so 100000000000 should make 5 minutes for the session around 37.5 MOR 1. Click Execute ---------------- \ No newline at end of file diff --git a/docs/04-consumer-setup.md b/docs/04-consumer-setup.md index dd2064bf..449e8aa6 100644 --- a/docs/04-consumer-setup.md +++ b/docs/04-consumer-setup.md @@ -34,8 +34,8 @@ It will run 3 different pieces of software on your local machine: ## Validation Steps: 1. Local Test: Once the UI is up and running, - 1. You should see tokens for saETH and saMOR that you sent to this wallet earlier. - * If this is a new wallet, you will need to send saMOR and saETH to this wallet to be able to interact with the blockchain + 1. You should see tokens for ETH and MOR that you sent to this wallet earlier. + * If this is a new wallet, you will need to send MOR and ETH to this wallet to be able to interact with the blockchain * This can be done externally via metamask or usual Arbitrum testnet faucets 1. Once you have a funded Wallet, you can interact with the local model 1. Click on the `Chat` icon on the left side of the screen diff --git a/docs/04a-consumer-setup-source.md b/docs/04a-consumer-setup-source.md index abe2182f..1dae7c1a 100644 --- a/docs/04a-consumer-setup-source.md +++ b/docs/04a-consumer-setup-source.md @@ -2,7 +2,7 @@ This document provides a step-by-step guide to setting up a Consumer Node for the Morepheus Network so that you can setup session and interact with the remote providers. ## Pre-requisites: -* Create or use an existing ERC-20 wallet that has saMOR and saETH (Sepolia Arbitrum) tokens - you can use Metamask (new wallet..not derived) or any other ERC-20 wallet. You will need to have access to the wallet's private key **NEVER SHARE THIS WITH ANYONE** for steps below to authorize the contract to spend on your behalf. +* Create or use an existing ERC-20 wallet that has MOR and ETH (Sepolia Arbitrum) tokens - you can use Metamask (new wallet..not derived) or any other ERC-20 wallet. You will need to have access to the wallet's private key **NEVER SHARE THIS WITH ANYONE** for steps below to authorize the contract to spend on your behalf. ## TL;DR * Install and Configure the proxy-router node (once) @@ -62,7 +62,7 @@ Loaded config: {AIEngine:{OpenAIBaseURL: OpenAIKey:} Blockchain:{EthNodeAddress: ### B. Authorize the contract to spend on your behalf Either via the swagger interface http://localhost:8082/swagger/index.html#/wallet/post_blockchain_allowance or following CLI, you can authorize the contract to spend on your behalf. **This only needs to be done once per wallet, or when funds have been depleted.** -`curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF&amount=3' -H 'accept: application/json' -d ''` # Approve the contract to spend 3 saMOR tokens on your behalf +`curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF&amount=3' -H 'accept: application/json' -d ''` # Approve the contract to spend 3 MOR tokens on your behalf ### C. Query the blockchain for various models / providers (Get ModelID) You can query the blockchain for various models and providers to get the ModelID. This can be done via the swagger interface http://localhost:8082/swagger/index.html#/marketplace/get_marketplace_models or following CLI: @@ -145,7 +145,7 @@ curl -X 'POST' \ ### Quick and Dirty Sample: `curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF&amount=3' -H 'accept: application/json' -d ''` - # approves the smart contract `0x8e19...dE45` to spend 3 saMOR tokens on your behalf + # approves the smart contract `0x8e19...dE45` to spend 3 MOR tokens on your behalf `curl -s -X 'GET' 'http://localhost:8082/wallet' -H 'accept: application/json' | jq .address` # returns the wallet ID (confirm that it matches your wallet) diff --git a/docs/mac-boot-strap.md b/docs/mac-boot-strap.md index abac0944..52f3020f 100644 --- a/docs/mac-boot-strap.md +++ b/docs/mac-boot-strap.md @@ -153,8 +153,8 @@ yarn dev - At this point, the electon app should start and if this is the first time walking through, should run through onboarding - Check the following: - Lower left corner - is this your correct ERC20 Wallet Address? - - On the Wallet tab, do you show saMOR and saETH balances? - - if not, you'll need to convert and transfer some saETH to saMOR to get started + - On the Wallet tab, do you show MOR and ETH balances? + - if not, you'll need to convert and transfer some ETH to MOR to get started - On the Chat tab, your should see your `Provider: (local)` selected by default...use the "Ask me anything..." prompt to run inference - this verifies that all three layers have been setup correctly? - Click the Change Model, select a remote model, click Start and now you should be able to interact with the model through the UI. diff --git a/docs/proxy-router-api-direct.md b/docs/proxy-router-api-direct.md index ad0ea436..b0a6aeb6 100644 --- a/docs/proxy-router-api-direct.md +++ b/docs/proxy-router-api-direct.md @@ -2,7 +2,7 @@ This document provides a step-by-step flow to query your local proxy router and interact with a remote model using only the API. This is useful for developers or users who want to interact with the model directly without using the MorpheusUI. ## Pre-requisites: -* Create or use an existing ERC-20 wallet that has saMOR and saETH (Sepolia Arbitrum) tokens - you can use Metamask (new wallet..not derived) or any other ERC-20 wallet. +* Create or use an existing ERC-20 wallet that has MOR and ETH (Sepolia Arbitrum) tokens - you can use Metamask (new wallet..not derived) or any other ERC-20 wallet. * You will need to have access to the wallet's private key **NEVER SHARE THIS WITH ANYONE** for steps below to authorize the contract to spend on your behalf. * Install and launch the local llama.cpp and proxy-router from source (see [/docs/mac-boot-strap.md](/docs/mac-boot-strap.md) for instructions) @@ -18,7 +18,7 @@ This document provides a step-by-step flow to query your local proxy router and ### B. Authorize the contract to spend on your behalf Either via the swagger interface http://localhost:8082/swagger/index.html#/wallet/post_blockchain_allowance or following CLI, you can authorize the contract to spend on your behalf. **This only needs to be done once per wallet, or when funds have been depleted.** -Approve the contract to spend 3 saMOR tokens on your behalf +Approve the contract to spend 3 MOR tokens on your behalf ```sh curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF&amount=3' -H 'accept: application/json' -d '' @@ -111,7 +111,7 @@ curl -X 'POST' \ ### Quick and Dirty Sample: `curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF&amount=3' -H 'accept: application/json' -d ''` - # approves the smart contract `0x8e19...dE45` to spend 3 saMOR tokens on your behalf + # approves the smart contract `0x8e19...dE45` to spend 3 MOR tokens on your behalf `curl -s -X 'GET' 'http://localhost:8082/wallet' -H 'accept: application/json' | jq .address` # returns the wallet ID (confirm that it matches your wallet) diff --git a/docs/proxy-router.all.env b/docs/proxy-router.all.env index f72a913f..76deed79 100644 --- a/docs/proxy-router.all.env +++ b/docs/proxy-router.all.env @@ -3,7 +3,7 @@ # Currently aligned to TESTNET, but after MAINNET launch, will be updated to MAINNET values # Application Configurations -# Set to true to reset keychain on start +# Set to true to reset mac keychain on start APP_RESET_KEYCHAIN=false # Blockchain Configurations @@ -17,23 +17,27 @@ ETH_NODE_LEGACY_TX=false # Blockchain explorer API URL (required, must be a valid URL) # TESTNET: https://api-sepolia.arbiscan.io/api, MAINNET: https://api.arbiscan.io/api EXPLORER_API_URL=https://api-sepolia.arbiscan.io/api -# Set to true to enable subscriptions for blockchain events, otherwise, polling will be used +# Delay between retries for blockchain explorer API (defaults to 5s if not set) +EXPLORER_RETRY_DELAY= +# Maximum retries for explorer requests (defaults to 5 if not set) +EXPLORER_MAX_RETRIES= +# Set to true to enable subscriptions/wss for blockchain events, otherwise, http polling will be used ETH_NODE_USE_SUBSCRIPTIONS=false -# Interval for polling eth node for new events (defaults to 1s) -ETH_NODE_POLLING_INTERVAL=1s -# Maximum number of reconnect attempts to Ethereum node (defaults to 30) -ETH_NODE_MAX_RECONNECTS=30 +# Interval for polling eth node for new events (defaults to 10s if not set) +ETH_NODE_POLLING_INTERVAL= +# Maximum number of reconnect attempts to Ethereum node (defaults to 30 if not set) +ETH_NODE_MAX_RECONNECTS= # Environment Configuration -# Environment for the application (default is "development") +# Environment for the application (default is "development", production is "production") ENVIRONMENT=development # Marketplace Configurations # Diamond contract address (optional, must be a valid Ethereum address) -# TESTNET: 0xb8C55cD613af947E73E262F0d3C54b7211Af16CF, MAINNET: TBD +# TESTNET: 0xb8C55cD613af947E73E262F0d3C54b7211Af16CF, MAINNET: 0xDE819AaEE474626E3f34Ef0263373357e5a6C71b DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF # MOR token address (optional, must be a valid Ethereum address) -# TESTNET: 0x34a285a1b1c166420df5b6630132542923b5b27e, MAINNET: TBD +# TESTNET: 0x34a285a1b1c166420df5b6630132542923b5b27e, MAINNET: 0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e # Private key for signing transactions; if not set, the system keychain will be used WALLET_PRIVATE_KEY= @@ -41,7 +45,7 @@ WALLET_PRIVATE_KEY= # Logging Configurations # Enable colored logging LOG_COLOR=false -# Folder path for log files (optional, must be a valid directory path) +# Enables logging and folder path for log files (must be a valid directory path) LOG_FOLDER_PATH= # Set to true for production log format LOG_IS_PROD=false @@ -57,7 +61,7 @@ LOG_LEVEL_RPC=info LOG_LEVEL_BADGER=info # Proxy Configurations -# Address for the proxy (default is "0.0.0.0:3333") +# Address for the proxy (default is "0.0.0.0:3333" if not set) PROXY_ADDRESS=0.0.0.0:3333 # Path for proxy storage (default is "./data/badger/") PROXY_STORAGE_PATH=./data/badger/ @@ -88,7 +92,7 @@ SYS_SOMAXCONN=100000 SYS_TCP_MAX_SYN_BACKLOG=100000 # Web Configurations -# Address for the web server (default is "0.0.0.0:8082") +# Address for the web server (default is "0.0.0.0:8082" if not set) WEB_ADDRESS=0.0.0.0:8082 -# Public URL of the proxyrouter (falls back to WEB_ADDRESS if empty) +# Public URL of the proxyrouter (falls back to http://Localhost:WEB_ADDRESS if not set) WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.main.example b/proxy-router/.env.main.example new file mode 100644 index 00000000..1caeb27d --- /dev/null +++ b/proxy-router/.env.main.example @@ -0,0 +1,19 @@ +# MAINNET +# Minimalist ENV File to enable the proxy-router to run +# Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env + +WALLET_PRIVATE_KEY= +DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +EXPLORER_API_URL="https://api.arbiscan.io/api" +ETH_NODE_CHAIN_ID=42161 +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ENVIRONMENT=production +ETH_NODE_LEGACY_TX=false +PROXY_STORE_CHAT_CONTEXT=true +PROXY_STORAGE_PATH=./data/ +LOG_COLOR=true +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.main.example.win b/proxy-router/.env.main.example.win new file mode 100644 index 00000000..1d944bc7 --- /dev/null +++ b/proxy-router/.env.main.example.win @@ -0,0 +1,19 @@ +# MAINNET +# Minimalist ENV File to enable the proxy-router to run +# Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env + +WALLET_PRIVATE_KEY= +DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +EXPLORER_API_URL="https://api.arbiscan.io/api" +ETH_NODE_CHAIN_ID=42161 +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ENVIRONMENT=production +ETH_NODE_LEGACY_TX=false +PROXY_STORE_CHAT_CONTEXT=true +PROXY_STORAGE_PATH=.\data\ +LOG_COLOR=true +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.example b/proxy-router/.env.test.example similarity index 79% rename from proxy-router/.env.example rename to proxy-router/.env.test.example index e3bbe559..2d1ae1a0 100644 --- a/proxy-router/.env.example +++ b/proxy-router/.env.test.example @@ -1,6 +1,6 @@ +# TESTNET # Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 and are TESTNET values -# ...when mainnet release is imminent, update the values to mainnet alignment +# Contract and Token current as of 11/15/2024 # Full ENV details can be found in /docs/proxy-router.full.env WALLET_PRIVATE_KEY= diff --git a/proxy-router/.env.example.win b/proxy-router/.env.test.example.win similarity index 79% rename from proxy-router/.env.example.win rename to proxy-router/.env.test.example.win index 51fef090..21ba29f9 100644 --- a/proxy-router/.env.example.win +++ b/proxy-router/.env.test.example.win @@ -1,6 +1,6 @@ +# TESTNET # Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 and are TESTNET values -# ...when mainnet release is imminent, update the values to mainnet alignment +# Contract and Token current as of 11/15/2024 # Full ENV details can be found in /docs/proxy-router.full.env WALLET_PRIVATE_KEY= diff --git a/readme.md b/readme.md index 2a7d2e45..fd7e0cc5 100644 --- a/readme.md +++ b/readme.md @@ -20,14 +20,15 @@ manages secure sessions between consumers and providers and routes prompts and r ## Tokens and Contract Information (update 11/15/2024) ### MainNet: (MAIN Branch and MAIN-* Releases) +* Blockchain: Arbitrum One (ChainID: `42161`) * Morpheus MOR Token: `0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86` * Diamond MarketPlace Contract: `0xDE819AaEE474626E3f34Ef0263373357e5a6C71b` * Blockchain Explorer: `https://arbiscan.io/` -### TestNet (DEV & STG Branches and TEST-* Releases) -* Morpheus saMOR Token: `0x34a285a1b1c166420df5b6630132542923b5b27e` +### TestNet (STG Branch and TEST-* Releases) +* Blockchain: Sepolia Arbitrum (ChainID: `421614`) +* Morpheus MOR Token: `0x34a285a1b1c166420df5b6630132542923b5b27e` * Diamond MarketPlace Contract: `0xb8C55cD613af947E73E262F0d3C54b7211Af16CF` - * Interact with the Morpheus Contract: https://louper.dev/diamond/0xb8C55cD613af947E73E262F0d3C54b7211Af16CF?network=arbitrumSepolia#write * Blockchain Explorer: `https://sepolia.arbiscan.io/` ## Funds From 230d4e2181d3df830ed02b315a8a1541bab9b3a8 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Wed, 20 Nov 2024 17:46:51 +0200 Subject: [PATCH 04/41] extend LocalModel response --- proxy-router/docs/docs.go | 8 +++++++- proxy-router/docs/swagger.json | 8 +++++++- proxy-router/docs/swagger.yaml | 6 +++++- proxy-router/internal/aiengine/ai_engine.go | 10 ++++++---- proxy-router/internal/aiengine/structs.go | 10 ++++++---- proxy-router/internal/proxyapi/controller_http.go | 2 +- 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index 42c985f9..33c6d4f6 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -1321,7 +1321,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "chat" + "system" ], "summary": "Get local models", "responses": { @@ -1452,6 +1452,9 @@ const docTemplate = `{ "apiType": { "type": "string" }, + "capacityPolicy": { + "type": "string" + }, "id": { "type": "string" }, @@ -1460,6 +1463,9 @@ const docTemplate = `{ }, "name": { "type": "string" + }, + "slots": { + "type": "integer" } } }, diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index 4f18c145..ca1ecf7e 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -1314,7 +1314,7 @@ "application/json" ], "tags": [ - "chat" + "system" ], "summary": "Get local models", "responses": { @@ -1445,6 +1445,9 @@ "apiType": { "type": "string" }, + "capacityPolicy": { + "type": "string" + }, "id": { "type": "string" }, @@ -1453,6 +1456,9 @@ }, "name": { "type": "string" + }, + "slots": { + "type": "integer" } } }, diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index 618ad9cb..eb941c1b 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -4,12 +4,16 @@ definitions: properties: apiType: type: string + capacityPolicy: + type: string id: type: string model: type: string name: type: string + slots: + type: integer type: object genericchatstorage.Chat: properties: @@ -1452,7 +1456,7 @@ paths: type: array summary: Get local models tags: - - chat + - system /wallet: delete: description: Remove wallet from proxy storage diff --git a/proxy-router/internal/aiengine/ai_engine.go b/proxy-router/internal/aiengine/ai_engine.go index 633716b0..14c6cc25 100644 --- a/proxy-router/internal/aiengine/ai_engine.go +++ b/proxy-router/internal/aiengine/ai_engine.go @@ -71,10 +71,12 @@ func (a *AiEngine) GetLocalModels() ([]LocalModel, error) { IDs, modelsFromConfig := a.modelsConfigLoader.GetAll() for i, model := range modelsFromConfig { models = append(models, LocalModel{ - Id: IDs[i], - Name: model.ModelName, - Model: model.ModelName, - ApiType: model.ApiType, + Id: IDs[i], + Name: model.ModelName, + Model: model.ModelName, + ApiType: model.ApiType, + Slots: model.ConcurrentSlots, + CapacityPolicy: model.CapacityPolicy, }) } diff --git a/proxy-router/internal/aiengine/structs.go b/proxy-router/internal/aiengine/structs.go index 99e5e568..d76039af 100644 --- a/proxy-router/internal/aiengine/structs.go +++ b/proxy-router/internal/aiengine/structs.go @@ -1,8 +1,10 @@ package aiengine type LocalModel struct { - Id string - Name string - Model string - ApiType string + Id string + Name string + Model string + ApiType string + Slots int + CapacityPolicy string } diff --git a/proxy-router/internal/proxyapi/controller_http.go b/proxy-router/internal/proxyapi/controller_http.go index 9db0fef5..38eb34cf 100644 --- a/proxy-router/internal/proxyapi/controller_http.go +++ b/proxy-router/internal/proxyapi/controller_http.go @@ -157,7 +157,7 @@ func (c *ProxyController) Prompt(ctx *gin.Context) { // GetLocalModels godoc // // @Summary Get local models -// @Tags chat +// @Tags system // @Produce json // @Success 200 {object} []aiengine.LocalModel // @Router /v1/models [get] From a8b69f3d46516f1b197d26268ef160d763053626 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Wed, 20 Nov 2024 11:05:36 -0500 Subject: [PATCH 05/41] updated desktop ui defaults --- docs/ui-desktop.all.env | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ui-desktop.all.env b/docs/ui-desktop.all.env index d8f01d21..470aefe5 100644 --- a/docs/ui-desktop.all.env +++ b/docs/ui-desktop.all.env @@ -8,18 +8,18 @@ BYPASS_AUTH=false # Chain ID for blockchain network (required) # Ethereum Chain ID (must be a number) -# TESTNET: 421614, MAINNET: 1 +# TESTNET: 421614, MAINNET: 42161 CHAIN_ID= # Default currency symbol for sellers (default is "BTC") DEFAULT_SELLER_CURRENCY=BTC # Diamond contract address for the chain (required) -# TESTNET: 0xb8C55cD613af947E73E262F0d3C54b7211Af16CF, MAINNET: TBD +# TESTNET: 0xb8C55cD613af947E73E262F0d3C54b7211Af16CF, MAINNET: 0xDE819AaEE474626E3f34Ef0263373357e5a6C71b DIAMOND_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF # Display name for the application (optional) -DISPLAY_NAME= +DISPLAY_NAME=MorpheusUI # Blockchain explorer URL (optional, must be a valid URL) EXPLORER_URL= @@ -29,7 +29,7 @@ EXPLORER_URL= PROXY_WEB_DEFAULT_PORT=8082 # Token contract address for the main token (required) -# TESTNET: 0x34a285a1b1c166420df5b6630132542923b5b27e, MAINNET: TBD +# TESTNET: 0x34a285a1b1c166420df5b6630132542923b5b27e, MAINNET: 0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e # Symbol for the primary token (default is "MOR") From 1ecc9d7df71c7fbfbb8cf7c07dae2c10c7c0f579 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Wed, 20 Nov 2024 15:41:56 -0500 Subject: [PATCH 06/41] started proxy-router troubleshooting --- docs/99-troubleshooting.md | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/99-troubleshooting.md diff --git a/docs/99-troubleshooting.md b/docs/99-troubleshooting.md new file mode 100644 index 00000000..d1af067b --- /dev/null +++ b/docs/99-troubleshooting.md @@ -0,0 +1,41 @@ +# Troubleshooting guides for proxy-router and MorpheusUI desktop applications +* These are some common scenarios and failure modes to watch out for when running the proxy-router and MorpheusUI desktop applications. +* One of the most critical things to remember, especially about the desktop application (MorpheusUI) or the Swagger API (http://localhost:8082/swagger/index.html) is that you **must** have a good startup of the proxy-router. + * If the proxy-router is not running or is not able to connect to the blockchain node, the desktop application will not be able to talk to the blockchain, manage your wallet or send prompts to the provider. + * The proxy-router is the bridge between the blockchain node and the desktop application. It is responsible for routing prompts and responses between the consumer and provider. +* One of the best ways to observe the health of the proxy-router is to start it (either `./proxy-router` or `./mor-launch` for the release) from a terminal or command line session where you can see the output in real-time. +* There are also many options for adding log destination and details of log entries (please see [proxy-router.all.env](./proxy-router.all.env) #Logging Configuration section for more information. + +## Proxy-Router +### Proxy-Router is not starting +* **Expected Result or "How do I know it's good?":** + * The proxy-router should start successfully and be able to connect to the blockchain node and listening on both 8082 and 3333 ports (these are the default ports) + * In the terminal or logs after startup, you should see the following messages with regard to the proxy-router (there will be other messages as well): + ``` + INFO HTTP http server is listening: 0.0.0.0:8082 + INFO TCP tcp server is listening: 0.0.0.0:3333 + ``` + * The other way to verify that the proxy-router has started is to query the Swagger API at http://localhost:8082/swagger/index.html (or whatever url you've set in the .env file for your proxy-router IP or DNS address) and see the API documentation. + +* **Symptoms:** The proxy-router is not starting or is crashing immediately after starting. +* **Possible Causes:** + * **.env file misconfiguration** (e.g. missing or incorrect values) + * These four items MUST be accurate to the chain and your OS., Use the example files `/proxy-router/env.main.example` to make sure you have the correct values: + * `DIAMOND_CONTRACT_ADDRESS=` + * `MOR_TOKEN_ADDRESS=` + * `EXPLORER_API_URL=` + * `ETH_NODE_CHAIN_ID=` + * `PROXY_STORAGE_PATH=` + * If you are running the proxy-router by itself (without the UI), you will need to set the private key of your provider wallet in the `WALLET_PRIVATE_KEY=` + * **IF** you use your own ETH node (Alchemy, Infura, etc.) to communicate with the blockchain, you will need to make sure that the `ETH_NODE_URL=` entry in the .env file is correct for your chain. + * We recommend https:// instead of wss:// for the ETH_NODE_URL ...which also means that `ETH_NODE_USE_SUBSCRIPTIONS=false` should be set in the .env file + + * **models-config.json misconfiguration** (e.g. missing or incorrect values) + * The `models-config.json` file is used to direct the proxy to find the models that the proxy-router will use to route prompts and responses between consumers and providers. + * Ensure that: + * the `MODELS_CONFIG_PATH=` in your .env file is correct and points to the correct directory and file and has the right permissions + * the `models-confi.json` should follow the formatting shown in the example file [models-config.json.md](./models-config.json.md) +* **Resolution:** + * Check the .env and models-config.json file configuration and ensure that the blockchain node is accessible. + * Restart the proxy-router and check the logs for any errors related to the connection. + \ No newline at end of file From 44d76d5312c2a1befdef3dd9552fcb41b8a4ec6b Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 21 Nov 2024 14:01:48 +0200 Subject: [PATCH 07/41] expose API URL --- proxy-router/docs/docs.go | 3 +++ proxy-router/docs/swagger.json | 3 +++ proxy-router/docs/swagger.yaml | 2 ++ proxy-router/internal/aiengine/ai_engine.go | 1 + proxy-router/internal/aiengine/structs.go | 1 + 5 files changed, 10 insertions(+) diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index 33c6d4f6..f8967e27 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -1452,6 +1452,9 @@ const docTemplate = `{ "apiType": { "type": "string" }, + "apiUrl": { + "type": "string" + }, "capacityPolicy": { "type": "string" }, diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index ca1ecf7e..238375b7 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -1445,6 +1445,9 @@ "apiType": { "type": "string" }, + "apiUrl": { + "type": "string" + }, "capacityPolicy": { "type": "string" }, diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index eb941c1b..3e3ae7d6 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -4,6 +4,8 @@ definitions: properties: apiType: type: string + apiUrl: + type: string capacityPolicy: type: string id: diff --git a/proxy-router/internal/aiengine/ai_engine.go b/proxy-router/internal/aiengine/ai_engine.go index 14c6cc25..c66f595c 100644 --- a/proxy-router/internal/aiengine/ai_engine.go +++ b/proxy-router/internal/aiengine/ai_engine.go @@ -75,6 +75,7 @@ func (a *AiEngine) GetLocalModels() ([]LocalModel, error) { Name: model.ModelName, Model: model.ModelName, ApiType: model.ApiType, + ApiUrl: model.ApiURL, Slots: model.ConcurrentSlots, CapacityPolicy: model.CapacityPolicy, }) diff --git a/proxy-router/internal/aiengine/structs.go b/proxy-router/internal/aiengine/structs.go index d76039af..7af26081 100644 --- a/proxy-router/internal/aiengine/structs.go +++ b/proxy-router/internal/aiengine/structs.go @@ -5,6 +5,7 @@ type LocalModel struct { Name string Model string ApiType string + ApiUrl string Slots int CapacityPolicy string } From e8795f90628931820c942cd828b3ceed202db590 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Thu, 21 Nov 2024 09:48:49 -0500 Subject: [PATCH 08/41] add gitlab trigger for build and doc cleanup --- .github/workflows/build.yml | 2 +- .github/workflows/trigger-gitlab.yml | 39 ++++++++++++++++++++++++++++ LICENSE | 2 +- docs/models-config.json.md | 6 ++--- proxy-router/LICENSE | 27 +++++++++---------- 5 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/trigger-gitlab.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57fec873..c3b3a695 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -314,7 +314,7 @@ jobs: name: mor-launch-win-x64.zip release: - if: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg')) || github.event.inputs.create_release == 'true' }} + if: ${{ github.repository =='Lumerin-protocol/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg')) || github.event.inputs.create_release == 'true' }} runs-on: ubuntu-latest needs: - Ubuntu-22-x64 diff --git a/.github/workflows/trigger-gitlab.yml b/.github/workflows/trigger-gitlab.yml new file mode 100644 index 00000000..d922f53f --- /dev/null +++ b/.github/workflows/trigger-gitlab.yml @@ -0,0 +1,39 @@ +name: Trigger GitLab Pipeline + +on: + push: + branches: + - dev + - stg + - main + +jobs: + trigger_gitlab_pipeline: + runs-on: ubuntu-latest + + if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} + + steps: + - name: Determine GitLab Target Branch + id: set_target_branch + run: | + if [ "${{ github.ref_name }}" == "dev" ]; then + echo "gitlab_branch=dev" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "stg" ]; then + echo "gitlab_branch=stg" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "main" ]; then + echo "gitlab_branch=main" >> $GITHUB_ENV + else + echo "This branch is not configured to trigger GitLab pipelines." + exit 1 + fi + + - name: Trigger GitLab Pipeline + run: | + echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }}" + curl --request POST \ + --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ + --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ + --form "ref=${{ env.gitlab_branch }}" \ + --form "variables[SOURCE_REPO]=${{ github.repository }}" \ + --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}" \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5af4dd1c..f5b64120 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/docs/models-config.json.md b/docs/models-config.json.md index dee8d26b..0452b7a8 100644 --- a/docs/models-config.json.md +++ b/docs/models-config.json.md @@ -1,5 +1,5 @@ # Example models config file. Local model configurations are stored in this file -* `rooot_key` (required) is the model id +* `root_key` (required) is the model id * `modelName` (required) is the name of the model * `apiType` (required) is the type of the model api. Currently supported values are "prodia" and "openai" * `apiUrl` (required) is the url of the LLM server or model API @@ -8,13 +8,13 @@ * `capacityPolicy` (optional) can be one of the following: "idle_timeout", "simple" ## Examples of models-config.json entries -* The first key (`0x6a...9018`) is the model id of the local default model (llama2) +* The first key (`0x000...0000`) is the model id of the local default model (llama2) * The middle two keys are examples of externally hosted and owned models where the morpheus-proxy-router enables proxying requests to the external model API * The last key is an example of a model hosted on a server owned by the morpheus-proxy-router operator ```bash { - "0x6a4813e866a48da528c533e706344ea853a1d3f21e37b4c8e7ffd5ff25779018": { + "0x0000000000000000000000000000000000000000000000000000000000000000": { "modelName": "llama2", "apiType": "openai", "apiUrl": "http://localhost:8080/v1" diff --git a/proxy-router/LICENSE b/proxy-router/LICENSE index 424057e1..5af4dd1c 100644 --- a/proxy-router/LICENSE +++ b/proxy-router/LICENSE @@ -1,20 +1,21 @@ -The MIT License (MIT) +MIT License -Copyright (c) Titan Industries +Copyright (c) 2024 Morpheus -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 24c565aa7102e7df693825a56af22bfee2458ef9 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Thu, 21 Nov 2024 09:56:23 -0500 Subject: [PATCH 09/41] ensure all builds and triggers only run in Lumerin-protocol fork --- .github/workflows/build.yml | 4 ++++ .github/workflows/trigger-gitlab.yml | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3b3a695..5baf7189 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,7 @@ defaults: jobs: Ubuntu-22-x64: + if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} runs-on: ubuntu-22.04 steps: - name: Clone @@ -94,6 +95,7 @@ jobs: name: mor-launch-ubuntu-x64.zip macOS-13-x64: + if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} runs-on: macos-13 steps: - name: Clone @@ -165,6 +167,7 @@ jobs: name: mor-launch-macos-x64.zip macOS-14-arm64: + if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} runs-on: macos-14 steps: - name: Clone @@ -236,6 +239,7 @@ jobs: name: mor-launch-macos-arm64.zip Windows-avx2-x64: + if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} runs-on: windows-latest steps: - name: Clone diff --git a/.github/workflows/trigger-gitlab.yml b/.github/workflows/trigger-gitlab.yml index d922f53f..1ad23c51 100644 --- a/.github/workflows/trigger-gitlab.yml +++ b/.github/workflows/trigger-gitlab.yml @@ -10,9 +10,7 @@ on: jobs: trigger_gitlab_pipeline: runs-on: ubuntu-latest - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} - steps: - name: Determine GitLab Target Branch id: set_target_branch From aff35755fdc1a46ef6d2c7ba9e6e2740e4efdaf2 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Thu, 21 Nov 2024 10:17:45 -0500 Subject: [PATCH 10/41] consolidated workflow back to single file and trigger deploy after builds are successful. --- .github/workflows/build.yml | 41 +++++++++++++++++++++++++++- .github/workflows/trigger-gitlab.yml | 37 ------------------------- 2 files changed, 40 insertions(+), 38 deletions(-) delete mode 100644 .github/workflows/trigger-gitlab.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5baf7189..286d68ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: CI +name: CI-CD on: workflow_dispatch: @@ -376,3 +376,42 @@ jobs: } } + trigger_gitlab_pipeline: + runs-on: ubuntu-latest + if: ${{ github.repository =='Lumerin-protocol/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/dev')) }} + needs: + - Ubuntu-22-x64 + - macOS-13-x64 + - macOS-14-arm64 + - Windows-avx2-x64 + steps: + - name: Determine GitLab Target Branch + id: set_target_branch + run: | + if [ "${{ github.ref_name }}" == "dev" ]; then + echo "gitlab_branch=dev" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "stg" ]; then + echo "gitlab_branch=stg" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "main" ]; then + echo "gitlab_branch=main" >> $GITHUB_ENV + else + echo "This branch is not configured to trigger GitLab pipelines." + exit 1 + fi + + - name: Trigger GitLab Pipeline + run: | + echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }}" + response=$(curl --write-out "%{http_code}" --silent --output /dev/null \ + --request POST \ + --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ + --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ + --form "ref=${{ env.gitlab_branch }}" \ + --form "variables[SOURCE_REPO]=${{ github.repository }}" \ + --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}") + + if [ "$response" -ne 200 ]; then + echo "Failed to trigger GitLab pipeline. HTTP status: $response" + exit 1 + fi + diff --git a/.github/workflows/trigger-gitlab.yml b/.github/workflows/trigger-gitlab.yml deleted file mode 100644 index 1ad23c51..00000000 --- a/.github/workflows/trigger-gitlab.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Trigger GitLab Pipeline - -on: - push: - branches: - - dev - - stg - - main - -jobs: - trigger_gitlab_pipeline: - runs-on: ubuntu-latest - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} - steps: - - name: Determine GitLab Target Branch - id: set_target_branch - run: | - if [ "${{ github.ref_name }}" == "dev" ]; then - echo "gitlab_branch=dev" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "stg" ]; then - echo "gitlab_branch=stg" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "main" ]; then - echo "gitlab_branch=main" >> $GITHUB_ENV - else - echo "This branch is not configured to trigger GitLab pipelines." - exit 1 - fi - - - name: Trigger GitLab Pipeline - run: | - echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }}" - curl --request POST \ - --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ - --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ - --form "ref=${{ env.gitlab_branch }}" \ - --form "variables[SOURCE_REPO]=${{ github.repository }}" \ - --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}" \ No newline at end of file From f60783c080c5141cc492db6e67e4f5fe78b21e7a Mon Sep 17 00:00:00 2001 From: shev Date: Fri, 22 Nov 2024 18:34:21 +0100 Subject: [PATCH 11/41] fix: invalid headers parsing --- proxy-router/internal/aiengine/openai.go | 10 ++++++++-- proxy-router/internal/handlers/tcphandlers/tcp.go | 3 +-- proxy-router/internal/proxyapi/controller_morrpc.go | 2 +- proxy-router/internal/proxyapi/proxy_receiver.go | 2 +- proxy-router/internal/proxyapi/proxy_sender.go | 2 -- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/proxy-router/internal/aiengine/openai.go b/proxy-router/internal/aiengine/openai.go index cf1edb3b..a6596379 100644 --- a/proxy-router/internal/aiengine/openai.go +++ b/proxy-router/internal/aiengine/openai.go @@ -62,8 +62,7 @@ func (a *OpenAI) Prompt(ctx context.Context, compl *openai.ChatCompletionRequest } defer resp.Body.Close() - contentType := resp.Header.Get(c.HEADER_CONTENT_TYPE) - if contentType == c.CONTENT_TYPE_EVENT_STREAM { + if isContentTypeStream(resp.Header) { return a.readStream(ctx, resp.Body, cb) } @@ -130,6 +129,13 @@ func isStreamFinished(data string) bool { return strings.Index(data, StreamDone) != -1 } +func isContentTypeStream(header http.Header) bool { + contentType := header.Get(c.HEADER_CONTENT_TYPE) + cTypeParams := strings.Split(contentType, ";") + cType := strings.TrimSpace(cTypeParams[0]) + return cType == c.CONTENT_TYPE_EVENT_STREAM +} + const ( StreamDone = "[DONE]" StreamDataPrefix = "data: " diff --git a/proxy-router/internal/handlers/tcphandlers/tcp.go b/proxy-router/internal/handlers/tcphandlers/tcp.go index 92e54142..855d2f6e 100644 --- a/proxy-router/internal/handlers/tcphandlers/tcp.go +++ b/proxy-router/internal/handlers/tcphandlers/tcp.go @@ -38,8 +38,7 @@ func NewTCPHandler( sourceLog.Errorf("Error sending message: %s", err) return err } - sourceLog.Debug("sent message") - return err + return nil }) if err != nil { sourceLog.Errorf("Error handling message: %s\nMessage: %s\n", err, msg) diff --git a/proxy-router/internal/proxyapi/controller_morrpc.go b/proxy-router/internal/proxyapi/controller_morrpc.go index d4cbbbde..14af93a3 100644 --- a/proxy-router/internal/proxyapi/controller_morrpc.go +++ b/proxy-router/internal/proxyapi/controller_morrpc.go @@ -98,7 +98,7 @@ func (s *MORRPCController) sessionPrompt(ctx context.Context, msg m.RPCMessage, return lib.WrapError(ErrValidation, err) } - sourceLog.Debugf("Received prompt from session %s, timestamp: %s", req.SessionID, req.Timestamp) + sourceLog.Debugf("received prompt from session %s, timestamp: %d", req.SessionID, req.Timestamp) session, err := s.sessionRepo.GetSession(ctx, req.SessionID) if err != nil { return fmt.Errorf("session cannot be loaded %s", err) diff --git a/proxy-router/internal/proxyapi/proxy_receiver.go b/proxy-router/internal/proxyapi/proxy_receiver.go index de4c1fa4..43272750 100644 --- a/proxy-router/internal/proxyapi/proxy_receiver.go +++ b/proxy-router/internal/proxyapi/proxy_receiver.go @@ -174,7 +174,7 @@ func (s *ProxyReceiver) SessionRequest(ctx context.Context, msgID string, reqID } func (s *ProxyReceiver) SessionReport(ctx context.Context, msgID string, reqID string, session *storages.Session, sourceLog lib.ILogger) (*msg.RpcResponse, error) { - sourceLog.Debugf("Received session report request for %s, timestamp: %s", session.Id) + sourceLog.Debugf("received session report request for %s", session.Id) tps := 0 ttft := 0 diff --git a/proxy-router/internal/proxyapi/proxy_sender.go b/proxy-router/internal/proxyapi/proxy_sender.go index febfd49c..e02cf385 100644 --- a/proxy-router/internal/proxyapi/proxy_sender.go +++ b/proxy-router/internal/proxyapi/proxy_sender.go @@ -468,8 +468,6 @@ func (p *ProxyServiceSender) rpcRequestStreamV2( } } - p.log.Debugf("Received stream msg: %v", msg) - if msg.Error != nil { return nil, ttftMs, totalTokens, lib.WrapError(ErrResponseErr, fmt.Errorf("error: %v, data: %v", msg.Error.Message, msg.Error.Data)) } From 4353d0e3c15191ea31e0b2864ad76194c286446f Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 12:45:55 -0500 Subject: [PATCH 12/41] jethro recommended changes ! origin and test on feature branch --- .github/workflows/build.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 286d68ba..a7466fbf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,8 @@ on: branches: - main - stg + - fix-examples + paths: ['.github/workflows/**', '**/Makefile', '**/*.go', '**/*.json', '**/*.yml', '**/*.ts', '**/*.js'] pull_request: types: [opened, reopened, synchronize] @@ -26,7 +28,7 @@ defaults: jobs: Ubuntu-22-x64: - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: ubuntu-22.04 steps: - name: Clone @@ -95,7 +97,7 @@ jobs: name: mor-launch-ubuntu-x64.zip macOS-13-x64: - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: macos-13 steps: - name: Clone @@ -167,7 +169,7 @@ jobs: name: mor-launch-macos-x64.zip macOS-14-arm64: - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: macos-14 steps: - name: Clone @@ -239,7 +241,7 @@ jobs: name: mor-launch-macos-arm64.zip Windows-avx2-x64: - if: ${{ github.repository == 'Lumerin-protocol/Morpheus-Lumerin-Node' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: windows-latest steps: - name: Clone @@ -318,7 +320,7 @@ jobs: name: mor-launch-win-x64.zip release: - if: ${{ github.repository =='Lumerin-protocol/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg')) || github.event.inputs.create_release == 'true' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/fix-envexamples')) || github.event.inputs.create_release == 'true' }} runs-on: ubuntu-latest needs: - Ubuntu-22-x64 @@ -378,7 +380,7 @@ jobs: trigger_gitlab_pipeline: runs-on: ubuntu-latest - if: ${{ github.repository =='Lumerin-protocol/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/dev')) }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/dev'|| github.ref == 'refs/heads/fix-envexamples')) }} needs: - Ubuntu-22-x64 - macOS-13-x64 From d47af2bb88383fbf1978fdce8ac7b5e229c020b4 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 12:50:18 -0500 Subject: [PATCH 13/41] with proper feature branch naming --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7466fbf..72aee189 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: branches: - main - stg - - fix-examples + - fix-envexamples paths: ['.github/workflows/**', '**/Makefile', '**/*.go', '**/*.json', '**/*.yml', '**/*.ts', '**/*.js'] pull_request: From 8e6c5fe939e9d8c761727a767bbeb4b617f944d3 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 13:12:36 -0500 Subject: [PATCH 14/41] changed stg branch actions to test and removed featurbranch triggers --- .github/workflows/build.yml | 9 ++++----- docs/03-provider-offer.md | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72aee189..c0b286ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,8 +10,7 @@ on: push: branches: - main - - stg - - fix-envexamples + - test paths: ['.github/workflows/**', '**/Makefile', '**/*.go', '**/*.json', '**/*.yml', '**/*.ts', '**/*.js'] pull_request: @@ -320,7 +319,7 @@ jobs: name: mor-launch-win-x64.zip release: - if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/fix-envexamples')) || github.event.inputs.create_release == 'true' }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' )) || github.event.inputs.create_release == 'true' }} runs-on: ubuntu-latest needs: - Ubuntu-22-x64 @@ -380,7 +379,7 @@ jobs: trigger_gitlab_pipeline: runs-on: ubuntu-latest - if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/stg' || github.ref == 'refs/heads/dev'|| github.ref == 'refs/heads/fix-envexamples')) }} + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev' )) }} needs: - Ubuntu-22-x64 - macOS-13-x64 @@ -392,7 +391,7 @@ jobs: run: | if [ "${{ github.ref_name }}" == "dev" ]; then echo "gitlab_branch=dev" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "stg" ]; then + elif [ "${{ github.ref_name }}" == "test" ]; then echo "gitlab_branch=stg" >> $GITHUB_ENV elif [ "${{ github.ref_name }}" == "main" ]; then echo "gitlab_branch=main" >> $GITHUB_ENV diff --git a/docs/03-provider-offer.md b/docs/03-provider-offer.md index f5cd687a..f4203522 100644 --- a/docs/03-provider-offer.md +++ b/docs/03-provider-offer.md @@ -1,5 +1,10 @@ # Creating Provider, Model and Bid on the Blockchain: +**Contract Minimums** As of 11/22/2024: +* "providerMinStake": `200000000000000000`, (0.2 MOR) +* "modelMinStake": `100000000000000000`, (0.1 MOR) +* "marketplaceBidFee": `300000000000000000`, (0.3 MOR) +* "bidPricePerSeconMin `10000000000`, (0.00000001 MOR) **Needed information (samples):** * Provider/Owner: `0x9E26Fea97F7d644BAf62d0e20e4d4b8F836C166c` # Your ERC-20 Wallet with MOR & ETH @@ -7,7 +12,7 @@ * Model ID: `0xe1e6e3e77148d140065ef2cd4fba7f4ae59c90e1639184b6df5c84` # Random 32byte/hex that you generate * ipfcCID: `0xc2d3a5e4f9b7c1a2c8f0b1d5e6c78492fa7bcd34e9a3b9c9e18f25d3be47a1f6` # Another 32byte/hex random for future use * Model Name: `CapybaraHermes-v2.5-Mistral-7b` # Human Readable name for the model -* Bid Cost: `200000000000` (1*10^18 or ~7MOR) # What will the model cost per second to use +* Bid Cost: `10000000000` (0.00000001 MOR) # What will the model cost per second to use ## Steps 1. To complete these steps, you will need to be running the proxy-router and also have access to the API Port (default=8082)for the Swagger API Interface @@ -17,21 +22,22 @@ 1. http://localhost:8082/swagger/index.html#/transactions/post_blockchain_approve 1. Spender Address = Diamond Contract 1. Authorized Amount = remember that this is in the form `1*10^18` so make sure there's enough MOR granted to cover the contract fees + 1. To become a provider, offer a model and offer a bid based on the current minimums, you will need to authorize the contract to spend at least `600000000000000000` (0.6 MOR) on your behalf 1. The Diamond Contract is now authorized to spend MOR on provider's behalf 1. Create Provider in the Diamond contract via swagger api: - 1. Start proxy-router - 1. http://localhost:8082/swagger/index.html#/providers/post_blockchain_providers - 1. Enter required fields: - 1. addStake = Amount of stake for provider to risk - Stake can be 0 now + 1. http://localhost:8082/swagger/index.html#/providers/post_blockchain_providers + 1. addStake = Amount of stake for provider to risk + - Minimum Provider stake is `200000000000000000`, (0.2 MOR) + - Provider stake to become a subnet is `10000000000000000000000`, (10,000 MOR) 1. Endpoint = Your **publicly accessible endpoint** for the proxy-router provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` 1. Create Model in the contract: 1. Go to http://localhost:8082/swagger/index.html#/models/post_blockchain_models and enter - 1. modelId: random 32byte/hex that will uniquely identify model (uuid) + 1. modelId: random 32byte/hex that will be used in conjunction with providerId to uniquely identify model (uuid) 1. ipfsCID: another random32byte/hex for future use (model library) 1. Fee: fee for the model usage - 1. addStake: stake for model usage + 1. addStake: "modelMinStake": `100000000000000000`, (0.1 MOR) 1. Owner: Provider Wallet Address 1. name: Human Readable model like "Llama 2.0" or "Mistral 2.5" or "Collective Cognition 1.1" 1. tags: array of tag strings for the model @@ -45,8 +51,8 @@ 1. Offer Model Bid in the contract: 1. Navigate to http://localhost:8082/swagger/index.html#/bids/post_blockchain_bids and enter - 1. modelID: Model ID Created in last step: - 1. pricePerSecond: this is in 1*10^18 format so 100000000000 should make 5 minutes for the session around 37.5 MOR - 1. Click Execute + 1. modelID: Model ID Created above + 1. pricePerSecond: "bidPricePerSeconMin `10000000000`, (0.00000001 MOR) + 1. Click Execute and capture the `bidID` from the JSON response ---------------- \ No newline at end of file From ca7481332aa5f058f225ddd81b6437209664925a Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 13:55:07 -0500 Subject: [PATCH 15/41] fixed ci actions for new branch name and updated docs --- .github/actions/copy_env_files/action.yml | 2 +- .github/actions/gen_tag_name/action.yml | 2 +- .github/workflows/proxy-router.main.env | 31 +- .github/workflows/proxy-router.test.env | 28 +- .github/workflows/ui-desktop.main.env | 32 +- .github/workflows/ui-desktop.test.env | 29 +- docs/02-provider-setup.md | 20 +- proxy-router/.env.example | 33 ++ proxy-router/.env.example.win | 33 ++ proxy-router/.env.main.example | 19 - proxy-router/.env.main.example.win | 19 - proxy-router/.env.test.example | 19 - proxy-router/.env.test.example.win | 19 - proxy-router/.gitlab-ci.yml | 401 ---------------------- readme.md | 2 +- 15 files changed, 177 insertions(+), 512 deletions(-) create mode 100644 proxy-router/.env.example create mode 100644 proxy-router/.env.example.win delete mode 100644 proxy-router/.env.main.example delete mode 100644 proxy-router/.env.main.example.win delete mode 100644 proxy-router/.env.test.example delete mode 100644 proxy-router/.env.test.example.win delete mode 100644 proxy-router/.gitlab-ci.yml diff --git a/.github/actions/copy_env_files/action.yml b/.github/actions/copy_env_files/action.yml index 16d400ea..f1688098 100644 --- a/.github/actions/copy_env_files/action.yml +++ b/.github/actions/copy_env_files/action.yml @@ -12,7 +12,7 @@ runs: cp ./.github/workflows/proxy-router.main.env ./proxy-router/.env cp ./.github/workflows/proxy-router.main.env .env cp ./.github/workflows/ui-desktop.main.env ./ui-desktop/.env - elif [[ "${GITHUB_REF}" == "refs/heads/stg" ]]; then + elif [[ "${GITHUB_REF}" == "refs/heads/test" ]]; then cp ./.github/workflows/proxy-router.test.env ./proxy-router/.env cp ./.github/workflows/proxy-router.test.env .env cp ./.github/workflows/ui-desktop.test.env ./ui-desktop/.env diff --git a/.github/actions/gen_tag_name/action.yml b/.github/actions/gen_tag_name/action.yml index 50a11dbc..a167a8ac 100644 --- a/.github/actions/gen_tag_name/action.yml +++ b/.github/actions/gen_tag_name/action.yml @@ -11,7 +11,7 @@ runs: echo $SHORT_HASH if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then PREFIX="main-" - elif [[ "${GITHUB_REF}" == "refs/heads/stg" ]]; then + elif [[ "${GITHUB_REF}" == "refs/heads/test" ]]; then PREFIX="test-" else PREFIX="dev-" diff --git a/.github/workflows/proxy-router.main.env b/.github/workflows/proxy-router.main.env index 3bd12acc..c967e546 100644 --- a/.github/workflows/proxy-router.main.env +++ b/.github/workflows/proxy-router.main.env @@ -1,13 +1,36 @@ # This file is used to set the environment variables for the cicd workflow -# To Generate the STG or TestNet release -# Contract and Token current as of 11/15/2024 (and are using the same values as the testnet) -# ...when mainnet release is imminent, update the values +# Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env +# Includes both TestNet and MainNet values, uncomment sections as desired +# Wallet_Private_Key is not needed if you will be running the MorpheusUI in conjunction with proxy-router +WALLET_PRIVATE_KEY= + +# MAINNET VALUES (only MAINNET or TESTNET section should be uncommented) DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 EXPLORER_API_URL="https://api.arbiscan.io/api" ETH_NODE_CHAIN_ID=42161 + +# TESTNET VALUES +# DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF +# MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e +# EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" +# ETH_NODE_CHAIN_ID=421614 + +# COMMON +PROXY_ADDRESS=0.0.0.0:3333 +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 +MODELS_CONFIG_PATH= +PROVIDER_ALLOW_LIST= +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ETH_NODE_LEGACY_TX=false PROXY_STORE_CHAT_CONTEXT=true # PROXY_STORAGE_PATH=.\data\ (for windows) PROXY_STORAGE_PATH=./data/ -LOG_COLOR=true \ No newline at end of file +LOG_COLOR=true + +# Set to true to reset mac keychain on start (MorpheusUI only on Mac) +APP_RESET_KEYCHAIN=false \ No newline at end of file diff --git a/.github/workflows/proxy-router.test.env b/.github/workflows/proxy-router.test.env index 8a3c0541..6e90a60e 100644 --- a/.github/workflows/proxy-router.test.env +++ b/.github/workflows/proxy-router.test.env @@ -1,12 +1,36 @@ # This file is used to set the environment variables for the cicd workflow -# To Generate the STG or TestNet release # Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env +# Includes both TestNet and MainNet values, uncomment sections as desired +# Wallet_Private_Key is not needed if you will be running the MorpheusUI in conjunction with proxy-router +WALLET_PRIVATE_KEY= + +# MAINNET VALUES (only MAINNET or TESTNET section should be uncommented) +# DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +# MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +# EXPLORER_API_URL="https://api.arbiscan.io/api" +# ETH_NODE_CHAIN_ID=42161 + +# TESTNET VALUES DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" ETH_NODE_CHAIN_ID=421614 + +# COMMON +PROXY_ADDRESS=0.0.0.0:3333 +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 +MODELS_CONFIG_PATH= +PROVIDER_ALLOW_LIST= +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ETH_NODE_LEGACY_TX=false PROXY_STORE_CHAT_CONTEXT=true # PROXY_STORAGE_PATH=.\data\ (for windows) PROXY_STORAGE_PATH=./data/ -LOG_COLOR=true \ No newline at end of file +LOG_COLOR=true + +# Set to true to reset mac keychain on start (MorpheusUI only on Mac) +APP_RESET_KEYCHAIN=false \ No newline at end of file diff --git a/.github/workflows/ui-desktop.main.env b/.github/workflows/ui-desktop.main.env index 79fe192c..f35cf4d4 100644 --- a/.github/workflows/ui-desktop.main.env +++ b/.github/workflows/ui-desktop.main.env @@ -1,20 +1,32 @@ # This file is used to set the environment variables for the cicd workflow -# To Generate the MAIN or MAINNET release -# Contract and Token current as of 11/5/2024 (and are using the same values as the testnet) -# ...when mainnet release is imminent, update the values -BYPASS_AUTH=false +# Contract and Token current as of 11/5/2024 + +# MAINNET VALUES CHAIN_ID=42161 -DEBUG=false -DEFAULT_SELLER_CURRENCY=BTC -DEV_TOOLS=false DIAMOND_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b DISPLAY_NAME=Arbitrum EXPLORER_URL=https://arbiscan.io/tx/{{hash}} +TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +DEV_TOOLS=false +SYMBOL_ETH=ETH +SYMBOL_COIN=MOR + +# TESTNET VALUES +# CHAIN_ID=421614 +# DIAMOND_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF +# DISPLAY_NAME=Sepolia Arbitrum +# EXPLORER_URL=https://sepolia.arbiscan.io/tx/{{hash}} +# TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e +# DEV_TOOLS=true +# SYMBOL_ETH=saETH +# SYMBOL_COIN=saMOR + +# COMMON +BYPASS_AUTH=false +DEBUG=false +DEFAULT_SELLER_CURRENCY=BTC IGNORE_DEBUG_LOGS=false PROXY_WEB_DEFAULT_PORT=8082 SENTRY_DSN= -SYMBOL_ETH=ETH -SYMBOL_COIN=MOR -TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 TRACKING_ID= FAILOVER_ENABLED= \ No newline at end of file diff --git a/.github/workflows/ui-desktop.test.env b/.github/workflows/ui-desktop.test.env index c60be473..57007290 100644 --- a/.github/workflows/ui-desktop.test.env +++ b/.github/workflows/ui-desktop.test.env @@ -1,19 +1,32 @@ # This file is used to set the environment variables for the cicd workflow -# To Generate the STG or TestNet release # Contract and Token current as of 11/5/2024 -BYPASS_AUTH=false + +# MAINNET VALUES +# CHAIN_ID=42161 +# DIAMOND_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +# DISPLAY_NAME=Arbitrum +# EXPLORER_URL=https://arbiscan.io/tx/{{hash}} +# TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +# DEV_TOOLS=false +# SYMBOL_ETH=ETH +# SYMBOL_COIN=MOR + +# TESTNET VALUES CHAIN_ID=421614 -DEBUG=false -DEFAULT_SELLER_CURRENCY=BTC -DEV_TOOLS=true DIAMOND_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF DISPLAY_NAME=Sepolia Arbitrum EXPLORER_URL=https://sepolia.arbiscan.io/tx/{{hash}} +TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e +DEV_TOOLS=true +SYMBOL_ETH=saETH +SYMBOL_COIN=saMOR + +# COMMON +BYPASS_AUTH=false +DEBUG=false +DEFAULT_SELLER_CURRENCY=BTC IGNORE_DEBUG_LOGS=false PROXY_WEB_DEFAULT_PORT=8082 SENTRY_DSN= -SYMBOL_ETH=saETH -SYMBOL_COIN=saMOR -TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e TRACKING_ID= FAILOVER_ENABLED= \ No newline at end of file diff --git a/docs/02-provider-setup.md b/docs/02-provider-setup.md index a63569e1..699fc58e 100644 --- a/docs/02-provider-setup.md +++ b/docs/02-provider-setup.md @@ -18,7 +18,7 @@ 2. Source Code: * Clone the repository: `git clone -b branch https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node.git` * Mainnet Branch = `main` - * Testnet Branch = `stg` + * Testnet Branch = `test` * Development Branch = `dev`(not recommended unless directed by the development team) 1. Extract the zip to a local folder (examples) @@ -27,18 +27,22 @@ * On MacOS you may need to execute `xattr -c proxy-router` in a command window to remove the quarantine flag on MacOS 1. Environment configuration - * In most cases, to start with, the default .env file will work for the proxy-router...in some cases you will want to modify the .env file with advanced capability (log entries, private keys, private endpoints, etc) - * Choose which chain and environment you want to work with (MAIN or TEST) and select the right example .env file - * Mainnet: `env.main.example` for Max/Unix or `env.main.example.win` for Windows - * Testnet: `env.test.example` for Max/Unix or `env.test.example.win` for Windows - * Change the name of the desired file to `.env` - * Please see [proxy-router.all.env](proxy-router.all.env) for more information on the key values needed in the .env file + * In most cases, the default .env file will work for the proxy-router...In some cases you will want to modify the .env file with advanced capability (log entries, private keys, private endpoints, etc) + * Please see [proxy-router.all.env](proxy-router.all.env) for more information on the key values available in the .env file + 1. Choose OS environment you are working with: + * Linux/Mac: `env.example` or `env.example.win` for Windows + * Change the name of the desired file to `.env` + * Edit values within the file (Wallet_Private_Key, for example) as desired + 2. Choose the **blockchain** you'd like to work on...**Arbitrum MAINNET is the default** + * To operate on the Sepolia Arbitrum TESTNET, + * Edit the .env file and + * Uncomment the `TESTNET VALUES` and comment the `MAINNET VALUES` lines & save the file 1. **(OPTIONAL)** - External Provider or Pass through * In some cases you will want to leverage external or existing AI Providers in the network via their own, private API * Dependencies: * `model-config.json` file in the proxy-router directory - * proxy-router .env file for proxy-router must also be updated to include `MODELS_CONFIG_PATH=/models-config.json` + * proxy-router .env file for proxy-router must also be edited to adjust `MODELS_CONFIG_PATH=/models-config.json` * Once your provider is up and running, deploy a new model and model bid via the API interface (you will need the `model_ID` for the configuration) * Edit the model-config.json to the following json format * The JSON ID will be the ModelID that you created above, modelName, apiTYpe, apiURL and apiKey are from the external provider and specific to their offered models diff --git a/proxy-router/.env.example b/proxy-router/.env.example new file mode 100644 index 00000000..9540990b --- /dev/null +++ b/proxy-router/.env.example @@ -0,0 +1,33 @@ +# Minimalist ENV File to enable the proxy-router to run, defaults to MAINNET +# Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env +# Includes both TestNet and MainNet values, uncomment sections as desired + +WALLET_PRIVATE_KEY= + +# MAINNET VALUES (only MAINNET or TESTNET section should be uncommented) +DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +EXPLORER_API_URL="https://api.arbiscan.io/api" +ETH_NODE_CHAIN_ID=42161 +ENVIRONMENT=production + +# TESTNET VALUES +# DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF +# MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e +# EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" +# ETH_NODE_CHAIN_ID=421614 +# ENVIRONMENT=development + +# COMMON +PROXY_ADDRESS=0.0.0.0:3333 +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 +MODELS_CONFIG_PATH= +PROVIDER_ALLOW_LIST= +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ETH_NODE_LEGACY_TX=false +PROXY_STORE_CHAT_CONTEXT=true +PROXY_STORAGE_PATH=./data/ +LOG_COLOR=true \ No newline at end of file diff --git a/proxy-router/.env.example.win b/proxy-router/.env.example.win new file mode 100644 index 00000000..85f431cd --- /dev/null +++ b/proxy-router/.env.example.win @@ -0,0 +1,33 @@ +# Minimalist ENV File to enable the proxy-router to run, defaults to MAINNET +# Contract and Token current as of 11/15/2024 +# Full ENV details can be found in /docs/proxy-router.full.env +# Includes both TestNet and MainNet values, uncomment sections as desired + +WALLET_PRIVATE_KEY= + +# MAINNET VALUES (only MAINNET or TESTNET section should be uncommented) +DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b +MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 +EXPLORER_API_URL="https://api.arbiscan.io/api" +ETH_NODE_CHAIN_ID=42161 +ENVIRONMENT=production + +# TESTNET VALUES +# DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF +# MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e +# EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" +# ETH_NODE_CHAIN_ID=421614 +# ENVIRONMENT=development + +# COMMON +PROXY_ADDRESS=0.0.0.0:3333 +WEB_ADDRESS=0.0.0.0:8082 +WEB_PUBLIC_URL=http://localhost:8082 +MODELS_CONFIG_PATH= +PROVIDER_ALLOW_LIST= +ETH_NODE_USE_SUBSCRIPTIONS=false +ETH_NODE_ADDRESS= +ETH_NODE_LEGACY_TX=false +PROXY_STORE_CHAT_CONTEXT=true +PROXY_STORAGE_PATH=.\data\ +LOG_COLOR=true \ No newline at end of file diff --git a/proxy-router/.env.main.example b/proxy-router/.env.main.example deleted file mode 100644 index 1caeb27d..00000000 --- a/proxy-router/.env.main.example +++ /dev/null @@ -1,19 +0,0 @@ -# MAINNET -# Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 -# Full ENV details can be found in /docs/proxy-router.full.env - -WALLET_PRIVATE_KEY= -DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b -MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 -EXPLORER_API_URL="https://api.arbiscan.io/api" -ETH_NODE_CHAIN_ID=42161 -ETH_NODE_USE_SUBSCRIPTIONS=false -ETH_NODE_ADDRESS= -ENVIRONMENT=production -ETH_NODE_LEGACY_TX=false -PROXY_STORE_CHAT_CONTEXT=true -PROXY_STORAGE_PATH=./data/ -LOG_COLOR=true -WEB_ADDRESS=0.0.0.0:8082 -WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.main.example.win b/proxy-router/.env.main.example.win deleted file mode 100644 index 1d944bc7..00000000 --- a/proxy-router/.env.main.example.win +++ /dev/null @@ -1,19 +0,0 @@ -# MAINNET -# Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 -# Full ENV details can be found in /docs/proxy-router.full.env - -WALLET_PRIVATE_KEY= -DIAMOND_CONTRACT_ADDRESS=0xDE819AaEE474626E3f34Ef0263373357e5a6C71b -MOR_TOKEN_ADDRESS=0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86 -EXPLORER_API_URL="https://api.arbiscan.io/api" -ETH_NODE_CHAIN_ID=42161 -ETH_NODE_USE_SUBSCRIPTIONS=false -ETH_NODE_ADDRESS= -ENVIRONMENT=production -ETH_NODE_LEGACY_TX=false -PROXY_STORE_CHAT_CONTEXT=true -PROXY_STORAGE_PATH=.\data\ -LOG_COLOR=true -WEB_ADDRESS=0.0.0.0:8082 -WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.test.example b/proxy-router/.env.test.example deleted file mode 100644 index 2d1ae1a0..00000000 --- a/proxy-router/.env.test.example +++ /dev/null @@ -1,19 +0,0 @@ -# TESTNET -# Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 -# Full ENV details can be found in /docs/proxy-router.full.env - -WALLET_PRIVATE_KEY= -DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF -MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e -EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" -ETH_NODE_CHAIN_ID=421614 -ETH_NODE_USE_SUBSCRIPTIONS=false -ETH_NODE_ADDRESS= -ENVIRONMENT=development -ETH_NODE_LEGACY_TX=false -PROXY_STORE_CHAT_CONTEXT=true -PROXY_STORAGE_PATH=./data/ -LOG_COLOR=true -WEB_ADDRESS=0.0.0.0:8082 -WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.env.test.example.win b/proxy-router/.env.test.example.win deleted file mode 100644 index 21ba29f9..00000000 --- a/proxy-router/.env.test.example.win +++ /dev/null @@ -1,19 +0,0 @@ -# TESTNET -# Minimalist ENV File to enable the proxy-router to run -# Contract and Token current as of 11/15/2024 -# Full ENV details can be found in /docs/proxy-router.full.env - -WALLET_PRIVATE_KEY= -DIAMOND_CONTRACT_ADDRESS=0xb8C55cD613af947E73E262F0d3C54b7211Af16CF -MOR_TOKEN_ADDRESS=0x34a285a1b1c166420df5b6630132542923b5b27e -EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" -ETH_NODE_CHAIN_ID=421614 -ETH_NODE_USE_SUBSCRIPTIONS=false -ETH_NODE_ADDRESS= -ENVIRONMENT=development -ETH_NODE_LEGACY_TX=false -PROXY_STORE_CHAT_CONTEXT=true -PROXY_STORAGE_PATH=.\data\ -LOG_COLOR=true -WEB_ADDRESS=0.0.0.0:8082 -WEB_PUBLIC_URL=http://localhost:8082 \ No newline at end of file diff --git a/proxy-router/.gitlab-ci.yml b/proxy-router/.gitlab-ci.yml deleted file mode 100644 index eb31a558..00000000 --- a/proxy-router/.gitlab-ci.yml +++ /dev/null @@ -1,401 +0,0 @@ -variables: # keep this list alphabetically sorted - CI_AWS_TASK: "proxy-router" - ENVIRONMENT: "production" - NODE_DEF: "prt" - WEB_ADDRESS: "0.0.0.0:8080" - -stages: - - test - - build - - deploy - # - e2e-test - - release - -default: - image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest - -.ecr_login_script: &ecr_login_script | - echo "**************************" - echo "*** ECR Login to Shared Titanio-NET Repo in USE-1" - echo "**************************" - docker system prune -af - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $CI_AWS_TitanIO_NET_ECR - -.create_image_tag: &create_image_tag | - echo "**************************" - echo "*** Tag Image " - echo "**************************" - IMAGE_TAG="$(echo $CI_COMMIT_SHA | head -c 8)-$TGT_ENV-$NODE_DEF" - echo $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$IMAGE_TAG - -####################### -# TEST STAGE -####################### -lint: - inherit: - default: false - image: golangci/golangci-lint:v1.50.1-alpine - stage: test - only: - - branches - - tags - - merge_requests - script: - - golangci-lint run -v - -test: - inherit: - default: false - image: golang:1.19.3-alpine - stage: test - only: - - branches - - tags - - merge_requests - script: - - apk add --no-cache git make musl-dev gcc - - go version - - go mod download - - make test-unit - -####################### -# BUILD STAGE -####################### -.build_raw_image: &build_raw_image | - echo "**************************" - echo "*** Build RAW Image with no build arguments " - echo "**************************" - docker build \ - --build-arg COMMIT=$CI_COMMIT_SHORT_SHA \ - -t $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$IMAGE_TAG --no-cache . - - echo "**************************" - echo "*** Tag Image with $TGT_ENV-latest" - echo "**************************" - docker tag $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$IMAGE_TAG $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$TGT_ENV-latest - - echo "**************************" - echo "*** Push Images" - echo "**************************" - docker push $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$IMAGE_TAG - docker push $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$TGT_ENV-latest - -####################### -# BUILD DEVELOPMENT -####################### -bedrock-02-DEV-rawimage: - stage: build - environment: dev - needs: ["test", "lint"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - when: always - - if: $CI_COMMIT_BRANCH != "dev" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-dev - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_DEV - TGT_ENV: dev - NODE_DEF: raw - script: - - *ecr_login_script - - *create_image_tag - - *build_raw_image - - echo "$TGT_ENV Raw Image updated" - -####################### -# BUILD STAGING -####################### -bedrock-03-STG-rawimage: - stage: build - environment: stg - needs: ["test", "lint"] - rules: - - if: $CI_COMMIT_BRANCH == "stg" - when: always - - if: $CI_COMMIT_BRANCH != "stg" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-stg - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_STG - TGT_ENV: stg - NODE_DEF: raw - script: - - *ecr_login_script - - *create_image_tag - - *build_raw_image - - echo "$TGT_ENV Raw Image updated" - -####################### -# BUILD PRODUCTION/MAIN/LMN -####################### -bedrock-04-PRD-rawimage: - stage: build - environment: lmn - needs: ["test", "lint"] - rules: - - if: $CI_COMMIT_BRANCH == "main" - when: always - - if: $CI_COMMIT_BRANCH != "main" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-lmn - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_LMN - TGT_ENV: lmn - NODE_DEF: raw - script: - - *ecr_login_script - - *create_image_tag - - *build_raw_image - - echo "$TGT_ENV Raw Image updated" - -####################### -# DEPLOY STAGE -####################### -.update_raw_task_definition: &update_raw_task_definition | - echo "**************************" - echo "*** Update Validator Task Definition" - echo "*** Unique elements that should be set in calling module are Web_public_url" - echo "*** CI_WEB_PUBLIC_URL" - echo "**************************" - aws ecs describe-task-definition --region $AWS_DEFAULT_REGION --task-definition tsk-$CI_AWS_TASK > output.json - echo "**************************" - echo "*** Original Task Definition" - echo "**************************" - jq . output.json - # Update the Image, Ulimit and Env Vars - jq '.taskDefinition.containerDefinitions[].image = "'$CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$TGT_ENV-latest'" | - .taskDefinition.containerDefinitions[].ulimits = [{"name": "nofile", "softLimit": 15000, "hardLimit": 15000}] | - .taskDefinition.containerDefinitions[].environment = [ - { "name": "COMMIT", "value": "'"$(git rev-parse HEAD)"'" }, - { "name": "ETH_NODE_ADDRESS", "value": "'"$CI_ETH_NODE_ADDRESS"'" }, - { "name": "ETH_NODE_LEGACY_TX", "value": "'"$ETH_NODE_LEGACY_TX"'" }, - { "name": "ENVIRONMENT", "value": "'"$ENVIRONMENT"'" }, - { "name": "LOG_COLOR", "value": "'"$LOG_COLOR"'" }, - { "name": "LOG_JSON", "value": "'"$LOG_JSON"'" }, - { "name": "LOG_LEVEL_APP", "value": "'"$LOG_LEVEL_APP"'" }, - { "name": "LOG_LEVEL_CONNECTION", "value": "'"$LOG_LEVEL_CONNECTION"'" }, - { "name": "LOG_LEVEL_PROXY", "value": "'"$LOG_LEVEL_PROXY"'" }, - { "name": "LOG_LEVEL_SCHEDULER", "value": "'"$LOG_LEVEL_SCHEDULER"'" }, - { "name": "LOG_LEVEL_CONTRACT", "value": "'"$LOG_LEVEL_CONTRACT"'" }, - { "name": "PROXY_ADDRESS", "value": "'"$PROXY_ADDRESS"'" }, - { "name": "SYS_ENABLE", "value": "'"$SYS_ENABLE"'" }, - { "name": "SYS_LOCAL_PORT_RANGE", "value": "'"$SYS_LOCAL_PORT_RANGE"'" }, - { "name": "SYS_NET_DEV_MAX_BACKLOG", "value": "'"$SYS_NET_DEV_MAX_BACKLOG"'" }, - { "name": "SYS_RLIMIT_HARD", "value": "'"$SYS_RLIMIT_HARD"'" }, - { "name": "SYS_RLIMIT_SOFT", "value": "'"$SYS_RLIMIT_SOFT"'" }, - { "name": "SYS_SOMAXCONN", "value": "'"$SYS_SOMAXCONN"'" }, - { "name": "SYS_TCP_MAX_SYN_BACKLOG", "value": "'"$SYS_TCP_MAX_SYN_BACKLOG"'" }, - { "name": "WEB_ADDRESS", "value": "'"$WEB_ADDRESS"'" }, - { "name": "WEB_PUBLIC_URL", "value": "'"$CI_WEB_PUBLIC_URL"'" } - ]' output.json > updated.json - # Extract JUST Task Definition from the output.json file - jq '.taskDefinition' updated.json > extracted.json - # Remove sections that are not needed - jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredBy, .registeredAt)' extracted.json > input.json - sed -i 's/'$CI_AWS_ACCOUNT_SBX'/'$TGT_ACCOUNT'/g' input.json - echo "**************************" - echo "*** New Task Definition" - echo "**************************" - cat input.json | jq . - -.deploy_new_task_definition: &deploy_new_task_definition | - aws ecs register-task-definition --region $AWS_DEFAULT_REGION --cli-input-json file://input.json - REVISION=$(aws ecs describe-task-definition --task-definition tsk-$CI_AWS_TASK --region $AWS_DEFAULT_REGION | egrep "revision" | tr "/" " " | awk '{print $2}' | sed 's/"$//' | cut -d "," -f 1) - echo "****************************************************" - echo "****************************************************" - echo "*** Update Task: " - echo "*** - AWS Account: Titanio-$TGT_ENV" - echo "*** - Cluster: ecs-$CI_AWS_ECR_REPO-$TGT_ENV-$CI_AWS_ECS_CLUSTER_REGION" - echo "*** - Service: svc-$CI_AWS_TASK-$TGT_ENV-$CI_AWS_ECS_CLUSTER_REGION" - echo "*** - Task: tsk-$CI_AWS_TASK:$REVISION" - echo "*** - Image: $CI_AWS_TitanIO_NET_ECR/$CI_AWS_ECR_REPO:$IMAGE_TAG" - echo "****************************************************" - echo "****************************************************" - aws ecs update-service --region $AWS_DEFAULT_REGION --cluster ecs-$CI_AWS_ECR_REPO-$TGT_ENV-$CI_AWS_ECS_CLUSTER_REGION --service svc-$CI_AWS_TASK-$TGT_ENV-$CI_AWS_ECS_CLUSTER_REGION --task-definition tsk-$CI_AWS_TASK:$REVISION - -.deploy_raw_seller: &deploy_raw_seller - - CI_AWS_TASK="proxy-router" - - CI_WALLET_PRIVATE_KEY=$SELLER_PRIVATEKEY - - CI_WEB_PUBLIC_URL=$WEB_PUBLIC_URL - - CI_ETH_NODE_ADDRESS=$PROXY_ROUTER_ETH_NODE_ADDRESS - - *ecr_login_script - - *update_raw_task_definition - - *deploy_new_task_definition - -####################### -# DEPLOY DEVELOPMENT -####################### -bedrock-02-DEV-seller: - stage: deploy - environment: dev - needs: ["bedrock-02-DEV-rawimage"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - when: always - - if: $CI_COMMIT_BRANCH != "dev" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-dev - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_DEV - TGT_ENV: dev - script: - - *deploy_raw_seller - - echo "$TGT_ENV seller updated" - -####################### -# DEPLOY STAGING -####################### -bedrock-03-STG-seller: - stage: deploy - environment: stg - needs: ["bedrock-03-STG-rawimage"] - rules: - - if: $CI_COMMIT_BRANCH == "stg" - when: always - - if: $CI_COMMIT_BRANCH != "stg" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-stg - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_STG - TGT_ENV: stg - script: - - *deploy_raw_seller - - echo "$TGT_ENV seller updated" - -####################### -# DEPLOY PRODUCTION / MAIN / LMN -####################### -bedrock-04-PRD-seller: - stage: deploy - environment: lmn - needs: ["bedrock-04-PRD-rawimage"] - rules: - - if: $CI_COMMIT_BRANCH == "main" - when: manual - - if: $CI_COMMIT_BRANCH != "main" - when: never - - if: $CI_MERGE_REQUEST_ID - when: never - tags: - - proxy #devops - - bedrock - - shell - - titanio-lmn - variables: - TGT_ACCOUNT: $CI_AWS_ACCOUNT_LMN - TGT_ENV: lmn - PROXY_LOG_STRATUM: "false" - script: - - *deploy_raw_seller - - echo "$TGT_ENV seller updated" - -####################### -# E2E TEST STAGE -####################### -# e2e-test: -# stage: e2e-test -# allow_failure: true -# trigger: -# project: TitanInd/proxy/router-test -# branch: $CI_COMMIT_BRANCH -# strategy: depend - -####################### -# RELEASE STAGE -####################### -.default-release: &default-release - inherit: - default: false - stage: release - needs: ["test", "lint"] - image: - name: goreleaser/goreleaser:v1.19.2 - entrypoint: [""] - -.default-tag: &default-tag - stage: release - needs: ["test", "lint"] - image: node:alpine - before_script: - - export $(grep -v '^#' .version | xargs) - - apk --no-cache add git - - PROJECT_URL=$(echo $CI_PROJECT_URL | sed 's/https:\/\///') - - git remote set-url origin https://oauth2:$CI_TAG_PUSH_TOKEN@$PROJECT_URL - -create_tag: - <<: *default-tag - rules: - - if: $CI_COMMIT_BRANCH == "dev" - when: manual - - if: $CI_COMMIT_BRANCH == "stg" - when: on_success - script: - - git fetch origin -f --prune --prune-tags - - git tag "$VERSION-$CI_COMMIT_BRANCH" - - git push origin --tags - -create_tag_main: - <<: *default-tag - rules: - - if: $CI_COMMIT_BRANCH == "main" - when: on_success - script: - - git fetch origin -f --prune --prune-tags - - git tag "$VERSION-lmn" - - git push origin --tags - -release-internal: - <<: *default-release - variables: - GIT_DEPTH: 0 - GITLAB_TOKEN: $CI_TAG_PUSH_TOKEN - GORELEASER_FORCE_TOKEN: gitlab - only: - - /.*-dev$/ - - /.*-stg$/ - script: - - goreleaser release --clean -f "./.goreleaser-internal.yaml" - artifacts: - paths: - - dist/proxy-router_* - -release-external: - <<: *default-release - variables: - GIT_DEPTH: 0 - GORELEASER_FORCE_TOKEN: github - only: - - /.*-lmn$/ - script: - - goreleaser release --clean \ No newline at end of file diff --git a/readme.md b/readme.md index fd7e0cc5..5de9e2af 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ manages secure sessions between consumers and providers and routes prompts and r * Diamond MarketPlace Contract: `0xDE819AaEE474626E3f34Ef0263373357e5a6C71b` * Blockchain Explorer: `https://arbiscan.io/` -### TestNet (STG Branch and TEST-* Releases) +### TestNet (TEST Branch and TEST-* Releases) * Blockchain: Sepolia Arbitrum (ChainID: `421614`) * Morpheus MOR Token: `0x34a285a1b1c166420df5b6630132542923b5b27e` * Diamond MarketPlace Contract: `0xb8C55cD613af947E73E262F0d3C54b7211Af16CF` From 6c9bfb4b59e358ad2a6612773c7ae281f3f11567 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 14:31:21 -0500 Subject: [PATCH 16/41] update ci-cd conditions for gitlab trigger --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0b286ac..8d577776 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,12 +7,14 @@ on: description: 'Create new release' required: true type: boolean + push: branches: - main - test - + - dev paths: ['.github/workflows/**', '**/Makefile', '**/*.go', '**/*.json', '**/*.yml', '**/*.ts', '**/*.js'] + pull_request: types: [opened, reopened, synchronize] paths: ['.github/workflows/**', '**/Makefile', '**/*.go', '**/*.json', '**/*.yml', '**/*.ts', '**/*.js'] From 5462899ddef13c36f56688f0696ff0d61b1b418e Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 14:54:04 -0500 Subject: [PATCH 17/41] updated error handling for gitlab trigger --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d577776..d394280c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -413,7 +413,7 @@ jobs: --form "variables[SOURCE_REPO]=${{ github.repository }}" \ --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}") - if [ "$response" -ne 200 ]; then + if [[ "$response" != 2* ]]; then echo "Failed to trigger GitLab pipeline. HTTP status: $response" exit 1 fi From d8f56037bbf26d7820a21fd65a09a91aca8ab926 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 15:25:52 -0500 Subject: [PATCH 18/41] fine tuned gitlab-trigger --- .github/workflows/build.yml | 88 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d394280c..22a95086 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,53 @@ defaults: shell: bash jobs: + trigger_gitlab: + runs-on: ubuntu-latest + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev' )) }} + + steps: + - name: Generate Tag Name + uses: ./.github/actions/gen_tag_name + + - name: Determine GitLab Target Branch + id: set_target_branch + run: | + if [ "${{ github.ref_name }}" == "dev" ]; then + echo "gitlab_branch=dev" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "test" ]; then + echo "gitlab_branch=stg" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "main" ]; then + echo "gitlab_branch=main" >> $GITHUB_ENV + else + echo "This branch is not configured to trigger GitLab pipelines." + exit 1 + fi + + - name: Trigger GitLab Pipeline + run: | + echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }} with tag ${{ env.TAG_NAME }}" + + # Send request to GitLab + response=$(curl --write-out "\n%{http_code}" --silent --output /tmp/gitlab_response \ + --request POST \ + --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ + --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ + --form "ref=${{ env.gitlab_branch }}" \ + --form "variables[SOURCE_REPO]=${{ github.repository }}" \ + --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}" \ + --form "variables[GITHUB_TAG]=${{ env.TAG_NAME }}") + + # Extract status code and response body + status_code=$(tail -n 1 /tmp/gitlab_response) + response_body=$(head -n -1 /tmp/gitlab_response) + + # Log and handle response + if [[ "$status_code" != 2* ]]; then + echo "Gitlab pipeline FAILED. HTTP status: $status_code" + echo "GitLab Response: $response_body" + exit 1 + fi + echo "GitLab pipeline triggered successfully. HTTP status: $status_code" Ubuntu-22-x64: if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: ubuntu-22.04 @@ -378,43 +425,4 @@ jobs: }); } } - - trigger_gitlab_pipeline: - runs-on: ubuntu-latest - if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev' )) }} - needs: - - Ubuntu-22-x64 - - macOS-13-x64 - - macOS-14-arm64 - - Windows-avx2-x64 - steps: - - name: Determine GitLab Target Branch - id: set_target_branch - run: | - if [ "${{ github.ref_name }}" == "dev" ]; then - echo "gitlab_branch=dev" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "test" ]; then - echo "gitlab_branch=stg" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "main" ]; then - echo "gitlab_branch=main" >> $GITHUB_ENV - else - echo "This branch is not configured to trigger GitLab pipelines." - exit 1 - fi - - - name: Trigger GitLab Pipeline - run: | - echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }}" - response=$(curl --write-out "%{http_code}" --silent --output /dev/null \ - --request POST \ - --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ - --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ - --form "ref=${{ env.gitlab_branch }}" \ - --form "variables[SOURCE_REPO]=${{ github.repository }}" \ - --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}") - - if [[ "$response" != 2* ]]; then - echo "Failed to trigger GitLab pipeline. HTTP status: $response" - exit 1 - fi - + \ No newline at end of file From 5c5b63f153f3a6350b7694e3ccac80fa89ac539b Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 15:31:19 -0500 Subject: [PATCH 19/41] add clone to enable actions --- .github/workflows/build.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22a95086..51e5bc73 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,10 +29,13 @@ defaults: jobs: trigger_gitlab: - runs-on: ubuntu-latest if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev' )) }} - + runs-on: ubuntu-latest steps: + - name: Clone + uses: actions/checkout@v4 + id: checkout + - name: Generate Tag Name uses: ./.github/actions/gen_tag_name @@ -75,6 +78,7 @@ jobs: exit 1 fi echo "GitLab pipeline triggered successfully. HTTP status: $status_code" + Ubuntu-22-x64: if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} runs-on: ubuntu-22.04 From 81e14803e76f60d4c8fe4544f91da1372d8db674 Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 16:08:03 -0500 Subject: [PATCH 20/41] fixed json response in gitlab pipeline api --- .github/workflows/build.yml | 39 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51e5bc73..726e119c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,14 +28,18 @@ defaults: shell: bash jobs: - trigger_gitlab: - if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev' )) }} + GitLab-Deploy: + if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' && (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || github.ref == 'refs/heads/dev')) }} runs-on: ubuntu-latest steps: - name: Clone uses: actions/checkout@v4 id: checkout - + + - name: Install dependencies + run: | + sudo apt-get update && sudo apt-get install -y jq + - name: Generate Tag Name uses: ./.github/actions/gen_tag_name @@ -55,29 +59,34 @@ jobs: - name: Trigger GitLab Pipeline run: | - echo "Triggering GitLab pipeline for branch ${{ env.gitlab_branch }} with tag ${{ env.TAG_NAME }}" + echo "Triggering GitLab pipeline for branch ${{ github.ref_name }} with tag ${{ env.TAG_NAME }}" # Send request to GitLab - response=$(curl --write-out "\n%{http_code}" --silent --output /tmp/gitlab_response \ + response=$(curl --silent \ --request POST \ --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ - --form "ref=${{ env.gitlab_branch }}" \ + --form "ref=${{ github.ref_name }}" \ --form "variables[SOURCE_REPO]=${{ github.repository }}" \ --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}" \ --form "variables[GITHUB_TAG]=${{ env.TAG_NAME }}") - - # Extract status code and response body - status_code=$(tail -n 1 /tmp/gitlab_response) - response_body=$(head -n -1 /tmp/gitlab_response) - # Log and handle response - if [[ "$status_code" != 2* ]]; then - echo "Gitlab pipeline FAILED. HTTP status: $status_code" - echo "GitLab Response: $response_body" + # Parse JSON response using jq + gitlab_status=$(echo "$response" | jq -r '.status // "unknown"') + gitlab_web_url=$(echo "$response" | jq -r '.web_url // "N/A"') + + # Log the response + echo "GitLab Response: $response" + + # Validate the status field + if [[ "$gitlab_status" =~ ^(created|preparing|success|running|scheduled)$ ]]; then + echo "GitLab pipeline triggered successfully! Status: $gitlab_status" + echo "Pipeline details: $gitlab_web_url" + else + echo "GitLab pipeline FAILED. Invalid status: $gitlab_status" + echo "Pipeline details: $gitlab_web_url" exit 1 fi - echo "GitLab pipeline triggered successfully. HTTP status: $status_code" Ubuntu-22-x64: if: ${{ github.repository != 'MorpheusAIs/Morpheus-Lumerin-Node' }} From 4f2dfb9a890d4d624015790e5650b6be2aa3270e Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 16:19:48 -0500 Subject: [PATCH 21/41] final test trigger --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 726e119c..14eaf15b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: - name: Trigger GitLab Pipeline run: | - echo "Triggering GitLab pipeline for branch ${{ github.ref_name }} with tag ${{ env.TAG_NAME }}" + echo "Triggering GitLab Build and Deploy for branch ${{ github.ref_name }} with tag ${{ env.TAG_NAME }}" # Send request to GitLab response=$(curl --silent \ From ee66afe650edc1e4e3128eddbdad04c24f8856dd Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 16:29:48 -0500 Subject: [PATCH 22/41] reset gitlab ref branch based on lookup --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14eaf15b..7452217b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: --request POST \ --url "${{ secrets.GITLAB_TRIGGER_URL }}" \ --form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ - --form "ref=${{ github.ref_name }}" \ + --form "ref=${{ env.gitlab_branch }}" \ --form "variables[SOURCE_REPO]=${{ github.repository }}" \ --form "variables[SOURCE_BRANCH]=${{ github.ref_name }}" \ --form "variables[GITHUB_TAG]=${{ env.TAG_NAME }}") From 3a66308fbd91366cabe84397d3f79758f39dcefe Mon Sep 17 00:00:00 2001 From: abs2023 Date: Fri, 22 Nov 2024 16:35:31 -0500 Subject: [PATCH 23/41] update readme --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 5de9e2af..f8263e11 100644 --- a/readme.md +++ b/readme.md @@ -24,12 +24,14 @@ manages secure sessions between consumers and providers and routes prompts and r * Morpheus MOR Token: `0x092bAaDB7DEf4C3981454dD9c0A0D7FF07bCFc86` * Diamond MarketPlace Contract: `0xDE819AaEE474626E3f34Ef0263373357e5a6C71b` * Blockchain Explorer: `https://arbiscan.io/` +* GitHub Source: https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node/tree/main ### TestNet (TEST Branch and TEST-* Releases) * Blockchain: Sepolia Arbitrum (ChainID: `421614`) * Morpheus MOR Token: `0x34a285a1b1c166420df5b6630132542923b5b27e` * Diamond MarketPlace Contract: `0xb8C55cD613af947E73E262F0d3C54b7211Af16CF` * Blockchain Explorer: `https://sepolia.arbiscan.io/` +* GitHub Source: https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node/tree/test ## Funds * **WALLET:** For testing as a provider or consumer, you will need both `MOR` and `ETH` tokens in your wallet. From 6725e1b8c060efa9404251c60f98c2e4a318d7c4 Mon Sep 17 00:00:00 2001 From: shev Date: Mon, 25 Nov 2024 15:31:52 +0100 Subject: [PATCH 24/41] feat: introduce json schema for models-config.json --- proxy-router/internal/aiengine/openai.go | 1 - .../internal/config/models-config-schema.json | 40 +++++++++++++++++++ proxy-router/models-config.json.example | 27 ++++++++----- 3 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 proxy-router/internal/config/models-config-schema.json diff --git a/proxy-router/internal/aiengine/openai.go b/proxy-router/internal/aiengine/openai.go index cf1edb3b..387dcff1 100644 --- a/proxy-router/internal/aiengine/openai.go +++ b/proxy-router/internal/aiengine/openai.go @@ -100,7 +100,6 @@ func (a *OpenAI) readStream(ctx context.Context, body io.Reader, cb gcs.Completi var compl openai.ChatCompletionStreamResponse if err := json.Unmarshal([]byte(data), &compl); err != nil { if isStreamFinished(data) { - a.log.Debugf("reached end of the response") return nil } else { return fmt.Errorf("error decoding response: %s\n%s", err, line) diff --git a/proxy-router/internal/config/models-config-schema.json b/proxy-router/internal/config/models-config-schema.json new file mode 100644 index 00000000..8922f86c --- /dev/null +++ b/proxy-router/internal/config/models-config-schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "models": { + "type": "array", + "additionalProperties": { + "type": "object", + "properties": { + "modelId": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "apiType": { + "type": "string", + "enum": [ + "openai", + "prodia-sd", + "prodia-sdxl" + ] + }, + "apiUrl": { + "type": "string", + "format": "uri" + }, + "apiKey": { + "type": "string" + } + }, + "required": [ + "modelName", + "apiType", + "apiUrl" + ] + } + } + } +} \ No newline at end of file diff --git a/proxy-router/models-config.json.example b/proxy-router/models-config.json.example index b9d1391a..cd7792fb 100644 --- a/proxy-router/models-config.json.example +++ b/proxy-router/models-config.json.example @@ -1,13 +1,18 @@ { - "0x0000000000000000000000000000000000000000000000000000000000000000": { - "modelName": "llama2", - "apiType": "openai", - "apiUrl": "http://localhost:8080/v1" - }, - "0x0000000000000000000000000000000000000000000000000000000000000001": { - "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", - "apiType": "prodia-sd", - "apiUrl": "https://api.prodia.com/v1", - "apiKey": "" - } + "$schema": "./internal/config/models-config-schema.json", + "models": [ + { + "0x0000000000000000000000000000000000000000000000000000000000000000": { + "modelName": "llama2", + "apiType": "openai", + "apiUrl": "http://localhost:8080/v1" + }, + "0x0000000000000000000000000000000000000000000000000000000000000001": { + "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", + "apiType": "prodia-sd", + "apiUrl": "https://api.prodia.com/v1", + "apiKey": "" + } + } + ] } From 79f7314ff8a370cadd964eb4c29f64c73ab708ba Mon Sep 17 00:00:00 2001 From: shev Date: Mon, 25 Nov 2024 15:32:28 +0100 Subject: [PATCH 25/41] feat: configurable rating config --- proxy-router/cmd/main.go | 14 +- proxy-router/internal/blockchainapi/rating.go | 114 ------------ .../internal/blockchainapi/rating_mock.go | 4 +- .../internal/blockchainapi/rating_test.go | 36 +--- .../internal/blockchainapi/service.go | 101 ++++++++--- proxy-router/internal/config/config.go | 7 +- proxy-router/internal/config/models_config.go | 50 ++++-- proxy-router/internal/config/rating_config.go | 28 +++ .../internal/proxyapi/proxy_sender.go | 4 +- proxy-router/internal/rating/common.go | 69 +++++++ proxy-router/internal/rating/interface.go | 31 ++++ proxy-router/internal/rating/mock.go | 12 ++ .../internal/rating/rating-config-schema.json | 35 ++++ proxy-router/internal/rating/rating.go | 64 +++++++ proxy-router/internal/rating/rating_config.go | 78 ++++++++ .../internal/rating/scorer_default.go | 84 +++++++++ .../internal/rating/scorer_default_config.go | 34 ++++ .../internal/rating/scorer_default_mock.go | 11 ++ .../rating/scorer_default_schema.json | 27 +++ .../internal/rating/scorer_default_test.go | 169 ++++++++++++++++++ .../registries/provider_registry.go | 9 + proxy-router/rating-config.json | 14 ++ 22 files changed, 799 insertions(+), 196 deletions(-) delete mode 100644 proxy-router/internal/blockchainapi/rating.go create mode 100644 proxy-router/internal/config/rating_config.go create mode 100644 proxy-router/internal/rating/common.go create mode 100644 proxy-router/internal/rating/interface.go create mode 100644 proxy-router/internal/rating/mock.go create mode 100644 proxy-router/internal/rating/rating-config-schema.json create mode 100644 proxy-router/internal/rating/rating.go create mode 100644 proxy-router/internal/rating/rating_config.go create mode 100644 proxy-router/internal/rating/scorer_default.go create mode 100644 proxy-router/internal/rating/scorer_default_config.go create mode 100644 proxy-router/internal/rating/scorer_default_mock.go create mode 100644 proxy-router/internal/rating/scorer_default_schema.json create mode 100644 proxy-router/internal/rating/scorer_default_test.go create mode 100644 proxy-router/rating-config.json diff --git a/proxy-router/cmd/main.go b/proxy-router/cmd/main.go index f32d56e1..52e2af95 100644 --- a/proxy-router/cmd/main.go +++ b/proxy-router/cmd/main.go @@ -8,7 +8,6 @@ import ( "os" "os/signal" "path/filepath" - "strings" "syscall" "time" @@ -33,7 +32,6 @@ import ( "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/storages" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/system" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/walletapi" - "github.com/ethereum/go-ethereum/common" docs "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/docs" ) @@ -260,13 +258,9 @@ func start() error { log.Warnf("failed to load model config: %s, run with empty", err) } - providerAllowList := []common.Address{} - if cfg.Proxy.ProviderAllowList != "" { - addresses := strings.Split(cfg.Proxy.ProviderAllowList, ",") - for _, address := range addresses { - addr := strings.TrimSpace(address) - providerAllowList = append(providerAllowList, common.HexToAddress(addr)) - } + scorer, err := config.LoadScorer(cfg.Proxy.RatingConfigPath, log) + if err != nil { + return err } chatStoragePath := filepath.Join(cfg.Proxy.StoragePath, "chats") @@ -278,7 +272,7 @@ func start() error { sessionRepo := sessionrepo.NewSessionRepositoryCached(sessionStorage, sessionRouter, marketplace) proxyRouterApi := proxyapi.NewProxySender(chainID, publicUrl, wallet, contractLogStorage, sessionStorage, sessionRepo, log) explorer := blockchainapi.NewExplorerClient(cfg.Blockchain.ExplorerApiUrl, *cfg.Marketplace.MorTokenAddress, cfg.Blockchain.ExplorerRetryDelay, cfg.Blockchain.ExplorerMaxRetries) - blockchainApi := blockchainapi.NewBlockchainService(ethClient, multicallBackend, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, explorer, wallet, proxyRouterApi, sessionRepo, providerAllowList, proxyLog, cfg.Blockchain.EthLegacyTx) + blockchainApi := blockchainapi.NewBlockchainService(ethClient, multicallBackend, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, explorer, wallet, proxyRouterApi, sessionRepo, scorer, proxyLog, cfg.Blockchain.EthLegacyTx) proxyRouterApi.SetSessionService(blockchainApi) aiEngine := aiengine.NewAiEngine(proxyRouterApi, chatStorage, modelConfigLoader, log) diff --git a/proxy-router/internal/blockchainapi/rating.go b/proxy-router/internal/blockchainapi/rating.go deleted file mode 100644 index eedf5c73..00000000 --- a/proxy-router/internal/blockchainapi/rating.go +++ /dev/null @@ -1,114 +0,0 @@ -package blockchainapi - -import ( - "math" - "sort" - - "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/blockchainapi/structs" - "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" - m "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/marketplace" - pr "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/providerregistry" - s "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" -) - -func rateBids(bidIds [][32]byte, bids []m.IBidStorageBid, pmStats []s.IStatsStorageProviderModelStats, provider []pr.IProviderStorageProvider, mStats *s.IStatsStorageModelStats, log lib.ILogger) []structs.ScoredBid { - scoredBids := make([]structs.ScoredBid, len(bids)) - - for i := range bids { - score := getScore(bids[i], pmStats[i], provider[i], mStats) - if math.IsNaN(score) || math.IsInf(score, 0) { - log.Errorf("provider score is not valid %d for bid %v, pmStats %v, mStats %v", score, bidIds[i], pmStats[i], mStats) - score = 0 - } - scoredBid := structs.ScoredBid{ - Bid: structs.Bid{ - Id: bidIds[i], - Provider: bids[i].Provider, - ModelAgentId: bids[i].ModelId, - PricePerSecond: &lib.BigInt{Int: *(bids[i].PricePerSecond)}, - Nonce: &lib.BigInt{Int: *(bids[i].Nonce)}, - CreatedAt: &lib.BigInt{Int: *(bids[i].CreatedAt)}, - DeletedAt: &lib.BigInt{Int: *(bids[i].DeletedAt)}, - }, - Score: score, - } - scoredBids[i] = scoredBid - } - - sort.Slice(scoredBids, func(i, j int) bool { - return scoredBids[i].Score > scoredBids[j].Score - }) - - return scoredBids -} - -func getScore(bid m.IBidStorageBid, pmStats s.IStatsStorageProviderModelStats, pr pr.IProviderStorageProvider, mStats *s.IStatsStorageModelStats) float64 { - tpsWeight, ttftWeight, durationWeight, successWeight, stakeWeight := 0.24, 0.08, 0.24, 0.32, 0.12 - count := int64(mStats.Count) - minStake := int64(0.2 * math.Pow10(18)) // 0.2 MOR - - tpsScore := tpsWeight * normRange(normZIndex(pmStats.TpsScaled1000.Mean, mStats.TpsScaled1000, count), 3.0) - // ttft impact is negative - ttftScore := ttftWeight * normRange(-1*normZIndex(pmStats.TtftMs.Mean, mStats.TtftMs, count), 3.0) - durationScore := durationWeight * normRange(normZIndex(int64(pmStats.TotalDuration), mStats.TotalDuration, count), 3.0) - successScore := successWeight * math.Pow(ratioScore(pmStats.SuccessCount, pmStats.TotalCount), 2) - stakeScore := stakeWeight * normMinMax(pr.Stake.Int64(), minStake, 10*minStake) - - priceFloatDecimal, _ := bid.PricePerSecond.Float64() - priceFloat := priceFloatDecimal / math.Pow10(18) - - result := (tpsScore + ttftScore + durationScore + successScore + stakeScore) / priceFloat - - return result -} - -func ratioScore(num, denom uint32) float64 { - if denom == 0 { - return 0 - } - return float64(num) / float64(denom) -} - -// normZIndex normalizes the value using z-index -func normZIndex(pmMean int64, mSD s.LibSDSD, obsNum int64) float64 { - sd := getSD(mSD, obsNum) - if sd == 0 { - return 0 - } - // TODO: consider variance(SD) of provider model stats - return float64(pmMean-mSD.Mean) / getSD(mSD, obsNum) -} - -// normRange normalizes the incoming data within the range [-normRange, normRange] -// to the range [0, 1] cutting off the values outside the range -func normRange(input float64, normRange float64) float64 { - return cutRange01((input + normRange) / (2.0 * normRange)) -} - -func getSD(sd s.LibSDSD, obsNum int64) float64 { - return math.Sqrt(getVariance(sd, obsNum)) -} - -func getVariance(sd s.LibSDSD, obsNum int64) float64 { - if obsNum <= 1 { - return 0 - } - return float64(sd.SqSum) / float64(obsNum-1) -} - -func cutRange01(val float64) float64 { - if val > 1 { - return 1 - } - if val < 0 { - return 0 - } - return val -} - -func normMinMax(val, min, max int64) float64 { - if max == min { - return 0 - } - return float64(val-min) / float64(max-min) -} diff --git a/proxy-router/internal/blockchainapi/rating_mock.go b/proxy-router/internal/blockchainapi/rating_mock.go index 19a9be82..00214906 100644 --- a/proxy-router/internal/blockchainapi/rating_mock.go +++ b/proxy-router/internal/blockchainapi/rating_mock.go @@ -16,7 +16,7 @@ type ModelStats struct { Count int } -func sampleDataTPS() ([][32]byte, []m.IBidStorageBid, []s.IStatsStorageProviderModelStats, ModelStats) { +func sampleDataTPS() ([][32]byte, []m.IBidStorageBid, []s.IStatsStorageProviderModelStats, *s.IStatsStorageModelStats) { modelID := common.HexToHash("0x01") bidIds := [][32]byte{ {0x01}, @@ -73,7 +73,7 @@ func sampleDataTPS() ([][32]byte, []m.IBidStorageBid, []s.IStatsStorageProviderM TotalCount: 10, }, } - mStats := ModelStats{ + mStats := &s.IStatsStorageModelStats{ TpsScaled1000: s.LibSDSD{Mean: 20, SqSum: 100}, TtftMs: s.LibSDSD{Mean: 20, SqSum: 200}, TotalDuration: s.LibSDSD{Mean: 30, SqSum: 300}, diff --git a/proxy-router/internal/blockchainapi/rating_test.go b/proxy-router/internal/blockchainapi/rating_test.go index 7e4c03a0..ab57666e 100644 --- a/proxy-router/internal/blockchainapi/rating_test.go +++ b/proxy-router/internal/blockchainapi/rating_test.go @@ -1,49 +1,21 @@ package blockchainapi import ( - "math" + "math/big" "testing" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/blockchainapi/scorer" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" - s "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/providerregistry" "github.com/stretchr/testify/require" ) func TestRating(t *testing.T) { bidIds, bids, pmStats, mStats := sampleDataTPS() - scoredBids := rateBids(bidIds, bids, pmStats, mStats, lib.NewTestLogger()) + scoredBids := RateBids(bidIds, bids, pmStats, []providerregistry.IProviderStorageProvider{}, mStats, scorer.NewScorerMock(), big.NewInt(0), lib.NewTestLogger()) for i := 1; i < len(scoredBids); i++ { require.GreaterOrEqual(t, scoredBids[i-1].Score, scoredBids[i].Score, "scoredBids not sorted") } } - -func TestGetScoreZeroObservations(t *testing.T) { - _, bids, _, _ := sampleDataTPS() - score := getScore(bids[0], s.IStatsStorageProviderModelStats{}, ModelStats{}) - if math.IsNaN(score) { - require.Fail(t, "score is NaN") - } -} - -func TestGetScoreSingleObservation(t *testing.T) { - _, bids, _, _ := sampleDataTPS() - pmStats := s.IStatsStorageProviderModelStats{ - TpsScaled1000: s.LibSDSD{Mean: 1000, SqSum: 0}, - TtftMs: s.LibSDSD{Mean: 1000, SqSum: 0}, - TotalDuration: 1000, - SuccessCount: 1, - TotalCount: 1, - } - mStats := ModelStats{ - TpsScaled1000: s.LibSDSD{Mean: 1000, SqSum: 0}, - TtftMs: s.LibSDSD{Mean: 1000, SqSum: 0}, - TotalDuration: s.LibSDSD{Mean: 1000, SqSum: 0}, - Count: 1, - } - score := getScore(bids[0], pmStats, mStats) - if math.IsNaN(score) { - require.Fail(t, "score is NaN") - } -} diff --git a/proxy-router/internal/blockchainapi/service.go b/proxy-router/internal/blockchainapi/service.go index eabc0bef..d6e74520 100644 --- a/proxy-router/internal/blockchainapi/service.go +++ b/proxy-router/internal/blockchainapi/service.go @@ -13,8 +13,10 @@ import ( i "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/interfaces" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/proxyapi" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/rating" m "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/marketplace" pr "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/providerregistry" + s "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" sr "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/multicall" r "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/registries" @@ -39,8 +41,8 @@ type BlockchainService struct { sessionRepo *sessionrepo.SessionRepositoryCached proxyService *proxyapi.ProxyServiceSender diamonContractAddr common.Address - - providerAllowList []common.Address + rating *rating.Rating + minStake *big.Int legacyTx bool privateKey i.PrKeyProvider @@ -81,7 +83,7 @@ func NewBlockchainService( privateKey i.PrKeyProvider, proxyService *proxyapi.ProxyServiceSender, sessionRepo *sessionrepo.SessionRepositoryCached, - providerAllowList []common.Address, + scorerAlgo *rating.Rating, log lib.ILogger, legacyTx bool, ) *BlockchainService { @@ -103,8 +105,8 @@ func NewBlockchainService( explorerClient: explorer, proxyService: proxyService, diamonContractAddr: diamonContractAddr, - providerAllowList: providerAllowList, sessionRepo: sessionRepo, + rating: scorerAlgo, log: log, } } @@ -204,12 +206,63 @@ func (s *BlockchainService) GetRatedBids(ctx context.Context, modelID common.Has if err != nil { return nil, err } + minStake, err := s.getMinStakeCached(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get min stake: %w", err) + } - ratedBids := rateBids(bidIDs, bids, providerModelStats, provider, modelStats, s.log) + ratedBids := s.rateBids(bidIDs, bids, providerModelStats, provider, modelStats, minStake, s.log) return ratedBids, nil } +func (s *BlockchainService) rateBids(bidIds [][32]byte, bids []m.IBidStorageBid, pmStats []s.IStatsStorageProviderModelStats, provider []pr.IProviderStorageProvider, mStats *s.IStatsStorageModelStats, minStake *big.Int, log lib.ILogger) []structs.ScoredBid { + ratingInputs := make([]rating.RatingInput, len(bids)) + bidIDIndexMap := make(map[common.Hash]int) + + for i := range bids { + ratingInputs[i] = rating.RatingInput{ + ScoreInput: rating.ScoreInput{ + ProviderModel: &pmStats[i], + Model: mStats, + ProviderStake: provider[i].Stake, + PricePerSecond: bids[i].PricePerSecond, + MinStake: minStake, + }, + BidID: bidIds[i], + ModelID: bids[i].ModelId, + ProviderID: bids[i].Provider, + } + bidIDIndexMap[bidIds[i]] = i + } + + result := s.rating.RateBids(ratingInputs, log) + scoredBids := make([]structs.ScoredBid, len(result)) + + for i, score := range result { + inputBidIndex := bidIDIndexMap[score.BidID] + scoredBid := structs.ScoredBid{ + Bid: structs.Bid{ + Id: bidIds[inputBidIndex], + Provider: bids[inputBidIndex].Provider, + ModelAgentId: bids[inputBidIndex].ModelId, + PricePerSecond: &lib.BigInt{Int: *(bids[inputBidIndex].PricePerSecond)}, + Nonce: &lib.BigInt{Int: *(bids[inputBidIndex].Nonce)}, + CreatedAt: &lib.BigInt{Int: *(bids[inputBidIndex].CreatedAt)}, + DeletedAt: &lib.BigInt{Int: *(bids[inputBidIndex].DeletedAt)}, + }, + Score: score.Score, + } + scoredBids[i] = scoredBid + } + + sort.Slice(scoredBids, func(i, j int) bool { + return scoredBids[i].Score > scoredBids[j].Score + }) + + return scoredBids +} + func (s *BlockchainService) OpenSession(ctx context.Context, approval, approvalSig []byte, stake *big.Int, directPayment bool) (common.Hash, error) { prKey, err := s.privateKey.GetPrivateKey() if err != nil { @@ -745,7 +798,12 @@ func (s *BlockchainService) OpenSessionByModelId(ctx context.Context, modelID co return common.Hash{}, lib.WrapError(ErrMyAddress, err) } - scoredBids := rateBids(bidIDs, bids, providerStats, providers, modelStats, s.log) + minStake, err := s.getMinStakeCached(ctx) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to get min stake: %w", err) + } + + scoredBids := s.rateBids(bidIDs, bids, providerStats, providers, modelStats, minStake, s.log) for i, bid := range scoredBids { providerAddr := bid.Bid.Provider if providerAddr == omitProvider { @@ -753,11 +811,6 @@ func (s *BlockchainService) OpenSessionByModelId(ctx context.Context, modelID co continue } - if !s.isProviderAllowed(providerAddr) { - s.log.Infof("skipping not allowed provider #%d %s", i, providerAddr.String()) - continue - } - s.log.Infof("trying to open session with provider #%d %s", i, bid.Bid.Provider.String()) durationCopy := new(big.Int).Set(duration) @@ -883,19 +936,6 @@ func (s *BlockchainService) GetMyAddress(ctx context.Context) (common.Address, e return lib.PrivKeyBytesToAddr(prKey) } -func (s *BlockchainService) isProviderAllowed(providerAddr common.Address) bool { - if len(s.providerAllowList) == 0 { - return true - } - - for _, addr := range s.providerAllowList { - if addr.Hex() == providerAddr.Hex() { - return true - } - } - return false -} - func (s *BlockchainService) getTransactOpts(ctx context.Context, privKey lib.HexString) (*bind.TransactOpts, error) { privateKey, err := crypto.ToECDSA(privKey) if err != nil { @@ -940,3 +980,16 @@ func (s *BlockchainService) signTx(ctx context.Context, tx *types.Transaction, p return types.SignTx(tx, types.NewEIP155Signer(chainId), privateKey) } + +func (s *BlockchainService) getMinStakeCached(ctx context.Context) (*big.Int, error) { + if s.minStake != nil { + return s.minStake, nil + } + + minStake, err := s.providerRegistry.GetMinStake(ctx) + if err != nil { + return nil, err + } + s.minStake = minStake + return minStake, nil +} diff --git a/proxy-router/internal/config/config.go b/proxy-router/internal/config/config.go index 4bf2e5cd..34074314 100644 --- a/proxy-router/internal/config/config.go +++ b/proxy-router/internal/config/config.go @@ -60,7 +60,7 @@ type Config struct { StoreChatContext *lib.Bool `env:"PROXY_STORE_CHAT_CONTEXT" flag:"proxy-store-chat-context" desc:"store chat context in the proxy storage"` ForwardChatContext *lib.Bool `env:"PROXY_FORWARD_CHAT_CONTEXT" flag:"proxy-forward-chat-context" desc:"prepend whole stored message history to the prompt"` ModelsConfigPath string `env:"MODELS_CONFIG_PATH" flag:"models-config-path" validate:"omitempty"` - ProviderAllowList string `env:"PROVIDER_ALLOW_LIST" flag:"provider-allow-list" validate:"omitempty" desc:"comma separated list of provider addresses allowed to open session with"` + RatingConfigPath string `env:"RATING_CONFIG_PATH" flag:"rating-config-path" validate:"omitempty" desc:"path to the rating config file"` } System struct { Enable bool `env:"SYS_ENABLE" flag:"sys-enable" desc:"enable system level configuration adjustments"` @@ -177,6 +177,9 @@ func (cfg *Config) SetDefaults() { val := true cfg.Proxy.ForwardChatContext = &lib.Bool{Bool: &val} } + if cfg.Proxy.RatingConfigPath == "" { + cfg.Proxy.RatingConfigPath = "./rating-config.json" + } } // GetSanitized returns a copy of the config with sensitive data removed @@ -208,10 +211,10 @@ func (cfg *Config) GetSanitized() interface{} { publicCfg.Proxy.Address = cfg.Proxy.Address publicCfg.Proxy.ModelsConfigPath = cfg.Proxy.ModelsConfigPath - publicCfg.Proxy.ProviderAllowList = cfg.Proxy.ProviderAllowList publicCfg.Proxy.StoragePath = cfg.Proxy.StoragePath publicCfg.Proxy.StoreChatContext = cfg.Proxy.StoreChatContext publicCfg.Proxy.ForwardChatContext = cfg.Proxy.ForwardChatContext + publicCfg.Proxy.RatingConfigPath = cfg.Proxy.RatingConfigPath publicCfg.System.Enable = cfg.System.Enable publicCfg.System.LocalPortRange = cfg.System.LocalPortRange diff --git a/proxy-router/internal/config/models_config.go b/proxy-router/internal/config/models_config.go index 02c11f55..4678804b 100644 --- a/proxy-router/internal/config/models_config.go +++ b/proxy-router/internal/config/models_config.go @@ -2,14 +2,15 @@ package config import ( "encoding/json" + "fmt" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" ) type ModelConfigLoader struct { - log *lib.Logger - modelConfigs ModelConfigs - path string + log *lib.Logger + modelConfigsMap ModelConfigs + path string } type ModelConfig struct { @@ -22,12 +23,18 @@ type ModelConfig struct { } type ModelConfigs map[string]ModelConfig +type ModelConfigsV2 struct { + Models []struct { + ID string `json:"modelId"` + ModelConfig + } `json:"models"` +} func NewModelConfigLoader(path string, log *lib.Logger) *ModelConfigLoader { return &ModelConfigLoader{ - log: log, - modelConfigs: ModelConfigs{}, - path: path, + log: log, + modelConfigsMap: ModelConfigs{}, + path: path, } } @@ -37,7 +44,6 @@ func (e *ModelConfigLoader) Init() error { filePath = e.path } - e.log.Warnf("loading models config from file: %s", filePath) modelsConfig, err := lib.ReadJSONFile(filePath) if err != nil { e.log.Errorf("failed to read models config file: %s", err) @@ -47,14 +53,38 @@ func (e *ModelConfigLoader) Init() error { return err } + e.log.Infof("models config loaded from file: %s", filePath) + // check config format + var cfgMap map[string]json.RawMessage + err = json.Unmarshal([]byte(modelsConfig), &cfgMap) + if err != nil { + return fmt.Errorf("invalid models config format: %s", err) + } + if cfgMap["models"] != nil { + var modelConfigsV2 ModelConfigsV2 + err = json.Unmarshal([]byte(modelsConfig), &modelConfigsV2) + if err != nil { + return fmt.Errorf("invalid models config V2 format: %s", err) + } + for _, v := range modelConfigsV2.Models { + e.modelConfigsMap[v.ID] = v.ModelConfig + e.log.Infof("local model: %s", v.ModelName) + } + return nil + } + + e.log.Warnf("failed to unmarshal to new models config, trying legacy") + + // try old config format var modelConfigs ModelConfigs err = json.Unmarshal([]byte(modelsConfig), &modelConfigs) if err != nil { e.log.Errorf("failed to unmarshal models config: %s", err) return err } - e.modelConfigs = modelConfigs + + e.modelConfigsMap = modelConfigs return nil } @@ -63,7 +93,7 @@ func (e *ModelConfigLoader) ModelConfigFromID(ID string) *ModelConfig { return &ModelConfig{} } - modelConfig := e.modelConfigs[ID] + modelConfig := e.modelConfigsMap[ID] if modelConfig == (ModelConfig{}) { e.log.Warnf("model config not found for ID: %s", ID) return &ModelConfig{} @@ -75,7 +105,7 @@ func (e *ModelConfigLoader) ModelConfigFromID(ID string) *ModelConfig { func (e *ModelConfigLoader) GetAll() ([]string, []ModelConfig) { var modelConfigs []ModelConfig var modelIDs []string - for ID, v := range e.modelConfigs { + for ID, v := range e.modelConfigsMap { modelConfigs = append(modelConfigs, v) modelIDs = append(modelIDs, ID) } diff --git a/proxy-router/internal/config/rating_config.go b/proxy-router/internal/config/rating_config.go new file mode 100644 index 00000000..08452277 --- /dev/null +++ b/proxy-router/internal/config/rating_config.go @@ -0,0 +1,28 @@ +package config + +import ( + "fmt" + + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/rating" +) + +const ( + DefaultRatingConfigPath = "rating-config.json" +) + +func LoadScorer(path string, log lib.ILogger) (*rating.Rating, error) { + filePath := DefaultRatingConfigPath + if path != "" { + filePath = path + } + + modelsConfig, err := lib.ReadJSONFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read models config file: %s", err) + } + + log.Infof("rating config loaded from file: %s", filePath) + + return rating.NewRatingFromConfig([]byte(modelsConfig)) +} diff --git a/proxy-router/internal/proxyapi/proxy_sender.go b/proxy-router/internal/proxyapi/proxy_sender.go index febfd49c..47ef64c9 100644 --- a/proxy-router/internal/proxyapi/proxy_sender.go +++ b/proxy-router/internal/proxyapi/proxy_sender.go @@ -249,7 +249,7 @@ func (p *ProxyServiceSender) rpcRequest(url string, rpcMessage *msgs.RPCMessage) conn, err := dialer.Dial("tcp", url) if err != nil { err = lib.WrapError(ErrConnectProvider, err) - p.log.Errorf("%s", err) + p.log.Warnf(err.Error()) return nil, http.StatusInternalServerError, err } defer conn.Close() @@ -392,7 +392,7 @@ func (p *ProxyServiceSender) rpcRequestStreamV2( conn, err := dialer.Dial("tcp", url) if err != nil { err = lib.WrapError(ErrConnectProvider, err) - p.log.Errorf("%s", err) + p.log.Warnf(err.Error()) return nil, 0, 0, err } defer conn.Close() diff --git a/proxy-router/internal/rating/common.go b/proxy-router/internal/rating/common.go new file mode 100644 index 00000000..ca255e04 --- /dev/null +++ b/proxy-router/internal/rating/common.go @@ -0,0 +1,69 @@ +package rating + +import ( + "math" + + s "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" +) + +// ratioScore calculates the ratio of two numbers +func ratioScore(num, denom uint32) float64 { + if denom == 0 { + return 0 + } + return float64(num) / float64(denom) +} + +// normZIndex normalizes the value using z-index +func normZIndex(pmMean int64, mSD s.LibSDSD, obsNum int64) float64 { + sd := getSD(mSD, obsNum) + if sd == 0 { + return 0 + } + // TODO: consider variance(SD) of provider model stats + return float64(pmMean-mSD.Mean) / sd +} + +// normRange normalizes the incoming data within the range [-normRange, normRange] +// cutting off the values outside the range, and then shifts and scales to the range [0, 1] +func normRange(input float64, normRange float64) float64 { + return cutRange01((input + normRange) / (2.0 * normRange)) +} + +// getSD calculates the standard deviation from the standard deviation struct +func getSD(sd s.LibSDSD, obsNum int64) float64 { + return math.Sqrt(getVariance(sd, obsNum)) +} + +// getVariance calculates the variance from the standard deviation struct +func getVariance(sd s.LibSDSD, obsNum int64) float64 { + if obsNum <= 1 { + return 0 + } + return float64(sd.SqSum) / float64(obsNum-1) +} + +// cutRange01 cuts the value to the range [0, 1] +func cutRange01(val float64) float64 { + if val > 1 { + return 1 + } + if val < 0 { + return 0 + } + return val +} + +// normMinMax normalizes the value within the range [min, max] to the range [0, 1] +func normMinMax(val, min, max int64) float64 { + if max == min { + return 0 + } + if val < min { + return 0 + } + if val > max { + return 1 + } + return float64(val-min) / float64(max-min) +} diff --git a/proxy-router/internal/rating/interface.go b/proxy-router/internal/rating/interface.go new file mode 100644 index 00000000..a0fb119f --- /dev/null +++ b/proxy-router/internal/rating/interface.go @@ -0,0 +1,31 @@ +package rating + +import ( + "math/big" + + s "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/sessionrouter" +) + +type Scorer interface { + // GetScore returns the score of a bid. Returns -Inf if the bid should be skipped + GetScore(args *ScoreInput) float64 +} + +// ScoreInput is a struct that holds the input data for the rating algorithm of a bid +type ScoreInput struct { + ProviderModel *s.IStatsStorageProviderModelStats // stats of the provider specific to the model + Model *s.IStatsStorageModelStats // stats of the model across providers + PricePerSecond *big.Int + ProviderStake *big.Int + MinStake *big.Int +} + +func NewScoreArgs() *ScoreInput { + return &ScoreInput{ + ProviderModel: &s.IStatsStorageProviderModelStats{}, + Model: &s.IStatsStorageModelStats{}, + ProviderStake: big.NewInt(0), + PricePerSecond: big.NewInt(0), + MinStake: big.NewInt(0), + } +} diff --git a/proxy-router/internal/rating/mock.go b/proxy-router/internal/rating/mock.go new file mode 100644 index 00000000..e3c6d418 --- /dev/null +++ b/proxy-router/internal/rating/mock.go @@ -0,0 +1,12 @@ +package rating + +type ScorerMock struct { +} + +func NewScorerMock() *ScorerMock { + return &ScorerMock{} +} + +func (m *ScorerMock) GetScore(args *ScoreInput) float64 { + return 0.5 +} diff --git a/proxy-router/internal/rating/rating-config-schema.json b/proxy-router/internal/rating/rating-config-schema.json new file mode 100644 index 00000000..3672d6cb --- /dev/null +++ b/proxy-router/internal/rating/rating-config-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": ["algorithm", "params"], + "properties": { + "algorithm": { + "type": "string", + "enum": ["default"] + }, + "providerAllowlist": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "algorithm": { + "const": "default" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "./scorer_default_schema.json" + } + } + } + } + ] +} diff --git a/proxy-router/internal/rating/rating.go b/proxy-router/internal/rating/rating.go new file mode 100644 index 00000000..2037ce82 --- /dev/null +++ b/proxy-router/internal/rating/rating.go @@ -0,0 +1,64 @@ +package rating + +import ( + "math" + "sort" + + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/ethereum/go-ethereum/common" +) + +// Filters bids based on the config, uses the scorer to rate the bids and sorts them +type Rating struct { + scorer Scorer + providerAllowList map[common.Address]struct{} +} + +func (r *Rating) RateBids(scoreInputs []RatingInput, log lib.ILogger) []RatingRes { + scoredBids := make([]RatingRes, 0) + + for _, input := range scoreInputs { + score := r.scorer.GetScore(&input.ScoreInput) + if !r.isAllowed(input.ProviderID) { + log.Warnf("provider %s is not in the allow list, skipping", input.ProviderID.String()) + continue + } + + if math.IsNaN(score) || math.IsInf(score, 0) { + log.Warnf("provider score is not valid %d for %+v), skipping", score, input) + continue + } + + scoredBid := RatingRes{ + BidID: input.BidID, + Score: score, + } + scoredBids = append(scoredBids, scoredBid) + } + + sort.Slice(scoredBids, func(i, j int) bool { + return scoredBids[i].Score > scoredBids[j].Score + }) + + return scoredBids +} + +func (r *Rating) isAllowed(provider common.Address) bool { + if len(r.providerAllowList) == 0 { + return true + } + _, ok := r.providerAllowList[provider] + return ok +} + +type RatingInput struct { + ScoreInput + BidID common.Hash + ModelID common.Hash + ProviderID common.Address +} + +type RatingRes struct { + BidID common.Hash + Score float64 +} diff --git a/proxy-router/internal/rating/rating_config.go b/proxy-router/internal/rating/rating_config.go new file mode 100644 index 00000000..dc623570 --- /dev/null +++ b/proxy-router/internal/rating/rating_config.go @@ -0,0 +1,78 @@ +package rating + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/ethereum/go-ethereum/common" +) + +type RatingConfig struct { + Algorithm string `json:"algorithm"` + Params json.RawMessage `json:"params"` + ProviderAllowList []common.Address `json:"providerAllowlist"` +} + +func NewRatingFromConfig(config json.RawMessage) (*Rating, error) { + var cfg RatingConfig + err := json.Unmarshal(config, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal rating config: %w", err) + } + + scorer, err := factory(cfg.Algorithm, cfg.Params) + if err != nil { + return nil, err + } + + allowList := map[common.Address]struct{}{} + for _, addr := range cfg.ProviderAllowList { + allowList[addr] = struct{}{} + } + + providerAllowListLegacy := os.Getenv("PROVIDER_ALLOW_LIST") + + if providerAllowListLegacy != "" { + addresses := strings.Split(providerAllowListLegacy, ",") + for _, address := range addresses { + addr := strings.TrimSpace(address) + allowList[common.HexToAddress(addr)] = struct{}{} + } + } + + return &Rating{ + scorer: scorer, + providerAllowList: allowList, + }, nil +} + +var ( + ErrUnknownAlgorithm = errors.New("unknown rating algorithm") + ErrAlgorithmParams = errors.New("invalid algorithm params") +) + +func factory(algo string, params json.RawMessage) (a Scorer, err error) { + switch algo { + case ScorerNameDefault: + a, err = NewScorerDefaultFromJSON(params) + break + // place here the new rating algorithms + // + // case "new_algorithm": + // a, err = NewScorerNewAlgorithmFromJSON(params) + // break + // + } + if err != nil { + return nil, lib.WrapError(ErrAlgorithmParams, fmt.Errorf("algorithm %s, error: %w, config: %s", algo, err, params)) + } + if a == nil { + return nil, lib.WrapError(ErrUnknownAlgorithm, fmt.Errorf("%s", algo)) + } + + return a, nil +} diff --git a/proxy-router/internal/rating/scorer_default.go b/proxy-router/internal/rating/scorer_default.go new file mode 100644 index 00000000..fd5698b1 --- /dev/null +++ b/proxy-router/internal/rating/scorer_default.go @@ -0,0 +1,84 @@ +package rating + +import ( + "math" + "math/big" +) + +const ( + ScorerNameDefault = "default" +) + +type ScorerDefault struct { + params ScorerDefaultParams +} + +func NewScorerDefault(weights ScorerDefaultParams) *ScorerDefault { + return &ScorerDefault{params: weights} +} + +func (r *ScorerDefault) GetScore(args *ScoreInput) float64 { + tpsScore := r.params.Weights.TPS * tpsScore(args) + ttftScore := r.params.Weights.TTFT * ttftScore(args) + durationScore := r.params.Weights.Duration * durationScore(args) + successScore := r.params.Weights.Success * successScore(args) + stakeScore := r.params.Weights.Stake * stakeScore(args) + + totalScore := tpsScore + ttftScore + durationScore + successScore + stakeScore + + return priceAdjust(totalScore, args.PricePerSecond) +} + +func tpsScore(args *ScoreInput) float64 { + // normalize provider model tps value using stats from the model from other providers + zIndex := normZIndex(args.ProviderModel.TpsScaled1000.Mean, args.Model.TpsScaled1000, int64(args.Model.Count)) + + // cut off the values outside the range [-3, 3] and normalize to [0, 1] + return normRange(zIndex, 3.0) +} + +func ttftScore(args *ScoreInput) float64 { + // normalize provider model ttft value using stats for this model from other providers + zIndex := normZIndex(args.ProviderModel.TtftMs.Mean, args.Model.TtftMs, int64(args.Model.Count)) + + // invert the value, because the higher the ttft, the worse the score + zIndex = -zIndex + + // cut off the values outside the range [-3, 3] and normalize to [0, 1] + return normRange(zIndex, 3.0) +} + +func durationScore(args *ScoreInput) float64 { + // normalize provider model duration value using stats for this model from other providers + zIndex := normZIndex(int64(args.ProviderModel.TotalDuration), args.Model.TotalDuration, int64(args.Model.Count)) + + // cut off the values outside the range [-3, 3] and normalize to [0, 1] + return normRange(zIndex, 3.0) +} + +func successScore(args *ScoreInput) float64 { + // calculate the ratio of successful requests to total requests + ratio := ratioScore(args.ProviderModel.SuccessCount, args.ProviderModel.TotalCount) + + // the higher the ratio, the better the score + return math.Pow(ratio, 2) +} + +func stakeScore(args *ScoreInput) float64 { + // normalize provider stake value to the range [0, 10x min stake] + return normMinMax(args.ProviderStake.Int64(), args.MinStake.Int64(), 10*args.MinStake.Int64()) +} + +func priceAdjust(score float64, pricePerSecond *big.Int) float64 { + priceFloatDecimal, _ := pricePerSecond.Float64() + + // since the price is in decimals, we adjust it to have less of the exponent + // TODO: consider removing it and using the price as is, since the exponent will be + // the same for all providers and can be removed from the equation + priceFloat := priceFloatDecimal / math.Pow10(18) + + // price cannot be 0 according to smart contract, so we can safely divide by it + return score / priceFloat +} + +var _ Scorer = &ScorerDefault{} diff --git a/proxy-router/internal/rating/scorer_default_config.go b/proxy-router/internal/rating/scorer_default_config.go new file mode 100644 index 00000000..7261da9c --- /dev/null +++ b/proxy-router/internal/rating/scorer_default_config.go @@ -0,0 +1,34 @@ +package rating + +import ( + "encoding/json" + "errors" +) + +var ErrInvalidWeights = errors.New("weights must sum to 1") + +type ScorerDefaultParams struct { + Weights struct { + TPS float64 `json:"tps"` + TTFT float64 `json:"ttft"` + Duration float64 `json:"duration"` + Success float64 `json:"success"` + Stake float64 `json:"stake"` + } `json:"weights"` +} + +func (w *ScorerDefaultParams) Validate() bool { + return w.Weights.TPS+w.Weights.TTFT+w.Weights.Duration+w.Weights.Success+w.Weights.Stake == 1 +} + +func NewScorerDefaultFromJSON(data json.RawMessage) (*ScorerDefault, error) { + var params ScorerDefaultParams + err := json.Unmarshal(data, ¶ms) + if err != nil { + return nil, err + } + if !params.Validate() { + return nil, ErrInvalidWeights + } + return &ScorerDefault{params: params}, nil +} diff --git a/proxy-router/internal/rating/scorer_default_mock.go b/proxy-router/internal/rating/scorer_default_mock.go new file mode 100644 index 00000000..19468e24 --- /dev/null +++ b/proxy-router/internal/rating/scorer_default_mock.go @@ -0,0 +1,11 @@ +package rating + +func ScorerDefaultParamsMock() ScorerDefaultParams { + var sc = ScorerDefaultParams{} + sc.Weights.Duration = 0.24 + sc.Weights.Stake = 0.12 + sc.Weights.Success = 0.32 + sc.Weights.TPS = 0.24 + sc.Weights.TTFT = 0.08 + return sc +} diff --git a/proxy-router/internal/rating/scorer_default_schema.json b/proxy-router/internal/rating/scorer_default_schema.json new file mode 100644 index 00000000..79ea5f6b --- /dev/null +++ b/proxy-router/internal/rating/scorer_default_schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "weights": { + "type": "object", + "properties": { + "tps": { + "type": "number" + }, + "ttft": { + "type": "number" + }, + "duration": { + "type": "number" + }, + "success": { + "type": "number" + }, + "stake": { + "type": "number" + } + } + } + }, + "required": ["weights"] +} diff --git a/proxy-router/internal/rating/scorer_default_test.go b/proxy-router/internal/rating/scorer_default_test.go new file mode 100644 index 00000000..655d78db --- /dev/null +++ b/proxy-router/internal/rating/scorer_default_test.go @@ -0,0 +1,169 @@ +package rating + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseConfig(t *testing.T) { + cfg := `{"weights":{"tps":0.24,"ttft":0.08,"duration":0.24,"success":0.32,"stake":0.12}}` + k, err := NewScorerDefaultFromJSON([]byte(cfg)) + require.NoError(t, err) + fmt.Printf("%+v", k) +} + +func TestZeroObservations(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(1) + + score := sc.GetScore(args) + + require.NotEqual(t, math.NaN(), score) + require.NotEqual(t, math.Inf(0), score) + require.NotEqual(t, math.Inf(-1), score) +} + +func TestPriceIncrease(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(1) + + score1 := sc.GetScore(args) + + args.PricePerSecond.SetUint64(2) + score2 := sc.GetScore(args) + + require.Greater(t, score1, score2) +} + +func TestFirstTTFTObservation(t *testing.T) { + // when there is only one observation, of in rare case few with same values + // the score should be the same (because standard deviation is 0) + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + + args.ProviderModel.TtftMs.Mean = 100 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + args.Model.Count = 1 + score1 := sc.GetScore(args) + + args.ProviderModel.TtftMs.Mean = 200 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + args.Model.Count = 1 + score2 := sc.GetScore(args) + + require.Equal(t, score1, score2) +} + +func TestTPSImpact(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + + args.Model.Count = 2 + args.Model.TpsScaled1000.Mean = 100 + args.Model.TpsScaled1000.SqSum = 1000 + + args.ProviderModel.TpsScaled1000.Mean = 100 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + score1 := sc.GetScore(args) + + args.ProviderModel.TpsScaled1000.Mean = 150 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + score2 := sc.GetScore(args) + + require.Less(t, score1, score2) +} + +func TestTTFTImpact(t *testing.T) { + // when there is more than one observation, larger ttft should produce lower score + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + + args.Model.Count = 2 + args.Model.TtftMs.Mean = 100 + args.Model.TtftMs.SqSum = 1000 + + args.ProviderModel.TtftMs.Mean = 100 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + score1 := sc.GetScore(args) + + args.ProviderModel.TtftMs.Mean = 150 + args.ProviderModel.TotalCount = 1 + args.ProviderModel.SuccessCount = 1 + score2 := sc.GetScore(args) + + require.Greater(t, score1, score2) +} + +func TestSuccessScoreImpact(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + args.Model.Count = 2 + + args.ProviderModel.TotalCount = 2 + args.ProviderModel.SuccessCount = 1 + score1 := sc.GetScore(args) + + args.ProviderModel.SuccessCount = 2 + score2 := sc.GetScore(args) + + require.Less(t, score1, score2) +} + +func TestStakeScoreImpact(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + args.MinStake.SetUint64(1) + + args.ProviderStake.SetUint64(2) + score1 := sc.GetScore(args) + + args.ProviderStake.SetUint64(3) + score2 := sc.GetScore(args) + + require.Less(t, score1, score2) +} + +func TestStakeScoreImpactLimit(t *testing.T) { + // stake score should be capped at 10x min stake + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + minStake := uint64(10) + args.MinStake.SetUint64(minStake) + + args.ProviderStake.SetUint64(10 * minStake) + score1 := sc.GetScore(args) + + args.ProviderStake.SetUint64(11 * minStake) + score2 := sc.GetScore(args) + + require.Equal(t, score1, score2) +} + +func TestPriceImpact(t *testing.T) { + sc := NewScorerDefault(ScorerDefaultParamsMock()) + args := NewScoreArgs() + args.PricePerSecond.SetUint64(10000000000000000) + + score1 := sc.GetScore(args) + + args.PricePerSecond.SetUint64(20000000000000000) + score2 := sc.GetScore(args) + + require.Greater(t, score1, score2) +} diff --git a/proxy-router/internal/repositories/registries/provider_registry.go b/proxy-router/internal/repositories/registries/provider_registry.go index e239bd54..dfc90d08 100644 --- a/proxy-router/internal/repositories/registries/provider_registry.go +++ b/proxy-router/internal/repositories/registries/provider_registry.go @@ -136,6 +136,15 @@ func (g *ProviderRegistry) GetProviderById(ctx context.Context, id common.Addres return &provider, nil } +func (g *ProviderRegistry) GetMinStake(ctx context.Context) (*big.Int, error) { + minStake, err := g.providerRegistry.GetProviderMinimumStake(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, err + } + + return minStake, nil +} + func (g *ProviderRegistry) getMultipleProviders(ctx context.Context, IDs []common.Address) ([]common.Address, []providerregistry.IProviderStorageProvider, error) { args := make([][]interface{}, len(IDs)) for i, id := range IDs { diff --git a/proxy-router/rating-config.json b/proxy-router/rating-config.json new file mode 100644 index 00000000..22e3a95e --- /dev/null +++ b/proxy-router/rating-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "./internal/rating/rating-config-schema.json", + "algorithm": "default", + "providerAllowlist": [], + "params": { + "weights": { + "tps": 0.24, + "ttft": 0.08, + "duration": 0.24, + "success": 0.32, + "stake": 0.12 + } + } +} From e42356a32e974cdb721d32f3b29be7040cf7951f Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 14:21:36 +0100 Subject: [PATCH 26/41] fix: update schemas with titles and basic validations --- .../internal/config/models-config-schema.json | 39 +++++++++++-------- .../internal/rating/rating-config-schema.json | 13 +++++-- .../rating/scorer_default_schema.json | 34 +++++++++++++--- proxy-router/rating-config.json | 2 +- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/proxy-router/internal/config/models-config-schema.json b/proxy-router/internal/config/models-config-schema.json index 8922f86c..58f2d44d 100644 --- a/proxy-router/internal/config/models-config-schema.json +++ b/proxy-router/internal/config/models-config-schema.json @@ -1,40 +1,45 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "type": "object", "properties": { "models": { "type": "array", - "additionalProperties": { + "items": { "type": "object", "properties": { "modelId": { - "type": "string" + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{64}$", + "title": "Model ID", + "description": "The model ID from blockchain", + "examples": ["0x0000000000000000000000000000000000000000000000000000000000000001"] }, "modelName": { - "type": "string" + "type": "string", + "title": "Model Name", + "description": "The name of the model to be used from this provider" }, "apiType": { "type": "string", - "enum": [ - "openai", - "prodia-sd", - "prodia-sdxl" - ] + "enum": ["openai", "prodia-sd", "prodia-sdxl"], + "title": "API Type", + "description": "Defines the type of API to be used with this model" }, "apiUrl": { "type": "string", - "format": "uri" + "format": "uri", + "title": "API URL", + "description": "The URL of the API to be used with this model", + "examples": ["http://localhost:11434/v1"] }, "apiKey": { - "type": "string" + "type": "string", + "title": "API Key", + "description": "Optional API key" } }, - "required": [ - "modelName", - "apiType", - "apiUrl" - ] + "required": ["modelId", "modelName", "apiType", "apiUrl"] } } } -} \ No newline at end of file +} diff --git a/proxy-router/internal/rating/rating-config-schema.json b/proxy-router/internal/rating/rating-config-schema.json index 3672d6cb..88cb4e79 100644 --- a/proxy-router/internal/rating/rating-config-schema.json +++ b/proxy-router/internal/rating/rating-config-schema.json @@ -1,17 +1,22 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "type": "object", "required": ["algorithm", "params"], "properties": { "algorithm": { "type": "string", - "enum": ["default"] + "enum": ["default"], + "title": "Rating algorithm", + "description": "The algorithm used to calculate the rating of a provider" }, "providerAllowlist": { "type": "array", "items": { - "type": "string" - } + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "title": "Provider allowlist", + "description": "List of provider addresses that are allowed to open session with" } }, "allOf": [ diff --git a/proxy-router/internal/rating/scorer_default_schema.json b/proxy-router/internal/rating/scorer_default_schema.json index 79ea5f6b..def03b7f 100644 --- a/proxy-router/internal/rating/scorer_default_schema.json +++ b/proxy-router/internal/rating/scorer_default_schema.json @@ -1,24 +1,46 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "type": "object", "properties": { "weights": { + "title": "Weights", + "description": "Weights for each metric", "type": "object", "properties": { "tps": { - "type": "number" + "title": "TPS", + "description": "Tokens per second weight", + "type": "number", + "minimum": 0, + "maximum": 1 }, "ttft": { - "type": "number" + "title": "TTFT", + "description": "Time to first token weight", + "type": "number", + "minimum": 0, + "maximum": 1 }, "duration": { - "type": "number" + "title": "Duration", + "description": "Duration weight", + "type": "number", + "minimum": 0, + "maximum": 1 }, "success": { - "type": "number" + "title": "Success rate", + "description": "Success rate weight", + "type": "number", + "minimum": 0, + "maximum": 1 }, "stake": { - "type": "number" + "title": "Stake", + "description": "Stake weight", + "type": "number", + "minimum": 0, + "maximum": 1 } } } diff --git a/proxy-router/rating-config.json b/proxy-router/rating-config.json index 22e3a95e..0ef58e3f 100644 --- a/proxy-router/rating-config.json +++ b/proxy-router/rating-config.json @@ -1,7 +1,7 @@ { "$schema": "./internal/rating/rating-config-schema.json", "algorithm": "default", - "providerAllowlist": [], + "providerAllowlist": ["0x0000000000000000000000000000000000000000"], "params": { "weights": { "tps": 0.24, From 9dc1bce38e849d9d9ff9ca3c21046ed75c300fbd Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 14:21:54 +0100 Subject: [PATCH 27/41] chore: renaming --- proxy-router/cmd/main.go | 2 +- proxy-router/internal/config/rating_config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy-router/cmd/main.go b/proxy-router/cmd/main.go index 52e2af95..72ad8d9c 100644 --- a/proxy-router/cmd/main.go +++ b/proxy-router/cmd/main.go @@ -258,7 +258,7 @@ func start() error { log.Warnf("failed to load model config: %s, run with empty", err) } - scorer, err := config.LoadScorer(cfg.Proxy.RatingConfigPath, log) + scorer, err := config.LoadRating(cfg.Proxy.RatingConfigPath, log) if err != nil { return err } diff --git a/proxy-router/internal/config/rating_config.go b/proxy-router/internal/config/rating_config.go index 08452277..f47cbee0 100644 --- a/proxy-router/internal/config/rating_config.go +++ b/proxy-router/internal/config/rating_config.go @@ -11,7 +11,7 @@ const ( DefaultRatingConfigPath = "rating-config.json" ) -func LoadScorer(path string, log lib.ILogger) (*rating.Rating, error) { +func LoadRating(path string, log lib.ILogger) (*rating.Rating, error) { filePath := DefaultRatingConfigPath if path != "" { filePath = path From c043877bea382030fd1b0185451ec77d7821db2f Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 14:55:41 +0100 Subject: [PATCH 28/41] feat: stricter validation --- proxy-router/.gitignore | 3 ++- .../internal/config/models-config-schema.json | 24 ++++++++++--------- .../internal/rating/rating-config-schema.json | 3 ++- ...schema.json => scorer-default-schema.json} | 3 ++- proxy-router/rating-config.json.example | 14 +++++++++++ 5 files changed, 33 insertions(+), 14 deletions(-) rename proxy-router/internal/rating/{scorer_default_schema.json => scorer-default-schema.json} (94%) create mode 100644 proxy-router/rating-config.json.example diff --git a/proxy-router/.gitignore b/proxy-router/.gitignore index e77303d8..57b9093e 100644 --- a/proxy-router/.gitignore +++ b/proxy-router/.gitignore @@ -10,4 +10,5 @@ bin data* -models-config.json \ No newline at end of file +models-config.json +rating-config.json \ No newline at end of file diff --git a/proxy-router/internal/config/models-config-schema.json b/proxy-router/internal/config/models-config-schema.json index 58f2d44d..f5a094f9 100644 --- a/proxy-router/internal/config/models-config-schema.json +++ b/proxy-router/internal/config/models-config-schema.json @@ -8,34 +8,36 @@ "type": "object", "properties": { "modelId": { - "type": "string", - "pattern": "^(0x)?[0-9a-fA-F]{64}$", "title": "Model ID", "description": "The model ID from blockchain", + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{64}$", "examples": ["0x0000000000000000000000000000000000000000000000000000000000000001"] }, "modelName": { - "type": "string", "title": "Model Name", - "description": "The name of the model to be used from this provider" + "description": "The name of the model to be used from this provider", + "type": "string", + "minLength": 1 }, "apiType": { - "type": "string", - "enum": ["openai", "prodia-sd", "prodia-sdxl"], "title": "API Type", - "description": "Defines the type of API to be used with this model" + "description": "Defines the type of API to be used with this model", + "type": "string", + "enum": ["openai", "prodia-sd", "prodia-sdxl"] }, "apiUrl": { - "type": "string", - "format": "uri", "title": "API URL", "description": "The URL of the API to be used with this model", + "type": "string", + "format": "uri", "examples": ["http://localhost:11434/v1"] }, "apiKey": { - "type": "string", "title": "API Key", - "description": "Optional API key" + "description": "Optional API key", + "type": "string", + "minLength": 1 } }, "required": ["modelId", "modelName", "apiType", "apiUrl"] diff --git a/proxy-router/internal/rating/rating-config-schema.json b/proxy-router/internal/rating/rating-config-schema.json index 88cb4e79..3db4154c 100644 --- a/proxy-router/internal/rating/rating-config-schema.json +++ b/proxy-router/internal/rating/rating-config-schema.json @@ -15,6 +15,7 @@ "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" }, + "uniqueItems": true, "title": "Provider allowlist", "description": "List of provider addresses that are allowed to open session with" } @@ -31,7 +32,7 @@ "then": { "properties": { "params": { - "$ref": "./scorer_default_schema.json" + "$ref": "./scorer-default-schema.json" } } } diff --git a/proxy-router/internal/rating/scorer_default_schema.json b/proxy-router/internal/rating/scorer-default-schema.json similarity index 94% rename from proxy-router/internal/rating/scorer_default_schema.json rename to proxy-router/internal/rating/scorer-default-schema.json index def03b7f..6e051d96 100644 --- a/proxy-router/internal/rating/scorer_default_schema.json +++ b/proxy-router/internal/rating/scorer-default-schema.json @@ -42,7 +42,8 @@ "minimum": 0, "maximum": 1 } - } + }, + "required": ["tps", "ttft", "duration", "success", "stake"] } }, "required": ["weights"] diff --git a/proxy-router/rating-config.json.example b/proxy-router/rating-config.json.example new file mode 100644 index 00000000..0ef58e3f --- /dev/null +++ b/proxy-router/rating-config.json.example @@ -0,0 +1,14 @@ +{ + "$schema": "./internal/rating/rating-config-schema.json", + "algorithm": "default", + "providerAllowlist": ["0x0000000000000000000000000000000000000000"], + "params": { + "weights": { + "tps": 0.24, + "ttft": 0.08, + "duration": 0.24, + "success": 0.32, + "stake": 0.12 + } + } +} From a719073670adb17de6282b12d1852d39d629cb6e Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 14:56:06 +0100 Subject: [PATCH 29/41] chore: logging --- proxy-router/internal/config/rating_config.go | 6 ++--- .../{rating_config.go => rating_factory.go} | 22 ++++++++++++++++--- .../contracts/log_watcher_polling.go | 8 +++---- 3 files changed, 26 insertions(+), 10 deletions(-) rename proxy-router/internal/rating/{rating_config.go => rating_factory.go} (70%) diff --git a/proxy-router/internal/config/rating_config.go b/proxy-router/internal/config/rating_config.go index f47cbee0..3648a456 100644 --- a/proxy-router/internal/config/rating_config.go +++ b/proxy-router/internal/config/rating_config.go @@ -17,12 +17,12 @@ func LoadRating(path string, log lib.ILogger) (*rating.Rating, error) { filePath = path } - modelsConfig, err := lib.ReadJSONFile(filePath) + config, err := lib.ReadJSONFile(filePath) if err != nil { - return nil, fmt.Errorf("failed to read models config file: %s", err) + return nil, fmt.Errorf("failed to rating config file: %s", err) } log.Infof("rating config loaded from file: %s", filePath) - return rating.NewRatingFromConfig([]byte(modelsConfig)) + return rating.NewRatingFromConfig([]byte(config), log) } diff --git a/proxy-router/internal/rating/rating_config.go b/proxy-router/internal/rating/rating_factory.go similarity index 70% rename from proxy-router/internal/rating/rating_config.go rename to proxy-router/internal/rating/rating_factory.go index dc623570..34ff1ef8 100644 --- a/proxy-router/internal/rating/rating_config.go +++ b/proxy-router/internal/rating/rating_factory.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "os" + "sort" "strings" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/ethereum/go-ethereum/common" + "golang.org/x/exp/maps" ) type RatingConfig struct { @@ -17,13 +19,15 @@ type RatingConfig struct { ProviderAllowList []common.Address `json:"providerAllowlist"` } -func NewRatingFromConfig(config json.RawMessage) (*Rating, error) { +func NewRatingFromConfig(config json.RawMessage, log lib.ILogger) (*Rating, error) { var cfg RatingConfig err := json.Unmarshal(config, &cfg) if err != nil { return nil, fmt.Errorf("failed to unmarshal rating config: %w", err) } + log.Infof("rating algorithm: %s, params %s", cfg.Algorithm, string(cfg.Params)) + scorer, err := factory(cfg.Algorithm, cfg.Params) if err != nil { return nil, err @@ -37,11 +41,23 @@ func NewRatingFromConfig(config json.RawMessage) (*Rating, error) { providerAllowListLegacy := os.Getenv("PROVIDER_ALLOW_LIST") if providerAllowListLegacy != "" { + log.Warnf("PROVIDER_ALLOW_LIST is deprecated, please use providerAllowList in rating config") addresses := strings.Split(providerAllowListLegacy, ",") for _, address := range addresses { - addr := strings.TrimSpace(address) - allowList[common.HexToAddress(addr)] = struct{}{} + addr := common.HexToAddress(strings.TrimSpace(address)) + allowList[addr] = struct{}{} } + log.Warnf("added %d addresses from PROVIDER_ALLOW_LIST", len(addresses)) + } + + if len(allowList) == 0 { + log.Infof("providerAllowList is disabled, all providers are allowed") + } else { + keys := maps.Keys(allowList) + sort.Slice(keys, func(i, j int) bool { + return keys[i].Hex() < keys[j].Hex() + }) + log.Infof("providerAllowList: %v", keys) } return &Rating{ diff --git a/proxy-router/internal/repositories/contracts/log_watcher_polling.go b/proxy-router/internal/repositories/contracts/log_watcher_polling.go index 0b773bf2..427bea89 100644 --- a/proxy-router/internal/repositories/contracts/log_watcher_polling.go +++ b/proxy-router/internal/repositories/contracts/log_watcher_polling.go @@ -32,7 +32,7 @@ func NewLogWatcherPolling(client i.EthClient, pollInterval time.Duration, maxRec client: client, pollInterval: pollInterval, maxReconnects: maxReconnects, - log: log, + log: log.Named("POLLING"), } } @@ -76,7 +76,7 @@ func (w *LogWatcherPolling) pollReconnect(ctx context.Context, quit <-chan struc if w.maxReconnects == 0 { maxReconnects = "∞" } - w.log.Warnf("polling error, retrying (%d/%s): %s", i, maxReconnects, err) + w.log.Warnf("request error, retrying (%d/%s): %s", i, maxReconnects, err) lastErr = err // retry delay @@ -89,7 +89,7 @@ func (w *LogWatcherPolling) pollReconnect(ctx context.Context, quit <-chan struc } } - err := fmt.Errorf("polling error, retries exhausted (%d), stopping: %s", w.maxReconnects, lastErr) + err := fmt.Errorf("request error, retries exhausted (%d), stopping: %s", w.maxReconnects, lastErr) w.log.Warnf(err.Error()) return nextFromBlock, err } @@ -123,7 +123,7 @@ func (w *LogWatcherPolling) pollChanges(ctx context.Context, nextFromBlock *big. ToBlock: currentBlock.Number, } - w.log.Debugf("=====> calling poll from %s to %s", query.FromBlock.String(), query.ToBlock.String()) + w.log.Debugf("requesting changes from %s to %s block", query.FromBlock.String(), query.ToBlock.String()) sub, err := w.client.FilterLogs(ctx, query) if err != nil { return nextFromBlock, err From 6a585020101610c1f2bc3879d6d034951ecfbe06 Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 15:09:04 +0100 Subject: [PATCH 30/41] ci: update build --- .github/actions/copy_env_files/action.yml | 2 ++ .github/workflows/models-config.json | 8 ++++++-- .github/workflows/rating-config.json | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/rating-config.json diff --git a/.github/actions/copy_env_files/action.yml b/.github/actions/copy_env_files/action.yml index 16d400ea..6b63ba06 100644 --- a/.github/actions/copy_env_files/action.yml +++ b/.github/actions/copy_env_files/action.yml @@ -8,6 +8,8 @@ runs: run: | cp ./.github/workflows/models-config.json ./proxy-router/models-config.json cp ./.github/workflows/models-config.json models-config.json + cp ./.github/workflows/rating-config.json ./proxy-router/rating-config.json + cp ./.github/workflows/rating-config.json rating-config.json if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then cp ./.github/workflows/proxy-router.main.env ./proxy-router/.env cp ./.github/workflows/proxy-router.main.env .env diff --git a/.github/workflows/models-config.json b/.github/workflows/models-config.json index c4004d23..49fa043a 100644 --- a/.github/workflows/models-config.json +++ b/.github/workflows/models-config.json @@ -1,7 +1,11 @@ { - "0x0000000000000000000000000000000000000000000000000000000000000000": { + "$schema": "https://raw.githubusercontent.com/Lumerin-protocol/Morpheus-Lumerin-Node/a719073670adb17de6282b12d1852d39d629cb6e/proxy-router/internal/config/models-config-schema.json", + "models": [ + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000000", "modelName": "llama2", "apiType": "openai", "apiUrl": "http://localhost:8080/v1" } - } \ No newline at end of file + ] +} diff --git a/.github/workflows/rating-config.json b/.github/workflows/rating-config.json new file mode 100644 index 00000000..8b3a6672 --- /dev/null +++ b/.github/workflows/rating-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/Lumerin-protocol/Morpheus-Lumerin-Node/a719073670adb17de6282b12d1852d39d629cb6e/proxy-router/internal/rating/rating-config-schema.json", + "algorithm": "default", + "providerAllowlist": ["0x0000000000000000000000000000000000000000"], + "params": { + "weights": { + "tps": 0.24, + "ttft": 0.08, + "duration": 0.24, + "success": 0.32, + "stake": 0.12 + } + } +} From 2a9f8c26124d159e77ccb258bc2861fc1e9b42f9 Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 15:53:56 +0100 Subject: [PATCH 31/41] docs: update docs --- docs/models-config.json.md | 101 ++++++++++++++++++++++++------------- docs/rating-config.json.md | 26 ++++++++++ 2 files changed, 91 insertions(+), 36 deletions(-) create mode 100644 docs/rating-config.json.md diff --git a/docs/models-config.json.md b/docs/models-config.json.md index 0452b7a8..deb97004 100644 --- a/docs/models-config.json.md +++ b/docs/models-config.json.md @@ -1,42 +1,71 @@ -# Example models config file. Local model configurations are stored in this file -* `root_key` (required) is the model id -* `modelName` (required) is the name of the model -* `apiType` (required) is the type of the model api. Currently supported values are "prodia" and "openai" -* `apiUrl` (required) is the url of the LLM server or model API -* `apiKey` (optional) is the api key for the model -* `cononcurrentSlots` (optional) are number of available distinct chats on the llm server and used for capacity policy -* `capacityPolicy` (optional) can be one of the following: "idle_timeout", "simple" +# Example models config file. Local model configurations are stored in this file -## Examples of models-config.json entries -* The first key (`0x000...0000`) is the model id of the local default model (llama2) -* The middle two keys are examples of externally hosted and owned models where the morpheus-proxy-router enables proxying requests to the external model API -* The last key is an example of a model hosted on a server owned by the morpheus-proxy-router operator +- `modelId` (required) is the model id +- `modelName` (required) is the name of the model +- `apiType` (required) is the type of the model api. Currently supported values are "prodia" and "openai" +- `apiUrl` (required) is the url of the LLM server or model API +- `apiKey` (optional) is the api key for the model +- `concurrentSlots` (optional) are number of available distinct chats on the llm server and used for capacity policy +- `capacityPolicy` (optional) can be one of the following: "idle_timeout", "simple" -```bash +## Examples of models-config.json entries + +This file enables the morpheus-proxy-router to route requests to the correct model API. The model API can be hosted on the same server as the morpheus-proxy-router or on an external server. Please refer to the json-schema for descriptions and list of required and optional fields. + +```json { - "0x0000000000000000000000000000000000000000000000000000000000000000": { - "modelName": "llama2", - "apiType": "openai", - "apiUrl": "http://localhost:8080/v1" - }, - "0x60d5900b10534de1a668fd990bd790fa3fe04df8177e740f249c750023a680fb": { - "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", - "apiType": "prodia-sd", - "apiUrl": "https://api.prodia.com/v1", - "apiKey": "replace-with-your-api-key" + "$schema": "./internal/config/models-config-schema.json", + "models": [ + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "modelName": "llama2", + "apiType": "openai", + "apiUrl": "http://localhost:8080/v1", + "capacityPolicy": "simple", + "concurrentSlots": 2 }, - "0x0d90cf8ca0a811a5cd2347148171e9a2401e9fbc32f683b648c5c1381df91ff7": { - "modelName": "animagineXLV3_v30.safetensors [75f2f05b]", - "apiType": "prodia-sdxl", - "apiUrl": "https://api.prodia.com/v1", - "apiKey": "replace-with-your-api-key" - }, - "0x06c7b502d0b7f14a96bb4fda5f2ba941068601d5f6b90804c9330e96b093b0ce": { - "modelName": "LMR-Collective Cognition Mistral 7B", - "apiType": "openai", - "apiUrl": "http://llmserver.domain.io:8080/v1", - "concurrentSlots": 8, - "capacityPolicy": "simple" + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000001", + "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", + "apiType": "prodia-sd", + "apiUrl": "https://api.prodia.com/v1", + "apiKey": "FILL_ME_IN" } + ] +} +``` + +## Examples of models-config.json entries LEGACY + +- The first key (`0x000...0000`) is the model id of the local default model (llama2) +- The middle two keys are examples of externally hosted and owned models where the morpheus-proxy-router enables proxying requests to the external model API +- The last key is an example of a model hosted on a server owned by the morpheus-proxy-router operator + +```json +{ + "0x0000000000000000000000000000000000000000000000000000000000000000": { + "modelName": "llama2", + "apiType": "openai", + "apiUrl": "http://localhost:8080/v1" + }, + "0x60d5900b10534de1a668fd990bd790fa3fe04df8177e740f249c750023a680fb": { + "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", + "apiType": "prodia-sd", + "apiUrl": "https://api.prodia.com/v1", + "apiKey": "replace-with-your-api-key" + }, + "0x0d90cf8ca0a811a5cd2347148171e9a2401e9fbc32f683b648c5c1381df91ff7": { + "modelName": "animagineXLV3_v30.safetensors [75f2f05b]", + "apiType": "prodia-sdxl", + "apiUrl": "https://api.prodia.com/v1", + "apiKey": "replace-with-your-api-key" + }, + "0x06c7b502d0b7f14a96bb4fda5f2ba941068601d5f6b90804c9330e96b093b0ce": { + "modelName": "LMR-Collective Cognition Mistral 7B", + "apiType": "openai", + "apiUrl": "http://llmserver.domain.io:8080/v1", + "concurrentSlots": 8, + "capacityPolicy": "simple" + } } -``` \ No newline at end of file +``` diff --git a/docs/rating-config.json.md b/docs/rating-config.json.md new file mode 100644 index 00000000..41e0500e --- /dev/null +++ b/docs/rating-config.json.md @@ -0,0 +1,26 @@ +# Information about rating-config.json configuration file + +This file configures rating system of proxy-router, that is responsible for provider selection. The file should be placed in the root directory of the project. + +- `providerAllowlist` - the list of providers that are allowed to be used by the proxy-router. Keep it empty to allow all providers. +- `algorithm` - the algorithm used for rating calculation. +- `params` - algorithm parameters, like weights for different metrics. Each algorithm has its own set of parameters. + +Please refer to the json schema for the full list of available fields. + +```json +{ + "$schema": "./internal/rating/rating-config-schema.json", + "algorithm": "default", + "providerAllowlist": [], + "params": { + "weights": { + "tps": 0.24, + "ttft": 0.08, + "duration": 0.24, + "success": 0.32, + "stake": 0.12 + } + } +} +``` From d7ded440c6ac4833dfe09cb03477330e6e14e43b Mon Sep 17 00:00:00 2001 From: shev Date: Tue, 26 Nov 2024 15:54:17 +0100 Subject: [PATCH 32/41] docs: update schema and example --- docs/proxy-router.all.env | 2 -- .../internal/config/models-config-schema.json | 12 ++++++++++ proxy-router/models-config.json.example | 22 +++++++++---------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/proxy-router.all.env b/docs/proxy-router.all.env index 76deed79..f9e4c862 100644 --- a/docs/proxy-router.all.env +++ b/docs/proxy-router.all.env @@ -71,8 +71,6 @@ PROXY_STORE_CHAT_CONTEXT=true PROXY_FORWARD_CHAT_CONTEXT=true # Path to models configuration file MODELS_CONFIG_PATH= -# Comma-separated list of provider addresses allowed to open a session -PROVIDER_ALLOW_LIST= # System Configurations # Enable system-level configuration adjustments diff --git a/proxy-router/internal/config/models-config-schema.json b/proxy-router/internal/config/models-config-schema.json index f5a094f9..a3910f93 100644 --- a/proxy-router/internal/config/models-config-schema.json +++ b/proxy-router/internal/config/models-config-schema.json @@ -38,6 +38,18 @@ "description": "Optional API key", "type": "string", "minLength": 1 + }, + "concurrentSlots": { + "title": "Concurrent Slots", + "description": "The number of concurrent slots to be used with this model", + "type": "integer", + "minimum": 1 + }, + "capacityPolicy": { + "title": "Capacity Policy", + "description": "The policy to be used for capacity management", + "type": "string", + "enum": ["simple", "idle_timeout"] } }, "required": ["modelId", "modelName", "apiType", "apiUrl"] diff --git a/proxy-router/models-config.json.example b/proxy-router/models-config.json.example index cd7792fb..e35e18e9 100644 --- a/proxy-router/models-config.json.example +++ b/proxy-router/models-config.json.example @@ -2,17 +2,17 @@ "$schema": "./internal/config/models-config-schema.json", "models": [ { - "0x0000000000000000000000000000000000000000000000000000000000000000": { - "modelName": "llama2", - "apiType": "openai", - "apiUrl": "http://localhost:8080/v1" - }, - "0x0000000000000000000000000000000000000000000000000000000000000001": { - "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", - "apiType": "prodia-sd", - "apiUrl": "https://api.prodia.com/v1", - "apiKey": "" - } + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "modelName": "llama2", + "apiType": "openai", + "apiUrl": "http://localhost:8080/v1" + }, + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000001", + "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", + "apiType": "prodia-sd", + "apiUrl": "https://api.prodia.com/v1", + "apiKey": "FILL_ME_IN" } ] } From 39cb286fc7a53047dd397c521949c104d965f2e0 Mon Sep 17 00:00:00 2001 From: "bohdan.konorin" Date: Wed, 27 Nov 2024 10:36:15 +0100 Subject: [PATCH 33/41] Added UI tag for chain --- .../src/components/common/ChainHeader.jsx | 85 +++++++++++++++++++ .../src/components/dashboard/Dashboard.jsx | 13 ++- .../renderer/src/components/icons/ArbLogo.jsx | 73 ++++++++++++++++ 3 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 ui-desktop/src/renderer/src/components/common/ChainHeader.jsx create mode 100644 ui-desktop/src/renderer/src/components/icons/ArbLogo.jsx diff --git a/ui-desktop/src/renderer/src/components/common/ChainHeader.jsx b/ui-desktop/src/renderer/src/components/common/ChainHeader.jsx new file mode 100644 index 00000000..d533bcef --- /dev/null +++ b/ui-desktop/src/renderer/src/components/common/ChainHeader.jsx @@ -0,0 +1,85 @@ + +import styled from 'styled-components'; + +import ArbLogo from '../icons/ArbLogo'; + +const Container = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + position: sticky; + width: 100%; + padding: 0 0 1.5rem 0; + z-index: 2; + right: 0; + left: 0; + top: 0; +`; + +const TitleRow = styled.div` + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +`; + +const Title = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 2.4rem; + line-height: 3rem; + white-space: nowrap; + margin: 0; + font-weight: 600; + color: ${p => p.theme.colors.morMain}; + margin-right: 2.4rem; + cursor: default; + /* width: 100%; */ + + @media (min-width: 1140px) { + } + + @media (min-width: 1200px) { + } +`; + +const ChainContainer = styled.div` + display: flex; + align-items: center; + gap: 10px; + color: white; + font-size: 1.2rem; + padding: 0.6rem 1.2rem; + background: rgba(255,255,255,0.04); + border-width: 1px; + border: 1px solid rgba(255,255,255,0.04); +` + +const getChainLogo = (chainId) => { + const arbLogo = (); + if(chainId === 42161 || chainId === 421614) { + return arbLogo; + } + // Handle other icons (ETH, Base, etc.) + + return arbLogo; +} + +export const ChainHeader = ({ title, children, chain }) => ( + + + + <div>{title}</div> + <ChainContainer> + {getChainLogo(Number(chain?.chainId || 42161))} + <div>{chain?.displayName || "Arbitrum"}</div> + </ChainContainer> + + {children} + + +); diff --git a/ui-desktop/src/renderer/src/components/dashboard/Dashboard.jsx b/ui-desktop/src/renderer/src/components/dashboard/Dashboard.jsx index 0edd3c7d..5d5d4ef6 100644 --- a/ui-desktop/src/renderer/src/components/dashboard/Dashboard.jsx +++ b/ui-desktop/src/renderer/src/components/dashboard/Dashboard.jsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import withDashboardState from '../../store/hocs/withDashboardState' -import { LayoutHeader } from '../common/LayoutHeader' +import { ChainHeader } from '../common/ChainHeader' import BalanceBlock from './BalanceBlock' import TransactionModal from './tx-modal' import TxList from './tx-list/TxList' @@ -11,7 +11,6 @@ import { View } from '../common/View' import { toUSD } from '../../store/utils/syncAmounts'; import { BtnAccent, - } from './BalanceBlock.styles'; const CustomBtn = styled(BtnAccent)` @@ -27,14 +26,11 @@ const WidjetsContainer = styled.div` const WidjetItem = styled.div` margin: 1.6rem 0 1.6rem; - background-color: #fff; padding: 1.6rem 3.2rem; border-radius: 0.375rem; color: white; max-width: 720px; - background: rgba(255,255,255,0.04); - border-width: 1px; - border: 1px solid rgba(255,255,255,0.04); + color: white; ` @@ -43,6 +39,9 @@ const StakingWidjet = styled(WidjetItem)` flex-direction: column; align-items: center; justify-content: center; + background: rgba(255,255,255,0.04); + border-width: 1px; + border: 1px solid rgba(255,255,255,0.04); ` const Dashboard = ({ @@ -157,7 +156,7 @@ const Dashboard = ({ return ( - + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default ArbLogo; From 70615cfc194d253a251379e876c6edf34fed6fcf Mon Sep 17 00:00:00 2001 From: shev Date: Wed, 27 Nov 2024 13:45:05 +0100 Subject: [PATCH 34/41] fix: incorrect pagination when overflow --- proxy-router/docs/docs.go | 179 ++++++++++++++---- proxy-router/docs/swagger.json | 179 ++++++++++++++---- proxy-router/docs/swagger.yaml | 136 ++++++++++--- .../internal/blockchainapi/controller.go | 42 ++-- .../internal/blockchainapi/structs/req.go | 12 +- .../repositories/registries/pagination.go | 42 +++- .../registries/pagination_test.go | 76 ++++++++ .../registries/session_router_test.go | 7 +- 8 files changed, 538 insertions(+), 135 deletions(-) create mode 100644 proxy-router/internal/repositories/registries/pagination_test.go diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index f8967e27..e9280a0a 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -261,16 +261,26 @@ const docTemplate = `{ ], "summary": "Get models list", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" } ], @@ -361,6 +371,28 @@ const docTemplate = `{ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, + { + "type": "string", + "example": "0", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -454,16 +486,26 @@ const docTemplate = `{ ], "summary": "Get providers list", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" } ], @@ -517,6 +559,15 @@ const docTemplate = `{ "providers" ], "summary": "Deregister Provider", + "parameters": [ + { + "type": "string", + "description": "Provider ID", + "name": "id", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", @@ -540,22 +591,32 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "Provider ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Provider ID", - "name": "id", - "in": "path", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -585,6 +646,28 @@ const docTemplate = `{ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, + { + "type": "string", + "example": "0", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -724,16 +807,26 @@ const docTemplate = `{ ], "summary": "Get Sessions for Provider", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" }, { @@ -767,22 +860,32 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "User address", + "name": "user", + "in": "query", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "User address", - "name": "user", - "in": "query", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -808,22 +911,32 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "User address", + "name": "user", + "in": "query", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "User address", - "name": "user", - "in": "query", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index 238375b7..b2abcc1d 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -254,16 +254,26 @@ ], "summary": "Get models list", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" } ], @@ -354,6 +364,28 @@ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, + { + "type": "string", + "example": "0", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -447,16 +479,26 @@ ], "summary": "Get providers list", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" } ], @@ -510,6 +552,15 @@ "providers" ], "summary": "Deregister Provider", + "parameters": [ + { + "type": "string", + "description": "Provider ID", + "name": "id", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", @@ -533,22 +584,32 @@ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "Provider ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Provider ID", - "name": "id", - "in": "path", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -578,6 +639,28 @@ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, + { + "type": "string", + "example": "0", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -717,16 +800,26 @@ ], "summary": "Get Sessions for Provider", "parameters": [ + { + "type": "integer", + "example": 10, + "name": "limit", + "in": "query" + }, { "type": "string", - "description": "Offset", + "example": "0", "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "Limit", - "name": "limit", + "example": "asc", + "name": "order", "in": "query" }, { @@ -760,22 +853,32 @@ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "User address", + "name": "user", + "in": "query", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "User address", - "name": "user", - "in": "query", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { @@ -801,22 +904,32 @@ "parameters": [ { "type": "string", - "description": "Offset", - "name": "offset", + "description": "User address", + "name": "user", + "in": "query", + "required": true + }, + { + "type": "integer", + "example": 10, + "name": "limit", "in": "query" }, { "type": "string", - "description": "Limit", - "name": "limit", + "example": "0", + "name": "offset", "in": "query" }, { + "enum": [ + "asc", + "desc" + ], "type": "string", - "description": "User address", - "name": "user", - "in": "query", - "required": true + "example": "asc", + "name": "order", + "in": "query" } ], "responses": { diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index 3e3ae7d6..7eace39d 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -752,13 +752,20 @@ paths: get: description: Get models list from blokchain parameters: - - description: Offset + - example: 10 + in: query + name: limit + type: integer + - example: "0" in: query name: offset type: string - - description: Limit + - enum: + - asc + - desc + example: asc in: query - name: limit + name: order type: string produces: - application/json @@ -820,6 +827,21 @@ paths: name: id required: true type: string + - example: 10 + in: query + name: limit + type: integer + - example: "0" + in: query + name: offset + type: string + - enum: + - asc + - desc + example: asc + in: query + name: order + type: string produces: - application/json responses: @@ -880,13 +902,20 @@ paths: get: description: Get providers list from blokchain parameters: - - description: Offset + - example: 10 + in: query + name: limit + type: integer + - example: "0" in: query name: offset type: string - - description: Limit + - enum: + - asc + - desc + example: asc in: query - name: limit + name: order type: string produces: - application/json @@ -920,6 +949,12 @@ paths: - providers /blockchain/providers/{id}: delete: + parameters: + - description: Provider ID + in: path + name: id + required: true + type: string produces: - application/json responses: @@ -934,19 +969,26 @@ paths: get: description: Get bids from blockchain by provider parameters: - - description: Offset - in: query - name: offset - type: string - - description: Limit - in: query - name: limit - type: string - description: Provider ID in: path name: id required: true type: string + - example: 10 + in: query + name: limit + type: integer + - example: "0" + in: query + name: offset + type: string + - enum: + - asc + - desc + example: asc + in: query + name: order + type: string produces: - application/json responses: @@ -966,6 +1008,21 @@ paths: name: id required: true type: string + - example: 10 + in: query + name: limit + type: integer + - example: "0" + in: query + name: offset + type: string + - enum: + - asc + - desc + example: asc + in: query + name: order + type: string produces: - application/json responses: @@ -1093,13 +1150,20 @@ paths: get: description: Get sessions from blockchain by provider parameters: - - description: Offset + - example: 10 + in: query + name: limit + type: integer + - example: "0" in: query name: offset type: string - - description: Limit + - enum: + - asc + - desc + example: asc in: query - name: limit + name: order type: string - description: Provider address in: query @@ -1120,18 +1184,25 @@ paths: get: description: Get sessions from blockchain by user parameters: - - description: Offset + - description: User address in: query - name: offset + name: user + required: true type: string - - description: Limit + - example: 10 in: query name: limit + type: integer + - example: "0" + in: query + name: offset type: string - - description: User address + - enum: + - asc + - desc + example: asc in: query - name: user - required: true + name: order type: string produces: - application/json @@ -1147,18 +1218,25 @@ paths: get: description: Get sessions from blockchain by user parameters: - - description: Offset + - description: User address in: query - name: offset + name: user + required: true type: string - - description: Limit + - example: 10 in: query name: limit + type: integer + - example: "0" + in: query + name: offset type: string - - description: User address + - enum: + - asc + - desc + example: asc in: query - name: user - required: true + name: order type: string produces: - application/json diff --git a/proxy-router/internal/blockchainapi/controller.go b/proxy-router/internal/blockchainapi/controller.go index 29f62e86..e26124b5 100644 --- a/proxy-router/internal/blockchainapi/controller.go +++ b/proxy-router/internal/blockchainapi/controller.go @@ -135,8 +135,7 @@ func (c *BlockchainController) claimProviderBalance(ctx *gin.Context) { // @Description Get providers list from blokchain // @Tags providers // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" +// @Param request query structs.QueryOffsetLimitOrderNoDefault true "Query Params" // @Success 200 {object} structs.ProvidersRes // @Router /blockchain/providers [get] func (c *BlockchainController) getAllProviders(ctx *gin.Context) { @@ -228,9 +227,8 @@ func (c *BlockchainController) sendMOR(ctx *gin.Context) { // @Description Get bids from blockchain by provider // @Tags bids // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param id path string true "Provider ID" +// @Param id path string true "Provider ID" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" // @Success 200 {object} structs.BidsRes // @Router /blockchain/providers/{id}/bids [get] func (c *BlockchainController) getBidsByProvider(ctx *gin.Context) { @@ -266,8 +264,9 @@ func (c *BlockchainController) getBidsByProvider(ctx *gin.Context) { // @Description Get bids from blockchain by provider // @Tags bids // @Produce json -// @Param id path string true "Provider ID" -// @Success 200 {object} structs.BidsRes +// @Param id path string true "Provider ID" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" +// @Success 200 {object} structs.BidsRes // @Router /blockchain/providers/{id}/bids/active [get] func (c *BlockchainController) getActiveBidsByProvider(ctx *gin.Context) { var params structs.PathEthAddrID @@ -302,8 +301,7 @@ func (c *BlockchainController) getActiveBidsByProvider(ctx *gin.Context) { // @Description Get models list from blokchain // @Tags models // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" +// @Param request query structs.QueryOffsetLimitOrderNoDefault true "Query Params" // @Success 200 {object} structs.ModelsRes // @Router /blockchain/models [get] func (c *BlockchainController) getAllModels(ctx *gin.Context) { @@ -339,9 +337,8 @@ func (c *BlockchainController) getAllModels(ctx *gin.Context) { // @Description Get bids from blockchain by model agent // @Tags bids // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param id path string true "ModelAgent ID" +// @Param id path string true "ModelAgent ID" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" // @Success 200 {object} structs.BidsRes // @Router /blockchain/models/{id}/bids [get] func (c *BlockchainController) getBidsByModelAgent(ctx *gin.Context) { @@ -377,8 +374,9 @@ func (c *BlockchainController) getBidsByModelAgent(ctx *gin.Context) { // @Description Get bids from blockchain by model agent // @Tags bids // @Produce json -// @Param id path string true "ModelAgent ID" -// @Success 200 {object} structs.BidsRes +// @Param id path string true "ModelAgent ID" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" +// @Success 200 {object} structs.BidsRes // @Router /blockchain/models/{id}/bids [get] func (c *BlockchainController) getActiveBidsByModel(ctx *gin.Context) { var params structs.PathHex32ID @@ -684,9 +682,8 @@ func (c *BlockchainController) getSession(ctx *gin.Context) { // @Description Get sessions from blockchain by user // @Tags sessions // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param user query string true "User address" +// @Param user query string true "User address" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" // @Success 200 {object} structs.SessionsRes // @Router /blockchain/sessions/user [get] func (c *BlockchainController) getSessionsForUser(ctx *gin.Context) { @@ -722,9 +719,8 @@ func (c *BlockchainController) getSessionsForUser(ctx *gin.Context) { // @Description Get sessions from blockchain by user // @Tags sessions // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param user query string true "User address" +// @Param user query string true "User address" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" // @Success 200 {object} structs.SessionsRes // @Router /blockchain/sessions/user/ids [get] func (c *BlockchainController) getSessionsIdsForUser(ctx *gin.Context) { @@ -760,9 +756,8 @@ func (c *BlockchainController) getSessionsIdsForUser(ctx *gin.Context) { // @Description Get sessions from blockchain by provider // @Tags sessions // @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param provider query string true "Provider address" +// @Param request query structs.QueryOffsetLimitOrder true "Query Params" +// @Param provider query string true "Provider address" // @Success 200 {object} structs.SessionsRes // @Router /blockchain/sessions/provider [get] func (c *BlockchainController) getSessionsForProvider(ctx *gin.Context) { @@ -942,6 +937,7 @@ func (c *BlockchainController) createProvider(ctx *gin.Context) { // @Summary Deregister Provider // @Tags providers // @Produce json +// @Param id path string true "Provider ID" // @Success 200 {object} structs.TxRes // @Router /blockchain/providers/{id} [delete] func (c *BlockchainController) deregisterProvider(ctx *gin.Context) { diff --git a/proxy-router/internal/blockchainapi/structs/req.go b/proxy-router/internal/blockchainapi/structs/req.go index 24d3dc2e..41cabecc 100644 --- a/proxy-router/internal/blockchainapi/structs/req.go +++ b/proxy-router/internal/blockchainapi/structs/req.go @@ -30,15 +30,15 @@ type PathEthAddrID struct { } type QueryOffsetLimitOrder struct { - Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number"` - Limit uint8 `form:"limit,default=10" binding:"omitempty" validate:"number"` - Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc"` + Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number" example:"0"` + Limit uint8 `form:"limit,default=10" binding:"omitempty" validate:"number" example:"10"` + Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc" example:"asc"` } type QueryOffsetLimitOrderNoDefault struct { - Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number"` - Limit uint8 `form:"limit,default=0" binding:"omitempty" validate:"number"` - Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc"` + Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number" example:"0"` + Limit uint8 `form:"limit,default=0" binding:"omitempty" validate:"number" example:"10"` + Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc" example:"asc"` } type QueryPageLimit struct { diff --git a/proxy-router/internal/repositories/registries/pagination.go b/proxy-router/internal/repositories/registries/pagination.go index 147dfae2..023b1156 100644 --- a/proxy-router/internal/repositories/registries/pagination.go +++ b/proxy-router/internal/repositories/registries/pagination.go @@ -13,17 +13,34 @@ const ( ) // function to "reverse" pagination based on the desired ordering -func adjustPagination(order Order, length *big.Int, offset *big.Int, limit uint8) (_offset *big.Int, _limit *big.Int) { - bigLimit := big.NewInt(int64(limit)) - if order == OrderASC { - return offset, bigLimit +func adjustPagination(order Order, length *big.Int, offset *big.Int, limit uint8) (newOffset *big.Int, newLimit *big.Int) { + newOffset, newLimit = new(big.Int), new(big.Int) + + // if offset is larger than the length of the array, + // just return empty array + if offset.Cmp(length) >= 0 { + return newOffset, newLimit } - offsetPlusLimit := new(big.Int).Add(offset, bigLimit) - if offsetPlusLimit.Cmp(length) > 0 { - offsetPlusLimit.Set(length) + + newOffset.Set(offset) + newLimit.SetUint64(uint64(limit)) + + // calculate the remaining elements at the end of the array + remainingAtEnd := new(big.Int).Add(offset, newLimit) + remainingAtEnd.Sub(length, remainingAtEnd) + + if remainingAtEnd.Sign() < 0 { + // means that offset+limit is out of bounds, + // so limit has to be reduced by the amount of overflow + newLimit.Add(newLimit, remainingAtEnd) } - newOffset := new(big.Int).Sub(length, offsetPlusLimit) - return newOffset, bigLimit + + // if the order is DESC, the offset has to be adjusted + if order == OrderDESC { + newOffset.Set(max(remainingAtEnd, big.NewInt(0))) + } + + return newOffset, newLimit } // adjustOrder in-plase reverses the order of the array if the order is DESC @@ -33,3 +50,10 @@ func adjustOrder[T any](order Order, arr []T) { } slices.Reverse(arr) } + +func max(a, b *big.Int) *big.Int { + if a.Cmp(b) > 0 { + return a + } + return b +} diff --git a/proxy-router/internal/repositories/registries/pagination_test.go b/proxy-router/internal/repositories/registries/pagination_test.go new file mode 100644 index 00000000..76c487ad --- /dev/null +++ b/proxy-router/internal/repositories/registries/pagination_test.go @@ -0,0 +1,76 @@ +package registries + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOffsetLimit(t *testing.T) { + type args struct { + order Order + length *big.Int + offset *big.Int + limit uint8 + } + + type want struct { + offset *big.Int + limit *big.Int + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "ASC within bounds", + args: args{order: OrderASC, length: big.NewInt(10), offset: big.NewInt(0), limit: 5}, + want: want{offset: big.NewInt(0), limit: big.NewInt(5)}, + }, + { + name: "ASC offset out of bounds", + args: args{order: OrderASC, length: big.NewInt(10), offset: big.NewInt(15), limit: 5}, + want: want{offset: big.NewInt(0), limit: big.NewInt(0)}, + }, + { + name: "ASC limit out of bounds", + args: args{order: OrderASC, length: big.NewInt(10), offset: big.NewInt(5), limit: 10}, + want: want{offset: big.NewInt(5), limit: big.NewInt(5)}, + }, + { + name: "ASC offset and limit out of bounds", + args: args{order: OrderASC, length: big.NewInt(10), offset: big.NewInt(15), limit: 15}, + want: want{offset: big.NewInt(0), limit: big.NewInt(0)}, + }, + { + name: "DESC within bounds", + args: args{order: OrderDESC, length: big.NewInt(10), offset: big.NewInt(0), limit: 5}, + want: want{offset: big.NewInt(5), limit: big.NewInt(5)}, + }, + { + name: "DESC offset out of bounds", + args: args{order: OrderDESC, length: big.NewInt(10), offset: big.NewInt(15), limit: 5}, + want: want{offset: big.NewInt(0), limit: big.NewInt(0)}, + }, + { + name: "DESC limit out of bounds", + args: args{order: OrderDESC, length: big.NewInt(10), offset: big.NewInt(5), limit: 10}, + want: want{offset: big.NewInt(0), limit: big.NewInt(5)}, + }, + { + name: "DESC offset and limit out of bounds", + args: args{order: OrderDESC, length: big.NewInt(10), offset: big.NewInt(15), limit: 15}, + want: want{offset: big.NewInt(0), limit: big.NewInt(0)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + newOffset, newLimit := adjustPagination(tt.args.order, tt.args.length, tt.args.offset, tt.args.limit) + require.Equalf(t, tt.want.offset.Cmp(newOffset), 0, "expected offset %v, got %v", tt.want.offset, newOffset) + require.Equalf(t, tt.want.limit.Cmp(newLimit), 0, "expected limit %v, got %v", tt.want.limit, newLimit) + }) + } +} diff --git a/proxy-router/internal/repositories/registries/session_router_test.go b/proxy-router/internal/repositories/registries/session_router_test.go index 0a006b87..4c1ffdcf 100644 --- a/proxy-router/internal/repositories/registries/session_router_test.go +++ b/proxy-router/internal/repositories/registries/session_router_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/multicall" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" @@ -18,9 +19,11 @@ func TestGetSessions(t *testing.T) { ethClient, err := ethclient.Dial(ethNodeAddr) require.NoError(t, err) + mc := multicall.NewMulticall3(ethClient) + diamondAddr := common.HexToAddress("0xb8C55cD613af947E73E262F0d3C54b7211Af16CF") - sr := NewSessionRouter(diamondAddr, ethClient, lib.NewTestLogger()) - sessionIDs, err := sr.GetSessionsIDsByProvider(context.Background(), common.HexToAddress("0x1441Bc52156Cf18c12cde6A92aE6BDE8B7f775D4"), big.NewInt(0), 2) + sr := NewSessionRouter(diamondAddr, ethClient, mc, lib.NewTestLogger()) + sessionIDs, err := sr.GetSessionsIDsByProvider(context.Background(), common.HexToAddress("0x1441Bc52156Cf18c12cde6A92aE6BDE8B7f775D4"), big.NewInt(0), 2, OrderASC) require.NoError(t, err) for _, sessionID := range sessionIDs { fmt.Printf("sessionID: %v\n", common.Hash(sessionID).Hex()) From f51efd4a88b189a59ad1607947c15e5650289dfc Mon Sep 17 00:00:00 2001 From: shev Date: Wed, 27 Nov 2024 13:53:10 +0100 Subject: [PATCH 35/41] fix: validation to paging --- proxy-router/docs/docs.go | 16 ++++++++++++++++ proxy-router/docs/swagger.json | 16 ++++++++++++++++ proxy-router/docs/swagger.yaml | 16 ++++++++++++++++ .../internal/blockchainapi/structs/req.go | 8 ++++---- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index e9280a0a..d35a1291 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -262,12 +262,14 @@ const docTemplate = `{ "summary": "Get models list", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -373,12 +375,14 @@ const docTemplate = `{ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -487,12 +491,14 @@ const docTemplate = `{ "summary": "Get providers list", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -597,12 +603,14 @@ const docTemplate = `{ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -648,12 +656,14 @@ const docTemplate = `{ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -808,12 +818,14 @@ const docTemplate = `{ "summary": "Get Sessions for Provider", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -866,12 +878,14 @@ const docTemplate = `{ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -917,12 +931,14 @@ const docTemplate = `{ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index b2abcc1d..9b31b9bc 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -255,12 +255,14 @@ "summary": "Get models list", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -366,12 +368,14 @@ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -480,12 +484,14 @@ "summary": "Get providers list", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -590,12 +596,14 @@ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -641,12 +649,14 @@ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -801,12 +811,14 @@ "summary": "Get Sessions for Provider", "parameters": [ { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -859,12 +871,14 @@ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", @@ -910,12 +924,14 @@ "required": true }, { + "minimum": 0, "type": "integer", "example": 10, "name": "limit", "in": "query" }, { + "minLength": 0, "type": "string", "example": "0", "name": "offset", diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index 7eace39d..9a0c29f6 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -754,10 +754,12 @@ paths: parameters: - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -829,10 +831,12 @@ paths: type: string - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -904,10 +908,12 @@ paths: parameters: - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -976,10 +982,12 @@ paths: type: string - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -1010,10 +1018,12 @@ paths: type: string - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -1152,10 +1162,12 @@ paths: parameters: - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -1191,10 +1203,12 @@ paths: type: string - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: @@ -1225,10 +1239,12 @@ paths: type: string - example: 10 in: query + minimum: 0 name: limit type: integer - example: "0" in: query + minLength: 0 name: offset type: string - enum: diff --git a/proxy-router/internal/blockchainapi/structs/req.go b/proxy-router/internal/blockchainapi/structs/req.go index 41cabecc..8500ebe5 100644 --- a/proxy-router/internal/blockchainapi/structs/req.go +++ b/proxy-router/internal/blockchainapi/structs/req.go @@ -30,14 +30,14 @@ type PathEthAddrID struct { } type QueryOffsetLimitOrder struct { - Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number" example:"0"` - Limit uint8 `form:"limit,default=10" binding:"omitempty" validate:"number" example:"10"` + Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number,gte=0" example:"0"` + Limit uint8 `form:"limit,default=10" binding:"omitempty" validate:"number,gte=1" example:"10"` Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc" example:"asc"` } type QueryOffsetLimitOrderNoDefault struct { - Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number" example:"0"` - Limit uint8 `form:"limit,default=0" binding:"omitempty" validate:"number" example:"10"` + Offset lib.BigInt `form:"offset,default=0" binding:"omitempty" validate:"number,gte=0" example:"0"` + Limit uint8 `form:"limit,default=0" binding:"omitempty" validate:"number,gte=1" example:"10"` Order string `form:"order,default=asc" binding:"omitempty" validate:"oneof=asc desc" example:"asc"` } From eaa30c2f390e54a4c916c93149f6ca9501154b18 Mon Sep 17 00:00:00 2001 From: "bohdan.konorin" Date: Mon, 2 Dec 2024 12:38:31 +0100 Subject: [PATCH 36/41] added paging to some request --- .../src/renderer/src/components/chat/Chat.tsx | 11 ++--- .../src/components/chat/ChatHistory.tsx | 9 +++- .../renderer/src/store/hocs/withChatState.jsx | 38 +-------------- .../src/store/utils/apiCallsHelper.tsx | 46 +++++++++++++++++-- 4 files changed, 57 insertions(+), 47 deletions(-) diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.tsx b/ui-desktop/src/renderer/src/components/chat/Chat.tsx index 7024c393..2ccfceb4 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.tsx +++ b/ui-desktop/src/renderer/src/components/chat/Chat.tsx @@ -146,11 +146,6 @@ const Chat = (props) => { }, []) const toggleDrawer = async () => { - if (!isOpen) { - setIsLoading(true); - await refreshSessions() - setIsLoading(false); - } setIsOpen((prevState) => !prevState) } @@ -574,7 +569,11 @@ const Chat = (props) => { deleteHistory={deleteChatEntry} models={chainData?.models || []} onSelectChat={selectChat} - refreshSessions={refreshSessions} + refreshSessions={async () => { + setIsLoading(true); + await refreshSessions() + setIsLoading(false); + }} onChangeTitle={wrapChangeTitle} onCloseSession={closeSession} /> diff --git a/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx b/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx index b68460a7..0d973df5 100644 --- a/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx +++ b/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx @@ -15,7 +15,7 @@ interface ChatHistoryProps { open: boolean, onCloseSession: (id: string) => void; onSelectChat: (chat: ChatData) => void; - refreshSessions: () => void; + refreshSessions: () => Promise; deleteHistory: (chatId: string) => void onChangeTitle: (data: { id: string, title: string }) => Promise; sessions: any[]; @@ -143,6 +143,12 @@ export const ChatHistory = (props: ChatHistoryProps) => { return result; } + const handleTabSwitch = async (tabName) => { + if(tabName == 'sessions') { + await props.refreshSessions(); + } + } + const filterdModels = props.chatData && search ? props.chatData.filter(m => m.title?.includes(search)) : (props.chatData || []); const groupedItems = getGroupHistory(filterdModels); @@ -151,6 +157,7 @@ export const ChatHistory = (props: ChatHistoryProps) => {
{ static displayName = `withChatState(${WrappedComponent.displayName || WrappedComponent.name})`; - getBidsByModels = async (modelId) => { - try { - const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models/${modelId}/bids` - const response = await fetch(path); - const data = await response.json(); - if (data.error) { - console.error(data.error); - return []; - } - return data.bids; - } - catch (e) { - console.log("Error", e) - return []; - } - } - getProviders = async () => { try { const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/providers` @@ -77,23 +60,6 @@ const withChatState = WrappedComponent => { } } - getBidsRatingByModel = async (modelId) => { - try { - const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models/${modelId}/bids/rated`; - const response = await fetch(path); - const data = await response.json(); - if (data.error) { - console.error(data.error); - return []; - } - return data.bids; - } - catch (e) { - console.log("Error", e) - return []; - } - } - getAllModels = async () => { try { const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models`; @@ -150,7 +116,7 @@ const withChatState = WrappedComponent => { const responses = (await Promise.all( models.map(async m => { const id = m.Id; - const bids = (await this.getBidsByModels(id)) + const bids = (await getBidsByModelId(this.props.config.chain.localProxyRouterUrl, id)) .filter(b => +b.DeletedAt === 0) .map(b => ({ ...b, ProviderData: providersMap[b.Provider.toLowerCase()], Model: m })) .filter(b => b.ProviderData); diff --git a/ui-desktop/src/renderer/src/store/utils/apiCallsHelper.tsx b/ui-desktop/src/renderer/src/store/utils/apiCallsHelper.tsx index f2c71265..13354489 100644 --- a/ui-desktop/src/renderer/src/store/utils/apiCallsHelper.tsx +++ b/ui-desktop/src/renderer/src/store/utils/apiCallsHelper.tsx @@ -5,7 +5,7 @@ export const getSessionsByUser = async (url, user) => { const getSessions = async (user, offset, limit) => { try { - const path = `${url}/blockchain/sessions/user?user=${user}&offset=${offset}&limit=${limit}`; + const path = `${url}/blockchain/sessions/user?user=${user}&offset=${offset}&limit=${limit}&order=desc`; const response = await fetch(path); const data = await response.json(); return data.sessions; @@ -15,9 +15,8 @@ export const getSessionsByUser = async (url, user) => { return []; } } - - const limit = 50; + const limit = 20; let offset = 0; let sessions: any[] = []; let all = false; @@ -36,4 +35,43 @@ export const getSessionsByUser = async (url, user) => { } return sessions; - } \ No newline at end of file +} + +export const getBidsByModelId = async (url, modelId) => { + if(!modelId || !url) { + return; + } + + const getBidsByModels = async (modelId, offset, limit) => { + try { + const path = `${url}/blockchain/models/${modelId}/bids?offset=${offset}&limit=${limit}&order=desc` + const response = await fetch(path); + const data = await response.json(); + return data.bids; + } + catch (e) { + console.log("Error", e) + return []; + } + } + + const limit = 20; + let offset = 0; + let bids: any[] = []; + let all = false; + + while (!all) { + console.log("Getting bids by model id: ", modelId, offset, limit) + const bidsRes = await getBidsByModels(modelId, offset, limit); + bids.push(...bidsRes); + + if(bids.length != limit) { + all = true; + } + else { + offset++; + } + } + + return bids; +} \ No newline at end of file From 862f2ae66de4fb058b4ac75385092f84c799a6a5 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 2 Dec 2024 13:46:22 +0200 Subject: [PATCH 37/41] feat: prodia video generation --- proxy-router/internal/aiengine/ai_engine.go | 10 +- proxy-router/internal/aiengine/factory.go | 2 + proxy-router/internal/aiengine/prodia_v2.go | 108 ++++++++++++++++++ proxy-router/internal/aiengine/remote.go | 1 + .../internal/chatstorage/file_chat_storage.go | 13 ++- .../genericchatstorage/chat_responses.go | 6 + .../genericchatstorage/completion.go | 31 +++++ .../genericchatstorage/interface.go | 12 +- proxy-router/internal/constants.go | 1 + .../internal/proxyapi/proxy_sender.go | 29 ++++- .../src/components/chat/Chat.styles.tsx | 15 ++- .../src/renderer/src/components/chat/Chat.tsx | 81 +++++++------ .../src/components/chat/interfaces.tsx | 2 + 13 files changed, 259 insertions(+), 52 deletions(-) create mode 100644 proxy-router/internal/aiengine/prodia_v2.go diff --git a/proxy-router/internal/aiengine/ai_engine.go b/proxy-router/internal/aiengine/ai_engine.go index c66f595c..886e41c3 100644 --- a/proxy-router/internal/aiengine/ai_engine.go +++ b/proxy-router/internal/aiengine/ai_engine.go @@ -55,7 +55,15 @@ func (a *AiEngine) GetAdapter(ctx context.Context, chatID, modelID, sessionID co } if storeChatContext { - engine = NewHistory(engine, a.storage, chatID, modelID, forwardChatContext, a.log) + var actualModelID common.Hash + if modelID == (common.Hash{}) { + modelID, err := a.service.GetModelIdSession(ctx, sessionID) + if err != nil { + return nil, err + } + actualModelID = modelID + } + engine = NewHistory(engine, a.storage, chatID, actualModelID, forwardChatContext, a.log) } return engine, nil diff --git a/proxy-router/internal/aiengine/factory.go b/proxy-router/internal/aiengine/factory.go index 820f82e3..b9edd31e 100644 --- a/proxy-router/internal/aiengine/factory.go +++ b/proxy-router/internal/aiengine/factory.go @@ -10,6 +10,8 @@ func ApiAdapterFactory(apiType string, modelName string, url string, apikey stri return NewProdiaSDEngine(modelName, url, apikey, log), true case API_TYPE_PRODIA_SDXL: return NewProdiaSDXLEngine(modelName, url, apikey, log), true + case API_TYPE_PRODIA_V2: + return NewProdiaV2Engine(modelName, url, apikey, log), true } return nil, false } diff --git a/proxy-router/internal/aiengine/prodia_v2.go b/proxy-router/internal/aiengine/prodia_v2.go new file mode 100644 index 00000000..9606b63f --- /dev/null +++ b/proxy-router/internal/aiengine/prodia_v2.go @@ -0,0 +1,108 @@ +package aiengine + +import ( + "bytes" + "context" + b64 "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + c "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" + gcs "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/chatstorage/genericchatstorage" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/sashabaranov/go-openai" +) + +const API_TYPE_PRODIA_V2 = "prodia-v2" +const PRODIA_V2_DEFAULT_BASE_URL = "https://inference.prodia.com/v2" + +var ( + ErrCapacity = errors.New("unable to schedule job with current token") + ErrBadResponse = errors.New("bad response") + ErrVideoGenerationRequest = errors.New("video generation error") +) + +type ProdiaV2 struct { + modelName string + apiURL string + apiKey string + + log lib.ILogger +} + +func NewProdiaV2Engine(modelName, apiURL, apiKey string, log lib.ILogger) *ProdiaV2 { + if apiURL == "" { + apiURL = PRODIA_V2_DEFAULT_BASE_URL + } + return &ProdiaV2{ + modelName: modelName, + apiURL: apiURL, + apiKey: apiKey, + log: log, + } +} + +func (s *ProdiaV2) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequest, cb gcs.CompletionCallback) error { + body := map[string]interface{}{ + "type": s.modelName, + "config": map[string]string{ + "prompt": prompt.Messages[len(prompt.Messages)-1].Content, + }, + } + + payload, err := json.Marshal(body) + if err != nil { + err = lib.WrapError(ErrImageGenerationInvalidRequest, err) + s.log.Error(err) + return err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/job", s.apiURL), bytes.NewReader(payload)) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + } + + req.Header.Add(c.HEADER_ACCEPT, c.CONTENT_TYPE_VIDEO_MP4) + req.Header.Add(c.HEADER_CONTENT_TYPE, c.CONTENT_TYPE_JSON) + req.Header.Add(c.HEADER_AUTHORIZATION, fmt.Sprintf("Bearer %s", s.apiKey)) + + res, err := http.DefaultClient.Do(req) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + return err + } + defer res.Body.Close() + + if res.StatusCode == http.StatusTooManyRequests { + return ErrCapacity + } else if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusMultipleChoices { + return lib.WrapError(ErrBadResponse, fmt.Errorf("status code: %d", res.StatusCode)) + } + + response, err := io.ReadAll(res.Body) + if err != nil { + err = lib.WrapError(ErrVideoGenerationRequest, err) + s.log.Error(err) + return err + } + + sEnc := b64.StdEncoding.EncodeToString(response) + + dataPrefix := "data:video/mp4;base64," + chunk := gcs.NewChunkVideo(&gcs.VideoGenerationResult{ + VideoRawContent: dataPrefix + sEnc, + }) + + return cb(ctx, chunk) +} + +func (s *ProdiaV2) ApiType() string { + return API_TYPE_PRODIA_V2 +} + +var _ AIEngineStream = &ProdiaV2{} diff --git a/proxy-router/internal/aiengine/remote.go b/proxy-router/internal/aiengine/remote.go index 86088600..ba958b2a 100644 --- a/proxy-router/internal/aiengine/remote.go +++ b/proxy-router/internal/aiengine/remote.go @@ -15,6 +15,7 @@ type RemoteModel struct { type ProxyService interface { SendPromptV2(ctx context.Context, sessionID common.Hash, prompt *openai.ChatCompletionRequest, cb gcs.CompletionCallback) (interface{}, error) + GetModelIdSession(ctx context.Context, sessionID common.Hash) (common.Hash, error) } func (p *RemoteModel) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequest, cb gcs.CompletionCallback) error { diff --git a/proxy-router/internal/chatstorage/file_chat_storage.go b/proxy-router/internal/chatstorage/file_chat_storage.go index d10c7325..964baa4f 100644 --- a/proxy-router/internal/chatstorage/file_chat_storage.go +++ b/proxy-router/internal/chatstorage/file_chat_storage.go @@ -76,16 +76,19 @@ func (cs *ChatStorage) StorePromptResponseToFile(identifier string, isLocal bool } isImageContent := false + isVideoRawContent := false if len(responses) > 0 { isImageContent = responses[0].Type() == gcs.ChunkTypeImage + isVideoRawContent = responses[0].Type() == gcs.ChunkTypeVideo } newEntry := gcs.ChatMessage{ - Prompt: p, - Response: strings.Join(resps, ""), - PromptAt: promptAt.Unix(), - ResponseAt: responseAt.Unix(), - IsImageContent: isImageContent, + Prompt: p, + Response: strings.Join(resps, ""), + PromptAt: promptAt.Unix(), + ResponseAt: responseAt.Unix(), + IsImageContent: isImageContent, + IsVideoRawContent: isVideoRawContent, } if chatHistory.Messages == nil && len(chatHistory.Messages) == 0 { diff --git a/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go b/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go index 4eb6129d..f016d943 100644 --- a/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go +++ b/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go @@ -139,3 +139,9 @@ type ImageGenerationResult struct { } type ImageGenerationCallback func(completion *ImageGenerationResult) error + +type VideoGenerationResult struct { + VideoRawContent string `json:"videoRawContent"` +} + +type VideoGenerationCallback func(completion *VideoGenerationResult) error diff --git a/proxy-router/internal/chatstorage/genericchatstorage/completion.go b/proxy-router/internal/chatstorage/genericchatstorage/completion.go index eb9fd0fc..b75fe402 100644 --- a/proxy-router/internal/chatstorage/genericchatstorage/completion.go +++ b/proxy-router/internal/chatstorage/genericchatstorage/completion.go @@ -139,6 +139,36 @@ func (c *ChunkImage) Data() interface{} { return c.data } +type ChunkVideo struct { + data *VideoGenerationResult +} + +func NewChunkVideo(data *VideoGenerationResult) *ChunkVideo { + return &ChunkVideo{ + data: data, + } +} + +func (c *ChunkVideo) IsStreaming() bool { + return false +} + +func (c *ChunkVideo) Tokens() int { + return 1 +} + +func (c *ChunkVideo) Type() ChunkType { + return ChunkTypeVideo +} + +func (c *ChunkVideo) String() string { + return c.data.VideoRawContent +} + +func (c *ChunkVideo) Data() interface{} { + return c.data +} + type Chunk interface { IsStreaming() bool Tokens() int @@ -151,3 +181,4 @@ var _ Chunk = &ChunkText{} var _ Chunk = &ChunkImage{} var _ Chunk = &ChunkControl{} var _ Chunk = &ChunkStreaming{} +var _ Chunk = &ChunkVideo{} diff --git a/proxy-router/internal/chatstorage/genericchatstorage/interface.go b/proxy-router/internal/chatstorage/genericchatstorage/interface.go index 690c00f1..bc2cf9d9 100644 --- a/proxy-router/internal/chatstorage/genericchatstorage/interface.go +++ b/proxy-router/internal/chatstorage/genericchatstorage/interface.go @@ -47,12 +47,14 @@ func (h *ChatHistory) AppendChatHistory(req *openai.ChatCompletionRequest) *open } type ChatMessage struct { - Prompt OpenAiCompletionRequest `json:"prompt"` - Response string `json:"response"` - PromptAt int64 `json:"promptAt"` - ResponseAt int64 `json:"responseAt"` - IsImageContent bool `json:"isImageContent"` + Prompt OpenAiCompletionRequest `json:"prompt"` + Response string `json:"response"` + PromptAt int64 `json:"promptAt"` + ResponseAt int64 `json:"responseAt"` + IsImageContent bool `json:"isImageContent"` + IsVideoRawContent bool `json:"isVideoRawContent"` } + type Chat struct { ChatID string `json:"chatId"` ModelID string `json:"modelId"` diff --git a/proxy-router/internal/constants.go b/proxy-router/internal/constants.go index 8efcb1ff..1ee3288e 100644 --- a/proxy-router/internal/constants.go +++ b/proxy-router/internal/constants.go @@ -5,6 +5,7 @@ const ( CONTENT_TYPE_JSON = "application/json" CONTENT_TYPE_EVENT_STREAM = "text/event-stream" + CONTENT_TYPE_VIDEO_MP4 = "video/mp4" CONNECTION_KEEP_ALIVE = "keep-alive" diff --git a/proxy-router/internal/proxyapi/proxy_sender.go b/proxy-router/internal/proxyapi/proxy_sender.go index e02cf385..09071da4 100644 --- a/proxy-router/internal/proxyapi/proxy_sender.go +++ b/proxy-router/internal/proxyapi/proxy_sender.go @@ -285,6 +285,14 @@ func (p *ProxyServiceSender) validateMsgSignature(result any, signature lib.HexS return p.morRPC.VerifySignature(result, signature, providerPubicKey, p.log) } +func (p *ProxyServiceSender) GetModelIdSession(ctx context.Context, sessionID common.Hash) (common.Hash, error) { + session, err := p.sessionRepo.GetSession(ctx, sessionID) + if err != nil { + return common.Hash{}, ErrSessionNotFound + } + return session.ModelID(), nil +} + func (p *ProxyServiceSender) SendPromptV2(ctx context.Context, sessionID common.Hash, prompt *openai.ChatCompletionRequest, cb gcs.CompletionCallback) (interface{}, error) { session, err := p.sessionRepo.GetSession(ctx, sessionID) if err != nil { @@ -378,7 +386,7 @@ func (p *ProxyServiceSender) rpcRequestStreamV2( ) (interface{}, int, int, error) { const ( TIMEOUT_TO_ESTABLISH_CONNECTION = time.Second * 3 - TIMEOUT_TO_RECEIVE_FIRST_RESPONSE = time.Second * 5 + TIMEOUT_TO_RECEIVE_FIRST_RESPONSE = time.Second * 30 MAX_RETRIES = 5 ) @@ -522,12 +530,21 @@ func (p *ProxyServiceSender) rpcRequestStreamV2( } else { var imageGenerationResult gcs.ImageGenerationResult err = json.Unmarshal(aiResponse, &imageGenerationResult) - if err != nil { - return nil, ttftMs, totalTokens, lib.WrapError(ErrInvalidResponse, err) + if err == nil && imageGenerationResult.ImageUrl != "" { + totalTokens += 1 + responses = append(responses, imageGenerationResult) + chunk = gcs.NewChunkImage(&imageGenerationResult) + } else { + var videoGenerationResult gcs.VideoGenerationResult + err = json.Unmarshal(aiResponse, &videoGenerationResult) + if err == nil && videoGenerationResult.VideoRawContent != "" { + totalTokens += 1 + responses = append(responses, videoGenerationResult) + chunk = gcs.NewChunkVideo(&videoGenerationResult) + } else { + return nil, ttftMs, totalTokens, lib.WrapError(ErrInvalidResponse, err) + } } - totalTokens += 1 - responses = append(responses, imageGenerationResult) - chunk = gcs.NewChunkImage(&imageGenerationResult) } if ctx.Err() != nil { diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx b/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx index f50d6216..a1043c3d 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx +++ b/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx @@ -94,7 +94,7 @@ export const Avatar = styled.div` ` export const AvatarHeader = styled.div` - color: ${p => p.theme.colors.morMain} + color: ${p => p.theme.colors.morMain}; font-weight: 900; padding: 0 8px; font-size: 18px; @@ -118,7 +118,7 @@ export const MessageBody = styled.div` ` export const ChatTitleContainer = styled.div` - color: ${p => p.theme.colors.morMain} + color: ${p => p.theme.colors.morMain}; font-weight: 900; padding: 0 8px; font-size: 18px; @@ -221,6 +221,17 @@ export const ImageContainer = styled.img` } ` +export const VideoContainer = styled.div` + cursor: pointer; + padding: 0.25rem; + max-width: 100%; + height: 256px; + + @media (min-height: 700px) { + height: 320px; + } +` + export const SubPriceLabel = styled.span` color: ${p => p.theme.colors.morMain}; ` \ No newline at end of file diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.tsx b/ui-desktop/src/renderer/src/components/chat/Chat.tsx index 7024c393..f0faf34a 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.tsx +++ b/ui-desktop/src/renderer/src/components/chat/Chat.tsx @@ -18,7 +18,8 @@ import { SendBtn, LoadingCover, ImageContainer, - SubPriceLabel + SubPriceLabel, + VideoContainer } from './Chat.styles'; import { BtnAccent } from '../dashboard/BalanceBlock.styles'; import { withRouter } from 'react-router-dom'; @@ -223,7 +224,7 @@ const Chat = (props) => { const aiColor = getColor(aiIcon); messages.push({ id: makeId(16), text: m.prompt.messages[0].content, user: userMessage.user, role: userMessage.role, icon: userMessage.icon, color: userMessage.color }); - messages.push({ id: makeId(16), text: m.response, user: modelName, role: "assistant", icon: aiIcon, color: aiColor, isImageContent: m.isImageContent }); + messages.push({ id: makeId(16), text: m.response, user: modelName, role: "assistant", icon: aiIcon, color: aiColor, isImageContent: m.isImageContent, isVideoRawContent: m.isVideoRawContent }); }); setMessages(messages); } @@ -272,7 +273,7 @@ const Chat = (props) => { return; } - const selectedModel = chainData.models.find((m: any) => m.Id == modelId); + const selectedModel = chainData.isLocal ? chainData.models.find((m: any) => m.Id == modelId) : chainData.models.find((m: any) => m.Id == modelId && m.bids); setSelectedModel(selectedModel); setIsReadonly(false); @@ -421,8 +422,9 @@ const Chat = (props) => { } const imageContent = part.imageUrl; + const videoRawContent = part.videoRawContent; - if (!part?.id && !imageContent) { + if (!part?.id && !imageContent && !videoRawContent) { return; } @@ -432,6 +434,9 @@ const Chat = (props) => { if (imageContent) { result = [...otherMessages, { id: part.job, user: modelName, role: "assistant", text: imageContent, isImageContent: true, ...iconProps }]; } + if (videoRawContent) { + result = [...otherMessages, { id: part.job, user: modelName, role: "assistant", text: videoRawContent, isVideoRawContent: true, ...iconProps }]; + } else { const text = `${message?.text || ''}${part?.choices[0]?.delta?.content || ''}`.replace("<|im_start|>", "").replace("<|im_end|>", ""); result = [...otherMessages, { id: part.id, user: modelName, role: "assistant", text: text, ...iconProps }]; @@ -521,7 +526,7 @@ const Chat = (props) => { setIsReadonly(false); setChat({ id: generateHashId(), createdAt: new Date(), modelId, isLocal }); - const selectedModel = chainData.models.find((m: any) => m.Id == modelId); + const selectedModel = isLocal ? chainData.models.find((m: any) => m.Id == modelId) : chainData.models.find((m: any) => m.Id == modelId && m.bids); setSelectedModel(selectedModel); if (isLocal) { @@ -534,7 +539,7 @@ const Chat = (props) => { const openModelSession = openSessions.find(s => s.ModelAgentId == modelId); if (openModelSession) { - const selectedBid = selectedModel.bids.find(b => b.Id == openModelSession.BidID); + const selectedBid = selectedModel.bids.find(b => b.Id == openModelSession.BidID && b.bids); if (selectedBid) { setSelectedBid(selectedBid); } @@ -721,6 +726,42 @@ const Chat = (props) => { ) } +const renderMessage = (message, onOpenImage) => { + if (message.isImageContent) { + return ({ onOpenImage(message.text)} />}) + } + + if (message.isVideoRawContent) { + return () + } + + return ( + + + ) : ( + + {children} + + ) + } + }} + /> + ) +}; + const Message = ({ message, onOpenImage }) => { return (
@@ -730,33 +771,7 @@ const Message = ({ message, onOpenImage }) => {
{message.user} { - message.isImageContent - ? ({ onOpenImage(message.text)} />}) - : ( - - - ) : ( - - {children} - - ) - } - }} - /> - ) + renderMessage(message, onOpenImage) }
) diff --git a/ui-desktop/src/renderer/src/components/chat/interfaces.tsx b/ui-desktop/src/renderer/src/components/chat/interfaces.tsx index e8d31bb0..df40c786 100644 --- a/ui-desktop/src/renderer/src/components/chat/interfaces.tsx +++ b/ui-desktop/src/renderer/src/components/chat/interfaces.tsx @@ -22,6 +22,7 @@ export interface HistoryMessage { icon: string; color: string; isImageContent?: boolean; + isVideoRawContent?: boolean; } export interface ChatHistoryInterface { @@ -36,6 +37,7 @@ export interface ChatMessage { promptAt: number; responseAt: number; isImageContent?: boolean; + isVideoRawContent?: boolean; } export interface ChatPrompt { From 38a0d170cc0ef8ffd5a0b6fa348002b8d3967aa0 Mon Sep 17 00:00:00 2001 From: loonerin Date: Tue, 26 Nov 2024 16:01:30 -0500 Subject: [PATCH 38/41] cicd: versioning --- .github/actions/gen_tag_name/action.yml | 32 +++++++++++++++++-------- .github/workflows/build.yml | 30 ++++++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.github/actions/gen_tag_name/action.yml b/.github/actions/gen_tag_name/action.yml index a167a8ac..133904b9 100644 --- a/.github/actions/gen_tag_name/action.yml +++ b/.github/actions/gen_tag_name/action.yml @@ -7,15 +7,27 @@ runs: id: tag shell: bash run: | - SHORT_HASH="$(git rev-parse --short=7 HEAD)" - echo $SHORT_HASH - if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then - PREFIX="main-" - elif [[ "${GITHUB_REF}" == "refs/heads/test" ]]; then - PREFIX="test-" + VMAJ=1 + VMIN=0 + VPAT=0 + set +o pipefail + VLAST=$(git describe --tags --abbrev=0 --match='v[1-9]*' 2>/dev/null | cut -c2-) + [ $VLAST ] && IFS=. read -r VMAJ VMIN VPAT <<< $VLAST + if [ "$GITHUB_REF_NAME" = "main" ] + then + VPAT=0 + VMIN=$((VMIN+1)) + VFULL=${VMAJ}.${VMIN} + VTAG=v$VFULL else - PREFIX="dev-" + MB=$(git merge-base refs/remotes/origin/dev refs/remotes/origin/main HEAD) + VPAT=$(git rev-list --count --no-merges ${MB}..HEAD) + VFULL=${VMAJ}.${VMIN}.${VPAT} + RNAME=${GITHUB_REF_NAME##*/} + [ "$GITHUB_EVENT_NAME" = "pull_request" ] && RNAME=pr${GITHUB_REF_NAME%/merge} + VTAG=v${VFULL}-${RNAME} fi - TAG_NAME="${PREFIX}${SHORT_HASH}" - echo $TAG_NAME - echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV \ No newline at end of file + echo "VLAST=$VLAST VMAJ=$VMAJ VMIN=$VMIN VPAT=$VPAT VFULL=$VFULL VTAG=$VTAG" + echo "TAG_NAME=${VTAG}" >> $GITHUB_ENV + echo "VTAG=${VTAG}" >> $GITHUB_ENV + echo "VFULL=${VFULL}" >> $GITHUB_ENV diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7452217b..0f2bdb3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,8 +33,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone - uses: actions/checkout@v4 id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Install dependencies run: | @@ -93,8 +96,11 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Clone - uses: actions/checkout@v4 id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Setup Go uses: actions/setup-go@v5 @@ -162,8 +168,11 @@ jobs: runs-on: macos-13 steps: - name: Clone - uses: actions/checkout@v4 id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Setup Go uses: actions/setup-go@v5 @@ -234,8 +243,11 @@ jobs: runs-on: macos-14 steps: - name: Clone - uses: actions/checkout@v4 id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Setup Go uses: actions/setup-go@v5 @@ -306,8 +318,11 @@ jobs: runs-on: windows-latest steps: - name: Clone - uses: actions/checkout@v4 id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Setup Go uses: actions/setup-go@v5 @@ -392,6 +407,9 @@ jobs: - name: Clone id: checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - name: Generate Tag Name uses: ./.github/actions/gen_tag_name @@ -438,4 +456,4 @@ jobs: }); } } - \ No newline at end of file + From 60279fb359b711301d165f20b806f276a4114565 Mon Sep 17 00:00:00 2001 From: loonerin Date: Mon, 2 Dec 2024 10:44:04 -0500 Subject: [PATCH 39/41] cicd: versioning patch change Patch number always count of commits from main only, not dev. --- .github/actions/gen_tag_name/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/gen_tag_name/action.yml b/.github/actions/gen_tag_name/action.yml index 133904b9..fb97d2ea 100644 --- a/.github/actions/gen_tag_name/action.yml +++ b/.github/actions/gen_tag_name/action.yml @@ -20,7 +20,7 @@ runs: VFULL=${VMAJ}.${VMIN} VTAG=v$VFULL else - MB=$(git merge-base refs/remotes/origin/dev refs/remotes/origin/main HEAD) + MB=$(git merge-base refs/remotes/origin/main HEAD) VPAT=$(git rev-list --count --no-merges ${MB}..HEAD) VFULL=${VMAJ}.${VMIN}.${VPAT} RNAME=${GITHUB_REF_NAME##*/} From ffc1cd225df24c52bb091a0493a61061a2f055c6 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 2 Dec 2024 18:20:27 +0200 Subject: [PATCH 40/41] update docs --- docs/models-config.json.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/models-config.json.md b/docs/models-config.json.md index 0452b7a8..a767c42f 100644 --- a/docs/models-config.json.md +++ b/docs/models-config.json.md @@ -1,7 +1,7 @@ # Example models config file. Local model configurations are stored in this file * `root_key` (required) is the model id * `modelName` (required) is the name of the model -* `apiType` (required) is the type of the model api. Currently supported values are "prodia" and "openai" +* `apiType` (required) is the type of the model api. Currently supported values are "prodia-sd", "prodia-sdxl", "prodia-v2" and "openai" * `apiUrl` (required) is the url of the LLM server or model API * `apiKey` (optional) is the api key for the model * `cononcurrentSlots` (optional) are number of available distinct chats on the llm server and used for capacity policy @@ -37,6 +37,12 @@ "apiUrl": "http://llmserver.domain.io:8080/v1", "concurrentSlots": 8, "capacityPolicy": "simple" + }, + "0xe086adc275c99e32bb10b0aff5e8bfc391aad18cbb184727a75b2569149425c6": { + "modelName": "inference.mochi1.txt2vid.v1", + "apiType": "prodia-v2", + "apiUrl": "https://inference.prodia.com/v2", + "apiKey": "replace-with-your-api-key" } } ``` \ No newline at end of file From 3531a56dcf645e1d091dae2ca90d85fe4e893021 Mon Sep 17 00:00:00 2001 From: shev Date: Mon, 2 Dec 2024 19:03:12 +0100 Subject: [PATCH 41/41] fix: build args, version --- proxy-router/build.sh | 4 ++-- proxy-router/internal/blockchainapi/rating_test.go | 8 ++++++-- proxy-router/internal/rating/rating_factory.go | 13 ++++++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/proxy-router/build.sh b/proxy-router/build.sh index ea7c4235..03a22e3c 100755 --- a/proxy-router/build.sh +++ b/proxy-router/build.sh @@ -12,7 +12,7 @@ go mod tidy go build \ -tags docker \ -ldflags="-s -w \ - -X 'github.com/Lumerin-protocol/Morpheus-Lumerin-Node/proxy-router/internal/config.BuildVersion=$VERSION' \ - -X 'github.com/Lumerin-protocol/Morpheus-Lumerin-Node/proxy-router/internal/config.Commit=$COMMIT' \ + -X 'github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/config.BuildVersion=$VERSION' \ + -X 'github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/config.Commit=$COMMIT' \ " \ -o bin/proxy-router cmd/main.go diff --git a/proxy-router/internal/blockchainapi/rating_test.go b/proxy-router/internal/blockchainapi/rating_test.go index ab57666e..f91ff726 100644 --- a/proxy-router/internal/blockchainapi/rating_test.go +++ b/proxy-router/internal/blockchainapi/rating_test.go @@ -4,8 +4,8 @@ import ( "math/big" "testing" - "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/blockchainapi/scorer" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/rating" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/contracts/bindings/providerregistry" "github.com/stretchr/testify/require" ) @@ -13,7 +13,11 @@ import ( func TestRating(t *testing.T) { bidIds, bids, pmStats, mStats := sampleDataTPS() - scoredBids := RateBids(bidIds, bids, pmStats, []providerregistry.IProviderStorageProvider{}, mStats, scorer.NewScorerMock(), big.NewInt(0), lib.NewTestLogger()) + bs := BlockchainService{ + rating: rating.NewRating(rating.NewScorerMock(), nil, lib.NewTestLogger()), + } + + scoredBids := bs.rateBids(bidIds, bids, pmStats, []providerregistry.IProviderStorageProvider{}, mStats, big.NewInt(0), lib.NewTestLogger()) for i := 1; i < len(scoredBids); i++ { require.GreaterOrEqual(t, scoredBids[i-1].Score, scoredBids[i].Score, "scoredBids not sorted") diff --git a/proxy-router/internal/rating/rating_factory.go b/proxy-router/internal/rating/rating_factory.go index 34ff1ef8..0522eaf8 100644 --- a/proxy-router/internal/rating/rating_factory.go +++ b/proxy-router/internal/rating/rating_factory.go @@ -33,9 +33,16 @@ func NewRatingFromConfig(config json.RawMessage, log lib.ILogger) (*Rating, erro return nil, err } + return NewRating(scorer, cfg.ProviderAllowList, log), nil +} + +func NewRating(scorer Scorer, providerAllowList []common.Address, log lib.ILogger) *Rating { allowList := map[common.Address]struct{}{} - for _, addr := range cfg.ProviderAllowList { - allowList[addr] = struct{}{} + + if providerAllowList != nil { + for _, addr := range providerAllowList { + allowList[addr] = struct{}{} + } } providerAllowListLegacy := os.Getenv("PROVIDER_ALLOW_LIST") @@ -63,7 +70,7 @@ func NewRatingFromConfig(config json.RawMessage, log lib.ILogger) (*Rating, erro return &Rating{ scorer: scorer, providerAllowList: allowList, - }, nil + } } var (