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

update sentiment_tool.py

Browse files
Files changed (1) hide show
  1. tools/sentiment_tool.py +90 -62
tools/sentiment_tool.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import requests
3
  from crewai.tools import BaseTool
@@ -5,6 +7,9 @@ from openai import OpenAI
5
  from typing import Type
6
  from pydantic import BaseModel, Field
7
 
 
 
 
8
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
9
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
10
 
@@ -14,8 +19,7 @@ client = OpenAI(api_key=OPENAI_API_KEY)
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
@@ -23,110 +27,134 @@ class SentimentInput(BaseModel):
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
  }
 
1
+ # tools/sentiment_tool.py
2
+
3
  import os
4
  import requests
5
  from crewai.tools import BaseTool
 
7
  from typing import Type
8
  from pydantic import BaseModel, Field
9
 
10
+ # -------------------------
11
+ # Environment Variables
12
+ # -------------------------
13
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
14
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
15
 
 
19
  # Input Schema
20
  # -------------------------
21
  class SentimentInput(BaseModel):
22
+ query: str = Field(default="bitcoin", description="Cryptocurrency name to analyze.")
 
23
 
24
  # -------------------------
25
  # Sentiment Tool
 
27
  class SentimentTool(BaseTool):
28
  name: str = "get_crypto_sentiment"
29
  description: str = (
30
+ "Fetches cryptocurrency sentiment using Serper (news + Reddit), "
31
+ "then uses GPT-4.1 to classify sentiment as bullish, bearish, or neutral."
 
32
  )
33
  args_schema: Type[BaseModel] = SentimentInput
34
 
 
 
 
35
  def _run(self, query: str = "bitcoin") -> dict:
36
+ headers = {
37
+ "X-API-KEY": SERPER_API_KEY,
38
+ "Content-Type": "application/json"
39
+ }
40
+
41
+ # ----------------------------------------
42
+ # 1) FETCH NEWS USING SERPER
43
+ # ----------------------------------------
44
  news_headlines = []
 
45
  news_error = None
 
 
 
 
 
46
  try:
47
+ news_payload = {
48
+ "q": f"{query} cryptocurrency",
49
+ "num": 5
50
+ }
51
 
52
+ news_res = requests.post(
53
+ "https://google.serper.dev/news",
54
+ headers=headers,
55
+ json=news_payload,
56
+ timeout=10
57
+ )
58
+ news_res.raise_for_status()
59
 
60
+ news_json = news_res.json()
61
+ news_items = news_json.get("news", [])
62
+ news_headlines = [item.get("title") for item in news_items if "title" in item]
63
 
64
  except Exception as e:
65
  news_error = str(e)
66
 
67
+ # ----------------------------------------
68
+ # 2) FETCH REDDIT POSTS USING SERPER (CORRECT ENDPOINT)
69
+ # ----------------------------------------
70
+ reddit_titles = []
71
+ reddit_error = None
72
+
73
  try:
74
+ reddit_payload = {
75
+ "q": query,
76
+ "num": 5,
77
+ "reddit": True # <-- THIS IS THE CORRECT WAY TO FILTER REDDIT RESULTS
 
 
 
 
 
 
 
 
 
78
  }
79
 
80
+ reddit_res = requests.post(
81
+ "https://google.serper.dev/search",
82
+ headers=headers,
83
+ json=reddit_payload,
84
+ timeout=10
85
+ )
86
+ reddit_res.raise_for_status()
87
 
88
+ reddit_json = reddit_res.json()
89
+ reddit_items = reddit_json.get("reddit", [])
90
+
91
+ reddit_titles = [
92
+ item.get("title")
93
+ for item in reddit_items
94
+ if "title" in item
95
+ ]
96
 
97
  except Exception as e:
98
  reddit_error = str(e)
99
 
100
+ # ----------------------------------------
101
+ # 3) COMBINE TEXT FOR SENTIMENT CLASSIFICATION
102
+ # ----------------------------------------
103
+ combined_text = (
104
+ "News Headlines:\n" + "\n".join(news_headlines) +
105
+ "\n\nReddit Posts:\n" + "\n".join(reddit_titles)
106
+ )
107
+
108
+ # ----------------------------------------
109
+ # 4) GPT-4.1 SENTIMENT INTERPRETATION
110
+ # ----------------------------------------
111
  sentiment_prompt = f"""
112
  You are a cryptocurrency sentiment analyst.
113
 
114
+ Classify sentiment toward **{query}** based on NEWS and REDDIT content.
 
 
 
 
 
115
 
116
+ Return ONLY valid JSON using this format:
117
 
118
  {{
119
+ "sentiment": "bullish" | "bearish" | "neutral",
120
+ "reasoning": "short explanation",
121
  "news_headlines": [...],
122
  "reddit_titles": [...],
123
+ "news_error": "... or null",
124
+ "reddit_error": "... or null"
125
  }}
126
+
127
+ Content to analyze:
128
+ {combined_text}
129
  """
130
 
131
  try:
132
  completion = client.chat.completions.create(
133
  model="gpt-4.1",
134
  messages=[
135
+ {"role": "system", "content": "You extract structured sentiment from crypto news and Reddit posts."},
136
  {"role": "user", "content": sentiment_prompt}
137
+ ],
138
+ temperature=0.2
139
  )
140
 
141
+ # GPT returns JSON string → CrewAI needs dict
142
+ result_text = completion.choices[0].message.content
143
+ import json
144
+ parsed = json.loads(result_text)
145
+
146
+ # Ensure errors included even if GPT forgets them
147
+ parsed["news_error"] = news_error
148
+ parsed["reddit_error"] = reddit_error
149
 
150
+ return parsed
 
151
 
152
  except Exception as e:
153
  return {
154
+ "sentiment": "unknown",
155
+ "reasoning": "GPT sentiment classification failed.",
156
  "news_headlines": news_headlines,
157
  "reddit_titles": reddit_titles,
158
  "news_error": news_error,
159
+ "reddit_error": str(e)
160
  }