From d9e437eb0041447e7ca4f79172beaac3c0103e50 Mon Sep 17 00:00:00 2001 From: ygarg25 Date: Tue, 1 Oct 2024 14:13:26 +0530 Subject: [PATCH 01/15] added/feature: Portfolio News Agent --- .../news_agent_benchmarks/README.md | 20 +++ .../news_agent_benchmarks/__init__.py | 0 .../news_agent_benchmarks/benchmarks.py | 38 +++++ .../news_agent_benchmarks/config.py | 14 ++ .../news_agent_benchmarks/helpers.py | 48 +++++++ .../moragents_dockers/agents/requirements.txt | 6 +- .../moragents_dockers/agents/src/config.py | 7 + .../agents/src/news_agent/src/__init__.py | 0 .../agents/src/news_agent/src/agent.py | 130 ++++++++++++++++++ .../agents/src/news_agent/src/config.py | 129 +++++++++++++++++ .../agents/src/news_agent/src/tools.py | 71 ++++++++++ 11 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 submodules/benchmarks/news_agent_benchmarks/README.md create mode 100644 submodules/benchmarks/news_agent_benchmarks/__init__.py create mode 100644 submodules/benchmarks/news_agent_benchmarks/benchmarks.py create mode 100644 submodules/benchmarks/news_agent_benchmarks/config.py create mode 100644 submodules/benchmarks/news_agent_benchmarks/helpers.py create mode 100644 submodules/moragents_dockers/agents/src/news_agent/src/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/news_agent/src/agent.py create mode 100644 submodules/moragents_dockers/agents/src/news_agent/src/config.py create mode 100644 submodules/moragents_dockers/agents/src/news_agent/src/tools.py diff --git a/submodules/benchmarks/news_agent_benchmarks/README.md b/submodules/benchmarks/news_agent_benchmarks/README.md new file mode 100644 index 0000000..d1c4b90 --- /dev/null +++ b/submodules/benchmarks/news_agent_benchmarks/README.md @@ -0,0 +1,20 @@ +# Benchmarking & Testing News Agent Guide + + +## How to Run the Tests: +1) In the parent directory: +- ```cd submodules/moragents_dockers/agents``` + +2) ```docker build -t agent .``` + +2. NOTE: If you are using Apple Silicon then you may experience problems due to the base image not being build for arm64. We have included a separate Dockerfile in order to deal with this issue, run: + +- ```docker build . -t agent -f Dockerfile-apple``` + +3) To run the agent: + +- ```docker run --name agent -p 5000:5000 agent``` + +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 diff --git a/submodules/benchmarks/news_agent_benchmarks/__init__.py b/submodules/benchmarks/news_agent_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/news_agent_benchmarks/benchmarks.py b/submodules/benchmarks/news_agent_benchmarks/benchmarks.py new file mode 100644 index 0000000..843c33d --- /dev/null +++ b/submodules/benchmarks/news_agent_benchmarks/benchmarks.py @@ -0,0 +1,38 @@ +# submodules/benchmarks/news_agent_benchmarks/benchmarks.py + +import pytest +import logging +from submodules.benchmarks.news_agent_benchmarks.config import Config +from submodules.benchmarks.news_agent_benchmarks.helpers import ask_news_agent, extract_classification + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def test_news_classification(): + for i, test_case in enumerate(Config.TEST_CASES): + article_text = test_case["article_text"] + expected_classification = test_case["expected_classification"] + + logger.info(f"Testing article classification (Test {i + 1})") + + # Ask the news agent to classify the article + response = ask_news_agent(article_text, Config.LOCAL_AGENT_URL) + + # Extract the classification from the response + classification = extract_classification(response) + + if classification == "UNKNOWN": + 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}" + + logger.info(f"Test case {i + 1} passed: Correctly classified as {classification}") + + logger.info("All test cases passed successfully") + + +if __name__ == "__main__": + pytest.main() diff --git a/submodules/benchmarks/news_agent_benchmarks/config.py b/submodules/benchmarks/news_agent_benchmarks/config.py new file mode 100644 index 0000000..2868b17 --- /dev/null +++ b/submodules/benchmarks/news_agent_benchmarks/config.py @@ -0,0 +1,14 @@ +class Config: + TEST_CASES = [ + { + "article_text": "ETH Prices zooms 10 percent today while Bitcoin's price surged to $70,000 today, breaking all previous records. Analysts attribute this to increased institutional adoption and positive regulatory news from several countries.", + "expected_classification": "RELEVANT", + }, + { + "article_text": "A new Tesla facility has opened in Texas, utilizing 100% renewable energy.", + "expected_classification": "NOT RELEVANT", + }, + # Add more test cases as needed + ] + + LOCAL_AGENT_URL = "http://127.0.0.1:5000/" diff --git a/submodules/benchmarks/news_agent_benchmarks/helpers.py b/submodules/benchmarks/news_agent_benchmarks/helpers.py new file mode 100644 index 0000000..7ce2623 --- /dev/null +++ b/submodules/benchmarks/news_agent_benchmarks/helpers.py @@ -0,0 +1,48 @@ +# submodules/benchmarks/news_agent_benchmarks/helpers.py + +import requests +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def ask_news_agent(article_text: str, url: str) -> dict: + headers = {'Content-Type': 'application/json'} + payload = { + "prompt": { + "role": "user", + "content": f"Classify if this article is relevant to cryptocurrency price movements: {article_text}" + } + } + response = requests.post(url, headers=headers, json=payload) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Request failed with status code {response.status_code}: {response.text}") + + +def extract_classification(response: dict) -> str: + if not isinstance(response, dict): + logger.warning(f"Unexpected response type: {type(response)}") + return "UNKNOWN" + + content = response.get('content') + + if content is None: + logger.warning("Response content is None") + return "UNKNOWN" + + if not isinstance(content, str): + logger.warning(f"Unexpected content type: {type(content)}") + return "UNKNOWN" + + content = content.upper() + + if "NOT RELEVANT" in content: + return "NOT RELEVANT" + elif "RELEVANT" in content: + return "RELEVANT" + else: + logger.warning(f"Could not determine relevance from content: {content}") + return "NOT RELEVANT" \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/requirements.txt b/submodules/moragents_dockers/agents/requirements.txt index e87e422..0987f5a 100644 --- a/submodules/moragents_dockers/agents/requirements.txt +++ b/submodules/moragents_dockers/agents/requirements.txt @@ -14,4 +14,8 @@ langchain-text-splitters==0.2.2 langchain-core==0.2.24 langchain-community==0.2.10 torch -tweepy \ No newline at end of file +tweepy +feedparser +python-dateutil +pytz +pyshorteners \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index f52f542..1e194da 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -58,5 +58,12 @@ class Config: "name": "reward agent", "upload_required": False, }, + { + "path": "news_agent.src.agent", + "class": "NewsAgent", + "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/news_agent/src/__init__.py b/submodules/moragents_dockers/agents/src/news_agent/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/agent.py b/submodules/moragents_dockers/agents/src/news_agent/src/agent.py new file mode 100644 index 0000000..4dbb93c --- /dev/null +++ b/submodules/moragents_dockers/agents/src/news_agent/src/agent.py @@ -0,0 +1,130 @@ +import json +import logging +import re +import urllib.parse +from news_agent.src.config import Config +from news_agent.src.tools import clean_html, is_within_time_window, fetch_rss_feed +import pyshorteners + +logger = logging.getLogger(__name__) + +class NewsAgent: + def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): + self.agent_info = agent_info + self.llm = llm + self.flask_app = flask_app + self.tools_provided = self.get_tools() + self.url_shortener = pyshorteners.Shortener() + logger.info("NewsAgent initialized") + + def get_tools(self): + return [ + { + "type": "function", + "function": { + "name": "fetch_crypto_news", + "description": "Fetch and analyze cryptocurrency news for potential price impacts", + "parameters": { + "type": "object", + "properties": { + "coins": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of cryptocurrency symbols to fetch news for" + } + }, + "required": ["coins"] + } + } + } + ] + + def check_relevance_and_summarize(self, title, content, coin): + logger.info(f"Checking relevance for {coin}: {title}") + prompt = Config.RELEVANCE_PROMPT.format(coin=coin, title=title, content=content) + result = self.llm.create_chat_completion( + messages=[{"role": "user", "content": prompt}], + max_tokens=Config.LLM_MAX_TOKENS, + temperature=Config.LLM_TEMPERATURE + ) + return result['choices'][0]['message']['content'].strip() + + def process_rss_feed(self, feed_url, coin): + logger.info(f"Processing RSS feed for {coin}: {feed_url}") + feed = fetch_rss_feed(feed_url) + results = [] + for entry in feed.entries: + published_time = entry.get('published') or entry.get('updated') + if is_within_time_window(published_time): + title = clean_html(entry.title) + content = clean_html(entry.summary) + 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 + }) + if len(results) >= Config.ARTICLES_PER_TOKEN: + break + else: + logger.info(f"Skipping article: {entry.title} (published: {published_time})") + logger.info(f"Found {len(results)} relevant articles for {coin}") + return results + + def fetch_crypto_news(self, coins): + logger.info(f"Fetching news for coins: {coins}") + all_news = [] + for coin in coins: + logger.info(f"Processing news for {coin}") + coin_name = Config.CRYPTO_DICT.get(coin.upper(), coin) + 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]]) + + logger.info(f"Total news items fetched: {len(all_news)}") + return all_news + + def chat(self, request): + try: + data = request.get_json() + if 'prompt' in data: + prompt = data['prompt'] + if isinstance(prompt, dict) and 'content' in prompt: + prompt = prompt['content'] + + # Updated coin detection logic + coins = re.findall(r'\b(' + '|'.join(re.escape(key) for key in Config.CRYPTO_DICT.keys()) + r')\b', + prompt.upper()) + + if not coins: + return {"role": "assistant", + "content": "I couldn't identify any cryptocurrency symbols in your message. Please specify the cryptocurrencies you want news for.", + "next_turn_agent": None} + + news = self.fetch_crypto_news(coins) + + if not news: + return {"role": "assistant", + "content": "No relevant news found for the specified cryptocurrencies in the last 24 hours.", + "next_turn_agent": None} + + response = "Here are the latest news items relevant to changes in price movement of the mentioned tokens in the last 24 hours:\n\n" + for index, item in enumerate(news, start=1): + coin_name = Config.CRYPTO_DICT.get(item['Coin'], item['Coin']) + short_url = self.url_shortener.tinyurl.short(item['Link']) + response += f"{index}. ***{coin_name} News***:\n" + response += f"{item['Title']}\n" + response += f"{item['Summary']}\n" + response += f"Read more: {short_url}\n\n" + + return {"role": "assistant", "content": response, "next_turn_agent": None} + else: + return {"role": "assistant", "content": "Missing required parameters", "next_turn_agent": None} + + except Exception as e: + logger.error(f"Error in chat method: {str(e)}", exc_info=True) + return {"role": "assistant", "content": f"An error occurred: {str(e)}", "next_turn_agent": None} \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/config.py b/submodules/moragents_dockers/agents/src/news_agent/src/config.py new file mode 100644 index 0000000..c8a399b --- /dev/null +++ b/submodules/moragents_dockers/agents/src/news_agent/src/config.py @@ -0,0 +1,129 @@ +import logging + +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" + + # Time window for news (in hours) + NEWS_TIME_WINDOW = 24 + + # Number of articles to show per token + ARTICLES_PER_TOKEN = 1 + + # LLM configuration + LLM_MAX_TOKENS = 150 + LLM_TEMPERATURE = 0.3 + + # Prompts + RELEVANCE_PROMPT = ( + "Consider the following news article about {coin}:\n\n" + "Title: {title}\n\nContent: {content}\n\n" + "Is this article relevant to potential price impacts on the cryptocurrency? " + "If yes, provide a concise summary focused on how it might impact trading or prices. " + "If it's not relevant or only about price movements, respond with 'NOT RELEVANT'." + ) + + # 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' + } \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/tools.py b/submodules/moragents_dockers/agents/src/news_agent/src/tools.py new file mode 100644 index 0000000..607f2fc --- /dev/null +++ b/submodules/moragents_dockers/agents/src/news_agent/src/tools.py @@ -0,0 +1,71 @@ +import feedparser +from datetime import datetime, timedelta +import pytz +from dateutil import parser +import re +from html import unescape +from news_agent.src.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) + cleantext = unescape(cleantext) + cleantext = ' '.join(cleantext.split()) + return cleantext + + +def is_within_time_window(published_time, hours=24): + if not published_time: + return False + try: + pub_date = parser.parse(published_time, fuzzy=True) + now = datetime.now(pytz.UTC) + if pub_date.tzinfo is None: + pub_date = pub_date.replace(tzinfo=pytz.UTC) + return (now - pub_date) <= timedelta(hours=hours) + except Exception as e: + logger.error(f"Error parsing date: {str(e)} for date {published_time}") + return False + + +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']] + encoded_query = urllib.parse.urlencode(query_params, doseq=True) + encoded_url = urllib.parse.urlunparse(parsed_url._replace(query=encoded_query)) + + return feedparser.parse(encoded_url) + + +def get_tools(): + """Return a list of tools for the agent.""" + return [ + { + "type": "function", + "function": { + "name": "fetch_crypto_news", + "description": "Fetch and analyze cryptocurrency news for potential price impacts", + "parameters": { + "type": "object", + "properties": { + "coins": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of cryptocurrency symbols to fetch news for" + } + }, + "required": ["coins"] + } + } + } + ] \ No newline at end of file From b1296d13944f2a1f047f8f0a5f4408f613f2a7c6 Mon Sep 17 00:00:00 2001 From: danxyu Date: Sun, 6 Oct 2024 18:03:42 -0700 Subject: [PATCH 02/15] major refactor to fastapi, setup realtime web search agent --- .github/workflows/mor-agents-build-linux.yml | 336 +++++++++--------- build_assets/macOS/preinstall_ollama.sh | 2 +- build_assets/macOS/welcome.html | 87 +++-- config.py | 24 +- submodules/moragents_dockers/README.md | 62 ++-- .../moragents_dockers/agents/Dockerfile | 8 +- .../moragents_dockers/agents/Dockerfile-apple | 9 +- .../agents/download_model.py | 29 -- .../moragents_dockers/agents/model_config.py | 13 - .../moragents_dockers/agents/requirements.txt | 6 +- .../moragents_dockers/agents/src/__init__.py | 0 .../agents/src/agent_manager.py | 12 + .../agents/src/agents/__init__.py | 0 .../crypto_data}/README.md | 0 .../agents/src/agents/crypto_data/__init__.py | 0 .../src => agents/crypto_data}/agent.py | 5 +- .../src => agents/crypto_data}/config.py | 12 +- .../agents/src/agents/crypto_data/routes.py | 14 + .../src => agents/crypto_data}/tools.py | 187 +++++----- .../agents/src/agents/mor_claims/__init__.py | 0 .../src => agents/mor_claims}/agent.py | 120 ++++--- .../agents/src/agents/mor_claims/config.py | 35 ++ .../src => agents/mor_claims}/tools.py | 60 ++-- .../agents/src/agents/mor_rewards/__init__.py | 0 .../src => agents/mor_rewards}/agent.py | 30 +- .../agents/src/agents/mor_rewards/config.py | 26 ++ .../src => agents/mor_rewards}/tools.py | 28 +- .../agents/src/agents/rag/__init__.py | 0 .../{rag_agent/src => agents/rag}/agent.py | 0 .../{rag_agent/src => agents/rag}/config.py | 5 +- .../token_swap}/README.md | 0 .../agents/src/agents/token_swap/__init__.py | 0 .../src => agents/token_swap}/agent.py | 105 ++++-- .../agents/src/agents/token_swap/config.py | 58 +++ .../src => agents/token_swap}/tools.py | 119 ++++--- .../tweet_sizzler}/README.md | 0 .../src/agents/tweet_sizzler/__init__.py | 0 .../src => agents/tweet_sizzler}/agent.py | 0 .../src => agents/tweet_sizzler}/config.py | 0 .../agents/src/agents/web_search/agent.py | 100 ++++++ .../moragents_dockers/agents/src/app.py | 144 +++----- .../agents/src/claim_agent/src/config.py | 58 --- .../moragents_dockers/agents/src/config.py | 32 +- .../src/data_agent/src/data_agent_config.py | 27 -- .../moragents_dockers/agents/src/delegator.py | 92 +++-- .../agents/src/models/__init__.py | 0 .../agents/src/models/messages.py | 11 + .../agents/src/reward_agent/src/config.py | 39 -- .../agents/src/swap_agent/src/config.py | 26 -- .../src/swap_agent/src/swap_agent_config.py | 25 -- .../docker-compose-apple.yml | 4 +- .../moragents_dockers/docker-compose.yml | 4 +- .../moragents_dockers/frontend/config.ts | 44 +-- .../frontend/package-lock.json | 4 +- .../moragents_dockers/frontend/package.json | 2 +- .../frontend/services/backendClient.ts | 9 +- wizard_windows.iss | 4 +- 57 files changed, 1091 insertions(+), 926 deletions(-) delete mode 100644 submodules/moragents_dockers/agents/download_model.py delete mode 100644 submodules/moragents_dockers/agents/model_config.py create mode 100644 submodules/moragents_dockers/agents/src/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agent_manager.py create mode 100644 submodules/moragents_dockers/agents/src/agents/__init__.py rename submodules/moragents_dockers/agents/src/{data_agent => agents/crypto_data}/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py rename submodules/moragents_dockers/agents/src/{data_agent/src => agents/crypto_data}/agent.py (98%) rename submodules/moragents_dockers/agents/src/{data_agent/src => agents/crypto_data}/config.py (80%) create mode 100644 submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py rename submodules/moragents_dockers/agents/src/{data_agent/src => agents/crypto_data}/tools.py (64%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py rename submodules/moragents_dockers/agents/src/{claim_agent/src => agents/mor_claims}/agent.py (51%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_claims/config.py rename submodules/moragents_dockers/agents/src/{claim_agent/src => agents/mor_claims}/tools.py (71%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py rename submodules/moragents_dockers/agents/src/{reward_agent/src => agents/mor_rewards}/agent.py (71%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py rename submodules/moragents_dockers/agents/src/{reward_agent/src => agents/mor_rewards}/tools.py (73%) create mode 100644 submodules/moragents_dockers/agents/src/agents/rag/__init__.py rename submodules/moragents_dockers/agents/src/{rag_agent/src => agents/rag}/agent.py (100%) rename submodules/moragents_dockers/agents/src/{rag_agent/src => agents/rag}/config.py (59%) rename submodules/moragents_dockers/agents/src/{swap_agent => agents/token_swap}/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py rename submodules/moragents_dockers/agents/src/{swap_agent/src => agents/token_swap}/agent.py (71%) create mode 100644 submodules/moragents_dockers/agents/src/agents/token_swap/config.py rename submodules/moragents_dockers/agents/src/{swap_agent/src => agents/token_swap}/tools.py (61%) rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent => agents/tweet_sizzler}/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent/src => agents/tweet_sizzler}/agent.py (100%) rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent/src => agents/tweet_sizzler}/config.py (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/web_search/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/claim_agent/src/config.py delete mode 100644 submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py create mode 100644 submodules/moragents_dockers/agents/src/models/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/models/messages.py delete mode 100644 submodules/moragents_dockers/agents/src/reward_agent/src/config.py delete mode 100644 submodules/moragents_dockers/agents/src/swap_agent/src/config.py delete mode 100644 submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py diff --git a/.github/workflows/mor-agents-build-linux.yml b/.github/workflows/mor-agents-build-linux.yml index 3d7dcac..da84275 100644 --- a/.github/workflows/mor-agents-build-linux.yml +++ b/.github/workflows/mor-agents-build-linux.yml @@ -2,178 +2,178 @@ name: MOR Agents Build Linux on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: jobs: build: runs-on: ubuntu-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 --name="MORagents" --add-data "images/moragents.png:images" main.py - - - name: Create Debian package - run: | - mkdir -p debian/DEBIAN - mkdir -p debian/usr/bin - mkdir -p debian/usr/share/applications - mkdir -p debian/usr/share/icons/hicolor/256x256/apps - cp -r dist/MORagents/* debian/usr/bin/ - cp images/moragents.png debian/usr/share/icons/hicolor/256x256/apps/moragents.png - echo "[Desktop Entry] - Name=MORagents - Exec=/usr/bin/MORagents - Icon=moragents - Type=Application - Categories=Utility;" > debian/usr/share/applications/moragents.desktop - echo "Package: moragents - Version: 1.0 - Section: utils - Priority: optional - Architecture: amd64 - Maintainer: LachsBagel - Description: MORagents application - MORagents is a desktop application for AI agents." > debian/DEBIAN/control - - dpkg-deb --build debian moragents.deb - - - name: Create setup script - run: | - cat << EOF > moragents-setup.sh - #!/bin/bash - set -e - - # Colors for output - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[0;33m' - NC='\033[0m' # No Color - - # Function to check if a command exists - command_exists() { - command -v "$1" >/dev/null 2>&1 - } - - # Function to add current user to docker group - add_user_to_docker_group() { - local current_user=\$(whoami) - if [ "\$current_user" != "root" ]; then - echo -e "\${YELLOW}Adding current user to docker group...${NC}" - sudo usermod -aG docker "\$current_user" - echo -e "\${GREEN}User added to docker group. Please log out and log back in for changes to take effect.${NC}" - else - echo -e "\${YELLOW}Running as root. Skipping user addition to docker group.${NC}" - fi - } - - # Function to wait for Ollama service to be ready - wait_for_ollama() { - echo -e "\${YELLOW}Waiting for Ollama service to be ready...${NC}" - for i in {1..30}; do - if curl -s http://localhost:11434/api/tags >/dev/null; then - echo -e "\${GREEN}Ollama service is ready.${NC}" - return 0 - fi - sleep 2 - done - echo -e "\${RED}Timed out waiting for Ollama service.${NC}" - return 1 - } - - # Function to pull Ollama model with retries - pull_ollama_model() { - 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 - echo -e "\${GREEN}Successfully pulled \$model.${NC}" - return 0 - fi - echo -e "\${YELLOW}Failed to pull \$model. Retrying...${NC}" - sleep 5 - attempt=\$((attempt + 1)) - done - - echo -e "\${RED}Failed to pull \$model after \$max_attempts attempts.${NC}" - return 1 - } - - # Install curl if not present - if ! command_exists curl; then - echo -e "\${YELLOW}Installing curl...${NC}" - sudo apt-get update - sudo apt-get install -y curl - fi - - # Install Docker if not present - if ! command_exists docker; then - echo -e "\${YELLOW}Installing Docker...${NC}" - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - add_user_to_docker_group - sudo systemctl enable docker - sudo systemctl start docker - else - echo -e "\${GREEN}Docker is already installed.${NC}" - fi - - # Install Ollama - echo -e "\${YELLOW}Installing Ollama...${NC}" - curl -fsSL https://ollama.com/install.sh | sh - - # Start Ollama service - echo -e "\${YELLOW}Starting Ollama service...${NC}" - nohup ollama serve > /dev/null 2>&1 & - - # Wait for Ollama service to be ready - wait_for_ollama - - # Pull Ollama models - echo -e "\${YELLOW}Pulling Ollama models...${NC}" - pull_ollama_model llama3.1 - pull_ollama_model nomic-embed-text - - # Pull necessary Docker images - echo -e "\${YELLOW}Pulling Docker images...${NC}" - sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.1.0 - sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.1.0 - - # Start Docker containers - echo -e "\${YELLOW}Starting Docker containers...${NC}" - sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.1.0 - sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.1.0 - - echo -e "\${GREEN}Setup complete!${NC}" - EOF - - chmod +x moragents-setup.sh - - - name: Upload Debian Package and Setup Script - uses: actions/upload-artifact@v4 - with: - name: MORagentsSetup-Linux - path: | - moragents.deb - moragents-setup.sh \ No newline at end of file + - 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 --name="MORagents" --add-data "images/moragents.png:images" main.py + + - name: Create Debian package + run: | + mkdir -p debian/DEBIAN + mkdir -p debian/usr/bin + mkdir -p debian/usr/share/applications + mkdir -p debian/usr/share/icons/hicolor/256x256/apps + cp -r dist/MORagents/* debian/usr/bin/ + cp images/moragents.png debian/usr/share/icons/hicolor/256x256/apps/moragents.png + echo "[Desktop Entry] + Name=MORagents + Exec=/usr/bin/MORagents + Icon=moragents + Type=Application + Categories=Utility;" > debian/usr/share/applications/moragents.desktop + echo "Package: moragents + Version: 1.0 + Section: utils + Priority: optional + Architecture: amd64 + Maintainer: LachsBagel + Description: MORagents application + MORagents is a desktop application for AI agents." > debian/DEBIAN/control + + dpkg-deb --build debian moragents.deb + + - name: Create setup script + run: | + cat << EOF > moragents-setup.sh + #!/bin/bash + set -e + + # Colors for output + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + NC='\033[0m' # No Color + + # Function to check if a command exists + command_exists() { + command -v "$1" >/dev/null 2>&1 + } + + # Function to add current user to docker group + add_user_to_docker_group() { + local current_user=\$(whoami) + if [ "\$current_user" != "root" ]; then + echo -e "\${YELLOW}Adding current user to docker group...${NC}" + sudo usermod -aG docker "\$current_user" + echo -e "\${GREEN}User added to docker group. Please log out and log back in for changes to take effect.${NC}" + else + echo -e "\${YELLOW}Running as root. Skipping user addition to docker group.${NC}" + fi + } + + # Function to wait for Ollama service to be ready + wait_for_ollama() { + echo -e "\${YELLOW}Waiting for Ollama service to be ready...${NC}" + for i in {1..30}; do + if curl -s http://localhost:11434/api/tags >/dev/null; then + echo -e "\${GREEN}Ollama service is ready.${NC}" + return 0 + fi + sleep 2 + done + echo -e "\${RED}Timed out waiting for Ollama service.${NC}" + return 1 + } + + # Function to pull Ollama model with retries + pull_ollama_model() { + 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 + echo -e "\${GREEN}Successfully pulled \$model.${NC}" + return 0 + fi + echo -e "\${YELLOW}Failed to pull \$model. Retrying...${NC}" + sleep 5 + attempt=\$((attempt + 1)) + done + + echo -e "\${RED}Failed to pull \$model after \$max_attempts attempts.${NC}" + return 1 + } + + # Install curl if not present + if ! command_exists curl; then + echo -e "\${YELLOW}Installing curl...${NC}" + sudo apt-get update + sudo apt-get install -y curl + fi + + # Install Docker if not present + if ! command_exists docker; then + echo -e "\${YELLOW}Installing Docker...${NC}" + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + add_user_to_docker_group + sudo systemctl enable docker + sudo systemctl start docker + else + echo -e "\${GREEN}Docker is already installed.${NC}" + fi + + # Install Ollama + echo -e "\${YELLOW}Installing Ollama...${NC}" + curl -fsSL https://ollama.com/install.sh | sh + + # Start Ollama service + echo -e "\${YELLOW}Starting Ollama service...${NC}" + nohup ollama serve > /dev/null 2>&1 & + + # Wait for Ollama service to be ready + wait_for_ollama + + # Pull Ollama models + echo -e "\${YELLOW}Pulling Ollama models...${NC}" + pull_ollama_model llama3.2:3b + pull_ollama_model nomic-embed-text + + # Pull necessary Docker images + echo -e "\${YELLOW}Pulling Docker images...${NC}" + sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.1.1 + sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.1.1 + + # Start Docker containers + echo -e "\${YELLOW}Starting Docker containers...${NC}" + sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.1.1 + sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.1.1 + + echo -e "\${GREEN}Setup complete!${NC}" + EOF + + chmod +x moragents-setup.sh + + - name: Upload Debian Package and Setup Script + uses: actions/upload-artifact@v4 + with: + name: MORagentsSetup-Linux + path: | + moragents.deb + moragents-setup.sh diff --git a/build_assets/macOS/preinstall_ollama.sh b/build_assets/macOS/preinstall_ollama.sh index dfcb2e7..a74c1d5 100644 --- a/build_assets/macOS/preinstall_ollama.sh +++ b/build_assets/macOS/preinstall_ollama.sh @@ -7,5 +7,5 @@ chmod +x ollama sudo mv ollama /usr/local/bin/ nohup /usr/local/bin/ollama serve > /dev/null 2>&1 & -/usr/local/bin/ollama pull llama3.1 +/usr/local/bin/ollama pull llama3.2:3b /usr/local/bin/ollama pull nomic-embed-text diff --git a/build_assets/macOS/welcome.html b/build_assets/macOS/welcome.html index 18ca86d..32a3e56 100644 --- a/build_assets/macOS/welcome.html +++ b/build_assets/macOS/welcome.html @@ -1,44 +1,57 @@ - - - - Welcome to MORagents v0.1.0 Installer - - - -

Welcome to MORagents v0.1.0 Installer

-

Thank you for choosing to install MORagents on your system. This installer will guide you through the process of setting up MORagents and its dependencies.

-

The installer will perform the following steps:

- -

Please note that during the installation process, you may be prompted to enter your system password to authorize the installation of required components.

-

Click "Continue" to proceed with the installation.

- + li { + margin-bottom: 10px; + } + + + +

Welcome to MORagents v0.1.1 Installer

+

+ Thank you for choosing to install MORagents on your system. This installer + will guide you through the process of setting up MORagents and its + dependencies. +

+

The installer will perform the following steps:

+ +

+ Please note that during the installation process, you may be prompted to + enter your system password to authorize the installation of required + components. +

+

Click "Continue" to proceed with the installation.

+ diff --git a/config.py b/config.py index 43bae40..92ba497 100644 --- a/config.py +++ b/config.py @@ -16,20 +16,20 @@ class AgentDockerConfig: MACOS_APPLE_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:apple-0.1.0", - "lachsbagel/moragents_dockers-agents:apple-0.1.0" + "lachsbagel/moragents_dockers-nginx:apple-0.1.1", + "lachsbagel/moragents_dockers-agents:apple-0.1.1" ] MACOS_INTEL_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx:amd64-0.1.1", + "lachsbagel/moragents_dockers-agents:amd64-0.1.1" ] WINDOWS_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx:amd64-0.1.1", + "lachsbagel/moragents_dockers-agents:amd64-0.1.1" ] LINUX_IMAGE_NAMES = [ # TODO, may need linux specific tagged images - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx:amd64-0.1.1", + "lachsbagel/moragents_dockers-agents:amd64-0.1.1" ] @staticmethod @@ -50,8 +50,8 @@ class AgentDockerConfigDeprecate: "morpheus/price_fetcher_agent:latest", "moragents_dockers-nginx:latest", "moragents_dockers-agents:latest", - "lachsbagel/moragents_dockers-nginx:apple-0.0.9", - "lachsbagel/moragents_dockers-agents:apple-0.0.9", - "lachsbagel/moragents_dockers-nginx:amd64-0.0.9", - "lachsbagel/moragents_dockers-agents:amd64-0.0.9" + "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" ] diff --git a/submodules/moragents_dockers/README.md b/submodules/moragents_dockers/README.md index 38327fc..e07693e 100644 --- a/submodules/moragents_dockers/README.md +++ b/submodules/moragents_dockers/README.md @@ -1,41 +1,44 @@ -# Moragents +# Moragents ## Overview -This project is a Flask-based AI chat application featuring intelligent responses from various language models and embeddings. It includes file uploading, cryptocurrency swapping, and a delegator system to manage multiple agents. The application, along with a dApp for agent interaction, runs locally and is containerized with Docker. +This project is a Flask-based AI chat application featuring intelligent responses from various language models and embeddings. It includes file uploading, cryptocurrency swapping, and a delegator system to manage multiple agents. The application, along with a dApp for agent interaction, runs locally and is containerized with Docker. ## Pre-requisites -* [Download Ollama](https://ollama.com/ )for your operating system -* Then after finishing installation pull these two models: -```ollama pull llama3.1``` +- [Download Ollama](https://ollama.com/)for your operating system +- Then after finishing installation pull these two models: + +`ollama pull llama3.2:3b` -```ollama pull nomic-embed-text``` +`ollama pull nomic-embed-text` ## Run with Docker Compose -Docker compose will build and run two containers. One will be for the agents, the other will be for the UI. +Docker compose will build and run two containers. One will be for the agents, the other will be for the UI. 1. Ensure you're in the submodules/moragents_dockers folder - ```sh - $ cd submodules/moragents_dockers - ``` + + ```sh + $ cd submodules/moragents_dockers + ``` 2. Build Images and Launch Containers: - 1. For Intel / AMD / x86_64 - ```sh - docker-compose up - ``` + 1. For Intel / AMD / x86_64 + ```sh + docker-compose up + ``` 2. For Apple silicon (M1, M2, M3, etc) - ```sh - docker-compose -f docker-compose-apple.yml up - ``` + ```sh + docker-compose -f docker-compose-apple.yml up + ``` -Open in the browser: ```http://localhost:3333/``` +Open in the browser: `http://localhost:3333/` -Docker build will download the model. The first time that one of the agents are called, the model will be loaded into memory and this instance will be shared between all agents. +Docker build will download the model. The first time that one of the agents are called, the model will be loaded into memory and this instance will be shared between all agents. ## Agents + Five agents are included: ### Data Agent @@ -53,6 +56,7 @@ It currently supports the following metrics: It is possible to ask questions about assets by referring to them either by their name or their ticker symbol. ### Swap Agent + This agent will enable you to perform swaps between cryptoassets. It should be used with the accompanying UI which provides a browser-based front-end to chat with the agent, display quotes and sign transactions. A typical flow looks like this: @@ -65,35 +69,43 @@ A typical flow looks like this: - If the allowance for the token being sold is too low, an approval transaction will be generated first ## RAG Agent + This agent will answer questions about an uploaded PDF file. ## Tweet Sizzler Agent -This agent will let you generate tweets, edit with a WSYWIG. -Provided you enter API creds in the Settings you can also directly post to your X account. + +This agent will let you generate tweets, edit with a WSYWIG. +Provided you enter API creds in the Settings you can also directly post to your X account. ## MOR Rewards Agent + Ask the agent to check your MOR rewards and it will retrieve claimable MOR stats from both capital and coder pools. --- # Delegator + The Delegator handles user queries by analyzing the prompt and delegating it to the appropriate agent. ## API Endpoints 1. **Chat Functionality** + - Endpoint: `POST /` - Handles chat interactions, delegating to appropriate agents when necessary. 2. **Message History** + - Endpoint: `GET /messages` - Retrieves chat message history. 3. **Clear Messages** + - Endpoint: `GET /clear_messages` - Clears the chat message history. 4. **Swap Operations** + - Endpoints: - `POST /tx_status`: Check transaction status - `POST /allowance`: Get allowance @@ -129,6 +141,7 @@ This allows the delegator to delegate to the correct task agent based on the use - `upload`: A boolean indicating if the agent requires a file to be uploaded from the front-end before it should be called. #### Example: + ```python:agents/src/config.py DELEGATOR_CONFIG = { "agents": [ @@ -144,13 +157,13 @@ DELEGATOR_CONFIG = { } ``` - ### 3. Implement Agent Logic 1. **Define the agent class** in the specified path. 2. **Ensure the agent can handle the queries** it is designed for. #### Example: + ```python:agents/src/new_agent/src/agent.py class NewAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): @@ -177,12 +190,12 @@ class NewAgent: # Add other methods as needed ``` - ### 4. Handle Multi-Turn Conversations Agents can handle multi-turn conversations by returning a next_turn_agent which indicates the name of the agent that should handle the next turn. #### Example: + ```python class NewAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): @@ -205,7 +218,7 @@ class NewAgent: def chat(self, request, user_id): # Process the query and determine the next agent next_turn_agent = self.agent_info["name"] - + # Generate response where we want to initiate a multi-turn conversation with the same agent. return response, next_turn_agent @@ -215,6 +228,7 @@ class NewAgent: ### 5. Integration The `Delegator` will automatically: + - Import the agent module. - Instantiate the agent class. - Add the agent to its internal dictionary. diff --git a/submodules/moragents_dockers/agents/Dockerfile b/submodules/moragents_dockers/agents/Dockerfile index 52bcec7..40444e0 100644 --- a/submodules/moragents_dockers/agents/Dockerfile +++ b/submodules/moragents_dockers/agents/Dockerfile @@ -12,14 +12,8 @@ RUN apt-get update && apt-get install -y gcc g++ procps && rm -rf /var/lib/apt/l # Install Python dependencies RUN python3 -m pip install --no-cache-dir --upgrade pip && \ python3 -m pip install --no-cache-dir --upgrade -r requirements.txt - -COPY download_model.py . -COPY model_config.py . - -RUN python3 download_model.py - -copy . . +COPY . . # Expose the port your application listens on EXPOSE 5000 diff --git a/submodules/moragents_dockers/agents/Dockerfile-apple b/submodules/moragents_dockers/agents/Dockerfile-apple index ad80504..55a4517 100644 --- a/submodules/moragents_dockers/agents/Dockerfile-apple +++ b/submodules/moragents_dockers/agents/Dockerfile-apple @@ -12,19 +12,12 @@ RUN apt-get update && apt-get install -y gcc g++ procps && rm -rf /var/lib/apt/l # Install Python dependencies RUN python3 -m pip install --no-cache-dir --upgrade pip && \ python3 -m pip install --no-cache-dir --upgrade -r requirements.txt - -COPY download_model.py . -COPY model_config.py . - -RUN python3 download_model.py - -copy . . +COPY . . # Expose the port your application listens on EXPOSE 5000 - # Set the environment variable for Flask ENV FLASK_APP=src/app.py diff --git a/submodules/moragents_dockers/agents/download_model.py b/submodules/moragents_dockers/agents/download_model.py deleted file mode 100644 index 55f3fd4..0000000 --- a/submodules/moragents_dockers/agents/download_model.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import shutil -from huggingface_hub import hf_hub_download -from model_config import Config - - -def download_model(model_name, revision): - """Function to download model from the hub""" - model_directory = hf_hub_download(repo_id=model_name, filename=revision) - return model_directory - - -def move_files(src_dir, dest_dir): - """Move files from source to destination directory.""" - for f in os.listdir(src_dir): - src_path = os.path.join(src_dir, f) - dst_path = os.path.join(dest_dir, f) - shutil.copy2(src_path, dst_path) - os.remove(src_path) - - -if __name__ == "__main__": - download_dir = Config.DOWNLOAD_DIR - os.makedirs(download_dir, exist_ok=True) - model_name = Config.MODEL_NAME - revision = Config.MODEL_REVISION - path = download_model(model_name, revision) - model_path = "/".join(path.split("/")[:-1]) + "/" - move_files(model_path, download_dir) diff --git a/submodules/moragents_dockers/agents/model_config.py b/submodules/moragents_dockers/agents/model_config.py deleted file mode 100644 index a5868e4..0000000 --- a/submodules/moragents_dockers/agents/model_config.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" diff --git a/submodules/moragents_dockers/agents/requirements.txt b/submodules/moragents_dockers/agents/requirements.txt index e87e422..4e2003d 100644 --- a/submodules/moragents_dockers/agents/requirements.txt +++ b/submodules/moragents_dockers/agents/requirements.txt @@ -1,9 +1,7 @@ llama-cpp-python==0.2.90 -transformers==4.43.3 sentencepiece==0.2.0 protobuf==5.27.2 scikit-learn==1.5.1 -huggingface-hub==0.24.3 flask==2.2.2 Werkzeug==2.2.2 flask-cors==4.0.1 @@ -13,5 +11,5 @@ faiss-cpu==1.8.0.post1 langchain-text-splitters==0.2.2 langchain-core==0.2.24 langchain-community==0.2.10 -torch -tweepy \ No newline at end of file +langchain_experimental==0.3.2 +tweepy==4.14.0 diff --git a/submodules/moragents_dockers/agents/src/__init__.py b/submodules/moragents_dockers/agents/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agent_manager.py b/submodules/moragents_dockers/agents/src/agent_manager.py new file mode 100644 index 0000000..b1530dc --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agent_manager.py @@ -0,0 +1,12 @@ +class AgentManager: + def __init__(self): + self.active_agents = {} + + def get_active_agent(self, user_id): + return self.active_agents.get(user_id) + + def set_active_agent(self, user_id, agent_name): + self.active_agents[user_id] = agent_name + + def clear_active_agent(self, user_id): + self.active_agents.pop(user_id, None) diff --git a/submodules/moragents_dockers/agents/src/agents/__init__.py b/submodules/moragents_dockers/agents/src/agents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/data_agent/README.md b/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/data_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/crypto_data/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py similarity index 98% rename from submodules/moragents_dockers/agents/src/data_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py index 1f5dd54..4794a76 100644 --- a/submodules/moragents_dockers/agents/src/data_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py @@ -1,11 +1,12 @@ import json -from data_agent.src import tools import logging +from src.agents.crypto_data import tools + logger = logging.getLogger(__name__) -class DataAgent: +class CryptoDataAgent: def __init__(self, config, llm, llm_ollama, embeddings, flask_app): self.llm = llm self.flask_app = flask_app diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py similarity index 80% rename from submodules/moragents_dockers/agents/src/data_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/config.py index 0c916ec..b87337b 100644 --- a/submodules/moragents_dockers/agents/src/data_agent/src/config.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py @@ -3,12 +3,13 @@ # Logging configuration logging.basicConfig(level=logging.INFO) + # Configuration object class Config: # Model configuration MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION + MODEL_PATH = "model/" + MODEL_REVISION DOWNLOAD_DIR = "model" # API endpoints COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" @@ -16,12 +17,15 @@ 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." - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py new file mode 100644 index 0000000..d8417f5 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py @@ -0,0 +1,14 @@ +import logging + +from flask import Blueprint, request, jsonify + +crypto_data_agent_bp = Blueprint('crypto_data_agent', __name__) +logger = logging.getLogger(__name__) + +@crypto_data_agent_bp.route('/process_data', methods=['POST']) +def process_data(): + logger.info("Data Agent: Received process_data request") + data = request.get_json() + # Implement your data processing logic here + response = {"status": "success", "message": "Data processed"} + return jsonify(response) diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py similarity index 64% rename from submodules/moragents_dockers/agents/src/data_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py index e02839a..0b0b100 100644 --- a/submodules/moragents_dockers/agents/src/data_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py @@ -1,9 +1,9 @@ import requests import logging -from data_agent.src.config import Config from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity +from src.agents.crypto_data.config import Config def get_most_similar(text, data): @@ -13,9 +13,12 @@ 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 + def get_coingecko_id(text, type="coin"): """Get the CoinGecko ID for a given coin or NFT.""" url = f"{Config.COINGECKO_BASE_URL}/search" @@ -25,30 +28,32 @@ def get_coingecko_id(text, type="coin"): response.raise_for_status() data = response.json() if type == "coin": - return data['coins'][0]['id'] if data['coins'] else None + return data["coins"][0]["id"] if data["coins"] else None elif type == "nft": - return data['nfts'][0]['id'] if data.get('nfts') else None + return data["nfts"][0]["id"] if data.get("nfts") else None else: raise ValueError("Invalid type specified") except requests.exceptions.RequestException as e: logging.error(f"API request failed: {str(e)}") raise + def get_price(coin): """Get the price of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: return None url = f"{Config.COINGECKO_BASE_URL}/simple/price" - params = {'ids': coin_id, 'vs_currencies': 'USD'} + params = {"ids": coin_id, "vs_currencies": "USD"} try: response = requests.get(url, params=params) response.raise_for_status() - return response.json()[coin_id]['usd'] + return response.json()[coin_id]["usd"] except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve price: {str(e)}") raise + def get_floor_price(nft): """Get the floor price of an NFT from CoinGecko API.""" nft_id = get_coingecko_id(str(nft), type="nft") @@ -63,6 +68,7 @@ def get_floor_price(nft): logging.error(f"Failed to retrieve floor price: {str(e)}") raise + def get_fdv(coin): """Get the fully diluted valuation of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") @@ -78,21 +84,23 @@ def get_fdv(coin): logging.error(f"Failed to retrieve FDV: {str(e)}") raise + def get_market_cap(coin): """Get the market cap of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: return None url = f"{Config.COINGECKO_BASE_URL}/coins/markets" - params = {'ids': coin_id, 'vs_currency': 'USD'} + params = {"ids": coin_id, "vs_currency": "USD"} try: response = requests.get(url, params=params) response.raise_for_status() - return response.json()[0]['market_cap'] + return response.json()[0]["market_cap"] except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve market cap: {str(e)}") raise + def get_protocols_list(): """Get the list of protocols from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/protocols" @@ -100,11 +108,16 @@ def get_protocols_list(): response = requests.get(url) response.raise_for_status() data = response.json() - return [item['slug'] for item in data] ,[item['name'] for item in data] ,[item['gecko_id'] for item in data] + return ( + [item["slug"] for item in data], + [item["name"] for item in data], + [item["gecko_id"] for item in data], + ) except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve protocols list: {str(e)}") raise + def get_tvl_value(protocol_id): """Gets the TVL value using the protocol ID from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/tvl/{protocol_id}" @@ -114,11 +127,12 @@ def get_tvl_value(protocol_id): return response.json() except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve protocol TVL: {str(e)}") - raise + raise + def get_protocol_tvl(protocol_name): """Get the TVL (Total Value Locked) of a protocol from DefiLlama API.""" - id,name,gecko = get_protocols_list() + id, name, gecko = get_protocols_list() tag = get_coingecko_id(protocol_name) if tag: protocol_id = next((i for i, j in zip(id, gecko) if j == tag), None) @@ -157,7 +171,9 @@ 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 @@ -168,8 +184,10 @@ def get_protocol_total_value_locked_tool(protocol_name): tvl = get_protocol_tvl(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) + 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 + ) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE @@ -191,96 +209,99 @@ 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 + def get_tools(): """Return a list of tools for the agent.""" return [ { - "type": "function", - "function": { - "name": "get_price", - "description": "Get the price of a cryptocurrency", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "The name of the coin.", - } + "type": "function", + "function": { + "name": "get_price", + "description": "Get the price of a cryptocurrency", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "The name of the coin.", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - }, { - "type": "function", - "function": { - "name": "get_floor_price", - "description": "Get the floor price of an NFT", - "parameters": { - "type": "object", - "properties": { - "nft_name": { - "type": "string", - "description": "Name of the NFT", - } + "type": "function", + "function": { + "name": "get_floor_price", + "description": "Get the floor price of an NFT", + "parameters": { + "type": "object", + "properties": { + "nft_name": { + "type": "string", + "description": "Name of the NFT", + } + }, + "required": ["nft_name"], }, - "required": ["nft_name"], }, }, - }, - { - "type": "function", - "function": { - "name": "get_tvl", - "description": "Get the TVL (Total Value Locked) of a protocol.", - "parameters": { - "type": "object", - "properties": { - "protocol_name": { - "type": "string", - "description": "Name of the protocol", - } + { + "type": "function", + "function": { + "name": "get_tvl", + "description": "Get the TVL (Total Value Locked) of a protocol.", + "parameters": { + "type": "object", + "properties": { + "protocol_name": { + "type": "string", + "description": "Name of the protocol", + } + }, + "required": ["protocol_name"], }, - "required": ["protocol_name"], }, }, - }, - { - "type": "function", - "function": { - "name": "get_fdv", - "description": "Get the fdv or fully diluted valuation of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } + { + "type": "function", + "function": { + "name": "get_fdv", + "description": "Get the fdv or fully diluted valuation of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - } , - { - "type": "function", - "function": { - "name": "get_market_cap", - "description": "Get the mc or market cap of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } + { + "type": "function", + "function": { + "name": "get_market_cap", + "description": "Get the mc or market cap of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - } ] diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py similarity index 51% rename from submodules/moragents_dockers/agents/src/claim_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py index d753f52..2f98c7d 100644 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py @@ -1,8 +1,7 @@ -import json -from claim_agent.src import tools -from claim_agent.src.config import Config +from src.agents.mor_claims import tools -class ClaimAgent: + +class MorClaimsAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): self.agent_info = agent_info self.llm = llm @@ -18,27 +17,53 @@ def get_response(self, message, wallet_address): if state == "initial": rewards = { 0: tools.get_current_user_reward(wallet_address, 0), - 1: tools.get_current_user_reward(wallet_address, 1) + 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" - 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", self.agent_info["name"] + 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" + 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", + self.agent_info["name"], + ) else: - return f"No rewards found for your wallet address {wallet_address} in either pool. Claim cannot be processed.", "assistant", None + return ( + f"No rewards found for your wallet address {wallet_address} in either pool. Claim cannot be processed.", + "assistant", + None, + ) elif state == "awaiting_confirmation": - user_input = message[-1]['content'].lower() - if any(word in user_input for word in ['yes', 'proceed', 'confirm', 'claim']): + user_input = message[-1]["content"].lower() + if any( + word in user_input for word in ["yes", "proceed", "confirm", "claim"] + ): return self.prepare_transactions(wallet_address) else: - return "Please confirm if you want to proceed with the claim by saying 'yes', 'proceed', 'confirm', or 'claim'.", "assistant", self.agent_info["name"] - - return "I'm sorry, I didn't understand that. Can you please rephrase your request?", "assistant", self.agent_info["name"] + return ( + "Please confirm if you want to proceed with the claim by saying 'yes', 'proceed', 'confirm', or 'claim'.", + "assistant", + self.agent_info["name"], + ) + + return ( + "I'm sorry, I didn't understand that. Can you please rephrase your request?", + "assistant", + self.agent_info["name"], + ) def prepare_transactions(self, wallet_address): available_rewards = self.conversation_state[wallet_address]["available_rewards"] @@ -50,37 +75,47 @@ def prepare_transactions(self, wallet_address): tx_data = tools.prepare_claim_transaction(pool_id, receiver_address) transactions.append({"pool": pool_id, "transaction": tx_data}) except Exception as e: - return f"Error preparing transaction for pool {pool_id}: {str(e)}", "assistant", None + return ( + f"Error preparing transaction for pool {pool_id}: {str(e)}", + "assistant", + None, + ) self.conversation_state[wallet_address]["transactions"] = transactions # Return a structured response - return { - "role": "claim", - "content": { - "transactions": transactions, - "claim_tx_cb": "/claim" - } - }, "claim", None + return ( + { + "role": "claim", + "content": {"transactions": transactions, "claim_tx_cb": "/claim"}, + }, + "claim", + None, + ) def chat(self, request): try: data = request.get_json() - 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) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} + 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 + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } else: return {"error": "Missing required parameters"}, 400 except Exception as e: return {"Error": str(e)}, 500 - def claim(self, request): try: data = request.get_json() - wallet_address = data['wallet_address'] + wallet_address = data["wallet_address"] transactions = self.conversation_state[wallet_address]["transactions"] return jsonify({"transactions": transactions}) except Exception as e: @@ -89,9 +124,9 @@ def claim(self, request): def claim_status(self, request): try: data = request.get_json() - wallet_address = data.get('wallet_address') - transaction_hash = data.get('transaction_hash') - status = data.get('status') + wallet_address = data.get("wallet_address") + transaction_hash = data.get("transaction_hash") + status = data.get("status") if not all([wallet_address, transaction_hash, status]): return jsonify({"error": "Missing required parameters"}), 400 @@ -102,9 +137,8 @@ def claim_status(self, request): except Exception as e: return jsonify({"error": str(e)}), 500 - def get_status(self, flag, tx_hash, tx_type): - response = '' + response = "" if flag == "cancelled": response = f"The claim transaction has been cancelled." @@ -113,14 +147,18 @@ 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 = response + f" The transaction hash is {tx_hash}. " \ - f"Here's the link to the Etherscan transaction: " \ - f"https://etherscan.io/tx/{tx_hash}" + response = ( + response + f" The transaction hash is {tx_hash}. " + f"Here's the link to the Etherscan transaction: " + f"https://etherscan.io/tx/{tx_hash}" + ) if flag != "initiated": response = response + " Is there anything else I can help you with?" - return {"role": "assistant", "content": response} \ No newline at end of file + return {"role": "assistant", "content": response} diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py new file mode 100644 index 0000000..44d7639 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py @@ -0,0 +1,35 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + + WEB3RPCURL = {"1": "https://eth.llamarpc.com/"} + MINT_FEE = 0.001 # in ETH + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "receiver_", "type": "address"}, + ], + "name": "claim", + "outputs": [], + "stateMutability": "payable", + "type": "function", + }, + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + }, + ] diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py similarity index 71% rename from submodules/moragents_dockers/agents/src/claim_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py index bee91c6..e5f0143 100644 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py @@ -1,11 +1,13 @@ from web3 import Web3 -from claim_agent.src.config import Config + +from src.agents.mor_claims.config import Config + 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: @@ -13,37 +15,41 @@ 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)}") + def prepare_claim_transaction(pool_id, wallet_address): try: web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL["1"])) contract = web3.eth.contract( address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), - abi=Config.DISTRIBUTION_ABI + abi=Config.DISTRIBUTION_ABI, + ) + tx_data = contract.encode_abi( + fn_name="claim", args=[pool_id, web3.to_checksum_address(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} ) - tx_data = contract.encode_abi(fn_name="claim", args=[pool_id, web3.to_checksum_address(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 - }) return { "to": Config.DISTRIBUTION_PROXY_ADDRESS, "data": tx_data, "value": str(mint_fee), "gas": str(estimated_gas), - "chainId": "1" + "chainId": "1", } except Exception as e: raise Exception(f"Failed to prepare claim transaction: {str(e)}") + def get_tools(): return [ { @@ -56,16 +62,16 @@ def get_tools(): "properties": { "wallet_address": { "type": "string", - "description": "The wallet address to check rewards for" + "description": "The wallet address to check rewards for", }, "pool_id": { "type": "integer", - "description": "The ID of the pool to check rewards from" - } + "description": "The ID of the pool to check rewards from", + }, }, - "required": ["wallet_address", "pool_id"] - } - } + "required": ["wallet_address", "pool_id"], + }, + }, }, { "type": "function", @@ -77,15 +83,15 @@ def get_tools(): "properties": { "pool_id": { "type": "integer", - "description": "The ID of the pool to claim from" + "description": "The ID of the pool to claim from", }, "wallet_address": { "type": "string", - "description": "The wallet address to claim rewards for" - } + "description": "The wallet address to claim rewards for", + }, }, - "required": ["pool_id", "wallet_address"] - } - } - } - ] \ No newline at end of file + "required": ["pool_id", "wallet_address"], + }, + }, + }, + ] diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py similarity index 71% rename from submodules/moragents_dockers/agents/src/reward_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py index 44555da..5a32d46 100644 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py @@ -1,11 +1,11 @@ -import json import logging -from reward_agent.src import tools + +from src.agents.mor_rewards import tools logger = logging.getLogger(__name__) -class RewardAgent: +class MorRewardsAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): self.agent_info = agent_info self.llm = llm @@ -20,7 +20,7 @@ def get_response(self, message, wallet_address): try: rewards = { 0: tools.get_current_user_reward(wallet_address, 0), - 1: tools.get_current_user_reward(wallet_address, 1) + 1: tools.get_current_user_reward(wallet_address, 1), } response = f"Your current MOR rewards:\n" @@ -31,16 +31,26 @@ def get_response(self, message, wallet_address): return response, "assistant", None except Exception as e: logger.error(f"Error occurred while checking rewards: {str(e)}") - return f"An error occurred while checking your rewards: {str(e)}", "assistant", None + return ( + f"An error occurred while checking your rewards: {str(e)}", + "assistant", + None, + ) def chat(self, request): try: data = request.get_json() - 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) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} + 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 + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } else: logger.warning("Missing required parameters in request") return {"error": "Missing required parameters"}, 400 diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py new file mode 100644 index 0000000..e8ac97e --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py @@ -0,0 +1,26 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + + WEB3RPCURL = { + "1": "https://eth.llamarpc.com/", + } + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + } + ] diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py similarity index 73% rename from submodules/moragents_dockers/agents/src/reward_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py index cbb749f..553041e 100644 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py @@ -1,11 +1,13 @@ from web3 import Web3 -from reward_agent.src.config import Config + +from src.agents.mor_rewards.config import Config + 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 + address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), + abi=Config.DISTRIBUTION_ABI, ) try: @@ -13,14 +15,14 @@ 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)}") + def get_tools(): return [ { @@ -33,15 +35,15 @@ def get_tools(): "properties": { "wallet_address": { "type": "string", - "description": "The wallet address to check rewards for" + "description": "The wallet address to check rewards for", }, "pool_id": { "type": "integer", - "description": "The ID of the pool to check rewards from" - } + "description": "The ID of the pool to check rewards from", + }, }, - "required": ["wallet_address", "pool_id"] - } - } + "required": ["wallet_address", "pool_id"], + }, + }, } - ] \ No newline at end of file + ] diff --git a/submodules/moragents_dockers/agents/src/agents/rag/__init__.py b/submodules/moragents_dockers/agents/src/agents/rag/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/rag_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/rag/agent.py similarity index 100% rename from submodules/moragents_dockers/agents/src/rag_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/rag/agent.py diff --git a/submodules/moragents_dockers/agents/src/rag_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/rag/config.py similarity index 59% rename from submodules/moragents_dockers/agents/src/rag_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/rag/config.py index 63983c2..13716f4 100644 --- a/submodules/moragents_dockers/agents/src/rag_agent/src/config.py +++ b/submodules/moragents_dockers/agents/src/agents/rag/config.py @@ -3,7 +3,8 @@ # Logging configuration logging.basicConfig(level=logging.INFO) + # Configuration object class Config: - MAX_FILE_SIZE=5 * 1024 * 1024 # 5 MB - MAX_LENGTH=16 * 1024 * 1024 \ No newline at end of file + MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB + MAX_LENGTH = 16 * 1024 * 1024 diff --git a/submodules/moragents_dockers/agents/src/swap_agent/README.md b/submodules/moragents_dockers/agents/src/agents/token_swap/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/swap_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/token_swap/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py b/submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py similarity index 71% rename from submodules/moragents_dockers/agents/src/swap_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/token_swap/agent.py index 73f2694..9d09320 100644 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -1,11 +1,12 @@ import json import requests from flask import jsonify -from swap_agent.src import tools -from swap_agent.src.config import Config +from src.agents.token_swap import tools +from src.config import Config -class SwapAgent: + +class TokenSwapAgent: def __init__(self, config, llm, llm_ollama, embeddings, flask_app): self.llm = llm self.flask_app = flask_app @@ -21,14 +22,18 @@ def check_allowance(self, token_address, wallet_address, chain_id): url = self.api_request_url( "/approve/allowance", {"tokenAddress": token_address, "walletAddress": wallet_address}, - chain_id + chain_id, ) response = requests.get(url, headers=Config.HEADERS) data = response.json() return data def approve_transaction(self, token_address, chain_id, amount=None): - query_params = {"tokenAddress": token_address, "amount": amount} if amount else {"tokenAddress": token_address} + query_params = ( + {"tokenAddress": token_address, "amount": amount} + if amount + else {"tokenAddress": token_address} + ) url = self.api_request_url("/approve/transaction", query_params, chain_id) response = requests.get(url, headers=Config.HEADERS) transaction = response.json() @@ -49,7 +54,7 @@ def get_response(self, message, chain_id, wallet_address): "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " "request is ambiguous. you only need the value of token1 we dont need the value of token2. After " "starting from scratch do not assume the name of token1 or token2" - ) + ), } ] prompt.extend(message) @@ -57,26 +62,38 @@ def get_response(self, message, chain_id, wallet_address): messages=prompt, tools=self.tools_provided, tool_choice="auto", - temperature=0.01 + temperature=0.01, ) if "tool_calls" in result["choices"][0]["message"].keys(): - func = result["choices"][0]["message"]["tool_calls"][0]['function'] + func = result["choices"][0]["message"]["tool_calls"][0]["function"] if func["name"] == "swap_agent": args = json.loads(func["arguments"]) tok1 = args["token1"] tok2 = args["token2"] value = args["value"] try: - res, role = tools.swap_coins(tok1, tok2, float(value), chain_id, wallet_address) - except (tools.InsufficientFundsError, tools.TokenNotFoundError, tools.SwapNotPossibleError) as e: + res, role = tools.swap_coins( + tok1, tok2, float(value), chain_id, wallet_address + ) + except ( + tools.InsufficientFundsError, + tools.TokenNotFoundError, + tools.SwapNotPossibleError, + ) as e: self.context = [] return str(e), "assistant", None return res, role, None - self.context.append({"role": "assistant", "content": result["choices"][0]["message"]['content']}) - return result["choices"][0]["message"]['content'], "assistant", "crypto swap agent" + self.context.append( + {"role": "assistant", "content": result["choices"][0]["message"]["content"]} + ) + return ( + result["choices"][0]["message"]["content"], + "assistant", + "crypto swap agent", + ) def get_status(self, flag, tx_hash, tx_type): - response = '' + response = "" if flag == "cancelled": response = f"The {tx_type} transaction has been cancelled." @@ -98,24 +115,34 @@ 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): try: data = request.get_json() - if 'prompt' in data: - prompt = data['prompt'] - wallet_address = data['wallet_address'] - chain_id = data['chain_id'] - response, role, next_turn_agent = self.generate_response(prompt, chain_id, wallet_address) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} + if "prompt" in data: + prompt = data["prompt"] + wallet_address = data["wallet_address"] + chain_id = data["chain_id"] + response, role, next_turn_agent = self.generate_response( + prompt, chain_id, wallet_address + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } else: return {"error": "Missing required parameters"}, 400 except Exception as e: @@ -124,10 +151,10 @@ def chat(self, request): def tx_status(self, request): try: data = request.get_json() - if 'status' in data: - prompt = data['status'] - tx_hash = data.get('tx_hash', '') - tx_type = data.get('tx_type', '') + if "status" in data: + prompt = data["status"] + tx_hash = data.get("tx_hash", "") + tx_type = data.get("tx_type", "") response = self.get_status(prompt, tx_hash, tx_type) return response else: @@ -138,9 +165,9 @@ def tx_status(self, request): def get_allowance(self, request): try: data = request.get_json() - if 'tokenAddress' in data: - token = data['tokenAddress'] - wallet_address = data['walletAddress'] + if "tokenAddress" in data: + token = data["tokenAddress"] + wallet_address = data["walletAddress"] chain_id = data["chain_id"] res = self.check_allowance(token, wallet_address, chain_id) return jsonify({"response": res}) @@ -152,10 +179,10 @@ def get_allowance(self, request): def approve(self, request): try: data = request.get_json() - if 'tokenAddress' in data: - token = data['tokenAddress'] - chain_id = data['chain_id'] - amount = data['amount'] + if "tokenAddress" in data: + token = data["tokenAddress"] + chain_id = data["chain_id"] + amount = data["amount"] res = self.approve_transaction(token, chain_id, amount) return jsonify({"response": res}) else: @@ -166,13 +193,13 @@ def approve(self, request): def swap(self, request): try: data = request.get_json() - if 'src' in data: - token1 = data['src'] - token2 = data['dst'] - wallet_address = data['walletAddress'] - amount = data['amount'] - slippage = data['slippage'] - chain_id = data['chain_id'] + if "src" in data: + token1 = data["src"] + token2 = data["dst"] + wallet_address = data["walletAddress"] + amount = data["amount"] + slippage = data["slippage"] + chain_id = data["chain_id"] swap_params = { "src": token1, "dst": token2, diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/config.py b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py new file mode 100644 index 0000000..2a73f47 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py @@ -0,0 +1,58 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + # Model configuration + MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" + MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" + MODEL_PATH = "model/" + MODEL_REVISION + DOWNLOAD_DIR = "model" + # API endpoints + INCH_URL = "https://api.1inch.dev/token" + QUOTE_URL = "https://api.1inch.dev/swap" + APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" + HEADERS = { + "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", + "accept": "application/json", + } + WEB3RPCURL = { + "56": "https://bsc-dataseed.binance.org", + "42161": "https://arb1.arbitrum.io/rpc", + "137": "https://polygon-rpc.com", + "1": "https://eth.llamarpc.com/", + "10": "https://mainnet.optimism.io", + "8453": "https://mainnet.base.org", + } + NATIVE_TOKENS = { + "137": "MATIC", + "56": "BNB", + "1": "ETH", + "42161": "ETH", + "10": "ETH", + "8453": "ETH", + } + ERC20_ABI = [ + { + "constant": True, + "inputs": [], + "name": "decimals", + "outputs": [{"name": "", "type": "uint8"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + ] + INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py similarity index 61% rename from submodules/moragents_dockers/agents/src/swap_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/token_swap/tools.py index 1800dfe..2e2c871 100644 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py @@ -2,52 +2,69 @@ import logging import time from web3 import Web3 -from swap_agent.src.config import Config + +from src.agents.token_swap.config import Config class InsufficientFundsError(Exception): pass + class TokenNotFoundError(Exception): pass + class SwapNotPossibleError(Exception): pass 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) + params = {"query": query, "limit": limit, "ignore_listed": ignore_listed} + response = requests.get( + Config.INCH_URL + endpoint, params=params, headers=Config.HEADERS + ) if response.status_code == 200: return response.json() else: logging.error(f"Failed to search tokens. Status code: {response.status_code}") return None -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 + +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: """Convert an amount in ETH to wei.""" return int(amount_in_eth * 10**18) + def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): native = Config.NATIVE_TOKENS # token1 is the native token if token1.lower() == native[str(chain_id)].lower(): - t1 = [{'symbol': native[str(chain_id)], 'address': Config.INCH_NATIVE_TOKEN_ADDRESS}] - t1_bal = get_token_balance(web3, wallet_address, '', Config.ERC20_ABI) + t1 = [ + { + "symbol": native[str(chain_id)], + "address": Config.INCH_NATIVE_TOKEN_ADDRESS, + } + ] + t1_bal = get_token_balance(web3, wallet_address, "", Config.ERC20_ABI) smallest_amount = eth_to_wei(amount) # token1 is an erc20 token @@ -56,12 +73,19 @@ 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) - smallest_amount = convert_to_smallest_unit(web3, amount, t1[0]['address']) + 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 if token2.lower() == native[str(chain_id)].lower(): - t2 = [{'symbol': native[str(chain_id)], 'address': Config.INCH_NATIVE_TOKEN_ADDRESS}] + t2 = [ + { + "symbol": native[str(chain_id)], + "address": Config.INCH_NATIVE_TOKEN_ADDRESS, + } + ] else: t2 = search_tokens(token2, chain_id) time.sleep(2) @@ -72,54 +96,64 @@ def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): if t1_bal < smallest_amount: raise InsufficientFundsError(f"Insufficient funds to perform the swap.") - return t1[0]['address'], t1[0]['symbol'], t2[0]['address'], t2[0]['symbol'] + return t1[0]["address"], t1[0]["symbol"], t2[0]["address"], t2[0]["symbol"] + 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) + params = {"src": token1, "dst": token2, "amount": int(amount_in_wei)} + response = requests.get( + Config.QUOTE_URL + endpoint, params=params, headers=Config.HEADERS + ) if response.status_code == 200: return response.json() else: logging.error(f"Failed to get quote. Status code: {response.status_code}") return None + def get_token_decimals(web3: Web3, token_address: str) -> int: if not token_address: return 18 # Assuming 18 decimals for the native gas token else: - contract = web3.eth.contract(address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI) + contract = web3.eth.contract( + address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI + ) return contract.functions.decimals().call() + def convert_to_smallest_unit(web3: Web3, amount: float, token_address: str) -> int: decimals = get_token_decimals(web3, token_address) - return int(amount * (10 ** decimals)) + 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) + return smallest_unit_amount / (10**decimals) 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 + t1_address = "" if t1_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t1_a smallest_unit_amount = convert_to_smallest_unit(web3, amount, t1_address) result = get_quote(t1_a, t2_a, smallest_unit_amount, chain_id) - + if result: price = result["dstAmount"] - t2_address = '' if t2_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t2_a + t2_address = "" if t2_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t2_a t2_quote = convert_to_readable_unit(web3, int(price), t2_address) else: - raise SwapNotPossibleError("Failed to generate a quote. Please ensure you're on the correct network.") + raise SwapNotPossibleError( + "Failed to generate a quote. Please ensure you're on the correct network." + ) return { "dst": t2_id, @@ -129,9 +163,10 @@ def swap_coins(token1, token2, amount, chain_id, wallet_address): "src_address": t1_a, "src_amount": amount, "approve_tx_cb": "/approve", - "swap_tx_cb": "/swap" + "swap_tx_cb": "/swap", }, "swap" + def get_tools(): """Return a list of tools for the agent.""" return [ @@ -145,19 +180,19 @@ def get_tools(): "properties": { "token1": { "type": "string", - "description": "name or address of the cryptocurrency to sell" + "description": "name or address of the cryptocurrency to sell", }, "token2": { "type": "string", - "description": "name or address of the cryptocurrency to buy" + "description": "name or address of the cryptocurrency to buy", }, "value": { "type": "string", - "description": "Value or amount of the cryptocurrency to sell" - } + "description": "Value or amount of the cryptocurrency to sell", + }, }, - "required": ["token1", "token2", "value"] - } - } + "required": ["token1", "token2", "value"], + }, + }, } - ] \ No newline at end of file + ] diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/README.md b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py similarity index 100% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py similarity index 100% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py diff --git a/submodules/moragents_dockers/agents/src/agents/web_search/agent.py b/submodules/moragents_dockers/agents/src/agents/web_search/agent.py new file mode 100644 index 0000000..07b79c0 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/web_search/agent.py @@ -0,0 +1,100 @@ +import logging +import time +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +from bs4 import BeautifulSoup + +# Configure logging +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + + +class SearchAgent: + def __init__(self, config, llm, llm_ollama, embeddings, flask_app): + self.llm = llm + self.flask_app = flask_app + self.config = config + self.last_search_term = None + + def perform_search(self, search_term=None): + # State management for search regeneration purposes + if search_term is not None: + self.last_search_term = search_term + elif self.last_search_term is None: + logger.warning("No search term available for web search") + return "Web search failed. Please provide a search term." + else: + search_term = self.last_search_term + + logger.info(f"Performing web search for: {search_term}") + + # Set up Chrome options for headless browsing + chrome_options = Options() + chrome_options.add_argument("--headless") + + # Initialize the webdriver + driver = webdriver.Chrome(options=chrome_options) + + try: + # Navigate to Google + driver.get("https://www.google.com") + + # Find the search box and enter the search term + search_box = driver.find_element(By.NAME, "q") + search_box.send_keys(search_term) + search_box.send_keys(Keys.RETURN) + + # Wait for the results to load + time.sleep(2) + + # Get the page source and parse it with BeautifulSoup + soup = BeautifulSoup(driver.page_source, "html.parser") + + # Find the search results + search_results = soup.find_all("div", class_="g") + + # Process and format the results + formatted_results = [] + for result in search_results[:5]: # Limit to top 5 results + result_text = result.get_text(strip=True) + formatted_results.append(f"Result:\n{result_text}") + + return "\n\n".join(formatted_results) + + except Exception as e: + logger.error(f"Error performing web search: {str(e)}") + return f"Error performing web search: {str(e)}" + + finally: + # Close the browser + driver.quit() + + def chat(self, request): + try: + data = request.get_json() + logger.info(f"Received chat request: {data}") + if "prompt" in data: + prompt = data["prompt"] + action = data.get("action", "search") + logger.debug(f"Extracted prompt: {prompt}, action: {action}") + + if action == "search": + logger.info( + f"Performing web search for prompt: {prompt['content']}" + ) + search_results = self.perform_search(prompt["content"]) + logger.info(f"Search results: {search_results}") + return {"role": "assistant", "content": search_results} + else: + logger.error(f"Invalid action received: {action}") + return {"error": "Invalid action"}, 400 + else: + logger.error("Missing 'prompt' in chat request data") + return {"error": "Missing parameters"}, 400 + except Exception as e: + logger.exception(f"Unexpected error in chat method: {str(e)}") + return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index dd84abe..465e921 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -1,16 +1,21 @@ import os import logging import time -from functools import wraps -from config import Config -from llama_cpp import Llama + from flask_cors import CORS from flask import Flask, request, jsonify + +from fastapi import FastAPI, HTTPException, Request +from fastapi.middleware.cors import CORSMiddleware + from langchain_community.llms import Ollama -from delegator import Delegator -from llama_cpp.llama_tokenizer import LlamaHFTokenizer from langchain_community.embeddings import OllamaEmbeddings +from src.config import Config +from src.delegator import Delegator +from src.agent_manager import AgentManager +from src.models.messages import ChatRequest + # Constants INITIAL_MESSAGE = { "role": "assistant", @@ -28,72 +33,48 @@ logger = logging.getLogger(__name__) -def load_llm(): - logger.info("Loading LLM model") - try: - llm = Llama( - model_path=Config.MODEL_PATH, - chat_format="functionary-v2", - tokenizer=LlamaHFTokenizer.from_pretrained( - "meetkai/functionary-small-v2.4-GGUF" - ), - n_gpu_layers=-1, # Use all available GPU layers - n_batch=1024, # Increase batch size for faster processing - n_ctx=1024, # Increase context size for better performance - verbose=False, # Disable verbose output for speed - use_mlock=True, # Lock memory to prevent swapping - use_mmap=True, # Use memory mapping for faster loading - n_threads=16, # Increase number of threads for more parallel processing - ) - logger.info("LLM model loaded successfully") - return llm - except Exception as e: - logger.error(f"Error loading LLM model: {str(e)}") - raise - - -app = Flask(__name__) -CORS(app) +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) upload_state = False os.makedirs(UPLOAD_FOLDER, exist_ok=True) app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER app.config["MAX_CONTENT_LENGTH"] = Config.MAX_UPLOAD_LENGTH -try: - llm = load_llm() -except TimeoutError: - logger.error("LLM loading timed out") - llm = None -except Exception as e: - logger.error(f"Failed to load LLM: {str(e)}") - llm = None - -llm_ollama = Ollama(model="llama3.1", base_url=Config.OLLAMA_URL) +llm_ollama = Ollama(model="llama3.2", base_url=Config.OLLAMA_URL) embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=Config.OLLAMA_URL) -delegator = Delegator(Config.DELEGATOR_CONFIG, llm, llm_ollama, embeddings, app) +delegator = Delegator(Config.DELEGATOR_CONFIG, llm_ollama, llm_ollama, embeddings, app) +agent_manager = AgentManager() messages = [INITIAL_MESSAGE] next_turn_agent = None -@app.route("/", methods=["POST"]) -def chat(): - global next_turn_agent, messages - data = request.get_json() - logger.info(f"Received chat request: {data}") +@app.post("/chat") +async def chat(chat_request: ChatRequest): + user_id = chat_request.user_id + prompt = chat_request.prompt.dict() + + logger.info(f"Received chat request for user {user_id}: {prompt}") try: - current_agent = None - if "prompt" in data: - prompt = data["prompt"] - messages.append(prompt) + active_agent = agent_manager.get_active_agent(user_id) - if not next_turn_agent: - logger.info("No next turn agent, getting delegator response") + if not active_agent: + logger.info( + f"No active agent for user {user_id}, getting delegator response" + ) start_time = time.time() - result = delegator.get_delegator_response(prompt, upload_state) + result = delegator.get_delegator_response( + prompt["content"], False + ) # Assuming upload_state is False end_time = time.time() logger.info(f"Delegator response time: {end_time - start_time:.2f} seconds") logger.info(f"Delegator response: {result}") @@ -102,47 +83,36 @@ def chat(): logger.error(f"Missing 'next' key in delegator response: {result}") raise ValueError("Invalid delegator response: missing 'next' key") - next_agent = result["next"] - current_agent, response_swap = delegator.delegate_chat(next_agent, request) - else: - logger.info(f"Delegating chat to next turn agent: {next_turn_agent}") - current_agent, response_swap = delegator.delegate_chat( - next_turn_agent, request - ) - - # Handle both dictionary and tuple returns from delegate_chat - response, status_code = ( - response_swap if isinstance(response_swap, tuple) else (response_swap, 200) - ) + active_agent = result["next"] + agent_manager.set_active_agent(user_id, active_agent) - # If response_swap is an error, reset next_turn_agent - next_turn_agent = ( - response_swap.get("next_turn_agent") - if isinstance(response_swap, dict) - else None + logger.info( + f"Delegating chat to active agent for user {user_id}: {active_agent}" ) + current_agent, response = delegator.delegate_chat(active_agent, chat_request) if isinstance(response, dict) and "role" in response and "content" in response: response_with_agent = response.copy() response_with_agent["agentName"] = current_agent or "Unknown" - messages.append(response_with_agent) - - logger.info("Sending response: %s", response_with_agent) - return jsonify(response_with_agent), status_code + logger.info(f"Sending response for user {user_id}: {response_with_agent}") + return response_with_agent else: - logger.error(f"Invalid response format: {response}") - return jsonify({"error": "Invalid response format"}), 500 + logger.error(f"Invalid response format for user {user_id}: {response}") + raise HTTPException(status_code=500, detail="Invalid response format") except TimeoutError: - logger.error("Chat request timed out") - return jsonify({"error": "Request timed out"}), 504 + logger.error(f"Chat request timed out for user {user_id}") + raise HTTPException(status_code=504, detail="Request timed out") except ValueError as ve: - logger.error(f"Input formatting error: {str(ve)}") - return jsonify({"error": str(ve)}), 400 + logger.error(f"Input formatting error for user {user_id}: {str(ve)}") + raise HTTPException(status_code=400, detail=str(ve)) except Exception as e: - logger.error(f"Error in chat route: {str(e)}", exc_info=True) - return jsonify({"error": str(e)}), 500 + logger.error(f"Error in chat route for user {user_id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +# Example curl command for the / endpoint @app.route("/tx_status", methods=["POST"]) @@ -215,14 +185,16 @@ def set_x_api_key(): logger.info("Received set X API key request") return delegator.delegate_route("tweet sizzler agent", request, "set_x_api_key") + @app.route("/claim", methods=["POST"]) def claim_agent_claim(): logger.info("Received claim request") return delegator.delegate_route("claim agent", request, "claim") -@app.route("/claim_status", methods=["POST"]) -def update_claim_status(): - return claim_agent.claim_status(request) + +# @app.route("/claim_status", methods=["POST"]) +# def update_claim_status(): +# return claim_agent.claim_status(request) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/config.py b/submodules/moragents_dockers/agents/src/claim_agent/src/config.py deleted file mode 100644 index c7bbddc..0000000 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/config.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - - WEB3RPCURL = { - "1": "https://eth.llamarpc.com/" - } - MINT_FEE = 0.001 # in ETH - - DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" - DISTRIBUTION_ABI = [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver_", - "type": "address" - } - ], - "name": "claim", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user_", - "type": "address" - } - ], - "name": "getCurrentUserReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index f52f542..19d194c 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -8,54 +8,52 @@ class Config: # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" + OLLAMA_MODEL = "llama3.2:3b" OLLAMA_URL = "http://host.docker.internal:11434" + MAX_UPLOAD_LENGTH = 16 * 1024 * 1024 DELEGATOR_CONFIG = { "agents": [ { - "path": "rag_agent.src.agent", + "path": "src.agents.rag.agent", "class": "RagAgent", "description": "Handles general queries, information retrieval, and context-based questions. Use for any query that doesn't explicitly match other agents' specialties.", "name": "general purpose and context-based rag agent", "upload_required": True, }, { - "path": "data_agent.src.agent", - "class": "DataAgent", + "path": "src.agents.crypto_data.agent", + "class": "CryptoDataAgent", "description": "Crypto-specific. Provides real-time cryptocurrency data such as price, market cap, and fully diluted valuation (FDV).", "name": "crypto data agent", "upload_required": False, }, { - "path": "swap_agent.src.agent", - "class": "SwapAgent", + "path": "src.agents.token_swap.agent", + "class": "TokenSwapAgent", "description": "Handles cryptocurrency swapping operations. Use when the query explicitly mentions swapping, exchanging, or converting one cryptocurrency to another.", - "name": "crypto swap agent", + "name": "token swap agent", "upload_required": False, }, { - "path": "tweet_sizzler_agent.src.agent", + "path": "src.agents.tweet_sizzler.agent", "class": "TweetSizzlerAgent", "description": "Generates and posts engaging tweets. Use when the query explicitly mentions Twitter, tweeting, or X platform.", "name": "tweet sizzler agent", "upload_required": False, }, { - "path": "claim_agent.src.agent", - "class": "ClaimAgent", + "path": "src.agents.mor_claims.agent", + "class": "MorClaimsAgent", "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", - "name": "claim agent", + "name": "mor claims agent", "upload_required": False, }, { - "path": "reward_agent.src.agent", - "class": "RewardAgent", + "path": "src.agents.mor_rewards.agent", + "class": "MorRewardsAgent", "description": "Provides information about user's accrued MOR rewards or tokens. Use when the query is about checking or querying reward balances.", - "name": "reward agent", + "name": "mor rewards agent", "upload_required": False, }, ] diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py b/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py deleted file mode 100644 index 0c916ec..0000000 --- a/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" - DEFILLAMA_BASE_URL = "https://api.llama.fi" - 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." - 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." - API_ERROR_MESSAGE = "I can't seem to access the API at the moment." - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/delegator.py b/submodules/moragents_dockers/agents/src/delegator.py index bc4d605..8f631ea 100644 --- a/submodules/moragents_dockers/agents/src/delegator.py +++ b/submodules/moragents_dockers/agents/src/delegator.py @@ -1,13 +1,15 @@ import importlib import logging import json +from langchain_experimental.llms.ollama_functions import OllamaFunctions +from langchain.prompts import ChatPromptTemplate +from langchain.schema import SystemMessage, HumanMessage logger = logging.getLogger(__name__) # Configurable default agent DEFAULT_AGENT = "general purpose and context-based rag agent" - class Delegator: def __init__(self, config, llm, llm_ollama, embeddings, flask_app): self.llm = llm @@ -50,70 +52,64 @@ def get_delegator_response(self, prompt, upload_state): ) prompt_text = ( - "### Instruction: Your name is Morpheus. " + "Your name is Morpheus. " "Your primary function is to select the correct agent based on the user's input. " - "You MUST use the 'route' function to select an agent. " + "You MUST use the 'choose_appropriate_agent' function to select an agent. " "Available agents and their descriptions:\n" f"{agent_descriptions}\n" "Analyze the user's input and select the most appropriate agent. " - "Do not respond with any text other than calling the 'route' function. " - "###" + "Do not respond with any text other than calling the 'choose_appropriate_agent' function." ) + # Define the tool in the format expected by OllamaFunctions tools = [ { - "type": "function", - "function": { - "name": "route", - "description": "Choose which agent to run next", - "parameters": { - "type": "object", - "properties": { - "next": { - "type": "string", - "enum": available_agents, - "description": "The name of the next agent to run", - } - }, - "required": ["next"], + "name": "choose_appropriate_agent", + "description": "Choose which agent to run next", + "parameters": { + "type": "object", + "properties": { + "next": { + "type": "string", + "enum": available_agents, + "description": "The name of the next agent to run", + } }, + "required": ["next"], }, } ] - message_list = [ - {"role": "system", "content": prompt_text}, - prompt, - { - "role": "system", - "content": "Remember, you must use the 'route' function to select an agent.", - }, + # Initialize the AI model with the correct tool format + model = OllamaFunctions( + model="llama3.2:3b", format="json", temperature=0, base_url="http://host.docker.internal:11434" + ) + model = model.bind_tools(tools=tools, function_call={"name": "choose_appropriate_agent"}) + + # Create the messages for the AI model + print("prompt", prompt) + messages = [ + SystemMessage(content=prompt_text), + HumanMessage(content=prompt), ] - logger.info("Sending prompt to LLM: %s", prompt) - result = self.llm.create_chat_completion( - messages=message_list, - tools=tools, - tool_choice="auto", - temperature=0.3, - ) - logger.info("Received response from LLM: %s", result) + # Process the query + try: + result = model.invoke(messages) + logger.info(f"Model result: {result}") - response = result["choices"][0]["message"] + # Parse the result to extract the chosen agent + if hasattr(result, 'additional_kwargs') and 'function_call' in result.additional_kwargs: + function_call = result.additional_kwargs['function_call'] + if function_call['name'] == 'choose_appropriate_agent': + next_agent = json.loads(function_call['arguments'])['next'] + return {"next": next_agent} + + logger.warning("Unexpected response format, defaulting to general purpose agent") + return {"next": DEFAULT_AGENT} - if response.get("tool_calls"): - try: - function_args = json.loads( - response["tool_calls"][0]["function"]["arguments"] - ) - return {"next": function_args["next"]} - except (json.JSONDecodeError, KeyError) as e: - logger.error(f"Error parsing function call: {e}") - return {"next": DEFAULT_AGENT} - else: - logger.warning( - "No tool calls in LLM response, defaulting to general purpose agent" - ) + except Exception as e: + logger.error(f"Error during model invocation: {e}") return {"next": DEFAULT_AGENT} def delegate_chat(self, agent_name, request): diff --git a/submodules/moragents_dockers/agents/src/models/__init__.py b/submodules/moragents_dockers/agents/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/models/messages.py b/submodules/moragents_dockers/agents/src/models/messages.py new file mode 100644 index 0000000..bec583b --- /dev/null +++ b/submodules/moragents_dockers/agents/src/models/messages.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class ChatMessage(BaseModel): + role: str + content: str + + +class ChatRequest(BaseModel): + user_id: str + prompt: ChatMessage diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/config.py b/submodules/moragents_dockers/agents/src/reward_agent/src/config.py deleted file mode 100644 index ff7c6d6..0000000 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/config.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - - WEB3RPCURL = { - "1": "https://eth.llamarpc.com/", - } - - DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" - DISTRIBUTION_ABI = [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user_", - "type": "address" - } - ], - "name": "getCurrentUserReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/config.py b/submodules/moragents_dockers/agents/src/swap_agent/src/config.py deleted file mode 100644 index 2e57c81..0000000 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/config.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - INCH_URL = "https://api.1inch.dev/token" - QUOTE_URL = "https://api.1inch.dev/swap" - APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" - HEADERS = { "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", "accept": "application/json" } - WEB3RPCURL = {"56":"https://bsc-dataseed.binance.org","42161":"https://arb1.arbitrum.io/rpc","137":"https://polygon-rpc.com","1":"https://eth.llamarpc.com/","10":"https://mainnet.optimism.io","8453":"https://mainnet.base.org"} - NATIVE_TOKENS={"137":"MATIC","56":"BNB","1":"ETH","42161":"ETH","10":"ETH","8453":"ETH"} - ERC20_ABI = [ - {"constant": True, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", "type": "function"}, - {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"} - ] - INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py b/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py deleted file mode 100644 index b91a88f..0000000 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - INCH_URL = "https://api.1inch.dev/token" - QUOTE_URL = "https://api.1inch.dev/swap" - APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" - HEADERS = { "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", "accept": "application/json" } - WEB3RPCURL = {"56":"https://bsc-dataseed.binance.org","42161":"https://arb1.arbitrum.io/rpc","137":"https://polygon-rpc.com","1":"https://cloudflare-eth.com","10":"https://mainnet.optimism.io","8453":"https://mainnet.base.org"} - NATIVE_TOKENS={"137":"MATIC","56":"BNB","1":"ETH","42161":"ETH","10":"ETH","8453":"ETH"} - ERC20_ABI = [ - {"constant": True, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", "type": "function"}, - {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"} - ] - INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - \ No newline at end of file diff --git a/submodules/moragents_dockers/docker-compose-apple.yml b/submodules/moragents_dockers/docker-compose-apple.yml index 9cf6833..b06f976 100644 --- a/submodules/moragents_dockers/docker-compose-apple.yml +++ b/submodules/moragents_dockers/docker-compose-apple.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:apple-0.1.0 + image: lachsbagel/moragents_dockers-agents:apple-0.1.1 build: dockerfile: Dockerfile-apple context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:apple-0.1.0 + image: lachsbagel/moragents_dockers-nginx:apple-0.1.1 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/docker-compose.yml b/submodules/moragents_dockers/docker-compose.yml index 135b524..2eaa3fb 100644 --- a/submodules/moragents_dockers/docker-compose.yml +++ b/submodules/moragents_dockers/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:amd64-0.1.0 + image: lachsbagel/moragents_dockers-agents:amd64-0.1.1 build: dockerfile: Dockerfile context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:amd64-0.1.0 + image: lachsbagel/moragents_dockers-nginx:amd64-0.1.1 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/frontend/config.ts b/submodules/moragents_dockers/frontend/config.ts index 0f4d11a..647d724 100644 --- a/submodules/moragents_dockers/frontend/config.ts +++ b/submodules/moragents_dockers/frontend/config.ts @@ -1,25 +1,25 @@ -export const routerAddress = '0x111111125421cA6dc452d289314280a0f8842A65'; -export const oneInchNativeToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; +export const routerAddress = "0x111111125421cA6dc452d289314280a0f8842A65"; +export const oneInchNativeToken = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; export const availableAgents: { - [key: string]: { - name: string, - description: string, - endpoint: string, - requirements: { - connectedWallet: boolean - }, - supportsFiles?: boolean - - } + [key: string]: { + name: string; + description: string; + endpoint: string; + requirements: { + connectedWallet: boolean; + }; + supportsFiles?: boolean; + }; } = { - 'swap-agent': { - 'name': 'Morpheus', - 'description': 'performs multiple tasks crypto data agent,swap agent and rag agent', - 'endpoint': 'http://127.0.0.1:8080', - requirements: { - connectedWallet: true - }, - supportsFiles: true - } -} \ No newline at end of file + "swap-agent": { + name: "Morpheus", + description: + "performs multiple tasks crypto data agent,swap agent and rag agent", + endpoint: "http://127.0.0.1:8080", + requirements: { + connectedWallet: true, + }, + supportsFiles: true, + }, +}; diff --git a/submodules/moragents_dockers/frontend/package-lock.json b/submodules/moragents_dockers/frontend/package-lock.json index 217bdae..e186c02 100644 --- a/submodules/moragents_dockers/frontend/package-lock.json +++ b/submodules/moragents_dockers/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "Morpheus AI", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Morpheus AI", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@chakra-ui/icons": "^2.1.1", diff --git a/submodules/moragents_dockers/frontend/package.json b/submodules/moragents_dockers/frontend/package.json index b7ecc2c..cbb090a 100644 --- a/submodules/moragents_dockers/frontend/package.json +++ b/submodules/moragents_dockers/frontend/package.json @@ -1,7 +1,7 @@ { "name": "Morpheus AI", "private": true, - "version": "0.1.0", + "version": "0.1.1", "scripts": { "dev": "next dev", "build": "next build", diff --git a/submodules/moragents_dockers/frontend/services/backendClient.ts b/submodules/moragents_dockers/frontend/services/backendClient.ts index e1d936e..a81b7eb 100644 --- a/submodules/moragents_dockers/frontend/services/backendClient.ts +++ b/submodules/moragents_dockers/frontend/services/backendClient.ts @@ -5,7 +5,6 @@ export type ChatMessageBase = { role: "user" | "assistant" | "swap" | "claim"; }; - export type UserOrAssistantMessage = ChatMessageBase & { role: "user" | "assistant"; content: string; @@ -90,7 +89,11 @@ export type ClaimMessage = ChatMessageBase & { }; // Update the ChatMessage type to include ClaimMessage -export type ChatMessage = UserOrAssistantMessage | SwapMessage | SystemMessage | ClaimMessage; +export type ChatMessage = + | UserOrAssistantMessage + | SwapMessage + | SystemMessage + | ClaimMessage; export type ChatsListItem = { index: number; // index at chats array @@ -338,4 +341,4 @@ export const sendClaimStatus = async ( role: responseBody.data.role, content: responseBody.data.content, } as ChatMessage; -}; \ No newline at end of file +}; diff --git a/wizard_windows.iss b/wizard_windows.iss index 34d0565..ab2579e 100644 --- a/wizard_windows.iss +++ b/wizard_windows.iss @@ -1,6 +1,6 @@ [Setup] AppName=MORagents -AppVersion=0.1.0 +AppVersion=0.1.1 DefaultDirName={commonpf}\MORagents OutputDir=.\MORagentsWindowsInstaller OutputBaseFilename=MORagentsSetup @@ -27,7 +27,7 @@ Filename: "{tmp}\DockerDesktopInstaller.exe"; Parameters: "install"; StatusMsg: Filename: "{tmp}\OllamaSetup.exe"; StatusMsg: "Installing Ollama..."; Flags: waituntilterminated Filename: "{app}\LICENSE.md"; Description: "View License Agreement"; Flags: postinstall shellexec skipifsilent Filename: "{app}\MORagents.exe"; Description: "Launch MORagents"; Flags: postinstall nowait skipifsilent unchecked -Filename: "cmd.exe"; Parameters: "/c ollama pull llama3.1"; StatusMsg: "Pulling llama3.1 model..."; Flags: runhidden waituntilterminated +Filename: "cmd.exe"; Parameters: "/c ollama pull llama3.2:3b"; StatusMsg: "Pulling llama3.2:3b model..."; Flags: runhidden waituntilterminated Filename: "cmd.exe"; Parameters: "/c ollama pull nomic-embed-text"; StatusMsg: "Pulling nomic-embed-text model..."; Flags: runhidden waituntilterminated [Code] From 9356935289f77c6aafaf6a16d0745507e7ad7cd6 Mon Sep 17 00:00:00 2001 From: danxyu Date: Mon, 7 Oct 2024 00:59:50 -0700 Subject: [PATCH 03/15] fix realtime search, implement chat history clearing --- .../moragents_dockers/agents/Dockerfile | 7 +- .../moragents_dockers/agents/Dockerfile-apple | 7 +- .../moragents_dockers/agents/requirements.txt | 14 +- .../agents/src/agent_manager.py | 17 +- .../agents/src/agents/crypto_data/agent.py | 95 +++++----- .../agents/src/agents/mor_claims/agent.py | 34 ++-- .../agents/src/agents/mor_rewards/agent.py | 9 +- .../agents/src/agents/rag/agent.py | 86 +++++---- .../src/agents/realtime_search/__init__.py | 0 .../src/agents/realtime_search/agent.py | 96 ++++++++++ .../agents/src/agents/token_swap/agent.py | 170 +++++++++--------- .../agents/src/agents/tweet_sizzler/agent.py | 79 ++++---- .../agents/src/agents/tweet_sizzler/config.py | 7 +- .../agents/src/agents/web_search/agent.py | 100 ----------- .../moragents_dockers/agents/src/app.py | 134 ++++++-------- .../moragents_dockers/agents/src/config.py | 7 + .../moragents_dockers/agents/src/delegator.py | 75 +++----- .../agents/src/models/messages.py | 1 - .../frontend/components/HeaderBar/index.tsx | 22 ++- .../frontend/components/Tweet/index.tsx | 4 +- .../frontend/pages/index.tsx | 148 ++++++++------- .../frontend/services/backendClient.ts | 28 +-- 22 files changed, 593 insertions(+), 547 deletions(-) create mode 100644 submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/agents/web_search/agent.py diff --git a/submodules/moragents_dockers/agents/Dockerfile b/submodules/moragents_dockers/agents/Dockerfile index 40444e0..14b0556 100644 --- a/submodules/moragents_dockers/agents/Dockerfile +++ b/submodules/moragents_dockers/agents/Dockerfile @@ -18,8 +18,5 @@ COPY . . # Expose the port your application listens on EXPOSE 5000 -# Set the environment variable for Flask -ENV FLASK_APP=src/app.py - -# Run the application -CMD ["flask", "run", "--host", "0.0.0.0"] \ No newline at end of file +# 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 diff --git a/submodules/moragents_dockers/agents/Dockerfile-apple b/submodules/moragents_dockers/agents/Dockerfile-apple index 55a4517..2e28f3b 100644 --- a/submodules/moragents_dockers/agents/Dockerfile-apple +++ b/submodules/moragents_dockers/agents/Dockerfile-apple @@ -18,8 +18,5 @@ COPY . . # Expose the port your application listens on EXPOSE 5000 -# Set the environment variable for Flask -ENV FLASK_APP=src/app.py - -# Run the application -CMD ["flask", "run", "--host", "0.0.0.0"] \ No newline at end of file +# 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 diff --git a/submodules/moragents_dockers/agents/requirements.txt b/submodules/moragents_dockers/agents/requirements.txt index 4e2003d..b74671b 100644 --- a/submodules/moragents_dockers/agents/requirements.txt +++ b/submodules/moragents_dockers/agents/requirements.txt @@ -2,14 +2,16 @@ llama-cpp-python==0.2.90 sentencepiece==0.2.0 protobuf==5.27.2 scikit-learn==1.5.1 -flask==2.2.2 +fastapi==0.115.0 Werkzeug==2.2.2 -flask-cors==4.0.1 web3==6.20.1 pymupdf==1.22.5 faiss-cpu==1.8.0.post1 -langchain-text-splitters==0.2.2 -langchain-core==0.2.24 -langchain-community==0.2.10 -langchain_experimental==0.3.2 +langchain-text-splitters==0.3.0 +langchain-core==0.3.9 +langchain-community==0.3.1 +langchain-ollama==0.2.0 tweepy==4.14.0 +uvicorn==0.31.0 +python-multipart==0.0.12 +beautifulsoup4==4.12.3 diff --git a/submodules/moragents_dockers/agents/src/agent_manager.py b/submodules/moragents_dockers/agents/src/agent_manager.py index b1530dc..8528878 100644 --- a/submodules/moragents_dockers/agents/src/agent_manager.py +++ b/submodules/moragents_dockers/agents/src/agent_manager.py @@ -1,12 +1,15 @@ class AgentManager: def __init__(self): - self.active_agents = {} + self.active_agent = None - def get_active_agent(self, user_id): - return self.active_agents.get(user_id) + def get_active_agent(self): + return self.active_agent - def set_active_agent(self, user_id, agent_name): - self.active_agents[user_id] = agent_name + def set_active_agent(self, agent_name): + self.active_agent = agent_name - def clear_active_agent(self, user_id): - self.active_agents.pop(user_id, None) + def clear_active_agent(self): + self.active_agent = None + + +agent_manager = AgentManager() 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 4794a76..c5e9cc8 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py @@ -2,68 +2,81 @@ import logging from src.agents.crypto_data import tools +from src.models.messages import ChatRequest +from src.agent_manager import agent_manager logger = logging.getLogger(__name__) class CryptoDataAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm + self.embeddings = embeddings self.tools_provided = tools.get_tools() def get_response(self, message): + system_prompt = ( + "Don't make assumptions about the value of the arguments for the function " + "they should always be supplied by the user and do not alter the value of the arguments. " + "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " + "request is ambiguous." + ) + messages = [ - { - "role": "system", - "content": ( - "Don't make assumptions about the value of the arguments for the function " - "they should always be supplied by the user and do not alter the value of the arguments. " - "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " - "request is ambiguous." - ), - } + {"role": "system", "content": system_prompt}, ] messages.extend(message) + logger.info("Sending request to LLM with %d messages", len(messages)) - result = self.llm.create_chat_completion( - messages=messages, tools=self.tools_provided, tool_choice="auto" - ) - logger.info("Received response from LLM: %s", result) + llm_with_tools = self.llm.bind_tools(self.tools_provided) - if "tool_calls" in result["choices"][0]["message"].keys(): - func = result["choices"][0]["message"]["tool_calls"][0]["function"] - logger.info("LLM suggested using tool: %s", func["name"]) - args = json.loads(func["arguments"]) - if func["name"] == "get_price": - return tools.get_coin_price_tool(args["coin_name"]), "assistant" - elif func["name"] == "get_floor_price": - return tools.get_nft_floor_price_tool(args["nft_name"]), "assistant" - elif func["name"] == "get_fdv": - return ( - tools.get_fully_diluted_valuation_tool(args["coin_name"]), - "assistant", - ) - elif func["name"] == "get_tvl": - return ( - tools.get_protocol_total_value_locked_tool(args["protocol_name"]), - "assistant", - ) - elif func["name"] == "get_market_cap": - return tools.get_coin_market_cap_tool(args["coin_name"]), "assistant" - else: - logger.info("LLM provided a direct response without using tools") - return result["choices"][0]["message"]["content"], "assistant" + try: + result = llm_with_tools.invoke(messages) + logger.info("Received response from LLM: %s", result) + + if result.tool_calls: + tool_call = result.tool_calls[0] + func_name = tool_call.get("name") + args = tool_call.get("args") + logger.info("LLM suggested using tool: %s", func_name) + + if func_name == "get_price": + return tools.get_coin_price_tool(args["coin_name"]), "assistant" + elif func_name == "get_floor_price": + return tools.get_nft_floor_price_tool(args["nft_name"]), "assistant" + elif func_name == "get_fdv": + return ( + tools.get_fully_diluted_valuation_tool(args["coin_name"]), + "assistant", + ) + elif func_name == "get_tvl": + return ( + tools.get_protocol_total_value_locked_tool( + args["protocol_name"] + ), + "assistant", + ) + elif func_name == "get_market_cap": + return ( + tools.get_coin_market_cap_tool(args["coin_name"]), + "assistant", + ) + else: + logger.info("LLM provided a direct response without using tools") + return result.content, "assistant" + except Exception as e: + logger.error(f"Error in get_response: {str(e)}") + return f"An error occurred: {str(e)}", "assistant" def generate_response(self, prompt): response, role = self.get_response([prompt]) return response, role - def chat(self, request): + def chat(self, request: ChatRequest): try: - data = request.get_json() + data = request.dict() if "prompt" in data: prompt = data["prompt"] logger.info( 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 2f98c7d..3adacb3 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py @@ -1,20 +1,25 @@ from src.agents.mor_claims import tools +from src.models.messages import ChatRequest +from src.agent_manager import agent_manager class MorClaimsAgent: - def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): + def __init__(self, agent_info, llm, embeddings): self.agent_info = agent_info self.llm = llm + self.embeddings = embeddings self.tools_provided = tools.get_tools() self.conversation_state = {} - def get_response(self, message, wallet_address): + def _get_response(self, message, wallet_address): if wallet_address not in self.conversation_state: self.conversation_state[wallet_address] = {"state": "initial"} state = self.conversation_state[wallet_address]["state"] if state == "initial": + agent_manager.set_active_agent(self.agent_info["name"]) + rewards = { 0: tools.get_current_user_reward(wallet_address, 0), 1: tools.get_current_user_reward(wallet_address, 1), @@ -93,13 +98,13 @@ def prepare_transactions(self, wallet_address): None, ) - def chat(self, request): + def chat(self, request: ChatRequest): try: - data = request.get_json() + data = request.dict() 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( + response, role, next_turn_agent = self._get_response( [prompt], wallet_address ) return { @@ -112,30 +117,31 @@ def chat(self, request): except Exception as e: return {"Error": str(e)}, 500 - def claim(self, request): + def claim(self, request: ChatRequest): try: - data = request.get_json() + data = request.dict() wallet_address = data["wallet_address"] transactions = self.conversation_state[wallet_address]["transactions"] - return jsonify({"transactions": transactions}) + agent_manager.clear_active_agent() + return {"transactions": transactions} except Exception as e: - return jsonify({"error": str(e)}), 500 + return {"error": str(e)}, 500 - def claim_status(self, request): + def claim_status(self, request: ChatRequest): try: - data = request.get_json() + data = request.dict() wallet_address = data.get("wallet_address") transaction_hash = data.get("transaction_hash") status = data.get("status") if not all([wallet_address, transaction_hash, status]): - return jsonify({"error": "Missing required parameters"}), 400 + return {"error": "Missing required parameters"}, 400 # Generate and return the status message response = self.get_status(status, transaction_hash, "claim") - return jsonify(response), 200 + return response, 200 except Exception as e: - return jsonify({"error": str(e)}), 500 + return {"error": str(e)}, 500 def get_status(self, flag, tx_hash, tx_type): response = "" 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 5a32d46..d3dfff1 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py @@ -1,17 +1,16 @@ import logging from src.agents.mor_rewards import tools +from src.models.messages import ChatRequest logger = logging.getLogger(__name__) class MorRewardsAgent: - def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): + def __init__(self, agent_info, llm, embeddings): self.agent_info = agent_info self.llm = llm - self.llm_ollama = llm_ollama self.embeddings = embeddings - self.flask_app = flask_app self.tools_provided = tools.get_tools() def get_response(self, message, wallet_address): @@ -37,9 +36,9 @@ def get_response(self, message, wallet_address): None, ) - def chat(self, request): + def chat(self, request: ChatRequest): try: - data = request.get_json() + data = request.dict() if "prompt" in data and "wallet_address" in data: prompt = data["prompt"] wallet_address = data["wallet_address"] diff --git a/submodules/moragents_dockers/agents/src/agents/rag/agent.py b/submodules/moragents_dockers/agents/src/agents/rag/agent.py index 0880cf0..cbe9bf6 100644 --- a/submodules/moragents_dockers/agents/src/agents/rag/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/rag/agent.py @@ -1,25 +1,28 @@ import os import logging + +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 import RecursiveCharacterTextSplitter -from langchain.chains.combine_documents import create_stuff_documents_chain -from langchain.chains import create_retrieval_chain -from werkzeug.utils import secure_filename +from langchain_text_splitters.character import RecursiveCharacterTextSplitter +from src.models.messages import ChatRequest logging.basicConfig(level=logging.DEBUG) +UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") class RagAgent: - def __init__(self, config, llm, llm_ollama, embeddings,flask_app): - self.llm = llm_ollama - self.flask_app = flask_app - self.embedding=embeddings + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm + self.embedding = embeddings self.agent = None - self.messages = [{'role': "assistant", "content": "Please upload a file to begin"}] + self.messages = [ + {"role": "assistant", "content": "Please upload a file to begin"} + ] self.upload_state = False self.prompt = ChatPromptTemplate.from_template( """ @@ -32,59 +35,68 @@ def __init__(self, config, llm, llm_ollama, embeddings,flask_app): Question: {input} """ ) - self.UPLOAD_FOLDER = flask_app.config['UPLOAD_FOLDER'] self.max_size = 5 * 1024 * 1024 self.retriever = None - - def handle_file_upload(self,file): - if not os.path.exists(self.UPLOAD_FOLDER): - os.makedirs(self.UPLOAD_FOLDER, exist_ok=True) + def handle_file_upload(self, file): + if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER, exist_ok=True) filename = secure_filename(file.filename) - file.save(os.path.join(self.UPLOAD_FOLDER, filename)) - # DocumentToolsGenerator class instantiation - loader = PyMuPDFLoader(os.path.join(self.UPLOAD_FOLDER,filename)) + file.save(os.path.join(UPLOAD_FOLDER, filename)) + + # DocumentToolsGenerator class instantiation + loader = PyMuPDFLoader(os.path.join(UPLOAD_FOLDER, filename)) docs = loader.load() - text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024,chunk_overlap=20,length_function=len,is_separator_regex=False) + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1024, + chunk_overlap=20, + length_function=len, + is_separator_regex=False, + ) split_documents = text_splitter.split_documents(docs) vector_store = FAISS.from_documents(split_documents, self.embedding) self.retriever = vector_store.as_retriever(search_kwargs={"k": 7}) - - def upload_file(self,request): - if 'file' not in request.files: - return {'error': 'No file part'}, 400 - file = request.files['file'] - if file.filename == '': - return {'error': 'No selected file'}, 400 + def upload_file(self, request: ChatRequest): + data = request.dict() + if "file" not in data: + return {"error": "No file part"}, 400 + file = data["file"] + if file.filename == "": + return {"error": "No selected file"}, 400 # Check file size file.seek(0, os.SEEK_END) file_length = file.tell() file.seek(0, 0) # Reset the file pointer to the beginning if file_length > self.max_size: - return {"role": "assistant", "content": 'please use a file less than 5 MB'} + return {"role": "assistant", "content": "please use a file less than 5 MB"} try: self.handle_file_upload(file) self.upload_state = True - return {"role": "assistant", "content": 'You have successfully uploaded the text'} + return { + "role": "assistant", + "content": "You have successfully uploaded the text", + } except Exception as e: - logging.error(f'Error during file upload: {str(e)}') - return {'error': str(e)}, 500 + logging.error(f"Error during file upload: {str(e)}") + return {"error": str(e)}, 500 - def chat(self,request): + def chat(self, request: ChatRequest): try: - data = request.get_json() - if 'prompt' in data: - prompt = data['prompt']['content'] + data = request.dict() + if "prompt" in data: + prompt = data["prompt"]["content"] role = "assistant" retrieved_docs = self.retriever.invoke(prompt) - formatted_context = "\n\n".join(doc.page_content for doc in retrieved_docs) + formatted_context = "\n\n".join( + doc.page_content for doc in retrieved_docs + ) formatted_prompt = f"Question: {prompt}\n\nContext: {formatted_context}" - answer=self.llm(formatted_prompt) - response = answer if self.upload_state else "please upload a file first" + answer = self.llm(formatted_prompt) + response = answer if self.upload_state else "please upload a file first" return {"role": role, "content": response} else: return {"error": "Missing required parameters"}, 400 except Exception as e: - logging.error(f'Error in chat endpoint: {str(e)}') + logging.error(f"Error in chat endpoint: {str(e)}") return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py b/submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py new file mode 100644 index 0000000..aaf4956 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py @@ -0,0 +1,96 @@ +import logging +import requests + +from bs4 import BeautifulSoup +from src.models.messages import ChatRequest + +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + + +class RealtimeSearchAgent: + def __init__(self, config, llm, embeddings): + self.config = config + self.llm = llm + self.embeddings = embeddings + self.last_search_term = None + + def perform_search(self, search_term=None): + if search_term is not None: + self.last_search_term = search_term + elif self.last_search_term is None: + logger.warning("No search term available for web search") + return "Web search failed. Please provide a search term." + else: + search_term = self.last_search_term + + logger.info(f"Performing web search for: {search_term}") + + try: + url = f"https://www.google.com/search?q={search_term}" + headers = {"User-Agent": USER_AGENT} + response = requests.get(url, headers=headers) + response.raise_for_status() + + soup = BeautifulSoup(response.text, "html.parser") + + search_results = soup.find_all("div", class_="g") + + formatted_results = [] + for result in search_results[:5]: + result_text = result.get_text(strip=True) + formatted_results.append(f"Result:\n{result_text}") + + return "\n\n".join(formatted_results) + + except requests.RequestException as e: + logger.error(f"Error performing web search: {str(e)}") + return f"Error performing web search: {str(e)}" + + def synthesize_answer(self, search_term, search_results): + logger.info("Synthesizing answer from search results") + messages = [ + { + "role": "system", + "content": "You are a helpful assistant that synthesizes information from web search results to answer user queries.", + }, + { + "role": "user", + "content": f"Based on the following search results for the query '{search_term}', provide a concise and informative answer:\n\n{search_results}", + }, + ] + + try: + result = self.llm.invoke(messages) + logger.info(f"Received response from LLM: {result}") + return result.content.strip() + except Exception as e: + logger.error(f"Error synthesizing answer: {str(e)}") + raise + + def chat(self, request: ChatRequest): + try: + data = request.dict() + logger.info(f"Received chat request: {data}") + if "prompt" in data: + prompt = data["prompt"] + search_term = prompt["content"] + logger.info(f"Performing web search for prompt: {search_term}") + + search_results = self.perform_search(search_term) + logger.info(f"Search results obtained") + + synthesized_answer = self.synthesize_answer(search_term, search_results) + logger.info(f"Synthesized answer: {synthesized_answer}") + + return {"role": "assistant", "content": synthesized_answer} + else: + logger.error("Missing 'prompt' in chat request data") + return {"error": "Missing parameters"}, 400 + except Exception as e: + logger.exception(f"Unexpected error in chat method: {str(e)}") + return {"Error": str(e)}, 500 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 9d09320..536ea86 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -1,16 +1,20 @@ import json import requests -from flask import jsonify +import logging from src.agents.token_swap import tools -from src.config import Config +from src.agents.token_swap.config import Config +from src.models.messages import ChatRequest +from src.agent_manager import agent_manager + +logger = logging.getLogger(__name__) class TokenSwapAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm + self.embeddings = embeddings self.tools_provided = tools.get_tools() self.context = [] @@ -45,53 +49,57 @@ def build_tx_for_swap(self, swap_params, chain_id): return swap_transaction def get_response(self, message, chain_id, wallet_address): - prompt = [ - { - "role": "system", - "content": ( - "Don't make assumptions about the value of the arguments for the function " - "they should always be supplied by the user and do not alter the value of the arguments. " - "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " - "request is ambiguous. you only need the value of token1 we dont need the value of token2. After " - "starting from scratch do not assume the name of token1 or token2" - ), - } - ] - prompt.extend(message) - result = self.llm.create_chat_completion( - messages=prompt, - tools=self.tools_provided, - tool_choice="auto", - temperature=0.01, - ) - if "tool_calls" in result["choices"][0]["message"].keys(): - func = result["choices"][0]["message"]["tool_calls"][0]["function"] - if func["name"] == "swap_agent": - args = json.loads(func["arguments"]) - tok1 = args["token1"] - tok2 = args["token2"] - value = args["value"] - try: - res, role = tools.swap_coins( - tok1, tok2, float(value), chain_id, wallet_address - ) - except ( - tools.InsufficientFundsError, - tools.TokenNotFoundError, - tools.SwapNotPossibleError, - ) as e: - self.context = [] - return str(e), "assistant", None - return res, role, None - self.context.append( - {"role": "assistant", "content": result["choices"][0]["message"]["content"]} - ) - return ( - result["choices"][0]["message"]["content"], - "assistant", - "crypto swap agent", + system_prompt = ( + "Don't make assumptions about the value of the arguments for the function " + "they should always be supplied by the user and do not alter the value of the arguments. " + "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " + "request is ambiguous. you only need the value of token1 we dont need the value of token2. After " + "starting from scratch do not assume the name of token1 or token2" ) + messages = [ + {"role": "system", "content": system_prompt}, + ] + messages.extend(message) + + logger.info("Sending request to LLM with %d messages", len(messages)) + + llm_with_tools = self.llm.bind_tools(self.tools_provided) + + try: + result = llm_with_tools.invoke(messages) + logger.info("Received response from LLM: %s", result) + + if result.tool_calls: + tool_call = result.tool_calls[0] + func_name = tool_call.name + args = tool_call.args + logger.info("LLM suggested using tool: %s", func_name) + + if func_name == "swap_agent": + args_dict = json.loads(args) + tok1 = args_dict["token1"] + tok2 = args_dict["token2"] + value = args_dict["value"] + try: + res, role = tools.swap_coins( + tok1, tok2, float(value), chain_id, wallet_address + ) + except ( + tools.InsufficientFundsError, + tools.TokenNotFoundError, + tools.SwapNotPossibleError, + ) as e: + self.context = [] + return str(e), "assistant", None + return res, role, None + else: + logger.info("LLM provided a direct response without using tools") + return result.content, "assistant", "crypto swap agent" + except Exception as e: + logger.error(f"Error in get_response: {str(e)}") + return f"An error occurred: {str(e)}", "assistant", None + def get_status(self, flag, tx_hash, tx_type): response = "" @@ -128,9 +136,9 @@ def generate_response(self, prompt, chain_id, wallet_address): ) return response, role, next_turn_agent - def chat(self, request): + def chat(self, request: ChatRequest): + data = request.dict() try: - data = request.get_json() if "prompt" in data: prompt = data["prompt"] wallet_address = data["wallet_address"] @@ -148,9 +156,8 @@ def chat(self, request): except Exception as e: return {"Error": str(e)}, 500 - def tx_status(self, request): + def tx_status(self, data): try: - data = request.get_json() if "status" in data: prompt = data["status"] tx_hash = data.get("tx_hash", "") @@ -162,44 +169,41 @@ def tx_status(self, request): except Exception as e: return {"Error": str(e)}, 500 - def get_allowance(self, request): + def get_allowance(self, request_data): try: - data = request.get_json() - if "tokenAddress" in data: - token = data["tokenAddress"] - wallet_address = data["walletAddress"] - chain_id = data["chain_id"] + if "tokenAddress" in request_data: + token = request_data["tokenAddress"] + wallet_address = request_data["walletAddress"] + chain_id = request_data["chain_id"] res = self.check_allowance(token, wallet_address, chain_id) - return jsonify({"response": res}) + return {"response": res} else: - return jsonify({"error": "Missing required parameters"}), 400 + return {"error": "Missing required parameters"}, 400 except Exception as e: - return jsonify({"Error": str(e)}), 500 + return {"Error": str(e)}, 500 - def approve(self, request): + def approve(self, request_data): try: - data = request.get_json() - if "tokenAddress" in data: - token = data["tokenAddress"] - chain_id = data["chain_id"] - amount = data["amount"] + if "tokenAddress" in request_data: + token = request_data["tokenAddress"] + chain_id = request_data["chain_id"] + amount = request_data["amount"] res = self.approve_transaction(token, chain_id, amount) - return jsonify({"response": res}) + return {"response": res} else: - return jsonify({"error": "Missing required parameters"}), 400 + return {"error": "Missing required parameters"}, 400 except Exception as e: - return jsonify({"Error": str(e)}), 500 + return {"Error": str(e)}, 500 - def swap(self, request): + def swap(self, request_data): try: - data = request.get_json() - if "src" in data: - token1 = data["src"] - token2 = data["dst"] - wallet_address = data["walletAddress"] - amount = data["amount"] - slippage = data["slippage"] - chain_id = data["chain_id"] + if "src" in request_data: + token1 = request_data["src"] + token2 = request_data["dst"] + wallet_address = request_data["walletAddress"] + amount = request_data["amount"] + slippage = request_data["slippage"] + chain_id = request_data["chain_id"] swap_params = { "src": token1, "dst": token2, @@ -212,6 +216,6 @@ def swap(self, request): swap_transaction = self.build_tx_for_swap(swap_params, chain_id) return swap_transaction else: - return jsonify({"error": "Missing required parameters"}), 400 + return {"error": "Missing required parameters"}, 400 except Exception as e: - return jsonify({"Error": str(e)}), 500 + return {"Error": str(e)}, 500 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 08dbf32..5040783 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,8 @@ import logging import tweepy -from .config import Config + +from src.agents.tweet_sizzler.config import Config +from src.models.messages import ChatRequest # Configure logging logging.basicConfig( @@ -10,10 +12,10 @@ class TweetSizzlerAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm + self.embeddings = embeddings self.x_api_key = None self.last_prompt_content = None self.twitter_client = None @@ -38,12 +40,15 @@ def generate_tweet(self, prompt_content=None): ] try: - result = self.llm.create_chat_completion( - messages=messages, - max_tokens=Config.LLM_MAX_TOKENS, - temperature=Config.LLM_TEMPERATURE, - ) - tweet = result["choices"][0]["message"]["content"] + result = self.llm.invoke(messages) + logger.info(f"Received response from LLM: {result}") + tweet = result.content.strip() + tweet = " ".join(tweet.split()) + + # Remove any dictionary-like formatting, if present + if tweet.startswith("{") and tweet.endswith("}"): + tweet = tweet.strip("{}").split(":", 1)[-1].strip().strip('"') + logger.info(f"Tweet generated successfully: {tweet}") return tweet except Exception as e: @@ -111,33 +116,37 @@ def set_x_api_key(self, request): return {"success": "API credentials saved successfully"}, 200 - def chat(self, request): + def chat(self, chat_request: ChatRequest): try: - data = request.get_json() - logger.info(f"Received chat request: {data}") - if "prompt" in data: - prompt = data["prompt"] - action = data.get("action", Config.DEFAULT_ACTION) - logger.debug(f"Extracted prompt: {prompt}, action: {action}") - - if action == "generate": - logger.info(f"Generating tweet for prompt: {prompt['content']}") - tweet = self.generate_tweet(prompt["content"]) - logger.info(f"Generated tweet: {tweet}") - return {"role": "assistant", "content": tweet} - elif action == "post": - logger.info("Attempting to post tweet") - result, status_code = self.post_tweet(request) - logger.info( - f"Posted tweet result: {result}, status code: {status_code}" - ) + prompt = chat_request.prompt.dict() + logger.info(f"Received chat request: {prompt}") + + action = prompt.get("action", Config.DEFAULT_ACTION) + logger.debug( + f"Extracted prompt content: {prompt['content']}, action: {action}" + ) + + if action == "generate": + logger.info(f"Generating tweet for prompt: {prompt['content']}") + tweet = self.generate_tweet(prompt["content"]) + logger.info(f"Generated tweet: {tweet}") + return {"role": "assistant", "content": tweet} + 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}" + ) + if isinstance(result, dict) and "error" in result: return result, status_code - else: - logger.error(f"Invalid action received: {action}") - return {"error": Config.ERROR_INVALID_ACTION}, 400 + return { + "role": "assistant", + "content": f"Tweet posted successfully: {result.get('tweet', '')}", + }, status_code else: - logger.error("Missing 'prompt' in chat request data") - return {"error": Config.ERROR_MISSING_PARAMETERS}, 400 + logger.error(f"Invalid action received: {action}") + return {"role": "assistant", "content": Config.ERROR_INVALID_ACTION} + except Exception as e: logger.exception(f"Unexpected error in chat method: {str(e)}") - return {"Error": str(e)}, 500 + return {"role": "assistant", "content": f"Error: {str(e)}"} diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py index b7380b7..3b8e37b 100644 --- a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py +++ b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py @@ -21,9 +21,10 @@ class Config: "attention-grabbing tweets based on the user's prompt. It is CRUCIAL that you " "keep the tweets strictly under 280 characters - this is a hard limit. Make the " "tweets as engaging as possible while adhering to this character limit. Do not " - "surround your tweet with quotes. Do not preface it with any text like 'here is " - "your tweet'. Simply generate and output the tweet, ensuring it is less than " - "280 characters long." + "surround your tweet with quotes or any other formatting. Do not preface it with " + "any text like 'here is your tweet'. Simply generate and output the tweet, ensuring " + "it is less than 280 characters long. Use newlines sparingly. Do not surrounded with " + "quotes or braces. Do not use any other formatting." ) DEFAULT_ACTION = "generate" diff --git a/submodules/moragents_dockers/agents/src/agents/web_search/agent.py b/submodules/moragents_dockers/agents/src/agents/web_search/agent.py deleted file mode 100644 index 07b79c0..0000000 --- a/submodules/moragents_dockers/agents/src/agents/web_search/agent.py +++ /dev/null @@ -1,100 +0,0 @@ -import logging -import time -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.chrome.options import Options -from bs4 import BeautifulSoup - -# Configure logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -class SearchAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app - self.config = config - self.last_search_term = None - - def perform_search(self, search_term=None): - # State management for search regeneration purposes - if search_term is not None: - self.last_search_term = search_term - elif self.last_search_term is None: - logger.warning("No search term available for web search") - return "Web search failed. Please provide a search term." - else: - search_term = self.last_search_term - - logger.info(f"Performing web search for: {search_term}") - - # Set up Chrome options for headless browsing - chrome_options = Options() - chrome_options.add_argument("--headless") - - # Initialize the webdriver - driver = webdriver.Chrome(options=chrome_options) - - try: - # Navigate to Google - driver.get("https://www.google.com") - - # Find the search box and enter the search term - search_box = driver.find_element(By.NAME, "q") - search_box.send_keys(search_term) - search_box.send_keys(Keys.RETURN) - - # Wait for the results to load - time.sleep(2) - - # Get the page source and parse it with BeautifulSoup - soup = BeautifulSoup(driver.page_source, "html.parser") - - # Find the search results - search_results = soup.find_all("div", class_="g") - - # Process and format the results - formatted_results = [] - for result in search_results[:5]: # Limit to top 5 results - result_text = result.get_text(strip=True) - formatted_results.append(f"Result:\n{result_text}") - - return "\n\n".join(formatted_results) - - except Exception as e: - logger.error(f"Error performing web search: {str(e)}") - return f"Error performing web search: {str(e)}" - - finally: - # Close the browser - driver.quit() - - def chat(self, request): - try: - data = request.get_json() - logger.info(f"Received chat request: {data}") - if "prompt" in data: - prompt = data["prompt"] - action = data.get("action", "search") - logger.debug(f"Extracted prompt: {prompt}, action: {action}") - - if action == "search": - logger.info( - f"Performing web search for prompt: {prompt['content']}" - ) - search_results = self.perform_search(prompt["content"]) - logger.info(f"Search results: {search_results}") - return {"role": "assistant", "content": search_results} - else: - logger.error(f"Invalid action received: {action}") - return {"error": "Invalid action"}, 400 - else: - logger.error("Missing 'prompt' in chat request data") - return {"error": "Missing parameters"}, 400 - except Exception as e: - logger.exception(f"Unexpected error in chat method: {str(e)}") - return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index 465e921..19a3403 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -1,19 +1,18 @@ import os import logging import time +import uvicorn -from flask_cors import CORS -from flask import Flask, request, jsonify - -from fastapi import FastAPI, HTTPException, Request +from fastapi import FastAPI, HTTPException, Request, UploadFile, File from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel -from langchain_community.llms import Ollama +from langchain_ollama import ChatOllama from langchain_community.embeddings import OllamaEmbeddings from src.config import Config from src.delegator import Delegator -from src.agent_manager import AgentManager +from src.agent_manager import agent_manager from src.models.messages import ChatRequest # Constants @@ -32,7 +31,6 @@ ) logger = logging.getLogger(__name__) - app = FastAPI() app.add_middleware( CORSMiddleware, @@ -42,159 +40,145 @@ allow_headers=["*"], ) -upload_state = False os.makedirs(UPLOAD_FOLDER, exist_ok=True) -app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER -app.config["MAX_CONTENT_LENGTH"] = Config.MAX_UPLOAD_LENGTH -llm_ollama = Ollama(model="llama3.2", base_url=Config.OLLAMA_URL) +llm = ChatOllama( + model="llama3.2:3b", + base_url="http://host.docker.internal:11434", +) embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=Config.OLLAMA_URL) -delegator = Delegator(Config.DELEGATOR_CONFIG, llm_ollama, llm_ollama, embeddings, app) -agent_manager = AgentManager() +delegator = Delegator(Config.DELEGATOR_CONFIG, llm, embeddings) messages = [INITIAL_MESSAGE] -next_turn_agent = None @app.post("/chat") async def chat(chat_request: ChatRequest): - user_id = chat_request.user_id prompt = chat_request.prompt.dict() - - logger.info(f"Received chat request for user {user_id}: {prompt}") + messages.append(prompt) try: - active_agent = agent_manager.get_active_agent(user_id) + active_agent = agent_manager.get_active_agent() if not active_agent: - logger.info( - f"No active agent for user {user_id}, getting delegator response" - ) + logger.info("No active agent, getting delegator response") start_time = time.time() - result = delegator.get_delegator_response( - prompt["content"], False - ) # Assuming upload_state is False + result = delegator.get_delegator_response(prompt["content"], False) end_time = time.time() logger.info(f"Delegator response time: {end_time - start_time:.2f} seconds") logger.info(f"Delegator response: {result}") - if "next" not in result: - logger.error(f"Missing 'next' key in delegator response: {result}") - raise ValueError("Invalid delegator response: missing 'next' key") + if "agent" not in result: + logger.error(f"Missing 'agent' key in delegator response: {result}") + raise ValueError("Invalid delegator response: missing 'agent' key") - active_agent = result["next"] - agent_manager.set_active_agent(user_id, active_agent) + active_agent = result["agent"] - logger.info( - f"Delegating chat to active agent for user {user_id}: {active_agent}" - ) + logger.info(f"Delegating chat to active agent: {active_agent}") current_agent, response = delegator.delegate_chat(active_agent, chat_request) + if isinstance(response, tuple) and len(response) == 2: + error_message, status_code = response + logger.error(f"Error from agent: {error_message}") + raise HTTPException(status_code=status_code, detail=error_message) + if isinstance(response, dict) and "role" in response and "content" in response: response_with_agent = response.copy() response_with_agent["agentName"] = current_agent or "Unknown" + messages.append(response_with_agent) - logger.info(f"Sending response for user {user_id}: {response_with_agent}") + logger.info(f"Sending response: {response_with_agent}") return response_with_agent else: - logger.error(f"Invalid response format for user {user_id}: {response}") + logger.error(f"Invalid response format: {response}") raise HTTPException(status_code=500, detail="Invalid response format") except TimeoutError: - logger.error(f"Chat request timed out for user {user_id}") + logger.error("Chat request timed out") raise HTTPException(status_code=504, detail="Request timed out") except ValueError as ve: - logger.error(f"Input formatting error for user {user_id}: {str(ve)}") + logger.error(f"Input formatting error: {str(ve)}") raise HTTPException(status_code=400, detail=str(ve)) except Exception as e: - logger.error(f"Error in chat route for user {user_id}: {str(e)}", exc_info=True) + logger.error(f"Error in chat route: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) -# Example curl command for the / endpoint - - -@app.route("/tx_status", methods=["POST"]) -def swap_agent_tx_status(): +@app.post("/tx_status") +async def swap_agent_tx_status(request: Request): logger.info("Received tx_status request") response = delegator.delegate_route("crypto swap agent", request, "tx_status") messages.append(response) - return jsonify(response) + return response -@app.route("/messages", methods=["GET"]) -def get_messages(): +@app.get("/messages") +async def get_messages(): logger.info("Received get_messages request") - return jsonify({"messages": messages}) + return {"messages": messages} -@app.route("/clear_messages", methods=["GET"]) -def clear_messages(): +@app.get("/clear_messages") +async def clear_messages(): global messages logger.info("Clearing message history") messages = [INITIAL_MESSAGE] - return jsonify({"response": "successfully cleared message history"}) + return {"response": "successfully cleared message history"} -@app.route("/allowance", methods=["POST"]) -def swap_agent_allowance(): +@app.post("/allowance") +async def swap_agent_allowance(request: Request): logger.info("Received allowance request") return delegator.delegate_route("crypto swap agent", request, "get_allowance") -@app.route("/approve", methods=["POST"]) -def swap_agent_approve(): +@app.post("/approve") +async def swap_agent_approve(request: Request): logger.info("Received approve request") return delegator.delegate_route("crypto swap agent", request, "approve") -@app.route("/swap", methods=["POST"]) -def swap_agent_swap(): +@app.post("/swap") +async def swap_agent_swap(request: Request): logger.info("Received swap request") return delegator.delegate_route("crypto swap agent", request, "swap") -@app.route("/upload", methods=["POST"]) -def rag_agent_upload(): - global messages, upload_state +@app.post("/upload") +async def rag_agent_upload(file: UploadFile = File(...)): + global messages logger.info("Received upload request") response = delegator.delegate_route( - "general purpose and context-based rag agent", request, "upload_file" + "general purpose and context-based rag agent", {"file": file}, "upload_file" ) messages.append(response) - upload_state = True - return jsonify(response) + return response -@app.route("/regenerate_tweet", methods=["POST"]) -def regenerate_tweet(): +@app.post("/regenerate_tweet") +async def regenerate_tweet(): logger.info("Received generate tweet request") return delegator.delegate_route("tweet sizzler agent", None, "generate_tweet") -@app.route("/post_tweet", methods=["POST"]) -def post_tweet(): +@app.post("/post_tweet") +async def post_tweet(request: Request): logger.info("Received x post request") return delegator.delegate_route("tweet sizzler agent", request, "post_tweet") -# TODO: Persist the X API key in the database (once we set this up) -@app.route("/set_x_api_key", methods=["POST"]) -def set_x_api_key(): +@app.post("/set_x_api_key") +async def set_x_api_key(request: Request): logger.info("Received set X API key request") return delegator.delegate_route("tweet sizzler agent", request, "set_x_api_key") -@app.route("/claim", methods=["POST"]) -def claim_agent_claim(): +@app.post("/claim") +async def claim_agent_claim(request: Request): logger.info("Received claim request") return delegator.delegate_route("claim agent", request, "claim") -# @app.route("/claim_status", methods=["POST"]) -# def update_claim_status(): -# return claim_agent.claim_status(request) - if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000, debug=True) + uvicorn.run(app, host="0.0.0.0", port=5000, reload=True) diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index 19d194c..836f365 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -56,5 +56,12 @@ class Config: "name": "mor rewards agent", "upload_required": False, }, + { + "path": "src.agents.realtime_search.agent", + "class": "RealtimeSearchAgent", + "description": "Performs real-time web searches. Use when the query is about searching the web for real-time information.", + "name": "realtime search agent", + "upload_required": False, + }, ] } diff --git a/submodules/moragents_dockers/agents/src/delegator.py b/submodules/moragents_dockers/agents/src/delegator.py index 8f631ea..3ae8616 100644 --- a/submodules/moragents_dockers/agents/src/delegator.py +++ b/submodules/moragents_dockers/agents/src/delegator.py @@ -1,22 +1,20 @@ import importlib import logging import json -from langchain_experimental.llms.ollama_functions import OllamaFunctions -from langchain.prompts import ChatPromptTemplate -from langchain.schema import SystemMessage, HumanMessage + +from langchain.schema import SystemMessage, HumanMessage, AIMessage logger = logging.getLogger(__name__) # Configurable default agent DEFAULT_AGENT = "general purpose and context-based rag agent" + class Delegator: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app - self.llm_ollama = llm_ollama - self.embeddings = embeddings + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm # This is now a ChatOllama instance + self.embeddings = embeddings self.agents = self.load_agents(config) logger.info("Delegator initialized with %d agents", len(self.agents)) @@ -29,9 +27,7 @@ def load_agents(self, config): agent_instance = agent_class( agent_info, self.llm, - self.llm_ollama, self.embeddings, - self.flask_app, ) agents[agent_info["name"]] = agent_instance logger.info("Loaded agent: %s", agent_info["name"]) @@ -51,66 +47,49 @@ def get_delegator_response(self, prompt, upload_state): if agent_info["name"] in available_agents ) - prompt_text = ( + system_prompt = ( "Your name is Morpheus. " "Your primary function is to select the correct agent based on the user's input. " - "You MUST use the 'choose_appropriate_agent' function to select an agent. " - "Available agents and their descriptions:\n" - f"{agent_descriptions}\n" + "You MUST use the 'select_agent' function to select an agent. " + f"Available agents and their descriptions: {agent_descriptions}\n" "Analyze the user's input and select the most appropriate agent. " - "Do not respond with any text other than calling the 'choose_appropriate_agent' function." ) - # Define the tool in the format expected by OllamaFunctions tools = [ { - "name": "choose_appropriate_agent", - "description": "Choose which agent to run next", + "name": "select_agent", + "description": "Choose which agent should be used to respond to the user query", "parameters": { "type": "object", "properties": { - "next": { + "agent": { "type": "string", "enum": available_agents, - "description": "The name of the next agent to run", - } + "description": "The name of the agent to be used to respond to the user query", + }, }, - "required": ["next"], + "required": ["agent"], }, } ] - # Initialize the AI model with the correct tool format - model = OllamaFunctions( - model="llama3.2:3b", format="json", temperature=0, base_url="http://host.docker.internal:11434" - ) - model = model.bind_tools(tools=tools, function_call={"name": "choose_appropriate_agent"}) + agent_selection_llm = self.llm.bind_tools(tools) - # Create the messages for the AI model - print("prompt", prompt) messages = [ - SystemMessage(content=prompt_text), + SystemMessage(content=system_prompt), HumanMessage(content=prompt), ] - # Process the query - try: - result = model.invoke(messages) - logger.info(f"Model result: {result}") - - # Parse the result to extract the chosen agent - if hasattr(result, 'additional_kwargs') and 'function_call' in result.additional_kwargs: - function_call = result.additional_kwargs['function_call'] - if function_call['name'] == 'choose_appropriate_agent': - next_agent = json.loads(function_call['arguments'])['next'] - return {"next": next_agent} - - logger.warning("Unexpected response format, defaulting to general purpose agent") - return {"next": DEFAULT_AGENT} - - except Exception as e: - logger.error(f"Error during model invocation: {e}") - return {"next": DEFAULT_AGENT} + result = agent_selection_llm.invoke(messages) + tool_calls = result.tool_calls + if not tool_calls: + raise ValueError("No agent was selected by the model.") + + selected_agent = tool_calls[0] + logger.info(f"Selected agent: {selected_agent}") + selected_agent_name = selected_agent.get("args").get("agent") + + return {"agent": selected_agent_name} def delegate_chat(self, agent_name, request): logger.info(f"Attempting to delegate chat to agent: {agent_name}") diff --git a/submodules/moragents_dockers/agents/src/models/messages.py b/submodules/moragents_dockers/agents/src/models/messages.py index bec583b..28884d6 100644 --- a/submodules/moragents_dockers/agents/src/models/messages.py +++ b/submodules/moragents_dockers/agents/src/models/messages.py @@ -7,5 +7,4 @@ class ChatMessage(BaseModel): class ChatRequest(BaseModel): - user_id: str prompt: ChatMessage diff --git a/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx b/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx index 14ea0ca..cfc243a 100644 --- a/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx +++ b/submodules/moragents_dockers/frontend/components/HeaderBar/index.tsx @@ -1,9 +1,14 @@ import React, { FC, ComponentPropsWithoutRef } from "react"; import Image from "next/image"; -import { Box, HStack, Spacer } from "@chakra-ui/react"; +import { Box, HStack, Spacer, Button } from "@chakra-ui/react"; import { ConnectButton } from "@rainbow-me/rainbowkit"; import SettingsButton from "../Settings"; import classes from "./index.module.css"; +import { + getHttpClient, + clearMessagesHistory, +} from "../../services/backendClient"; +import { useRouter } from "next/router"; export interface HeaderBarProps extends ComponentPropsWithoutRef<"div"> { onAgentChanged(agent: string): void; @@ -11,6 +16,18 @@ export interface HeaderBarProps extends ComponentPropsWithoutRef<"div"> { } export const HeaderBar: FC = (props) => { + const backendClient = getHttpClient(); + const router = useRouter(); + + const handleClearChatHistory = async () => { + try { + await clearMessagesHistory(backendClient); + router.reload(); + } catch (error) { + console.error("Failed to clear chat history:", error); + } + }; + return ( @@ -19,6 +36,9 @@ export const HeaderBar: FC = (props) => { + diff --git a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx b/submodules/moragents_dockers/frontend/components/Tweet/index.tsx index 4563293..1deab63 100644 --- a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Tweet/index.tsx @@ -54,7 +54,7 @@ export const Tweet: FC = ({ initialContent, selectedAgent }) => { const handleTweet = async () => { setIsTweeting(true); - const backendClient = getHttpClient(selectedAgent); + const backendClient = getHttpClient(); try { await postTweet(backendClient, tweetContent); @@ -72,7 +72,7 @@ export const Tweet: FC = ({ initialContent, selectedAgent }) => { const handleRegenerate = async () => { setIsRegenerating(true); - const backendClient = getHttpClient(selectedAgent); + const backendClient = getHttpClient(); try { const newTweet = await regenerateTweet(backendClient); diff --git a/submodules/moragents_dockers/frontend/pages/index.tsx b/submodules/moragents_dockers/frontend/pages/index.tsx index 8b0d192..d156de9 100644 --- a/submodules/moragents_dockers/frontend/pages/index.tsx +++ b/submodules/moragents_dockers/frontend/pages/index.tsx @@ -1,33 +1,41 @@ -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import type { NextPage } from 'next'; -import { Flex, Grid, GridItem } from '@chakra-ui/react'; -import { LeftSidebar } from '../components/LeftSidebar'; -import { Chat } from '../components/Chat'; -import { writeMessage, getHttpClient, ChatMessage, getMessagesHistory, sendSwapStatus, SWAP_STATUS, uploadFile } from '../services/backendClient'; -import { useEffect, useMemo, useState } from 'react'; -import { useAccount, useChainId, useWalletClient } from 'wagmi'; -import { HeaderBar } from '../components/HeaderBar'; -import { availableAgents } from '../config'; -import { WalletRequiredModal } from '../components/WalletRequiredModal'; -import { ErrorBackendModal } from '../components/ErrorBackendModal'; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import type { NextPage } from "next"; +import { Flex, Grid, GridItem } from "@chakra-ui/react"; +import { LeftSidebar } from "../components/LeftSidebar"; +import { Chat } from "../components/Chat"; +import { + writeMessage, + getHttpClient, + ChatMessage, + getMessagesHistory, + sendSwapStatus, + SWAP_STATUS, + uploadFile, +} from "../services/backendClient"; +import { useEffect, useMemo, useState } from "react"; +import { useAccount, useChainId, useWalletClient } from "wagmi"; +import { HeaderBar } from "../components/HeaderBar"; +import { availableAgents } from "../config"; +import { WalletRequiredModal } from "../components/WalletRequiredModal"; +import { ErrorBackendModal } from "../components/ErrorBackendModal"; const Home: NextPage = () => { const [chatHistory, setChatHistory] = useState([]); const chainId = useChainId(); const { address } = useAccount(); - const [selectedAgent, setSelectedAgent] = useState('swap-agent'); // default is swap agent for now. + const [selectedAgent, setSelectedAgent] = useState("swap-agent"); // default is swap agent for now. const [showBackendError, setShowBackendError] = useState(false); useEffect(() => { - getMessagesHistory( - getHttpClient(selectedAgent), - ).then((messages: ChatMessage[]) => { - setChatHistory([...messages]) - }).catch((e) => { - console.error(`Failed to get initial messages history. Error: ${e}`); - setShowBackendError(true); - }); - }, [selectedAgent]); + getMessagesHistory(getHttpClient()) + .then((messages: ChatMessage[]) => { + setChatHistory([...messages]); + }) + .catch((e) => { + console.error(`Failed to get initial messages history. Error: ${e}`); + setShowBackendError(true); + }); + }, []); // Empty dependency array to run only on component initialization const isWalletRequired = useMemo(() => { const agent = availableAgents[selectedAgent] || null; @@ -40,34 +48,34 @@ const Home: NextPage = () => { }, [selectedAgent]); return ( -
+
{ setSelectedAgent(agent); }} - currentAgent={selectedAgent || ''} + currentAgent={selectedAgent || ""} /> - - + - - + { // 0 is swap, 1 is approve @@ -76,29 +84,36 @@ const Home: NextPage = () => { } try { - await sendSwapStatus(getHttpClient(selectedAgent), chainId, address, SWAP_STATUS.CANCELLED, '', fromAction); + await sendSwapStatus( + getHttpClient(), + chainId, + address, + SWAP_STATUS.CANCELLED, + "", + fromAction + ); } catch (e) { console.error(`Failed to cancel swap . Error: ${e}`); setShowBackendError(true); - } finally { try { const _updatedMessages = await getMessagesHistory( - getHttpClient(selectedAgent), - ) + getHttpClient() + ); setChatHistory([..._updatedMessages]); } catch (e) { - console.error(`Failed to get messages history after send swap status. Error: ${e}`); + console.error( + `Failed to get messages history after send swap status. Error: ${e}` + ); setShowBackendError(true); } } - - - }} - onSubmitMessage={async (message: string, file: File | null): Promise => { - + onSubmitMessage={async ( + message: string, + file: File | null + ): Promise => { const agent = availableAgents[selectedAgent] || null; if (null !== agent && agent.requirements.connectedWallet) { @@ -107,26 +122,29 @@ const Home: NextPage = () => { } } - setChatHistory([...chatHistory, { - role: 'user', - content: message - } as ChatMessage]); + setChatHistory([ + ...chatHistory, + { + role: "user", + content: message, + } as ChatMessage, + ]); let _newHistory = []; try { if (!file) { - _newHistory = await writeMessage(chatHistory, message, getHttpClient(selectedAgent), chainId, address || ''); - + _newHistory = await writeMessage( + chatHistory, + message, + getHttpClient(), + chainId, + address || "" + ); } else { - await uploadFile( - getHttpClient(selectedAgent), - file, - ) - - _newHistory = await getMessagesHistory( - getHttpClient(selectedAgent), - ) + await uploadFile(getHttpClient(), file); + + _newHistory = await getMessagesHistory(getHttpClient()); } - setChatHistory([..._newHistory]) + setChatHistory([..._newHistory]); } catch (e) { console.error(`Failed to send message. Error: ${e}`); setShowBackendError(true); @@ -142,7 +160,6 @@ const Home: NextPage = () => { {/* */} - {/* { return; } - const _newHistory = await writeMessage(chatHistory, message, getHttpClient(selectedAgent), chainId, address); + const _newHistory = await writeMessage(chatHistory, message, getHttpClient(), chainId, address); setChatHistory([..._newHistory]) }} /> */} -
diff --git a/submodules/moragents_dockers/frontend/services/backendClient.ts b/submodules/moragents_dockers/frontend/services/backendClient.ts index a81b7eb..2123abc 100644 --- a/submodules/moragents_dockers/frontend/services/backendClient.ts +++ b/submodules/moragents_dockers/frontend/services/backendClient.ts @@ -1,5 +1,4 @@ import axios, { Axios } from "axios"; -import { availableAgents } from "../config"; export type ChatMessageBase = { role: "user" | "assistant" | "swap" | "claim"; @@ -100,18 +99,9 @@ export type ChatsListItem = { title: string; // title of the chat (first message content) }; -export const getHttpClient = (selectedAgent: string) => { - const agentData = availableAgents[selectedAgent]; - - if (typeof agentData === "undefined") { - // if no agent selected lets select by default swap agent for now. - } - - const swapAgentUrl = - agentData?.endpoint || availableAgents["swap-agent"].endpoint; - +export const getHttpClient = () => { return axios.create({ - baseURL: swapAgentUrl || "http://localhost:8080", + baseURL: "http://localhost:8080", }); }; @@ -223,6 +213,18 @@ export const getMessagesHistory = async ( } as ChatMessage; }); }; + +export const clearMessagesHistory = async ( + backendClient: Axios +): Promise => { + try { + await backendClient.get("/clear_messages"); + } catch (error) { + console.error("Failed to clear message history:", error); + throw error; + } +}; + export const writeMessage = async ( history: ChatMessage[], message: string, @@ -238,7 +240,7 @@ export const writeMessage = async ( history.push(newMessage); let resp; try { - resp = await backendClient.post("/", { + resp = await backendClient.post("/chat", { prompt: { role: "user", content: message, From cb04c13d229a9ef809f66ff638a6f22b65f373b0 Mon Sep 17 00:00:00 2001 From: danxyu Date: Mon, 7 Oct 2024 12:39:57 -0700 Subject: [PATCH 04/15] wrapping up, ready for check in --- .../agents/src/agents/crypto_data/agent.py | 2 +- .../agents/src/agents/mor_claims/agent.py | 2 +- .../src/agents/realtime_search/agent.py | 6 ++- .../agents/src/agents/token_swap/agent.py | 14 +++---- .../moragents_dockers/agents/src/app.py | 27 +++++-------- .../agents/src/models/messages.py | 2 + .../agents/src/stores/__init__.py | 5 +++ .../agents/src/{ => stores}/agent_manager.py | 3 -- .../agents/src/stores/chat_manager.py | 40 +++++++++++++++++++ 9 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 submodules/moragents_dockers/agents/src/stores/__init__.py rename submodules/moragents_dockers/agents/src/{ => stores}/agent_manager.py (89%) create mode 100644 submodules/moragents_dockers/agents/src/stores/chat_manager.py 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 c5e9cc8..4240e55 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py @@ -3,7 +3,7 @@ from src.agents.crypto_data import tools from src.models.messages import ChatRequest -from src.agent_manager import agent_manager +from src.stores import agent_manager logger = logging.getLogger(__name__) 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 3adacb3..a18f7f4 100644 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py @@ -1,6 +1,6 @@ from src.agents.mor_claims import tools from src.models.messages import ChatRequest -from src.agent_manager import agent_manager +from src.stores import agent_manager class MorClaimsAgent: 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 aaf4956..931c979 100644 --- a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py @@ -56,11 +56,13 @@ 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.", + "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.""", }, { "role": "user", - "content": f"Based on the following search results for the query '{search_term}', provide a concise and informative answer:\n\n{search_results}", + "content": f"""Based on the following search results for the query '{search_term}', provide a concise and informative answer: {search_results}""", }, ] 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 536ea86..e99e9b3 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -5,7 +5,7 @@ from src.agents.token_swap import tools from src.agents.token_swap.config import Config from src.models.messages import ChatRequest -from src.agent_manager import agent_manager +from src.stores import agent_manager logger = logging.getLogger(__name__) @@ -72,15 +72,15 @@ def get_response(self, message, chain_id, wallet_address): if result.tool_calls: tool_call = result.tool_calls[0] - func_name = tool_call.name - args = tool_call.args + logger.info("Selected tool: %s", tool_call) + func_name = tool_call.get("name") + args = tool_call.get("args") logger.info("LLM suggested using tool: %s", func_name) if func_name == "swap_agent": - args_dict = json.loads(args) - tok1 = args_dict["token1"] - tok2 = args_dict["token2"] - value = args_dict["value"] + tok1 = args["token1"] + tok2 = args["token2"] + value = args["value"] try: res, role = tools.swap_coins( tok1, tok2, float(value), chain_id, wallet_address diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index 19a3403..9a0ecce 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -12,14 +12,10 @@ from src.config import Config from src.delegator import Delegator -from src.agent_manager import agent_manager +from src.stores import agent_manager, chat_manager from src.models.messages import ChatRequest # Constants -INITIAL_MESSAGE = { - "role": "assistant", - "content": "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.", -} UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") # Configure logging @@ -49,13 +45,12 @@ embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=Config.OLLAMA_URL) delegator = Delegator(Config.DELEGATOR_CONFIG, llm, embeddings) -messages = [INITIAL_MESSAGE] @app.post("/chat") async def chat(chat_request: ChatRequest): prompt = chat_request.prompt.dict() - messages.append(prompt) + chat_manager.add_message(prompt) try: active_agent = agent_manager.get_active_agent() @@ -84,12 +79,10 @@ async def chat(chat_request: ChatRequest): raise HTTPException(status_code=status_code, detail=error_message) if isinstance(response, dict) and "role" in response and "content" in response: - response_with_agent = response.copy() - response_with_agent["agentName"] = current_agent or "Unknown" - messages.append(response_with_agent) + chat_manager.add_response(response, current_agent or "Unknown") - logger.info(f"Sending response: {response_with_agent}") - return response_with_agent + logger.info(f"Sending response: {response}") + return response else: logger.error(f"Invalid response format: {response}") raise HTTPException(status_code=500, detail="Invalid response format") @@ -109,21 +102,20 @@ async def chat(chat_request: ChatRequest): async def swap_agent_tx_status(request: Request): logger.info("Received tx_status request") response = delegator.delegate_route("crypto swap agent", request, "tx_status") - messages.append(response) + chat_manager.add_message(response) return response @app.get("/messages") async def get_messages(): logger.info("Received get_messages request") - return {"messages": messages} + return {"messages": chat_manager.get_messages()} @app.get("/clear_messages") async def clear_messages(): - global messages logger.info("Clearing message history") - messages = [INITIAL_MESSAGE] + chat_manager.clear_messages() return {"response": "successfully cleared message history"} @@ -147,12 +139,11 @@ async def swap_agent_swap(request: Request): @app.post("/upload") async def rag_agent_upload(file: UploadFile = File(...)): - global messages logger.info("Received upload request") response = delegator.delegate_route( "general purpose and context-based rag agent", {"file": file}, "upload_file" ) - messages.append(response) + chat_manager.add_message(response) return response diff --git a/submodules/moragents_dockers/agents/src/models/messages.py b/submodules/moragents_dockers/agents/src/models/messages.py index 28884d6..e17d693 100644 --- a/submodules/moragents_dockers/agents/src/models/messages.py +++ b/submodules/moragents_dockers/agents/src/models/messages.py @@ -8,3 +8,5 @@ class ChatMessage(BaseModel): class ChatRequest(BaseModel): prompt: ChatMessage + chain_id: str + wallet_address: str diff --git a/submodules/moragents_dockers/agents/src/stores/__init__.py b/submodules/moragents_dockers/agents/src/stores/__init__.py new file mode 100644 index 0000000..18cc281 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/stores/__init__.py @@ -0,0 +1,5 @@ +from src.stores.agent_manager import AgentManager +from src.stores.chat_manager import ChatManager + +agent_manager = AgentManager() +chat_manager = ChatManager() diff --git a/submodules/moragents_dockers/agents/src/agent_manager.py b/submodules/moragents_dockers/agents/src/stores/agent_manager.py similarity index 89% rename from submodules/moragents_dockers/agents/src/agent_manager.py rename to submodules/moragents_dockers/agents/src/stores/agent_manager.py index 8528878..82508df 100644 --- a/submodules/moragents_dockers/agents/src/agent_manager.py +++ b/submodules/moragents_dockers/agents/src/stores/agent_manager.py @@ -10,6 +10,3 @@ def set_active_agent(self, agent_name): def clear_active_agent(self): self.active_agent = None - - -agent_manager = AgentManager() diff --git a/submodules/moragents_dockers/agents/src/stores/chat_manager.py b/submodules/moragents_dockers/agents/src/stores/chat_manager.py new file mode 100644 index 0000000..2ad61b6 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/stores/chat_manager.py @@ -0,0 +1,40 @@ +import logging +from typing import List, Dict + +logger = logging.getLogger(__name__) + + +class ChatManager: + def __init__(self): + self.messages: List[Dict[str, str]] = [ + { + "role": "assistant", + "content": """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.""", + } + ] + + def add_message(self, message: Dict[str, str]): + self.messages.append(message) + logger.info(f"Added message: {message}") + + def get_messages(self) -> List[Dict[str, str]]: + return self.messages + + def clear_messages(self): + self.messages = [self.messages[0]] # Keep the initial message + logger.info("Cleared message history") + + def get_last_message(self) -> Dict[str, str]: + return self.messages[-1] if self.messages else {} + + def add_response(self, response: Dict[str, str], agent_name: str): + response_with_agent = response.copy() + response_with_agent["agentName"] = agent_name + self.add_message(response_with_agent) + logger.info(f"Added response from agent {agent_name}: {response_with_agent}") + + def get_chat_history(self) -> str: + return "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.messages]) From f2f49f0aeb8f68da0755207650396e66fcf37b0a Mon Sep 17 00:00:00 2001 From: danxyu Date: Mon, 7 Oct 2024 12:52:03 -0700 Subject: [PATCH 05/15] move more config constants to config --- .../agents/src/agents/crypto_data/config.py | 6 +- .../agents/src/agents/token_swap/config.py | 6 +- .../moragents_dockers/agents/src/app.py | 8 +- .../moragents_dockers/agents/src/config.py | 1 + .../frontend/components/Chat/index.tsx | 155 +++++++++--------- .../frontend/components/SwapForm/index.tsx | 22 +-- 6 files changed, 89 insertions(+), 109 deletions(-) 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 b87337b..63db777 100644 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py @@ -6,11 +6,7 @@ # Configuration object class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" + # API endpoints COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" DEFILLAMA_BASE_URL = "https://api.llama.fi" diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/config.py b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py index 2a73f47..d479874 100644 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/config.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py @@ -6,11 +6,7 @@ # Configuration object class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" + # API endpoints INCH_URL = "https://api.1inch.dev/token" QUOTE_URL = "https://api.1inch.dev/swap" diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index 9a0ecce..58334ba 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -39,10 +39,12 @@ os.makedirs(UPLOAD_FOLDER, exist_ok=True) llm = ChatOllama( - model="llama3.2:3b", - base_url="http://host.docker.internal:11434", + model=Config.OLLAMA_MODEL, + base_url=Config.OLLAMA_URL, +) +embeddings = OllamaEmbeddings( + model=Config.OLLAMA_EMBEDDING_MODEL, base_url=Config.OLLAMA_URL ) -embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=Config.OLLAMA_URL) delegator = Delegator(Config.DELEGATOR_CONFIG, llm, embeddings) diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index 836f365..b1f5c19 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -9,6 +9,7 @@ class Config: # Model configuration OLLAMA_MODEL = "llama3.2:3b" + OLLAMA_EMBEDDING_MODEL = "nomic-embed-text" OLLAMA_URL = "http://host.docker.internal:11434" MAX_UPLOAD_LENGTH = 16 * 1024 * 1024 diff --git a/submodules/moragents_dockers/frontend/components/Chat/index.tsx b/submodules/moragents_dockers/frontend/components/Chat/index.tsx index 31d6123..37011db 100644 --- a/submodules/moragents_dockers/frontend/components/Chat/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Chat/index.tsx @@ -9,7 +9,7 @@ import { sendSwapStatus, getHttpClient, SWAP_STATUS, - CLAIM_STATUS + CLAIM_STATUS, } from "../../services/backendClient"; import { useAccount, @@ -65,7 +65,7 @@ export const Chat: FC = ({ async (status: string, hash: string, isApprove: number) => { try { const response: ChatMessage = await sendSwapStatus( - getHttpClient(selectedAgent), + getHttpClient(), chainId, address?.toLowerCase() || "0x", status, @@ -158,94 +158,93 @@ export const Chat: FC = ({ [address, handleSwapStatus, sendTransaction] ); - const handleClaimStatus = useCallback( - async (status: string, hash: string) => { - try { - const response: ChatMessage = await sendClaimStatus( - getHttpClient(selectedAgent), - chainId, - address?.toLowerCase() || "0x", - status, - hash - ); + async (status: string, hash: string) => { + try { + const response: ChatMessage = await sendClaimStatus( + getHttpClient(), + chainId, + address?.toLowerCase() || "0x", + status, + hash + ); - if ( - response.role === "assistant" && - typeof response.content === "string" - ) { - setMessagesData((prev) => [ - ...prev, - { - role: "assistant", - content: response.content, - } as UserOrAssistantMessage, - ]); - } else if (response.role === "claim") { - setMessagesData((prev) => [...prev, response as ClaimMessage]); - } + if ( + response.role === "assistant" && + typeof response.content === "string" + ) { + setMessagesData((prev) => [ + ...prev, + { + role: "assistant", + content: response.content, + } as UserOrAssistantMessage, + ]); + } else if (response.role === "claim") { + setMessagesData((prev) => [...prev, response as ClaimMessage]); + } - setTxHash(""); - setShowSpinner(false); - } catch (error) { - console.log(`Error sending claim status: ${error}`); - onBackendError(); - setTxHash(""); - setShowSpinner(false); - } - }, - [selectedAgent, chainId, address, onBackendError] -); + setTxHash(""); + setShowSpinner(false); + } catch (error) { + console.log(`Error sending claim status: ${error}`); + onBackendError(); + setTxHash(""); + setShowSpinner(false); + } + }, + [selectedAgent, chainId, address, onBackendError] + ); // Add this near your other useTransactionConfirmations hooks -const claimConfirmations = useTransactionConfirmations({ - hash: (txHash || "0x") as `0x${string}`, -}); + const claimConfirmations = useTransactionConfirmations({ + hash: (txHash || "0x") as `0x${string}`, + }); -// Add this effect to watch for claim transaction confirmations -useEffect(() => { - if (txHash && claimConfirmations.data && claimConfirmations.data >= 1) { - handleClaimStatus(CLAIM_STATUS.SUCCESS, txHash); - } -}, [txHash, claimConfirmations.data, handleClaimStatus]); + // Add this effect to watch for claim transaction confirmations + useEffect(() => { + if (txHash && claimConfirmations.data && claimConfirmations.data >= 1) { + handleClaimStatus(CLAIM_STATUS.SUCCESS, txHash); + } + }, [txHash, claimConfirmations.data, handleClaimStatus]); -// Modify handleClaimSubmit to use the same txHash state -const handleClaimSubmit = useCallback( - (claimTx: ClaimTransactionPayload) => { - setTxHash(""); - console.log("Claim transaction to be sent:", claimTx); - sendTransaction( - { - account: address, - data: claimTx.data as `0x${string}`, - to: claimTx.to as `0x${string}`, - value: BigInt(claimTx.value), - chainId: parseInt(claimTx.chainId), - }, - { - onSuccess: (hash) => { - setTxHash(hash); - handleClaimStatus(CLAIM_STATUS.INIT, hash); - }, - onError: (error) => { - console.log(`Error sending transaction: ${error}`); - handleClaimStatus(CLAIM_STATUS.FAIL, ""); + // Modify handleClaimSubmit to use the same txHash state + const handleClaimSubmit = useCallback( + (claimTx: ClaimTransactionPayload) => { + setTxHash(""); + console.log("Claim transaction to be sent:", claimTx); + sendTransaction( + { + account: address, + data: claimTx.data as `0x${string}`, + to: claimTx.to as `0x${string}`, + value: BigInt(claimTx.value), + chainId: parseInt(claimTx.chainId), }, - } - ); - }, - [address, handleClaimStatus, sendTransaction] -); + { + onSuccess: (hash) => { + setTxHash(hash); + handleClaimStatus(CLAIM_STATUS.INIT, hash); + }, + onError: (error) => { + console.log(`Error sending transaction: ${error}`); + handleClaimStatus(CLAIM_STATUS.FAIL, ""); + }, + } + ); + }, + [address, handleClaimStatus, sendTransaction] + ); return ( + messages={messagesData} + selectedAgent={selectedAgent} + onCancelSwap={onCancelSwap} + onSwapSubmit={handleSwapSubmit} + onClaimSubmit={handleClaimSubmit} + /> {showSpinner && } = ({ } const _payload = await getApprovalTxPayload( - getHttpClient(selectedAgent), + getHttpClient(), chainId, src_address, Number(src_amount), @@ -166,14 +166,7 @@ export const SwapForm: FC = ({ setIsButtonLoading(false); } }, - [ - onSubmitApprove, - isNativeToken, - decimals?.data, - fromMessage, - chainId, - selectedAgent, - ] + [onSubmitApprove, isNativeToken, decimals?.data, fromMessage, chainId] ); const handleSwap = useCallback( @@ -182,7 +175,7 @@ export const SwapForm: FC = ({ try { const _payload = await getSwapTxPayload( - getHttpClient(selectedAgent), + getHttpClient(), fromMessage.src_address, fromMessage.dst_address, address, @@ -202,14 +195,7 @@ export const SwapForm: FC = ({ setIsButtonLoading(false); } }, - [ - fromMessage, - chainId, - selectedAgent, - formData.slippage, - onSubmitSwap, - decimals, - ] + [fromMessage, chainId, formData.slippage, onSubmitSwap, decimals] ); useEffect(() => { From bef56bacd02b1fab2512361b8e6a7ab3e3f9f5be Mon Sep 17 00:00:00 2001 From: danxyu Date: Mon, 7 Oct 2024 12:58:55 -0700 Subject: [PATCH 06/15] check in fallback to headless browsing for realtime search --- .../moragents_dockers/agents/requirements.txt | 1 + .../src/agents/realtime_search/agent.py | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/submodules/moragents_dockers/agents/requirements.txt b/submodules/moragents_dockers/agents/requirements.txt index b74671b..d75a6bc 100644 --- a/submodules/moragents_dockers/agents/requirements.txt +++ b/submodules/moragents_dockers/agents/requirements.txt @@ -15,3 +15,4 @@ tweepy==4.14.0 uvicorn==0.31.0 python-multipart==0.0.12 beautifulsoup4==4.12.3 +selenium==4.25.0 \ No newline at end of file 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 931c979..1c098b9 100644 --- a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py @@ -1,7 +1,13 @@ import logging import requests +import time from bs4 import BeautifulSoup +from selenium import webdriver +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( @@ -19,7 +25,7 @@ def __init__(self, config, llm, embeddings): self.embeddings = embeddings self.last_search_term = None - def perform_search(self, search_term=None): + def perform_search_with_web_scraping(self, search_term=None): if search_term is not None: self.last_search_term = search_term elif self.last_search_term is None: @@ -49,7 +55,40 @@ def perform_search(self, search_term=None): except requests.RequestException as e: logger.error(f"Error performing web search: {str(e)}") + logger.info("Attempting fallback to headless browsing") + return self.perform_search_with_headless_browsing(search_term) + + def perform_search_with_headless_browsing(self, search_term): + chrome_options = Options() + chrome_options.add_argument("--headless") + + driver = webdriver.Chrome(options=chrome_options) + + try: + driver.get("https://www.google.com") + + search_box = driver.find_element(By.NAME, "q") + search_box.send_keys(search_term) + search_box.send_keys(Keys.RETURN) + + time.sleep(2) + + soup = BeautifulSoup(driver.page_source, "html.parser") + + search_results = soup.find_all("div", class_="g") + + formatted_results = [] + for result in search_results[:5]: + result_text = result.get_text(strip=True) + formatted_results.append(f"Result:\n{result_text}") + + return "\n\n".join(formatted_results) + + except Exception as e: + logger.error(f"Error performing headless web search: {str(e)}") return f"Error performing web search: {str(e)}" + finally: + driver.quit() def synthesize_answer(self, search_term, search_results): logger.info("Synthesizing answer from search results") @@ -83,7 +122,7 @@ def chat(self, request: ChatRequest): search_term = prompt["content"] logger.info(f"Performing web search for prompt: {search_term}") - search_results = self.perform_search(search_term) + search_results = self.perform_search_with_web_scraping(search_term) logger.info(f"Search results obtained") synthesized_answer = self.synthesize_answer(search_term, search_results) From f9dba45d7a52c89150ba1d52436e749b1d977612 Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Tue, 8 Oct 2024 01:09:37 -0400 Subject: [PATCH 07/15] Add security workflow --- .github/workflows/security_scan.yml | 44 +++++++++++++++++++++++++++++ requirements.txt | 2 ++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/security_scan.yml diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml new file mode 100644 index 0000000..a0592f7 --- /dev/null +++ b/.github/workflows/security_scan.yml @@ -0,0 +1,44 @@ +name: Security Scan + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + security_scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install safety bandit + + - name: Run Safety check + run: safety check -r requirements.txt + + - name: Run Bandit + run: bandit -r . -f custom + + - name: Run Trivy vulnerability scanner in repo mode + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: 'trivy-results.sarif' diff --git a/requirements.txt b/requirements.txt index 70969cd..c4a1bda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ requests setuptools pyinstaller torch +safety +bandit From 52313cc1739a7af88b16e631f63a06b0a8e31693 Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Tue, 8 Oct 2024 01:16:21 -0400 Subject: [PATCH 08/15] trivy --- .github/workflows/security_scan.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index a0592f7..cbad971 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -25,6 +25,7 @@ jobs: - name: Run Safety check run: safety check -r requirements.txt + continue-on-error: true - name: Run Bandit run: bandit -r . -f custom @@ -36,9 +37,11 @@ jobs: ignore-unfixed: true format: 'sarif' output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif' + wait-for-processing: true From a654fdc6bd83e08411913bdb78f1da494fcba755 Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Tue, 8 Oct 2024 01:25:22 -0400 Subject: [PATCH 09/15] trivy --- .github/workflows/security_scan.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index cbad971..b1390d4 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -31,17 +31,15 @@ jobs: run: bandit -r . -f custom - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.20.0 with: scan-type: 'fs' ignore-unfixed: true format: 'sarif' output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' + severity: 'CRITICAL' - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 - if: always() with: sarif_file: 'trivy-results.sarif' - wait-for-processing: true From 9a0eba7e234bf43045085d055071b3f91daf93fd Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Tue, 8 Oct 2024 01:28:40 -0400 Subject: [PATCH 10/15] continue-on-error --- .github/workflows/security_scan.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index b1390d4..ab7ebac 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -29,6 +29,7 @@ jobs: - name: Run Bandit run: bandit -r . -f custom + continue-on-error: true - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.20.0 From 1023dd0fd5031fe0b3516f51cddfec60b7679932 Mon Sep 17 00:00:00 2001 From: LachsBagel <149974355+LachsBagel@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:23:39 -0500 Subject: [PATCH 11/15] WIP --- .github/workflows/mor-agents-build-linux.yml | 8 ++++---- build_assets/macOS/welcome.html | 4 ++-- config.py | 20 +++++++++++-------- .../__init__.py | 0 .../agents}/__init__.py | 0 .../agents/src/agents/news_agent}/__init__.py | 0 .../src => agents/news_agent}/agent.py | 4 ++-- .../src => agents/news_agent}/config.py | 0 .../src => agents/news_agent}/tools.py | 2 +- .../moragents_dockers/agents/src/config.py | 2 +- .../agents/tests}/__init__.py | 0 .../tests}/claim_agent_benchmarks/README.md | 0 .../tests/claim_agent_benchmarks}/__init__.py | 0 .../adapters/__init__.py | 0 .../adapters/claim_adapter.py | 0 .../claim_agent_benchmarks/benchmarks.py | 2 +- .../tests}/claim_agent_benchmarks/config.py | 0 .../tests}/claim_agent_benchmarks/helpers.py | 4 ++-- .../tests}/news_agent_benchmarks/README.md | 0 .../tests/news_agent_benchmarks}/__init__.py | 0 .../news_agent_benchmarks/benchmarks.py | 4 ++-- .../tests}/news_agent_benchmarks/config.py | 0 .../tests}/news_agent_benchmarks/helpers.py | 0 .../agents/tests/price_fetching}/__init__.py | 0 .../price_fetching/adapters}/__init__.py | 0 .../price_fetching/adapters/base_adapter.py | 0 .../adapters/coingecko_adapter.py | 0 .../adapters/defillama_adapter.py | 0 .../tests}/price_fetching/benchmarks.py | 2 +- .../agents/tests}/price_fetching/config.py | 0 .../agents/tests}/price_fetching/helpers.py | 0 .../agents/tests}/price_fetching/readme.md | 0 .../tests}/price_fetching/requirements.txt | 0 .../reward_check_agent_benchmarks/README.md | 0 .../reward_check_agent_benchmarks/__init__.py | 0 .../adapters/__init__.py | 0 .../adapters/reward_check_adapter.py | 0 .../benchmarks.py | 4 ++-- .../reward_check_agent_benchmarks/config.py | 2 +- .../reward_check_agent_benchmarks/helpers.py | 0 .../docker-compose-apple.yml | 4 ++-- .../moragents_dockers/docker-compose.yml | 4 ++-- .../frontend/package-lock.json | 4 ++-- .../moragents_dockers/frontend/package.json | 2 +- wizard_windows.iss | 2 +- 45 files changed, 39 insertions(+), 35 deletions(-) rename submodules/{benchmarks => moragents_dockers}/__init__.py (100%) rename submodules/{benchmarks/claim_agent_benchmarks => moragents_dockers/agents}/__init__.py (100%) rename submodules/{benchmarks/claim_agent_benchmarks/adapters => moragents_dockers/agents/src/agents/news_agent}/__init__.py (100%) rename submodules/moragents_dockers/agents/src/{news_agent/src => agents/news_agent}/agent.py (97%) rename submodules/moragents_dockers/agents/src/{news_agent/src => agents/news_agent}/config.py (100%) rename submodules/moragents_dockers/agents/src/{news_agent/src => agents/news_agent}/tools.py (98%) rename submodules/{benchmarks/news_agent_benchmarks => moragents_dockers/agents/tests}/__init__.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/README.md (100%) rename submodules/{benchmarks/price_fetching => moragents_dockers/agents/tests/claim_agent_benchmarks}/__init__.py (100%) rename submodules/{benchmarks/price_fetching => moragents_dockers/agents/tests/claim_agent_benchmarks}/adapters/__init__.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/adapters/claim_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/benchmarks.py (92%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/config.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/helpers.py (71%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/news_agent_benchmarks/README.md (100%) rename submodules/{benchmarks/reward_check_agent_benchmarks => moragents_dockers/agents/tests/news_agent_benchmarks}/__init__.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/news_agent_benchmarks/benchmarks.py (86%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/news_agent_benchmarks/config.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/news_agent_benchmarks/helpers.py (100%) rename submodules/{benchmarks/reward_check_agent_benchmarks/adapters => moragents_dockers/agents/tests/price_fetching}/__init__.py (100%) rename submodules/moragents_dockers/agents/{src/news_agent/src => tests/price_fetching/adapters}/__init__.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/base_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/coingecko_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/defillama_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/benchmarks.py (94%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/config.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/helpers.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/readme.md (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/requirements.txt (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py create mode 100644 submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/adapters/reward_check_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/benchmarks.py (93%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/config.py (97%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/helpers.py (100%) diff --git a/.github/workflows/mor-agents-build-linux.yml b/.github/workflows/mor-agents-build-linux.yml index da84275..09d09cb 100644 --- a/.github/workflows/mor-agents-build-linux.yml +++ b/.github/workflows/mor-agents-build-linux.yml @@ -157,13 +157,13 @@ jobs: # Pull necessary Docker images echo -e "\${YELLOW}Pulling Docker images...${NC}" - sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.1.1 - sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.1.1 + sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.2.0 + sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.2.0 # Start Docker containers echo -e "\${YELLOW}Starting Docker containers...${NC}" - sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.1.1 - sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.1.1 + sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.2.0 + sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.2.0 echo -e "\${GREEN}Setup complete!${NC}" EOF diff --git a/build_assets/macOS/welcome.html b/build_assets/macOS/welcome.html index 32a3e56..a355874 100644 --- a/build_assets/macOS/welcome.html +++ b/build_assets/macOS/welcome.html @@ -3,7 +3,7 @@ - Welcome to MORagents v0.1.1 Installer + Welcome to MORagents v0.2.0 Installer -

Welcome to MORagents v0.1.1 Installer

+

Welcome to MORagents v0.2.0 Installer

Thank you for choosing to install MORagents on your system. This installer will guide you through the process of setting up MORagents and its diff --git a/config.py b/config.py index 92ba497..1738205 100644 --- a/config.py +++ b/config.py @@ -16,20 +16,20 @@ class AgentDockerConfig: MACOS_APPLE_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:apple-0.1.1", - "lachsbagel/moragents_dockers-agents:apple-0.1.1" + "lachsbagel/moragents_dockers-nginx:apple-0.2.0", + "lachsbagel/moragents_dockers-agents:apple-0.2.0" ] MACOS_INTEL_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.1", - "lachsbagel/moragents_dockers-agents:amd64-0.1.1" + "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", + "lachsbagel/moragents_dockers-agents:amd64-0.2.0" ] WINDOWS_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.1", - "lachsbagel/moragents_dockers-agents:amd64-0.1.1" + "lachsbagel/moragents_dockers-nginx: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.1.1", - "lachsbagel/moragents_dockers-agents:amd64-0.1.1" + "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", + "lachsbagel/moragents_dockers-agents:amd64-0.2.0" ] @staticmethod @@ -50,6 +50,10 @@ class AgentDockerConfigDeprecate: "morpheus/price_fetcher_agent:latest", "moragents_dockers-nginx:latest", "moragents_dockers-agents:latest", + "lachsbagel/moragents_dockers-nginx:apple-0.0.9", + "lachsbagel/moragents_dockers-agents:apple-0.0.9", + "lachsbagel/moragents_dockers-nginx:amd64-0.0.9", + "lachsbagel/moragents_dockers-agents:amd64-0.0.9", "lachsbagel/moragents_dockers-nginx:apple-0.1.0", "lachsbagel/moragents_dockers-agents:apple-0.1.0", "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", diff --git a/submodules/benchmarks/__init__.py b/submodules/moragents_dockers/__init__.py similarity index 100% rename from submodules/benchmarks/__init__.py rename to submodules/moragents_dockers/__init__.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/__init__.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/__init__.py rename to submodules/moragents_dockers/agents/__init__.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/adapters/__init__.py rename to submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py similarity index 97% rename from submodules/moragents_dockers/agents/src/news_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/news_agent/agent.py index 4dbb93c..dc5df22 100644 --- a/submodules/moragents_dockers/agents/src/news_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py @@ -2,8 +2,8 @@ import logging import re import urllib.parse -from news_agent.src.config import Config -from news_agent.src.tools import clean_html, is_within_time_window, fetch_rss_feed +from src.agents.news_agent.config import Config +from src.agents.news_agent.tools import clean_html, is_within_time_window, fetch_rss_feed import pyshorteners logger = logging.getLogger(__name__) diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py similarity index 100% rename from submodules/moragents_dockers/agents/src/news_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/news_agent/config.py diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py similarity index 98% rename from submodules/moragents_dockers/agents/src/news_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/news_agent/tools.py index 607f2fc..94b86b6 100644 --- a/submodules/moragents_dockers/agents/src/news_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py @@ -4,7 +4,7 @@ from dateutil import parser import re from html import unescape -from news_agent.src.config import Config +from news_agent.config import Config import logging import urllib.parse diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index 18f779f..083b880 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -65,7 +65,7 @@ class Config: "upload_required": False, }, { - "path": "news_agent.src.agent", + "path": "src.agents.news_agent.agent", "class": "NewsAgent", "description": "Fetches and analyzes cryptocurrency news for potential price impacts.", "name": "crypto news agent", diff --git a/submodules/benchmarks/news_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/__init__.py similarity index 100% rename from submodules/benchmarks/news_agent_benchmarks/__init__.py rename to submodules/moragents_dockers/agents/tests/__init__.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/README.md rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md diff --git a/submodules/benchmarks/price_fetching/__init__.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py similarity index 100% rename from submodules/benchmarks/price_fetching/__init__.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py diff --git a/submodules/benchmarks/price_fetching/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/__init__.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/adapters/claim_adapter.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/adapters/claim_adapter.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py similarity index 92% rename from submodules/benchmarks/claim_agent_benchmarks/benchmarks.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py index d550827..880365d 100644 --- a/submodules/benchmarks/claim_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py @@ -1,6 +1,6 @@ import pytest from helpers import request_claim, provide_receiver_address, confirm_transaction -from submodules.benchmarks.claim_agent_benchmarks.config import Config +from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config def test_claim_process(): diff --git a/submodules/benchmarks/claim_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/config.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py similarity index 71% rename from submodules/benchmarks/claim_agent_benchmarks/helpers.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py index afab9a6..a319834 100644 --- a/submodules/benchmarks/claim_agent_benchmarks/helpers.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py @@ -1,5 +1,5 @@ -from submodules.benchmarks.claim_agent_benchmarks.config import Config -from submodules.benchmarks.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) diff --git a/submodules/benchmarks/news_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md similarity index 100% rename from submodules/benchmarks/news_agent_benchmarks/README.md rename to submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/__init__.py rename to submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py diff --git a/submodules/benchmarks/news_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py similarity index 86% rename from submodules/benchmarks/news_agent_benchmarks/benchmarks.py rename to submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py index 843c33d..7b7df4c 100644 --- a/submodules/benchmarks/news_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py @@ -2,8 +2,8 @@ import pytest import logging -from submodules.benchmarks.news_agent_benchmarks.config import Config -from submodules.benchmarks.news_agent_benchmarks.helpers import ask_news_agent, extract_classification +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 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/submodules/benchmarks/news_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py similarity index 100% rename from submodules/benchmarks/news_agent_benchmarks/config.py rename to submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py diff --git a/submodules/benchmarks/news_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py similarity index 100% rename from submodules/benchmarks/news_agent_benchmarks/helpers.py rename to submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/price_fetching/__init__.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/adapters/__init__.py rename to submodules/moragents_dockers/agents/tests/price_fetching/__init__.py diff --git a/submodules/moragents_dockers/agents/src/news_agent/src/__init__.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py similarity index 100% rename from submodules/moragents_dockers/agents/src/news_agent/src/__init__.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py diff --git a/submodules/benchmarks/price_fetching/adapters/base_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/base_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py diff --git a/submodules/benchmarks/price_fetching/adapters/coingecko_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/coingecko_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py diff --git a/submodules/benchmarks/price_fetching/adapters/defillama_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/defillama_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py diff --git a/submodules/benchmarks/price_fetching/benchmarks.py b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py similarity index 94% rename from submodules/benchmarks/price_fetching/benchmarks.py rename to submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py index 728a9b4..d376fcd 100644 --- a/submodules/benchmarks/price_fetching/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py @@ -1,7 +1,7 @@ import time import argparse from helpers import ask_data_agent, compare_usd_values, extract_agent_usd_value -from submodules.benchmarks.price_fetching.config import coins, price_prompts, mcap_prompts, price_error_tolerance, mcap_error_tolerance, loop_delay +from submodules.moragents_dockers.agents.tests.price_fetching import coins, price_prompts, mcap_prompts, price_error_tolerance, mcap_error_tolerance, loop_delay from adapters.coingecko_adapter import CoingeckoAdapter from adapters.defillama_adapter import DefillamaAdapter diff --git a/submodules/benchmarks/price_fetching/config.py b/submodules/moragents_dockers/agents/tests/price_fetching/config.py similarity index 100% rename from submodules/benchmarks/price_fetching/config.py rename to submodules/moragents_dockers/agents/tests/price_fetching/config.py diff --git a/submodules/benchmarks/price_fetching/helpers.py b/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py similarity index 100% rename from submodules/benchmarks/price_fetching/helpers.py rename to submodules/moragents_dockers/agents/tests/price_fetching/helpers.py diff --git a/submodules/benchmarks/price_fetching/readme.md b/submodules/moragents_dockers/agents/tests/price_fetching/readme.md similarity index 100% rename from submodules/benchmarks/price_fetching/readme.md rename to submodules/moragents_dockers/agents/tests/price_fetching/readme.md diff --git a/submodules/benchmarks/price_fetching/requirements.txt b/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt similarity index 100% rename from submodules/benchmarks/price_fetching/requirements.txt rename to submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/README.md rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/adapters/reward_check_adapter.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/adapters/reward_check_adapter.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py similarity index 93% rename from submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py index 3e8df66..03395e7 100644 --- a/submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py @@ -1,6 +1,6 @@ import pytest -from submodules.benchmarks.claim_agent_benchmarks.helpers import request_claim, confirm_transaction -from submodules.benchmarks.claim_agent_benchmarks.config import Config +from submodules.benchmarks.claim_agent_benchmarks.helpers import request_claim +from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config def test_claim_process(): diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py similarity index 97% rename from submodules/benchmarks/reward_check_agent_benchmarks/config.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py index 9f75ab4..2522622 100644 --- a/submodules/benchmarks/reward_check_agent_benchmarks/config.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py @@ -7,7 +7,7 @@ # Configuration object class Config: WEB3RPCURL = { - "1": "https://cloudflare-eth.com", + "1": "https://eth.llamarpc.com/", } DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/helpers.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py diff --git a/submodules/moragents_dockers/docker-compose-apple.yml b/submodules/moragents_dockers/docker-compose-apple.yml index b06f976..27b8acb 100644 --- a/submodules/moragents_dockers/docker-compose-apple.yml +++ b/submodules/moragents_dockers/docker-compose-apple.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:apple-0.1.1 + image: lachsbagel/moragents_dockers-agents:apple-0.2.0 build: dockerfile: Dockerfile-apple context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:apple-0.1.1 + image: lachsbagel/moragents_dockers-nginx:apple-0.2.0 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/docker-compose.yml b/submodules/moragents_dockers/docker-compose.yml index 2eaa3fb..f9ad6b0 100644 --- a/submodules/moragents_dockers/docker-compose.yml +++ b/submodules/moragents_dockers/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:amd64-0.1.1 + image: lachsbagel/moragents_dockers-agents:amd64-0.2.0 build: dockerfile: Dockerfile context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:amd64-0.1.1 + image: lachsbagel/moragents_dockers-nginx:amd64-0.2.0 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/frontend/package-lock.json b/submodules/moragents_dockers/frontend/package-lock.json index e186c02..6f0fca1 100644 --- a/submodules/moragents_dockers/frontend/package-lock.json +++ b/submodules/moragents_dockers/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "Morpheus AI", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Morpheus AI", - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "dependencies": { "@chakra-ui/icons": "^2.1.1", diff --git a/submodules/moragents_dockers/frontend/package.json b/submodules/moragents_dockers/frontend/package.json index cbb090a..fd6af89 100644 --- a/submodules/moragents_dockers/frontend/package.json +++ b/submodules/moragents_dockers/frontend/package.json @@ -1,7 +1,7 @@ { "name": "Morpheus AI", "private": true, - "version": "0.1.1", + "version": "0.2.0", "scripts": { "dev": "next dev", "build": "next build", diff --git a/wizard_windows.iss b/wizard_windows.iss index ab2579e..0fdcf7b 100644 --- a/wizard_windows.iss +++ b/wizard_windows.iss @@ -1,6 +1,6 @@ [Setup] AppName=MORagents -AppVersion=0.1.1 +AppVersion=0.2.0 DefaultDirName={commonpf}\MORagents OutputDir=.\MORagentsWindowsInstaller OutputBaseFilename=MORagentsSetup From 5001441ed497f9297961162ba826b6126b90d8e1 Mon Sep 17 00:00:00 2001 From: LachsBagel <149974355+LachsBagel@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:41:22 -0500 Subject: [PATCH 12/15] refactored news agent to new format; temporarily disabled as it interferes with real time agent --- .../agents/src/agents/news_agent/agent.py | 15 ++++++--------- .../agents/src/agents/news_agent/config.py | 3 ++- .../agents/src/agents/news_agent/tools.py | 2 +- .../moragents_dockers/agents/src/config.py | 16 ++++++++-------- 4 files changed, 17 insertions(+), 19 deletions(-) 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 dc5df22..732ff0f 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py @@ -9,10 +9,10 @@ logger = logging.getLogger(__name__) class NewsAgent: - def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): + def __init__(self, agent_info, llm, embeddings): self.agent_info = agent_info self.llm = llm - self.flask_app = flask_app + self.embeddings = embeddings self.tools_provided = self.get_tools() self.url_shortener = pyshorteners.Shortener() logger.info("NewsAgent initialized") @@ -44,12 +44,9 @@ def get_tools(self): def check_relevance_and_summarize(self, title, content, coin): logger.info(f"Checking relevance for {coin}: {title}") prompt = Config.RELEVANCE_PROMPT.format(coin=coin, title=title, content=content) - result = self.llm.create_chat_completion( - messages=[{"role": "user", "content": prompt}], - max_tokens=Config.LLM_MAX_TOKENS, - temperature=Config.LLM_TEMPERATURE - ) - return result['choices'][0]['message']['content'].strip() + result = self.llm.invoke([{"role": "user", "content": prompt}]) + result = " ".join(result.content.strip().split()) + return result def process_rss_feed(self, feed_url, coin): logger.info(f"Processing RSS feed for {coin}: {feed_url}") @@ -90,7 +87,7 @@ def fetch_crypto_news(self, coins): def chat(self, request): try: - data = request.get_json() + data = request.dict() if 'prompt' in data: prompt = data['prompt'] if isinstance(prompt, dict) and 'content' in prompt: 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 c8a399b..e9fe7d3 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py @@ -125,5 +125,6 @@ class Config: 'EIGEN': 'Eigenlayer', 'ORDI': 'ORDI', 'CFX': 'Conflux', - 'W': 'Wormhole' + 'W': 'Wormhole', + 'MOR': 'Morpheus AI' } \ No newline at end of file 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 94b86b6..f017b22 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py @@ -4,7 +4,7 @@ from dateutil import parser import re from html import unescape -from news_agent.config import Config +from src.agents.news_agent.config import Config import logging import urllib.parse diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index 083b880..f32e083 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -60,16 +60,16 @@ class Config: { "path": "src.agents.realtime_search.agent", "class": "RealtimeSearchAgent", - "description": "Performs real-time web searches. Use when the query is about searching the web for real-time information.", + "description": "Performs real-time web searches. Use when query is for searching web for real-time information or general news.", "name": "realtime search agent", "upload_required": False, }, - { - "path": "src.agents.news_agent.agent", - "class": "NewsAgent", - "description": "Fetches and analyzes cryptocurrency news for potential price impacts.", - "name": "crypto news agent", - "upload_required": False, - } + # { + # "path": "src.agents.news_agent.agent", + # "class": "NewsAgent", + # "description": "Fetches and analyzes cryptocurrency headlines for potential price impacts. Use when query is about project updates related to a token or cryptocurrency.", + # "name": "crypto news agent", + # "upload_required": False, + # } ] } From 90369f0f42f34b81a94e19a1b0116b902049bc78 Mon Sep 17 00:00:00 2001 From: LachsBagel <149974355+LachsBagel@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:25:47 -0500 Subject: [PATCH 13/15] WIP in fixing RAG agent --- .../agents/src/agents/rag/agent.py | 20 +++++-- .../moragents_dockers/agents/src/config.py | 56 +++++++++---------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/submodules/moragents_dockers/agents/src/agents/rag/agent.py b/submodules/moragents_dockers/agents/src/agents/rag/agent.py index cbe9bf6..690e317 100644 --- a/submodules/moragents_dockers/agents/src/agents/rag/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/rag/agent.py @@ -42,10 +42,13 @@ def handle_file_upload(self, file): if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER, exist_ok=True) filename = secure_filename(file.filename) - file.save(os.path.join(UPLOAD_FOLDER, filename)) + file_path = os.path.join(UPLOAD_FOLDER, filename) + + with open(file_path, "wb") as f: + f.write(file.file.read()) # DocumentToolsGenerator class instantiation - loader = PyMuPDFLoader(os.path.join(UPLOAD_FOLDER, filename)) + loader = PyMuPDFLoader(file_path) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter( chunk_size=1024, @@ -58,16 +61,18 @@ def handle_file_upload(self, file): self.retriever = vector_store.as_retriever(search_kwargs={"k": 7}) def upload_file(self, request: ChatRequest): - data = request.dict() + data = request if "file" not in data: return {"error": "No file part"}, 400 file = data["file"] if file.filename == "": return {"error": "No selected file"}, 400 # Check file size - file.seek(0, os.SEEK_END) - file_length = file.tell() - file.seek(0, 0) # Reset the file pointer to the beginning + try: + file_length = file.file.size + except AttributeError: + file_length = len(file.file.read()) + file.file.seek(0) # Reset the file pointer to the beginning if file_length > self.max_size: return {"role": "assistant", "content": "please use a file less than 5 MB"} try: @@ -92,6 +97,9 @@ def chat(self, request: ChatRequest): doc.page_content for doc in retrieved_docs ) formatted_prompt = f"Question: {prompt}\n\nContext: {formatted_context}" + + print(formatted_prompt) + answer = self.llm(formatted_prompt) response = answer if self.upload_state else "please upload a file first" return {"role": role, "content": response} diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index f32e083..7fb12d5 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -15,13 +15,13 @@ class Config: MAX_UPLOAD_LENGTH = 16 * 1024 * 1024 DELEGATOR_CONFIG = { "agents": [ - { - "path": "src.agents.rag.agent", - "class": "RagAgent", - "description": "Handles general queries, information retrieval, and context-based questions. Use for any query that doesn't explicitly match other agents' specialties.", - "name": "general purpose and context-based rag agent", - "upload_required": True, - }, + # { + # "path": "src.agents.rag.agent", + # "class": "RagAgent", + # "description": "Default model. Use for summarization, explaining, and main points from successfully uploaded text", + # "name": "general purpose and context-based rag agent", + # "upload_required": True, + # }, { "path": "src.agents.crypto_data.agent", "class": "CryptoDataAgent", @@ -29,13 +29,13 @@ class Config: "name": "crypto data agent", "upload_required": False, }, - { - "path": "src.agents.token_swap.agent", - "class": "TokenSwapAgent", - "description": "Handles cryptocurrency swapping operations. Use when the query explicitly mentions swapping, exchanging, or converting one cryptocurrency to another.", - "name": "token swap agent", - "upload_required": False, - }, + # { + # "path": "src.agents.token_swap.agent", + # "class": "TokenSwapAgent", + # "description": "Handles cryptocurrency swapping operations. Use when the query explicitly mentions swapping, exchanging, or converting one cryptocurrency to another.", + # "name": "token swap agent", + # "upload_required": False, + # }, { "path": "src.agents.tweet_sizzler.agent", "class": "TweetSizzlerAgent", @@ -43,13 +43,13 @@ class Config: "name": "tweet sizzler agent", "upload_required": False, }, - { - "path": "src.agents.mor_claims.agent", - "class": "MorClaimsAgent", - "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", - "name": "mor claims agent", - "upload_required": False, - }, + # { + # "path": "src.agents.mor_claims.agent", + # "class": "MorClaimsAgent", + # "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", + # "name": "mor claims agent", + # "upload_required": False, + # }, { "path": "src.agents.mor_rewards.agent", "class": "MorRewardsAgent", @@ -64,12 +64,12 @@ class Config: "name": "realtime search agent", "upload_required": False, }, - # { - # "path": "src.agents.news_agent.agent", - # "class": "NewsAgent", - # "description": "Fetches and analyzes cryptocurrency headlines for potential price impacts. Use when query is about project updates related to a token or cryptocurrency.", - # "name": "crypto news agent", - # "upload_required": False, - # } + { + "path": "src.agents.news_agent.agent", + "class": "NewsAgent", + "description": "Fetches and analyzes cryptocurrency headlines for potential price impacts. Use when query is about project updates related to a token or cryptocurrency.", + "name": "crypto news agent", + "upload_required": False, + } ] } From 31833502ad5e6a68b92d8902455644ff497f2ea7 Mon Sep 17 00:00:00 2001 From: danxyu Date: Thu, 17 Oct 2024 13:45:53 -0700 Subject: [PATCH 14/15] v0.2.0 with fixes --- build_assets/macOS/preinstall_ollama.sh | 2 +- build_assets/macOS/welcome.html | 87 ++++--- config.py | 22 +- submodules/moragents_dockers/README.md | 62 +++-- .../__init__.py | 0 .../moragents_dockers/agents/Dockerfile | 15 +- .../moragents_dockers/agents/Dockerfile-apple | 16 +- .../agents}/__init__.py | 0 .../agents/download_model.py | 29 --- .../moragents_dockers/agents/model_config.py | 13 - .../moragents_dockers/agents/requirements.txt | 22 +- .../agents/src}/__init__.py | 0 .../agents/src/agents}/__init__.py | 0 .../crypto_data}/README.md | 0 .../src/agents/crypto_data}/__init__.py | 0 .../agents/src/agents/crypto_data/agent.py | 93 +++++++ .../src => agents/crypto_data}/config.py | 16 +- .../agents/src/agents/crypto_data/routes.py | 14 ++ .../src => agents/crypto_data}/tools.py | 187 +++++++------- .../agents/src/agents/mor_claims}/__init__.py | 0 .../agents/src/agents/mor_claims/agent.py | 170 +++++++++++++ .../agents/src/agents/mor_claims/config.py | 35 +++ .../src => agents/mor_claims}/tools.py | 60 +++-- .../src/agents/mor_rewards}/__init__.py | 0 .../src => agents/mor_rewards}/agent.py | 39 +-- .../agents/src/agents/mor_rewards/config.py | 26 ++ .../src => agents/mor_rewards}/tools.py | 28 ++- .../agents/src/agents/news_agent/__init__.py | 0 .../agents/src/agents/news_agent/agent.py | 158 ++++++++++++ .../agents/src/agents/news_agent/config.py | 129 ++++++++++ .../agents/src/agents/news_agent/tools.py | 71 ++++++ .../agents/src/agents/rag/__init__.py | 0 .../agents/src/agents/rag/agent.py | 117 +++++++++ .../{rag_agent/src => agents/rag}/config.py | 5 +- .../src/agents/realtime_search/__init__.py | 0 .../src/agents/realtime_search/agent.py | 137 ++++++++++ .../token_swap}/README.md | 0 .../agents/src/agents/token_swap/__init__.py | 0 .../agents/src/agents/token_swap/agent.py | 221 +++++++++++++++++ .../agents/src/agents/token_swap/config.py | 54 ++++ .../src => agents/token_swap}/tools.py | 119 +++++---- .../tweet_sizzler}/README.md | 0 .../src/agents/tweet_sizzler/__init__.py | 0 .../src => agents/tweet_sizzler}/agent.py | 85 ++++--- .../src => agents/tweet_sizzler}/config.py | 7 +- .../moragents_dockers/agents/src/app.py | 234 +++++++----------- .../agents/src/claim_agent/src/agent.py | 126 ---------- .../agents/src/claim_agent/src/config.py | 58 ----- .../moragents_dockers/agents/src/config.py | 49 ++-- .../agents/src/data_agent/src/agent.py | 79 ------ .../src/data_agent/src/data_agent_config.py | 27 -- .../moragents_dockers/agents/src/delegator.py | 91 +++---- .../agents/src/models/__init__.py | 0 .../agents/src/models/messages.py | 12 + .../agents/src/rag_agent/src/agent.py | 90 ------- .../agents/src/reward_agent/src/config.py | 39 --- .../agents/src/stores/__init__.py | 5 + .../agents/src/stores/agent_manager.py | 12 + .../agents/src/stores/chat_manager.py | 48 ++++ .../agents/src/swap_agent/src/agent.py | 190 -------------- .../agents/src/swap_agent/src/config.py | 26 -- .../src/swap_agent/src/swap_agent_config.py | 25 -- .../agents/tests/__init__.py | 0 .../tests}/claim_agent_benchmarks/README.md | 0 .../tests/claim_agent_benchmarks/__init__.py | 0 .../adapters/__init__.py | 0 .../adapters/claim_adapter.py | 0 .../claim_agent_benchmarks/benchmarks.py | 2 +- .../tests}/claim_agent_benchmarks/config.py | 0 .../tests}/claim_agent_benchmarks/helpers.py | 4 +- .../tests/news_agent_benchmarks/README.md | 20 ++ .../tests/news_agent_benchmarks/__init__.py | 0 .../tests/news_agent_benchmarks/benchmarks.py | 38 +++ .../tests/news_agent_benchmarks/config.py | 14 ++ .../tests/news_agent_benchmarks/helpers.py | 48 ++++ .../agents/tests/price_fetching/__init__.py | 0 .../tests/price_fetching/adapters/__init__.py | 0 .../price_fetching/adapters/base_adapter.py | 0 .../adapters/coingecko_adapter.py | 0 .../adapters/defillama_adapter.py | 0 .../tests}/price_fetching/benchmarks.py | 2 +- .../agents/tests}/price_fetching/config.py | 0 .../agents/tests}/price_fetching/helpers.py | 0 .../agents/tests}/price_fetching/readme.md | 0 .../tests}/price_fetching/requirements.txt | 0 .../reward_check_agent_benchmarks/README.md | 0 .../reward_check_agent_benchmarks/__init__.py | 0 .../adapters/__init__.py | 0 .../adapters/reward_check_adapter.py | 0 .../benchmarks.py | 4 +- .../reward_check_agent_benchmarks/config.py | 2 +- .../reward_check_agent_benchmarks/helpers.py | 0 .../docker-compose-apple.yml | 4 +- .../moragents_dockers/docker-compose.yml | 4 +- .../frontend/components/Chat/index.tsx | 155 ++++++------ .../frontend/components/HeaderBar/index.tsx | 22 +- .../frontend/components/SwapForm/index.tsx | 22 +- .../frontend/components/Tweet/index.tsx | 4 +- .../moragents_dockers/frontend/config.ts | 44 ++-- .../frontend/package-lock.json | 7 +- .../moragents_dockers/frontend/package.json | 6 +- .../frontend/pages/index.tsx | 148 ++++++----- .../frontend/services/backendClient.ts | 39 +-- wizard_windows.iss | 4 +- 104 files changed, 2270 insertions(+), 1473 deletions(-) rename submodules/{benchmarks => moragents_dockers}/__init__.py (100%) rename submodules/{benchmarks/claim_agent_benchmarks => moragents_dockers/agents}/__init__.py (100%) delete mode 100644 submodules/moragents_dockers/agents/download_model.py delete mode 100644 submodules/moragents_dockers/agents/model_config.py rename submodules/{benchmarks/claim_agent_benchmarks/adapters => moragents_dockers/agents/src}/__init__.py (100%) rename submodules/{benchmarks/price_fetching => moragents_dockers/agents/src/agents}/__init__.py (100%) rename submodules/moragents_dockers/agents/src/{data_agent => agents/crypto_data}/README.md (100%) rename submodules/{benchmarks/price_fetching/adapters => moragents_dockers/agents/src/agents/crypto_data}/__init__.py (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py rename submodules/moragents_dockers/agents/src/{data_agent/src => agents/crypto_data}/config.py (70%) create mode 100644 submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py rename submodules/moragents_dockers/agents/src/{data_agent/src => agents/crypto_data}/tools.py (64%) rename submodules/{benchmarks/reward_check_agent_benchmarks => moragents_dockers/agents/src/agents/mor_claims}/__init__.py (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_claims/config.py rename submodules/moragents_dockers/agents/src/{claim_agent/src => agents/mor_claims}/tools.py (71%) rename submodules/{benchmarks/reward_check_agent_benchmarks/adapters => moragents_dockers/agents/src/agents/mor_rewards}/__init__.py (100%) rename submodules/moragents_dockers/agents/src/{reward_agent/src => agents/mor_rewards}/agent.py (62%) create mode 100644 submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py rename submodules/moragents_dockers/agents/src/{reward_agent/src => agents/mor_rewards}/tools.py (73%) create mode 100644 submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agents/news_agent/agent.py create mode 100644 submodules/moragents_dockers/agents/src/agents/news_agent/config.py create mode 100644 submodules/moragents_dockers/agents/src/agents/news_agent/tools.py create mode 100644 submodules/moragents_dockers/agents/src/agents/rag/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agents/rag/agent.py rename submodules/moragents_dockers/agents/src/{rag_agent/src => agents/rag}/config.py (59%) create mode 100644 submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py rename submodules/moragents_dockers/agents/src/{swap_agent => agents/token_swap}/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/agents/token_swap/agent.py create mode 100644 submodules/moragents_dockers/agents/src/agents/token_swap/config.py rename submodules/moragents_dockers/agents/src/{swap_agent/src => agents/token_swap}/tools.py (61%) rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent => agents/tweet_sizzler}/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent/src => agents/tweet_sizzler}/agent.py (63%) rename submodules/moragents_dockers/agents/src/{tweet_sizzler_agent/src => agents/tweet_sizzler}/config.py (76%) delete mode 100644 submodules/moragents_dockers/agents/src/claim_agent/src/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/claim_agent/src/config.py delete mode 100644 submodules/moragents_dockers/agents/src/data_agent/src/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py create mode 100644 submodules/moragents_dockers/agents/src/models/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/models/messages.py delete mode 100644 submodules/moragents_dockers/agents/src/rag_agent/src/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/reward_agent/src/config.py create mode 100644 submodules/moragents_dockers/agents/src/stores/__init__.py create mode 100644 submodules/moragents_dockers/agents/src/stores/agent_manager.py create mode 100644 submodules/moragents_dockers/agents/src/stores/chat_manager.py delete mode 100644 submodules/moragents_dockers/agents/src/swap_agent/src/agent.py delete mode 100644 submodules/moragents_dockers/agents/src/swap_agent/src/config.py delete mode 100644 submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py create mode 100644 submodules/moragents_dockers/agents/tests/__init__.py rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py create mode 100644 submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/adapters/claim_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/benchmarks.py (92%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/config.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/claim_agent_benchmarks/helpers.py (71%) create mode 100644 submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md create mode 100644 submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py create mode 100644 submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py create mode 100644 submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py create mode 100644 submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py create mode 100644 submodules/moragents_dockers/agents/tests/price_fetching/__init__.py create mode 100644 submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/base_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/coingecko_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/adapters/defillama_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/benchmarks.py (94%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/config.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/helpers.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/readme.md (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/price_fetching/requirements.txt (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/README.md (100%) create mode 100644 submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py create mode 100644 submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/adapters/reward_check_adapter.py (100%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/benchmarks.py (93%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/config.py (97%) rename submodules/{benchmarks => moragents_dockers/agents/tests}/reward_check_agent_benchmarks/helpers.py (100%) diff --git a/build_assets/macOS/preinstall_ollama.sh b/build_assets/macOS/preinstall_ollama.sh index dfcb2e7..a74c1d5 100644 --- a/build_assets/macOS/preinstall_ollama.sh +++ b/build_assets/macOS/preinstall_ollama.sh @@ -7,5 +7,5 @@ chmod +x ollama sudo mv ollama /usr/local/bin/ nohup /usr/local/bin/ollama serve > /dev/null 2>&1 & -/usr/local/bin/ollama pull llama3.1 +/usr/local/bin/ollama pull llama3.2:3b /usr/local/bin/ollama pull nomic-embed-text diff --git a/build_assets/macOS/welcome.html b/build_assets/macOS/welcome.html index 18ca86d..a355874 100644 --- a/build_assets/macOS/welcome.html +++ b/build_assets/macOS/welcome.html @@ -1,44 +1,57 @@ - - - - Welcome to MORagents v0.1.0 Installer - - - -

Welcome to MORagents v0.1.0 Installer

-

Thank you for choosing to install MORagents on your system. This installer will guide you through the process of setting up MORagents and its dependencies.

-

The installer will perform the following steps:

-
    -
  • Check the installed Python version and install Python 3.12.0 if necessary.
  • -
  • Check if Docker is installed and install Docker Desktop if needed.
  • -
  • Check if Ollama is installed and install Ollama if needed.
  • -
  • Install the MORagents application.
  • -
-

Please note that during the installation process, you may be prompted to enter your system password to authorize the installation of required components.

-

Click "Continue" to proceed with the installation.

- + li { + margin-bottom: 10px; + } + + + +

Welcome to MORagents v0.2.0 Installer

+

+ Thank you for choosing to install MORagents on your system. This installer + will guide you through the process of setting up MORagents and its + dependencies. +

+

The installer will perform the following steps:

+
    +
  • + Check the installed Python version and install Python 3.12.0 if + necessary. +
  • +
  • + Check if Docker is installed and install Docker Desktop if needed. +
  • +
  • Check if Ollama is installed and install Ollama if needed.
  • +
  • Install the MORagents application.
  • +
+

+ Please note that during the installation process, you may be prompted to + enter your system password to authorize the installation of required + components. +

+

Click "Continue" to proceed with the installation.

+ diff --git a/config.py b/config.py index 43bae40..1738205 100644 --- a/config.py +++ b/config.py @@ -16,20 +16,20 @@ class AgentDockerConfig: MACOS_APPLE_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:apple-0.1.0", - "lachsbagel/moragents_dockers-agents:apple-0.1.0" + "lachsbagel/moragents_dockers-nginx:apple-0.2.0", + "lachsbagel/moragents_dockers-agents:apple-0.2.0" ] MACOS_INTEL_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", + "lachsbagel/moragents_dockers-agents:amd64-0.2.0" ] WINDOWS_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx: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.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0" + "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", + "lachsbagel/moragents_dockers-agents:amd64-0.2.0" ] @staticmethod @@ -53,5 +53,9 @@ class AgentDockerConfigDeprecate: "lachsbagel/moragents_dockers-nginx:apple-0.0.9", "lachsbagel/moragents_dockers-agents:apple-0.0.9", "lachsbagel/moragents_dockers-nginx:amd64-0.0.9", - "lachsbagel/moragents_dockers-agents:amd64-0.0.9" + "lachsbagel/moragents_dockers-agents:amd64-0.0.9", + "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" ] diff --git a/submodules/moragents_dockers/README.md b/submodules/moragents_dockers/README.md index 38327fc..e07693e 100644 --- a/submodules/moragents_dockers/README.md +++ b/submodules/moragents_dockers/README.md @@ -1,41 +1,44 @@ -# Moragents +# Moragents ## Overview -This project is a Flask-based AI chat application featuring intelligent responses from various language models and embeddings. It includes file uploading, cryptocurrency swapping, and a delegator system to manage multiple agents. The application, along with a dApp for agent interaction, runs locally and is containerized with Docker. +This project is a Flask-based AI chat application featuring intelligent responses from various language models and embeddings. It includes file uploading, cryptocurrency swapping, and a delegator system to manage multiple agents. The application, along with a dApp for agent interaction, runs locally and is containerized with Docker. ## Pre-requisites -* [Download Ollama](https://ollama.com/ )for your operating system -* Then after finishing installation pull these two models: -```ollama pull llama3.1``` +- [Download Ollama](https://ollama.com/)for your operating system +- Then after finishing installation pull these two models: + +`ollama pull llama3.2:3b` -```ollama pull nomic-embed-text``` +`ollama pull nomic-embed-text` ## Run with Docker Compose -Docker compose will build and run two containers. One will be for the agents, the other will be for the UI. +Docker compose will build and run two containers. One will be for the agents, the other will be for the UI. 1. Ensure you're in the submodules/moragents_dockers folder - ```sh - $ cd submodules/moragents_dockers - ``` + + ```sh + $ cd submodules/moragents_dockers + ``` 2. Build Images and Launch Containers: - 1. For Intel / AMD / x86_64 - ```sh - docker-compose up - ``` + 1. For Intel / AMD / x86_64 + ```sh + docker-compose up + ``` 2. For Apple silicon (M1, M2, M3, etc) - ```sh - docker-compose -f docker-compose-apple.yml up - ``` + ```sh + docker-compose -f docker-compose-apple.yml up + ``` -Open in the browser: ```http://localhost:3333/``` +Open in the browser: `http://localhost:3333/` -Docker build will download the model. The first time that one of the agents are called, the model will be loaded into memory and this instance will be shared between all agents. +Docker build will download the model. The first time that one of the agents are called, the model will be loaded into memory and this instance will be shared between all agents. ## Agents + Five agents are included: ### Data Agent @@ -53,6 +56,7 @@ It currently supports the following metrics: It is possible to ask questions about assets by referring to them either by their name or their ticker symbol. ### Swap Agent + This agent will enable you to perform swaps between cryptoassets. It should be used with the accompanying UI which provides a browser-based front-end to chat with the agent, display quotes and sign transactions. A typical flow looks like this: @@ -65,35 +69,43 @@ A typical flow looks like this: - If the allowance for the token being sold is too low, an approval transaction will be generated first ## RAG Agent + This agent will answer questions about an uploaded PDF file. ## Tweet Sizzler Agent -This agent will let you generate tweets, edit with a WSYWIG. -Provided you enter API creds in the Settings you can also directly post to your X account. + +This agent will let you generate tweets, edit with a WSYWIG. +Provided you enter API creds in the Settings you can also directly post to your X account. ## MOR Rewards Agent + Ask the agent to check your MOR rewards and it will retrieve claimable MOR stats from both capital and coder pools. --- # Delegator + The Delegator handles user queries by analyzing the prompt and delegating it to the appropriate agent. ## API Endpoints 1. **Chat Functionality** + - Endpoint: `POST /` - Handles chat interactions, delegating to appropriate agents when necessary. 2. **Message History** + - Endpoint: `GET /messages` - Retrieves chat message history. 3. **Clear Messages** + - Endpoint: `GET /clear_messages` - Clears the chat message history. 4. **Swap Operations** + - Endpoints: - `POST /tx_status`: Check transaction status - `POST /allowance`: Get allowance @@ -129,6 +141,7 @@ This allows the delegator to delegate to the correct task agent based on the use - `upload`: A boolean indicating if the agent requires a file to be uploaded from the front-end before it should be called. #### Example: + ```python:agents/src/config.py DELEGATOR_CONFIG = { "agents": [ @@ -144,13 +157,13 @@ DELEGATOR_CONFIG = { } ``` - ### 3. Implement Agent Logic 1. **Define the agent class** in the specified path. 2. **Ensure the agent can handle the queries** it is designed for. #### Example: + ```python:agents/src/new_agent/src/agent.py class NewAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): @@ -177,12 +190,12 @@ class NewAgent: # Add other methods as needed ``` - ### 4. Handle Multi-Turn Conversations Agents can handle multi-turn conversations by returning a next_turn_agent which indicates the name of the agent that should handle the next turn. #### Example: + ```python class NewAgent: def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): @@ -205,7 +218,7 @@ class NewAgent: def chat(self, request, user_id): # Process the query and determine the next agent next_turn_agent = self.agent_info["name"] - + # Generate response where we want to initiate a multi-turn conversation with the same agent. return response, next_turn_agent @@ -215,6 +228,7 @@ class NewAgent: ### 5. Integration The `Delegator` will automatically: + - Import the agent module. - Instantiate the agent class. - Add the agent to its internal dictionary. diff --git a/submodules/benchmarks/__init__.py b/submodules/moragents_dockers/__init__.py similarity index 100% rename from submodules/benchmarks/__init__.py rename to submodules/moragents_dockers/__init__.py diff --git a/submodules/moragents_dockers/agents/Dockerfile b/submodules/moragents_dockers/agents/Dockerfile index 52bcec7..14b0556 100644 --- a/submodules/moragents_dockers/agents/Dockerfile +++ b/submodules/moragents_dockers/agents/Dockerfile @@ -12,20 +12,11 @@ RUN apt-get update && apt-get install -y gcc g++ procps && rm -rf /var/lib/apt/l # Install Python dependencies RUN python3 -m pip install --no-cache-dir --upgrade pip && \ python3 -m pip install --no-cache-dir --upgrade -r requirements.txt - -COPY download_model.py . -COPY model_config.py . - -RUN python3 download_model.py - -copy . . +COPY . . # Expose the port your application listens on EXPOSE 5000 -# Set the environment variable for Flask -ENV FLASK_APP=src/app.py - -# Run the application -CMD ["flask", "run", "--host", "0.0.0.0"] \ No newline at end of file +# 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 diff --git a/submodules/moragents_dockers/agents/Dockerfile-apple b/submodules/moragents_dockers/agents/Dockerfile-apple index ad80504..2e28f3b 100644 --- a/submodules/moragents_dockers/agents/Dockerfile-apple +++ b/submodules/moragents_dockers/agents/Dockerfile-apple @@ -12,21 +12,11 @@ RUN apt-get update && apt-get install -y gcc g++ procps && rm -rf /var/lib/apt/l # Install Python dependencies RUN python3 -m pip install --no-cache-dir --upgrade pip && \ python3 -m pip install --no-cache-dir --upgrade -r requirements.txt - -COPY download_model.py . -COPY model_config.py . - -RUN python3 download_model.py - -copy . . +COPY . . # Expose the port your application listens on EXPOSE 5000 - -# Set the environment variable for Flask -ENV FLASK_APP=src/app.py - -# Run the application -CMD ["flask", "run", "--host", "0.0.0.0"] \ No newline at end of file +# 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 diff --git a/submodules/benchmarks/claim_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/__init__.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/__init__.py rename to submodules/moragents_dockers/agents/__init__.py diff --git a/submodules/moragents_dockers/agents/download_model.py b/submodules/moragents_dockers/agents/download_model.py deleted file mode 100644 index 55f3fd4..0000000 --- a/submodules/moragents_dockers/agents/download_model.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import shutil -from huggingface_hub import hf_hub_download -from model_config import Config - - -def download_model(model_name, revision): - """Function to download model from the hub""" - model_directory = hf_hub_download(repo_id=model_name, filename=revision) - return model_directory - - -def move_files(src_dir, dest_dir): - """Move files from source to destination directory.""" - for f in os.listdir(src_dir): - src_path = os.path.join(src_dir, f) - dst_path = os.path.join(dest_dir, f) - shutil.copy2(src_path, dst_path) - os.remove(src_path) - - -if __name__ == "__main__": - download_dir = Config.DOWNLOAD_DIR - os.makedirs(download_dir, exist_ok=True) - model_name = Config.MODEL_NAME - revision = Config.MODEL_REVISION - path = download_model(model_name, revision) - model_path = "/".join(path.split("/")[:-1]) + "/" - move_files(model_path, download_dir) diff --git a/submodules/moragents_dockers/agents/model_config.py b/submodules/moragents_dockers/agents/model_config.py deleted file mode 100644 index a5868e4..0000000 --- a/submodules/moragents_dockers/agents/model_config.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" diff --git a/submodules/moragents_dockers/agents/requirements.txt b/submodules/moragents_dockers/agents/requirements.txt index e87e422..8542cb4 100644 --- a/submodules/moragents_dockers/agents/requirements.txt +++ b/submodules/moragents_dockers/agents/requirements.txt @@ -1,17 +1,23 @@ llama-cpp-python==0.2.90 -transformers==4.43.3 sentencepiece==0.2.0 protobuf==5.27.2 scikit-learn==1.5.1 -huggingface-hub==0.24.3 -flask==2.2.2 +fastapi==0.115.0 Werkzeug==2.2.2 -flask-cors==4.0.1 web3==6.20.1 pymupdf==1.22.5 faiss-cpu==1.8.0.post1 -langchain-text-splitters==0.2.2 -langchain-core==0.2.24 -langchain-community==0.2.10 +feedparser +langchain-text-splitters==0.3.0 +langchain-core==0.3.9 +langchain-community==0.3.1 +langchain-ollama==0.2.0 +tweepy==4.14.0 +uvicorn==0.31.0 +python-dateutil +python-multipart==0.0.12 +beautifulsoup4==4.12.3 +selenium==4.25.0 torch -tweepy \ No newline at end of file +pytz +pyshorteners diff --git a/submodules/benchmarks/claim_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/src/__init__.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/adapters/__init__.py rename to submodules/moragents_dockers/agents/src/__init__.py diff --git a/submodules/benchmarks/price_fetching/__init__.py b/submodules/moragents_dockers/agents/src/agents/__init__.py similarity index 100% rename from submodules/benchmarks/price_fetching/__init__.py rename to submodules/moragents_dockers/agents/src/agents/__init__.py diff --git a/submodules/moragents_dockers/agents/src/data_agent/README.md b/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/data_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/crypto_data/README.md diff --git a/submodules/benchmarks/price_fetching/adapters/__init__.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/__init__.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py new file mode 100644 index 0000000..4240e55 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py @@ -0,0 +1,93 @@ +import json +import logging + +from src.agents.crypto_data import tools +from src.models.messages import ChatRequest +from src.stores import agent_manager + +logger = logging.getLogger(__name__) + + +class CryptoDataAgent: + def __init__(self, config, llm, embeddings): + self.config = config + self.llm = llm + self.embeddings = embeddings + self.tools_provided = tools.get_tools() + + def get_response(self, message): + system_prompt = ( + "Don't make assumptions about the value of the arguments for the function " + "they should always be supplied by the user and do not alter the value of the arguments. " + "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " + "request is ambiguous." + ) + + messages = [ + {"role": "system", "content": system_prompt}, + ] + messages.extend(message) + + logger.info("Sending request to LLM with %d messages", len(messages)) + + llm_with_tools = self.llm.bind_tools(self.tools_provided) + + try: + result = llm_with_tools.invoke(messages) + logger.info("Received response from LLM: %s", result) + + if result.tool_calls: + tool_call = result.tool_calls[0] + func_name = tool_call.get("name") + args = tool_call.get("args") + logger.info("LLM suggested using tool: %s", func_name) + + if func_name == "get_price": + return tools.get_coin_price_tool(args["coin_name"]), "assistant" + elif func_name == "get_floor_price": + return tools.get_nft_floor_price_tool(args["nft_name"]), "assistant" + elif func_name == "get_fdv": + return ( + tools.get_fully_diluted_valuation_tool(args["coin_name"]), + "assistant", + ) + elif func_name == "get_tvl": + return ( + tools.get_protocol_total_value_locked_tool( + args["protocol_name"] + ), + "assistant", + ) + elif func_name == "get_market_cap": + return ( + tools.get_coin_market_cap_tool(args["coin_name"]), + "assistant", + ) + else: + logger.info("LLM provided a direct response without using tools") + return result.content, "assistant" + except Exception as e: + logger.error(f"Error in get_response: {str(e)}") + return f"An error occurred: {str(e)}", "assistant" + + def generate_response(self, prompt): + response, role = self.get_response([prompt]) + return response, role + + def chat(self, request: ChatRequest): + try: + data = request.dict() + if "prompt" in data: + prompt = data["prompt"] + logger.info( + "Received chat request with prompt: %s", + prompt[:50] + "..." if len(prompt) > 50 else prompt, + ) + response, role = self.generate_response(prompt) + return {"role": role, "content": response} + else: + logger.warning("Received chat request without 'prompt' in data") + return {"error": "Missing required parameters"}, 400 + except Exception as e: + logger.error("Error in chat method: %s", str(e), exc_info=True) + return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py similarity index 70% rename from submodules/moragents_dockers/agents/src/data_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/config.py index 0c916ec..63db777 100644 --- a/submodules/moragents_dockers/agents/src/data_agent/src/config.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/config.py @@ -3,25 +3,25 @@ # Logging configuration logging.basicConfig(level=logging.INFO) + # Configuration object class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" + # API endpoints COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" DEFILLAMA_BASE_URL = "https://api.llama.fi" 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." - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py new file mode 100644 index 0000000..d8417f5 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/routes.py @@ -0,0 +1,14 @@ +import logging + +from flask import Blueprint, request, jsonify + +crypto_data_agent_bp = Blueprint('crypto_data_agent', __name__) +logger = logging.getLogger(__name__) + +@crypto_data_agent_bp.route('/process_data', methods=['POST']) +def process_data(): + logger.info("Data Agent: Received process_data request") + data = request.get_json() + # Implement your data processing logic here + response = {"status": "success", "message": "Data processed"} + return jsonify(response) diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py similarity index 64% rename from submodules/moragents_dockers/agents/src/data_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py index e02839a..0b0b100 100644 --- a/submodules/moragents_dockers/agents/src/data_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py @@ -1,9 +1,9 @@ import requests import logging -from data_agent.src.config import Config from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity +from src.agents.crypto_data.config import Config def get_most_similar(text, data): @@ -13,9 +13,12 @@ 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 + def get_coingecko_id(text, type="coin"): """Get the CoinGecko ID for a given coin or NFT.""" url = f"{Config.COINGECKO_BASE_URL}/search" @@ -25,30 +28,32 @@ def get_coingecko_id(text, type="coin"): response.raise_for_status() data = response.json() if type == "coin": - return data['coins'][0]['id'] if data['coins'] else None + return data["coins"][0]["id"] if data["coins"] else None elif type == "nft": - return data['nfts'][0]['id'] if data.get('nfts') else None + return data["nfts"][0]["id"] if data.get("nfts") else None else: raise ValueError("Invalid type specified") except requests.exceptions.RequestException as e: logging.error(f"API request failed: {str(e)}") raise + def get_price(coin): """Get the price of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: return None url = f"{Config.COINGECKO_BASE_URL}/simple/price" - params = {'ids': coin_id, 'vs_currencies': 'USD'} + params = {"ids": coin_id, "vs_currencies": "USD"} try: response = requests.get(url, params=params) response.raise_for_status() - return response.json()[coin_id]['usd'] + return response.json()[coin_id]["usd"] except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve price: {str(e)}") raise + def get_floor_price(nft): """Get the floor price of an NFT from CoinGecko API.""" nft_id = get_coingecko_id(str(nft), type="nft") @@ -63,6 +68,7 @@ def get_floor_price(nft): logging.error(f"Failed to retrieve floor price: {str(e)}") raise + def get_fdv(coin): """Get the fully diluted valuation of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") @@ -78,21 +84,23 @@ def get_fdv(coin): logging.error(f"Failed to retrieve FDV: {str(e)}") raise + def get_market_cap(coin): """Get the market cap of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: return None url = f"{Config.COINGECKO_BASE_URL}/coins/markets" - params = {'ids': coin_id, 'vs_currency': 'USD'} + params = {"ids": coin_id, "vs_currency": "USD"} try: response = requests.get(url, params=params) response.raise_for_status() - return response.json()[0]['market_cap'] + return response.json()[0]["market_cap"] except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve market cap: {str(e)}") raise + def get_protocols_list(): """Get the list of protocols from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/protocols" @@ -100,11 +108,16 @@ def get_protocols_list(): response = requests.get(url) response.raise_for_status() data = response.json() - return [item['slug'] for item in data] ,[item['name'] for item in data] ,[item['gecko_id'] for item in data] + return ( + [item["slug"] for item in data], + [item["name"] for item in data], + [item["gecko_id"] for item in data], + ) except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve protocols list: {str(e)}") raise + def get_tvl_value(protocol_id): """Gets the TVL value using the protocol ID from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/tvl/{protocol_id}" @@ -114,11 +127,12 @@ def get_tvl_value(protocol_id): return response.json() except requests.exceptions.RequestException as e: logging.error(f"Failed to retrieve protocol TVL: {str(e)}") - raise + raise + def get_protocol_tvl(protocol_name): """Get the TVL (Total Value Locked) of a protocol from DefiLlama API.""" - id,name,gecko = get_protocols_list() + id, name, gecko = get_protocols_list() tag = get_coingecko_id(protocol_name) if tag: protocol_id = next((i for i, j in zip(id, gecko) if j == tag), None) @@ -157,7 +171,9 @@ 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 @@ -168,8 +184,10 @@ def get_protocol_total_value_locked_tool(protocol_name): tvl = get_protocol_tvl(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) + 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 + ) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE @@ -191,96 +209,99 @@ 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 + def get_tools(): """Return a list of tools for the agent.""" return [ { - "type": "function", - "function": { - "name": "get_price", - "description": "Get the price of a cryptocurrency", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "The name of the coin.", - } + "type": "function", + "function": { + "name": "get_price", + "description": "Get the price of a cryptocurrency", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "The name of the coin.", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - }, { - "type": "function", - "function": { - "name": "get_floor_price", - "description": "Get the floor price of an NFT", - "parameters": { - "type": "object", - "properties": { - "nft_name": { - "type": "string", - "description": "Name of the NFT", - } + "type": "function", + "function": { + "name": "get_floor_price", + "description": "Get the floor price of an NFT", + "parameters": { + "type": "object", + "properties": { + "nft_name": { + "type": "string", + "description": "Name of the NFT", + } + }, + "required": ["nft_name"], }, - "required": ["nft_name"], }, }, - }, - { - "type": "function", - "function": { - "name": "get_tvl", - "description": "Get the TVL (Total Value Locked) of a protocol.", - "parameters": { - "type": "object", - "properties": { - "protocol_name": { - "type": "string", - "description": "Name of the protocol", - } + { + "type": "function", + "function": { + "name": "get_tvl", + "description": "Get the TVL (Total Value Locked) of a protocol.", + "parameters": { + "type": "object", + "properties": { + "protocol_name": { + "type": "string", + "description": "Name of the protocol", + } + }, + "required": ["protocol_name"], }, - "required": ["protocol_name"], }, }, - }, - { - "type": "function", - "function": { - "name": "get_fdv", - "description": "Get the fdv or fully diluted valuation of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } + { + "type": "function", + "function": { + "name": "get_fdv", + "description": "Get the fdv or fully diluted valuation of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - } , - { - "type": "function", - "function": { - "name": "get_market_cap", - "description": "Get the mc or market cap of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } + { + "type": "function", + "function": { + "name": "get_market_cap", + "description": "Get the mc or market cap of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], }, - "required": ["coin_name"], }, }, - } ] diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/__init__.py rename to submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py new file mode 100644 index 0000000..a18f7f4 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py @@ -0,0 +1,170 @@ +from src.agents.mor_claims import tools +from src.models.messages import ChatRequest +from src.stores import agent_manager + + +class MorClaimsAgent: + def __init__(self, agent_info, llm, embeddings): + self.agent_info = agent_info + self.llm = llm + self.embeddings = embeddings + self.tools_provided = tools.get_tools() + self.conversation_state = {} + + def _get_response(self, message, wallet_address): + if wallet_address not in self.conversation_state: + self.conversation_state[wallet_address] = {"state": "initial"} + + state = self.conversation_state[wallet_address]["state"] + + if state == "initial": + agent_manager.set_active_agent(self.agent_info["name"]) + + rewards = { + 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 + } + + 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" + 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", + self.agent_info["name"], + ) + else: + return ( + f"No rewards found for your wallet address {wallet_address} in either pool. Claim cannot be processed.", + "assistant", + None, + ) + + elif state == "awaiting_confirmation": + user_input = message[-1]["content"].lower() + if any( + word in user_input for word in ["yes", "proceed", "confirm", "claim"] + ): + return self.prepare_transactions(wallet_address) + else: + return ( + "Please confirm if you want to proceed with the claim by saying 'yes', 'proceed', 'confirm', or 'claim'.", + "assistant", + self.agent_info["name"], + ) + + return ( + "I'm sorry, I didn't understand that. Can you please rephrase your request?", + "assistant", + self.agent_info["name"], + ) + + def prepare_transactions(self, wallet_address): + available_rewards = self.conversation_state[wallet_address]["available_rewards"] + receiver_address = self.conversation_state[wallet_address]["receiver_address"] + transactions = [] + + for pool_id in available_rewards.keys(): + try: + tx_data = tools.prepare_claim_transaction(pool_id, receiver_address) + transactions.append({"pool": pool_id, "transaction": tx_data}) + except Exception as e: + return ( + f"Error preparing transaction for pool {pool_id}: {str(e)}", + "assistant", + None, + ) + + self.conversation_state[wallet_address]["transactions"] = transactions + + # Return a structured response + return ( + { + "role": "claim", + "content": {"transactions": transactions, "claim_tx_cb": "/claim"}, + }, + "claim", + None, + ) + + def chat(self, request: ChatRequest): + try: + data = request.dict() + 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 + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 + + def claim(self, request: ChatRequest): + try: + data = request.dict() + wallet_address = data["wallet_address"] + transactions = self.conversation_state[wallet_address]["transactions"] + agent_manager.clear_active_agent() + return {"transactions": transactions} + except Exception as e: + return {"error": str(e)}, 500 + + def claim_status(self, request: ChatRequest): + try: + data = request.dict() + wallet_address = data.get("wallet_address") + transaction_hash = data.get("transaction_hash") + status = data.get("status") + + if not all([wallet_address, transaction_hash, status]): + return {"error": "Missing required parameters"}, 400 + + # Generate and return the status message + response = self.get_status(status, transaction_hash, "claim") + return response, 200 + except Exception as e: + return {"error": str(e)}, 500 + + def get_status(self, flag, tx_hash, tx_type): + response = "" + + if flag == "cancelled": + response = f"The claim transaction has been cancelled." + elif flag == "success": + response = f"The claim transaction was successful." + 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." + ) + + if tx_hash: + response = ( + response + f" The transaction hash is {tx_hash}. " + f"Here's the link to the Etherscan transaction: " + f"https://etherscan.io/tx/{tx_hash}" + ) + + if flag != "initiated": + response = response + " Is there anything else I can help you with?" + + return {"role": "assistant", "content": response} diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py new file mode 100644 index 0000000..44d7639 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/config.py @@ -0,0 +1,35 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + + WEB3RPCURL = {"1": "https://eth.llamarpc.com/"} + MINT_FEE = 0.001 # in ETH + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "receiver_", "type": "address"}, + ], + "name": "claim", + "outputs": [], + "stateMutability": "payable", + "type": "function", + }, + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + }, + ] diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py similarity index 71% rename from submodules/moragents_dockers/agents/src/claim_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py index bee91c6..e5f0143 100644 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py @@ -1,11 +1,13 @@ from web3 import Web3 -from claim_agent.src.config import Config + +from src.agents.mor_claims.config import Config + 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: @@ -13,37 +15,41 @@ 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)}") + def prepare_claim_transaction(pool_id, wallet_address): try: web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL["1"])) contract = web3.eth.contract( address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), - abi=Config.DISTRIBUTION_ABI + abi=Config.DISTRIBUTION_ABI, + ) + tx_data = contract.encode_abi( + fn_name="claim", args=[pool_id, web3.to_checksum_address(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} ) - tx_data = contract.encode_abi(fn_name="claim", args=[pool_id, web3.to_checksum_address(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 - }) return { "to": Config.DISTRIBUTION_PROXY_ADDRESS, "data": tx_data, "value": str(mint_fee), "gas": str(estimated_gas), - "chainId": "1" + "chainId": "1", } except Exception as e: raise Exception(f"Failed to prepare claim transaction: {str(e)}") + def get_tools(): return [ { @@ -56,16 +62,16 @@ def get_tools(): "properties": { "wallet_address": { "type": "string", - "description": "The wallet address to check rewards for" + "description": "The wallet address to check rewards for", }, "pool_id": { "type": "integer", - "description": "The ID of the pool to check rewards from" - } + "description": "The ID of the pool to check rewards from", + }, }, - "required": ["wallet_address", "pool_id"] - } - } + "required": ["wallet_address", "pool_id"], + }, + }, }, { "type": "function", @@ -77,15 +83,15 @@ def get_tools(): "properties": { "pool_id": { "type": "integer", - "description": "The ID of the pool to claim from" + "description": "The ID of the pool to claim from", }, "wallet_address": { "type": "string", - "description": "The wallet address to claim rewards for" - } + "description": "The wallet address to claim rewards for", + }, }, - "required": ["pool_id", "wallet_address"] - } - } - } - ] \ No newline at end of file + "required": ["pool_id", "wallet_address"], + }, + }, + }, + ] diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/adapters/__init__.py rename to submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py similarity index 62% rename from submodules/moragents_dockers/agents/src/reward_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py index 44555da..d3dfff1 100644 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py @@ -1,17 +1,16 @@ -import json import logging -from reward_agent.src import tools + +from src.agents.mor_rewards import tools +from src.models.messages import ChatRequest logger = logging.getLogger(__name__) -class RewardAgent: - def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): +class MorRewardsAgent: + def __init__(self, agent_info, llm, embeddings): self.agent_info = agent_info self.llm = llm - self.llm_ollama = llm_ollama self.embeddings = embeddings - self.flask_app = flask_app self.tools_provided = tools.get_tools() def get_response(self, message, wallet_address): @@ -20,7 +19,7 @@ def get_response(self, message, wallet_address): try: rewards = { 0: tools.get_current_user_reward(wallet_address, 0), - 1: tools.get_current_user_reward(wallet_address, 1) + 1: tools.get_current_user_reward(wallet_address, 1), } response = f"Your current MOR rewards:\n" @@ -31,16 +30,26 @@ def get_response(self, message, wallet_address): return response, "assistant", None except Exception as e: logger.error(f"Error occurred while checking rewards: {str(e)}") - return f"An error occurred while checking your rewards: {str(e)}", "assistant", None + return ( + f"An error occurred while checking your rewards: {str(e)}", + "assistant", + None, + ) - def chat(self, request): + def chat(self, request: ChatRequest): try: - data = request.get_json() - 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) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} + data = request.dict() + 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 + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } else: logger.warning("Missing required parameters in request") return {"error": "Missing required parameters"}, 400 diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py new file mode 100644 index 0000000..e8ac97e --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/config.py @@ -0,0 +1,26 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + + WEB3RPCURL = { + "1": "https://eth.llamarpc.com/", + } + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + } + ] diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py similarity index 73% rename from submodules/moragents_dockers/agents/src/reward_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py index cbb749f..553041e 100644 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py @@ -1,11 +1,13 @@ from web3 import Web3 -from reward_agent.src.config import Config + +from src.agents.mor_rewards.config import Config + 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 + address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), + abi=Config.DISTRIBUTION_ABI, ) try: @@ -13,14 +15,14 @@ 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)}") + def get_tools(): return [ { @@ -33,15 +35,15 @@ def get_tools(): "properties": { "wallet_address": { "type": "string", - "description": "The wallet address to check rewards for" + "description": "The wallet address to check rewards for", }, "pool_id": { "type": "integer", - "description": "The ID of the pool to check rewards from" - } + "description": "The ID of the pool to check rewards from", + }, }, - "required": ["wallet_address", "pool_id"] - } - } + "required": ["wallet_address", "pool_id"], + }, + }, } - ] \ No newline at end of file + ] diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py b/submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py new file mode 100644 index 0000000..69ced40 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py @@ -0,0 +1,158 @@ +import json +import logging +import re +import urllib.parse +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.models.messages import ChatRequest +import pyshorteners + +logger = logging.getLogger(__name__) + + +class NewsAgent: + def __init__(self, agent_info, llm, embeddings): + self.agent_info = agent_info + self.llm = llm + self.embeddings = embeddings + self.tools_provided = self.get_tools() + self.url_shortener = pyshorteners.Shortener() + + def get_tools(self): + return [ + { + "type": "function", + "function": { + "name": "fetch_crypto_news", + "description": "Fetch and analyze cryptocurrency news for potential price impacts", + "parameters": { + "type": "object", + "properties": { + "coins": { + "type": "array", + "items": {"type": "string"}, + "description": "List of cryptocurrency symbols to fetch news for", + } + }, + "required": ["coins"], + }, + }, + } + ] + + def check_relevance_and_summarize(self, title, content, coin): + logger.info(f"Checking relevance for {coin}: {title}") + prompt = Config.RELEVANCE_PROMPT.format(coin=coin, title=title, content=content) + result = self.llm.create_chat_completion( + messages=[{"role": "user", "content": prompt}], + max_tokens=Config.LLM_MAX_TOKENS, + temperature=Config.LLM_TEMPERATURE, + ) + return result["choices"][0]["message"]["content"].strip() + + def process_rss_feed(self, feed_url, coin): + logger.info(f"Processing RSS feed for {coin}: {feed_url}") + feed = fetch_rss_feed(feed_url) + results = [] + for entry in feed.entries: + published_time = entry.get("published") or entry.get("updated") + if is_within_time_window(published_time): + title = clean_html(entry.title) + content = clean_html(entry.summary) + 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} + ) + if len(results) >= Config.ARTICLES_PER_TOKEN: + break + else: + logger.info( + f"Skipping article: {entry.title} (published: {published_time})" + ) + logger.info(f"Found {len(results)} relevant articles for {coin}") + return results + + def fetch_crypto_news(self, coins): + logger.info(f"Fetching news for coins: {coins}") + all_news = [] + for coin in coins: + logger.info(f"Processing news for {coin}") + coin_name = Config.CRYPTO_DICT.get(coin.upper(), coin) + 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] + ] + ) + + logger.info(f"Total news items fetched: {len(all_news)}") + return all_news + + def chat(self, request: ChatRequest): + try: + data = request.dict() + if "prompt" in data: + prompt = data["prompt"] + if isinstance(prompt, dict) and "content" in prompt: + prompt = prompt["content"] + + # Updated coin detection logic + coins = re.findall( + r"\b(" + + "|".join(re.escape(key) for key in Config.CRYPTO_DICT.keys()) + + r")\b", + prompt.upper(), + ) + + if not coins: + return { + "role": "assistant", + "content": "I couldn't identify any cryptocurrency symbols in your message. Please specify the cryptocurrencies you want news for.", + "next_turn_agent": None, + } + + news = self.fetch_crypto_news(coins) + + if not news: + return { + "role": "assistant", + "content": "No relevant news found for the specified cryptocurrencies in the last 24 hours.", + "next_turn_agent": None, + } + + response = "Here are the latest news items relevant to changes in price movement of the mentioned tokens in the last 24 hours:\n\n" + for index, item in enumerate(news, start=1): + coin_name = Config.CRYPTO_DICT.get(item["Coin"], item["Coin"]) + short_url = self.url_shortener.tinyurl.short(item["Link"]) + response += f"{index}. ***{coin_name} News***:\n" + response += f"{item['Title']}\n" + response += f"{item['Summary']}\n" + response += f"Read more: {short_url}\n\n" + + return { + "role": "assistant", + "content": response, + "next_turn_agent": None, + } + else: + return { + "role": "assistant", + "content": "Missing required parameters", + "next_turn_agent": None, + } + + except Exception as e: + logger.error(f"Error in chat method: {str(e)}", exc_info=True) + return { + "role": "assistant", + "content": f"An error occurred: {str(e)}", + "next_turn_agent": None, + } diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py new file mode 100644 index 0000000..c8a399b --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/config.py @@ -0,0 +1,129 @@ +import logging + +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" + + # Time window for news (in hours) + NEWS_TIME_WINDOW = 24 + + # Number of articles to show per token + ARTICLES_PER_TOKEN = 1 + + # LLM configuration + LLM_MAX_TOKENS = 150 + LLM_TEMPERATURE = 0.3 + + # Prompts + RELEVANCE_PROMPT = ( + "Consider the following news article about {coin}:\n\n" + "Title: {title}\n\nContent: {content}\n\n" + "Is this article relevant to potential price impacts on the cryptocurrency? " + "If yes, provide a concise summary focused on how it might impact trading or prices. " + "If it's not relevant or only about price movements, respond with 'NOT RELEVANT'." + ) + + # 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' + } \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py new file mode 100644 index 0000000..f017b22 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py @@ -0,0 +1,71 @@ +import feedparser +from datetime import datetime, timedelta +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) + cleantext = unescape(cleantext) + cleantext = ' '.join(cleantext.split()) + return cleantext + + +def is_within_time_window(published_time, hours=24): + if not published_time: + return False + try: + pub_date = parser.parse(published_time, fuzzy=True) + now = datetime.now(pytz.UTC) + if pub_date.tzinfo is None: + pub_date = pub_date.replace(tzinfo=pytz.UTC) + return (now - pub_date) <= timedelta(hours=hours) + except Exception as e: + logger.error(f"Error parsing date: {str(e)} for date {published_time}") + return False + + +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']] + encoded_query = urllib.parse.urlencode(query_params, doseq=True) + encoded_url = urllib.parse.urlunparse(parsed_url._replace(query=encoded_query)) + + return feedparser.parse(encoded_url) + + +def get_tools(): + """Return a list of tools for the agent.""" + return [ + { + "type": "function", + "function": { + "name": "fetch_crypto_news", + "description": "Fetch and analyze cryptocurrency news for potential price impacts", + "parameters": { + "type": "object", + "properties": { + "coins": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of cryptocurrency symbols to fetch news for" + } + }, + "required": ["coins"] + } + } + } + ] \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/agents/rag/__init__.py b/submodules/moragents_dockers/agents/src/agents/rag/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agents/rag/agent.py b/submodules/moragents_dockers/agents/src/agents/rag/agent.py new file mode 100644 index 0000000..a4cfcfa --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/rag/agent.py @@ -0,0 +1,117 @@ +import os +import logging + +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 + +logger = logging.getLogger(__name__) +UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") + + +class RagAgent: + 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.prompt = ChatPromptTemplate.from_template( + """ + Answer the following question only based on the given context + + + {context} + + + Question: {input} + """ + ) + self.max_size = 5 * 1024 * 1024 + self.retriever = None + + async def handle_file_upload(self, file): + if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER, exist_ok=True) + filename = secure_filename(file.filename) + file_path = os.path.join(UPLOAD_FOLDER, filename) + + # Save the file + with open(file_path, "wb") as buffer: + content = await file.read() + buffer.write(content) + + # DocumentToolsGenerator class instantiation + loader = PyMuPDFLoader(file_path) + docs = loader.load() + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1024, + chunk_overlap=20, + length_function=len, + is_separator_regex=False, + ) + split_documents = text_splitter.split_documents(docs) + vector_store = FAISS.from_documents(split_documents, self.embedding) + self.retriever = vector_store.as_retriever(search_kwargs={"k": 7}) + + async def upload_file(self, request: Request): + logger.info(f"Received upload request: {request}") + file = request["file"] + if file.filename == "": + return {"error": "No selected file"}, 400 + + # Check file size to ensure it's less than 5 MB + content = await file.read() + await file.seek(0) + if len(content) > self.max_size: + return {"role": "assistant", "content": "Please use a file less than 5 MB"} + + try: + await self.handle_file_upload(file) + chat_manager.set_uploaded_file(True) + return { + "role": "assistant", + "content": "You have successfully uploaded the text", + } + except Exception as e: + logging.error(f"Error during file upload: {str(e)}") + return {"error": str(e)}, 500 + + def _get_rag_response(self, prompt): + retrieved_docs = self.retriever.invoke(prompt) + formatted_context = "\n\n".join(doc.page_content for doc in retrieved_docs) + formatted_prompt = f"Question: {prompt}\n\nContext: {formatted_context}" + messages = [ + { + "role": "system", + "content": "You are a helpful assistant. Use the provided context to respond to the following question.", + }, + {"role": "user", "content": formatted_prompt}, + ] + result = self.llm.invoke(messages) + return result.content.strip() + + def chat(self, request: ChatRequest): + try: + data = request.dict() + if "prompt" in data: + prompt = data["prompt"]["content"] + + if chat_manager.get_uploaded_file_status(): + response = self._get_rag_response(prompt) + else: + response = "Please upload a file first" + return {"role": "assistant", "content": response} + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + logging.error(f"Error in chat endpoint: {str(e)}") + return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/rag_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/rag/config.py similarity index 59% rename from submodules/moragents_dockers/agents/src/rag_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/rag/config.py index 63983c2..13716f4 100644 --- a/submodules/moragents_dockers/agents/src/rag_agent/src/config.py +++ b/submodules/moragents_dockers/agents/src/agents/rag/config.py @@ -3,7 +3,8 @@ # Logging configuration logging.basicConfig(level=logging.INFO) + # Configuration object class Config: - MAX_FILE_SIZE=5 * 1024 * 1024 # 5 MB - MAX_LENGTH=16 * 1024 * 1024 \ No newline at end of file + MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB + MAX_LENGTH = 16 * 1024 * 1024 diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py b/submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py new file mode 100644 index 0000000..1c098b9 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py @@ -0,0 +1,137 @@ +import logging +import requests +import time + +from bs4 import BeautifulSoup +from selenium import webdriver +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( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + + +class RealtimeSearchAgent: + def __init__(self, config, llm, embeddings): + self.config = config + self.llm = llm + self.embeddings = embeddings + self.last_search_term = None + + def perform_search_with_web_scraping(self, search_term=None): + if search_term is not None: + self.last_search_term = search_term + elif self.last_search_term is None: + logger.warning("No search term available for web search") + return "Web search failed. Please provide a search term." + else: + search_term = self.last_search_term + + logger.info(f"Performing web search for: {search_term}") + + try: + url = f"https://www.google.com/search?q={search_term}" + headers = {"User-Agent": USER_AGENT} + response = requests.get(url, headers=headers) + response.raise_for_status() + + soup = BeautifulSoup(response.text, "html.parser") + + search_results = soup.find_all("div", class_="g") + + formatted_results = [] + for result in search_results[:5]: + result_text = result.get_text(strip=True) + formatted_results.append(f"Result:\n{result_text}") + + return "\n\n".join(formatted_results) + + except requests.RequestException as e: + logger.error(f"Error performing web search: {str(e)}") + logger.info("Attempting fallback to headless browsing") + return self.perform_search_with_headless_browsing(search_term) + + def perform_search_with_headless_browsing(self, search_term): + chrome_options = Options() + chrome_options.add_argument("--headless") + + driver = webdriver.Chrome(options=chrome_options) + + try: + driver.get("https://www.google.com") + + search_box = driver.find_element(By.NAME, "q") + search_box.send_keys(search_term) + search_box.send_keys(Keys.RETURN) + + time.sleep(2) + + soup = BeautifulSoup(driver.page_source, "html.parser") + + search_results = soup.find_all("div", class_="g") + + formatted_results = [] + for result in search_results[:5]: + result_text = result.get_text(strip=True) + formatted_results.append(f"Result:\n{result_text}") + + return "\n\n".join(formatted_results) + + except Exception as e: + logger.error(f"Error performing headless web search: {str(e)}") + return f"Error performing web search: {str(e)}" + finally: + driver.quit() + + def synthesize_answer(self, search_term, search_results): + logger.info("Synthesizing answer from 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. + Just provide the answer.""", + }, + { + "role": "user", + "content": f"""Based on the following search results for the query '{search_term}', provide a concise and informative answer: {search_results}""", + }, + ] + + try: + result = self.llm.invoke(messages) + logger.info(f"Received response from LLM: {result}") + return result.content.strip() + except Exception as e: + logger.error(f"Error synthesizing answer: {str(e)}") + raise + + def chat(self, request: ChatRequest): + try: + data = request.dict() + logger.info(f"Received chat request: {data}") + if "prompt" in data: + prompt = data["prompt"] + search_term = prompt["content"] + logger.info(f"Performing web search for prompt: {search_term}") + + search_results = self.perform_search_with_web_scraping(search_term) + logger.info(f"Search results obtained") + + synthesized_answer = self.synthesize_answer(search_term, search_results) + logger.info(f"Synthesized answer: {synthesized_answer}") + + return {"role": "assistant", "content": synthesized_answer} + else: + logger.error("Missing 'prompt' in chat request data") + return {"error": "Missing parameters"}, 400 + except Exception as e: + logger.exception(f"Unexpected error in chat method: {str(e)}") + return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/swap_agent/README.md b/submodules/moragents_dockers/agents/src/agents/token_swap/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/swap_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/token_swap/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py b/submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py new file mode 100644 index 0000000..e99e9b3 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/agent.py @@ -0,0 +1,221 @@ +import json +import requests +import logging + +from src.agents.token_swap import tools +from src.agents.token_swap.config import Config +from src.models.messages import ChatRequest +from src.stores import agent_manager + +logger = logging.getLogger(__name__) + + +class TokenSwapAgent: + def __init__(self, config, llm, embeddings): + self.config = config + self.llm = llm + self.embeddings = embeddings + self.tools_provided = tools.get_tools() + self.context = [] + + def api_request_url(self, method_name, query_params, chain_id): + base_url = Config.APIBASEURL + str(chain_id) + return f"{base_url}{method_name}?{'&'.join([f'{key}={value}' for key, value in query_params.items()])}" + + def check_allowance(self, token_address, wallet_address, chain_id): + url = self.api_request_url( + "/approve/allowance", + {"tokenAddress": token_address, "walletAddress": wallet_address}, + chain_id, + ) + response = requests.get(url, headers=Config.HEADERS) + data = response.json() + return data + + def approve_transaction(self, token_address, chain_id, amount=None): + query_params = ( + {"tokenAddress": token_address, "amount": amount} + if amount + else {"tokenAddress": token_address} + ) + url = self.api_request_url("/approve/transaction", query_params, chain_id) + response = requests.get(url, headers=Config.HEADERS) + transaction = response.json() + return transaction + + def build_tx_for_swap(self, swap_params, chain_id): + url = self.api_request_url("/swap", swap_params, chain_id) + swap_transaction = requests.get(url, headers=Config.HEADERS).json() + return swap_transaction + + def get_response(self, message, chain_id, wallet_address): + system_prompt = ( + "Don't make assumptions about the value of the arguments for the function " + "they should always be supplied by the user and do not alter the value of the arguments. " + "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " + "request is ambiguous. you only need the value of token1 we dont need the value of token2. After " + "starting from scratch do not assume the name of token1 or token2" + ) + + messages = [ + {"role": "system", "content": system_prompt}, + ] + messages.extend(message) + + logger.info("Sending request to LLM with %d messages", len(messages)) + + llm_with_tools = self.llm.bind_tools(self.tools_provided) + + try: + result = llm_with_tools.invoke(messages) + logger.info("Received response from LLM: %s", result) + + if result.tool_calls: + tool_call = result.tool_calls[0] + logger.info("Selected tool: %s", tool_call) + func_name = tool_call.get("name") + args = tool_call.get("args") + logger.info("LLM suggested using tool: %s", func_name) + + if func_name == "swap_agent": + tok1 = args["token1"] + tok2 = args["token2"] + value = args["value"] + try: + res, role = tools.swap_coins( + tok1, tok2, float(value), chain_id, wallet_address + ) + except ( + tools.InsufficientFundsError, + tools.TokenNotFoundError, + tools.SwapNotPossibleError, + ) as e: + self.context = [] + return str(e), "assistant", None + return res, role, None + else: + logger.info("LLM provided a direct response without using tools") + return result.content, "assistant", "crypto swap agent" + except Exception as e: + logger.error(f"Error in get_response: {str(e)}") + return f"An error occurred: {str(e)}", "assistant", None + + def get_status(self, flag, tx_hash, tx_type): + response = "" + + if flag == "cancelled": + response = f"The {tx_type} transaction has been cancelled." + elif flag == "success": + response = f"The {tx_type} transaction was successful." + elif flag == "failed": + response = f"The {tx_type} transaction has failed." + elif flag == "initiated": + response = f"Transaction has been sent, please wait for it to be confirmed." + + if tx_hash: + response = response + f" The transaction hash is {tx_hash}." + + if flag == "success" and tx_type == "approve": + response = response + " Please proceed with the swap transaction." + elif flag != "initiated": + response = response + " Is there anything else I can help you with?" + + if flag != "initiated": + self.context = [] + self.context.append({"role": "assistant", "content": response}) + 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 + ) + return response, role, next_turn_agent + + def chat(self, request: ChatRequest): + data = request.dict() + try: + if "prompt" in data: + prompt = data["prompt"] + wallet_address = data["wallet_address"] + chain_id = data["chain_id"] + response, role, next_turn_agent = self.generate_response( + prompt, chain_id, wallet_address + ) + return { + "role": role, + "content": response, + "next_turn_agent": next_turn_agent, + } + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 + + def tx_status(self, data): + try: + if "status" in data: + prompt = data["status"] + tx_hash = data.get("tx_hash", "") + tx_type = data.get("tx_type", "") + response = self.get_status(prompt, tx_hash, tx_type) + return response + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 + + def get_allowance(self, request_data): + try: + if "tokenAddress" in request_data: + token = request_data["tokenAddress"] + wallet_address = request_data["walletAddress"] + chain_id = request_data["chain_id"] + res = self.check_allowance(token, wallet_address, chain_id) + return {"response": res} + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 + + def approve(self, request_data): + try: + if "tokenAddress" in request_data: + token = request_data["tokenAddress"] + chain_id = request_data["chain_id"] + amount = request_data["amount"] + res = self.approve_transaction(token, chain_id, amount) + return {"response": res} + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 + + def swap(self, request_data): + try: + if "src" in request_data: + token1 = request_data["src"] + token2 = request_data["dst"] + wallet_address = request_data["walletAddress"] + amount = request_data["amount"] + slippage = request_data["slippage"] + chain_id = request_data["chain_id"] + swap_params = { + "src": token1, + "dst": token2, + "amount": amount, + "from": wallet_address, + "slippage": slippage, + "disableEstimate": False, + "allowPartialFill": False, + } + swap_transaction = self.build_tx_for_swap(swap_params, chain_id) + return swap_transaction + else: + return {"error": "Missing required parameters"}, 400 + except Exception as e: + return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/config.py b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py new file mode 100644 index 0000000..d479874 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/config.py @@ -0,0 +1,54 @@ +import logging + +# Logging configuration +logging.basicConfig(level=logging.INFO) + + +# Configuration object +class Config: + + # API endpoints + INCH_URL = "https://api.1inch.dev/token" + QUOTE_URL = "https://api.1inch.dev/swap" + APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" + HEADERS = { + "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", + "accept": "application/json", + } + WEB3RPCURL = { + "56": "https://bsc-dataseed.binance.org", + "42161": "https://arb1.arbitrum.io/rpc", + "137": "https://polygon-rpc.com", + "1": "https://eth.llamarpc.com/", + "10": "https://mainnet.optimism.io", + "8453": "https://mainnet.base.org", + } + NATIVE_TOKENS = { + "137": "MATIC", + "56": "BNB", + "1": "ETH", + "42161": "ETH", + "10": "ETH", + "8453": "ETH", + } + ERC20_ABI = [ + { + "constant": True, + "inputs": [], + "name": "decimals", + "outputs": [{"name": "", "type": "uint8"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + ] + INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/tools.py b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py similarity index 61% rename from submodules/moragents_dockers/agents/src/swap_agent/src/tools.py rename to submodules/moragents_dockers/agents/src/agents/token_swap/tools.py index 1800dfe..2e2c871 100644 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/tools.py +++ b/submodules/moragents_dockers/agents/src/agents/token_swap/tools.py @@ -2,52 +2,69 @@ import logging import time from web3 import Web3 -from swap_agent.src.config import Config + +from src.agents.token_swap.config import Config class InsufficientFundsError(Exception): pass + class TokenNotFoundError(Exception): pass + class SwapNotPossibleError(Exception): pass 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) + params = {"query": query, "limit": limit, "ignore_listed": ignore_listed} + response = requests.get( + Config.INCH_URL + endpoint, params=params, headers=Config.HEADERS + ) if response.status_code == 200: return response.json() else: logging.error(f"Failed to search tokens. Status code: {response.status_code}") return None -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 + +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: """Convert an amount in ETH to wei.""" return int(amount_in_eth * 10**18) + def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): native = Config.NATIVE_TOKENS # token1 is the native token if token1.lower() == native[str(chain_id)].lower(): - t1 = [{'symbol': native[str(chain_id)], 'address': Config.INCH_NATIVE_TOKEN_ADDRESS}] - t1_bal = get_token_balance(web3, wallet_address, '', Config.ERC20_ABI) + t1 = [ + { + "symbol": native[str(chain_id)], + "address": Config.INCH_NATIVE_TOKEN_ADDRESS, + } + ] + t1_bal = get_token_balance(web3, wallet_address, "", Config.ERC20_ABI) smallest_amount = eth_to_wei(amount) # token1 is an erc20 token @@ -56,12 +73,19 @@ 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) - smallest_amount = convert_to_smallest_unit(web3, amount, t1[0]['address']) + 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 if token2.lower() == native[str(chain_id)].lower(): - t2 = [{'symbol': native[str(chain_id)], 'address': Config.INCH_NATIVE_TOKEN_ADDRESS}] + t2 = [ + { + "symbol": native[str(chain_id)], + "address": Config.INCH_NATIVE_TOKEN_ADDRESS, + } + ] else: t2 = search_tokens(token2, chain_id) time.sleep(2) @@ -72,54 +96,64 @@ def validate_swap(web3: Web3, token1, token2, chain_id, amount, wallet_address): if t1_bal < smallest_amount: raise InsufficientFundsError(f"Insufficient funds to perform the swap.") - return t1[0]['address'], t1[0]['symbol'], t2[0]['address'], t2[0]['symbol'] + return t1[0]["address"], t1[0]["symbol"], t2[0]["address"], t2[0]["symbol"] + 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) + params = {"src": token1, "dst": token2, "amount": int(amount_in_wei)} + response = requests.get( + Config.QUOTE_URL + endpoint, params=params, headers=Config.HEADERS + ) if response.status_code == 200: return response.json() else: logging.error(f"Failed to get quote. Status code: {response.status_code}") return None + def get_token_decimals(web3: Web3, token_address: str) -> int: if not token_address: return 18 # Assuming 18 decimals for the native gas token else: - contract = web3.eth.contract(address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI) + contract = web3.eth.contract( + address=Web3.to_checksum_address(token_address), abi=Config.ERC20_ABI + ) return contract.functions.decimals().call() + def convert_to_smallest_unit(web3: Web3, amount: float, token_address: str) -> int: decimals = get_token_decimals(web3, token_address) - return int(amount * (10 ** decimals)) + 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) + return smallest_unit_amount / (10**decimals) 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 + t1_address = "" if t1_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t1_a smallest_unit_amount = convert_to_smallest_unit(web3, amount, t1_address) result = get_quote(t1_a, t2_a, smallest_unit_amount, chain_id) - + if result: price = result["dstAmount"] - t2_address = '' if t2_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t2_a + t2_address = "" if t2_a == Config.INCH_NATIVE_TOKEN_ADDRESS else t2_a t2_quote = convert_to_readable_unit(web3, int(price), t2_address) else: - raise SwapNotPossibleError("Failed to generate a quote. Please ensure you're on the correct network.") + raise SwapNotPossibleError( + "Failed to generate a quote. Please ensure you're on the correct network." + ) return { "dst": t2_id, @@ -129,9 +163,10 @@ def swap_coins(token1, token2, amount, chain_id, wallet_address): "src_address": t1_a, "src_amount": amount, "approve_tx_cb": "/approve", - "swap_tx_cb": "/swap" + "swap_tx_cb": "/swap", }, "swap" + def get_tools(): """Return a list of tools for the agent.""" return [ @@ -145,19 +180,19 @@ def get_tools(): "properties": { "token1": { "type": "string", - "description": "name or address of the cryptocurrency to sell" + "description": "name or address of the cryptocurrency to sell", }, "token2": { "type": "string", - "description": "name or address of the cryptocurrency to buy" + "description": "name or address of the cryptocurrency to buy", }, "value": { "type": "string", - "description": "Value or amount of the cryptocurrency to sell" - } + "description": "Value or amount of the cryptocurrency to sell", + }, }, - "required": ["token1", "token2", "value"] - } - } + "required": ["token1", "token2", "value"], + }, + }, } - ] \ No newline at end of file + ] diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/README.md b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md similarity index 100% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/README.md rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/agent.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py similarity index 63% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/agent.py rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py index 08dbf32..d840a5b 100644 --- a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/agent.py @@ -1,6 +1,8 @@ import logging import tweepy -from .config import Config + +from src.agents.tweet_sizzler.config import Config +from src.models.messages import ChatRequest # Configure logging logging.basicConfig( @@ -10,10 +12,10 @@ class TweetSizzlerAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm + self.embeddings = embeddings self.x_api_key = None self.last_prompt_content = None self.twitter_client = None @@ -38,20 +40,25 @@ def generate_tweet(self, prompt_content=None): ] try: - result = self.llm.create_chat_completion( - messages=messages, - max_tokens=Config.LLM_MAX_TOKENS, - temperature=Config.LLM_TEMPERATURE, - ) - tweet = result["choices"][0]["message"]["content"] + result = self.llm.invoke(messages) + logger.info(f"Received response from LLM: {result}") + tweet = result.content.strip() + tweet = " ".join(tweet.split()) + + # Remove any dictionary-like formatting, if present + if tweet.startswith("{") and tweet.endswith("}"): + tweet = tweet.strip("{}").split(":", 1)[-1].strip().strip('"') + logger.info(f"Tweet generated successfully: {tweet}") return tweet except Exception as e: logger.error(f"Error generating tweet: {str(e)}") raise - def post_tweet(self, request): - data = request.get_json() + async def post_tweet(self, request): + logger.info(f"Received tweet request: {request}") + data = await request.json() + logger.info(f"Received tweet data: {data}") tweet_content = data.get("post_content") logger.info(f"Received tweet content: {tweet_content}") @@ -111,33 +118,37 @@ def set_x_api_key(self, request): return {"success": "API credentials saved successfully"}, 200 - def chat(self, request): + def chat(self, chat_request: ChatRequest): try: - data = request.get_json() - logger.info(f"Received chat request: {data}") - if "prompt" in data: - prompt = data["prompt"] - action = data.get("action", Config.DEFAULT_ACTION) - logger.debug(f"Extracted prompt: {prompt}, action: {action}") - - if action == "generate": - logger.info(f"Generating tweet for prompt: {prompt['content']}") - tweet = self.generate_tweet(prompt["content"]) - logger.info(f"Generated tweet: {tweet}") - return {"role": "assistant", "content": tweet} - elif action == "post": - logger.info("Attempting to post tweet") - result, status_code = self.post_tweet(request) - logger.info( - f"Posted tweet result: {result}, status code: {status_code}" - ) + prompt = chat_request.prompt.dict() + logger.info(f"Received chat request: {prompt}") + + action = prompt.get("action", Config.DEFAULT_ACTION) + logger.debug( + f"Extracted prompt content: {prompt['content']}, action: {action}" + ) + + if action == "generate": + logger.info(f"Generating tweet for prompt: {prompt['content']}") + tweet = self.generate_tweet(prompt["content"]) + logger.info(f"Generated tweet: {tweet}") + return {"role": "assistant", "content": tweet} + 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}" + ) + if isinstance(result, dict) and "error" in result: return result, status_code - else: - logger.error(f"Invalid action received: {action}") - return {"error": Config.ERROR_INVALID_ACTION}, 400 + return { + "role": "assistant", + "content": f"Tweet posted successfully: {result.get('tweet', '')}", + }, status_code else: - logger.error("Missing 'prompt' in chat request data") - return {"error": Config.ERROR_MISSING_PARAMETERS}, 400 + logger.error(f"Invalid action received: {action}") + return {"role": "assistant", "content": Config.ERROR_INVALID_ACTION} + except Exception as e: logger.exception(f"Unexpected error in chat method: {str(e)}") - return {"Error": str(e)}, 500 + return {"role": "assistant", "content": f"Error: {str(e)}"} diff --git a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/config.py b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py similarity index 76% rename from submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/config.py rename to submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py index b7380b7..3b8e37b 100644 --- a/submodules/moragents_dockers/agents/src/tweet_sizzler_agent/src/config.py +++ b/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/config.py @@ -21,9 +21,10 @@ class Config: "attention-grabbing tweets based on the user's prompt. It is CRUCIAL that you " "keep the tweets strictly under 280 characters - this is a hard limit. Make the " "tweets as engaging as possible while adhering to this character limit. Do not " - "surround your tweet with quotes. Do not preface it with any text like 'here is " - "your tweet'. Simply generate and output the tweet, ensuring it is less than " - "280 characters long." + "surround your tweet with quotes or any other formatting. Do not preface it with " + "any text like 'here is your tweet'. Simply generate and output the tweet, ensuring " + "it is less than 280 characters long. Use newlines sparingly. Do not surrounded with " + "quotes or braces. Do not use any other formatting." ) DEFAULT_ACTION = "generate" diff --git a/submodules/moragents_dockers/agents/src/app.py b/submodules/moragents_dockers/agents/src/app.py index dd84abe..2abaef4 100644 --- a/submodules/moragents_dockers/agents/src/app.py +++ b/submodules/moragents_dockers/agents/src/app.py @@ -1,21 +1,21 @@ import os import logging import time -from functools import wraps -from config import Config -from llama_cpp import Llama -from flask_cors import CORS -from flask import Flask, request, jsonify -from langchain_community.llms import Ollama -from delegator import Delegator -from llama_cpp.llama_tokenizer import LlamaHFTokenizer +import uvicorn + +from fastapi import FastAPI, HTTPException, Request, UploadFile, File +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +from langchain_ollama import ChatOllama from langchain_community.embeddings import OllamaEmbeddings +from src.config import Config +from src.delegator import Delegator +from src.stores import agent_manager, chat_manager +from src.models.messages import ChatRequest + # Constants -INITIAL_MESSAGE = { - "role": "assistant", - "content": "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.", -} UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") # Configure logging @@ -27,202 +27,156 @@ ) logger = logging.getLogger(__name__) +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) -def load_llm(): - logger.info("Loading LLM model") - try: - llm = Llama( - model_path=Config.MODEL_PATH, - chat_format="functionary-v2", - tokenizer=LlamaHFTokenizer.from_pretrained( - "meetkai/functionary-small-v2.4-GGUF" - ), - n_gpu_layers=-1, # Use all available GPU layers - n_batch=1024, # Increase batch size for faster processing - n_ctx=1024, # Increase context size for better performance - verbose=False, # Disable verbose output for speed - use_mlock=True, # Lock memory to prevent swapping - use_mmap=True, # Use memory mapping for faster loading - n_threads=16, # Increase number of threads for more parallel processing - ) - logger.info("LLM model loaded successfully") - return llm - except Exception as e: - logger.error(f"Error loading LLM model: {str(e)}") - raise - - -app = Flask(__name__) -CORS(app) - -upload_state = False os.makedirs(UPLOAD_FOLDER, exist_ok=True) -app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER -app.config["MAX_CONTENT_LENGTH"] = Config.MAX_UPLOAD_LENGTH - -try: - llm = load_llm() -except TimeoutError: - logger.error("LLM loading timed out") - llm = None -except Exception as e: - logger.error(f"Failed to load LLM: {str(e)}") - llm = None -llm_ollama = Ollama(model="llama3.1", base_url=Config.OLLAMA_URL) -embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url=Config.OLLAMA_URL) +llm = ChatOllama( + model=Config.OLLAMA_MODEL, + base_url=Config.OLLAMA_URL, +) +embeddings = OllamaEmbeddings( + model=Config.OLLAMA_EMBEDDING_MODEL, base_url=Config.OLLAMA_URL +) -delegator = Delegator(Config.DELEGATOR_CONFIG, llm, llm_ollama, embeddings, app) -messages = [INITIAL_MESSAGE] -next_turn_agent = None +delegator = Delegator(Config.DELEGATOR_CONFIG, llm, embeddings) -@app.route("/", methods=["POST"]) -def chat(): - global next_turn_agent, messages - data = request.get_json() - logger.info(f"Received chat request: {data}") +@app.post("/chat") +async def chat(chat_request: ChatRequest): + prompt = chat_request.prompt.dict() + chat_manager.add_message(prompt) try: - current_agent = None - if "prompt" in data: - prompt = data["prompt"] - messages.append(prompt) + active_agent = agent_manager.get_active_agent() - if not next_turn_agent: - logger.info("No next turn agent, getting delegator response") + if not active_agent: + logger.info("No active agent, getting delegator response") start_time = time.time() - result = delegator.get_delegator_response(prompt, upload_state) + result = delegator.get_delegator_response( + prompt["content"], chat_manager.get_uploaded_file_status() + ) end_time = time.time() + logger.info(f"Delegator response time: {end_time - start_time:.2f} seconds") logger.info(f"Delegator response: {result}") - if "next" not in result: - logger.error(f"Missing 'next' key in delegator response: {result}") - raise ValueError("Invalid delegator response: missing 'next' key") + if "agent" not in result: + logger.error(f"Missing 'agent' key in delegator response: {result}") + raise ValueError("Invalid delegator response: missing 'agent' key") - next_agent = result["next"] - current_agent, response_swap = delegator.delegate_chat(next_agent, request) - else: - logger.info(f"Delegating chat to next turn agent: {next_turn_agent}") - current_agent, response_swap = delegator.delegate_chat( - next_turn_agent, request - ) + active_agent = result["agent"] - # Handle both dictionary and tuple returns from delegate_chat - response, status_code = ( - response_swap if isinstance(response_swap, tuple) else (response_swap, 200) - ) + logger.info(f"Delegating chat to active agent: {active_agent}") + current_agent, response = delegator.delegate_chat(active_agent, chat_request) - # If response_swap is an error, reset next_turn_agent - next_turn_agent = ( - response_swap.get("next_turn_agent") - if isinstance(response_swap, dict) - else None - ) + if isinstance(response, tuple) and len(response) == 2: + error_message, status_code = response + logger.error(f"Error from agent: {error_message}") + raise HTTPException(status_code=status_code, detail=error_message) if isinstance(response, dict) and "role" in response and "content" in response: - response_with_agent = response.copy() - response_with_agent["agentName"] = current_agent or "Unknown" - - messages.append(response_with_agent) + chat_manager.add_response(response, current_agent or "Unknown") - logger.info("Sending response: %s", response_with_agent) - return jsonify(response_with_agent), status_code + logger.info(f"Sending response: {response}") + return response else: logger.error(f"Invalid response format: {response}") - return jsonify({"error": "Invalid response format"}), 500 + raise HTTPException(status_code=500, detail="Invalid response format") except TimeoutError: logger.error("Chat request timed out") - return jsonify({"error": "Request timed out"}), 504 + raise HTTPException(status_code=504, detail="Request timed out") except ValueError as ve: logger.error(f"Input formatting error: {str(ve)}") - return jsonify({"error": str(ve)}), 400 + raise HTTPException(status_code=400, detail=str(ve)) except Exception as e: logger.error(f"Error in chat route: {str(e)}", exc_info=True) - return jsonify({"error": str(e)}), 500 + raise HTTPException(status_code=500, detail=str(e)) -@app.route("/tx_status", methods=["POST"]) -def swap_agent_tx_status(): +@app.post("/tx_status") +async def swap_agent_tx_status(request: Request): logger.info("Received tx_status request") - response = delegator.delegate_route("crypto swap agent", request, "tx_status") - messages.append(response) - return jsonify(response) + response = await delegator.delegate_route("crypto swap agent", request, "tx_status") + chat_manager.add_message(response) + return response -@app.route("/messages", methods=["GET"]) -def get_messages(): +@app.get("/messages") +async def get_messages(): logger.info("Received get_messages request") - return jsonify({"messages": messages}) + return {"messages": chat_manager.get_messages()} -@app.route("/clear_messages", methods=["GET"]) -def clear_messages(): - global messages +@app.get("/clear_messages") +async def clear_messages(): logger.info("Clearing message history") - messages = [INITIAL_MESSAGE] - return jsonify({"response": "successfully cleared message history"}) + chat_manager.clear_messages() + return {"response": "successfully cleared message history"} -@app.route("/allowance", methods=["POST"]) -def swap_agent_allowance(): +@app.post("/allowance") +async def swap_agent_allowance(request: Request): logger.info("Received allowance request") return delegator.delegate_route("crypto swap agent", request, "get_allowance") -@app.route("/approve", methods=["POST"]) -def swap_agent_approve(): +@app.post("/approve") +async def swap_agent_approve(request: Request): logger.info("Received approve request") return delegator.delegate_route("crypto swap agent", request, "approve") -@app.route("/swap", methods=["POST"]) -def swap_agent_swap(): +@app.post("/swap") +async def swap_agent_swap(request: Request): logger.info("Received swap request") return delegator.delegate_route("crypto swap agent", request, "swap") -@app.route("/upload", methods=["POST"]) -def rag_agent_upload(): - global messages, upload_state +@app.post("/upload") +async def rag_agent_upload(file: UploadFile = File(...)): logger.info("Received upload request") - response = delegator.delegate_route( - "general purpose and context-based rag agent", request, "upload_file" + response = await delegator.delegate_route( + "general purpose and context-based rag agent", {"file": file}, "upload_file" ) - messages.append(response) - upload_state = True - return jsonify(response) + chat_manager.add_message(response) + return response -@app.route("/regenerate_tweet", methods=["POST"]) -def regenerate_tweet(): +@app.post("/regenerate_tweet") +async def regenerate_tweet(): logger.info("Received generate tweet request") return delegator.delegate_route("tweet sizzler agent", None, "generate_tweet") -@app.route("/post_tweet", methods=["POST"]) -def post_tweet(): +@app.post("/post_tweet") +async def post_tweet(request: Request): logger.info("Received x post request") - return delegator.delegate_route("tweet sizzler agent", request, "post_tweet") + return await delegator.delegate_route("tweet sizzler agent", request, "post_tweet") -# TODO: Persist the X API key in the database (once we set this up) -@app.route("/set_x_api_key", methods=["POST"]) -def set_x_api_key(): +@app.post("/set_x_api_key") +async def set_x_api_key(request: Request): logger.info("Received set X API key request") - return 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.route("/claim", methods=["POST"]) -def claim_agent_claim(): +@app.post("/claim") +async def claim_agent_claim(request: Request): logger.info("Received claim request") return delegator.delegate_route("claim agent", request, "claim") -@app.route("/claim_status", methods=["POST"]) -def update_claim_status(): - return claim_agent.claim_status(request) if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000, debug=True) + uvicorn.run(app, host="0.0.0.0", port=5000, reload=True) diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/agent.py b/submodules/moragents_dockers/agents/src/claim_agent/src/agent.py deleted file mode 100644 index d753f52..0000000 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/agent.py +++ /dev/null @@ -1,126 +0,0 @@ -import json -from claim_agent.src import tools -from claim_agent.src.config import Config - -class ClaimAgent: - def __init__(self, agent_info, llm, llm_ollama, embeddings, flask_app): - self.agent_info = agent_info - self.llm = llm - self.tools_provided = tools.get_tools() - self.conversation_state = {} - - def get_response(self, message, wallet_address): - if wallet_address not in self.conversation_state: - self.conversation_state[wallet_address] = {"state": "initial"} - - state = self.conversation_state[wallet_address]["state"] - - if state == "initial": - rewards = { - 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} - - 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" - 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", self.agent_info["name"] - else: - return f"No rewards found for your wallet address {wallet_address} in either pool. Claim cannot be processed.", "assistant", None - - elif state == "awaiting_confirmation": - user_input = message[-1]['content'].lower() - if any(word in user_input for word in ['yes', 'proceed', 'confirm', 'claim']): - return self.prepare_transactions(wallet_address) - else: - return "Please confirm if you want to proceed with the claim by saying 'yes', 'proceed', 'confirm', or 'claim'.", "assistant", self.agent_info["name"] - - return "I'm sorry, I didn't understand that. Can you please rephrase your request?", "assistant", self.agent_info["name"] - - def prepare_transactions(self, wallet_address): - available_rewards = self.conversation_state[wallet_address]["available_rewards"] - receiver_address = self.conversation_state[wallet_address]["receiver_address"] - transactions = [] - - for pool_id in available_rewards.keys(): - try: - tx_data = tools.prepare_claim_transaction(pool_id, receiver_address) - transactions.append({"pool": pool_id, "transaction": tx_data}) - except Exception as e: - return f"Error preparing transaction for pool {pool_id}: {str(e)}", "assistant", None - - self.conversation_state[wallet_address]["transactions"] = transactions - - # Return a structured response - return { - "role": "claim", - "content": { - "transactions": transactions, - "claim_tx_cb": "/claim" - } - }, "claim", None - - def chat(self, request): - try: - data = request.get_json() - 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) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} - else: - return {"error": "Missing required parameters"}, 400 - except Exception as e: - return {"Error": str(e)}, 500 - - - def claim(self, request): - try: - data = request.get_json() - wallet_address = data['wallet_address'] - transactions = self.conversation_state[wallet_address]["transactions"] - return jsonify({"transactions": transactions}) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - def claim_status(self, request): - try: - data = request.get_json() - wallet_address = data.get('wallet_address') - transaction_hash = data.get('transaction_hash') - status = data.get('status') - - if not all([wallet_address, transaction_hash, status]): - return jsonify({"error": "Missing required parameters"}), 400 - - # Generate and return the status message - response = self.get_status(status, transaction_hash, "claim") - return jsonify(response), 200 - except Exception as e: - return jsonify({"error": str(e)}), 500 - - - def get_status(self, flag, tx_hash, tx_type): - response = '' - - if flag == "cancelled": - response = f"The claim transaction has been cancelled." - elif flag == "success": - response = f"The claim transaction was successful." - 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." - - if tx_hash: - response = response + f" The transaction hash is {tx_hash}. " \ - f"Here's the link to the Etherscan transaction: " \ - f"https://etherscan.io/tx/{tx_hash}" - - if flag != "initiated": - response = response + " Is there anything else I can help you with?" - - return {"role": "assistant", "content": response} \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/claim_agent/src/config.py b/submodules/moragents_dockers/agents/src/claim_agent/src/config.py deleted file mode 100644 index c7bbddc..0000000 --- a/submodules/moragents_dockers/agents/src/claim_agent/src/config.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - - WEB3RPCURL = { - "1": "https://eth.llamarpc.com/" - } - MINT_FEE = 0.001 # in ETH - - DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" - DISTRIBUTION_ABI = [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver_", - "type": "address" - } - ], - "name": "claim", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user_", - "type": "address" - } - ], - "name": "getCurrentUserReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index f52f542..b4809b7 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -8,55 +8,68 @@ class Config: # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/" + MODEL_REVISION - DOWNLOAD_DIR = "model" + OLLAMA_MODEL = "llama3.2:3b" + OLLAMA_EMBEDDING_MODEL = "nomic-embed-text" OLLAMA_URL = "http://host.docker.internal:11434" + MAX_UPLOAD_LENGTH = 16 * 1024 * 1024 DELEGATOR_CONFIG = { "agents": [ { - "path": "rag_agent.src.agent", + "path": "src.agents.rag.agent", "class": "RagAgent", - "description": "Handles general queries, information retrieval, and context-based questions. Use for any query that doesn't explicitly match other agents' specialties.", + "description": "Must be used anytime an upload or uploaded document is referred to. Handles general queries, information retrieval, and context-based questions. Use for any query that doesn't explicitly match other agents' specialties.", "name": "general purpose and context-based rag agent", "upload_required": True, }, { - "path": "data_agent.src.agent", - "class": "DataAgent", + "path": "src.agents.crypto_data.agent", + "class": "CryptoDataAgent", "description": "Crypto-specific. Provides real-time cryptocurrency data such as price, market cap, and fully diluted valuation (FDV).", "name": "crypto data agent", "upload_required": False, }, { - "path": "swap_agent.src.agent", - "class": "SwapAgent", + "path": "src.agents.token_swap.agent", + "class": "TokenSwapAgent", "description": "Handles cryptocurrency swapping operations. Use when the query explicitly mentions swapping, exchanging, or converting one cryptocurrency to another.", - "name": "crypto swap agent", + "name": "token swap agent", "upload_required": False, }, { - "path": "tweet_sizzler_agent.src.agent", + "path": "src.agents.tweet_sizzler.agent", "class": "TweetSizzlerAgent", "description": "Generates and posts engaging tweets. Use when the query explicitly mentions Twitter, tweeting, or X platform.", "name": "tweet sizzler agent", "upload_required": False, }, { - "path": "claim_agent.src.agent", - "class": "ClaimAgent", + "path": "src.agents.mor_claims.agent", + "class": "MorClaimsAgent", "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", - "name": "claim agent", + "name": "mor claims agent", "upload_required": False, }, { - "path": "reward_agent.src.agent", - "class": "RewardAgent", + "path": "src.agents.mor_rewards.agent", + "class": "MorRewardsAgent", "description": "Provides information about user's accrued MOR rewards or tokens. Use when the query is about checking or querying reward balances.", - "name": "reward agent", + "name": "mor rewards agent", "upload_required": False, }, + # { + # "path": "src.agents.realtime_search.agent", + # "class": "RealtimeSearchAgent", + # "description": "Only use this agent for real-time data. This agent is not for general purpose queries. Use when the query is about searching the web for real-time information.", + # "name": "realtime search agent", + # "upload_required": False, + # }, + # { + # "path": "src.agents.news_agent.agent", + # "class": "NewsAgent", + # "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/data_agent/src/agent.py b/submodules/moragents_dockers/agents/src/data_agent/src/agent.py deleted file mode 100644 index 1f5dd54..0000000 --- a/submodules/moragents_dockers/agents/src/data_agent/src/agent.py +++ /dev/null @@ -1,79 +0,0 @@ -import json -from data_agent.src import tools -import logging - -logger = logging.getLogger(__name__) - - -class DataAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app - self.config = config - self.tools_provided = tools.get_tools() - - def get_response(self, message): - messages = [ - { - "role": "system", - "content": ( - "Don't make assumptions about the value of the arguments for the function " - "they should always be supplied by the user and do not alter the value of the arguments. " - "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " - "request is ambiguous." - ), - } - ] - messages.extend(message) - logger.info("Sending request to LLM with %d messages", len(messages)) - result = self.llm.create_chat_completion( - messages=messages, tools=self.tools_provided, tool_choice="auto" - ) - - logger.info("Received response from LLM: %s", result) - - if "tool_calls" in result["choices"][0]["message"].keys(): - func = result["choices"][0]["message"]["tool_calls"][0]["function"] - logger.info("LLM suggested using tool: %s", func["name"]) - args = json.loads(func["arguments"]) - if func["name"] == "get_price": - return tools.get_coin_price_tool(args["coin_name"]), "assistant" - elif func["name"] == "get_floor_price": - return tools.get_nft_floor_price_tool(args["nft_name"]), "assistant" - elif func["name"] == "get_fdv": - return ( - tools.get_fully_diluted_valuation_tool(args["coin_name"]), - "assistant", - ) - elif func["name"] == "get_tvl": - return ( - tools.get_protocol_total_value_locked_tool(args["protocol_name"]), - "assistant", - ) - elif func["name"] == "get_market_cap": - return tools.get_coin_market_cap_tool(args["coin_name"]), "assistant" - else: - logger.info("LLM provided a direct response without using tools") - return result["choices"][0]["message"]["content"], "assistant" - - def generate_response(self, prompt): - response, role = self.get_response([prompt]) - return response, role - - def chat(self, request): - try: - data = request.get_json() - if "prompt" in data: - prompt = data["prompt"] - logger.info( - "Received chat request with prompt: %s", - prompt[:50] + "..." if len(prompt) > 50 else prompt, - ) - response, role = self.generate_response(prompt) - return {"role": role, "content": response} - else: - logger.warning("Received chat request without 'prompt' in data") - return {"error": "Missing required parameters"}, 400 - except Exception as e: - logger.error("Error in chat method: %s", str(e), exc_info=True) - return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py b/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py deleted file mode 100644 index 0c916ec..0000000 --- a/submodules/moragents_dockers/agents/src/data_agent/src/data_agent_config.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" - DEFILLAMA_BASE_URL = "https://api.llama.fi" - 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." - 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." - API_ERROR_MESSAGE = "I can't seem to access the API at the moment." - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/delegator.py b/submodules/moragents_dockers/agents/src/delegator.py index bc4d605..a0f9b5e 100644 --- a/submodules/moragents_dockers/agents/src/delegator.py +++ b/submodules/moragents_dockers/agents/src/delegator.py @@ -2,6 +2,8 @@ import logging import json +from langchain.schema import SystemMessage, HumanMessage, AIMessage + logger = logging.getLogger(__name__) # Configurable default agent @@ -9,12 +11,10 @@ class Delegator: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app - self.llm_ollama = llm_ollama - self.embeddings = embeddings + def __init__(self, config, llm, embeddings): self.config = config + self.llm = llm # This is now a ChatOllama instance + self.embeddings = embeddings self.agents = self.load_agents(config) logger.info("Delegator initialized with %d agents", len(self.agents)) @@ -27,9 +27,7 @@ def load_agents(self, config): agent_instance = agent_class( agent_info, self.llm, - self.llm_ollama, self.embeddings, - self.flask_app, ) agents[agent_info["name"]] = agent_instance logger.info("Loaded agent: %s", agent_info["name"]) @@ -43,78 +41,57 @@ def get_delegator_response(self, prompt, upload_state): for agent_info in self.config["agents"] if not (agent_info["upload_required"] and not upload_state) ] + logger.info(f"Available agents: {available_agents}") + agent_descriptions = "\n".join( f"- {agent_info['name']}: {agent_info['description']}" for agent_info in self.config["agents"] if agent_info["name"] in available_agents ) - prompt_text = ( - "### Instruction: Your name is Morpheus. " + system_prompt = ( + "Your name is Morpheus. " "Your primary function is to select the correct agent based on the user's input. " - "You MUST use the 'route' function to select an agent. " - "Available agents and their descriptions:\n" - f"{agent_descriptions}\n" + "You MUST use the 'select_agent' function to select an agent. " + f"Available agents and their descriptions: {agent_descriptions}\n" "Analyze the user's input and select the most appropriate agent. " - "Do not respond with any text other than calling the 'route' function. " - "###" ) tools = [ { - "type": "function", - "function": { - "name": "route", - "description": "Choose which agent to run next", - "parameters": { - "type": "object", - "properties": { - "next": { - "type": "string", - "enum": available_agents, - "description": "The name of the next agent to run", - } + "name": "select_agent", + "description": "Choose which agent should be used to respond to the user query", + "parameters": { + "type": "object", + "properties": { + "agent": { + "type": "string", + "enum": available_agents, + "description": "The name of the agent to be used to respond to the user query", }, - "required": ["next"], }, + "required": ["agent"], }, } ] - message_list = [ - {"role": "system", "content": prompt_text}, - prompt, - { - "role": "system", - "content": "Remember, you must use the 'route' function to select an agent.", - }, + agent_selection_llm = self.llm.bind_tools(tools) + + messages = [ + SystemMessage(content=system_prompt), + HumanMessage(content=prompt), ] - logger.info("Sending prompt to LLM: %s", prompt) - result = self.llm.create_chat_completion( - messages=message_list, - tools=tools, - tool_choice="auto", - temperature=0.3, - ) - logger.info("Received response from LLM: %s", result) + result = agent_selection_llm.invoke(messages) + tool_calls = result.tool_calls + if not tool_calls: + raise ValueError("No agent was selected by the model.") - response = result["choices"][0]["message"] + selected_agent = tool_calls[0] + logger.info(f"Selected agent: {selected_agent}") + selected_agent_name = selected_agent.get("args").get("agent") - if response.get("tool_calls"): - try: - function_args = json.loads( - response["tool_calls"][0]["function"]["arguments"] - ) - return {"next": function_args["next"]} - except (json.JSONDecodeError, KeyError) as e: - logger.error(f"Error parsing function call: {e}") - return {"next": DEFAULT_AGENT} - else: - logger.warning( - "No tool calls in LLM response, defaulting to general purpose agent" - ) - return {"next": DEFAULT_AGENT} + return {"agent": selected_agent_name} def delegate_chat(self, agent_name, request): logger.info(f"Attempting to delegate chat to agent: {agent_name}") diff --git a/submodules/moragents_dockers/agents/src/models/__init__.py b/submodules/moragents_dockers/agents/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/src/models/messages.py b/submodules/moragents_dockers/agents/src/models/messages.py new file mode 100644 index 0000000..e17d693 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/models/messages.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel + + +class ChatMessage(BaseModel): + role: str + content: str + + +class ChatRequest(BaseModel): + prompt: ChatMessage + chain_id: str + wallet_address: str diff --git a/submodules/moragents_dockers/agents/src/rag_agent/src/agent.py b/submodules/moragents_dockers/agents/src/rag_agent/src/agent.py deleted file mode 100644 index 0880cf0..0000000 --- a/submodules/moragents_dockers/agents/src/rag_agent/src/agent.py +++ /dev/null @@ -1,90 +0,0 @@ -import os -import logging -from langchain_community.document_loaders import PyMuPDFLoader -from langchain_community.vectorstores import FAISS -from langchain_core.prompts import ChatPromptTemplate -from langchain_text_splitters import RecursiveCharacterTextSplitter -from langchain.chains.combine_documents import create_stuff_documents_chain -from langchain.chains import create_retrieval_chain -from werkzeug.utils import secure_filename - - -logging.basicConfig(level=logging.DEBUG) - - -class RagAgent: - def __init__(self, config, llm, llm_ollama, embeddings,flask_app): - self.llm = llm_ollama - self.flask_app = flask_app - self.embedding=embeddings - self.config = config - self.agent = None - self.messages = [{'role': "assistant", "content": "Please upload a file to begin"}] - self.upload_state = False - self.prompt = ChatPromptTemplate.from_template( - """ - Answer the following question only based on the given context - - - {context} - - - Question: {input} - """ - ) - self.UPLOAD_FOLDER = flask_app.config['UPLOAD_FOLDER'] - self.max_size = 5 * 1024 * 1024 - self.retriever = None - - - def handle_file_upload(self,file): - if not os.path.exists(self.UPLOAD_FOLDER): - os.makedirs(self.UPLOAD_FOLDER, exist_ok=True) - filename = secure_filename(file.filename) - file.save(os.path.join(self.UPLOAD_FOLDER, filename)) - # DocumentToolsGenerator class instantiation - loader = PyMuPDFLoader(os.path.join(self.UPLOAD_FOLDER,filename)) - docs = loader.load() - text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024,chunk_overlap=20,length_function=len,is_separator_regex=False) - split_documents = text_splitter.split_documents(docs) - vector_store = FAISS.from_documents(split_documents, self.embedding) - self.retriever = vector_store.as_retriever(search_kwargs={"k": 7}) - - - def upload_file(self,request): - if 'file' not in request.files: - return {'error': 'No file part'}, 400 - file = request.files['file'] - if file.filename == '': - return {'error': 'No selected file'}, 400 - # Check file size - file.seek(0, os.SEEK_END) - file_length = file.tell() - file.seek(0, 0) # Reset the file pointer to the beginning - if file_length > self.max_size: - return {"role": "assistant", "content": 'please use a file less than 5 MB'} - try: - self.handle_file_upload(file) - self.upload_state = True - return {"role": "assistant", "content": 'You have successfully uploaded the text'} - except Exception as e: - logging.error(f'Error during file upload: {str(e)}') - return {'error': str(e)}, 500 - - def chat(self,request): - try: - data = request.get_json() - if 'prompt' in data: - prompt = data['prompt']['content'] - role = "assistant" - retrieved_docs = self.retriever.invoke(prompt) - formatted_context = "\n\n".join(doc.page_content for doc in retrieved_docs) - formatted_prompt = f"Question: {prompt}\n\nContext: {formatted_context}" - answer=self.llm(formatted_prompt) - response = answer if self.upload_state else "please upload a file first" - return {"role": role, "content": response} - else: - return {"error": "Missing required parameters"}, 400 - except Exception as e: - logging.error(f'Error in chat endpoint: {str(e)}') - return {"Error": str(e)}, 500 diff --git a/submodules/moragents_dockers/agents/src/reward_agent/src/config.py b/submodules/moragents_dockers/agents/src/reward_agent/src/config.py deleted file mode 100644 index ff7c6d6..0000000 --- a/submodules/moragents_dockers/agents/src/reward_agent/src/config.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - - WEB3RPCURL = { - "1": "https://eth.llamarpc.com/", - } - - DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" - DISTRIBUTION_ABI = [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user_", - "type": "address" - } - ], - "name": "getCurrentUserReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] diff --git a/submodules/moragents_dockers/agents/src/stores/__init__.py b/submodules/moragents_dockers/agents/src/stores/__init__.py new file mode 100644 index 0000000..18cc281 --- /dev/null +++ b/submodules/moragents_dockers/agents/src/stores/__init__.py @@ -0,0 +1,5 @@ +from src.stores.agent_manager import AgentManager +from src.stores.chat_manager import ChatManager + +agent_manager = AgentManager() +chat_manager = ChatManager() diff --git a/submodules/moragents_dockers/agents/src/stores/agent_manager.py b/submodules/moragents_dockers/agents/src/stores/agent_manager.py new file mode 100644 index 0000000..82508df --- /dev/null +++ b/submodules/moragents_dockers/agents/src/stores/agent_manager.py @@ -0,0 +1,12 @@ +class AgentManager: + def __init__(self): + self.active_agent = None + + def get_active_agent(self): + return self.active_agent + + def set_active_agent(self, agent_name): + self.active_agent = agent_name + + def clear_active_agent(self): + self.active_agent = None diff --git a/submodules/moragents_dockers/agents/src/stores/chat_manager.py b/submodules/moragents_dockers/agents/src/stores/chat_manager.py new file mode 100644 index 0000000..5bd79eb --- /dev/null +++ b/submodules/moragents_dockers/agents/src/stores/chat_manager.py @@ -0,0 +1,48 @@ +import logging +from typing import List, Dict + +logger = logging.getLogger(__name__) + + +class ChatManager: + def __init__(self): + self.has_uploaded_file = False + self.messages: List[Dict[str, str]] = [ + { + "role": "assistant", + "content": """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.""", + } + ] + + def add_message(self, message: Dict[str, str]): + self.messages.append(message) + logger.info(f"Added message: {message}") + + def get_messages(self) -> List[Dict[str, str]]: + return self.messages + + def set_uploaded_file(self, has_file: bool): + self.has_uploaded_file = has_file + logger.info(f"Set uploaded file status to: {has_file}") + + def get_uploaded_file_status(self) -> bool: + return self.has_uploaded_file + + def clear_messages(self): + self.messages = [self.messages[0]] # Keep the initial message + logger.info("Cleared message history") + + def get_last_message(self) -> Dict[str, str]: + return self.messages[-1] if self.messages else {} + + def add_response(self, response: Dict[str, str], agent_name: str): + response_with_agent = response.copy() + response_with_agent["agentName"] = agent_name + self.add_message(response_with_agent) + logger.info(f"Added response from agent {agent_name}: {response_with_agent}") + + def get_chat_history(self) -> str: + return "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.messages]) diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/agent.py b/submodules/moragents_dockers/agents/src/swap_agent/src/agent.py deleted file mode 100644 index 73f2694..0000000 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/agent.py +++ /dev/null @@ -1,190 +0,0 @@ -import json -import requests -from flask import jsonify -from swap_agent.src import tools -from swap_agent.src.config import Config - - -class SwapAgent: - def __init__(self, config, llm, llm_ollama, embeddings, flask_app): - self.llm = llm - self.flask_app = flask_app - self.config = config - self.tools_provided = tools.get_tools() - self.context = [] - - def api_request_url(self, method_name, query_params, chain_id): - base_url = Config.APIBASEURL + str(chain_id) - return f"{base_url}{method_name}?{'&'.join([f'{key}={value}' for key, value in query_params.items()])}" - - def check_allowance(self, token_address, wallet_address, chain_id): - url = self.api_request_url( - "/approve/allowance", - {"tokenAddress": token_address, "walletAddress": wallet_address}, - chain_id - ) - response = requests.get(url, headers=Config.HEADERS) - data = response.json() - return data - - def approve_transaction(self, token_address, chain_id, amount=None): - query_params = {"tokenAddress": token_address, "amount": amount} if amount else {"tokenAddress": token_address} - url = self.api_request_url("/approve/transaction", query_params, chain_id) - response = requests.get(url, headers=Config.HEADERS) - transaction = response.json() - return transaction - - def build_tx_for_swap(self, swap_params, chain_id): - url = self.api_request_url("/swap", swap_params, chain_id) - swap_transaction = requests.get(url, headers=Config.HEADERS).json() - return swap_transaction - - def get_response(self, message, chain_id, wallet_address): - prompt = [ - { - "role": "system", - "content": ( - "Don't make assumptions about the value of the arguments for the function " - "they should always be supplied by the user and do not alter the value of the arguments. " - "Don't make assumptions about what values to plug into functions. Ask for clarification if a user " - "request is ambiguous. you only need the value of token1 we dont need the value of token2. After " - "starting from scratch do not assume the name of token1 or token2" - ) - } - ] - prompt.extend(message) - result = self.llm.create_chat_completion( - messages=prompt, - tools=self.tools_provided, - tool_choice="auto", - temperature=0.01 - ) - if "tool_calls" in result["choices"][0]["message"].keys(): - func = result["choices"][0]["message"]["tool_calls"][0]['function'] - if func["name"] == "swap_agent": - args = json.loads(func["arguments"]) - tok1 = args["token1"] - tok2 = args["token2"] - value = args["value"] - try: - res, role = tools.swap_coins(tok1, tok2, float(value), chain_id, wallet_address) - except (tools.InsufficientFundsError, tools.TokenNotFoundError, tools.SwapNotPossibleError) as e: - self.context = [] - return str(e), "assistant", None - return res, role, None - self.context.append({"role": "assistant", "content": result["choices"][0]["message"]['content']}) - return result["choices"][0]["message"]['content'], "assistant", "crypto swap agent" - - def get_status(self, flag, tx_hash, tx_type): - response = '' - - if flag == "cancelled": - response = f"The {tx_type} transaction has been cancelled." - elif flag == "success": - response = f"The {tx_type} transaction was successful." - elif flag == "failed": - response = f"The {tx_type} transaction has failed." - elif flag == "initiated": - response = f"Transaction has been sent, please wait for it to be confirmed." - - if tx_hash: - response = response + f" The transaction hash is {tx_hash}." - - if flag == "success" and tx_type == "approve": - response = response + " Please proceed with the swap transaction." - elif flag != "initiated": - response = response + " Is there anything else I can help you with?" - - if flag != "initiated": - self.context = [] - self.context.append({"role": "assistant", "content": response}) - 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) - return response, role, next_turn_agent - - def chat(self, request): - try: - data = request.get_json() - if 'prompt' in data: - prompt = data['prompt'] - wallet_address = data['wallet_address'] - chain_id = data['chain_id'] - response, role, next_turn_agent = self.generate_response(prompt, chain_id, wallet_address) - return {"role": role, "content": response, "next_turn_agent": next_turn_agent} - else: - return {"error": "Missing required parameters"}, 400 - except Exception as e: - return {"Error": str(e)}, 500 - - def tx_status(self, request): - try: - data = request.get_json() - if 'status' in data: - prompt = data['status'] - tx_hash = data.get('tx_hash', '') - tx_type = data.get('tx_type', '') - response = self.get_status(prompt, tx_hash, tx_type) - return response - else: - return {"error": "Missing required parameters"}, 400 - except Exception as e: - return {"Error": str(e)}, 500 - - def get_allowance(self, request): - try: - data = request.get_json() - if 'tokenAddress' in data: - token = data['tokenAddress'] - wallet_address = data['walletAddress'] - chain_id = data["chain_id"] - res = self.check_allowance(token, wallet_address, chain_id) - return jsonify({"response": res}) - else: - return jsonify({"error": "Missing required parameters"}), 400 - except Exception as e: - return jsonify({"Error": str(e)}), 500 - - def approve(self, request): - try: - data = request.get_json() - if 'tokenAddress' in data: - token = data['tokenAddress'] - chain_id = data['chain_id'] - amount = data['amount'] - res = self.approve_transaction(token, chain_id, amount) - return jsonify({"response": res}) - else: - return jsonify({"error": "Missing required parameters"}), 400 - except Exception as e: - return jsonify({"Error": str(e)}), 500 - - def swap(self, request): - try: - data = request.get_json() - if 'src' in data: - token1 = data['src'] - token2 = data['dst'] - wallet_address = data['walletAddress'] - amount = data['amount'] - slippage = data['slippage'] - chain_id = data['chain_id'] - swap_params = { - "src": token1, - "dst": token2, - "amount": amount, - "from": wallet_address, - "slippage": slippage, - "disableEstimate": False, - "allowPartialFill": False, - } - swap_transaction = self.build_tx_for_swap(swap_params, chain_id) - return swap_transaction - else: - return jsonify({"error": "Missing required parameters"}), 400 - except Exception as e: - return jsonify({"Error": str(e)}), 500 diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/config.py b/submodules/moragents_dockers/agents/src/swap_agent/src/config.py deleted file mode 100644 index 2e57c81..0000000 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/config.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - INCH_URL = "https://api.1inch.dev/token" - QUOTE_URL = "https://api.1inch.dev/swap" - APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" - HEADERS = { "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", "accept": "application/json" } - WEB3RPCURL = {"56":"https://bsc-dataseed.binance.org","42161":"https://arb1.arbitrum.io/rpc","137":"https://polygon-rpc.com","1":"https://eth.llamarpc.com/","10":"https://mainnet.optimism.io","8453":"https://mainnet.base.org"} - NATIVE_TOKENS={"137":"MATIC","56":"BNB","1":"ETH","42161":"ETH","10":"ETH","8453":"ETH"} - ERC20_ABI = [ - {"constant": True, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", "type": "function"}, - {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"} - ] - INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py b/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py deleted file mode 100644 index b91a88f..0000000 --- a/submodules/moragents_dockers/agents/src/swap_agent/src/swap_agent_config.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging - -# Logging configuration -logging.basicConfig(level=logging.INFO) - -# Configuration object -class Config: - # Model configuration - MODEL_NAME = "meetkai/functionary-small-v2.4-GGUF" - MODEL_REVISION = "functionary-small-v2.4.Q4_0.gguf" - MODEL_PATH = "model/"+MODEL_REVISION - DOWNLOAD_DIR = "model" - # API endpoints - INCH_URL = "https://api.1inch.dev/token" - QUOTE_URL = "https://api.1inch.dev/swap" - APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" - HEADERS = { "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", "accept": "application/json" } - WEB3RPCURL = {"56":"https://bsc-dataseed.binance.org","42161":"https://arb1.arbitrum.io/rpc","137":"https://polygon-rpc.com","1":"https://cloudflare-eth.com","10":"https://mainnet.optimism.io","8453":"https://mainnet.base.org"} - NATIVE_TOKENS={"137":"MATIC","56":"BNB","1":"ETH","42161":"ETH","10":"ETH","8453":"ETH"} - ERC20_ABI = [ - {"constant": True, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", "type": "function"}, - {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"} - ] - INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/tests/__init__.py b/submodules/moragents_dockers/agents/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/claim_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/README.md rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/README.md diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/claim_agent_benchmarks/adapters/claim_adapter.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/adapters/claim_adapter.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/claim_adapter.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py similarity index 92% rename from submodules/benchmarks/claim_agent_benchmarks/benchmarks.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py index d550827..880365d 100644 --- a/submodules/benchmarks/claim_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/benchmarks.py @@ -1,6 +1,6 @@ import pytest from helpers import request_claim, provide_receiver_address, confirm_transaction -from submodules.benchmarks.claim_agent_benchmarks.config import Config +from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config def test_claim_process(): diff --git a/submodules/benchmarks/claim_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py similarity index 100% rename from submodules/benchmarks/claim_agent_benchmarks/config.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/config.py diff --git a/submodules/benchmarks/claim_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py similarity index 71% rename from submodules/benchmarks/claim_agent_benchmarks/helpers.py rename to submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py index afab9a6..a319834 100644 --- a/submodules/benchmarks/claim_agent_benchmarks/helpers.py +++ b/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/helpers.py @@ -1,5 +1,5 @@ -from submodules.benchmarks.claim_agent_benchmarks.config import Config -from submodules.benchmarks.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) diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md new file mode 100644 index 0000000..d1c4b90 --- /dev/null +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/README.md @@ -0,0 +1,20 @@ +# Benchmarking & Testing News Agent Guide + + +## How to Run the Tests: +1) In the parent directory: +- ```cd submodules/moragents_dockers/agents``` + +2) ```docker build -t agent .``` + +2. NOTE: If you are using Apple Silicon then you may experience problems due to the base image not being build for arm64. We have included a separate Dockerfile in order to deal with this issue, run: + +- ```docker build . -t agent -f Dockerfile-apple``` + +3) To run the agent: + +- ```docker run --name agent -p 5000:5000 agent``` + +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 diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py new file mode 100644 index 0000000..7b7df4c --- /dev/null +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/benchmarks.py @@ -0,0 +1,38 @@ +# submodules/benchmarks/news_agent_benchmarks/benchmarks.py + +import pytest +import logging +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 + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def test_news_classification(): + for i, test_case in enumerate(Config.TEST_CASES): + article_text = test_case["article_text"] + expected_classification = test_case["expected_classification"] + + logger.info(f"Testing article classification (Test {i + 1})") + + # Ask the news agent to classify the article + response = ask_news_agent(article_text, Config.LOCAL_AGENT_URL) + + # Extract the classification from the response + classification = extract_classification(response) + + if classification == "UNKNOWN": + 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}" + + logger.info(f"Test case {i + 1} passed: Correctly classified as {classification}") + + logger.info("All test cases passed successfully") + + +if __name__ == "__main__": + pytest.main() diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py new file mode 100644 index 0000000..2868b17 --- /dev/null +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/config.py @@ -0,0 +1,14 @@ +class Config: + TEST_CASES = [ + { + "article_text": "ETH Prices zooms 10 percent today while Bitcoin's price surged to $70,000 today, breaking all previous records. Analysts attribute this to increased institutional adoption and positive regulatory news from several countries.", + "expected_classification": "RELEVANT", + }, + { + "article_text": "A new Tesla facility has opened in Texas, utilizing 100% renewable energy.", + "expected_classification": "NOT RELEVANT", + }, + # Add more test cases as needed + ] + + LOCAL_AGENT_URL = "http://127.0.0.1:5000/" diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py new file mode 100644 index 0000000..7ce2623 --- /dev/null +++ b/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/helpers.py @@ -0,0 +1,48 @@ +# submodules/benchmarks/news_agent_benchmarks/helpers.py + +import requests +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def ask_news_agent(article_text: str, url: str) -> dict: + headers = {'Content-Type': 'application/json'} + payload = { + "prompt": { + "role": "user", + "content": f"Classify if this article is relevant to cryptocurrency price movements: {article_text}" + } + } + response = requests.post(url, headers=headers, json=payload) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Request failed with status code {response.status_code}: {response.text}") + + +def extract_classification(response: dict) -> str: + if not isinstance(response, dict): + logger.warning(f"Unexpected response type: {type(response)}") + return "UNKNOWN" + + content = response.get('content') + + if content is None: + logger.warning("Response content is None") + return "UNKNOWN" + + if not isinstance(content, str): + logger.warning(f"Unexpected content type: {type(content)}") + return "UNKNOWN" + + content = content.upper() + + if "NOT RELEVANT" in content: + return "NOT RELEVANT" + elif "RELEVANT" in content: + return "RELEVANT" + else: + logger.warning(f"Could not determine relevance from content: {content}") + return "NOT RELEVANT" \ No newline at end of file diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/__init__.py b/submodules/moragents_dockers/agents/tests/price_fetching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/price_fetching/adapters/base_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/base_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/base_adapter.py diff --git a/submodules/benchmarks/price_fetching/adapters/coingecko_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/coingecko_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/coingecko_adapter.py diff --git a/submodules/benchmarks/price_fetching/adapters/defillama_adapter.py b/submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py similarity index 100% rename from submodules/benchmarks/price_fetching/adapters/defillama_adapter.py rename to submodules/moragents_dockers/agents/tests/price_fetching/adapters/defillama_adapter.py diff --git a/submodules/benchmarks/price_fetching/benchmarks.py b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py similarity index 94% rename from submodules/benchmarks/price_fetching/benchmarks.py rename to submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py index 728a9b4..d376fcd 100644 --- a/submodules/benchmarks/price_fetching/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/price_fetching/benchmarks.py @@ -1,7 +1,7 @@ import time import argparse from helpers import ask_data_agent, compare_usd_values, extract_agent_usd_value -from submodules.benchmarks.price_fetching.config import coins, price_prompts, mcap_prompts, price_error_tolerance, mcap_error_tolerance, loop_delay +from submodules.moragents_dockers.agents.tests.price_fetching import coins, price_prompts, mcap_prompts, price_error_tolerance, mcap_error_tolerance, loop_delay from adapters.coingecko_adapter import CoingeckoAdapter from adapters.defillama_adapter import DefillamaAdapter diff --git a/submodules/benchmarks/price_fetching/config.py b/submodules/moragents_dockers/agents/tests/price_fetching/config.py similarity index 100% rename from submodules/benchmarks/price_fetching/config.py rename to submodules/moragents_dockers/agents/tests/price_fetching/config.py diff --git a/submodules/benchmarks/price_fetching/helpers.py b/submodules/moragents_dockers/agents/tests/price_fetching/helpers.py similarity index 100% rename from submodules/benchmarks/price_fetching/helpers.py rename to submodules/moragents_dockers/agents/tests/price_fetching/helpers.py diff --git a/submodules/benchmarks/price_fetching/readme.md b/submodules/moragents_dockers/agents/tests/price_fetching/readme.md similarity index 100% rename from submodules/benchmarks/price_fetching/readme.md rename to submodules/moragents_dockers/agents/tests/price_fetching/readme.md diff --git a/submodules/benchmarks/price_fetching/requirements.txt b/submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt similarity index 100% rename from submodules/benchmarks/price_fetching/requirements.txt rename to submodules/moragents_dockers/agents/tests/price_fetching/requirements.txt diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/README.md b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/README.md rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/README.md diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/adapters/reward_check_adapter.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/adapters/reward_check_adapter.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/reward_check_adapter.py diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py similarity index 93% rename from submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py index 3e8df66..03395e7 100644 --- a/submodules/benchmarks/reward_check_agent_benchmarks/benchmarks.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/benchmarks.py @@ -1,6 +1,6 @@ import pytest -from submodules.benchmarks.claim_agent_benchmarks.helpers import request_claim, confirm_transaction -from submodules.benchmarks.claim_agent_benchmarks.config import Config +from submodules.benchmarks.claim_agent_benchmarks.helpers import request_claim +from submodules.moragents_dockers.agents.tests.claim_agent_benchmarks.config import Config def test_claim_process(): diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/config.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py similarity index 97% rename from submodules/benchmarks/reward_check_agent_benchmarks/config.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py index 9f75ab4..2522622 100644 --- a/submodules/benchmarks/reward_check_agent_benchmarks/config.py +++ b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/config.py @@ -7,7 +7,7 @@ # Configuration object class Config: WEB3RPCURL = { - "1": "https://cloudflare-eth.com", + "1": "https://eth.llamarpc.com/", } DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" diff --git a/submodules/benchmarks/reward_check_agent_benchmarks/helpers.py b/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py similarity index 100% rename from submodules/benchmarks/reward_check_agent_benchmarks/helpers.py rename to submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/helpers.py diff --git a/submodules/moragents_dockers/docker-compose-apple.yml b/submodules/moragents_dockers/docker-compose-apple.yml index 9cf6833..27b8acb 100644 --- a/submodules/moragents_dockers/docker-compose-apple.yml +++ b/submodules/moragents_dockers/docker-compose-apple.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:apple-0.1.0 + image: lachsbagel/moragents_dockers-agents:apple-0.2.0 build: dockerfile: Dockerfile-apple context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:apple-0.1.0 + image: lachsbagel/moragents_dockers-nginx:apple-0.2.0 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/docker-compose.yml b/submodules/moragents_dockers/docker-compose.yml index 135b524..f9ad6b0 100644 --- a/submodules/moragents_dockers/docker-compose.yml +++ b/submodules/moragents_dockers/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: agents: - image: lachsbagel/moragents_dockers-agents:amd64-0.1.0 + image: lachsbagel/moragents_dockers-agents:amd64-0.2.0 build: dockerfile: Dockerfile context: ./agents @@ -18,7 +18,7 @@ services: - BASE_URL=http://host.docker.internal:11434 nginx: - image: lachsbagel/moragents_dockers-nginx:amd64-0.1.0 + image: lachsbagel/moragents_dockers-nginx:amd64-0.2.0 build: context: ./frontend dockerfile: Dockerfile diff --git a/submodules/moragents_dockers/frontend/components/Chat/index.tsx b/submodules/moragents_dockers/frontend/components/Chat/index.tsx index 31d6123..37011db 100644 --- a/submodules/moragents_dockers/frontend/components/Chat/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Chat/index.tsx @@ -9,7 +9,7 @@ import { sendSwapStatus, getHttpClient, SWAP_STATUS, - CLAIM_STATUS + CLAIM_STATUS, } from "../../services/backendClient"; import { useAccount, @@ -65,7 +65,7 @@ export const Chat: FC = ({ async (status: string, hash: string, isApprove: number) => { try { const response: ChatMessage = await sendSwapStatus( - getHttpClient(selectedAgent), + getHttpClient(), chainId, address?.toLowerCase() || "0x", status, @@ -158,94 +158,93 @@ export const Chat: FC = ({ [address, handleSwapStatus, sendTransaction] ); - const handleClaimStatus = useCallback( - async (status: string, hash: string) => { - try { - const response: ChatMessage = await sendClaimStatus( - getHttpClient(selectedAgent), - chainId, - address?.toLowerCase() || "0x", - status, - hash - ); + async (status: string, hash: string) => { + try { + const response: ChatMessage = await sendClaimStatus( + getHttpClient(), + chainId, + address?.toLowerCase() || "0x", + status, + hash + ); - if ( - response.role === "assistant" && - typeof response.content === "string" - ) { - setMessagesData((prev) => [ - ...prev, - { - role: "assistant", - content: response.content, - } as UserOrAssistantMessage, - ]); - } else if (response.role === "claim") { - setMessagesData((prev) => [...prev, response as ClaimMessage]); - } + if ( + response.role === "assistant" && + typeof response.content === "string" + ) { + setMessagesData((prev) => [ + ...prev, + { + role: "assistant", + content: response.content, + } as UserOrAssistantMessage, + ]); + } else if (response.role === "claim") { + setMessagesData((prev) => [...prev, response as ClaimMessage]); + } - setTxHash(""); - setShowSpinner(false); - } catch (error) { - console.log(`Error sending claim status: ${error}`); - onBackendError(); - setTxHash(""); - setShowSpinner(false); - } - }, - [selectedAgent, chainId, address, onBackendError] -); + setTxHash(""); + setShowSpinner(false); + } catch (error) { + console.log(`Error sending claim status: ${error}`); + onBackendError(); + setTxHash(""); + setShowSpinner(false); + } + }, + [selectedAgent, chainId, address, onBackendError] + ); // Add this near your other useTransactionConfirmations hooks -const claimConfirmations = useTransactionConfirmations({ - hash: (txHash || "0x") as `0x${string}`, -}); + const claimConfirmations = useTransactionConfirmations({ + hash: (txHash || "0x") as `0x${string}`, + }); -// Add this effect to watch for claim transaction confirmations -useEffect(() => { - if (txHash && claimConfirmations.data && claimConfirmations.data >= 1) { - handleClaimStatus(CLAIM_STATUS.SUCCESS, txHash); - } -}, [txHash, claimConfirmations.data, handleClaimStatus]); + // Add this effect to watch for claim transaction confirmations + useEffect(() => { + if (txHash && claimConfirmations.data && claimConfirmations.data >= 1) { + handleClaimStatus(CLAIM_STATUS.SUCCESS, txHash); + } + }, [txHash, claimConfirmations.data, handleClaimStatus]); -// Modify handleClaimSubmit to use the same txHash state -const handleClaimSubmit = useCallback( - (claimTx: ClaimTransactionPayload) => { - setTxHash(""); - console.log("Claim transaction to be sent:", claimTx); - sendTransaction( - { - account: address, - data: claimTx.data as `0x${string}`, - to: claimTx.to as `0x${string}`, - value: BigInt(claimTx.value), - chainId: parseInt(claimTx.chainId), - }, - { - onSuccess: (hash) => { - setTxHash(hash); - handleClaimStatus(CLAIM_STATUS.INIT, hash); - }, - onError: (error) => { - console.log(`Error sending transaction: ${error}`); - handleClaimStatus(CLAIM_STATUS.FAIL, ""); + // Modify handleClaimSubmit to use the same txHash state + const handleClaimSubmit = useCallback( + (claimTx: ClaimTransactionPayload) => { + setTxHash(""); + console.log("Claim transaction to be sent:", claimTx); + sendTransaction( + { + account: address, + data: claimTx.data as `0x${string}`, + to: claimTx.to as `0x${string}`, + value: BigInt(claimTx.value), + chainId: parseInt(claimTx.chainId), }, - } - ); - }, - [address, handleClaimStatus, sendTransaction] -); + { + onSuccess: (hash) => { + setTxHash(hash); + handleClaimStatus(CLAIM_STATUS.INIT, hash); + }, + onError: (error) => { + console.log(`Error sending transaction: ${error}`); + handleClaimStatus(CLAIM_STATUS.FAIL, ""); + }, + } + ); + }, + [address, handleClaimStatus, sendTransaction] + ); return ( + messages={messagesData} + selectedAgent={selectedAgent} + onCancelSwap={onCancelSwap} + onSwapSubmit={handleSwapSubmit} + onClaimSubmit={handleClaimSubmit} + /> {showSpinner && } { onAgentChanged(agent: string): void; @@ -11,6 +16,18 @@ export interface HeaderBarProps extends ComponentPropsWithoutRef<"div"> { } export const HeaderBar: FC = (props) => { + const backendClient = getHttpClient(); + const router = useRouter(); + + const handleClearChatHistory = async () => { + try { + await clearMessagesHistory(backendClient); + router.reload(); + } catch (error) { + console.error("Failed to clear chat history:", error); + } + }; + return ( @@ -19,6 +36,9 @@ export const HeaderBar: FC = (props) => { + diff --git a/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx b/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx index 5b323d8..1d44164 100644 --- a/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx +++ b/submodules/moragents_dockers/frontend/components/SwapForm/index.tsx @@ -151,7 +151,7 @@ export const SwapForm: FC = ({ } const _payload = await getApprovalTxPayload( - getHttpClient(selectedAgent), + getHttpClient(), chainId, src_address, Number(src_amount), @@ -166,14 +166,7 @@ export const SwapForm: FC = ({ setIsButtonLoading(false); } }, - [ - onSubmitApprove, - isNativeToken, - decimals?.data, - fromMessage, - chainId, - selectedAgent, - ] + [onSubmitApprove, isNativeToken, decimals?.data, fromMessage, chainId] ); const handleSwap = useCallback( @@ -182,7 +175,7 @@ export const SwapForm: FC = ({ try { const _payload = await getSwapTxPayload( - getHttpClient(selectedAgent), + getHttpClient(), fromMessage.src_address, fromMessage.dst_address, address, @@ -202,14 +195,7 @@ export const SwapForm: FC = ({ setIsButtonLoading(false); } }, - [ - fromMessage, - chainId, - selectedAgent, - formData.slippage, - onSubmitSwap, - decimals, - ] + [fromMessage, chainId, formData.slippage, onSubmitSwap, decimals] ); useEffect(() => { diff --git a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx b/submodules/moragents_dockers/frontend/components/Tweet/index.tsx index 4563293..1deab63 100644 --- a/submodules/moragents_dockers/frontend/components/Tweet/index.tsx +++ b/submodules/moragents_dockers/frontend/components/Tweet/index.tsx @@ -54,7 +54,7 @@ export const Tweet: FC = ({ initialContent, selectedAgent }) => { const handleTweet = async () => { setIsTweeting(true); - const backendClient = getHttpClient(selectedAgent); + const backendClient = getHttpClient(); try { await postTweet(backendClient, tweetContent); @@ -72,7 +72,7 @@ export const Tweet: FC = ({ initialContent, selectedAgent }) => { const handleRegenerate = async () => { setIsRegenerating(true); - const backendClient = getHttpClient(selectedAgent); + const backendClient = getHttpClient(); try { const newTweet = await regenerateTweet(backendClient); diff --git a/submodules/moragents_dockers/frontend/config.ts b/submodules/moragents_dockers/frontend/config.ts index 0f4d11a..647d724 100644 --- a/submodules/moragents_dockers/frontend/config.ts +++ b/submodules/moragents_dockers/frontend/config.ts @@ -1,25 +1,25 @@ -export const routerAddress = '0x111111125421cA6dc452d289314280a0f8842A65'; -export const oneInchNativeToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; +export const routerAddress = "0x111111125421cA6dc452d289314280a0f8842A65"; +export const oneInchNativeToken = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; export const availableAgents: { - [key: string]: { - name: string, - description: string, - endpoint: string, - requirements: { - connectedWallet: boolean - }, - supportsFiles?: boolean - - } + [key: string]: { + name: string; + description: string; + endpoint: string; + requirements: { + connectedWallet: boolean; + }; + supportsFiles?: boolean; + }; } = { - 'swap-agent': { - 'name': 'Morpheus', - 'description': 'performs multiple tasks crypto data agent,swap agent and rag agent', - 'endpoint': 'http://127.0.0.1:8080', - requirements: { - connectedWallet: true - }, - supportsFiles: true - } -} \ No newline at end of file + "swap-agent": { + name: "Morpheus", + description: + "performs multiple tasks crypto data agent,swap agent and rag agent", + endpoint: "http://127.0.0.1:8080", + requirements: { + connectedWallet: true, + }, + supportsFiles: true, + }, +}; diff --git a/submodules/moragents_dockers/frontend/package-lock.json b/submodules/moragents_dockers/frontend/package-lock.json index 217bdae..0529c6b 100644 --- a/submodules/moragents_dockers/frontend/package-lock.json +++ b/submodules/moragents_dockers/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "Morpheus AI", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Morpheus AI", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@chakra-ui/icons": "^2.1.1", @@ -6482,6 +6482,7 @@ "version": "18.19.49", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.49.tgz", "integrity": "sha512-ALCeIR6n0nQ7j0FUF1ycOhrp6+XutJWqEu/vtdEqXFUQwkBfgUA5cEg3ZNmjWGF/ZYA/FcF9QMkL55Ar0O6UrA==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -6500,6 +6501,7 @@ "version": "18.3.5", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -18291,6 +18293,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/submodules/moragents_dockers/frontend/package.json b/submodules/moragents_dockers/frontend/package.json index b7ecc2c..eca1a8c 100644 --- a/submodules/moragents_dockers/frontend/package.json +++ b/submodules/moragents_dockers/frontend/package.json @@ -1,7 +1,7 @@ { "name": "Morpheus AI", "private": true, - "version": "0.1.0", + "version": "0.2.0", "scripts": { "dev": "next dev", "build": "next build", @@ -21,9 +21,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.3.0", + "react-markdown": "^9.0.0", "viem": "^2.8.12", - "wagmi": "^2.5.11", - "react-markdown": "^9.0.0" + "wagmi": "^2.5.11" }, "devDependencies": { "@swc/cli": "^0.3.12", diff --git a/submodules/moragents_dockers/frontend/pages/index.tsx b/submodules/moragents_dockers/frontend/pages/index.tsx index 8b0d192..d156de9 100644 --- a/submodules/moragents_dockers/frontend/pages/index.tsx +++ b/submodules/moragents_dockers/frontend/pages/index.tsx @@ -1,33 +1,41 @@ -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import type { NextPage } from 'next'; -import { Flex, Grid, GridItem } from '@chakra-ui/react'; -import { LeftSidebar } from '../components/LeftSidebar'; -import { Chat } from '../components/Chat'; -import { writeMessage, getHttpClient, ChatMessage, getMessagesHistory, sendSwapStatus, SWAP_STATUS, uploadFile } from '../services/backendClient'; -import { useEffect, useMemo, useState } from 'react'; -import { useAccount, useChainId, useWalletClient } from 'wagmi'; -import { HeaderBar } from '../components/HeaderBar'; -import { availableAgents } from '../config'; -import { WalletRequiredModal } from '../components/WalletRequiredModal'; -import { ErrorBackendModal } from '../components/ErrorBackendModal'; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import type { NextPage } from "next"; +import { Flex, Grid, GridItem } from "@chakra-ui/react"; +import { LeftSidebar } from "../components/LeftSidebar"; +import { Chat } from "../components/Chat"; +import { + writeMessage, + getHttpClient, + ChatMessage, + getMessagesHistory, + sendSwapStatus, + SWAP_STATUS, + uploadFile, +} from "../services/backendClient"; +import { useEffect, useMemo, useState } from "react"; +import { useAccount, useChainId, useWalletClient } from "wagmi"; +import { HeaderBar } from "../components/HeaderBar"; +import { availableAgents } from "../config"; +import { WalletRequiredModal } from "../components/WalletRequiredModal"; +import { ErrorBackendModal } from "../components/ErrorBackendModal"; const Home: NextPage = () => { const [chatHistory, setChatHistory] = useState([]); const chainId = useChainId(); const { address } = useAccount(); - const [selectedAgent, setSelectedAgent] = useState('swap-agent'); // default is swap agent for now. + const [selectedAgent, setSelectedAgent] = useState("swap-agent"); // default is swap agent for now. const [showBackendError, setShowBackendError] = useState(false); useEffect(() => { - getMessagesHistory( - getHttpClient(selectedAgent), - ).then((messages: ChatMessage[]) => { - setChatHistory([...messages]) - }).catch((e) => { - console.error(`Failed to get initial messages history. Error: ${e}`); - setShowBackendError(true); - }); - }, [selectedAgent]); + getMessagesHistory(getHttpClient()) + .then((messages: ChatMessage[]) => { + setChatHistory([...messages]); + }) + .catch((e) => { + console.error(`Failed to get initial messages history. Error: ${e}`); + setShowBackendError(true); + }); + }, []); // Empty dependency array to run only on component initialization const isWalletRequired = useMemo(() => { const agent = availableAgents[selectedAgent] || null; @@ -40,34 +48,34 @@ const Home: NextPage = () => { }, [selectedAgent]); return ( -
+
{ setSelectedAgent(agent); }} - currentAgent={selectedAgent || ''} + currentAgent={selectedAgent || ""} /> - - + - - + { // 0 is swap, 1 is approve @@ -76,29 +84,36 @@ const Home: NextPage = () => { } try { - await sendSwapStatus(getHttpClient(selectedAgent), chainId, address, SWAP_STATUS.CANCELLED, '', fromAction); + await sendSwapStatus( + getHttpClient(), + chainId, + address, + SWAP_STATUS.CANCELLED, + "", + fromAction + ); } catch (e) { console.error(`Failed to cancel swap . Error: ${e}`); setShowBackendError(true); - } finally { try { const _updatedMessages = await getMessagesHistory( - getHttpClient(selectedAgent), - ) + getHttpClient() + ); setChatHistory([..._updatedMessages]); } catch (e) { - console.error(`Failed to get messages history after send swap status. Error: ${e}`); + console.error( + `Failed to get messages history after send swap status. Error: ${e}` + ); setShowBackendError(true); } } - - - }} - onSubmitMessage={async (message: string, file: File | null): Promise => { - + onSubmitMessage={async ( + message: string, + file: File | null + ): Promise => { const agent = availableAgents[selectedAgent] || null; if (null !== agent && agent.requirements.connectedWallet) { @@ -107,26 +122,29 @@ const Home: NextPage = () => { } } - setChatHistory([...chatHistory, { - role: 'user', - content: message - } as ChatMessage]); + setChatHistory([ + ...chatHistory, + { + role: "user", + content: message, + } as ChatMessage, + ]); let _newHistory = []; try { if (!file) { - _newHistory = await writeMessage(chatHistory, message, getHttpClient(selectedAgent), chainId, address || ''); - + _newHistory = await writeMessage( + chatHistory, + message, + getHttpClient(), + chainId, + address || "" + ); } else { - await uploadFile( - getHttpClient(selectedAgent), - file, - ) - - _newHistory = await getMessagesHistory( - getHttpClient(selectedAgent), - ) + await uploadFile(getHttpClient(), file); + + _newHistory = await getMessagesHistory(getHttpClient()); } - setChatHistory([..._newHistory]) + setChatHistory([..._newHistory]); } catch (e) { console.error(`Failed to send message. Error: ${e}`); setShowBackendError(true); @@ -142,7 +160,6 @@ const Home: NextPage = () => { {/* */} - {/* { return; } - const _newHistory = await writeMessage(chatHistory, message, getHttpClient(selectedAgent), chainId, address); + const _newHistory = await writeMessage(chatHistory, message, getHttpClient(), chainId, address); setChatHistory([..._newHistory]) }} /> */} -
diff --git a/submodules/moragents_dockers/frontend/services/backendClient.ts b/submodules/moragents_dockers/frontend/services/backendClient.ts index e1d936e..84a03e9 100644 --- a/submodules/moragents_dockers/frontend/services/backendClient.ts +++ b/submodules/moragents_dockers/frontend/services/backendClient.ts @@ -1,11 +1,9 @@ import axios, { Axios } from "axios"; -import { availableAgents } from "../config"; export type ChatMessageBase = { role: "user" | "assistant" | "swap" | "claim"; }; - export type UserOrAssistantMessage = ChatMessageBase & { role: "user" | "assistant"; content: string; @@ -90,25 +88,20 @@ export type ClaimMessage = ChatMessageBase & { }; // Update the ChatMessage type to include ClaimMessage -export type ChatMessage = UserOrAssistantMessage | SwapMessage | SystemMessage | ClaimMessage; +export type ChatMessage = + | UserOrAssistantMessage + | SwapMessage + | SystemMessage + | ClaimMessage; export type ChatsListItem = { index: number; // index at chats array title: string; // title of the chat (first message content) }; -export const getHttpClient = (selectedAgent: string) => { - const agentData = availableAgents[selectedAgent]; - - if (typeof agentData === "undefined") { - // if no agent selected lets select by default swap agent for now. - } - - const swapAgentUrl = - agentData?.endpoint || availableAgents["swap-agent"].endpoint; - +export const getHttpClient = () => { return axios.create({ - baseURL: swapAgentUrl || "http://localhost:8080", + baseURL: "http://localhost:8080", }); }; @@ -155,7 +148,7 @@ export const getApprovalTxPayload = async ( export const uploadFile = async (backendClient: Axios, file: File) => { const formData = new FormData(); formData.append("file", file); - + console.log("Uploading file:", file); return await backendClient.post("/upload", formData, { headers: { "Content-Type": "multipart/form-data", @@ -220,6 +213,18 @@ export const getMessagesHistory = async ( } as ChatMessage; }); }; + +export const clearMessagesHistory = async ( + backendClient: Axios +): Promise => { + try { + await backendClient.get("/clear_messages"); + } catch (error) { + console.error("Failed to clear message history:", error); + throw error; + } +}; + export const writeMessage = async ( history: ChatMessage[], message: string, @@ -235,7 +240,7 @@ export const writeMessage = async ( history.push(newMessage); let resp; try { - resp = await backendClient.post("/", { + resp = await backendClient.post("/chat", { prompt: { role: "user", content: message, @@ -338,4 +343,4 @@ export const sendClaimStatus = async ( role: responseBody.data.role, content: responseBody.data.content, } as ChatMessage; -}; \ No newline at end of file +}; diff --git a/wizard_windows.iss b/wizard_windows.iss index 34d0565..0fdcf7b 100644 --- a/wizard_windows.iss +++ b/wizard_windows.iss @@ -1,6 +1,6 @@ [Setup] AppName=MORagents -AppVersion=0.1.0 +AppVersion=0.2.0 DefaultDirName={commonpf}\MORagents OutputDir=.\MORagentsWindowsInstaller OutputBaseFilename=MORagentsSetup @@ -27,7 +27,7 @@ Filename: "{tmp}\DockerDesktopInstaller.exe"; Parameters: "install"; StatusMsg: Filename: "{tmp}\OllamaSetup.exe"; StatusMsg: "Installing Ollama..."; Flags: waituntilterminated Filename: "{app}\LICENSE.md"; Description: "View License Agreement"; Flags: postinstall shellexec skipifsilent Filename: "{app}\MORagents.exe"; Description: "Launch MORagents"; Flags: postinstall nowait skipifsilent unchecked -Filename: "cmd.exe"; Parameters: "/c ollama pull llama3.1"; StatusMsg: "Pulling llama3.1 model..."; Flags: runhidden waituntilterminated +Filename: "cmd.exe"; Parameters: "/c ollama pull llama3.2:3b"; StatusMsg: "Pulling llama3.2:3b model..."; Flags: runhidden waituntilterminated Filename: "cmd.exe"; Parameters: "/c ollama pull nomic-embed-text"; StatusMsg: "Pulling nomic-embed-text model..."; Flags: runhidden waituntilterminated [Code] From 799316c2cea504e19926c057eb91ad89d796afb2 Mon Sep 17 00:00:00 2001 From: LachsBagel <149974355+LachsBagel@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:35:34 -0500 Subject: [PATCH 15/15] Small fixes to news agent. Selection of agents to load in config. --- .../agents/src/agents/news_agent/agent.py | 6 +-- .../moragents_dockers/agents/src/config.py | 43 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) 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 ecfbfcc..ecbb4d0 100644 --- a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py +++ b/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py @@ -49,12 +49,12 @@ def get_tools(self): def check_relevance_and_summarize(self, title, content, coin): logger.info(f"Checking relevance for {coin}: {title}") prompt = Config.RELEVANCE_PROMPT.format(coin=coin, title=title, content=content) - result = self.llm.create_chat_completion( - messages=[{"role": "user", "content": prompt}], + result = self.llm.invoke( + input=[{"role": "user", "content": prompt}], max_tokens=Config.LLM_MAX_TOKENS, temperature=Config.LLM_TEMPERATURE, ) - return result["choices"][0]["message"]["content"].strip() + return result.content.strip() def process_rss_feed(self, feed_url, coin): logger.info(f"Processing RSS feed for {coin}: {feed_url}") diff --git a/submodules/moragents_dockers/agents/src/config.py b/submodules/moragents_dockers/agents/src/config.py index f7f9d56..05a06ee 100644 --- a/submodules/moragents_dockers/agents/src/config.py +++ b/submodules/moragents_dockers/agents/src/config.py @@ -37,20 +37,19 @@ class Config: # "upload_required": False, # }, { - "path": "src.agents.tweet_sizzler.agent", "class": "TweetSizzlerAgent", "description": "Generates and posts engaging tweets. Use when the query explicitly mentions Twitter, tweeting, or X platform.", "name": "tweet sizzler agent", "upload_required": False, }, - { - "path": "src.agents.mor_claims.agent", - "class": "MorClaimsAgent", - "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", - "name": "mor claims agent", - "upload_required": False, - }, + # { + # "path": "src.agents.mor_claims.agent", + # "class": "MorClaimsAgent", + # "description": "Manages the process of claiming rewards or tokens, specifically MOR rewards. Use when the query explicitly mentions claiming rewards or tokens.", + # "name": "mor claims agent", + # "upload_required": False, + # }, { "path": "src.agents.mor_rewards.agent", "class": "MorRewardsAgent", @@ -58,19 +57,19 @@ class Config: "name": "mor rewards agent", "upload_required": False, }, - # { - # "path": "src.agents.realtime_search.agent", - # "class": "RealtimeSearchAgent", - # "description": "Only use this agent for real-time data. This agent is not for general purpose queries. Use when the query is about searching the web for real-time information.", - # "name": "realtime search agent", - # "upload_required": False, - # }, - # { - # "path": "src.agents.news_agent.agent", - # "class": "NewsAgent", - # "description": "Fetches and analyzes cryptocurrency news for potential price impacts.", - # "name": "crypto news agent", - # "upload_required": False, - # } + { + "path": "src.agents.realtime_search.agent", + "class": "RealtimeSearchAgent", + "description": "Only use this agent for real-time data. This agent is not for general purpose queries. Use when the query is about searching the web for real-time information.", + "name": "realtime search agent", + "upload_required": False, + }, + { + "path": "src.agents.news_agent.agent", + "class": "NewsAgent", + "description": "Fetches and analyzes cryptocurrency news for potential price impacts.", + "name": "crypto news agent", + "upload_required": False, + } ] }