Skip to content

Commit

Permalink
Merge pull request #71 from ygarg25/develop
Browse files Browse the repository at this point in the history
added/feature: Portfolio News Agent
  • Loading branch information
LachsBagel authored Oct 8, 2024
2 parents 1014fdb + bbe3843 commit ef2a4ad
Show file tree
Hide file tree
Showing 11 changed files with 463 additions and 1 deletion.
20 changes: 20 additions & 0 deletions submodules/benchmarks/news_agent_benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -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`
Empty file.
38 changes: 38 additions & 0 deletions submodules/benchmarks/news_agent_benchmarks/benchmarks.py
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 14 additions & 0 deletions submodules/benchmarks/news_agent_benchmarks/config.py
Original file line number Diff line number Diff line change
@@ -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/"
48 changes: 48 additions & 0 deletions submodules/benchmarks/news_agent_benchmarks/helpers.py
Original file line number Diff line number Diff line change
@@ -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"
7 changes: 6 additions & 1 deletion submodules/moragents_dockers/agents/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ Werkzeug==2.2.2
web3==6.20.1
pymupdf==1.22.5
faiss-cpu==1.8.0.post1
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
selenium==4.25.0
torch
pytz
pyshorteners
7 changes: 7 additions & 0 deletions submodules/moragents_dockers/agents/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,12 @@ class Config:
"name": "realtime search 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,
}
]
}
Empty file.
130 changes: 130 additions & 0 deletions submodules/moragents_dockers/agents/src/news_agent/src/agent.py
Original file line number Diff line number Diff line change
@@ -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}
Loading

0 comments on commit ef2a4ad

Please sign in to comment.