From e5c84eca7594c0ab0e17730abece7a0bf84eee96 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:58:31 +0100 Subject: [PATCH] feat: Add Python implementation of Allora plugin (#217) * feat: Add Python implementation of Allora plugin - Create Python version of Allora plugin with same functionality as TypeScript version - Implement price prediction functionality for BTC and ETH - Support 5m and 8h timeframes - Match CoinGecko plugin structure - Add Poetry build configuration Co-Authored-By: Agus Armellini Fischer * chore: add changeset for allora python plugin Co-Authored-By: Agus Armellini Fischer * Delete .changeset/allora-python-plugin.md --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Agus Armellini Fischer Co-authored-by: Agus --- .../allora/goat_plugins/allora/__init__.py | 23 +++++++++ .../allora/goat_plugins/allora/parameters.py | 21 ++++++++ .../allora/goat_plugins/allora/service.py | 49 +++++++++++++++++++ python/src/plugins/allora/pyproject.toml | 43 ++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 python/src/plugins/allora/goat_plugins/allora/__init__.py create mode 100644 python/src/plugins/allora/goat_plugins/allora/parameters.py create mode 100644 python/src/plugins/allora/goat_plugins/allora/service.py create mode 100644 python/src/plugins/allora/pyproject.toml diff --git a/python/src/plugins/allora/goat_plugins/allora/__init__.py b/python/src/plugins/allora/goat_plugins/allora/__init__.py new file mode 100644 index 000000000..c35c2239d --- /dev/null +++ b/python/src/plugins/allora/goat_plugins/allora/__init__.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from typing import Optional + +from goat.classes.plugin_base import PluginBase +from .service import AlloraService + + +@dataclass +class AlloraPluginOptions: + api_key: Optional[str] = None + api_root: str = "https://api.upshot.xyz/v2/allora" + + +class AlloraPlugin(PluginBase): + def __init__(self, options: AlloraPluginOptions): + super().__init__("allora", [AlloraService(options.api_key, options.api_root)]) + + def supports_chain(self, chain) -> bool: + return True + + +def allora(options: AlloraPluginOptions) -> AlloraPlugin: + return AlloraPlugin(options) diff --git a/python/src/plugins/allora/goat_plugins/allora/parameters.py b/python/src/plugins/allora/goat_plugins/allora/parameters.py new file mode 100644 index 000000000..851930a0a --- /dev/null +++ b/python/src/plugins/allora/goat_plugins/allora/parameters.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, Field +from enum import Enum + + +class AlloraPricePredictionToken(str, Enum): + BTC = "BTC" + ETH = "ETH" + + +class AlloraPricePredictionTimeframe(str, Enum): + FIVE_MIN = "5m" + EIGHT_HOUR = "8h" + + +class GetAlloraPricePredictionParameters(BaseModel): + ticker: AlloraPricePredictionToken = Field( + description="The ticker of the currency for which to fetch a price prediction (BTC or ETH)." + ) + timeframe: AlloraPricePredictionTimeframe = Field( + description='The timeframe for the prediction (currently, either "5m" or "8h").' + ) diff --git a/python/src/plugins/allora/goat_plugins/allora/service.py b/python/src/plugins/allora/goat_plugins/allora/service.py new file mode 100644 index 000000000..4b5793eb2 --- /dev/null +++ b/python/src/plugins/allora/goat_plugins/allora/service.py @@ -0,0 +1,49 @@ +import aiohttp +from typing import Optional +from goat.decorators.tool import Tool +from .parameters import GetAlloraPricePredictionParameters, AlloraPricePredictionToken, AlloraPricePredictionTimeframe + + +class AlloraService: + def __init__(self, api_key: Optional[str] = None, api_root: str = "https://api.upshot.xyz/v2/allora"): + self.api_key = api_key + self.api_root = api_root.rstrip('/') # Remove trailing slash if present + + @Tool({ + "description": "Fetch a future price prediction for BTC or ETH for a given timeframe (5m or 8h)", + "parameters_schema": GetAlloraPricePredictionParameters + }) + async def get_price_prediction(self, parameters: dict): + """Fetch a future price prediction for a crypto asset from Allora Network""" + # Default to ethereum-11155111 (Sepolia) as in TypeScript version + signature_format = "ethereum-11155111" + + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } + if self.api_key: + headers["x-api-key"] = self.api_key + + # Extract parameters + ticker = parameters["ticker"] + timeframe = parameters["timeframe"] + + # Construct URL following TypeScript pattern + url = f"{self.api_root}/consumer/price/{signature_format}/{ticker}/{timeframe}" + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + if not response.ok: + raise Exception( + f"Allora plugin: error requesting price prediction: url={url} " + f"status={response.status} body={await response.text()}" + ) + + data = await response.json() + + # Validate response structure + if not data.get("data", {}).get("inference_data"): + raise Exception(f"API response missing data: {data}") + + return data["data"]["inference_data"] diff --git a/python/src/plugins/allora/pyproject.toml b/python/src/plugins/allora/pyproject.toml new file mode 100644 index 000000000..7bfe4e091 --- /dev/null +++ b/python/src/plugins/allora/pyproject.toml @@ -0,0 +1,43 @@ +[tool.poetry] +name = "goat-sdk-plugin-allora" +version = "0.1.0" +description = "Goat plugin for Allora Network price predictions" +authors = ["GOAT SDK Team"] +readme = "README.md" +keywords = ["goat", "sdk", "web3", "agents", "ai", "allora", "prediction"] +homepage = "https://ohmygoat.dev/" +repository = "https://github.com/goat-sdk/goat" +packages = [ + { include = "goat_plugins/allora" }, +] + +[tool.poetry.dependencies] +python = "^3.10" +aiohttp = "^3.8.6" +goat-sdk = "^0.1.1" + +[tool.poetry.group.test.dependencies] +pytest = "^8.3.4" +pytest-asyncio = "^0.25.0" + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/goat-sdk/goat/issues" + +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib", +] +pythonpath = "src" +asyncio_default_fixture_loop_scope = "function" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.8.6" +goat-sdk = { path = "../../goat-sdk", develop = true } + +[tool.ruff] +line-length = 120 +target-version = "py312"