cicboy commited on
Commit
fb0849f
·
1 Parent(s): 3421c83

update sentiment_tool.py

Browse files
Files changed (1) hide show
  1. tools/sentiment_tool.py +87 -89
tools/sentiment_tool.py CHANGED
@@ -11,124 +11,122 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
11
  client = OpenAI(api_key=OPENAI_API_KEY)
12
 
13
  # -------------------------
14
- # Input schema
15
  # -------------------------
16
  class SentimentInput(BaseModel):
17
- query: str = Field(default="bitcoin", description="Cryptocurrency to analyze sentiment for.")
 
18
 
19
  # -------------------------
20
- # Sentiment Tool (Option C)
21
  # -------------------------
22
  class SentimentTool(BaseTool):
23
  name: str = "get_crypto_sentiment"
24
  description: str = (
25
- "Fetch crypto sentiment using Serper Search only. "
26
- "News headlines come from Serper's News API. "
27
- "Reddit-like sentiment is extracted from URLs or snippets containing 'reddit.com'. "
28
- "Returns structured JSON: sentiment, reasoning, headlines, reddit_titles."
29
  )
30
  args_schema: Type[BaseModel] = SentimentInput
31
 
 
 
 
32
  def _run(self, query: str = "bitcoin") -> dict:
 
 
 
 
 
 
 
 
33
  try:
34
- # -----------------------------------------
35
- # Fetch NEWS via Serper News API
36
- # -----------------------------------------
37
- news_headlines = []
38
- news_error = None
39
- try:
40
- news_url = "https://google.serper.dev/news"
41
- headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
42
- payload = {"q": f"{query} crypto", "num": 5}
43
-
44
- news_response = requests.post(news_url, headers=headers, json=payload, timeout=10)
45
- news_response.raise_for_status()
46
-
47
- news_items = news_response.json().get("news", [])
48
- news_headlines = [n.get("title", "") for n in news_items[:5]]
49
-
50
- except Exception as e:
51
- news_error = str(e)
52
-
53
- # -----------------------------------------
54
- # Extract REDDIT-like content from Serper Search results
55
- # -----------------------------------------
56
- reddit_titles = []
57
- reddit_error = None
58
- try:
59
- search_url = "https://google.serper.dev/search"
60
- headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
61
- payload = {"q": f"{query} reddit crypto", "num": 8}
62
-
63
- search_response = requests.post(search_url, headers=headers, json=payload, timeout=10)
64
- search_response.raise_for_status()
65
- organic = search_response.json().get("organic", [])
66
-
67
- for item in organic:
68
- url = item.get("link", "")
69
- snippet = item.get("snippet", "")
70
- title = item.get("title", "")
71
-
72
- # Accept if URL or snippet mentions Reddit
73
- if "reddit.com" in url.lower() or "reddit" in snippet.lower():
74
- reddit_titles.append(title)
75
-
76
- except Exception as e:
77
- reddit_error = str(e)
78
-
79
- # -----------------------------------------
80
- # Build combined text for LLM classification
81
- # -----------------------------------------
82
- combined_text = (
83
- "News: " + " | ".join(news_headlines) +
84
- "\nReddit-like: " + " | ".join(reddit_titles)
85
- )
86
 
87
- # -----------------------------------------
88
- # LLM sentiment classification (JSON enforced)
89
- # -----------------------------------------
90
- sentiment_prompt = f"""
91
- You are a crypto sentiment analyst.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- Based on the following news headlines and Reddit-like search results,
94
- give an *overall sentiment classification* for "{query}" as:
95
- - bullish
96
- - bearish
97
- - neutral
98
 
99
- Respond ONLY in **valid JSON** using this schema:
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  {{
102
- "sentiment": "bullish/bearish/neutral",
103
- "reasoning": "short explanation summarizing why",
104
  "news_headlines": [...],
105
  "reddit_titles": [...],
106
- "news_error": null or error string,
107
- "reddit_error": null or error string
108
  }}
109
-
110
- Here is the text:
111
-
112
- {combined_text}
113
  """
114
 
 
115
  completion = client.chat.completions.create(
116
  model="gpt-4.1",
117
  messages=[
118
- {"role": "system", "content": "Return ONLY strict JSON. No commentary."},
119
- {"role": "user", "content": sentiment_prompt}
120
- ],
121
- temperature = 0.2
122
  )
123
 
124
- return completion.choices[0].message.content
 
 
 
125
 
126
  except Exception as e:
