From c355ea13408f075edb0c036319c19b18a172a031 Mon Sep 17 00:00:00 2001 From: yudhissatriya <56595821+yudhissatriya@users.noreply.github.com> Date: Fri, 2 May 2025 15:32:42 +0700 Subject: [PATCH 1/5] Create base.py CoinGeckoBaseTool inherits from IntentKitSkill and sets the category to coingecko. Attributes like name, description, and args_schema are populated by specific skills. skill_store is available for data persistence (unused in this skill). --- skills/coingecko/base.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 skills/coingecko/base.py diff --git a/skills/coingecko/base.py b/skills/coingecko/base.py new file mode 100644 index 00000000..10687d0b --- /dev/null +++ b/skills/coingecko/base.py @@ -0,0 +1,17 @@ +from typing import Type +from pydantic import BaseModel, Field +from abstracts.skill import SkillStoreABC +from skills.base import IntentKitSkill + + +class CoinGeckoBaseTool(IntentKitSkill): + """Base class for CoinGecko-related tools.""" + + name: str = Field(description="Name of the CoinGecko tool") + description: str = Field(description="Description of the CoinGecko tool's function") + args_schema: Type[BaseModel] + skill_store: SkillStoreABC = Field(description="Skill data storage") + + @property + def category(self) -> str: + return "coingecko" From a49b98143f6641061a8147944daaf91d83168f99 Mon Sep 17 00:00:00 2001 From: yudhissatriya <56595821+yudhissatriya@users.noreply.github.com> Date: Fri, 2 May 2025 15:35:01 +0700 Subject: [PATCH 2/5] Create crypto_price_checker.py Dependency: Uses httpx for HTTP requests. PriceCheckerInput: Defines inputs: coin_id (e.g., "bitcoin") and vs_currency (e.g., "usd", default). CryptoPriceChecker: name and description: Guides the LLM on when to use this skill. args_schema: Links to PriceCheckerInput. _arun: Main logic: Sends a GET request to https://api.coingecko.com/api/v3/simple/price with parameters for price, market cap, volume, and price change. Returns formatted data (e.g., price, market cap, etc.). Handles errors like coin not found (404) or rate limit (429). --- skills/coingecko/crypto_price_checker.py | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 skills/coingecko/crypto_price_checker.py diff --git a/skills/coingecko/crypto_price_checker.py b/skills/coingecko/crypto_price_checker.py new file mode 100644 index 00000000..7e9fe975 --- /dev/null +++ b/skills/coingecko/crypto_price_checker.py @@ -0,0 +1,72 @@ +import httpx +from typing import Type +from pydantic import BaseModel, Field +from skills.coingecko.base import CoinGeckoBaseTool + + +class PriceCheckerInput(BaseModel): + """Input for the CryptoPriceChecker skill.""" + coin_id: str = Field(description="ID of the cryptocurrency (e.g., bitcoin, ethereum)") + vs_currency: str = Field(description="Comparison currency (e.g., usd, idr)", default="usd") + + +class CryptoPriceChecker(CoinGeckoBaseTool): + """Fetches current price, market cap, and other data for a cryptocurrency from CoinGecko API.""" + + name: str = "crypto_price_checker" + description: str = ( + "Fetches the current price, market capitalization, 24-hour trading volume, and 24-hour price change " + "for a specific cryptocurrency using the CoinGecko API. " + "Use this skill when a user requests crypto price data, e.g., 'What is the price of Bitcoin?' " + "or 'What is Ethereum's market cap in IDR?'." + ) + args_schema: Type[BaseModel] = PriceCheckerInput + + async def _arun(self, coin_id: str, vs_currency: str = "usd", **kwargs) -> str: + """Fetches crypto price data from CoinGecko API.""" + base_url = "https://api.coingecko.com/api/v3/simple/price" + params = { + "ids": coin_id, + "vs_currencies": vs_currency, + "include_market_cap": "true", + "include_24hr_vol": "true", + "include_24hr_change": "true", + "include_last_updated_at": "true", + } + + try: + async with httpx.AsyncClient() as client: + response = await client.get(base_url, params=params) + response.raise_for_status() # Raise error if status code is not 200 + data = response.json() + + # Check if coin is found + if coin_id not in data: + return f"Error: Coin '{coin_id}' not found. Try using IDs like 'bitcoin' or 'ethereum'." + + # Extract data + coin_data = data[coin_id] + price = coin_data.get(vs_currency, "Not available") + market_cap = coin_data.get(f"{vs_currency}_market_cap", "Not available") + volume_24h = coin_data.get(f"{vs_currency}_24h_vol", "Not available") + change_24h = coin_data.get(f"{vs_currency}_24h_change", "Not available") + last_updated = coin_data.get("last_updated_at", "Not available") + + # Format output + output = ( + f"Data for {coin_id.upper()} ({vs_currency.upper()}):\n" + f"- Price: {price}\n" + f"- Market Cap: {market_cap}\n" + f"- 24h Volume: {volume_24h}\n" + f"- 24h Price Change: {change_24h}%\n" + f"- Last Updated: {last_updated} (timestamp)" + ) + return output + except httpx.HTTPStatusError as e: + if e.response.status_code == 404: + return f"Error: Coin '{coin_id}' not found on CoinGecko." + elif e.response.status_code == 429: + return "Error: CoinGecko API rate limit reached. Try again later." + return f"Error: Failed to fetch data. Status: {e.response.status_code}" + except Exception as e: + return f"Error: An issue occurred while fetching data: {str(e)}" From 817efdf30ef1dba80e1ea601d05d22005e72e386 Mon Sep 17 00:00:00 2001 From: yudhissatriya <56595821+yudhissatriya@users.noreply.github.com> Date: Fri, 2 May 2025 15:36:08 +0700 Subject: [PATCH 3/5] Create __init__.py SkillStates: Defines available skills (crypto_price_checker). Config: Requires only states (no API key needed). get_skills: Checks active skills based on status. get_coingecko_skill: Instantiates CryptoPriceChecker and caches it. --- skills/coingecko/__init__.py | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 skills/coingecko/__init__.py diff --git a/skills/coingecko/__init__.py b/skills/coingecko/__init__.py new file mode 100644 index 00000000..8870fbc0 --- /dev/null +++ b/skills/coingecko/__init__.py @@ -0,0 +1,50 @@ +from typing import TypedDict, List +from abstracts.skill import SkillStoreABC +from skills.base import SkillConfig, SkillState +from skills.coingecko.base import CoinGeckoBaseTool +from skills.coingecko.crypto_price_checker import CryptoPriceChecker + + +# Cache to store skill instances (skills are stateless) +_cache: dict[str, CoinGeckoBaseTool] = {} + + +class SkillStates(TypedDict): + crypto_price_checker: SkillState + + +class Config(SkillConfig): + """Configuration for CoinGecko skills.""" + states: SkillStates + + +async def get_skills( + config: Config, + is_private: bool, + store: SkillStoreABC, + **_, +) -> List[CoinGeckoBaseTool]: + """Retrieve all allowed CoinGecko skills based on configuration.""" + available_skills = [] + + # Check allowed skills based on status (public/private/disabled) + for skill_name, state in config["states"].items(): + if state == "disabled": + continue + elif state == "public" or (state == "private" and is_private): + available_skills.append(skill_name) + + return [get_coingecko_skill(name, store) for name in available_skills] + + +def get_coingecko_skill( + name: str, + store: SkillStoreABC, +) -> CoinGeckoBaseTool: + """Retrieve a CoinGecko skill by name.""" + if name == "crypto_price_checker": + if name not in _cache: + _cache[name] = CryptoPriceChecker(skill_store=store) + return _cache[name] + else: + raise ValueError(f"Unknown CoinGecko skill: {name}") From 2d89ce7750544d0770c837256268edf76f802a2f Mon Sep 17 00:00:00 2001 From: yudhissatriya <56595821+yudhissatriya@users.noreply.github.com> Date: Fri, 2 May 2025 15:38:02 +0700 Subject: [PATCH 4/5] Create schema.json Specifies that the coingecko category configuration requires states. Defines the status for crypto_price_checker (disabled, public, or private). --- skills/coingecko/schema.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 skills/coingecko/schema.json diff --git a/skills/coingecko/schema.json b/skills/coingecko/schema.json new file mode 100644 index 00000000..5c0938d1 --- /dev/null +++ b/skills/coingecko/schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "CoinGecko Skills", + "description": "Configuration for CoinGecko skills", + "properties": { + "states": { + "type": "object", + "properties": { + "crypto_price_checker": { + "type": "string", + "title": "Crypto Price Checker", + "enum": ["disabled", "public", "private"], + "description": "Status of the crypto_price_checker skill (disabled, public, or private)" + } + }, + "description": "Status for each CoinGecko skill (disabled, public, or private)" + } + }, + "required": ["states"], + "additionalProperties": true +} From e8bfcf0524620d66ee1540dbc938f3e5dbf0b1fe Mon Sep 17 00:00:00 2001 From: yudhissatriya <56595821+yudhissatriya@users.noreply.github.com> Date: Fri, 2 May 2025 16:06:01 +0700 Subject: [PATCH 5/5] Create agent.yaml Activates the crypto_price_checker skill with public status. No API key is required for the /simple/price endpoint. --- agent.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 agent.yaml diff --git a/agent.yaml b/agent.yaml new file mode 100644 index 00000000..5a8a5d39 --- /dev/null +++ b/agent.yaml @@ -0,0 +1,5 @@ +id: coingecko-agent +skills: + coingecko: + states: + crypto_price_checker: public