diff --git a/.env.example b/.env.example index f3e42a8..4331b68 100644 --- a/.env.example +++ b/.env.example @@ -21,7 +21,15 @@ COINBASE_API_KEY="" COINBASE_API_SECRET="" COINBASE_API_PASSPHRASE="" -TWITTER_API_KEY=your_api_key -TWITTER_API_SECRET=your_api_secret -TWITTER_ACCESS_TOKEN=your_access_token -TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret +# Twitter API Credentials +TWITTER_API_KEY="" +TWITTER_API_SECRET="" +TWITTER_ACCESS_TOKEN="" +TWITTER_ACCESS_TOKEN_SECRET="" + +# OpenAI API Credentials +OPENAI_API_KEY="" + + +# Synthesia API Key +SYNTHESIA_API_KEY="" diff --git a/htx_tool_example.py b/examples/htx_tool_example.py similarity index 100% rename from htx_tool_example.py rename to examples/htx_tool_example.py diff --git a/tool_chainer_example.py b/examples/tool_chainer_example.py similarity index 100% rename from tool_chainer_example.py rename to examples/tool_chainer_example.py diff --git a/mcs_twitter_bot.py b/mcs_twitter_bot.py index bc3b515..126be32 100644 --- a/mcs_twitter_bot.py +++ b/mcs_twitter_bot.py @@ -1,114 +1,216 @@ import os import time - from dotenv import load_dotenv from loguru import logger +import tweepy from swarm_models import OpenAIChat from swarms import Agent +from langchain_core.messages import HumanMessage -from swarms_tools.social_media.twitter_api import TwitterBot - +# Load environment variables load_dotenv() - -def init_medical_coder() -> Agent: - """Initialize the medical coding agent.""" - model = OpenAIChat( - model_name="gpt-4", - max_tokens=3000, - openai_api_key=os.getenv("OPENAI_API_KEY"), +# Verify environment variables are present +required_vars = [ + "TWITTER_API_KEY", + "TWITTER_API_SECRET", + "TWITTER_ACCESS_TOKEN", + "TWITTER_ACCESS_TOKEN_SECRET", + "OPENAI_API_KEY", +] + +missing_vars = [var for var in required_vars if not os.getenv(var)] +if missing_vars: + raise ValueError( + f"Missing required environment variables: {missing_vars}" ) - return Agent( - agent_name="Medical Coder", - system_prompt=""" - You are a highly experienced and certified medical coder with extensive knowledge of ICD-10 coding guidelines, clinical documentation standards, and compliance regulations. Your responsibility is to ensure precise, compliant, and well-documented coding for all clinical cases. - - ### Primary Responsibilities: - 1. **Review Clinical Documentation**: Analyze all available clinical records, including specialist inputs, physician notes, lab results, imaging reports, and discharge summaries. - 2. **Assign Accurate ICD-10 Codes**: Identify and assign appropriate codes for primary diagnoses, secondary conditions, symptoms, and complications. - 3. **Ensure Coding Compliance**: Follow the latest ICD-10-CM/PCS coding guidelines, payer-specific requirements, and organizational policies. - 4. **Document Code Justification**: Provide clear, evidence-based rationale for each assigned code. - - ### Output Requirements: - Provide a concise, Twitter-friendly response that includes: - 1. The most relevant ICD-10 codes - 2. Brief descriptions - 3. Key supporting evidence - - Keep responses under 280 characters when possible, split into threads if needed. - """, - llm=model, - max_loops=1, - dynamic_temperature_enabled=True, - ) +# Configure logging +logger.remove() # Remove default handler +logger.add( + "medical_coding_bot.log", + rotation="1 day", + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", + backtrace=True, + diagnose=True, +) +logger.add( + lambda msg: print(msg), level="INFO" +) # Also log to console class MedicalCodingBot: - """Twitter bot that provides medical coding assistance.""" - def __init__(self): - self.medical_coder = init_medical_coder() - self.twitter_bot = TwitterBot( - response_callback=self.generate_response - ) - logger.info("Medical Coding Bot initialized") - - def generate_response(self, message: str) -> str: - """Generate a medical coding response.""" + """Initialize the bot with Twitter and OpenAI credentials.""" try: - # Remove mentions from the message to focus on the medical query - clean_message = self.clean_message(message) - - # Get response from medical coder - response = self.medical_coder.run(clean_message) - - # Ensure response fits Twitter's character limit - return self.format_response(response) - - except Exception as e: - logger.error(f"Error generating response: {str(e)}") - return "I apologize, but I encountered an error processing your request. Please try again." - - def clean_message(self, message: str) -> str: - """Remove Twitter mentions and clean up the message.""" - # Remove mentions (starting with @) - words = message.split() + logger.debug("Initializing Twitter client...") + # Initialize Twitter client + self.client = tweepy.Client( + consumer_key=os.getenv("TWITTER_API_KEY"), + consumer_secret=os.getenv("TWITTER_API_SECRET"), + access_token=os.getenv("TWITTER_ACCESS_TOKEN"), + access_token_secret=os.getenv( + "TWITTER_ACCESS_TOKEN_SECRET" + ), + wait_on_rate_limit=True, + ) + + # Test Twitter authentication + logger.debug("Testing Twitter authentication...") + self.me = self.client.get_me() + if not self.me or not self.me.data: + raise ValueError("Failed to get Twitter user details") + logger.info( + f"Twitter initialized for @{self.me.data.username}" + ) + + # Initialize OpenAI + logger.debug("Initializing OpenAI...") + self.model = OpenAIChat( + model_name="gpt-4", + max_tokens=3000, + openai_api_key=os.getenv("OPENAI_API_KEY"), + ) + + # Test OpenAI + logger.debug("Testing OpenAI connection...") + test_message = HumanMessage(content="Test message") + test_response = self.model.invoke([test_message]) + if not test_response: + raise ValueError("Failed to get response from OpenAI") + logger.debug("OpenAI test successful") + + # Initialize Medical Coder + logger.debug("Initializing Medical Coder agent...") + self.medical_coder = Agent( + agent_name="Medical Coder", + system_prompt="""You are a medical coding expert. When asked about medical conditions, provide: + 1. The most relevant ICD-10 code + 2. A brief description (1-2 sentences) + Keep total response under 280 characters.""", + llm=self.model, + max_loops=1, + ) + + self.last_mention_time = None + logger.info("Medical Coding Bot initialized successfully") + + except Exception: + logger.error("Initialization failed", exc_info=True) + raise + + def clean_message(self, text: str) -> str: + """Remove mentions and clean the message.""" + words = text.split() clean_words = [ word for word in words if not word.startswith("@") ] return " ".join(clean_words).strip() - def format_response(self, response: str) -> str: - """Format the response to fit Twitter's character limit.""" - if len(response) <= 280: - return response - - # Split into multiple tweets if needed - # For now, just truncate to fit - return response[:277] + "..." - - def run(self, check_interval: int = 60): + def check_mentions(self): + """Check and respond to mentions.""" + try: + logger.info("Checking mentions...") + mentions = self.client.get_users_mentions( + self.me.data.id, + tweet_fields=["created_at", "text"], + max_results=10, + ) + + if not mentions.data: + logger.debug("No mentions found") + return + + logger.info(f"Found {len(mentions.data)} mentions") + for mention in mentions.data: + try: + # Skip if we've already processed this mention + if ( + self.last_mention_time + and mention.created_at + <= self.last_mention_time + ): + logger.debug( + f"Skipping already processed mention {mention.id}" + ) + continue + + logger.info( + f"Processing mention {mention.id}: {mention.text}" + ) + + # Clean the message + clean_text = self.clean_message(mention.text) + logger.debug(f"Cleaned text: {clean_text}") + + if not clean_text: + logger.debug( + "Skipping empty message after cleaning" + ) + continue + + # Generate response + logger.debug("Generating response...") + response = self.medical_coder.run(clean_text) + logger.debug(f"Generated response: {response}") + + if not response: + logger.warning( + "Empty response from medical coder" + ) + continue + + # Ensure response isn't too long + if len(response) > 280: + response = response[:277] + "..." + + # Reply to tweet + logger.debug(f"Sending reply to {mention.id}") + self.client.create_tweet( + text=response, in_reply_to_tweet_id=mention.id + ) + logger.info( + f"Successfully replied to mention {mention.id}" + ) + + self.last_mention_time = mention.created_at + + except Exception: + logger.error( + f"Error processing mention {mention.id}", + exc_info=True, + ) + continue + + except Exception: + logger.error("Error checking mentions", exc_info=True) + + def run(self): """Run the bot continuously.""" - logger.info("Starting Medical Coding Bot") - + logger.info("Starting bot...") + iteration = 0 while True: try: - # Handle mentions - self.twitter_bot.handle_mentions() - - # Handle DMs - self.twitter_bot.handle_dms() - - # Wait before next check - time.sleep(check_interval) - - except Exception as e: - logger.error(f"Error in bot loop: {str(e)}") - time.sleep(check_interval) + iteration += 1 + logger.info(f"Starting iteration {iteration}") + self.check_mentions() + logger.info( + f"Completed iteration {iteration}, sleeping for 60 seconds" + ) + time.sleep(60) # Check every minute + except Exception: + logger.error( + f"Error in iteration {iteration}", exc_info=True + ) + time.sleep(60) # Wait before retrying if __name__ == "__main__": - # Initialize and run the bot - medical_bot = MedicalCodingBot() - medical_bot.run() + try: + logger.info("Starting Medical Coding Bot") + bot = MedicalCodingBot() + bot.run() + except Exception: + logger.critical("Fatal error occurred", exc_info=True) + raise diff --git a/pyproject.toml b/pyproject.toml index dac165b..7e3e7f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms-tools" -version = "0.0.4" +version = "0.0.5" description = "Paper - Pytorch" license = "MIT" authors = ["Kye Gomez "] @@ -23,7 +23,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.10" -loguru = "^0.7.0" +loguru = "*" requests = "*" python-dotenv = "*" aiohttp = "^3.8.0" diff --git a/swarms_tools/character/__init__.py b/swarms_tools/character/__init__.py new file mode 100644 index 0000000..2203cb1 --- /dev/null +++ b/swarms_tools/character/__init__.py @@ -0,0 +1,3 @@ +from swarms_tools.character.synthesia_tool import synthesia_api + +__all__ = ["synthesia_api"] diff --git a/swarms_tools/character/synthesia_tool.py b/swarms_tools/character/synthesia_tool.py new file mode 100644 index 0000000..a4efdf6 --- /dev/null +++ b/swarms_tools/character/synthesia_tool.py @@ -0,0 +1,65 @@ +import os +import requests +from loguru import logger +from dotenv import load_dotenv + +load_dotenv() # Load environment variables + + +class SynthesiaAPI: + def __init__( + self, bearer_key: str = os.getenv("SYNTHESIA_API_KEY") + ): + """ + Initialize the Synthesia API client with a bearer key. + + Args: + bearer_key (str): The bearer key for authentication. + """ + self.url = "https://api.synthesia.io/v2/videos" + self.headers = { + "accept": "application/json", + "content-type": "application/json", + "Authorization": f"Bearer {bearer_key}", + } + + def create_video(self, payload: dict) -> str: + """ + Create a video using the Synthesia API. + + Args: + payload (dict): The payload for the video creation request. + + Returns: + str: The response text from the API. + """ + try: + response = requests.post( + self.url, json=payload, headers=self.headers + ) + logger.info("Video creation request sent successfully.") + return response.text + except requests.exceptions.RequestException as e: + logger.error(f"Failed to create video: {e}") + return "Failed to create video" + + +def synthesia_api(payload: dict) -> str: + """ + Create a video using the Synthesia API. + + Args: + payload (dict): The payload for the video creation request. + + Returns: + str: The response text from the API. + """ + synthesia = SynthesiaAPI() + payload = { + "test": True, + "title": "My first Synthetic video", + "visibility": "private", + "aspectRatio": "16:9", + } + response = synthesia.create_video(payload) + return response diff --git a/swarms_tools/finance/__init__.py b/swarms_tools/finance/__init__.py index 08e1d71..288c72c 100644 --- a/swarms_tools/finance/__init__.py +++ b/swarms_tools/finance/__init__.py @@ -1,13 +1,19 @@ -from swarms_tools.finance.eodh_api import fetch_stock_news -from swarms_tools.finance.htx_tool import fetch_htx_data -from swarms_tools.finance.yahoo_finance import ( - yahoo_finance_api, +from swarms_tools.finance.coinbase_tool import ( + get_coin_data, + place_buy_order, + place_sell_order, ) from swarms_tools.finance.coingecko_tool import ( coin_gecko_coin_api, ) +from swarms_tools.finance.eodh_api import fetch_stock_news from swarms_tools.finance.helius_api import helius_api_tool +from swarms_tools.finance.htx_tool import fetch_htx_data from swarms_tools.finance.okx_tool import okx_api_tool +from swarms_tools.finance.yahoo_finance import ( + yahoo_finance_api, +) +from swarms_tools.finance.coin_market_cap import coinmarketcap_api __all__ = [ "fetch_stock_news", @@ -16,4 +22,8 @@ "coin_gecko_coin_api", "helius_api_tool", "okx_api_tool", + "get_coin_data", + "place_buy_order", + "place_sell_order", + "coinmarketcap_api", ] diff --git a/swarms_tools/social_media/twitter_api.py b/swarms_tools/social_media/twitter_api.py index 50f99e1..093ccd7 100644 --- a/swarms_tools/social_media/twitter_api.py +++ b/swarms_tools/social_media/twitter_api.py @@ -24,6 +24,24 @@ class TwitterAPI: def __init__(self): """Initialize Twitter API with credentials from environment variables.""" try: + # Log environment variable presence (not their values) + logger.debug("Checking Twitter API credentials...") + credentials = { + "API_KEY": bool(os.getenv("TWITTER_API_KEY")), + "API_SECRET": bool(os.getenv("TWITTER_API_SECRET")), + "ACCESS_TOKEN": bool( + os.getenv("TWITTER_ACCESS_TOKEN") + ), + "ACCESS_TOKEN_SECRET": bool( + os.getenv("TWITTER_ACCESS_TOKEN_SECRET") + ), + } + logger.debug(f"Credentials present: {credentials}") + + if not all(credentials.values()): + missing = [k for k, v in credentials.items() if not v] + raise ValueError(f"Missing credentials: {missing}") + self.client = tweepy.Client( consumer_key=os.getenv("TWITTER_API_KEY"), consumer_secret=os.getenv("TWITTER_API_SECRET"), @@ -41,11 +59,23 @@ def __init__(self): os.getenv("TWITTER_ACCESS_TOKEN_SECRET"), ) self.api = tweepy.API(auth) - logger.info("Twitter API initialized successfully") + + # Verify credentials + self.api.verify_credentials() + self.me = self.client.get_me() + if not self.me: + raise tweepy.TweepyException( + "Failed to get user details" + ) + + logger.info( + f"Twitter API initialized successfully for user @{self.me.data.username}" + ) except Exception as e: logger.error( - f"Failed to initialize Twitter API: {str(e)}" + f"Failed to initialize Twitter API: {str(e)}", + exc_info=True, ) raise @@ -80,14 +110,32 @@ def send_dm(self, user_id: str, message: str) -> bool: def get_mentions(self) -> Optional[Dict]: """Get recent mentions of the authenticated user.""" try: + logger.info("Attempting to fetch recent mentions") + mentions = self.client.get_users_mentions( - self.client.get_me().data.id, - tweet_fields=["created_at"], - ).data - return mentions + self.me.data.id, + tweet_fields=["created_at", "text", "author_id"], + expansions=["author_id"], + max_results=100, + ) + + if mentions.data: + logger.info(f"Found {len(mentions.data)} mentions") + for mention in mentions.data: + logger.debug( + f"Mention ID: {mention.id}, " + f"Text: {mention.text}, " + f"Created at: {mention.created_at}" + ) + return mentions.data + else: + logger.info("No mentions found") + return None except Exception as e: - logger.error(f"Failed to get mentions: {str(e)}") + logger.error( + f"Failed to get mentions: {str(e)}", exc_info=True + ) return None def get_dms(self) -> Optional[Dict]: @@ -105,23 +153,88 @@ class TwitterBot: """A simplified Twitter bot that responds to mentions and DMs.""" def __init__(self, response_callback: Callable[[str], str]): - self.api = TwitterAPI() - self.response_callback = response_callback - self.last_mention_time = datetime.now() - self.processed_dms = set() + try: + self.api = TwitterAPI() + self.response_callback = response_callback + self.last_mention_time = datetime.now() + self.processed_dms = set() + logger.info("TwitterBot initialized successfully") + except Exception as e: + logger.error( + f"Failed to initialize TwitterBot: {str(e)}", + exc_info=True, + ) + raise def handle_mentions(self) -> None: """Process and respond to new mentions.""" - mentions = self.api.get_mentions() - if not mentions: - return + try: + logger.info("Checking for new mentions") + mentions = self.api.get_mentions() + + if not mentions: + logger.debug("No mentions to process") + return + + for mention in mentions: + try: + # Skip if we've already processed this mention + if ( + hasattr(mention, "id") + and str(mention.id) in self.processed_dms + ): + logger.debug( + f"Skipping already processed mention {mention.id}" + ) + continue + + mention_time = mention.created_at + logger.debug( + f"Processing mention {mention.id} from {mention_time}" + ) + + if mention_time > self.last_mention_time: + logger.info( + f"New mention found: {mention.id}" + ) + logger.debug(f"Mention text: {mention.text}") + + response = self.response_callback( + mention.text + ) + logger.debug( + f"Generated response: {response}" + ) + + if response: + success = self.api.reply_to_tweet( + mention.id, response + ) + if success: + logger.info( + f"Successfully replied to mention {mention.id}" + ) + self.last_mention_time = mention_time + self.processed_dms.add( + str(mention.id) + ) + else: + logger.error( + f"Failed to reply to mention {mention.id}" + ) + else: + logger.warning( + f"Empty response generated for mention {mention.id}" + ) + + except Exception as e: + logger.error( + f"Error processing mention {mention.id}: {str(e)}", + exc_info=True, + ) - for mention in mentions: - mention_time = mention.created_at - if mention_time > self.last_mention_time: - response = self.response_callback(mention.text) - self.api.reply_to_tweet(mention.id, response) - self.last_mention_time = mention_time + except Exception: + logger.error("Error in handle_mentions", exc_info=True) def handle_dms(self) -> None: """Process and respond to new DMs."""