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

update sentiment_tool.py

Browse files
Files changed (1) hide show
  1. tools/sentiment_tool.py +85 -91
tools/sentiment_tool.py CHANGED
@@ -5,136 +5,130 @@ from openai import OpenAI
5
  from typing import Type
6
  from pydantic import BaseModel, Field
7
 
8
- # Environment variables
9
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
10
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
11
 
12
  client = OpenAI(api_key=OPENAI_API_KEY)
13
 
14
-
15
- # -----------------------------
16
- # Pydantic Input Schema
17
- # -----------------------------
18
  class SentimentInput(BaseModel):
19
- query: str = Field(
20
- default="bitcoin",
21
- description="Cryptocurrency to evaluate sentiment for."
22
- )
23
 
24
-
25
- # -----------------------------
26
- # Sentiment Tool (Serper-only)
27
- # -----------------------------
28
  class SentimentTool(BaseTool):
29
  name: str = "get_crypto_sentiment"
30
  description: str = (
31
- "Fetches recent cryptocurrency sentiment using Serper.dev, including "
32
- "Google News headlines and Reddit discussions, then classifies overall "
33
- "sentiment as bullish, bearish, or neutral."
 
34
  )
35
  args_schema: Type[BaseModel] = SentimentInput
36
 
37
- # -----------------------------
38
- # Helper: call Serper API safely
39
- # -----------------------------
40
- def _serper_post(self, endpoint: str, payload: dict):
41
- url = f"https://google.serper.dev/{endpoint}"
42
- headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
43
-
44
  try:
45
- response = requests.post(url, headers=headers, json=payload, timeout=10)
46
- response.raise_for_status()
47
- return response.json()
48
- except Exception as e:
49
- return {"error": str(e)}
50
-
51
- # -----------------------------
52
- # Main execution
53
- # -----------------------------
54
- def _run(self, query: str = "bitcoin"):
55
- try:
56
- # -------------------------
57
- # 1️⃣ Fetch Google News
58
- # -------------------------
59
- news_result = self._serper_post(
60
- "news",
61
- {"q": f"{query} crypto", "num": 5}
62
- )
63
-
64
  news_headlines = []
65
  news_error = None
 
 
 
 
66
 
67
- if "error" in news_result:
68
- news_error = news_result["error"]
69
- else:
70
- raw_news = news_result.get("news", [])
71
- news_headlines = [item.get("title", "") for item in raw_news[:5]]
72
-
73
- # -------------------------
74
- # 2️⃣ Fetch Reddit Discussions
75
- # -------------------------
76
- reddit_result = self._serper_post(
77
- "reddit",
78
- {"q": query, "num": 5}
79
- )
80
 
 
 
 
81
  reddit_titles = []
82
  reddit_error = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- if "error" in reddit_result:
85
- reddit_error = reddit_result["error"]
86
- else:
87
- raw_reddit = reddit_result.get("organic", [])
88
- reddit_titles = [item.get("title", "") for item in raw_reddit[:5]]
89
-
90
- # -------------------------
91
- # 3️⃣ Create LLM prompt
92
- # -------------------------
93
- combined_text = f"""
94
- News Headlines:
95
- {news_headlines}
96
-
97
- Reddit Posts:
98
- {reddit_titles}
99
-
100
- Errors:
101
- news_error={news_error}
102
- reddit_error={reddit_error}
103
- """
104
-
105
  sentiment_prompt = f"""
106
- You are a cryptocurrency sentiment analyst.
107
 
108
- Given the following news and Reddit discussions for "{query}", classify overall sentiment
109
- as **bullish**, **bearish**, or **neutral**.
 
 
 
110
 
111
- Return ONLY valid JSON in the exact structure below:
112
 
113
  {{
114
  "sentiment": "bullish/bearish/neutral",
115
- "reasoning": "Short explanation",
116
  "news_headlines": [...],
117
  "reddit_titles": [...],
118
- "news_error": "... or null",
119
- "reddit_error": "... or null"
120
  }}
121
 
122
- Here is the data to analyze:
 
123
  {combined_text}
124
  """
125
 
126
- # -------------------------
127
- # 4️⃣ Call OpenAI GPT-4.1
128
- # -------------------------
129
  completion = client.chat.completions.create(
130
  model="gpt-4.1",
131
  messages=[
132
- {"role": "system", "content": "You are a precise sentiment classifier."},
133
- {"role": "user", "content": sentiment_prompt}
134
- ]
 
135
  )
136
 
137
  return completion.choices[0].message.content
138
 
139
  except Exception as e:
140
- return {"error": f"SentimentTool failed: {str(e)}"}
 
 
 
 
 
 
 
 
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 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
+ }