diff --git a/.flake8 b/.flake8 index a8652d6..4ac4516 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,43 @@ [flake8] max-line-length = 100 -exclude = .git,__pycache__,build,dist,*.egg-info -ignore = E203, W503, E731 +max-complexity = 10 +exclude = + .git, + __pycache__, + build, + dist, + *.egg-info, + venv, + .venv +extend-ignore = + # Black conflicts with E203 (whitespace before ':') + E203, + # Line break before binary operator (Black formats this way) + W503, + # Lambda assignments (used in routes) + E731, + # Bugbear errors that might cause recursion + B023, + # Ignore nested function definition + B006, + # Ignore missing docstring in public module/package/class/function/method + D100, + D101, + D104, + D102, + D103, + D105, + D106, + D107, + +per-file-ignores = + # Allow unused imports in __init__.py + __init__.py: F401 + # Allow longer lines in config files + config.py: E501 + +# Limit the plugins to avoid recursion issues +enable-extensions = B + +# Increased recursion depth adjustment for bugbear +max-recursion-depth = 1000 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 347c1e2..2aa40b4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,26 +24,14 @@ jobs: uses: actions/setup-node@v4 with: node-version: '20' - + - name: Install Python linting dependencies run: | python -m pip install --upgrade pip - pip install flake8 black isort mypy + pip install flake8 black isort pip install -r requirements.txt - - name: Install Node.js linting dependencies - working-directory: ./submodules/moragents_dockers/frontend - run: | - npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin - - name: Run Python linters run: | - flake8 . black . --check isort . --check-only - mypy . - - - name: Run JavaScript/TypeScript linters - working-directory: ./submodules/moragents_dockers/frontend - run: | - npx eslint . --ext .js,.jsx,.ts,.tsx \ No newline at end of file diff --git a/.github/workflows/mor-agents-build-linux.yml b/.github/workflows/mor-agents-build-linux.yml index 09d09cb..cdc44ae 100644 --- a/.github/workflows/mor-agents-build-linux.yml +++ b/.github/workflows/mor-agents-build-linux.yml @@ -104,7 +104,7 @@ jobs: local model=\$1 local max_attempts=3 local attempt=1 - + while [ \$attempt -le \$max_attempts ]; do echo -e "\${YELLOW}Pulling Ollama model \$model (Attempt \$attempt)...${NC}" if ollama pull \$model; then @@ -115,7 +115,7 @@ jobs: sleep 5 attempt=\$((attempt + 1)) done - + echo -e "\${RED}Failed to pull \$model after \$max_attempts attempts.${NC}" return 1 } diff --git a/.github/workflows/mor-agents-build-mac-arm.yml b/.github/workflows/mor-agents-build-mac-arm.yml index 0c9ea7f..644e82a 100644 --- a/.github/workflows/mor-agents-build-mac-arm.yml +++ b/.github/workflows/mor-agents-build-mac-arm.yml @@ -10,45 +10,45 @@ on: jobs: build: runs-on: macos-latest - + steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pyinstaller - + - name: Build with PyInstaller run: | pyinstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py - + - name: Move .app to expected location run: | mv dist/MORagents.app build_assets/macOS/ - + - name: Install Packages app run: | wget http://s.sudre.free.fr/files/Packages_1211_dev.dmg hdiutil attach Packages_1211_dev.dmg sudo installer -pkg /Volumes/Packages\ 1.2.11/packages/Packages.pkg -target / hdiutil detach /Volumes/Packages\ 1.2.11 - + - name: Create installer package run: | cd build_assets/macOS /usr/local/bin/packagesbuild --verbose --project MorpheusPackagesSudre.pkgproj - + - name: Upload Installer uses: actions/upload-artifact@v4 with: name: MORagentsSetup-macOS - path: ./build_assets/macOS/MORAgentsInstaller.pkg \ No newline at end of file + path: ./build_assets/macOS/MORAgentsInstaller.pkg diff --git a/.github/workflows/mor-agents-build-mac-intel.yml b/.github/workflows/mor-agents-build-mac-intel.yml index caa9124..a3677a4 100644 --- a/.github/workflows/mor-agents-build-mac-intel.yml +++ b/.github/workflows/mor-agents-build-mac-intel.yml @@ -10,45 +10,45 @@ on: jobs: build: runs-on: macos-latest - + steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pyinstaller - + - name: Build with PyInstaller run: | pyinstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py - + - name: Move .app to expected location run: | mv dist/MORagents.app build_assets/macOS/ - + - name: Install Packages app run: | wget http://s.sudre.free.fr/files/Packages_1211_dev.dmg hdiutil attach Packages_1211_dev.dmg sudo installer -pkg /Volumes/Packages\ 1.2.11/packages/Packages.pkg -target / hdiutil detach /Volumes/Packages\ 1.2.11 - + - name: Create installer package run: | cd build_assets/macOS /usr/local/bin/packagesbuild --verbose --project MorpheusPackagesSudreIntel.pkgproj - + - name: Upload Installer uses: actions/upload-artifact@v4 with: name: MORagentsSetup-macOS - path: ./build_assets/macOS/MORAgentsInstaller.pkg \ No newline at end of file + path: ./build_assets/macOS/MORAgentsInstaller.pkg diff --git a/.github/workflows/mor-agents-build-windows.yml b/.github/workflows/mor-agents-build-windows.yml index ddc0597..cb65dd8 100644 --- a/.github/workflows/mor-agents-build-windows.yml +++ b/.github/workflows/mor-agents-build-windows.yml @@ -10,7 +10,7 @@ on: jobs: build: runs-on: windows-latest - + steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index f04e9c5..3a512a0 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -40,4 +40,4 @@ jobs: - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: 'trivy-results.sarif' \ No newline at end of file + sarif_file: 'trivy-results.sarif' diff --git a/.gitignore b/.gitignore index 127f8ec..f9c8485 100644 --- a/.gitignore +++ b/.gitignore @@ -61,5 +61,4 @@ MORagentsWindowsInstaller.zip MORagents.app ## lint -.mypy_cache/ .pytest_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55672ad..c85ea8a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,28 +20,3 @@ repos: rev: 5.13.2 hooks: - id: isort - -- repo: https://github.com/pycqa/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - additional_dependencies: [ - 'flake8-bugbear', - 'flake8-comprehensions', - 'flake8-docstrings', - ] - -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 - hooks: - - id: mypy - additional_dependencies: [types-all] - -- repo: local - hooks: - - id: eslint - name: eslint - entry: bash -c 'cd submodules/moragents_dockers/frontend && npx eslint' - language: system - types_or: [javascript, jsx, ts, tsx] - require_serial: true \ No newline at end of file diff --git a/AGENTABILITIES.md b/AGENTABILITIES.md index da8a892..ae3c7e2 100644 --- a/AGENTABILITIES.md +++ b/AGENTABILITIES.md @@ -1,16 +1,16 @@ # MORagents -Welcome to the world of Web3 agents! If you're interested in building and using agents locally, this document will guide you through the principles and +Welcome to the world of Web3 agents! If you're interested in building and using agents locally, this document will guide you through the principles and current projects underway. ### Principles: -1. **Agents cannot execute decisions**: Agents should not be given private keys or allowed to make transactions on their own. They can only construct transaction +1. **Agents cannot execute decisions**: Agents should not be given private keys or allowed to make transactions on their own. They can only construct transaction payloads for a user's approval. This is due to the limitations of current LLMs in understanding complex transactions and the risk of [gaslighting](https://arxiv.org/abs/2311.04235). 2. **Local installation**: Agents should run on the user's laptop, typically with 8-16 GB of RAM. This allows for faster execution and better performance. -3. **No private keys**: Agents must not have access to private keys or be able to execute transactions independently. User's cryptographic approval is essential for any +3. **No private keys**: Agents must not have access to private keys or be able to execute transactions independently. User's cryptographic approval is essential for any transaction. ### Current Projects: -1. **lachsbagel on Discord** - [this repo](https://github.com/MorpheusAIs/moragents): +1. **lachsbagel on Discord** - [this repo](https://github.com/MorpheusAIs/moragents): 1. Architecture 2. **IODmitri, SanatSharma, LachsBagel on GitHhub** 1. [HideNSeek](https://github.com/MorpheusAIs/HideNSeek): An algorithm for verifying and fingerprinting which model a compute provider is actually running @@ -25,10 +25,10 @@ transaction. 4. CICD builds for Linux and macOS (apple and intel) 5. Vulnerability scanning of dependencies and code 4. GenLayer - 1. (pending) [FeedBuzz](https://github.com/yeagerai/feedbuzz-contracts) - AI filtered logging system to surface user demand and failure modes for new functionality + 1. (pending) [FeedBuzz](https://github.com/yeagerai/feedbuzz-contracts) - AI filtered logging system to surface user demand and failure modes for new functionality 5. **CliffordAttractor on Discord** - Following Assume 16GB+ RAM: 1. Developed a [price fetcher agent](submodules/moragents_dockers/agents/src/data_agent) using CoinGecko. - 2. A [web interface](submodules/moragents_dockers/frontend) which is served by the local Docker installation and integrated with Rainbow, enabling the use of MetaMask, WalletConnect, and other + 2. A [web interface](submodules/moragents_dockers/frontend) which is served by the local Docker installation and integrated with Rainbow, enabling the use of MetaMask, WalletConnect, and other EVM-based wallets. 3. (NEEDS REFACTORING DUE TO 1INCH CHANGE) [Swap agent](submodules/moragents_dockers/agents/src/swap_agent) which can iteratively ask users to provide needed details for disambiguation. 4. [General-purpose agent](https://github.com/MorpheusAIs/moragents/pull/34) that can ingest arbitrary documents, such as PDFs, for basic document QA and text generation. @@ -57,7 +57,7 @@ Pending Lumerin's work. Eventually Agent Builders will be able to permission-les ### How to Contribute: -- If you are working on an agent which can provide value through open models and relies on processing public data, please reach out to lachsbagel on Discord (link below) +- If you are working on an agent which can provide value through open models and relies on processing public data, please reach out to lachsbagel on Discord (link below) - Otherwise, you are more than welcome to publish your agent to the registry when it goes live pending Lumerin's work and any other necessary pieces which come up to better ensure security and verifiability of models in non-local execution environments. - If you are working on security and/or verifiability of models and the runtime, please reach out to LachsBagel on the Morpheus Discord. - Currently looking at [Hyperbolic.xyz](https://hyperbolic.xyz) and [6079](https://docs.6079.ai/technology/6079-proof-of-inference-protocol). See more ecosystem members [here](https://mor.org/ecosystem). diff --git a/DISCLAIMER.md b/DISCLAIMER.md index 92739bc..82a12da 100644 --- a/DISCLAIMER.md +++ b/DISCLAIMER.md @@ -1,2 +1,2 @@ -This highly experimental chatbot is not intended for making important decisions, and its responses are generated based on incomplete data and algorithms that may evolve +This highly experimental chatbot is not intended for making important decisions, and its responses are generated based on incomplete data and algorithms that may evolve rapidly. By using this chatbot, you acknowledge that you use it at your own discretion and assume all risks associated with its limitations and potential errors. diff --git a/README.md b/README.md index c4bb82f..8f89249 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Fully Extensible! Add your own agents and have them automatically invoked based #### Steps to Install 1. Download Installer - 1. For Mac on Apple Silicon M1/2/3 etc. (arm64) + 1. For Mac on Apple Silicon M1/2/3 etc. (arm64) 1. Download and run MORagents installer [MORagents020-apple.pkg](https://drive.proton.me/urls/20ENWS94AW#Kols2sA9mWLf) > SHA256 e65e11719a24ca9a00545443a35cda3b6d78f756b8e2ba535db00399ef75168f MORagents020-apple.pkg 2. For Mac on Intel (x86_64) @@ -52,11 +52,11 @@ Fully Extensible! Add your own agents and have them automatically invoked based #### Future Usage - Open the "MORagents" app from Mac search bar. - - For easier access: Right-click MORagents icon on dock -> Options -> Keep in Dock + - For easier access: Right-click MORagents icon on dock -> Options -> Keep in Dock #### Troubleshooting - If the app shows connections errors in connecting to agents. Please ensure Docker Desktop is running, then close and reopen **MORagents** from desktop. -- If installation is unsuccessful, run the following in your Terminal and open the MORagents....pkg again +- If installation is unsuccessful, run the following in your Terminal and open the MORagents....pkg again ```shell $ xcode-select --install ``` @@ -74,7 +74,7 @@ Fully Extensible! Add your own agents and have them automatically invoked based 2. If that still doesn't work, try temporarily disabling your antivirus and open the .exe again 4. Click and Run **MORagentsSetup.exe** 1. This will auto-install Docker and Ollama dependencies. Those will ask you for confirmation. -5. Open **MORagents** from Desktop +5. Open **MORagents** from Desktop 1. Wait for Docker engine to start... 2. If you see any errors or if anything hangs for >10min, please try opening the MORagents app again from the Desktop @@ -100,4 +100,3 @@ This will allow you to add custom agents which will be automatically invoked bas #### Build instructions: 1. [macOS](build_assets/macOS/README_MACOS_DEV_BUILD.md) 2. [Windows](build_assets/windows/README_WINDOWS_DEV_BUILD.md) - \ No newline at end of file diff --git a/build_assets/linux/install_moragents.sh b/build_assets/linux/install_moragents.sh index 6aa8e87..316d269 100644 --- a/build_assets/linux/install_moragents.sh +++ b/build_assets/linux/install_moragents.sh @@ -25,4 +25,4 @@ echo -e "${YELLOW}Running MORagents setup...${NC}" echo -e "${GREEN}Installation complete!${NC}" echo "You can now start MORagents from your application menu or by running 'MORagents' in the terminal." -echo -e "${YELLOW}NOTE: Please log out and log back in for Docker group changes to take effect.${NC}" \ No newline at end of file +echo -e "${YELLOW}NOTE: Please log out and log back in for Docker group changes to take effect.${NC}" diff --git a/build_assets/macOS/Packaging_Instructions_macOS.md b/build_assets/macOS/Packaging_Instructions_macOS.md index fe97b24..5cfb715 100644 --- a/build_assets/macOS/Packaging_Instructions_macOS.md +++ b/build_assets/macOS/Packaging_Instructions_macOS.md @@ -15,7 +15,7 @@ NOTE: you will need to use your own codesigning identity if you intend to distri 2. Under the Packages, either edit the existing package or create a new one. Set "Identifier" to com.morpheus.pkg.MORAgents, set the Payload to have MORAgents.app under /Applications, then under Scripts, add the preinstall.sh and postinstall.sh from the downloaded files.\ 3. Create a new Package, Set "Identifier" to com.morpheus.pkg.DockerDesktop, then under Payload, add the Docker Desktop Mac Install under /Applications\ 4. Create a new Package, Set "Identifier" to com.morpheus.pkg.Ollama, then under preinstall scripts, add the preinstall_ollama.sh script from the downloaded files.\ - 5. Navigate to Project, then in Presentation click the topright dropdown and select Introduction choose the welcome.html file, add a License section and choose license.html. + 5. Navigate to Project, then in Presentation click the topright dropdown and select Introduction choose the welcome.html file, add a License section and choose license.html. 6. In the upmost toolbar click Build -> Build to generate a .pkg file in the directory you saved the MORagents Packages package --- @@ -27,17 +27,17 @@ Future usage only requires you to run MORagents from your searchbar. --- ## Signing -```sh +```sh productsign --sign "Developer ID Installer: Liquid Tensor LLC (ZQN244GMTD)" MORagents.pkg MORagents010-[apple\|intel].pkg ``` -## Notarize -```sh +## Notarize +```sh xcrun notarytool submit MORagents020-[apple\|intel].pkg --keychain-profile "NotaryProfile" --wait ``` ## Staple -```sh +```sh xcrun stapler staple MORagents020-[apple\|intel].pkg ``` @@ -47,7 +47,7 @@ xcrun stapler staple MORagents020-[apple\|intel].pkg ### Verify notarization ```sh -xcrun notarytool info "" --keychain-profile "NotaryProfile" +xcrun notarytool info "" --keychain-profile "NotaryProfile" ``` ### Verify codesign @@ -55,4 +55,4 @@ xcrun notarytool info "" --keychain-profile "NotaryProfile" codesign --verify --verbose=4 MORagents.app codesign -dv --verbose=4 MORagents.app -``` \ No newline at end of file +``` diff --git a/build_assets/macOS/README_MACOS_DEV_BUILD.md b/build_assets/macOS/README_MACOS_DEV_BUILD.md index 223cea9..29d355c 100644 --- a/build_assets/macOS/README_MACOS_DEV_BUILD.md +++ b/build_assets/macOS/README_MACOS_DEV_BUILD.md @@ -16,7 +16,7 @@ You may instead simply download the [pre-built app](../../README.md) 3. Build Docker Image for Local Agent Execution ```shell -For ARM (M1, M2, M3) +For ARM (M1, M2, M3) $ docker-compose -f submodules/moragents_dockers/docker-compose-apple.yml up For Intel (x86_64) @@ -38,7 +38,7 @@ For Intel (x86_64) # If you have issues, try python -m PyInstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py -6. Install Application +6. Install Application ```shell $ cp dist/MORagents.app /Applications ``` diff --git a/build_assets/macOS/license.html b/build_assets/macOS/license.html index 2d8999a..f50d127 100644 --- a/build_assets/macOS/license.html +++ b/build_assets/macOS/license.html @@ -53,4 +53,4 @@

License Agreement

By clicking Continue, installing, and/or using the MORagents software you accept the terms of the License agreement.

