prabalGaur commited on
Commit
cf73b4f
·
verified ·
1 Parent(s): eb1355a

Upload community_contributions/chrys/agents/sentiment_agent.py with huggingface_hub

Browse files
community_contributions/chrys/agents/sentiment_agent.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SENTIMENT_AGENT: fetch news and score sentiment via LLM."""
2
+ import json
3
+ import os
4
+ from typing import List
5
+
6
+ import requests
7
+ from agents.base import AgentBase
8
+ from models import SentimentResult, TechResult
9
+
10
+ NEWS_API_URL = "https://newsapi.org/v2/everything"
11
+
12
+
13
+ def _fetch_news(asset: str, api_key: str, max_articles: int = 5) -> List[str]:
14
+ if not api_key:
15
+ return []
16
+ try:
17
+ # Map symbol to search term
18
+ q = asset.replace("/", " ").replace("USD", "") + " market"
19
+ r = requests.get(
20
+ NEWS_API_URL,
21
+ params={
22
+ "q": q,
23
+ "apiKey": api_key,
24
+ "sortBy": "publishedAt",
25
+ "pageSize": max_articles,
26
+ "language": "en",
27
+ },
28
+ timeout=10,
29
+ )
30
+ r.raise_for_status()
31
+ data = r.json()
32
+ articles = data.get("articles", [])[:max_articles]
33
+ return [a.get("title", "") for a in articles if a.get("title")]
34
+ except Exception:
35
+ return []
36
+
37
+
38
+ def _llm_sentiment(headlines: List[str], model: str, api_key: str = "") -> tuple[str, int, str]:
39
+ if not headlines:
40
+ return "MIXED", 0, "No headlines available."
41
+ try:
42
+ from litellm import completion
43
+ prompt = (
44
+ "Given these news headlines about a financial asset, determine the dominant sentiment.\n"
45
+ "Reply with exactly this JSON (no markdown): {\"sentiment\": \"POSITIVE\" or \"NEGATIVE\" or \"MIXED\", "
46
+ "\"sentiment_score\": 1 or 0 or -1, \"narrative\": \"one short sentence summarizing the key narrative\"}\n\n"
47
+ "Headlines:\n" + "\n".join(f"- {h}" for h in headlines[:5])
48
+ )
49
+ kwargs = {"model": model, "messages": [{"role": "user", "content": prompt}]}
50
+ if api_key and model.startswith("openrouter/"):
51
+ kwargs["api_key"] = api_key
52
+ resp = completion(**kwargs)
53
+ text = resp.choices[0].message.content.strip()
54
+ if "```" in text:
55
+ text = text.split("```")[1].replace("json", "").strip()
56
+ data = json.loads(text)
57
+ sent = data.get("sentiment", "MIXED").upper()
58
+ if "POSITIVE" in sent:
59
+ sent, score = "POSITIVE", 1
60
+ elif "NEGATIVE" in sent:
61
+ sent, score = "NEGATIVE", -1
62
+ else:
63
+ sent, score = "MIXED", 0
64
+ narrative = data.get("narrative", "")[:200]
65
+ return sent, score, narrative
66
+ except Exception:
67
+ return "MIXED", 0, "Sentiment could not be determined."
68
+
69
+
70
+ class SentimentAgent(AgentBase):
71
+ name = "SENTIMENT_AGENT"
72
+ logger_name = "aria.sentiment_agent"
73
+
74
+ def __init__(self, newsapi_key: str = "", model: str = "openrouter/openai/gpt-5-nano", openrouter_key: str = ""):
75
+ super().__init__()
76
+ self.newsapi_key = newsapi_key or os.getenv("NEWSAPI_KEY", "")
77
+ self.model = model
78
+ self.openrouter_key = openrouter_key or os.getenv("OPENROUTER_API_KEY", "")
79
+
80
+ def run(self, tech_results: List[TechResult], assets_for_sentiment: List[str]) -> List[SentimentResult]:
81
+ self.log("Running sentiment analysis for non-neutral assets")
82
+ results: List[SentimentResult] = []
83
+ for tr in tech_results:
84
+ if tr.asset not in assets_for_sentiment:
85
+ continue
86
+ headlines = _fetch_news(tr.asset, self.newsapi_key)
87
+ sentiment, score, narrative = _llm_sentiment(headlines, self.model, self.openrouter_key)
88
+ results.append(SentimentResult(
89
+ asset=tr.asset,
90
+ headlines=headlines,
91
+ sentiment=sentiment,
92
+ sentiment_score=score,
93
+ narrative=narrative,
94
+ ))
95
+ self.log(f"{tr.asset}: {sentiment} (score={score})")
96
+ self.log(f"Sentiment complete: {len(results)} assets")
97
+ return results