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

update sentiment_tool.py

Browse files
Files changed (1) hide show
  1. tools/sentiment_tool.py +53 -64
tools/sentiment_tool.py CHANGED
@@ -1,5 +1,3 @@
1
- # tools/sentiment_tool.py
2
-
3
  import os
4
  import requests
5
  from crewai.tools import BaseTool
@@ -7,47 +5,41 @@ from openai import OpenAI
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
 
16
  client = OpenAI(api_key=OPENAI_API_KEY)
17
 
18
- # -------------------------
19
- # Input Schema
20
- # -------------------------
21
  class SentimentInput(BaseModel):
22
- query: str = Field(default="bitcoin", description="Cryptocurrency name to analyze.")
23
 
24
- # -------------------------
25
- # Sentiment Tool
26
- # -------------------------
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",
@@ -57,24 +49,28 @@ class SentimentTool(BaseTool):
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(
@@ -86,45 +82,45 @@ class SentimentTool(BaseTool):
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
 
@@ -132,29 +128,22 @@ Content to analyze:
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
  }
 
 
 
1
  import os
2
  import requests
3
  from crewai.tools import BaseTool
 
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
 
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
+ # SENTIMENT TOOL
21
+ # ------------------------
22
  class SentimentTool(BaseTool):
23
  name: str = "get_crypto_sentiment"
24
  description: str = (
25
+ "Fetches recent cryptocurrency news and Reddit discussions using Serper.dev, "
26
+ "then performs sentiment analysis using OpenAI GPT. Returns structured JSON."
27
  )
28
+ arg_schema: Type[BaseModel] = SentimentInput
 
 
 
 
 
 
29
 
30
+ def _run(self, query: str = "bitcoin") -> str:
31
+ # ============================
32
+ # 1) FETCH NEWS VIA SERPER
33
+ # ============================
34
  news_headlines = []
35
  news_error = None
36
+
37
  try:
38
  news_payload = {
39
+ "q": f"{query} crypto news",
40
+ "num": 10
41
  }
42
+ headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
43
 
44
  news_res = requests.post(
45
  "https://google.serper.dev/news",
 
49
  )
50
  news_res.raise_for_status()
51
 
52
+ news_json = news_res.json().get("news", [])
53
+ news_headlines = [n.get("title") for n in news_json if n.get("title")]
54
+ news_headlines = news_headlines[:10]
55
 
56
  except Exception as e:
57
  news_error = str(e)
58
 
59
+ # ============================
60
+ # 2) FORCED REDDIT SCRAPING (RELIABLE)
61
+ # ============================
62
  reddit_titles = []
63
  reddit_error = None
64
 
65
  try:
66
+ # Serper search that *forces* Reddit results
67
  reddit_payload = {
68
+ "q": (
69
+ f"site:reddit.com/r/cryptocurrency OR "
70
+ f"site:reddit.com/r/{query} "
71
+ f"{query} discussion latest"
72
+ ),
73
+ "num": 10
74
  }
75
 
76
  reddit_res = requests.post(
 
82
  reddit_res.raise_for_status()
83
 
84
  reddit_json = reddit_res.json()
85
+ organic_results = reddit_json.get("organic", [])
86
 
87
  reddit_titles = [
88
  item.get("title")
89
+ for item in organic_results
90
+ if "reddit.com" in item.get("link", "")
91
  ]
92
 
93
+ reddit_titles = reddit_titles[:5]
94
+
95
  except Exception as e:
96
  reddit_error = str(e)
97
 
98
+ # ============================
99
+ # 3) SENTIMENT ANALYSIS
100
+ # ============================
101
  combined_text = (
102
  "News Headlines:\n" + "\n".join(news_headlines) +
103
  "\n\nReddit Posts:\n" + "\n".join(reddit_titles)
104
  )
105
 
 
 
 
106
  sentiment_prompt = f"""
107
  You are a cryptocurrency sentiment analyst.
108
 
109
+ Based on the following combined news headlines and Reddit discussions, classify the overall sentiment toward "{query}" as **bullish**, **bearish**, or **neutral**.
110
 
111
+ Return only valid JSON in this format:
112
 
113
  {{
114
+ "sentiment": "bullish/bearish/neutral",
115
  "reasoning": "short explanation",
116
  "news_headlines": [...],
117
  "reddit_titles": [...],
118
+ "news_error": null or string,
119
+ "reddit_error": null or string
120
  }}
121
 
122
+ CONTENT TO ANALYSE:
123
+ -------------------
124
  {combined_text}
125
  """
126
 
 
128
  completion = client.chat.completions.create(
129
  model="gpt-4.1",
130
  messages=[
131
+ {"role": "system", "content": "You are a precise sentiment classifier. Respond only with JSON."},
132
  {"role": "user", "content": sentiment_prompt}
133
  ],
134
  temperature=0.2
135
  )
136
+ sentiment_json = completion.choices[0].message.content
137
+ return sentiment_json
 
 
 
 
 
 
 
 
 
138
 
139
  except Exception as e:
140
+ # Return structured failure JSON for debugging
141
  return {
142
  "sentiment": "unknown",
143
+ "reasoning": "LLM sentiment analysis failed.",
144
  "news_headlines": news_headlines,
145
  "reddit_titles": reddit_titles,
146
  "news_error": news_error,
147
+ "reddit_error": reddit_error,
148
+ "llm_error": str(e)
149
  }