From 573a4686732657bb5baa0916010eefa5e9e9e40f Mon Sep 17 00:00:00 2001 From: yasinfakhar Date: Wed, 5 Mar 2025 17:48:37 +0330 Subject: [PATCH 1/5] fix[api]: changing config to env --- api/.env.sample | 43 ++++++++++- api/src/agentflow/agents/hub.py | 6 +- .../agentflow/providers/ether_scan_tools.py | 15 ++-- api/src/agentflow/providers/goldrush_tools.py | 16 ++-- api/src/agentflow/providers/moralis_tools.py | 5 +- api/src/util/configuration.py | 73 ++++++++++++++----- 6 files changed, 119 insertions(+), 39 deletions(-) diff --git a/api/.env.sample b/api/.env.sample index 2220a2e..6d45ebb 100644 --- a/api/.env.sample +++ b/api/.env.sample @@ -20,4 +20,45 @@ log_max_bytes=5242880 # 5 * 1024 * 1024 #------------------------ # SENTRY (Optional) #------------------------ -SENTRY_DSN= \ No newline at end of file +SENTRY_DSN= + + +#------------------------ +# LLM +#------------------------ + +# +-------------+------------------------------------------------+ +# | Provider | Recommended Model | +# +-------------+------------------------------------------------+ +# | openai | gpt-4o-mini | +# | google | gemini-2.0-flash | +# | together | deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free | +# | ollama | llama3.3 | +# | groq | llama-3.3-70b-versatile | +# | firework | accounts/fireworks/models/firefunction-v2 | +# | huggingface | meta-llama/Llama-3.3-70B-Instruct | +# +-------------+------------------------------------------------+ +LLM_PROVIDER=openai +LLM_MODEL=gpt-4o-mini +LLM_API_KEY= +LLM_HOST= +LLM_MODELS_PATH= + + +#------------------------ +# SERVICES +#------------------------ +ETHER_SCAN_API_KEY= +GOLDRUSH_API_KEY= +MORALIS_API_KEY= +EXA_API_KEY= +PERPLEXITY_API_KEY= +TAVILY_API_KEY= + +ETH_RPC_URL= + + +#------------------------ +# Agents +#------------------------ +AGENTS=ETHER_SCAN,GOLDRUSH,MORALIS \ No newline at end of file diff --git a/api/src/agentflow/agents/hub.py b/api/src/agentflow/agents/hub.py index 464c5a4..73ffb9c 100644 --- a/api/src/agentflow/agents/hub.py +++ b/api/src/agentflow/agents/hub.py @@ -16,9 +16,9 @@ class AgentHub: def __init__(self): self.agents = { - "etherscan": ether_scan_agent.etherscan_agent, - "goldrush": goldrush_agent.goldrush_agent, - "moralis": moralis_agent.moralis_agent + "ETHER_SCAN": ether_scan_agent.etherscan_agent, + "GOLDRUSH": goldrush_agent.goldrush_agent, + "MORALIS": moralis_agent.moralis_agent } def get_agents(self, agent_names: List[str]) -> List[Any]: diff --git a/api/src/agentflow/providers/ether_scan_tools.py b/api/src/agentflow/providers/ether_scan_tools.py index 1d653dc..551b0b1 100644 --- a/api/src/agentflow/providers/ether_scan_tools.py +++ b/api/src/agentflow/providers/ether_scan_tools.py @@ -13,9 +13,10 @@ _config = Config.get_config() -_eth_rpc_config = Config.get_service_config(_config, "eth_rpc") -_ether_scan_config = Config.get_service_config(_config, "etherscan") +_ether_scan_config = Config.get_service_config(_config, "ETHER_SCAN") +_ETHERSCAN_URL = "https://api.etherscan.io/v2/api" +_ETH_RPC = "https://eth-pokt.nodies.app" @handle_exceptions def fetch_contract_abi(contract_address: str, api_key: str) -> Dict: @@ -29,7 +30,7 @@ def fetch_contract_abi(contract_address: str, api_key: str) -> Dict: Returns: Dict: A dictionary representing the contract ABI. """ - url = _ether_scan_config["url"] + url = _ETHERSCAN_URL params = { "chainid": "1", "module": "contract", @@ -58,7 +59,7 @@ def fetch_contract_source_code(contract_address: str, api_key: str) -> str: Returns: str: The contract source code. """ - url = _ether_scan_config["url"] + url = _ETHERSCAN_URL params = { "chainid": "1", "module": "contract", @@ -100,7 +101,7 @@ def timestamp_to_block_number(timestamp: int, api_key: str) -> int: Returns: int: The closest block number. """ - url = _ether_scan_config["url"] + url = _ETHERSCAN_URL params = { "chainid": "1", "module": "block", @@ -229,7 +230,7 @@ def get_contract_events( api_key = _ether_scan_config["api_key"] abi = fetch_contract_abi(contract_address, api_key) - web3 = Web3(Web3.HTTPProvider(_eth_rpc_config["url"])) + web3 = Web3(Web3.HTTPProvider(_ETH_RPC)) contract = web3.eth.contract(address=contract_address, abi=abi) # Resolve the actual event name case-insensitively @@ -262,7 +263,7 @@ def get_latest_eth_block_number() -> int: Returns: int: The current block number on the Ethereum mainnet. """ - web3 = Web3(Web3.HTTPProvider(_eth_rpc_config["url"])) + web3 = Web3(Web3.HTTPProvider(_ETH_RPC)) return web3.eth.block_number diff --git a/api/src/agentflow/providers/goldrush_tools.py b/api/src/agentflow/providers/goldrush_tools.py index c992566..694410b 100644 --- a/api/src/agentflow/providers/goldrush_tools.py +++ b/api/src/agentflow/providers/goldrush_tools.py @@ -9,7 +9,9 @@ _config = Config.get_config() -_goldrush_config = Config.get_service_config(_config, "goldrush") +_goldrush_config = Config.get_service_config(_config, "GOLDRUSH") + +_GOLDRUSH_URL = "https://api.covalenthq.com" def _call_goldrush_api(url: str, params: Optional[Dict[str, Any]] = None) -> Dict: @@ -49,7 +51,7 @@ def get_wallet_activity(wallet_address: str, output_include: list[str]) -> List[ - name, chain_id, is_testnet, db_schema_name, label, category_label, logo_url, black_logo_url, white_logo_url, color_theme, is_appchain, appchain_of, last_seen_at """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL url = f"{base_url}/v1/address/{wallet_address}/activity/" data = _call_goldrush_api(url) results = data["data"]["items"] @@ -82,7 +84,7 @@ def get_balance_for_address(wallet_address: str, output_include: list[str]) -> s supports_erc, logo_url, logo_urls, last_transferred_at, native_token, type, is_spam, balance, balance_24h, quote_rate, quote_rate_24h, quote, quote_24h, pretty_quote, pretty_quote_24h, protocol_metadata. """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL chain_name = "eth-mainnet" url = f"{base_url}/v1/{chain_name}/address/{wallet_address}/balances_v2/" data = _call_goldrush_api(url) @@ -117,7 +119,7 @@ def get_wallet_transactions(wallet_address: str, output_include: List[str], page gas_offered, gas_spent, gas_price, fees_paid, gas_quote, pretty_gas_quote, gas_quote_rate, explorers, log_events """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL chain_name = "eth-mainnet" url = f"{base_url}/v1/{chain_name}/address/{wallet_address}/transactions_v3/page/{page}/" data = _call_goldrush_api(url) @@ -142,7 +144,7 @@ def get_transactions_summary(wallet_address: str) -> Dict: Returns: Dict: The transactions summary data. """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL chain_name = "eth-mainnet" url = f"{base_url}/v1/{chain_name}/address/{wallet_address}/transactions_summary/" data = _call_goldrush_api(url) @@ -170,7 +172,7 @@ def get_transaction_detail(tx_hash: str, output_include: List[str]) -> List[Dict gas_offered, gas_spent, gas_price, fees_paid, gas_quote, pretty_gas_quote, gas_quote_rate, explorers, log_events, internal_transfers, state_changes, input_data """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL chain_name = "eth-mainnet" url = f"{base_url}/v1/{chain_name}/transaction_v2/{tx_hash}/" data = _call_goldrush_api(url) @@ -203,7 +205,7 @@ def get_token_approvals(wallet_address: str, output_include: List[str]) -> Dict: quote_rate, balance, balance_quote, pretty_balance_quote, value_at_risk, value_at_risk_quote, pretty_value_at_risk_quote, spenders """ - base_url = _goldrush_config["url"] + base_url = _GOLDRUSH_URL chain_name = "eth-mainnet" url = f"{base_url}/v1/{chain_name}/approvals/{wallet_address}/" data = _call_goldrush_api(url) diff --git a/api/src/agentflow/providers/moralis_tools.py b/api/src/agentflow/providers/moralis_tools.py index c072887..84c5fc2 100644 --- a/api/src/agentflow/providers/moralis_tools.py +++ b/api/src/agentflow/providers/moralis_tools.py @@ -8,8 +8,9 @@ from src.agentflow.utils.shared_tools import handle_exceptions _config = Config.get_config() -_moralis_config = Config.get_service_config(_config, "moralis") +_moralis_config = Config.get_service_config(_config, "MORALIS") +_MORALIS_URL = "https://deep-index.moralis.io/api/v2" @tool @handle_exceptions @@ -226,7 +227,7 @@ def get_token_approvals(wallet_address: str, output_include: list[str], cursor: - block_number, block_timestamp, transaction_hash, value, value_formatted, token, spender """ - base_url = _moralis_config["url"] + base_url = _MORALIS_URL api_url = f"{base_url}/wallets/{wallet_address}/approvals" params = {'chain': 'eth'} diff --git a/api/src/util/configuration.py b/api/src/util/configuration.py index da974c6..4f69988 100644 --- a/api/src/util/configuration.py +++ b/api/src/util/configuration.py @@ -4,9 +4,10 @@ from src.util.singleton import Singleton from src.util.execptions import * + class Config(metaclass=Singleton): def __init__(self) -> None: - self._config = self._parse_config("config.json") + self._config = self._parse_config() self._check_config(self._config) @classmethod @@ -15,21 +16,63 @@ def get_config(cls) -> dict: instance = cls() # calls the Singleton metaclass, so it's still a singleton return instance._config - def _parse_config(self, file_path: str) -> dict: + def _parse_config(self) -> dict: """ Parse a JSON configuration file and returns its contents as a dictionary. - Args: - file_path (str): The path to the JSON configuration file. - Returns: dict: The contents of the JSON configuration file. """ - if not os.path.exists(file_path): - raise ConfigNotFoundError() - with open(file_path, 'r') as file: - data = json.load(file) - return data + config = {} + + # load agents + if os.environ["AGENTS"]: + agents = os.environ["AGENTS"].split(",") + config["agents"] = agents + + for agent in agents: + if not os.environ[f"{agent}_API_KEY"]: + raise Exception(f"{agent}_API_KEY not found") + + # load llm + if os.environ["LLM_PROVIDER"] or os.environ["LLM_MODEL"]: + config["llm"] = { + "provider": os.environ["LLM_PROVIDER"], + "model": os.environ["LLM_MODEL"], + "api_key": os.environ["LLM_API_KEY"], + "extra_params": { + "host": os.environ["LLM_HOST"], + "models_path": os.environ["LLM_MODELS_PATH"] + } + } + else: + raise Exception( + "LLM_PROVIDER or LLM_MODEL not set in .env") + + # load services + config["services"] = [] + if os.environ["ETHER_SCAN_API_KEY"]: + config["services"].append( + { + "name": "ETHER_SCAN", + "api_key": os.environ["ETHER_SCAN_API_KEY"] + }) + + if os.environ["GOLDRUSH_API_KEY"]: + config["services"].append( + { + "name": "GOLDRUSH", + "api_key": os.environ["GOLDRUSH_API_KEY"] + }) + + if os.environ["MORALIS_API_KEY"]: + config["services"].append( + { + "name": "MORALIS", + "api_key": os.environ["MORALIS_API_KEY"] + }) + + return config def _check_config(self, config: dict): """ @@ -43,20 +86,12 @@ def _check_config(self, config: dict): if key not in required_keys: raise ConfigKeyError(key) if config["llm"]["provider"] == "ollama": - # check if ollama is seted the host and models in extra_params should be seted + # check if ollama is seted the host and models in extra_params should be set if "host" not in config["llm"]["extra_params"]: raise ConfigKeyError("extra_params | host") if "models" not in config["llm"]["extra_params"]: raise ConfigKeyError("extra_params | models") - for service in config["services"]: - if "name" not in service: - raise ConfigKeyError("services | name") - if "url" not in service: - raise ConfigKeyError("services | url") - if "api_key" not in service: - raise ConfigKeyError("services | api_key") - if not config["agents"]: raise ItemNotFoundError("agents should not be empty") From 4656f65fed3843be2690333387fc5e21e4283ea4 Mon Sep 17 00:00:00 2001 From: yasinfakhar Date: Wed, 5 Mar 2025 17:56:42 +0330 Subject: [PATCH 2/5] fix[api]: readme updated --- api/README.md | 44 +-------------- api/config.json.sample | 53 ------------------- .../agentflow/providers/ether_scan_tools.py | 2 +- 3 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 api/config.json.sample diff --git a/api/README.md b/api/README.md index 2787fc2..bdfb66e 100644 --- a/api/README.md +++ b/api/README.md @@ -12,53 +12,13 @@ cp config.json.example config.json pip install -r requirements.txt ``` -Note: Fill up the .env and config.json file according to your config +Note: Fill up the `.env` file according to your config -### Configuration -Configuration file is in `config.json`. Edit it according to your needs. Notes: - Add your own tool services or use existing ones -- Add provided agents. (on top of each service tool is an agent so the name of agent should be same as service name) +- Add provided agents (currently : ``ETHER_SCAN, MORALIS, GOLDRUSH``) - Choose the llm provider - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Supported ProviderRecommended Model
openaigpt-4o-mini
googlegemini-2.0-flash
togetherdeepseek-ai/DeepSeek-R1-Distill-Llama-70B-free
ollamallama3.3
groqllama-3.3-70b-versatile
fireworkaccounts/fireworks/models/firefunction-v2
huggingfacemeta-llama/Llama-3.3-70B-Instruct
### Running the Application diff --git a/api/config.json.sample b/api/config.json.sample deleted file mode 100644 index f678586..0000000 --- a/api/config.json.sample +++ /dev/null @@ -1,53 +0,0 @@ -{ - "services": [ - { - "name": "etherscan", - "url": "https://api.etherscan.io/v2/api", - "api_key": "" - }, - { - "name": "goldrush", - "url": "https://api.covalenthq.com", - "api_key": "" - }, - { - "name": "moralis", - "url": "https://deep-index.moralis.io/api/v2", - "api_key": "" - }, - { - "name": "eth_rpc", - "url": "https://eth.llamarpc.com", - "api_key": "" - }, - { - "name": "exa", - "url": "https://api.exa.ai", - "api_key": "" - }, - { - "name": "perplexity", - "url": "https://api.perplexity.ai", - "api_key": "" - }, - { - "name": "tavily", - "url": "https://api.tavily.com", - "api_key": "" - }, - ], - "llm": { - "provider": "openai", - "model": "gpt-4o-mini", - "api_key": "", - "extra_params": { - "host": "", - "models": "" - } - }, - "agents": [ - "etherscan", - "goldrush", - "moralis" - ] -} \ No newline at end of file diff --git a/api/src/agentflow/providers/ether_scan_tools.py b/api/src/agentflow/providers/ether_scan_tools.py index 551b0b1..605085d 100644 --- a/api/src/agentflow/providers/ether_scan_tools.py +++ b/api/src/agentflow/providers/ether_scan_tools.py @@ -16,7 +16,7 @@ _ether_scan_config = Config.get_service_config(_config, "ETHER_SCAN") _ETHERSCAN_URL = "https://api.etherscan.io/v2/api" -_ETH_RPC = "https://eth-pokt.nodies.app" +_ETH_RPC = os.environ["ETH_RPC"] @handle_exceptions def fetch_contract_abi(contract_address: str, api_key: str) -> Dict: From 0866a40f696f881893a3f675fb0d6975dae57099 Mon Sep 17 00:00:00 2001 From: yasinfakhar Date: Wed, 5 Mar 2025 18:00:14 +0330 Subject: [PATCH 3/5] chore[api]: readme updated --- api/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/api/README.md b/api/README.md index bdfb66e..34eaeb7 100644 --- a/api/README.md +++ b/api/README.md @@ -8,7 +8,6 @@ To install the required dependencies, run: ```sh cp .env.example .env -cp config.json.example config.json pip install -r requirements.txt ``` From ca121619e80cfc0bcf408879ba403d37bc7a12b5 Mon Sep 17 00:00:00 2001 From: yasinfakhar Date: Thu, 6 Mar 2025 10:31:49 +0330 Subject: [PATCH 4/5] fix[api]: updating github action --- .github/workflows/dev.yml | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index dbdc63f..88b5569 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -62,7 +62,6 @@ jobs: --name pattern-core-api \ --network pattern_core_network \ -p 5001:8000 \ - -e OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} \ -e POSTGRES_HOST=api-postgres-1 \ -e POSTGRES_PORT=5432 \ -e POSTGRES_DB=pattern-core \ @@ -71,17 +70,15 @@ jobs: -e JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} \ -e QDRANT_HOST=http://qdrant:6333 \ -e QDRANT_COLLECTION=pattern-core \ - -e LANGCHAIN_API_KEY=${{ secrets.LANGCHAIN_API_KEY }} \ - -e GOOGLE_SEARCH_URL=https://google.serper.dev/search \ - -e REDDIT_SEARCH_URL=https://reddit-scraper2.p.rapidapi.com/search_posts \ - -e LINKEDIN_SEARCH_URL=https://linkedin-data-api.p.rapidapi.com/search-posts \ - -e WEATHER_URL=https://api.weatherapi.com/v1/current.json \ - -e ETH_RPC=https://ethereum-rpc.publicnode.com \ - -e GOLDRUSH_URL=https://api.covalenthq.com \ - -e EXA_URL=https://api.exa.ai \ - -e PERPLEXITY_URL=https://api.perplexity.ai \ - -e TAVILY_URL=https://api.tavily.com \ - -e SECRET_KEY=${{ secrets.SECRET_KEY }} \ + -e MORALIS_API_KEY=${{ secrets.MORALIS_API_KEY }} \ + -e ETHER_SCAN_API_KEY=${{ secrets.ETHER_SCAN_API_KEY }} \ + -e GOLDRUSH_API_KEY=${{ secrets.GOLDRUSH_API_KEY }} \ + -e ETH_RPC_URL=${{ secrets.ETH_RPC_URL }} \ + -e LLM_PROVIDER=${{ secrets.LLM_PROVIDER }} \ + -e LLM_MODEL=${{ secrets.LLM_MODEL }} \ + -e LLM_API_KEY=${{ secrets.LLM_API_KEY }} \ + -e AGENTS=${{ secrets.AGENTS }} \ + -e SENTRY_DSN=${{ secrets.SENTRY_DSN }} \ patterntechnology/pattern-core-api:latest # Wait a few seconds to give the container time to start From 026593f89524ec8ee60eb1b42b7f03873b886536 Mon Sep 17 00:00:00 2001 From: yasinfakhar Date: Thu, 6 Mar 2025 10:33:16 +0330 Subject: [PATCH 5/5] chore[api]: fixing typo --- .github/workflows/dev.yml | 2 +- api/.env.sample | 2 +- api/src/main.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 88b5569..0c2b8d9 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -78,7 +78,7 @@ jobs: -e LLM_MODEL=${{ secrets.LLM_MODEL }} \ -e LLM_API_KEY=${{ secrets.LLM_API_KEY }} \ -e AGENTS=${{ secrets.AGENTS }} \ - -e SENTRY_DSN=${{ secrets.SENTRY_DSN }} \ + -e SENTRY_DNS=${{ secrets.SENTRY_DNS }} \ patterntechnology/pattern-core-api:latest # Wait a few seconds to give the container time to start diff --git a/api/.env.sample b/api/.env.sample index 6d45ebb..814d61f 100644 --- a/api/.env.sample +++ b/api/.env.sample @@ -20,7 +20,7 @@ log_max_bytes=5242880 # 5 * 1024 * 1024 #------------------------ # SENTRY (Optional) #------------------------ -SENTRY_DSN= +SENTRY_DNS= #------------------------ diff --git a/api/src/main.py b/api/src/main.py index b3d7387..2408703 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -16,10 +16,10 @@ load_dotenv() _logger = Logging().get_logger() -if os.environ.get("SENTRY_DSN"): +if os.environ.get("SENTRY_DNS"): # Initialize Sentry with your DSN sentry_sdk.init( - dsn=os.environ.get("SENTRY_DSN"), + dsn=os.environ.get("SENTRY_DNS"), # Add data like request headers and IP for users, # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info send_default_pii=True,