From 7a9a1a8adc5949002523d96d2b1aa53c23dadbcb Mon Sep 17 00:00:00 2001 From: erhant Date: Mon, 18 Nov 2024 17:58:47 +0700 Subject: [PATCH] setup side net --- .env.example | 4 + .github/workflows/build_side_container.yml | 68 ++++++++++++ .github/workflows/build_side_exe.yml | 116 +++++++++++++++++++++ Cargo.lock | 6 +- Makefile | 6 +- compute/src/node.rs | 2 +- compute/src/utils/available_nodes.rs | 22 ++-- compute/src/utils/message.rs | 7 +- p2p/src/lib.rs | 4 +- workflows/src/config.rs | 16 ++- workflows/src/providers/mod.rs | 3 + workflows/src/providers/openrouter.rs | 103 ++++++++++++++++++ 12 files changed, 335 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/build_side_container.yml create mode 100644 .github/workflows/build_side_exe.yml create mode 100644 workflows/src/providers/openrouter.rs diff --git a/.env.example b/.env.example index 475f51c..9e4cfc4 100644 --- a/.env.example +++ b/.env.example @@ -28,6 +28,9 @@ OPENAI_API_KEY= ## Gemini (if used, required) ## GEMINI_API_KEY= +## Open Router (if used, required) ## +OPENROUTER_API_KEY= + ## Ollama (if used, optional) ## # do not change this, it is used by Docker OLLAMA_HOST=http://host.docker.internal @@ -40,3 +43,4 @@ OLLAMA_AUTO_PULL=true ## Additional Services (optional) SERPER_API_KEY= JINA_API_KEY= + diff --git a/.github/workflows/build_side_container.yml b/.github/workflows/build_side_container.yml new file mode 100644 index 0000000..c20e918 --- /dev/null +++ b/.github/workflows/build_side_container.yml @@ -0,0 +1,68 @@ +name: Create Side Image +on: + push: + branches: ["side"] + paths: + # Source files in each member + - "compute/src/**" + - "p2p/src/**" + - "workflows/src/**" + # Cargo in each member + - "compute/Cargo.toml" + - "p2p/Cargo.toml" + - "workflows/Cargo.toml" + # root-level changes + - "Cargo.lock" + - "Cross.toml" + - "Dockerfile" + - "compose.yml" + # workflow itself + - ".github/workflows/build_side_container.yml" + +jobs: + build-and-push: + name: Build and Push + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get Unix Time + id: timestamp + run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT + + - name: Get SHA + id: sha + run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Get Branch Name + id: branch + run: echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT + + - name: Set Image Tag + id: itag + run: echo "itag=${{ steps.branch.outputs.branch }}-${{ steps.sha.outputs.sha }}-${{ steps.timestamp.outputs.timestamp }}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v6 + env: + IMAGE_TAG: ${{ steps.itag.outputs.itag }} + with: + platforms: linux/amd64, linux/arm64, linux/arm, linux/arm64v8 + push: true + tags: | + firstbatch/dkn-compute-node:unstable + firstbatch/dkn-compute-node:${{ env.IMAGE_TAG }} diff --git a/.github/workflows/build_side_exe.yml b/.github/workflows/build_side_exe.yml new file mode 100644 index 0000000..05b3622 --- /dev/null +++ b/.github/workflows/build_side_exe.yml @@ -0,0 +1,116 @@ +name: Build and Publish Compute Side Releases + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + check_release: + if: "contains(github.event.release.tag_name, '-side')" # continue if the tag ends with -side + runs-on: ubuntu-latest + steps: + - name: Echo tag + run: | + echo "tag name: ${{ github.event.release.tag_name }}" + echo "release name: ${{ github.event.release.name }}" + + build: + needs: check_release + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - { + runner: macos-latest, + osname: macOS, + arch: amd64, + target: x86_64-apple-darwin, + command: build, + } + - { + runner: macos-latest, + osname: macOS, + arch: arm64, + target: aarch64-apple-darwin, + command: build, + } + - { + runner: ubuntu-latest, + osname: linux, + arch: amd64, + target: x86_64-unknown-linux-gnu, + command: build, + } + - { + runner: ubuntu-latest, + osname: linux, + arch: arm64, + target: aarch64-unknown-linux-gnu, + command: build, + build_args: --no-default-features, + } + - { + runner: windows-latest, + osname: windows, + arch: amd64, + target: x86_64-pc-windows-msvc, + command: build, + extension: ".exe", + } + # - { runner: windows-latest, osname: windows, arch: arm64, target: aarch64-pc-windows-msvc, command: build, extension: ".exe", toolchain: nightly } + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get the release version from the tag + shell: bash + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: Build binary + uses: houseabsolute/actions-rust-cross@v0 + with: + command: ${{ matrix.command }} + target: ${{ matrix.target }} + args: "--locked --release ${{ matrix.build_args }}" + strip: true + + - name: Prepare Release File + run: | + # move the binary + mv target/${{ matrix.target }}/release/dkn-compute${{ matrix.extension }} ./dkn-compute-binary-${{ matrix.osname }}-${{ matrix.arch }}${{ matrix.extension }} + + - name: Upload Launch Artifacts + uses: actions/upload-artifact@v4 + with: + name: dkn-compute-binary-${{ matrix.osname }}-${{ matrix.arch }} + path: dkn-compute-binary-${{ matrix.osname }}-${{ matrix.arch }}${{ matrix.extension }} + + release: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all tags and history + + - name: Download Launch Artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: ./artifacts + + - name: Create release with artifacts + uses: ncipollo/release-action@v1 + with: + name: ${{ github.event.release.name }} + tag: ${{ github.event.release.tag_name }} + artifacts: "artifacts/*" + artifactContentType: application/octet-stream + allowUpdates: true + # draft: true diff --git a/Cargo.lock b/Cargo.lock index 17b6a5a..0770854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3356,7 +3356,7 @@ dependencies = [ [[package]] name = "ollama-workflows" version = "0.1.0" -source = "git+https://github.com/andthattoo/ollama-workflows#75ead48d237d1f408a82f20eef2cd350f217e592" +source = "git+https://github.com/andthattoo/ollama-workflows#12f622c1532ff167a4e11d8504f35f2f209b9312" dependencies = [ "async-trait", "base64 0.22.1", @@ -4482,9 +4482,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa 1.0.11", "memchr", diff --git a/Makefile b/Makefile index 2091c87..05a13e7 100644 --- a/Makefile +++ b/Makefile @@ -7,15 +7,15 @@ endif ############################################################################### .PHONY: launch # | Run with INFO logs in release mode launch: - RUST_LOG=none,dkn_compute=info,dkn_workflows=info,dkn_p2p=info cargo run --release + RUST_LOG=none,dkn_compute=info,dkn_workflows=info,dkn_p2p=info,libp2p_gossipsub=info cargo run --release .PHONY: run # | Run with INFO logs run: - RUST_LOG=none,dkn_compute=info,dkn_workflows=info,dkn_p2p=info cargo run + RUST_LOG=none,dkn_compute=info,dkn_workflows=info,dkn_p2p=info,libp2p=info cargo run .PHONY: debug # | Run with DEBUG logs with INFO log-level workflows debug: - RUST_LOG=warn,dkn_compute=debug,dkn_workflows=debug,dkn_p2p=debug,ollama_workflows=info cargo run + RUST_LOG=warn,dkn_compute=debug,dkn_workflows=debug,dkn_p2p=debug,ollama_workflows=info,libp2p_gossipsub=info cargo run .PHONY: trace # | Run with TRACE logs trace: diff --git a/compute/src/node.rs b/compute/src/node.rs index a2ff518..a4fda53 100644 --- a/compute/src/node.rs +++ b/compute/src/node.rs @@ -10,7 +10,7 @@ use crate::{ }; /// Number of seconds between refreshing the Admin RPC PeerIDs from Dria server. -const RPC_PEER_ID_REFRESH_INTERVAL_SECS: u64 = 30; +const RPC_PEER_ID_REFRESH_INTERVAL_SECS: u64 = 5; /// **Dria Compute Node** /// diff --git a/compute/src/utils/available_nodes.rs b/compute/src/utils/available_nodes.rs index c3ea0de..be28bef 100644 --- a/compute/src/utils/available_nodes.rs +++ b/compute/src/utils/available_nodes.rs @@ -4,26 +4,26 @@ use eyre::Result; use std::{env, fmt::Debug, str::FromStr}; /// Static bootstrap nodes for the Kademlia DHT bootstrap step. -const STATIC_BOOTSTRAP_NODES: [&str; 4] = [ - "/ip4/44.206.245.139/tcp/4001/p2p/16Uiu2HAm4q3LZU2T9kgjKK4ysy6KZYKLq8KiXQyae4RHdF7uqSt4", - "/ip4/18.234.39.91/tcp/4001/p2p/16Uiu2HAmJqegPzwuGKWzmb5m3RdSUJ7NhEGWB5jNCd3ca9zdQ9dU", - "/ip4/54.242.44.217/tcp/4001/p2p/16Uiu2HAmR2sAoh9F8jT9AZup9y79Mi6NEFVUbwRvahqtWamfabkz", - "/ip4/52.201.242.227/tcp/4001/p2p/16Uiu2HAmFEUCy1s1gjyHfc8jey4Wd9i5bSDnyFDbWTnbrF2J3KFb", +const STATIC_BOOTSTRAP_NODES: [&str; 0] = [ + // "/ip4/44.206.245.139/tcp/4001/p2p/16Uiu2HAm4q3LZU2T9kgjKK4ysy6KZYKLq8KiXQyae4RHdF7uqSt4", + // "/ip4/18.234.39.91/tcp/4001/p2p/16Uiu2HAmJqegPzwuGKWzmb5m3RdSUJ7NhEGWB5jNCd3ca9zdQ9dU", + // "/ip4/54.242.44.217/tcp/4001/p2p/16Uiu2HAmR2sAoh9F8jT9AZup9y79Mi6NEFVUbwRvahqtWamfabkz", + // "/ip4/52.201.242.227/tcp/4001/p2p/16Uiu2HAmFEUCy1s1gjyHfc8jey4Wd9i5bSDnyFDbWTnbrF2J3KFb", ]; /// Static relay nodes for the `P2pCircuit`. -const STATIC_RELAY_NODES: [&str; 4] = [ - "/ip4/34.201.33.141/tcp/4001/p2p/16Uiu2HAkuXiV2CQkC9eJgU6cMnJ9SMARa85FZ6miTkvn5fuHNufa", - "/ip4/18.232.93.227/tcp/4001/p2p/16Uiu2HAmHeGKhWkXTweHJTA97qwP81ww1W2ntGaebeZ25ikDhd4z", - "/ip4/54.157.219.194/tcp/4001/p2p/16Uiu2HAm7A5QVSy5FwrXAJdNNsdfNAcaYahEavyjnFouaEi22dcq", - "/ip4/54.88.171.104/tcp/4001/p2p/16Uiu2HAm5WP1J6bZC3aHxd7XCUumMt9txAystmbZSaMS2omHepXa", +const STATIC_RELAY_NODES: [&str; 0] = [ + // "/ip4/34.201.33.141/tcp/4001/p2p/16Uiu2HAkuXiV2CQkC9eJgU6cMnJ9SMARa85FZ6miTkvn5fuHNufa", + // "/ip4/18.232.93.227/tcp/4001/p2p/16Uiu2HAmHeGKhWkXTweHJTA97qwP81ww1W2ntGaebeZ25ikDhd4z", + // "/ip4/54.157.219.194/tcp/4001/p2p/16Uiu2HAm7A5QVSy5FwrXAJdNNsdfNAcaYahEavyjnFouaEi22dcq", + // "/ip4/54.88.171.104/tcp/4001/p2p/16Uiu2HAm5WP1J6bZC3aHxd7XCUumMt9txAystmbZSaMS2omHepXa", ]; /// Static RPC Peer IDs for the Admin RPC. const STATIC_RPC_PEER_IDS: [&str; 0] = []; /// API URL for refreshing the Admin RPC PeerIDs from Dria server. -const RPC_PEER_ID_REFRESH_API_URL: &str = "https://dkn.dria.co/available-nodes"; +const RPC_PEER_ID_REFRESH_API_URL: &str = "https://dkn.dria.co/sdk/available-nodes"; /// Available nodes within the hybrid P2P network. /// diff --git a/compute/src/utils/message.rs b/compute/src/utils/message.rs index f05f026..88f28a5 100644 --- a/compute/src/utils/message.rs +++ b/compute/src/utils/message.rs @@ -5,6 +5,7 @@ use crate::utils::{ use crate::DRIA_COMPUTE_NODE_VERSION; use base64::{prelude::BASE64_STANDARD, Engine}; use core::fmt; +use dkn_p2p::P2P_IDENTITY_PREFIX; use ecies::PublicKey; use eyre::{Context, Result}; use libsecp256k1::{verify, Message, SecretKey, Signature}; @@ -19,7 +20,10 @@ pub struct DKNMessage { /// /// NOTE: This can be obtained via TopicHash in GossipSub pub(crate) topic: String, - /// The version of the Dria Compute Node + /// Identity protocol string of the Dria Compute Node + #[serde(default)] + pub(crate) identity: String, + /// The full crate version of the Dria Compute Node /// /// NOTE: This can be obtained via Identify protocol version pub(crate) version: String, @@ -46,6 +50,7 @@ impl DKNMessage { payload: BASE64_STANDARD.encode(data), topic: topic.to_string(), version: DRIA_COMPUTE_NODE_VERSION.to_string(), + identity: P2P_IDENTITY_PREFIX.trim_end_matches('/').to_string(), timestamp: get_current_time_nanos(), } } diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 97eacec..80dd839 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -7,10 +7,10 @@ mod client; pub use client::DriaP2PClient; /// Prefix for Kademlia protocol, must start with `/`! -pub(crate) const P2P_KADEMLIA_PREFIX: &str = "/dria/kad/"; +pub const P2P_KADEMLIA_PREFIX: &str = "/dria-sdk/kad/"; /// Prefix for Identity protocol string. -pub(crate) const P2P_IDENTITY_PREFIX: &str = "dria/"; +pub const P2P_IDENTITY_PREFIX: &str = "dria-sdk/"; // re-exports pub use libp2p; diff --git a/workflows/src/config.rs b/workflows/src/config.rs index da528d1..559dc04 100644 --- a/workflows/src/config.rs +++ b/workflows/src/config.rs @@ -1,6 +1,6 @@ use crate::{ apis::{JinaConfig, SerperConfig}, - providers::{GeminiConfig, OllamaConfig, OpenAIConfig}, + providers::{GeminiConfig, OllamaConfig, OpenAIConfig, OpenRouterConfig}, split_csv_line, Model, ModelProvider, }; use eyre::{eyre, Result}; @@ -19,6 +19,7 @@ pub struct DriaWorkflowsConfig { /// Gemini configurations, e.g. API key, in case Gemini is used. /// Otherwise, can be ignored. pub gemini: GeminiConfig, + pub openrouter: OpenRouterConfig, /// Serper configurations, e.g. API key, in case Serper is given in environment. /// Otherwise, can be ignored. pub serper: SerperConfig, @@ -40,6 +41,7 @@ impl DriaWorkflowsConfig { ollama: OllamaConfig::new(), openai: OpenAIConfig::new(), gemini: GeminiConfig::new(), + openrouter: OpenRouterConfig::new(), serper: SerperConfig::new(), jina: JinaConfig::new(), } @@ -230,6 +232,18 @@ impl DriaWorkflowsConfig { ); } + // if OpenRouter is a provider, check that the API key is set + if unique_providers.contains(&ModelProvider::OpenRouter) { + let provider_models = self.get_models_for_provider(ModelProvider::OpenRouter); + good_models.extend( + self.openrouter + .check(provider_models) + .await? + .into_iter() + .map(|m| (ModelProvider::OpenRouter, m)), + ); + } + // update good models if good_models.is_empty() { Err(eyre!("No good models found, please check logs for errors.")) diff --git a/workflows/src/providers/mod.rs b/workflows/src/providers/mod.rs index 46fa08a..809f41d 100644 --- a/workflows/src/providers/mod.rs +++ b/workflows/src/providers/mod.rs @@ -6,3 +6,6 @@ pub use openai::OpenAIConfig; mod gemini; pub use gemini::GeminiConfig; + +mod openrouter; +pub use openrouter::OpenRouterConfig; diff --git a/workflows/src/providers/openrouter.rs b/workflows/src/providers/openrouter.rs new file mode 100644 index 0000000..53214c0 --- /dev/null +++ b/workflows/src/providers/openrouter.rs @@ -0,0 +1,103 @@ +use eyre::{eyre, Context, Result}; +use ollama_workflows::Model; +use reqwest::Client; +use std::env; + +use crate::utils::safe_read_env; + +const ENV_VAR_NAME: &str = "OPENROUTER_API_KEY"; + +/// OpenRouter-specific configurations. +#[derive(Debug, Clone, Default)] +pub struct OpenRouterConfig { + /// API key, if available. + api_key: Option, +} + +impl OpenRouterConfig { + /// Looks at the environment variables for OpenRouter API key. + pub fn new() -> Self { + Self { + api_key: safe_read_env(env::var(ENV_VAR_NAME)), + } + } + + /// Sets the API key for OpenRouter. + pub fn with_api_key(mut self, api_key: String) -> Self { + self.api_key = Some(api_key); + self + } + + /// Checks if the API key exists. + pub async fn check(&self, external_models: Vec) -> Result> { + log::info!("Checking OpenRouter API key"); + + // check API key + let Some(api_key) = &self.api_key else { + return Err(eyre!("OpenRouter API key not found")); + }; + + // make a dummy request with existing models + let mut available_models = Vec::new(); + for requested_model in external_models { + // make a dummy request + if let Err(err) = self.dummy_request(api_key.as_str(), &requested_model).await { + log::warn!( + "Model {} failed dummy request, ignoring it: {}", + requested_model, + err + ); + continue; + } + + available_models.push(requested_model) + } + + Ok(available_models) + } + + /// Makes a dummy request to the OpenAI API to check if the model is available & has credits. + async fn dummy_request(&self, api_key: &str, model: &Model) -> Result<()> { + log::debug!("Making a dummy request with: {}", model); + let client = Client::new(); + let request = client + .post("https://openrouter.ai/api/v1/chat/completions") + .header("Authorization", format!("Bearer {}", api_key)) + .header("Content-Type", "application/json") + .body( + serde_json::json!({ + "model": model.to_string(), + "messages": [ + { + "role": "user", + "content": "What is 2+2?" + } + ] + }) + .to_string(), + ) + .build() + .wrap_err("failed to build request")?; + + let response = client + .execute(request) + .await + .wrap_err("failed to send request")?; + + // ensure response is ok + if !response.status().is_success() { + return Err(eyre!( + "Failed to make OpenRouter chat request:\n{}", + response + .text() + .await + .unwrap_or("Could not get error text as well".to_string()) + )); + } + log::debug!("Dummy request successful for model {}", model); + + Ok(()) + } +} + +// FIXME: add tests