- \ No newline at end of file + diff --git a/build_assets/macOS/postinstall.sh b/build_assets/macOS/postinstall.sh index e6c4e53..68cced5 100644 --- a/build_assets/macOS/postinstall.sh +++ b/build_assets/macOS/postinstall.sh @@ -55,12 +55,12 @@ start_time=$(date +%s) while ! is_app_running "Docker Desktop"; do current_time=$(date +%s) elapsed_time=$((current_time - start_time)) - + if [ $elapsed_time -ge $timeout ]; then log_message "Warning: Docker Desktop did not start within the specified timeout." break fi - + sleep 5 done @@ -82,4 +82,4 @@ else fi log_message "Post-install script completed." -exit 0 \ No newline at end of file +exit 0 diff --git a/build_assets/macOS/postinstall_intel.sh b/build_assets/macOS/postinstall_intel.sh index 0dab78e..c78a2e2 100644 --- a/build_assets/macOS/postinstall_intel.sh +++ b/build_assets/macOS/postinstall_intel.sh @@ -61,12 +61,12 @@ start_time=$(date +%s) while ! is_app_running "Docker Desktop"; do current_time=$(date +%s) elapsed_time=$((current_time - start_time)) - + if [ $elapsed_time -ge $timeout ]; then log_message "Warning: Docker Desktop did not start within the specified timeout." break fi - + sleep 5 done @@ -88,4 +88,4 @@ else fi log_message "Post-install script completed." -exit 0 \ No newline at end of file +exit 0 diff --git a/build_assets/macOS/preinstall_docker.sh b/build_assets/macOS/preinstall_docker.sh index f110bbd..57b541a 100644 --- a/build_assets/macOS/preinstall_docker.sh +++ b/build_assets/macOS/preinstall_docker.sh @@ -52,4 +52,4 @@ log_message "Cleaning up..." rm -f "$DOCKER_DMG" log_message "Docker preinstall script completed successfully." -exit 0 \ No newline at end of file +exit 0 diff --git a/build_assets/macOS/preinstall_docker_intel.sh b/build_assets/macOS/preinstall_docker_intel.sh index 544f0c8..1ef67c5 100644 --- a/build_assets/macOS/preinstall_docker_intel.sh +++ b/build_assets/macOS/preinstall_docker_intel.sh @@ -58,4 +58,4 @@ log_message "Cleaning up..." rm -f "$DOCKER_DMG" log_message "Docker preinstall script completed successfully." -exit 0 \ No newline at end of file +exit 0 diff --git a/config.py b/config.py index 1738205..907d0c2 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,6 @@ import os import sys + from utils.host_utils import get_os_and_arch os_name, arch = get_os_and_arch() @@ -8,28 +9,29 @@ repo_root = os.path.dirname(__file__) elif os_name == "Windows": # Run as bundled executable if condition is met, else run as regular Python script - repo_root = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(__file__) + repo_root = sys._MEIPASS if getattr(sys, "frozen", False) else os.path.dirname(__file__) elif os_name == "Linux": repo_root = os.path.dirname(__file__) else: raise RuntimeError(f"Unsupported OS: {os_name}") + class AgentDockerConfig: MACOS_APPLE_IMAGE_NAMES = [ "lachsbagel/moragents_dockers-nginx:apple-0.2.0", - "lachsbagel/moragents_dockers-agents:apple-0.2.0" + "lachsbagel/moragents_dockers-agents:apple-0.2.0", ] MACOS_INTEL_IMAGE_NAMES = [ "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", - "lachsbagel/moragents_dockers-agents:amd64-0.2.0" + "lachsbagel/moragents_dockers-agents:amd64-0.2.0", ] WINDOWS_IMAGE_NAMES = [ "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", - "lachsbagel/moragents_dockers-agents:amd64-0.2.0" + "lachsbagel/moragents_dockers-agents:amd64-0.2.0", ] LINUX_IMAGE_NAMES = [ # TODO, may need linux specific tagged images "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", - "lachsbagel/moragents_dockers-agents:amd64-0.2.0" + "lachsbagel/moragents_dockers-agents:amd64-0.2.0", ] @staticmethod @@ -45,6 +47,7 @@ def get_current_image_names(): else: raise RuntimeError(f"Unsupported OS: {os_name}") + class AgentDockerConfigDeprecate: OLD_IMAGE_NAMES = [ "morpheus/price_fetcher_agent:latest", @@ -57,5 +60,5 @@ class AgentDockerConfigDeprecate: "lachsbagel/moragents_dockers-nginx:apple-0.1.0", "lachsbagel/moragents_dockers-agents:apple-0.1.0", "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-agents:amd64-0.1.0", ] diff --git a/main.py b/main.py index e69c7fb..148f39e 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,12 @@ -import time import logging +import time import webbrowser +from runtime_setup_linux import main as linux_setup from runtime_setup_macos import main as macos_setup from runtime_setup_windows import main as windows_setup -from runtime_setup_linux import main as linux_setup -from utils.logger_config import setup_logger from utils.host_utils import get_os_and_arch +from utils.logger_config import setup_logger # Configure logging logger = setup_logger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 079f7c9..8d47df5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ exclude = ''' /( \.git | \.hg - | \.mypy_cache | \.tox | \.venv | _build @@ -13,6 +12,8 @@ exclude = ''' | build | dist | \.eggs + | venv + | .venv )/ ''' @@ -23,13 +24,3 @@ include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true line_length = 100 - -[tool.mypy] -python_version = "3.12" -disallow_untyped_defs = true -disallow_any_unimported = true -no_implicit_optional = true -check_untyped_defs = true -warn_return_any = true -warn_unused_ignores = true -show_error_codes = true \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 417a243..e269091 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,9 +13,8 @@ pre-commit==3.5.0 flake8==7.0.0 black==24.2.0 isort==5.13.2 -mypy==1.8.0 types-requests==2.31.0.20240311 types-setuptools==69.2.0.20240316 flake8-bugbear==24.2.6 flake8-comprehensions==3.14.0 -flake8-docstrings==1.7.0 \ No newline at end of file +flake8-docstrings==1.7.0 diff --git a/runtime_setup_linux.py b/runtime_setup_linux.py index 3439dd4..e1484f6 100644 --- a/runtime_setup_linux.py +++ b/runtime_setup_linux.py @@ -2,16 +2,17 @@ import shutil import subprocess -from utils.logger_config import setup_logger from config import AgentDockerConfig, AgentDockerConfigDeprecate +from utils.logger_config import setup_logger logger = setup_logger(__name__) + def get_docker_path(): docker_paths = [ - '/usr/bin/docker', # Common Linux path - '/usr/local/bin/docker', # Alternative Linux path - shutil.which('docker') + "/usr/bin/docker", # Common Linux path + "/usr/local/bin/docker", # Alternative Linux path + shutil.which("docker"), ] for docker_path in docker_paths: if docker_path and os.path.exists(docker_path): @@ -20,16 +21,22 @@ def get_docker_path(): logger.error("Docker executable not found in PATH.") return None + def check_docker_installed(docker_path): try: - subprocess.run([docker_path, "--version"], - check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) logger.info(f"Docker was found at {docker_path}") return True except (subprocess.CalledProcessError, TypeError) as e: logger.error(f"Error checking Docker installation: {str(e)}") return False + def delete_docker_image(docker_path, image_name): try: # List all images @@ -49,37 +56,51 @@ def delete_docker_image(docker_path, image_name): except subprocess.CalledProcessError as e: logger.warning(f"Error deleting image: {e}") + def list_containers_for_image(docker_path, image_name): try: output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"]) + [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] + ) containers = output.decode().strip().split("\n") return [container for container in containers if container] except subprocess.CalledProcessError as e: logger.error(f"Failed to list containers for image '{image_name}': {e}") return [] + def remove_container(docker_path, container): try: - subprocess.run([docker_path, "rm", "-f", container], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "rm", "-f", container], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) except subprocess.CalledProcessError as e: logger.error(f"Failed to remove container '{container}': {e}") + def docker_image_present_on_host(docker_path, image_name): try: - subprocess.run([docker_path, "inspect", image_name], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "inspect", image_name], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) return True except (subprocess.CalledProcessError, TypeError) as e: return False + def remove_containers_for_image(docker_path, image_name): containers = list_containers_for_image(docker_path, image_name) for container in containers: remove_container(docker_path, container) logger.info(f"Removed container '{container}' for image '{image_name}'") + def remove_containers_by_name(docker_path, container_name): try: list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] @@ -95,12 +116,14 @@ def remove_containers_by_name(docker_path, container_name): except subprocess.CalledProcessError as e: logger.error(f"Error removing container '{container_name}': {str(e)}") + def migration_remove_old_images(docker_path): for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: if docker_image_present_on_host(docker_path, image_name): delete_docker_image(docker_path, image_name) logger.info(f"Deleted image '{image_name} from previous release") + def pull_docker_images(docker_path): for image in AgentDockerConfig.get_current_image_names(): try: @@ -110,18 +133,22 @@ def pull_docker_images(docker_path): logger.error(f"Failed to pull image {image}: {e}") raise + def start_ollama_server(): - ollama_path = '/usr/local/bin/ollama' # This path might need to be adjusted for Linux + ollama_path = "/usr/local/bin/ollama" # This path might need to be adjusted for Linux try: # Start Ollama server logger.info("Starting Ollama server...") - subprocess.Popen([ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.Popen( + [ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) logger.info("Ollama server started successfully.") except Exception as e: logger.info("Failed to start Ollama server.") logger.error(f"Failed to start Ollama server: {e}") + def docker_setup(): docker_path = get_docker_path() logger.info(f"Docker path: {docker_path}") @@ -144,18 +171,41 @@ def docker_setup(): pull_docker_images(docker_path) # Spin up Agent container - subprocess.run([ - docker_path, "run", "-d", "--name", "agents", - "-p", "8080:5000", "--restart", "always", - "-v", "/var/lib/agents:/var/lib/agents", "-v", "/app/src:/app/src", # Adjusted volume paths for Linux - AgentDockerConfig.get_current_image_names()[1] # agents image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "agents", + "-p", + "8080:5000", + "--restart", + "always", + "-v", + "/var/lib/agents:/var/lib/agents", + "-v", + "/app/src:/app/src", # Adjusted volume paths for Linux + AgentDockerConfig.get_current_image_names()[1], # agents image + ], + check=True, + ) # Spin up Nginx container - subprocess.run([ - docker_path, "run", "-d", "--name", "nginx", "-p", "3333:80", - AgentDockerConfig.get_current_image_names()[0] # nginx image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "nginx", + "-p", + "3333:80", + AgentDockerConfig.get_current_image_names()[0], # nginx image + ], + check=True, + ) + def main(): # main() called every time the app is opened (from main.py). Put all app open code here. @@ -163,5 +213,6 @@ def main(): start_ollama_server() docker_setup() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/runtime_setup_macos.py b/runtime_setup_macos.py index fda8f2e..26cfc1f 100644 --- a/runtime_setup_macos.py +++ b/runtime_setup_macos.py @@ -2,13 +2,17 @@ import shutil import subprocess -from utils.logger_config import setup_logger from config import AgentDockerConfig, AgentDockerConfigDeprecate +from utils.logger_config import setup_logger logger = setup_logger(__name__) + def get_docker_path(): - docker_paths = ['/Applications/Docker.app/Contents/Resources/bin/docker', shutil.which('docker')] + docker_paths = [ + "/Applications/Docker.app/Contents/Resources/bin/docker", + shutil.which("docker"), + ] for docker_path in docker_paths: if os.path.exists(docker_path): return docker_path @@ -19,14 +23,19 @@ def get_docker_path(): def check_docker_installed(docker_path): try: - subprocess.run([docker_path, "--version"], - check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) logger.info(f"Docker was found at {docker_path}") return True except (subprocess.CalledProcessError, TypeError) as e: logger.error(f"Error checking Docker installation: {str(e)}") return False + def delete_docker_image(docker_path, image_name): try: # List all images @@ -46,37 +55,51 @@ def delete_docker_image(docker_path, image_name): except subprocess.CalledProcessError as e: logger.warning(f"Error deleting image: {e}") + def list_containers_for_image(docker_path, image_name): try: output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"]) + [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] + ) containers = output.decode().strip().split("\n") return [container for container in containers if container] except subprocess.CalledProcessError as e: logger.error(f"Failed to list containers for image '{image_name}': {e}") return [] + def remove_container(docker_path, container): try: - subprocess.run([docker_path, "rm", "-f", container], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "rm", "-f", container], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) except subprocess.CalledProcessError as e: logger.error(f"Failed to remove container '{container}': {e}") + def docker_image_present_on_host(docker_path, image_name): try: - subprocess.run([docker_path, "inspect", image_name], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "inspect", image_name], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) return True except (subprocess.CalledProcessError, TypeError) as e: return False + def remove_containers_for_image(docker_path, image_name): containers = list_containers_for_image(docker_path, image_name) for container in containers: remove_container(docker_path, container) logger.info(f"Removed container '{container}' for image '{image_name}'") + def remove_containers_by_name(docker_path, container_name): try: list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] @@ -92,12 +115,14 @@ def remove_containers_by_name(docker_path, container_name): except subprocess.CalledProcessError as e: logger.error(f"Error removing container '{container_name}': {str(e)}") + def migration_remove_old_images(docker_path): for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: if docker_image_present_on_host(docker_path, image_name): delete_docker_image(docker_path, image_name) logger.info(f"Deleted image '{image_name} from previous release") + def pull_docker_images(docker_path): for image in AgentDockerConfig.get_current_image_names(): try: @@ -107,18 +132,22 @@ def pull_docker_images(docker_path): logger.error(f"Failed to pull image {image}: {e}") raise + def start_ollama_server(): - ollama_path = '/usr/local/bin/ollama' + ollama_path = "/usr/local/bin/ollama" try: # Start Ollama server logger.info("Starting Ollama server...") - subprocess.Popen([ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.Popen( + [ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) logger.info("Ollama server started successfully.") except Exception as e: logger.info("Failed to start Ollama server.") logger.error(f"Failed to start Ollama server: {e}") + def docker_setup(): docker_path = get_docker_path() logger.info(f"Docker path: {docker_path}") @@ -141,18 +170,41 @@ def docker_setup(): pull_docker_images(docker_path) # Spin up Agent container - subprocess.run([ - docker_path, "run", "-d", "--name", "agents", - "-p", "8080:5000", "--restart", "always", - "-v", "/var/lib/agents", "-v", "/app/src", - AgentDockerConfig.get_current_image_names()[1] # agents image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "agents", + "-p", + "8080:5000", + "--restart", + "always", + "-v", + "/var/lib/agents", + "-v", + "/app/src", + AgentDockerConfig.get_current_image_names()[1], # agents image + ], + check=True, + ) # Spin up Nginx container - subprocess.run([ - docker_path, "run", "-d", "--name", "nginx", "-p", "3333:80", - AgentDockerConfig.get_current_image_names()[0] # nginx image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "nginx", + "-p", + "3333:80", + AgentDockerConfig.get_current_image_names()[0], # nginx image + ], + check=True, + ) + def main(): # main() called every time the app is opened (from main.py). Put all app open code here. @@ -160,5 +212,6 @@ def main(): start_ollama_server() docker_setup() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/runtime_setup_windows.py b/runtime_setup_windows.py index 33de5d9..43024fc 100644 --- a/runtime_setup_windows.py +++ b/runtime_setup_windows.py @@ -1,20 +1,24 @@ import subprocess import time -from utils.logger_config import setup_logger from config import AgentDockerConfig, AgentDockerConfigDeprecate +from utils.logger_config import setup_logger logger = setup_logger(__name__) docker_path = "docker" + def check_docker_installed(): try: - subprocess.run([docker_path, "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run( + [docker_path, "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) return True except (subprocess.CalledProcessError, FileNotFoundError): return False + def start_docker(): try: subprocess.run(["C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"]) @@ -33,6 +37,7 @@ def start_docker(): logger.info("Waiting for Docker engine to start...") time.sleep(2) + def delete_docker_image(image_name): try: list_command = [docker_path, "images", "--format", "{{.Repository}}:{{.Tag}}"] @@ -46,37 +51,51 @@ def delete_docker_image(image_name): except subprocess.CalledProcessError as e: logger.warning(f"Error deleting image: {e}") + def list_containers_for_image(image_name): try: output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"]) + [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] + ) containers = output.decode().strip().split("\n") return [container for container in containers if container] except subprocess.CalledProcessError as e: logger.error(f"Failed to list containers for image '{image_name}': {e}") return [] + def remove_container(container): try: - subprocess.run([docker_path, "rm", "-f", container], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "rm", "-f", container], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) except subprocess.CalledProcessError as e: logger.error(f"Failed to remove container '{container}': {e}") + def docker_image_present_on_host(image_name): try: - subprocess.run([docker_path, "inspect", image_name], check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [docker_path, "inspect", image_name], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) return True except (subprocess.CalledProcessError, TypeError): return False + def remove_containers_for_image(image_name): containers = list_containers_for_image(image_name) for container in containers: remove_container(container) logger.info(f"Removed container '{container}' for image '{image_name}'") + def remove_containers_by_name(container_name): try: list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] @@ -92,12 +111,14 @@ def remove_containers_by_name(container_name): except subprocess.CalledProcessError as e: logger.error(f"Error removing container '{container_name}': {str(e)}") + def migration_remove_old_images(): for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: if docker_image_present_on_host(image_name): delete_docker_image(image_name) logger.info(f"Deleted image '{image_name}' from previous release") + def pull_docker_images(): for image_name in AgentDockerConfig.get_current_image_names(): try: @@ -107,15 +128,18 @@ def pull_docker_images(): logger.error(f"Failed to pull image {image_name}: {e}") raise + def start_ollama_server(): ollama_path = "ollama" try: print(f"Attempting to start Ollama server using: {ollama_path}") - subprocess.Popen([ollama_path, "serve"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - creationflags=subprocess.CREATE_NO_WINDOW) + subprocess.Popen( + [ollama_path, "serve"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW, + ) print("Ollama server started successfully.") except Exception as e: print(f"Failed to start Ollama server: {e}") @@ -140,23 +164,47 @@ def docker_setup(): remove_containers_by_name("nginx") # Spin up Agent container - subprocess.run([ - docker_path, "run", "-d", "--name", "agents", - "-p", "8080:5000", "--restart", "always", - "-v", "/var/lib/agents", "-v", "/app/src", - AgentDockerConfig.get_current_image_names()[1] # agents image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "agents", + "-p", + "8080:5000", + "--restart", + "always", + "-v", + "/var/lib/agents", + "-v", + "/app/src", + AgentDockerConfig.get_current_image_names()[1], # agents image + ], + check=True, + ) # Spin up Nginx container - subprocess.run([ - docker_path, "run", "-d", "--name", "nginx", "-p", "3333:80", - AgentDockerConfig.get_current_image_names()[0] # nginx image - ], check=True) + subprocess.run( + [ + docker_path, + "run", + "-d", + "--name", + "nginx", + "-p", + "3333:80", + AgentDockerConfig.get_current_image_names()[0], # nginx image + ], + check=True, + ) + def main(): # main() called every time the app is opened (from main.py). Put all app open code here. start_ollama_server() docker_setup() + if __name__ == "__main__": - docker_setup() \ No newline at end of file + docker_setup() diff --git a/submodules/moragents_dockers/LICENSE.md b/submodules/moragents_dockers/LICENSE.md index 5885ae8..149c400 100644 --- a/submodules/moragents_dockers/LICENSE.md +++ b/submodules/moragents_dockers/LICENSE.md @@ -1,5 +1,5 @@ -This repository is provided under the following MIT License subject to -it's continued status as a contribution to the Morpheus project with +This repository is provided under the following MIT License subject to +it's continued status as a contribution to the Morpheus project with weights as accepted in the initial contribution pull request. MIT License diff --git a/submodules/moragents_dockers/agents/Dockerfile b/submodules/moragents_dockers/agents/Dockerfile index 14b0556..ec3801e 100644 --- a/submodules/moragents_dockers/agents/Dockerfile +++ b/submodules/moragents_dockers/agents/Dockerfile @@ -19,4 +19,4 @@ COPY . . EXPOSE 5000 # Run the application using uvicorn with auto-reload enabled -CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "5000", "--reload"] \ No newline at end of file +CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "5000", "--reload"] diff --git a/submodules/moragents_dockers/agents/Dockerfile-apple b/submodules/moragents_dockers/agents/Dockerfile-apple index 2e28f3b..f039d6a 100644 --- a/submodules/moragents_dockers/agents/Dockerfile-apple +++ b/submodules/moragents_dockers/agents/Dockerfile-apple @@ -19,4 +19,4 @@ COPY . . EXPOSE 5000 # Run the application using uvicorn with auto-reload enabled -CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "5000", "--reload"] \ No newline at end of file +CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "5000", "--reload"] diff --git a/submodules/moragents_dockers/agents/pytest.ini b/submodules/moragents_dockers/agents/pytest.ini index 72a01cc..a7f7118 100644 --- a/submodules/moragents_dockers/agents/pytest.ini +++ b/submodules/moragents_dockers/agents/pytest.ini @@ -1,3 +1,3 @@ [pytest] pythonpath = . -addopts = --import-mode=importlib -p no:warnings \ No newline at end of file +addopts = --import-mode=importlib -p no:warnings diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md b/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md index 65c447b..ca5c34d 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md @@ -56,7 +56,7 @@ A notebook has been provided to run test queries against the API: ### Without Docker -To use the API run +To use the API run ```python agent.py``` To use an interactive CLI prompt to test the agent run: @@ -84,4 +84,4 @@ This project uses the openhermes-2.5-mistral-7b GGUF model and performs well on The CoinGecko search API is used to find the asset that is being referenced. In case multiple matching assets are found, the agent will select the one with the largest market cap -When consuming this API as part of a larger agent, care should be taken to ensure that responses do not pass thorugh an LLM that hallucinates the number before the response is sent to the user. \ No newline at end of file +When consuming this API as part of a larger agent, care should be taken to ensure that responses do not pass thorugh an LLM that hallucinates the number before the response is sent to the user. diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py index 4240e55..ac577d0 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py @@ -53,9 +53,7 @@ def get_response(self, message): ) elif func_name == "get_tvl": return ( - tools.get_protocol_total_value_locked_tool( - args["protocol_name"] - ), + tools.get_protocol_total_value_locked_tool(args["protocol_name"]), "assistant", ) elif func_name == "get_market_cap": diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py index 63db777..c189a55 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py @@ -13,15 +13,11 @@ class Config: PRICE_SUCCESS_MESSAGE = "The price of {coin_name} is ${price:,}" PRICE_FAILURE_MESSAGE = "Failed to retrieve price. Please enter a valid coin name." FLOOR_PRICE_SUCCESS_MESSAGE = "The floor price of {nft_name} is ${floor_price:,}" - FLOOR_PRICE_FAILURE_MESSAGE = ( - "Failed to retrieve floor price. Please enter a valid NFT name." - ) + FLOOR_PRICE_FAILURE_MESSAGE = "Failed to retrieve floor price. Please enter a valid NFT name." TVL_SUCCESS_MESSAGE = "The TVL of {protocol_name} is ${tvl:,}" TVL_FAILURE_MESSAGE = "Failed to retrieve TVL. Please enter a valid protocol name." FDV_SUCCESS_MESSAGE = "The fully diluted valuation of {coin_name} is ${fdv:,}" FDV_FAILURE_MESSAGE = "Failed to retrieve FDV. Please enter a valid coin name." MARKET_CAP_SUCCESS_MESSAGE = "The market cap of {coin_name} is ${market_cap:,}" - MARKET_CAP_FAILURE_MESSAGE = ( - "Failed to retrieve market cap. Please enter a valid coin name." - ) + MARKET_CAP_FAILURE_MESSAGE = "Failed to retrieve market cap. Please enter a valid coin name." API_ERROR_MESSAGE = "I can't seem to access the API at the moment." diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py index d8417f5..1f347a7 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py @@ -1,11 +1,12 @@ import logging -from flask import Blueprint, request, jsonify +from flask import Blueprint, jsonify, request -crypto_data_agent_bp = Blueprint('crypto_data_agent', __name__) +crypto_data_agent_bp = Blueprint("crypto_data_agent", __name__) logger = logging.getLogger(__name__) -@crypto_data_agent_bp.route('/process_data', methods=['POST']) + +@crypto_data_agent_bp.route("/process_data", methods=["POST"]) def process_data(): logger.info("Data Agent: Received process_data request") data = request.get_json() diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py index 0b0b100..cf68a1d 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py @@ -1,8 +1,8 @@ -import requests import logging + +import requests from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity - from src.agents.crypto_data.config import Config @@ -13,9 +13,7 @@ def get_most_similar(text, data): text_vector = vectorizer.transform([text]) similarity_scores = cosine_similarity(text_vector, sentence_vectors) top_indices = similarity_scores.argsort()[0][-20:] - top_matches = [ - data[item] for item in top_indices if similarity_scores[0][item] > 0.5 - ] + top_matches = [data[item] for item in top_indices if similarity_scores[0][item] > 0.5] return top_matches @@ -171,9 +169,7 @@ def get_nft_floor_price_tool(nft_name): floor_price = get_floor_price(nft_name) if floor_price is None: return Config.FLOOR_PRICE_FAILURE_MESSAGE - return Config.FLOOR_PRICE_SUCCESS_MESSAGE.format( - nft_name=nft_name, floor_price=floor_price - ) + return Config.FLOOR_PRICE_SUCCESS_MESSAGE.format(nft_name=nft_name, floor_price=floor_price) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE @@ -185,9 +181,7 @@ def get_protocol_total_value_locked_tool(protocol_name): if tvl is None: return Config.TVL_FAILURE_MESSAGE protocol, tvl_value = list(tvl.items())[0][0], list(tvl.items())[0][1] - return Config.TVL_SUCCESS_MESSAGE.format( - protocol_name=protocol_name, tvl=tvl_value - ) + return Config.TVL_SUCCESS_MESSAGE.format(protocol_name=protocol_name, tvl=tvl_value) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE @@ -209,9 +203,7 @@ def get_coin_market_cap_tool(coin_name): market_cap = get_market_cap(coin_name) if market_cap is None: return Config.MARKET_CAP_FAILURE_MESSAGE - return Config.MARKET_CAP_SUCCESS_MESSAGE.format( - coin_name=coin_name, market_cap=market_cap - ) + return Config.MARKET_CAP_SUCCESS_MESSAGE.format(coin_name=coin_name, market_cap=market_cap) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py index a18f7f4..d766736 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py @@ -24,21 +24,15 @@ def _get_response(self, message, wallet_address): 0: tools.get_current_user_reward(wallet_address, 0), 1: tools.get_current_user_reward(wallet_address, 1), } - available_rewards = { - pool: amount for pool, amount in rewards.items() if amount > 0 - } + available_rewards = {pool: amount for pool, amount in rewards.items() if amount > 0} if available_rewards: selected_pool = max(available_rewards, key=available_rewards.get) self.conversation_state[wallet_address]["available_rewards"] = { selected_pool: available_rewards[selected_pool] } - self.conversation_state[wallet_address][ - "receiver_address" - ] = wallet_address - self.conversation_state[wallet_address][ - "state" - ] = "awaiting_confirmation" + self.conversation_state[wallet_address]["receiver_address"] = wallet_address + self.conversation_state[wallet_address]["state"] = "awaiting_confirmation" return ( f"You have {available_rewards[selected_pool]} MOR rewards available in pool {selected_pool}. Would you like to proceed with claiming these rewards?", "assistant", @@ -53,9 +47,7 @@ def _get_response(self, message, wallet_address): elif state == "awaiting_confirmation": user_input = message[-1]["content"].lower() - if any( - word in user_input for word in ["yes", "proceed", "confirm", "claim"] - ): + if any(word in user_input for word in ["yes", "proceed", "confirm", "claim"]): return self.prepare_transactions(wallet_address) else: return ( @@ -104,9 +96,7 @@ def chat(self, request: ChatRequest): if "prompt" in data and "wallet_address" in data: prompt = data["prompt"] wallet_address = data["wallet_address"] - response, role, next_turn_agent = self._get_response( - [prompt], wallet_address - ) + response, role, next_turn_agent = self._get_response([prompt], wallet_address) return { "role": role, "content": response, @@ -153,9 +143,7 @@ def get_status(self, flag, tx_hash, tx_type): elif flag == "failed": response = f"The claim transaction has failed." elif flag == "initiated": - response = ( - f"Claim transaction has been sent, please wait for it to be confirmed." - ) + response = f"Claim transaction has been sent, please wait for it to be confirmed." if tx_hash: response = ( diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py index e5f0143..f921140 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py @@ -1,6 +1,5 @@ -from web3 import Web3 - from src.agents.mor_claims.config import Config +from web3 import Web3 def get_current_user_reward(wallet_address, pool_id): @@ -36,9 +35,7 @@ def prepare_claim_transaction(pool_id, wallet_address): mint_fee = web3.to_wei(Config.MINT_FEE, "ether") estimated_gas = contract.functions.claim( pool_id, web3.to_checksum_address(wallet_address) - ).estimate_gas( - {"from": web3.to_checksum_address(wallet_address), "value": mint_fee} - ) + ).estimate_gas({"from": web3.to_checksum_address(wallet_address), "value": mint_fee}) return { "to": Config.DISTRIBUTION_PROXY_ADDRESS, "data": tx_data, diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py index d3dfff1..39b7813 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py @@ -42,9 +42,7 @@ def chat(self, request: ChatRequest): if "prompt" in data and "wallet_address" in data: prompt = data["prompt"] wallet_address = data["wallet_address"] - response, role, next_turn_agent = self.get_response( - prompt, wallet_address - ) + response, role, next_turn_agent = self.get_response(prompt, wallet_address) return { "role": role, "content": response, diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py index 553041e..a674810 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py @@ -1,6 +1,5 @@ -from web3 import Web3 - from src.agents.mor_rewards.config import Config +from web3 import Web3 def get_current_user_reward(wallet_address, pool_id): diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py index ecbb4d0..07b9f1e 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py @@ -2,14 +2,11 @@ import logging import re import urllib.parse + +import pyshorteners from src.agents.news_agent.config import Config -from src.agents.news_agent.tools import ( - clean_html, - is_within_time_window, - fetch_rss_feed, -) +from src.agents.news_agent.tools import clean_html, fetch_rss_feed, is_within_time_window from src.models.messages import ChatRequest -import pyshorteners logger = logging.getLogger(__name__) @@ -42,7 +39,6 @@ def get_tools(self): "required": ["coins"], }, }, - } ] @@ -68,15 +64,11 @@ def process_rss_feed(self, feed_url, coin): logger.info(f"Checking relevance for article: {title}") result = self.check_relevance_and_summarize(title, content, coin) if not result.upper().startswith("NOT RELEVANT"): - results.append( - {"Title": title, "Summary": result, "Link": entry.link} - ) + results.append({"Title": title, "Summary": result, "Link": entry.link}) if len(results) >= Config.ARTICLES_PER_TOKEN: break else: - logger.info( - f"Skipping article: {entry.title} (published: {published_time})" - ) + logger.info(f"Skipping article: {entry.title} (published: {published_time})") logger.info(f"Found {len(results)} relevant articles for {coin}") return results @@ -89,10 +81,7 @@ def fetch_crypto_news(self, coins): google_news_url = Config.GOOGLE_NEWS_BASE_URL.format(coin_name) results = self.process_rss_feed(google_news_url, coin_name) all_news.extend( - [ - {"Coin": coin, **result} - for result in results[: Config.ARTICLES_PER_TOKEN] - ] + [{"Coin": coin, **result} for result in results[: Config.ARTICLES_PER_TOKEN]] ) logger.info(f"Total news items fetched: {len(all_news)}") @@ -108,9 +97,7 @@ def chat(self, request: ChatRequest): # Updated coin detection logic coins = re.findall( - r"\b(" - + "|".join(re.escape(key) for key in Config.CRYPTO_DICT.keys()) - + r")\b", + r"\b(" + "|".join(re.escape(key) for key in Config.CRYPTO_DICT.keys()) + r")\b", prompt.upper(), ) diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py index e9fe7d3..6db7ac0 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py @@ -2,6 +2,7 @@ logging.basicConfig(level=logging.INFO) + class Config: # RSS Feed URL GOOGLE_NEWS_BASE_URL = "https://news.google.com/rss/search?q={}&hl=en-US&gl=US&ceid=US:en" @@ -27,104 +28,104 @@ class Config: # Dictionary of top 100 popular tickers and their crypto names CRYPTO_DICT = { - 'BTC': 'Bitcoin', - 'ETH': 'Ethereum', - 'USDT': 'Tether', - 'BNB': 'BNB', - 'SOL': 'Solana', - 'USDC': 'USDC', - 'XRP': 'XRP', - 'STETH': 'Lido Staked Ether', - 'DOGE': 'Dogecoin', - 'TON': 'Toncoin', - 'ADA': 'Cardano', - 'TRX': 'TRON', - 'AVAX': 'Avalanche', - 'WSTETH': 'Wrapped stETH', - 'SHIB': 'Shiba Inu', - 'WBTC': 'Wrapped Bitcoin', - 'WETH': 'Binance-Peg WETH', - 'LINK': 'Chainlink', - 'BCH': 'Bitcoin Cash', - 'DOT': 'Polkadot', - 'NEAR': 'NEAR Protocol', - 'UNI': 'Uniswap', - 'LEO': 'LEO Token', - 'DAI': 'Dai', - 'SUI': 'Sui', - 'LTC': 'Litecoin', - 'PEPE': 'Pepe', - 'ICP': 'Internet Computer', - 'WEETH': 'Wrapped eETH', - 'TAO': 'Bittensor', - 'FET': 'Artificial Superintelligence Alliance', - 'APT': 'Aptos', - 'KAS': 'Kaspa', - 'POL': 'POL (ex-MATIC)', - 'XLM': 'Stellar', - 'ETC': 'Ethereum Classic', - 'STX': 'Stacks', - 'FDUSD': 'First Digital USD', - 'IMX': 'Immutable', - 'XMR': 'Monero', - 'RENDER': 'Render', - 'WIF': 'dogwifhat', - 'USDE': 'Ethena USDe', - 'OKB': 'OKB', - 'AAVE': 'Aave', - 'INJ': 'Injective', - 'OP': 'Optimism', - 'FIL': 'Filecoin', - 'CRO': 'Cronos', - 'ARB': 'Arbitrum', - 'HBAR': 'Hedera', - 'FTM': 'Fantom', - 'MNT': 'Mantle', - 'VET': 'VeChain', - 'ATOM': 'Cosmos Hub', - 'RUNE': 'THORChain', - 'BONK': 'Bonk', - 'GRT': 'The Graph', - 'SEI': 'Sei', - 'WBT': 'WhiteBIT Coin', - 'FLOKI': 'FLOKI', - 'AR': 'Arweave', - 'THETA': 'Theta Network', - 'RETH': 'Rocket Pool ETH', - 'BGB': 'Bitget Token', - 'MKR': 'Maker', - 'HNT': 'Helium', - 'METH': 'Mantle Staked Ether', - 'SOLVBTC': 'Solv Protocol SolvBTC', - 'PYTH': 'Pyth Network', - 'TIA': 'Celestia', - 'JUP': 'Jupiter', - 'LDO': 'Lido DAO', - 'MATIC': 'Polygon', - 'ONDO': 'Ondo', - 'ALGO': 'Algorand', - 'GT': 'Gate', - 'JASMY': 'JasmyCoin', - 'QNT': 'Quant', - 'OM': 'MANTRA', - 'BEAM': 'Beam', - 'POPCAT': 'Popcat', - 'BSV': 'Bitcoin SV', - 'KCS': 'KuCoin', - 'EZETH': 'Renzo Restaked ETH', - 'CORE': 'Core', - 'BRETT': 'Brett', - 'WLD': 'Worldcoin', - 'GALA': 'GALA', - 'BTT': 'BitTorrent', - 'FLOW': 'Flow', - 'NOT': 'Notcoin', - 'STRK': 'Starknet', - 'EETH': 'ether.fi Staked ETH', - 'MSOL': 'Marinade Staked SOL', - 'EIGEN': 'Eigenlayer', - 'ORDI': 'ORDI', - 'CFX': 'Conflux', - 'W': 'Wormhole', - 'MOR': 'Morpheus AI' - } \ No newline at end of file + "BTC": "Bitcoin", + "ETH": "Ethereum", + "USDT": "Tether", + "BNB": "BNB", + "SOL": "Solana", + "USDC": "USDC", + "XRP": "XRP", + "STETH": "Lido Staked Ether", + "DOGE": "Dogecoin", + "TON": "Toncoin", + "ADA": "Cardano", + "TRX": "TRON", + "AVAX": "Avalanche", + "WSTETH": "Wrapped stETH", + "SHIB": "Shiba Inu", + "WBTC": "Wrapped Bitcoin", + "WETH": "Binance-Peg WETH", + "LINK": "Chainlink", + "BCH": "Bitcoin Cash", + "DOT": "Polkadot", + "NEAR": "NEAR Protocol", + "UNI": "Uniswap", + "LEO": "LEO Token", + "DAI": "Dai", + "SUI": "Sui", + "LTC": "Litecoin", + "PEPE": "Pepe", + "ICP": "Internet Computer", + "WEETH": "Wrapped eETH", + "TAO": "Bittensor", + "FET": "Artificial Superintelligence Alliance", + "APT": "Aptos", + "KAS": "Kaspa", + "POL": "POL (ex-MATIC)", + "XLM": "Stellar", + "ETC": "Ethereum Classic", + "STX": "Stacks", + "FDUSD": "First Digital USD", + "IMX": "Immutable", + "XMR": "Monero", + "RENDER": "Render", + "WIF": "dogwifhat", + "USDE": "Ethena USDe", + "OKB": "OKB", + "AAVE": "Aave", + "INJ": "Injective", + "OP": "Optimism", + "FIL": "Filecoin", + "CRO": "Cronos", + "ARB": "Arbitrum", + "HBAR": "Hedera", + "FTM": "Fantom", + "MNT": "Mantle", + "VET": "VeChain", + "ATOM": "Cosmos Hub", + "RUNE": "THORChain", + "BONK": "Bonk", + "GRT": "The Graph", + "SEI": "Sei", + "WBT": "WhiteBIT Coin", + "FLOKI": "FLOKI", + "AR": "Arweave", + "THETA": "Theta Network", + "RETH": "Rocket Pool ETH", + "BGB": "Bitget Token", + "MKR": "Maker", + "HNT": "Helium", + "METH": "Mantle Staked Ether", + "SOLVBTC": "Solv Protocol SolvBTC", + "PYTH": "Pyth Network", + "TIA": "Celestia", + "JUP": "Jupiter", + "LDO": "Lido DAO", + "MATIC": "Polygon", + "ONDO": "Ondo", + "ALGO": "Algorand", + "GT": "Gate", + "JASMY": "JasmyCoin", + "QNT": "Quant", + "OM": "MANTRA", + "BEAM": "Beam", + "POPCAT": "Popcat", + "BSV": "Bitcoin SV", + "KCS": "KuCoin", + "EZETH": "Renzo Restaked ETH", + "CORE": "Core", + "BRETT": "Brett", + "WLD": "Worldcoin", + "GALA": "GALA", + "BTT": "BitTorrent", + "FLOW": "Flow", + "NOT": "Notcoin", + "STRK": "Starknet", + "EETH": "ether.fi Staked ETH", + "MSOL": "Marinade Staked SOL", + "EIGEN": "Eigenlayer", + "ORDI": "ORDI", + "CFX": "Conflux", + "W": "Wormhole", + "MOR": "Morpheus AI", + } diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py index f017b22..ac874d2 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py @@ -1,21 +1,22 @@ -import feedparser +import logging +import re +import urllib.parse from datetime import datetime, timedelta +from html import unescape + +import feedparser import pytz from dateutil import parser -import re -from html import unescape from src.agents.news_agent.config import Config -import logging -import urllib.parse logger = logging.getLogger(__name__) def clean_html(raw_html): - cleanr = re.compile('<.*?>') - cleantext = re.sub(cleanr, '', raw_html) + cleanr = re.compile("<.*?>") + cleantext = re.sub(cleanr, "", raw_html) cleantext = unescape(cleantext) - cleantext = ' '.join(cleantext.split()) + cleantext = " ".join(cleantext.split()) return cleantext @@ -37,8 +38,8 @@ def fetch_rss_feed(feed_url): # URL encode the query parameter parsed_url = urllib.parse.urlparse(feed_url) query_params = urllib.parse.parse_qs(parsed_url.query) - if 'q' in query_params: - query_params['q'] = [urllib.parse.quote(q) for q in query_params['q']] + if "q" in query_params: + query_params["q"] = [urllib.parse.quote(q) for q in query_params["q"]] encoded_query = urllib.parse.urlencode(query_params, doseq=True) encoded_url = urllib.parse.urlunparse(parsed_url._replace(query=encoded_query)) @@ -58,14 +59,12 @@ def get_tools(): "properties": { "coins": { "type": "array", - "items": { - "type": "string" - }, - "description": "List of cryptocurrency symbols to fetch news for" + "items": {"type": "string"}, + "description": "List of cryptocurrency symbols to fetch news for", } }, - "required": ["coins"] - } - } + "required": ["coins"], + }, + }, } - ] \ No newline at end of file + ] diff --git a/submodules/moragents_dockers/agents/src/agents/rag/agent.py b/submodules/moragents_dockers/agents/src/agents/rag/agent.py index 977de93..fbb0ccb 100644 --- a/submodules/moragents_dockers/agents/src/agents/rag/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/rag/agent.py @@ -1,16 +1,14 @@ -import os import logging +import os from fastapi import Request -from werkzeug.utils import secure_filename - from langchain_community.document_loaders import PyMuPDFLoader from langchain_community.vectorstores import FAISS from langchain_core.prompts import ChatPromptTemplate from langchain_text_splitters.character import RecursiveCharacterTextSplitter - from src.models.messages import ChatRequest from src.stores import chat_manager +from werkzeug.utils import secure_filename logger = logging.getLogger(__name__) @@ -22,18 +20,16 @@ def __init__(self, config, llm, embeddings): self.config = config self.llm = llm self.embedding = embeddings - self.messages = [ - {"role": "assistant", "content": "Please upload a file to begin"} - ] + self.messages = [{"role": "assistant", "content": "Please upload a file to begin"}] self.prompt = ChatPromptTemplate.from_template( """ Answer the following question only based on the given context - + {context} - + Question: {input} """ ) @@ -100,7 +96,7 @@ def _get_rag_response(self, prompt): ] result = self.llm.invoke(messages) return result.content.strip() - + def chat(self, request: ChatRequest): try: data = request.dict() diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py index 1c098b9..c747413 100644 --- a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py @@ -1,13 +1,12 @@ import logging -import requests import time +import requests from bs4 import BeautifulSoup from selenium import webdriver +from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys -from selenium.webdriver.chrome.options import Options - from src.models.messages import ChatRequest logging.basicConfig( @@ -95,8 +94,8 @@ def synthesize_answer(self, search_term, search_results): messages = [ { "role": "system", - "content": """You are a helpful assistant that synthesizes information from web search results to answer user queries. - Do not preface your answer with 'Based on the search results, I can tell you that:' or anything similar. + "content": """You are a helpful assistant that synthesizes information from web search results to answer user queries. + Do not preface your answer with 'Based on the search results, I can tell you that:' or anything similar. Just provide the answer.""", }, { diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/README.md b/submodules/moragents_dockers/agents/src/agents/token_swap/README.md index d22f59e..daa7bd4 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/README.md +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/README.md @@ -50,9 +50,9 @@ To run: Your agent should expose the following endpoints: -### 1. Chat -This is the main endpoint for chatting with the agent. - +### 1. Chat +This is the main endpoint for chatting with the agent. + ```http://127.0.0.1:5000/``` The chat API accepts inputs in OpenAI chat completion format - see the example below: @@ -65,14 +65,14 @@ data = {'prompt':message,'chain_id':56} response = requests.post(url, json=data) ``` -The response will also be in this format. +The response will also be in this format. ```sh -response = {"role":"assistant","content":"To proceed with the swap, I need to know which crypto currency you want to +response = {"role":"assistant","content":"To proceed with the swap, I need to know which crypto currency you want to buy in exchange for 1 ETH. Could you please specify the target crypto currency?"} ``` -If the agent has enough information (buy token, sell token, amount) it will then look up the token addresses on the current chain. +If the agent has enough information (buy token, sell token, amount) it will then look up the token addresses on the current chain. If the token symbols are valid, it will then check the user has sufficient balance of the sell token. @@ -91,12 +91,12 @@ response = {"role": "swap", If the user wants to perform a swap based on the quote, the following steps are required: - 1) Check allowance + 1) Check allowance 2) If allowance < swap amount, send an approve transaction 3) If allowance >= swap amount, send a swap transaction -### 2. Check Allowance +### 2. Check Allowance ```http://127.0.0.1:5000/allowance``` @@ -130,7 +130,7 @@ url='http://127.0.0.1:5000/approve ``` -### 4. Generate Swap tx +### 4. Generate Swap tx ```http://127.0.0.1:5000/swap``` @@ -156,7 +156,7 @@ url='http://127.0.0.1:5000/swap ```http://127.0.0.1:5000/tx_status``` This endpoint is used to inform the back-end of the status of transactions that have been signed by the user's wallet on the front-end. - + Status values: * "initiated" = tx has been sent to the wallet @@ -207,4 +207,4 @@ result=response.text ``` url = http://127.0.0.1:5000/clear_messages response = requests.get(url, json=data) -``` \ No newline at end of file +``` diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py index e99e9b3..46f3673 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -1,7 +1,7 @@ import json -import requests import logging +import requests from src.agents.token_swap import tools from src.agents.token_swap.config import Config from src.models.messages import ChatRequest @@ -123,17 +123,13 @@ def get_status(self, flag, tx_hash, tx_type): if flag != "initiated": self.context = [] self.context.append({"role": "assistant", "content": response}) - self.context.append( - {"role": "user", "content": "okay lets start again from scratch"} - ) + self.context.append({"role": "user", "content": "okay lets start again from scratch"}) return {"role": "assistant", "content": response} def generate_response(self, prompt, chain_id, wallet_address): self.context.append(prompt) - response, role, next_turn_agent = self.get_response( - self.context, chain_id, wallet_address - ) + response, role, next_turn_agent = self.get_response(self.context, chain_id, wallet_address) return response, role, next_turn_agent def chat(self, request: ChatRequest): diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py index 2e2c871..40f4bb9 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py @@ -1,9 +1,9 @@ -import requests import logging import time -from web3 import Web3 +import requests from src.agents.token_swap.config import Config +from web3 import Web3 class InsufficientFundsError(Exception): @@ -21,9 +21,7 @@ class SwapNotPossibleError(Exception): def search_tokens(query, chain_id, limit=1, ignore_listed="false"): endpoint = f"/v1.2/{chain_id}/search" params = {"query": query, "limit": limit, "ignore_listed": ignore_listed} - response = requests.get( - Config.INCH_URL + endpoint, params=params, headers=Config.HEADERS - ) + response = requests.get(Config.INCH_URL + endpoint, params=params, headers=Config.HEADERS) if response.status_code == 200: return response.json() else: @@ -31,21 +29,15 @@ def search_tokens(query, chain_id, limit=1, ignore_listed="false"): return None -def get_token_balance( - web3: Web3, wallet_address: str, token_address: str, abi: list -) -> int: +def get_token_balance(web3: Web3, wallet_address: str, token_address: str, abi: list) -> int: """Get the balance of an ERC-20 token for a given wallet address.""" if ( not token_address ): # If no token address is provided, assume checking ETH or native token balance return web3.eth.get_balance(web3.to_checksum_address(wallet_address)) else: - contract = web3.eth.contract( - address=web3.to_checksum_address(token_address), abi=abi - ) - return contract.functions.balanceOf( - web3.to_checksum_address(wallet_address) - ).call() + contract = web3.eth.contract(address=web3.to_checksum_address(token_address), abi=abi) + return contract.functions.balanceOf(web3.to_checksum_address(wallet_address)).call() def eth_to_wei(amount_in_eth: float) -> int: @@ -73,9 +65,7 @@ def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): time.sleep(2) if not t1: raise TokenNotFoundError(f"Token {token1} not found.") - t1_bal = get_token_balance( - web3, wallet_address, t1[0]["address"], Config.ERC20_ABI - ) + t1_bal = get_token_balance(web3, wallet_address, t1[0]["address"], Config.ERC20_ABI) smallest_amount = convert_to_smallest_unit(web3, amount, t1[0]["address"]) # Check if token2 is the native token @@ -102,9 +92,7 @@ def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): def get_quote(token1, token2, amount_in_wei, chain_id): endpoint = f"/v6.0/{chain_id}/quote" params = {"src": token1, "dst": token2, "amount": int(amount_in_wei)} - response = requests.get( - Config.QUOTE_URL + endpoint, params=params, headers=Config.HEADERS - ) + response = requests.get(Config.QUOTE_URL + endpoint, params=params, headers=Config.HEADERS) if response.status_code == 200: return response.json() else: @@ -127,9 +115,7 @@ def convert_to_smallest_unit(web3: Web3, amount: float, token_address: str) -> i return int(amount * (10**decimals)) -def convert_to_readable_unit( - web3: Web3, smallest_unit_amount: int, token_address: str -) -> float: +def convert_to_readable_unit(web3: Web3, smallest_unit_amount: int, token_address: str) -> float: decimals = get_token_decimals(web3, token_address) return smallest_unit_amount / (10**decimals) @@ -137,9 +123,7 @@ def convert_to_readable_unit( def swap_coins(token1, token2, amount, chain_id, wallet_address): """Swap two crypto coins with each other""" web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL[str(chain_id)])) - t1_a, t1_id, t2_a, t2_id = validate_swap( - web3, token1, token2, chain_id, amount, wallet_address - ) + t1_a, t1_id, t2_a, t2_id = validate_swap(web3, token1, token2, chain_id, amount, wallet_address) time.sleep(2) t1_address = "" if t1_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t1_a diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py index 2cbd3ff..5627a22 100644 --- a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py @@ -1,6 +1,6 @@ import logging -import tweepy +import tweepy from src.agents.tweet_sizzler.config import Config from src.models.messages import ChatRequest @@ -125,9 +125,7 @@ def chat(self, chat_request: ChatRequest): logger.info(f"Received chat request: {prompt}") action = prompt.get("action", Config.DEFAULT_ACTION) - logger.debug( - f"Extracted prompt content: {prompt['content']}, action: {action}" - ) + logger.debug(f"Extracted prompt content: {prompt['content']}, action: {action}") if action == "generate": logger.info(f"Generating tweet for prompt: {prompt['content']}") @@ -137,9 +135,7 @@ def chat(self, chat_request: ChatRequest): elif action == "post": logger.info("Attempting to post tweet") result, status_code = self.post_tweet(chat_request) - logger.info( - f"Posted tweet result: {result}, status code: {status_code}" - ) + logger.info(f"Posted tweet result: {result}, status code: {status_code}") if isinstance(result, dict) and "error" in result: return result, status_code return { diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index ba9e865..8044593 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -1,19 +1,17 @@ -import os import logging +import os import time -import uvicorn -from fastapi import FastAPI, HTTPException, Request, UploadFile, File +import uvicorn +from fastapi import FastAPI, File, HTTPException, Request, UploadFile from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel - -from langchain_ollama import ChatOllama from langchain_community.embeddings import OllamaEmbeddings - +from langchain_ollama import ChatOllama +from pydantic import BaseModel from src.config import Config from src.delegator import Delegator -from src.stores import agent_manager, chat_manager from src.models.messages import ChatRequest +from src.stores import agent_manager, chat_manager # Constants UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") @@ -42,9 +40,7 @@ model=Config.OLLAMA_MODEL, base_url=Config.OLLAMA_URL, ) -embeddings = OllamaEmbeddings( - model=Config.OLLAMA_EMBEDDING_MODEL, base_url=Config.OLLAMA_URL -) +embeddings = OllamaEmbeddings(model=Config.OLLAMA_EMBEDDING_MODEL, base_url=Config.OLLAMA_URL) delegator = Delegator(Config.DELEGATOR_CONFIG, llm, embeddings) @@ -147,7 +143,6 @@ async def swap_agent_swap(request: Request): async def rag_agent_upload(file: UploadFile = File(...)): logger.info("Received upload request") response = await delegator.delegate_route( - "general purpose and context-based rag agent", {"file": file}, "upload_file" ) chat_manager.add_message(response) @@ -169,9 +164,7 @@ async def post_tweet(request: Request): @app.post("/set_x_api_key") async def set_x_api_key(request: Request): logger.info("Received set X API key request") - return await delegator.delegate_route( - "tweet sizzler agent", request, "set_x_api_key" - ) + return await delegator.delegate_route("tweet sizzler agent", request, "set_x_api_key") @app.post("/claim") diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index 05a06ee..1702c5c 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -70,6 +70,6 @@ class Config: "description": "Fetches and analyzes cryptocurrency news for potential price impacts.", "name": "crypto news agent", "upload_required": False, - } + }, ] } diff --git a/submodules/moragents_dockers/agents/src/delegator.py b/submodules/moragents_dockers/agents/src/delegator.py index a0f9b5e..364d782 100644 --- a/submodules/moragents_dockers/agents/src/delegator.py +++ b/submodules/moragents_dockers/agents/src/delegator.py @@ -1,8 +1,8 @@ import importlib -import logging import json +import logging -from langchain.schema import SystemMessage, HumanMessage, AIMessage +from langchain.schema import AIMessage, HumanMessage, SystemMessage logger = logging.getLogger(__name__) @@ -118,11 +118,7 @@ def delegate_route(self, agent_name, request, method_name): method = getattr(agent, method_name) return method(request) else: - logger.warning( - "Method %s not found in agent %s", method_name, agent_name - ) - return { - "error": f"No such method '{method_name}' in agent '{agent_name}'" - }, 400 + logger.warning("Method %s not found in agent %s", method_name, agent_name) + return {"error": f"No such method '{method_name}' in agent '{agent_name}'"}, 400 logger.warning("Attempted to delegate to non-existent agent: %s", agent_name) return {"error": "No such agent registered"}, 400 diff --git a/submodules/moragents_dockers/agents/src/stores/chat_manager.py b/submodules/moragents_dockers/agents/src/stores/chat_manager.py index 5bd79eb..6532a57 100644 --- a/submodules/moragents_dockers/agents/src/stores/chat_manager.py +++ b/submodules/moragents_dockers/agents/src/stores/chat_manager.py @@ -1,5 +1,5 @@ import logging -from typing import List, Dict +from typing import Dict, List logger = logging.getLogger(__name__) diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md index 16c72ce..bb8c3ac 100644 --- a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md @@ -20,4 +20,4 @@ NOTE: this is made for the router compatible moragents repo 5) If it is running, navigate to `submodules/claim_agent_benchmarks` 6) run `pytest benchmarks.py` -NOTE: If needed use your own alchemy mainnet RPC instead of the default cloudflare one in `config.py` and please `pip install pytest web3` \ No newline at end of file +NOTE: If needed use your own alchemy mainnet RPC instead of the default cloudflare one in `config.py` and please `pip install pytest web3` diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py index 97afe42..a9bed8a 100644 --- a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py @@ -1,6 +1,8 @@ -import requests import json +import requests + + class ClaimAdapter: def __init__(self, url, headers): self.url = url @@ -11,4 +13,6 @@ def ask_agent(self, payload): if response.status_code == 200: return response.json() else: - raise Exception(f"Request failed with status code {response.status_code}: {response.text}") + raise Exception( + f"Request failed with status code {response.status_code}: {response.text}" + ) diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py index 880365d..af675a5 100644 --- a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py @@ -1,5 +1,6 @@ import pytest -from helpers import request_claim, provide_receiver_address, confirm_transaction +from helpers import confirm_transaction, provide_receiver_address, request_claim + from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config @@ -12,17 +13,17 @@ def test_claim_process(): # Step 1: Request to claim rewards response = request_claim(wallet_address) - assert "Please provide the receiver address" in response['content'] + assert "Please provide the receiver address" in response["content"] print(f"Step 1 passed for wallet {wallet_address}: Request to claim MOR rewards") # Step 2: Provide the receiver address response = provide_receiver_address(wallet_address, receiver_address) - assert "Please confirm if you want to proceed" in response['content'] + assert "Please confirm if you want to proceed" in response["content"] print(f"Step 2 passed for wallet {wallet_address}: Provided receiver address") # Step 3: Confirm the transaction response = confirm_transaction(wallet_address) - assert "Transaction data prepared" in response['content'] + assert "Transaction data prepared" in response["content"] print(f"Step 3 passed for wallet {wallet_address}: Transaction confirmed") diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py index e4ed3ea..d15c74d 100644 --- a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py @@ -1,13 +1,8 @@ class Config: - URL = 'http://127.0.0.1:5000/' - HEADERS = {'Content-Type': 'application/json'} + URL = "http://127.0.0.1:5000/" + HEADERS = {"Content-Type": "application/json"} # Test wallet addresses - WALLET_ADDRESSES = [ - {"wallet": "0x48d0EAc727A7e478f792F16527012452a000f2bd"} - ] + WALLET_ADDRESSES = [{"wallet": "0x48d0EAc727A7e478f792F16527012452a000f2bd"}] - PROMPTS = { - "claim_request": "I want to claim my MOR", - "proceed": "yes" - } + PROMPTS = {"claim_request": "I want to claim my MOR", "proceed": "yes"} diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py index a319834..a2a8653 100644 --- a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py @@ -1,18 +1,22 @@ +from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.adapters.claim_adapter import ( + ClaimAdapter, +) from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config -from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.adapters.claim_adapter import ClaimAdapter claim_adapter = ClaimAdapter(Config.URL, Config.HEADERS) + def request_claim(wallet_address): payload = { "prompt": {"role": "user", "content": Config.PROMPTS["claim_request"]}, - "wallet_address": wallet_address + "wallet_address": wallet_address, } return claim_adapter.ask_agent(payload) + def confirm_transaction(wallet_address): payload = { "prompt": {"role": "user", "content": Config.PROMPTS["proceed"]}, - "wallet_address": wallet_address + "wallet_address": wallet_address, } return claim_adapter.ask_agent(payload) diff --git a/submodules/moragents_dockers/agents/tests/data_agent/test_agent.py b/submodules/moragents_dockers/agents/tests/data_agent/test_agent.py index b44936f..13db7e4 100644 --- a/submodules/moragents_dockers/agents/tests/data_agent/test_agent.py +++ b/submodules/moragents_dockers/agents/tests/data_agent/test_agent.py @@ -1,95 +1,105 @@ -import sys import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'data_agent', 'src'))) +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src", "data_agent", "src")) +) # The above sys/os lines are needed because the agents have identical namings (i.e. agent.py, tools.py) +from unittest.mock import MagicMock, patch + import pytest -from unittest.mock import patch, MagicMock -from src.data_agent.src.agent import get_response, generate_response, chat, get_messages, clear_messages from flask import Flask, request +from src.data_agent.src.agent import ( + chat, + clear_messages, + generate_response, + get_messages, + get_response, +) + @pytest.fixture def mock_llm(): mock = MagicMock() mock.create_chat_completion.return_value = { - "choices": [ - { - "message": { - "content": "This is a test response" - } - } - ] + "choices": [{"message": {"content": "This is a test response"}}] } return mock + @pytest.fixture def app(): app = Flask(__name__) return app + def test_get_response(mock_llm): message = [{"role": "user", "content": "Hello"}] response, role = get_response(message, mock_llm) assert response == "This is a test response" assert role == "assistant" + def test_get_response_with_tool_call(mock_llm): mock_llm.create_chat_completion.return_value = { "choices": [ { "message": { "tool_calls": [ - { - "function": { - "name": "get_price", - "arguments": '{"coin_name": "bitcoin"}' - } - } + {"function": {"name": "get_price", "arguments": '{"coin_name": "bitcoin"}'}} ] } } ] } - - with patch('agent.tools.get_coin_price_tool') as mock_price_tool: + + with patch("agent.tools.get_coin_price_tool") as mock_price_tool: mock_price_tool.return_value = "The price of bitcoin is $50,000" message = [{"role": "user", "content": "What's the price of Bitcoin?"}] response, role = get_response(message, mock_llm) - + assert response == "The price of bitcoin is $50,000" assert role == "assistant" + def test_generate_response(mock_llm): prompt = {"role": "user", "content": "Hello"} response, role = generate_response(prompt, mock_llm) assert response == "This is a test response" assert role == "assistant" + def test_chat(app, mock_llm): with app.test_request_context(json={"prompt": "Hello"}): - with patch('agent.generate_response', return_value=("This is a test response", "assistant")): + with patch( + "agent.generate_response", return_value=("This is a test response", "assistant") + ): response, status_code = chat(request, mock_llm) - + assert status_code == 200 assert response.json == {"role": "assistant", "content": "This is a test response"} + def test_chat_missing_prompt(app): with app.test_request_context(json={}): response, status_code = chat(request, None) - + assert status_code == 400 assert "error" in response.json + def test_get_messages(app): with app.test_request_context(): response = get_messages() - + assert response.status_code == 200 assert "messages" in response.json + def test_clear_messages(app): with app.test_request_context(): response = clear_messages() - + assert response.status_code == 200 - assert response.json["response"] == "successfully cleared message history" \ No newline at end of file + assert response.json["response"] == "successfully cleared message history" diff --git a/submodules/moragents_dockers/agents/tests/data_agent/test_tools.py b/submodules/moragents_dockers/agents/tests/data_agent/test_tools.py index 2cbce79..4c51fcc 100644 --- a/submodules/moragents_dockers/agents/tests/data_agent/test_tools.py +++ b/submodules/moragents_dockers/agents/tests/data_agent/test_tools.py @@ -1,82 +1,105 @@ -import sys import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'data_agent', 'src'))) +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src", "data_agent", "src")) +) # The above sys/os lines are needed because the agents have identical namings (i.e. agent.py, tools.py) -import pytest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch +import pytest from src.data_agent.src.tools import ( + get_coin_market_cap_tool, + get_coin_price_tool, get_coingecko_id, - get_price, - get_floor_price, get_fdv, + get_floor_price, + get_fully_diluted_valuation_tool, get_market_cap, - get_protocol_tvl, - get_coin_price_tool, get_nft_floor_price_tool, - get_fully_diluted_valuation_tool, - get_coin_market_cap_tool, + get_price, + get_protocol_tvl, ) # Mock responses -mock_coingecko_search = { - 'coins': [{'id': 'bitcoin'}], - 'nfts': [{'id': 'bored-ape-yacht-club'}] -} -mock_price_response = {'bitcoin': {'usd': 50000}} -mock_floor_price_response = {'floor_price': {'usd': 100000}} -mock_fdv_response = {'market_data': {'fully_diluted_valuation': {'usd': 1000000000000}}} -mock_market_cap_response = [{'market_cap': 500000000000}] -mock_tvl_response = {'tvl': 10000000000} +mock_coingecko_search = {"coins": [{"id": "bitcoin"}], "nfts": [{"id": "bored-ape-yacht-club"}]} +mock_price_response = {"bitcoin": {"usd": 50000}} +mock_floor_price_response = {"floor_price": {"usd": 100000}} +mock_fdv_response = {"market_data": {"fully_diluted_valuation": {"usd": 1000000000000}}} +mock_market_cap_response = [{"market_cap": 500000000000}] +mock_tvl_response = {"tvl": 10000000000} + @pytest.fixture def mock_requests_get(): - with patch('requests.get') as mock_get: + with patch("requests.get") as mock_get: mock_get.return_value = MagicMock() mock_get.return_value.json.return_value = {} yield mock_get + def test_get_coingecko_id_coin(mock_requests_get): mock_requests_get.return_value.json.return_value = mock_coingecko_search - assert get_coingecko_id('bitcoin', type='coin') == 'bitcoin' + assert get_coingecko_id("bitcoin", type="coin") == "bitcoin" + def test_get_coingecko_id_nft(mock_requests_get): mock_requests_get.return_value.json.return_value = mock_coingecko_search - assert get_coingecko_id('bored ape', type='nft') == 'bored-ape-yacht-club' + assert get_coingecko_id("bored ape", type="nft") == "bored-ape-yacht-club" + def test_get_price(mock_requests_get): mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_price_response] - assert get_price('bitcoin') == 50000 + assert get_price("bitcoin") == 50000 + def test_get_floor_price(mock_requests_get): - mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_floor_price_response] - assert get_floor_price('bored ape') == 100000 + mock_requests_get.return_value.json.side_effect = [ + mock_coingecko_search, + mock_floor_price_response, + ] + assert get_floor_price("bored ape") == 100000 + def test_get_fdv(mock_requests_get): mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_fdv_response] - assert get_fdv('bitcoin') == 1000000000000 + assert get_fdv("bitcoin") == 1000000000000 + def test_get_market_cap(mock_requests_get): - mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_market_cap_response] - assert get_market_cap('bitcoin') == 500000000000 + mock_requests_get.return_value.json.side_effect = [ + mock_coingecko_search, + mock_market_cap_response, + ] + assert get_market_cap("bitcoin") == 500000000000 + def test_get_coin_price_tool(mock_requests_get): mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_price_response] - result = get_coin_price_tool('bitcoin') + result = get_coin_price_tool("bitcoin") assert "The price of bitcoin is $50,000" in result + def test_get_nft_floor_price_tool(mock_requests_get): - mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_floor_price_response] - result = get_nft_floor_price_tool('bored ape') + mock_requests_get.return_value.json.side_effect = [ + mock_coingecko_search, + mock_floor_price_response, + ] + result = get_nft_floor_price_tool("bored ape") assert "The floor price of bored ape is $100,000" in result + def test_get_fully_diluted_valuation_tool(mock_requests_get): mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_fdv_response] - result = get_fully_diluted_valuation_tool('bitcoin') + result = get_fully_diluted_valuation_tool("bitcoin") assert "The fully diluted valuation of bitcoin is $1,000,000,000,000" in result + def test_get_coin_market_cap_tool(mock_requests_get): - mock_requests_get.return_value.json.side_effect = [mock_coingecko_search, mock_market_cap_response] - result = get_coin_market_cap_tool('bitcoin') - assert "The market cap of bitcoin is $500,000,000,000" in result \ No newline at end of file + mock_requests_get.return_value.json.side_effect = [ + mock_coingecko_search, + mock_market_cap_response, + ] + result = get_coin_market_cap_tool("bitcoin") + assert "The market cap of bitcoin is $500,000,000,000" in result diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md index d1c4b90..2e7e805 100644 --- a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md @@ -17,4 +17,4 @@ 4) Check if the agent is up and running on docker or not 5) If it is running, navigate to `submodules/news_agent_benchmarks` -6) run `pytest benchmarks.py` \ No newline at end of file +6) run `pytest benchmarks.py` diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py index 7b7df4c..f5662b5 100644 --- a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py @@ -1,9 +1,14 @@ # submodules/benchmarks/news_agent_benchmarks/benchmarks.py -import pytest import logging + +import pytest + from submodules.moragents_dockers.agents.tests.news_agent_benchmarks.config import Config -from submodules.moragents_dockers.agents.tests.news_agent_benchmarks.helpers import ask_news_agent, extract_classification +from submodules.moragents_dockers.agents.tests.news_agent_benchmarks.helpers import ( + ask_news_agent, + extract_classification, +) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -23,11 +28,15 @@ def test_news_classification(): classification = extract_classification(response) if classification == "UNKNOWN": - logger.warning(f"Test case {i + 1} resulted in UNKNOWN classification. Response: {response}") + logger.warning( + f"Test case {i + 1} resulted in UNKNOWN classification. Response: {response}" + ) assert False, f"Test case {i + 1} failed: Could not determine classification" else: # Check if the classification matches the expected classification - assert classification == expected_classification, f"Test case {i + 1} failed: Expected {expected_classification}, but got {classification}" + assert ( + classification == expected_classification + ), f"Test case {i + 1} failed: Expected {expected_classification}, but got {classification}" logger.info(f"Test case {i + 1} passed: Correctly classified as {classification}") diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py index 7ce2623..d5eba22 100644 --- a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py @@ -1,18 +1,19 @@ # submodules/benchmarks/news_agent_benchmarks/helpers.py -import requests import logging +import requests + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def ask_news_agent(article_text: str, url: str) -> dict: - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} payload = { "prompt": { "role": "user", - "content": f"Classify if this article is relevant to cryptocurrency price movements: {article_text}" + "content": f"Classify if this article is relevant to cryptocurrency price movements: {article_text}", } } response = requests.post(url, headers=headers, json=payload) @@ -27,7 +28,7 @@ def extract_classification(response: dict) -> str: logger.warning(f"Unexpected response type: {type(response)}") return "UNKNOWN" - content = response.get('content') + content = response.get("content") if content is None: logger.warning("Response content is None") @@ -45,4 +46,4 @@ def extract_classification(response: dict) -> str: return "RELEVANT" else: logger.warning(f"Could not determine relevance from content: {content}") - return "NOT RELEVANT" \ No newline at end of file + return "NOT RELEVANT" diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py index 323f1be..48816c7 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod + class BaseAdapter(ABC): @property diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py index 9327118..6103722 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py @@ -1,6 +1,8 @@ import requests + from .base_adapter import BaseAdapter + # defillama and coingecko share the same identifiers class CoingeckoAdapter(BaseAdapter): @@ -10,18 +12,18 @@ def name(self) -> str: def get_price(self, coingecko_id: str) -> float: url = f"https://api.coingecko.com/api/v3/simple/price" - params = {'ids': coingecko_id, 'vs_currencies': 'usd'} + params = {"ids": coingecko_id, "vs_currencies": "usd"} response = requests.get(url, params=params) response.raise_for_status() - return response.json()[coingecko_id]['usd'] + return response.json()[coingecko_id]["usd"] def get_marketcap(self, coin_id: str) -> float: - request_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}" - response = requests.get(request_url) - response.raise_for_status() - data = response.json() - marketcap = data['market_data']['market_cap']['usd'] - return marketcap + request_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}" + response = requests.get(request_url) + response.raise_for_status() + data = response.json() + marketcap = data["market_data"]["market_cap"]["usd"] + return marketcap def has_get_marketcap(self) -> bool: return True diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py index 21bac2f..e98990d 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py @@ -1,22 +1,24 @@ import requests + from .base_adapter import BaseAdapter + class DefillamaAdapter(BaseAdapter): @property def name(self) -> str: return "Defillama" - + def get_price(self, coingecko_id: str) -> float: request_url = f"https://coins.llama.fi/prices/current/coingecko:{coingecko_id}" response = requests.get(request_url) response.raise_for_status() data = response.json() - price = data['coins'][f'coingecko:{coingecko_id}']['price'] + price = data["coins"][f"coingecko:{coingecko_id}"]["price"] return price def has_get_marketcap(self) -> bool: return False def has_get_price(self) -> bool: - return True \ No newline at end of file + return True diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py index d376fcd..c665393 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py @@ -1,25 +1,31 @@ -import time import argparse -from helpers import ask_data_agent, compare_usd_values, extract_agent_usd_value -from submodules.moragents_dockers.agents.tests.price_fetching import coins, price_prompts, mcap_prompts, price_error_tolerance, mcap_error_tolerance, loop_delay +import time + from adapters.coingecko_adapter import CoingeckoAdapter from adapters.defillama_adapter import DefillamaAdapter +from helpers import ask_data_agent, compare_usd_values, extract_agent_usd_value + +from submodules.moragents_dockers.agents.tests.price_fetching import ( + coins, + loop_delay, + mcap_error_tolerance, + mcap_prompts, + price_error_tolerance, + price_prompts, +) -all_adapters = [ - DefillamaAdapter(), - CoingeckoAdapter() -] +all_adapters = [DefillamaAdapter(), CoingeckoAdapter()] parser = argparse.ArgumentParser(description="Specify the type of prompts to use (price or mcap).") -parser.add_argument('type', choices=['price', 'mcap'], help="Type of prompts to use") +parser.add_argument("type", choices=["price", "mcap"], help="Type of prompts to use") args = parser.parse_args() benchmark_type = args.type -if benchmark_type == 'price': +if benchmark_type == "price": prompts = price_prompts error_tolerance = price_error_tolerance -elif benchmark_type == 'mcap': +elif benchmark_type == "mcap": prompts = mcap_prompts error_tolerance = mcap_error_tolerance else: @@ -28,7 +34,7 @@ total_checks = 0 failures = [] -print('benchmark type', benchmark_type) +print("benchmark type", benchmark_type) try: print() for prompt in prompts: @@ -41,7 +47,9 @@ agent_response = ask_data_agent(prompt.format(name_variation)) agent_usd_value = extract_agent_usd_value(agent_response) print(f"{agent_usd_value}") - time.sleep(loop_delay) # the agent gets rate limitted by coingecko if we call it too fast + time.sleep( + loop_delay + ) # the agent gets rate limitted by coingecko if we call it too fast except: result = f"FAIL DataAgent: {agent_prompt}" print(result) @@ -59,7 +67,15 @@ benchmark_value = adapter.get_marketcap(coingecko_id) else: continue - result = compare_usd_values(agent_usd_value, adapter, coingecko_id, name_variation, benchmark_value, error_tolerance, failures) + result = compare_usd_values( + agent_usd_value, + adapter, + coingecko_id, + name_variation, + benchmark_value, + error_tolerance, + failures, + ) except Exception as e: result = f"FAIL {adapter.name}: {coingecko_id} ({e})" failures.append(result) diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/config.py b/submodules/moragents_dockers/agents/tests/price_fetching/config.py index 34a764e..5b3bf19 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/config.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/config.py @@ -1,252 +1,182 @@ -loop_delay = 10 # prevent rate limiting +loop_delay = 10 # prevent rate limiting -price_error_tolerance = 0.01 # 1% -mcap_error_tolerance = 0.01 # 1% +price_error_tolerance = 0.01 # 1% +mcap_error_tolerance = 0.01 # 1% coins = [ - { - "coingecko_id": "bitcoin", - "name_variations": ["Bitcoin", "Bitcoins", "BITcoin", "BTC"] - }, - { - "coingecko_id": "ethereum", - "name_variations": ["Ether", "Ethereum", "Ethers", "ETH"] - }, - { - "coingecko_id": "tether", - "name_variations": ["Tether", "USDT", "USDTether", "TetherUSD"] - }, + {"coingecko_id": "bitcoin", "name_variations": ["Bitcoin", "Bitcoins", "BITcoin", "BTC"]}, + {"coingecko_id": "ethereum", "name_variations": ["Ether", "Ethereum", "Ethers", "ETH"]}, + {"coingecko_id": "tether", "name_variations": ["Tether", "USDT", "USDTether", "TetherUSD"]}, { "coingecko_id": "binancecoin", - "name_variations": ["BNB", "Binance Smart Chain", "Binancecoin", "Binance coin"] - }, - { - "coingecko_id": "solana", - "name_variations": ["Solana", "SOL", "Solanacoin"] - }, - { - "coingecko_id": "usd-coin", - "name_variations": ["USDC", "USDCoin", "USD coin", "CUSD"] + "name_variations": ["BNB", "Binance Smart Chain", "Binancecoin", "Binance coin"], }, + {"coingecko_id": "solana", "name_variations": ["Solana", "SOL", "Solanacoin"]}, + {"coingecko_id": "usd-coin", "name_variations": ["USDC", "USDCoin", "USD coin", "CUSD"]}, { "coingecko_id": "staked-ether", - "name_variations": ["Lido Staked Ether", "Lido Ether", "Lido Eth", "Staked Ether"] - }, - { - "coingecko_id": "ripple", - "name_variations": ["XRP", "Ripple", "XRPcoin", "XRP coin"] + "name_variations": ["Lido Staked Ether", "Lido Ether", "Lido Eth", "Staked Ether"], }, + {"coingecko_id": "ripple", "name_variations": ["XRP", "Ripple", "XRPcoin", "XRP coin"]}, { "coingecko_id": "the-open-network", - "name_variations": ["Toncoin", "TON", "the open network", "open network coin"] - }, - { - "coingecko_id": "dogecoin", - "name_variations": ["Dogecoin", "DOGE", "dogcoin", "doge coin"] - }, - { - "coingecko_id": "cardano", - "name_variations": ["Cardano", "ADA", "cardano coin"] - }, - { - "coingecko_id": "tron", - "name_variations": ["Tron", "TRX", "troncoin", "trxcoin"] + "name_variations": ["Toncoin", "TON", "the open network", "open network coin"], }, + {"coingecko_id": "dogecoin", "name_variations": ["Dogecoin", "DOGE", "dogcoin", "doge coin"]}, + {"coingecko_id": "cardano", "name_variations": ["Cardano", "ADA", "cardano coin"]}, + {"coingecko_id": "tron", "name_variations": ["Tron", "TRX", "troncoin", "trxcoin"]}, { "coingecko_id": "avalanche-2", - "name_variations": ["Avalanche", "AVAX", "Avalancecoin", "Avaxcoin"] + "name_variations": ["Avalanche", "AVAX", "Avalancecoin", "Avaxcoin"], }, { "coingecko_id": "shiba-inu", - "name_variations": ["Shiba Inu", "Shiba", "SHIB", "shibacoin", "Shiba inu coin"] + "name_variations": ["Shiba Inu", "Shiba", "SHIB", "shibacoin", "Shiba inu coin"], }, { "coingecko_id": "wrapped-bitcoin", - "name_variations": ["Wrapped Bitcoin", "WBTC", "wrapped btc", "wrapped BITcoin"] + "name_variations": ["Wrapped Bitcoin", "WBTC", "wrapped btc", "wrapped BITcoin"], }, { "coingecko_id": "polkadot", - "name_variations": ["Polkadot", "DOT", "polkadotcoin", "polka dot coin", "polka coin"] + "name_variations": ["Polkadot", "DOT", "polkadotcoin", "polka dot coin", "polka coin"], }, { "coingecko_id": "chainlink", - "name_variations": ["Chainlink", "LINK", "Chainlinkcoin", "Linkcoin"] + "name_variations": ["Chainlink", "LINK", "Chainlinkcoin", "Linkcoin"], }, { "coingecko_id": "bitcoin-cash", - "name_variations": ["Bitcoin Cash", "BCH", "BTC cash", "BCHcoin"] - }, - { - "coingecko_id": "uniswap", - "name_variations": ["Uniswap", "UNI", "Unicoin", "Uniswap coin"] - }, - { - "coingecko_id": "leo-token", - "name_variations": ["LEO token", "LEO", "leotoken", "leocoin"] - }, - { - "coingecko_id": "dai", - "name_variations": ["Dai", "Daicoin", "DaiUSD"] - }, - { - "coingecko_id": "near", - "name_variations": ["Near Protocol", "NEAR", "Nearcoin"] - }, - { - "coingecko_id": "litecoin", - "name_variations": ["Litecoin", "LTC", "LTCcoin", "lightcoin"] + "name_variations": ["Bitcoin Cash", "BCH", "BTC cash", "BCHcoin"], }, + {"coingecko_id": "uniswap", "name_variations": ["Uniswap", "UNI", "Unicoin", "Uniswap coin"]}, + {"coingecko_id": "leo-token", "name_variations": ["LEO token", "LEO", "leotoken", "leocoin"]}, + {"coingecko_id": "dai", "name_variations": ["Dai", "Daicoin", "DaiUSD"]}, + {"coingecko_id": "near", "name_variations": ["Near Protocol", "NEAR", "Nearcoin"]}, + {"coingecko_id": "litecoin", "name_variations": ["Litecoin", "LTC", "LTCcoin", "lightcoin"]}, { "coingecko_id": "matic-network", - "name_variations": ["Polygon", "Matic", "MATIC", "Polygoncoin", "Maticcoin"] + "name_variations": ["Polygon", "Matic", "MATIC", "Polygoncoin", "Maticcoin"], }, { "coingecko_id": "wrapped-eeth", - "name_variations": ["Wrapped eETH", "eETH", "WEETH", "eETHcoin", "WEETHcoin"] - }, - { - "coingecko_id": "kaspa", - "name_variations": ["Kaspa", "KAS", "Kaspacoin", "Kascoin"] - }, - { - "coingecko_id": "pepe", - "name_variations": ["Pepe", "Pepecoin"] + "name_variations": ["Wrapped eETH", "eETH", "WEETH", "eETHcoin", "WEETHcoin"], }, + {"coingecko_id": "kaspa", "name_variations": ["Kaspa", "KAS", "Kaspacoin", "Kascoin"]}, + {"coingecko_id": "pepe", "name_variations": ["Pepe", "Pepecoin"]}, { "coingecko_id": "ethena-usde", - "name_variations": ["Ethena", "USDE", "Ethena USD", "Ethenacoin", "USDEcoin"] + "name_variations": ["Ethena", "USDE", "Ethena USD", "Ethenacoin", "USDEcoin"], }, { "coingecko_id": "internet-computer", - "name_variations": ["Internet Computer", "ICP", "ICPcoin", "InternetComputercoin"] + "name_variations": ["Internet Computer", "ICP", "ICPcoin", "InternetComputercoin"], }, { "coingecko_id": "renzo-restaked-eth", - "name_variations": ["Renzo Restaked ETH", "Renzo Restaked Ethereum", "Renzocoin", "RenzoEth"] + "name_variations": [ + "Renzo Restaked ETH", + "Renzo Restaked Ethereum", + "Renzocoin", + "RenzoEth", + ], }, { "coingecko_id": "ethereum-classic", - "name_variations": ["Ethereum Classic", "Ether Classic", "ETH Classic", "ETC"] + "name_variations": ["Ethereum Classic", "Ether Classic", "ETH Classic", "ETC"], }, { "coingecko_id": "fetch-ai", "name_variations": [ - "Artificial Superintelligence Alliance", "FET", "FETcoin", "Fetch", - "Ocean Protocol", "Oceancoin", "Singularity", "Singularitycoin" - ] - }, - { - "coingecko_id": "monero", - "name_variations": ["Monero", "XMR", "Monerocoin", "XMRcoin"] - }, - { - "coingecko_id": "aptos", - "name_variations": ["Aptos", "APT", "Aptoscoin", "APTcoin"] - }, + "Artificial Superintelligence Alliance", + "FET", + "FETcoin", + "Fetch", + "Ocean Protocol", + "Oceancoin", + "Singularity", + "Singularitycoin", + ], + }, + {"coingecko_id": "monero", "name_variations": ["Monero", "XMR", "Monerocoin", "XMRcoin"]}, + {"coingecko_id": "aptos", "name_variations": ["Aptos", "APT", "Aptoscoin", "APTcoin"]}, { "coingecko_id": "render-token", - "name_variations": ["Render", "RNDR", "Rendercoin", "RNDRcoin", "Render token"] - }, - { - "coingecko_id": "stellar", - "name_variations": ["Stellar", "XLM", "Stellarcoin", "XLMcoin"] + "name_variations": ["Render", "RNDR", "Rendercoin", "RNDRcoin", "Render token"], }, + {"coingecko_id": "stellar", "name_variations": ["Stellar", "XLM", "Stellarcoin", "XLMcoin"]}, { "coingecko_id": "hedera-hashgraph", - "name_variations": ["Hedera", "HBAR", "Hederacoin", "HBARcoin", "Hashgraph"] + "name_variations": ["Hedera", "HBAR", "Hederacoin", "HBARcoin", "Hashgraph"], }, { "coingecko_id": "cosmos", - "name_variations": ["Cosmos", "Cosmoshub", "ATOM", "Cosmoscoin", "ATOMCoin"] - }, - { - "coingecko_id": "arbitrum", - "name_variations": ["Arbitrum", "ARB", "Arbitrumcoin", "ARBCoin"] + "name_variations": ["Cosmos", "Cosmoshub", "ATOM", "Cosmoscoin", "ATOMCoin"], }, + {"coingecko_id": "arbitrum", "name_variations": ["Arbitrum", "ARB", "Arbitrumcoin", "ARBCoin"]}, { "coingecko_id": "crypto-com-chain", - "name_variations": ["Cronos", "CRO", "Cronoscoin", "CROCoin", "Crypto.com"] - }, - { - "coingecko_id": "mantle", - "name_variations": ["Mantle", "MNT", "Mantlecoin", "MNTCoin"] + "name_variations": ["Cronos", "CRO", "Cronoscoin", "CROCoin", "Crypto.com"], }, + {"coingecko_id": "mantle", "name_variations": ["Mantle", "MNT", "Mantlecoin", "MNTCoin"]}, { "coingecko_id": "blockstack", - "name_variations": ["Stacks", "STX", "Stackscoin", "STXCoin", "Blockstack"] - }, - { - "coingecko_id": "filecoin", - "name_variations": ["Filecoin", "FIL", "FILCoin", "File coin"] - }, - { - "coingecko_id": "okb", - "name_variations": ["OKB", "OKBCoin"] + "name_variations": ["Stacks", "STX", "Stackscoin", "STXCoin", "Blockstack"], }, + {"coingecko_id": "filecoin", "name_variations": ["Filecoin", "FIL", "FILCoin", "File coin"]}, + {"coingecko_id": "okb", "name_variations": ["OKB", "OKBCoin"]}, { "coingecko_id": "maker", - "name_variations": ["Maker", "MKR", "MakerDAO", "Makercoin", "MRKCoin"] - }, - { - "coingecko_id": "vechain", - "name_variations": ["VeChain", "VET", "VeChaincoin", "VETCoin"] + "name_variations": ["Maker", "MKR", "MakerDAO", "Makercoin", "MRKCoin"], }, + {"coingecko_id": "vechain", "name_variations": ["VeChain", "VET", "VeChaincoin", "VETCoin"]}, { "coingecko_id": "injective-protocol", - "name_variations": ["Injective", "INJ", "Injectivecoin", "INJCoin", "Injective Protocol"] + "name_variations": ["Injective", "INJ", "Injectivecoin", "INJCoin", "Injective Protocol"], }, { "coingecko_id": "immutable-x", - "name_variations": ["Immutable", "IMX", "Immutablecoin", "IMXCoin", "ImmutableX"] + "name_variations": ["Immutable", "IMX", "Immutablecoin", "IMXCoin", "ImmutableX"], }, { "coingecko_id": "first-digital-usd", - "name_variations": ["First Digital USD", "FDUSD", "FirstDigitalUSD", "FDUSDCoin"] - }, - { - "coingecko_id": "optimism", - "name_variations": ["Optimism", "OP", "Optimismcoin", "OPCoin"] - }, - { - "coingecko_id": "morpheusai", - "name_variations": ["Morpheus", "MorpheusAI", "MOR", "MORCoin"] - }, - { - "coingecko_id": "aave", - "name_variations": ["Aave", "Aavecoin"] + "name_variations": ["First Digital USD", "FDUSD", "FirstDigitalUSD", "FDUSDCoin"], }, + {"coingecko_id": "optimism", "name_variations": ["Optimism", "OP", "Optimismcoin", "OPCoin"]}, + {"coingecko_id": "morpheusai", "name_variations": ["Morpheus", "MorpheusAI", "MOR", "MORCoin"]}, + {"coingecko_id": "aave", "name_variations": ["Aave", "Aavecoin"]}, { "coingecko_id": "aavegotchi", - "name_variations": ["Aavegotchi", "Ghost", "Aavegotchicoin", "Ghostcoin", "GHST"] + "name_variations": ["Aavegotchi", "Ghost", "Aavegotchicoin", "Ghostcoin", "GHST"], }, { "coingecko_id": "thorchain", - "name_variations": ["Thor", "THORChain", "RUNE", "Thorcoin", "Runecoin"] + "name_variations": ["Thor", "THORChain", "RUNE", "Thorcoin", "Runecoin"], }, { "coingecko_id": "ethereum-name-service", - "name_variations": ["Ethereum Name Service", "ENS", "ENScoin"] + "name_variations": ["Ethereum Name Service", "ENS", "ENScoin"], }, { "coingecko_id": "axie-infinity", - "name_variations": ["Axie Infinity", "AXS", "Axiecoin", "Axscoin"] - }, - { - "coingecko_id": "zombiecoin", - "name_variations": ["ZombieCoin", "Zombie", "ZMBCoin"] + "name_variations": ["Axie Infinity", "AXS", "Axiecoin", "Axscoin"], }, + {"coingecko_id": "zombiecoin", "name_variations": ["ZombieCoin", "Zombie", "ZMBCoin"]}, { "coingecko_id": "elon-xmas", - "name_variations": ["Elon Xmas", "XMAS", "ElonXmascoin", "XMASCoin"] - }, - { - "coingecko_id": "neblio", - "name_variations": ["Neblio", "NEBL", "Nebliocoin", "NEBLCoin"] + "name_variations": ["Elon Xmas", "XMAS", "ElonXmascoin", "XMASCoin"], }, + {"coingecko_id": "neblio", "name_variations": ["Neblio", "NEBL", "Nebliocoin", "NEBLCoin"]}, { "coingecko_id": "shapeshift-fox-token", - "name_variations": ["ShapeShift FOX", "ShapeShift", "ShapeShiftCoin", "FOXCoin", "FOXToken"] - } + "name_variations": [ + "ShapeShift FOX", + "ShapeShift", + "ShapeShiftCoin", + "FOXCoin", + "FOXToken", + ], + }, ] price_prompts = [ @@ -267,4 +197,4 @@ tvl_prompts = [ "What is the TVL of {}?", "How much value is locked in {}?", -] \ No newline at end of file +] diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py b/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py index e50c767..7712caa 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py @@ -1,47 +1,54 @@ -import requests import json import re + +import requests from adapters.base_adapter import BaseAdapter -url = 'http://127.0.0.1:8080/data_agent/' +url = "http://127.0.0.1:8080/data_agent/" headers = { - 'Accept': 'application/json, text/plain, */*', - 'Accept-Language': 'en-US,en;q=0.9', - 'Connection': 'keep-alive', - 'Content-Type': 'application/json', - 'Origin': 'http://localhost:3333', - 'Referer': 'http://localhost:3333/', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site', - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', - 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"macOS"', + "Accept": "application/json, text/plain, */*", + "Accept-Language": "en-US,en;q=0.9", + "Connection": "keep-alive", + "Content-Type": "application/json", + "Origin": "http://localhost:3333", + "Referer": "http://localhost:3333/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"macOS"', } + def ask_data_agent(prompt: str): - payload = { - "prompt": { - "role": "user", - "content": prompt - } - } + payload = {"prompt": {"role": "user", "content": prompt}} response = requests.post(url, headers=headers, data=json.dumps(payload)) - - result_content = response.json()['content'] + + result_content = response.json()["content"] return result_content + def extract_agent_usd_value(content: str): - match = re.search(r'\$\d+(?:,\d{3})*(?:\.\d{1,8})?', content) # 8 usd digits should be plenty + match = re.search(r"\$\d+(?:,\d{3})*(?:\.\d{1,8})?", content) # 8 usd digits should be plenty if match: - price_str = match.group(0).replace('$', '').replace(',', '') + price_str = match.group(0).replace("$", "").replace(",", "") return float(price_str) raise ValueError("Could not extract a price from the agent response") -def compare_usd_values(agent_value: float, adapter: BaseAdapter, coingecko_id: str, name_variation: str, benchmark_value: float, error_tolerance: float, failures: list): + +def compare_usd_values( + agent_value: float, + adapter: BaseAdapter, + coingecko_id: str, + name_variation: str, + benchmark_value: float, + error_tolerance: float, + failures: list, +): difference = abs(agent_value - benchmark_value) percent_difference = (difference / benchmark_value) * 100 result_value = f"${benchmark_value:.8f}, {percent_difference:.2f}% off" @@ -49,6 +56,6 @@ def compare_usd_values(agent_value: float, adapter: BaseAdapter, coingecko_id: s result_message = f"PASS {adapter.name}. {result_value}" else: result_message = f"FAIL {adapter.name}. {result_value}" - failure_message = f"FAIL {adapter.name}. {result_value}. {coingecko_id}. {name_variation}" # so we have more information to summarize failures at the end + failure_message = f"FAIL {adapter.name}. {result_value}. {coingecko_id}. {name_variation}" # so we have more information to summarize failures at the end failures.append(result_message) return result_message diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt b/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt index 663bd1f..f229360 100644 --- a/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt +++ b/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt @@ -1 +1 @@ -requests \ No newline at end of file +requests diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md index 9469618..3baf18d 100644 --- a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md @@ -20,4 +20,4 @@ NOTE: this is made for the router compatible moragents repo 5) If it is running, navigate to `submodules/reward_check_agent_benchmarks` 6) run `benchmarks.py` -NOTE: If needed use your own alchemy mainnet RPC instead of the default cloudflare one in `config.py` and please `pip install pytest web3` \ No newline at end of file +NOTE: If needed use your own alchemy mainnet RPC instead of the default cloudflare one in `config.py` and please `pip install pytest web3` diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py index 0482fc5..ce68f12 100644 --- a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py @@ -1,5 +1,6 @@ from submodules.moragents_dockers.agents.src.claim_agent.src.tools import get_current_user_reward + class RewardCheckAdapter: def __init__(self): pass diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py index 03395e7..85915fb 100644 --- a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py @@ -1,4 +1,5 @@ import pytest + from submodules.benchmarks.claim_agent_benchmarks.helpers import request_claim from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config @@ -13,25 +14,27 @@ def test_claim_process(): response = request_claim(wallet_address) # Check if the response contains the expected structure - assert 'role' in response - assert response['role'] == 'claim' - assert 'content' in response - assert isinstance(response['content'], dict) - assert 'content' in response['content'] - assert 'transactions' in response['content']['content'] - assert len(response['content']['content']['transactions']) > 0 + assert "role" in response + assert response["role"] == "claim" + assert "content" in response + assert isinstance(response["content"], dict) + assert "content" in response["content"] + assert "transactions" in response["content"]["content"] + assert len(response["content"]["content"]["transactions"]) > 0 - transaction = response['content']['content']['transactions'][0] - assert 'pool' in transaction - assert 'transaction' in transaction + transaction = response["content"]["content"]["transactions"][0] + assert "pool" in transaction + assert "transaction" in transaction - tx_data = transaction['transaction'] - assert all(key in tx_data for key in ['to', 'data', 'value', 'gas', 'chainId']) + tx_data = transaction["transaction"] + assert all(key in tx_data for key in ["to", "data", "value", "gas", "chainId"]) # Additional specific checks - assert tx_data['to'] == '0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790', "Incorrect 'to' address" - assert tx_data['value'] == '1000000000000000', "Incorrect 'value'" - assert tx_data['chainId'] == '1', "Incorrect 'chainId'" + assert ( + tx_data["to"] == "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + ), "Incorrect 'to' address" + assert tx_data["value"] == "1000000000000000", "Incorrect 'value'" + assert tx_data["chainId"] == "1", "Incorrect 'chainId'" print(f"Step 1 passed for wallet {wallet_address}: Claim process triggered successfully") @@ -39,4 +42,4 @@ def test_claim_process(): if __name__ == "__main__": - pytest.main() \ No newline at end of file + pytest.main() diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py index 2522622..a48d8bb 100644 --- a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py @@ -14,27 +14,13 @@ class Config: DISTRIBUTION_ABI = [ { "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user_", - "type": "address" - } + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, ], "name": "getCurrentUserReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", } ] @@ -59,7 +45,7 @@ class Config: { "pool_id": 1, "wallet_address": "0x5CD4C60f0e566dCa1Ae8456C36a63bc7A8D803de", - } + }, ] reward_check_prompts = [ diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py index 9f05361..7d010e4 100644 --- a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py @@ -1,26 +1,25 @@ -import requests import re + +import requests from web3 import Web3 + from submodules.benchmarks.reward_check_agent_benchmarks.config import Config -url = 'http://127.0.0.1:5000/' +url = "http://127.0.0.1:5000/" headers = { - 'Content-Type': 'application/json', + "Content-Type": "application/json", } def ask_claim_agent(prompt: str, wallet_address: str): payload = { - "prompt": { - "role": "user", - "content": prompt - }, - "wallet_address": wallet_address # Adding the wallet address in the payload + "prompt": {"role": "user", "content": prompt}, + "wallet_address": wallet_address, # Adding the wallet address in the payload } response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: - return response.json()['content'] + return response.json()["content"] else: raise Exception(f"Request failed with status code {response.status_code}: {response.text}") @@ -29,7 +28,7 @@ def get_current_user_reward(wallet_address, pool_id): web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL["1"])) distribution_contract = web3.eth.contract( address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), - abi=Config.DISTRIBUTION_ABI + abi=Config.DISTRIBUTION_ABI, ) try: @@ -37,10 +36,9 @@ def get_current_user_reward(wallet_address, pool_id): raise Exception("Unable to connect to Ethereum network") reward = distribution_contract.functions.getCurrentUserReward( - pool_id, - web3.to_checksum_address(wallet_address) + pool_id, web3.to_checksum_address(wallet_address) ).call() - formatted_reward = web3.from_wei(reward, 'ether') + formatted_reward = web3.from_wei(reward, "ether") return round(formatted_reward, 4) except Exception as e: raise Exception(f"Error occurred while fetching the reward: {str(e)}") @@ -49,9 +47,9 @@ def get_current_user_reward(wallet_address, pool_id): def extract_reward_value_from_response(response: str) -> dict: # Regex to extract rewards for both pools; adjusted to be more flexible matches = re.findall( - r'Capital Providers Pool \(Pool 0\):\s*([\d.]+)\s*MOR.*?Code Providers Pool \(Pool 1\):\s*([\d.]+)\s*MOR', + r"Capital Providers Pool \(Pool 0\):\s*([\d.]+)\s*MOR.*?Code Providers Pool \(Pool 1\):\s*([\d.]+)\s*MOR", response, - re.DOTALL + re.DOTALL, ) rewards = {} diff --git a/submodules/moragents_dockers/agents/tests/swap_agent/test_agent.py b/submodules/moragents_dockers/agents/tests/swap_agent/test_agent.py index 82f0fab..a1dd527 100644 --- a/submodules/moragents_dockers/agents/tests/swap_agent/test_agent.py +++ b/submodules/moragents_dockers/agents/tests/swap_agent/test_agent.py @@ -1,38 +1,49 @@ -import sys import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'swap_agent', 'src'))) +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src", "swap_agent", "src")) +) # The above sys/os lines are needed because the agents have identical namings (i.e. agent.py, tools.py) +from unittest.mock import MagicMock, patch + import pytest -from unittest.mock import patch, MagicMock from flask import Flask, request -from src.swap_agent.src.agent import get_response, generate_response, chat, get_messages, clear_messages, get_allowance, approve, swap +from src.swap_agent.src.agent import ( + approve, + chat, + clear_messages, + generate_response, + get_allowance, + get_messages, + get_response, + swap, +) + @pytest.fixture def mock_llm(): mock = MagicMock() mock.create_chat_completion.return_value = { - "choices": [ - { - "message": { - "content": "This is a test response" - } - } - ] + "choices": [{"message": {"content": "This is a test response"}}] } return mock + @pytest.fixture def app(): app = Flask(__name__) return app + def test_get_response(mock_llm): message = [{"role": "user", "content": "Hello"}] response, role = get_response(message, "chain_id", "wallet_address", mock_llm) assert response == "This is a test response" assert role == "assistant" + def test_get_response_with_tool_call(mock_llm): mock_llm.create_chat_completion.return_value = { "choices": [ @@ -42,7 +53,7 @@ def test_get_response_with_tool_call(mock_llm): { "function": { "name": "swap_agent", - "arguments": '{"token1": "ETH", "token2": "USDT", "value": "1.0"}' + "arguments": '{"token1": "ETH", "token2": "USDT", "value": "1.0"}', } } ] @@ -50,66 +61,85 @@ def test_get_response_with_tool_call(mock_llm): } ] } - - with patch('src.swap_agent.src.tools.swap_coins') as mock_swap_coins: + + with patch("src.swap_agent.src.tools.swap_coins") as mock_swap_coins: mock_swap_coins.return_value = ("Swap successful", "assistant") message = [{"role": "user", "content": "Swap 1 ETH for USDT"}] response, role = get_response(message, "chain_id", "wallet_address", mock_llm) - + assert response == "Swap successful" assert role == "assistant" + def test_generate_response(mock_llm): prompt = {"role": "user", "content": "Hello"} response, role = generate_response(prompt, "chain_id", "wallet_address", mock_llm) assert response == "This is a test response" assert role == "assistant" + def test_chat(app, mock_llm): - with app.test_request_context(json={"prompt": "Hello", "wallet_address": "0x123", "chain_id": "1"}): - with patch('src.swap_agent.src.agent.generate_response', return_value=("This is a test response", "assistant")): + with app.test_request_context( + json={"prompt": "Hello", "wallet_address": "0x123", "chain_id": "1"} + ): + with patch( + "src.swap_agent.src.agent.generate_response", + return_value=("This is a test response", "assistant"), + ): response = chat(request, mock_llm) - + assert response.status_code == 200 assert response.json == {"role": "assistant", "content": "This is a test response"} + def test_chat_missing_prompt(app): with app.test_request_context(json={}): response, status_code = chat(request, None) - + assert status_code == 400 assert "error" in response.json + def test_get_messages(app): with app.test_request_context(): response = get_messages() - + assert response.status_code == 200 assert "messages" in response.json + def test_clear_messages(app): with app.test_request_context(): response = clear_messages() - + assert response.status_code == 200 assert response.json["response"] == "successfully cleared message history" + def test_get_allowance(app): - with app.test_request_context(json={"tokenAddress": "0x123", "walletAddress": "0x456", "chain_id": "1"}): - with patch('src.swap_agent.src.agent.check_allowance', return_value={"allowance": "1000"}): + with app.test_request_context( + json={"tokenAddress": "0x123", "walletAddress": "0x456", "chain_id": "1"} + ): + with patch("src.swap_agent.src.agent.check_allowance", return_value={"allowance": "1000"}): response = get_allowance(request) - + assert response.status_code == 200 assert "response" in response.json + def test_approve(app): - with app.test_request_context(json={"tokenAddress": "0x123", "chain_id": "1", "amount": "1000"}): - with patch('src.swap_agent.src.agent.approve_transaction', return_value={"txHash": "0x789"}): + with app.test_request_context( + json={"tokenAddress": "0x123", "chain_id": "1", "amount": "1000"} + ): + with patch( + "src.swap_agent.src.agent.approve_transaction", return_value={"txHash": "0x789"} + ): response = approve(request) - + assert response.status_code == 200 assert "response" in response.json + def test_swap(app): swap_params = { "src": "ETH", @@ -117,10 +147,10 @@ def test_swap(app): "walletAddress": "0x123", "amount": "1.0", "slippage": "1", - "chain_id": "1" + "chain_id": "1", } with app.test_request_context(json=swap_params): - with patch('src.swap_agent.src.agent.build_tx_for_swap', return_value={"txHash": "0x789"}): + with patch("src.swap_agent.src.agent.build_tx_for_swap", return_value={"txHash": "0x789"}): response = swap(request) - - assert {'txHash': '0x789'} == response + + assert {"txHash": "0x789"} == response diff --git a/submodules/moragents_dockers/agents/tests/swap_agent/test_tools.py b/submodules/moragents_dockers/agents/tests/swap_agent/test_tools.py index 66b8004..1450f81 100644 --- a/submodules/moragents_dockers/agents/tests/swap_agent/test_tools.py +++ b/submodules/moragents_dockers/agents/tests/swap_agent/test_tools.py @@ -1,17 +1,30 @@ -import sys import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'swap_agent', 'src'))) +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src", "swap_agent", "src")) +) # The above sys/os lines are needed because the agents have identical namings (i.e. agent.py, tools.py) +from unittest.mock import MagicMock, patch + import pytest -from unittest.mock import patch, MagicMock -from web3 import Web3 from src.swap_agent.src.tools import ( - search_tokens, get_token_balance, eth_to_wei, validate_swap, - get_quote, get_token_decimals, convert_to_smallest_unit, - convert_to_readable_unit, swap_coins, InsufficientFundsError, - TokenNotFoundError, SwapNotPossibleError + InsufficientFundsError, + SwapNotPossibleError, + TokenNotFoundError, + convert_to_readable_unit, + convert_to_smallest_unit, + eth_to_wei, + get_quote, + get_token_balance, + get_token_decimals, + search_tokens, + swap_coins, + validate_swap, ) +from web3 import Web3 + @pytest.fixture def mock_web3(): @@ -21,94 +34,108 @@ def mock_web3(): mock.eth.contract = MagicMock() return mock + def test_search_tokens(): - with patch('requests.get') as mock_get: + with patch("requests.get") as mock_get: mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = [{"symbol": "ETH", "address": "0x123"}] result = search_tokens("ETH", 1) assert result[0]["symbol"] == "ETH" + def test_get_token_balance(mock_web3): mock_web3.eth.get_balance.return_value = 1000 balance = get_token_balance(mock_web3, "0x456", "", []) assert balance == 1000 + def test_eth_to_wei(): assert eth_to_wei(1) == 10**18 + def test_validate_swap(mock_web3): - with patch('src.swap_agent.src.tools.search_tokens') as mock_search: + with patch("src.swap_agent.src.tools.search_tokens") as mock_search: mock_search.side_effect = [ [{"symbol": "ETH", "address": "0x0000000000000000000000000000000000000000"}], - [{"symbol": "DAI", "address": "0x123"}] + [{"symbol": "DAI", "address": "0x123"}], ] mock_web3.eth.get_balance.return_value = 10**18 result = validate_swap(mock_web3, "ETH", "DAI", 1, 1, "0x456") assert result[1] == "ETH" + def test_get_quote(): - with patch('requests.get') as mock_get: + with patch("requests.get") as mock_get: mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = {"dstAmount": "1000000000000000000"} result = get_quote("0x123", "0x456", 10**18, 1) assert result["dstAmount"] == "1000000000000000000" + def test_get_token_decimals(mock_web3): mock_contract = MagicMock() mock_contract.functions.decimals.return_value.call.return_value = 18 mock_web3.eth.contract.return_value = mock_contract assert get_token_decimals(mock_web3, "0x1234567890123456789012345678901234567890") == 18 + def test_convert_to_smallest_unit(mock_web3): - with patch('src.swap_agent.src.tools.get_token_decimals', return_value=18): + with patch("src.swap_agent.src.tools.get_token_decimals", return_value=18): assert convert_to_smallest_unit(mock_web3, 1, "0x123") == 10**18 + def test_convert_to_readable_unit(mock_web3): - with patch('src.swap_agent.src.tools.get_token_decimals', return_value=18): + with patch("src.swap_agent.src.tools.get_token_decimals", return_value=18): assert convert_to_readable_unit(mock_web3, 10**18, "0x123") == 1 + def test_swap_coins(): - with patch('src.swap_agent.src.tools.Web3') as mock_web3, \ - patch('src.swap_agent.src.tools.validate_swap') as mock_validate, \ - patch('src.swap_agent.src.tools.get_quote') as mock_quote: - + with patch("src.swap_agent.src.tools.Web3") as mock_web3, patch( + "src.swap_agent.src.tools.validate_swap" + ) as mock_validate, patch("src.swap_agent.src.tools.get_quote") as mock_quote: + mock_web3.return_value = MagicMock() mock_validate.return_value = ("0x123", "ETH", "0x456", "DAI") mock_quote.return_value = {"dstAmount": "1000000000000000000"} - + result, role = swap_coins("ETH", "DAI", 1, 1, "0x789") assert result["src"] == "ETH" assert result["dst"] == "DAI" assert role == "swap" + def test_swap_coins_insufficient_funds(): - with patch('src.swap_agent.src.tools.Web3') as mock_web3, \ - patch('src.swap_agent.src.tools.validate_swap') as mock_validate: - + with patch("src.swap_agent.src.tools.Web3") as mock_web3, patch( + "src.swap_agent.src.tools.validate_swap" + ) as mock_validate: + mock_web3.return_value = MagicMock() mock_validate.side_effect = InsufficientFundsError("Not enough funds") - + with pytest.raises(InsufficientFundsError): swap_coins("ETH", "DAI", 1000, 1, "0x789") + def test_swap_coins_token_not_found(): - with patch('src.swap_agent.src.tools.Web3') as mock_web3, \ - patch('src.swap_agent.src.tools.validate_swap') as mock_validate: - + with patch("src.swap_agent.src.tools.Web3") as mock_web3, patch( + "src.swap_agent.src.tools.validate_swap" + ) as mock_validate: + mock_web3.return_value = MagicMock() mock_validate.side_effect = TokenNotFoundError("Token not found") - + with pytest.raises(TokenNotFoundError): swap_coins("UNKNOWN", "DAI", 1, 1, "0x789") + def test_swap_coins_swap_not_possible(): - with patch('src.swap_agent.src.tools.Web3') as mock_web3, \ - patch('src.swap_agent.src.tools.validate_swap') as mock_validate, \ - patch('src.swap_agent.src.tools.get_quote') as mock_quote: - + with patch("src.swap_agent.src.tools.Web3") as mock_web3, patch( + "src.swap_agent.src.tools.validate_swap" + ) as mock_validate, patch("src.swap_agent.src.tools.get_quote") as mock_quote: + mock_web3.return_value = MagicMock() mock_validate.return_value = ("0x123", "ETH", "0x456", "DAI") mock_quote.return_value = None - + with pytest.raises(SwapNotPossibleError): - swap_coins("ETH", "DAI", 1, 1, "0x789") \ No newline at end of file + swap_coins("ETH", "DAI", 1, 1, "0x789") diff --git a/submodules/moragents_dockers/frontend/README.md b/submodules/moragents_dockers/frontend/README.md index 91eda58..606d7e3 100644 --- a/submodules/moragents_dockers/frontend/README.md +++ b/submodules/moragents_dockers/frontend/README.md @@ -10,7 +10,7 @@ This is a front-end for the Morpheus local install. It connects directly to agen ### Adding a new agent -Edit ```frontend/config.ts``` +Edit ```frontend/config.ts``` ## Usage @@ -29,9 +29,9 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the Your agent should expose the following endpoints: -### 1. Chat -This is the main endpoint for chatting with the agent. - +### 1. Chat +This is the main endpoint for chatting with the agent. + ```http://127.0.0.1:5000/``` The chat API accepts inputs in OpenAI chat completion format - see the example below: @@ -44,14 +44,14 @@ data = {'prompt':message,'chain_id':56} response = requests.post(url, json=data) ``` -The response will also be in this format. +The response will also be in this format. ```sh -response = {"role":"assistant","content":"To proceed with the swap, I need to know which crypto currency you want to +response = {"role":"assistant","content":"To proceed with the swap, I need to know which crypto currency you want to buy in exchange for 1 ETH. Could you please specify the target crypto currency?"} ``` -If the agent has enough information (buy token, sell token, amount) it will then look up the token addresses on the current chain. +If the agent has enough information (buy token, sell token, amount) it will then look up the token addresses on the current chain. If the token symbols are valid, it will then check the user has sufficient balance of the sell token. @@ -70,12 +70,12 @@ response = {"role": "swap", If the user wants to perform a swap based on the quote, the following steps are required: - 1) Check allowance + 1) Check allowance 2) If allowance < swap amount, send an approve transaction 3) If allowance >= swap amount, send a swap transaction -### 2. Check Allowance +### 2. Check Allowance ```http://127.0.0.1:5000/allowance``` @@ -109,7 +109,7 @@ url='http://127.0.0.1:5000/approve ``` -### 4. Generate Swap tx +### 4. Generate Swap tx ```http://127.0.0.1:5000/swap``` @@ -135,7 +135,7 @@ url='http://127.0.0.1:5000/swap ```http://127.0.0.1:5000/tx_status``` This endpoint is used to inform the back-end of the status of transactions that have been signed by the user's wallet on the front-end. - + Status values: * "initiated" = tx has been sent to the wallet @@ -199,5 +199,3 @@ To learn more about this stack, take a look at the following resources: - [RainbowKit Documentation](https://rainbowkit.com) - Learn how to customize your wallet connection flow. - [wagmi Documentation](https://wagmi.sh) - Learn how to interact with Ethereum. - [Next.js Documentation](https://nextjs.org/docs) - Learn how to build a Next.js application - - diff --git a/submodules/moragents_dockers/frontend/components/Avatar/index.tsx b/submodules/moragents_dockers/frontend/components/Avatar/index.tsx index b3acdf2..059da05 100644 --- a/submodules/moragents_dockers/frontend/components/Avatar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Avatar/index.tsx @@ -18,4 +18,4 @@ export const Avatar: FC = ({ isAgent, agentName }: AvatarProps) => return ( ); -} \ No newline at end of file +} diff --git a/submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx b/submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx index ffe6f14..6826cdd 100644 --- a/submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx +++ b/submodules/moragents_dockers/frontend/components/ClaimForm/ClaimForm.tsx @@ -54,4 +54,4 @@ const handleClaim = async () => { ); -}; \ No newline at end of file +}; diff --git a/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx b/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx index f0d88f7..5e9ecdb 100644 --- a/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx +++ b/submodules/moragents_dockers/frontend/components/ClaimMessage/ClaimMessage.tsx @@ -28,4 +28,4 @@ export const ClaimMessage: FC = ({ onSubmitClaim={onSubmitClaim} /> ); -}; \ No newline at end of file +}; diff --git a/submodules/moragents_dockers/frontend/components/CustomIcon/SendIcon.tsx b/submodules/moragents_dockers/frontend/components/CustomIcon/SendIcon.tsx index d74cc30..a981e83 100644 --- a/submodules/moragents_dockers/frontend/components/CustomIcon/SendIcon.tsx +++ b/submodules/moragents_dockers/frontend/components/CustomIcon/SendIcon.tsx @@ -5,4 +5,4 @@ export const SendIcon = (props: any) => ( -) \ No newline at end of file +) diff --git a/submodules/moragents_dockers/frontend/components/ErrorBackendModal/index.tsx b/submodules/moragents_dockers/frontend/components/ErrorBackendModal/index.tsx index 3b141e1..e80b9d0 100644 --- a/submodules/moragents_dockers/frontend/components/ErrorBackendModal/index.tsx +++ b/submodules/moragents_dockers/frontend/components/ErrorBackendModal/index.tsx @@ -76,4 +76,4 @@ export const ErrorBackendModal: FC = ({ show }) => { ); -} \ No newline at end of file +} diff --git a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx index 95cfa57..8d77c47 100644 --- a/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/LeftSidebar/index.tsx @@ -21,4 +21,4 @@ export const LeftSidebar: FC = () => { ); -} \ No newline at end of file +} diff --git a/submodules/moragents_dockers/frontend/components/Loader/styles.module.css b/submodules/moragents_dockers/frontend/components/Loader/styles.module.css index f8f1732..75315c6 100644 --- a/submodules/moragents_dockers/frontend/components/Loader/styles.module.css +++ b/submodules/moragents_dockers/frontend/components/Loader/styles.module.css @@ -26,4 +26,4 @@ 30% { transform: translateY(-15px); } -} \ No newline at end of file +} diff --git a/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx b/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx index 9e8c537..fcb3780 100644 --- a/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx +++ b/submodules/moragents_dockers/frontend/components/MessageItem/index.tsx @@ -97,4 +97,4 @@ export const MessageItem: FC = ({ {renderContent()} ); -}; \ No newline at end of file +}; diff --git a/submodules/moragents_dockers/frontend/components/MessageList/index.tsx b/submodules/moragents_dockers/frontend/components/MessageList/index.tsx index d09f87d..90e7a72 100644 --- a/submodules/moragents_dockers/frontend/components/MessageList/index.tsx +++ b/submodules/moragents_dockers/frontend/components/MessageList/index.tsx @@ -55,4 +55,4 @@ export const MessageList: FC = ({ ))} ); -}; \ No newline at end of file +}; diff --git a/submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx b/submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx index 70fcd72..eaa62d5 100644 --- a/submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx +++ b/submodules/moragents_dockers/frontend/components/SwapAgentModal/index.tsx @@ -26,4 +26,4 @@ export const SwapAgentModal: FC = ({ isOpen, onClose }) => ); -}; \ No newline at end of file +}; diff --git a/submodules/moragents_dockers/frontend/components/WalletRequiredModal/index.tsx b/submodules/moragents_dockers/frontend/components/WalletRequiredModal/index.tsx index 0b43bea..16f895a 100644 --- a/submodules/moragents_dockers/frontend/components/WalletRequiredModal/index.tsx +++ b/submodules/moragents_dockers/frontend/components/WalletRequiredModal/index.tsx @@ -76,4 +76,4 @@ export const WalletRequiredModal: FC = ({ agentRequiresWall ); -} \ No newline at end of file +} diff --git a/submodules/moragents_dockers/frontend/public/assets/ellipse.svg b/submodules/moragents_dockers/frontend/public/assets/ellipse.svg index 81a6f4b..9a223cd 100644 --- a/submodules/moragents_dockers/frontend/public/assets/ellipse.svg +++ b/submodules/moragents_dockers/frontend/public/assets/ellipse.svg @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/submodules/moragents_dockers/frontend/public/assets/me.svg b/submodules/moragents_dockers/frontend/public/assets/me.svg index b381288..1624a09 100644 --- a/submodules/moragents_dockers/frontend/public/assets/me.svg +++ b/submodules/moragents_dockers/frontend/public/assets/me.svg @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/submodules/moragents_dockers/frontend/public/assets/send.svg b/submodules/moragents_dockers/frontend/public/assets/send.svg index 45122ff..724d93e 100644 --- a/submodules/moragents_dockers/frontend/public/assets/send.svg +++ b/submodules/moragents_dockers/frontend/public/assets/send.svg @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/submodules/moragents_dockers/frontend/styles/globals.css b/submodules/moragents_dockers/frontend/styles/globals.css index d42b61a..be1c58c 100644 --- a/submodules/moragents_dockers/frontend/styles/globals.css +++ b/submodules/moragents_dockers/frontend/styles/globals.css @@ -1 +1 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); \ No newline at end of file +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); diff --git a/utils/docker_utils.py b/utils/docker_utils.py index 2edd323..d78920f 100644 --- a/utils/docker_utils.py +++ b/utils/docker_utils.py @@ -8,7 +8,7 @@ def find_unused_port() -> int: while True: port = random.randint(49152, 65535) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - res = sock.connect_ex(('localhost', port)) + res = sock.connect_ex(("localhost", port)) if res != 0: return port @@ -18,12 +18,21 @@ def build_image_if_not_present(image_name, dockerfile_path) -> None: context_dir = os.path.dirname(dockerfile_path) try: - subprocess.run(f"docker inspect {image_name}", shell=True, check=True, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + f"docker inspect {image_name}", + shell=True, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) print(f"Docker image '{image_name}' already exists.") except subprocess.CalledProcessError: print(f"Docker image '{image_name}' not found. Building the image...") - subprocess.run(f"docker build -t {image_name} -f {dockerfile_path} {context_dir}", shell=True, check=True) + subprocess.run( + f"docker build -t {image_name} -f {dockerfile_path} {context_dir}", + shell=True, + check=True, + ) print(f"Docker image '{image_name}' built successfully.") @@ -57,6 +66,8 @@ def launch_container(image_name, internal_port, dockerfile_path) -> int: docker_command = f"docker run -d -p {host_port}:{internal_port} {image_name}" subprocess.run(docker_command, shell=True, check=True) - print(f"Docker container of image {image_name} launched with port mapping: {host_port}:{internal_port}") + print( + f"Docker container of image {image_name} launched with port mapping: {host_port}:{internal_port}" + ) return host_port diff --git a/utils/host_utils.py b/utils/host_utils.py index 01d88aa..cc4a286 100644 --- a/utils/host_utils.py +++ b/utils/host_utils.py @@ -1,24 +1,24 @@ -import sys import platform +import sys def get_os_and_arch(): os_name = "Unknown" arch = "Unknown" - if sys.platform.startswith('darwin'): + if sys.platform.startswith("darwin"): os_name = "macOS" - elif sys.platform.startswith('win'): + elif sys.platform.startswith("win"): os_name = "Windows" - elif sys.platform.startswith('linux'): + elif sys.platform.startswith("linux"): os_name = "Linux" machine = platform.machine().lower() - if machine == 'x86_64' or machine == 'amd64': + if machine == "x86_64" or machine == "amd64": arch = "x86_64" - elif machine.startswith('arm') or machine.startswith('aarch'): + elif machine.startswith("arm") or machine.startswith("aarch"): arch = "ARM64" - elif machine == 'i386': + elif machine == "i386": arch = "x86" return os_name, arch diff --git a/utils/logger_config.py b/utils/logger_config.py index 8e0a049..d64946b 100644 --- a/utils/logger_config.py +++ b/utils/logger_config.py @@ -8,7 +8,7 @@ def setup_logger(name, level=logging.INFO): console_handler = logging.StreamHandler() console_handler.setLevel(level) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") console_handler.setFormatter(formatter) logger.addHandler(console_handler) diff --git a/wizard_windows.iss b/wizard_windows.iss index 0fdcf7b..1f417b8 100644 --- a/wizard_windows.iss +++ b/wizard_windows.iss @@ -98,7 +98,7 @@ end; function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; - + { Skip EULA page if already accepted } if (PageID = EULAPage.ID) and EULAAccepted then Result := True;