127
  return {
128
- "sentiment": "unknown",
129
- "reasoning": "Sentiment tool failed to run.",
130
- "news_headlines": [],
131
- "reddit_titles": [],
132
- "news_error": str(e),
133
- "reddit_error": str(e)
134
  }
 
11
  client = OpenAI(api_key=OPENAI_API_KEY)
12
 
13
  # -------------------------
14
+ # Input Schema
15
  # -------------------------
16
  class SentimentInput(BaseModel):
17
+ query: str = Field(default="bitcoin", description="Cryptocurrency name to evaluate sentiment for.")
18
+
19
 
20
  # -------------------------
21
+ # Sentiment Tool
22
  # -------------------------
23
  class SentimentTool(BaseTool):
24
  name: str = "get_crypto_sentiment"
25
  description: str = (
26
+ "Fetches cryptocurrency sentiment from Google News and Reddit "
27
+ "via Serper.dev, then classifies sentiment as bullish, bearish, or neutral "
28
+ "and returns structured JSON."
 
29
  )
30
  args_schema: Type[BaseModel] = SentimentInput
31
 
32
+ # -------------------------
33
+ # Internal execution
34
+ # -------------------------
35
  def _run(self, query: str = "bitcoin") -> dict:
36
+ news_headlines = []
37
+ reddit_titles = []
38
+ news_error = None
39
+ reddit_error = None
40
+
41
+ # -------------------------
42
+ # 1) Fetch Google News (Serper News API)
43
+ # -------------------------
44
  try:
45
+ news_url = "https://google.serper.dev/news"
46
+ headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
47
+ payload = {"q": f"{query} crypto", "num": 5}
48
+
49
+ resp = requests.post(news_url, headers=headers, json=payload, timeout=10)
50
+ resp.raise_for_status()
51
+
52
+ news_items = resp.json().get("news", [])
53
+ news_headlines = [item["title"] for item in news_items[:5]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ except Exception as e:
56
+ news_error = str(e)
57
+
58
+ # -------------------------
59
+ # 2) Fetch Reddit Discussions (Serper Search API)
60
+ # -------------------------
61
+ try:
62
+ reddit_url = "https://google.serper.dev/search"
63
+ headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
64
+
65
+ # OPTION C — hybrid search (most accurate)
66
+ payload = {
67
+ "q": (
68
+ f'"{query}" '
69
+ f'(site:reddit.com/r/CryptoCurrency OR '
70
+ f'site:reddit.com/r/{query} OR '
71
+ f'site:reddit.com/r/Bitcoin OR '
72
+ f'site:reddit.com)'
73
+ ),
74
+ "num": 5
75
+ }
76
+
77
+ resp = requests.post(reddit_url, headers=headers, json=payload, timeout=10)
78
+ resp.raise_for_status()
79
+
80
+ organic = resp.json().get("organic", [])
81
+ reddit_titles = [item["title"] for item in organic[:5]]
82
 
83
+ except Exception as e:
84
+ reddit_error = str(e)
 
 
 
85
 
86
+ # -------------------------
87
+ # 3) Sentiment Analysis (OpenAI)
88
+ # -------------------------
89
+ sentiment_prompt = f"""
90
+ You are a cryptocurrency sentiment analyst.
91
+
92
+ Based on the following data:
93
+ News Headlines: {news_headlines}
94
+ Reddit Titles: {reddit_titles}
95
+
96
+ Classify the overall sentiment toward "{query}" as bullish, bearish, or neutral.
97
+ Write a concise reasoning sentence.
98
+
99
+ Return ONLY valid JSON exactly in this format:
100
 
101
  {{
102
+ "sentiment": "<bullish/bearish/neutral>",
103
+ "reasoning": "<short explanation>",
104
  "news_headlines": [...],
105
  "reddit_titles": [...],
106
+ "news_error": "<error or null>",
107
+ "reddit_error": "<error or null>"
108
  }}
 
 
 
 
109
  """
110
 
111
+ try:
112
  completion = client.chat.completions.create(
113
  model="gpt-4.1",
114
  messages=[
115
+ {"role": "system", "content": "You return ONLY valid JSON, no prose."},
116
+ {"role": "user", "content": sentiment_prompt}
117
+ ]
 
118
  )
119
 
120
+ raw_output = completion.choices[0].message.content
121
+
122
+ # The model returns correct JSON; just forward it:
123
+ return raw_output
124
 
125
  except Exception as e:
126
  return {
127
+ "error": f"Sentiment classification failed: {str(e)}",
128
+ "news_headlines": news_headlines,
129
+ "reddit_titles": reddit_titles,
130
+ "news_error": news_error,
131
+ "reddit_error": reddit_error
 
132
  }