diff --git a/.env.example b/.env.example index b565b16c..ad33a754 100644 --- a/.env.example +++ b/.env.example @@ -3,25 +3,25 @@ DB_CONNECTION=postgresql+psycopg://postgres:password@vector_db:5432/openagent # LLM provider settings (at least one required) # To get a Google Cloud Vertex project ID, visit: https://console.cloud.google.com/vertex-ai -VERTEX_PROJECT_ID=your_google_cloud_vertex_project_id +VERTEX_PROJECT_ID= # To get an OpenAI API key, sign up at: https://platform.openai.com/signup -OPENAI_API_KEY=your_openai_api_key +OPENAI_API_KEY= # To get a Google Gemini API key, visit: https://ai.google.dev -GOOGLE_GEMINI_API_KEY=your_google_gemini_api_key +GOOGLE_GEMINI_API_KEY= # For Ollama, download and install from: https://github.com/ollama/ollama OLLAMA_HOST=http://ollama:11434 # Optional API keys for additional features # Sign up for NFTScan API key at: https://developer.nftscan.com/ -NFTSCAN_API_KEY=your_nftscan_api_key +NFTSCAN_API_KEY= # Get your Tavily API key at: https://www.tavily.com/ -TAVILY_API_KEY=your_tavily_api_key +TAVILY_API_KEY= # Get your Covalent API key at: https://www.covalenthq.com/platform/auth/register/ -COVALENT_API_KEY=your_covalent_api_key +COVALENT_API_KEY= # Register for a RootData API key at: https://www.rootdata.com/ -ROOTDATA_API_KEY=your_rootdata_api_key +ROOTDATA_API_KEY= # Sign up for a CoinGecko API key at: https://www.coingecko.com/en/api/pricing -COINGECKO_API_KEY=your_coingecko_api_key +COINGECKO_API_KEY= # RSS3 API URLs (default values provided, change if needed) RSS3_DATA_API=https://gi.rss3.io RSS3_SEARCH_API=https://devnet.rss3.io/search diff --git a/openagent/agents/asset_management.py b/openagent/agents/asset_management.py index cc5add5c..9c918a4a 100644 --- a/openagent/agents/asset_management.py +++ b/openagent/agents/asset_management.py @@ -22,8 +22,7 @@ 1. Query and report on users' token balances 2. Check and inform about users' NFT holdings -3. Generate cross-chain swap widgets for users -4. Generate transfer widgets for users +3. Handle user requests to swap or transfer tokens When interacting with users: - Provide accurate and detailed information diff --git a/openagent/executors/swap_executor.py b/openagent/executors/swap_executor.py index 7bb15cc0..38376e6f 100644 --- a/openagent/executors/swap_executor.py +++ b/openagent/executors/swap_executor.py @@ -37,7 +37,8 @@ class ParamSchema(BaseModel): description="Blockchain network to swap from, support networks: 'ETH', 'BSC', 'ARBITRUM', 'OPTIMISM', 'POLYGON'. Default: 'ETH'.", ) to_chain: ChainLiteral = Field( - default="ETH", description="Blockchain network to swap to, support networks: 'ETH', 'BSC', 'ARBITRUM', 'OPTIMISM', 'POLYGON'. Default: 'ETH'." + default="ETH", + description="Blockchain network to swap to, support networks: 'ETH', 'BSC', 'ARBITRUM', 'OPTIMISM', 'POLYGON'. Default: 'ETH'.", ) amount: str = Field(description="Amount of the from-side token to swap, e.g., '0.1', '1', '10'. Default: '1'.") @@ -48,7 +49,7 @@ class SwapExecutor(BaseTool): """ name = "SwapExecutor" - description = "Use this tool to generate a swap widget for the user to swap cryptocurrencies." + description = "Use this tool to handle user requests to swap cryptocurrencies." args_schema: Type[ParamSchema] = ParamSchema return_direct = False diff --git a/openagent/executors/token_balance_executor.py b/openagent/executors/token_balance_executor.py index e00d9cc1..bd1b9ae3 100644 --- a/openagent/executors/token_balance_executor.py +++ b/openagent/executors/token_balance_executor.py @@ -19,7 +19,7 @@ class ARGS(BaseModel): class TokenBalanceExecutor(BaseTool): name = "TokenBalanceExecutor" - description = "get the token balance of a wallet." + description = "get the token balance of a wallet address." args_schema: Type[ARGS] = ARGS def _run( diff --git a/openagent/executors/token_util.py b/openagent/executors/token_util.py index bcf0ea0c..3c9146cb 100644 --- a/openagent/executors/token_util.py +++ b/openagent/executors/token_util.py @@ -40,7 +40,7 @@ def chain_name_to_id(chain_name: str) -> str: return chain_map.get(chain_name, "1") -@cached(ttl=60, cache=Cache.MEMORY) +@cached(ttl=300, cache=Cache.MEMORY) async def fetch_tokens() -> Dict[str, List[Dict]]: """ Fetch the token list from the API and cache it for 60 seconds. @@ -71,6 +71,14 @@ async def select_best_token(keyword: str, chain_id: str) -> Optional[Dict]: """ keyword = keyword.lower() + # special case for eth on non-ethereum chains + if keyword == "eth" and chain_id != "1": + keyword = "weth" + + # special case for btc + if keyword == "btc": + keyword = "wbtc" + tokens = await fetch_tokens() tokens_on_chain = tokens.get(chain_id, []) @@ -84,8 +92,10 @@ async def select_best_token(keyword: str, chain_id: str) -> Optional[Dict]: # Sort based on priority results.sort( key=lambda x: ( + "logoURI" in x, x["symbol"].lower() == keyword, - x["coinKey"].lower() == keyword, + x.get("coinKey", "").lower() == keyword, + x.get("priceUSD") is not None, x["name"].lower() == keyword, ), reverse=True, diff --git a/openagent/ui/app.py b/openagent/ui/app.py index bb78921b..7a18ef29 100644 --- a/openagent/ui/app.py +++ b/openagent/ui/app.py @@ -86,7 +86,6 @@ async def on_chat_start(): provider_key = profile_name_to_provider_key(profile) set_current_llm(provider_key) setup_runnable() - await cl.Message(content=f"starting chat using the {profile} chat profile").send() def build_token(token_symbol: str, token_address: str): diff --git a/openagent/ui/profile.py b/openagent/ui/profile.py index d9d8a134..0a39cc7e 100644 --- a/openagent/ui/profile.py +++ b/openagent/ui/profile.py @@ -40,7 +40,33 @@ def provider_to_profile(provider_key): profile_info = provider_key_to_profile_info.get(provider_key) if profile_info: - return cl.ChatProfile(name=profile_info["name"], markdown_description=profile_info["markdown_description"], icon=profile_info["icon"]) + return cl.ChatProfile( + name=profile_info["name"], + markdown_description=profile_info["markdown_description"], + icon=profile_info["icon"], + starters=[ + cl.Starter( + label="Swap coins", + message="Swap 1 ETH for USDC on the Ethereum.", + icon="/public/swap.png", + ), + cl.Starter( + label="Market analysis", + message="What's the current price of Ethereum and its market trend?", + icon="/public/market.png", + ), + cl.Starter( + label="Transfer coins", + message="Can you help me transfer 0.1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e on the Ethereum network?", + icon="/public/transfer.png", + ), + cl.Starter( + label="Block explorer", + message="What's the latest block height on the Ethereum network, and what are the current gas fees?", + icon="/public/block_chain.png", + ), + ], + ) return None diff --git a/openagent/workflows/member.py b/openagent/workflows/member.py index f61f49d8..7100cc5e 100644 --- a/openagent/workflows/member.py +++ b/openagent/workflows/member.py @@ -8,7 +8,12 @@ FALLBACK = "fallback_agent" AgentRole = Literal[ - "market_analysis_agent", "asset_management_agent", "block_explorer_agent", "feed_explorer_agent", "research_analyst_agent", "fallback_agent" + "market_analysis_agent", + "asset_management_agent", + "block_explorer_agent", + "feed_explorer_agent", + "research_analyst_agent", + "fallback_agent", ] members = [ @@ -33,7 +38,7 @@ Responsibilities: 1. Query and report on users' token balances 2. Check and inform about users' NFT holdings -3. Generate cross-chain swap widgets for users +3. Swap or transfer of cryptocurrency tokens Provide accurate information with a friendly tone, using occasional puns or emojis to keep interactions engaging. """.strip(), diff --git a/public/block_chain.png b/public/block_chain.png new file mode 100644 index 00000000..972d940c Binary files /dev/null and b/public/block_chain.png differ diff --git a/public/logo_dark.png b/public/logo_dark.png index bd83d78b..a561b541 100644 Binary files a/public/logo_dark.png and b/public/logo_dark.png differ diff --git a/public/logo_light.png b/public/logo_light.png index df6ccbc4..19b78fb2 100644 Binary files a/public/logo_light.png and b/public/logo_light.png differ diff --git a/public/market.png b/public/market.png new file mode 100644 index 00000000..ffdb5e6a Binary files /dev/null and b/public/market.png differ diff --git a/public/swap.png b/public/swap.png new file mode 100644 index 00000000..4132ddac Binary files /dev/null and b/public/swap.png differ diff --git a/public/transfer.png b/public/transfer.png new file mode 100644 index 00000000..9ec1bef3 Binary files /dev/null and b/public/transfer.png differ diff --git a/tests/supervisor_chain.py b/tests/supervisor_chain.py index e5503804..e415d7ab 100644 --- a/tests/supervisor_chain.py +++ b/tests/supervisor_chain.py @@ -16,8 +16,8 @@ def next_role(query) -> str: class TestNextRole(unittest.TestCase): def setUp(self): - # set_current_llm("gemini-1.5-pro") - set_current_llm("gemini-1.5-flash") + set_current_llm("gemini-1.5-pro") + # set_current_llm("gemini-1.5-flash") # set_current_llm("gpt-3.5-turbo") # set_current_llm("llama3.1:latest") @@ -33,6 +33,12 @@ def test_asset_management(self): result = next_role(query) self.assertEqual(result, expected_role, f"Expected {expected_role}, but got {result} for query: {query}") + def test_asset_management_swap(self): + query = "swap 1 eth to usdt on ethereum." + expected_role = "asset_management_agent" + result = next_role(query) + self.assertEqual(result, expected_role, f"Expected {expected_role}, but got {result} for query: {query}") + def test_transfer_expert(self): query = "Can you help me transfer 0.5 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e on the Ethereum network?" expected_role = "asset_management_agent"