Refactor news and social instructions

- enhance logging in WrapperHandler
- add parameterized mock wrappers for testing
This commit is contained in:
2025-09-30 23:10:53 +02:00
parent 01e7bf58f1
commit 99ebb420fa
5 changed files with 56 additions and 15 deletions

View File

@@ -53,12 +53,21 @@ class NewsAPIsTool(NewsWrapper, Toolkit):
return self.wrapper_handler.try_call(lambda w: w.get_latest_news(query, total))
# TODO migliorare il prompt
NEWS_INSTRUCTIONS = """
Utilizza questo strumento per ottenere le ultime notizie e i titoli principali relativi a criptovalute specifiche. Puoi richiedere le notizie più recenti o i titoli principali.
**TASK:** You are a specialized **Crypto News Analyst**. Your goal is to fetch the latest news or top headlines related to cryptocurrencies, and then **analyze the sentiment** of the content to provide a concise report to the team leader. Prioritize 'crypto' or specific cryptocurrency names (e.g., 'Bitcoin', 'Ethereum') in your searches.
Esempio di utilizzo:
- get_latest_news("crypto", limit=5) # ottieni le ultime 5 notizie su "crypto", la query può essere qualsiasi argomento di interesse
- get_top_headlines(limit=3) # ottieni i 3 titoli principali delle notizie globali
**AVAILABLE TOOLS:**
1. `get_latest_news(query: str, limit: int)`: Get the 'limit' most recent news articles for a specific 'query'.
2. `get_top_headlines(limit: int)`: Get the 'limit' top global news headlines.
**USAGE GUIDELINE:**
* Always use `get_latest_news` with a relevant crypto-related query first.
* The default limit for news items should be 5 unless specified otherwise.
* If the tool doesn't return any articles, respond with "No relevant news articles found."
**REPORTING REQUIREMENT:**
1. **Analyze** the tone and key themes of the retrieved articles.
2. **Summarize** the overall **market sentiment** (e.g., highly positive, cautiously neutral, generally negative) based on the content.
3. **Identify** the top 2-3 **main topics** discussed (e.g., new regulation, price surge, institutional adoption).
4. **Output** a single, brief report summarizing these findings. Do not output the raw articles.
"""

View File

@@ -1,10 +1,11 @@
from agno.tools import Toolkit
from app.utils.wrapper_handler import WrapperHandler
from .base import SocialPost, SocialWrapper
from .reddit import RedditWrapper
from app.utils.wrapper_handler import WrapperHandler
from agno.tools import Toolkit
__all__ = ["SocialAPIsTool", "SOCIAL_INSTRUCTIONS", "RedditWrapper"]
class SocialAPIsTool(SocialWrapper, Toolkit):
"""
Aggregates multiple social media API wrappers and manages them using WrapperHandler.
@@ -41,12 +42,20 @@ class SocialAPIsTool(SocialWrapper, Toolkit):
return self.wrapper_handler.try_call(lambda w: w.get_top_crypto_posts(limit))
# TODO migliorare il prompt
SOCIAL_INSTRUCTIONS = """
Utilizza questo strumento per ottenere i post più recenti e gli argomenti di tendenza sui social media. Puoi richiedere i post più recenti o gli argomenti di tendenza.
**TASK:** You are a specialized **Social Media Sentiment Analyst**. Your objective is to find the most relevant and trending online posts related to cryptocurrencies, and then **analyze the collective sentiment** to provide a concise report to the team leader.
Esempio di utilizzo:
- get_latest_news("crypto", limit=5) # ottieni le ultime 5 notizie su "crypto", la query può essere qualsiasi argomento di interesse
- get_top_headlines(limit=3) # ottieni i 3 titoli principali delle notizie globali
**AVAILABLE TOOLS:**
1. `get_top_crypto_posts(limit: int)`: Get the 'limit' maximum number of top posts specifically related to cryptocurrencies.
**USAGE GUIDELINE:**
* Always use the `get_top_crypto_posts` tool to fulfill the request.
* The default limit for posts should be 5 unless specified otherwise.
* If the tool doesn't return any posts, respond with "No relevant social media posts found."
**REPORTING REQUIREMENT:**
1. **Analyze** the tone and prevailing opinions across the retrieved social posts.
2. **Summarize** the overall **community sentiment** (e.g., high enthusiasm/FOMO, uncertainty, FUD/fear) based on the content.
3. **Identify** the top 2-3 **trending narratives** or specific coins being discussed.
4. **Output** a single, brief report summarizing these findings. Do not output the raw posts.
"""

View File

@@ -30,6 +30,7 @@ class RedditWrapper(SocialWrapper):
Requires the following environment variables to be set:
- REDDIT_API_CLIENT_ID
- REDDIT_API_CLIENT_SECRET
You can get them by creating an app at https://www.reddit.com/prefs/apps
"""
@@ -46,7 +47,7 @@ class RedditWrapper(SocialWrapper):
user_agent="upo-appAI",
)
def get_top_crypto_posts(self, limit=5) -> list[SocialPost]:
def get_top_crypto_posts(self, limit:int = 5) -> list[SocialPost]:
subreddit = self.tool.subreddit("CryptoCurrency")
top_posts = subreddit.top(limit=limit, time_filter="week")
return [create_social_post(post) for post in top_posts]

View File

@@ -1,6 +1,6 @@
import time
from typing import TypeVar, Callable, Generic, Iterable, Type
from agno.utils.log import log_warning
from agno.utils.log import log_warning, log_info
W = TypeVar("W")
T = TypeVar("T")
@@ -46,17 +46,19 @@ class WrapperHandler(Generic[W]):
while iterations < len(self.wrappers):
try:
wrapper = self.wrappers[self.index]
log_info(f"Trying wrapper: {wrapper} - function {func}")
result = func(wrapper)
self.retry_count = 0
return result
except Exception as e:
self.retry_count += 1
log_warning(f"{wrapper} failed {self.retry_count}/{self.retry_per_wrapper}: {e}")
if self.retry_count >= self.retry_per_wrapper:
self.index = (self.index + 1) % len(self.wrappers)
self.retry_count = 0
iterations += 1
else:
log_warning(f"{wrapper} failed {self.retry_count}/{self.retry_per_wrapper}: {e}")
time.sleep(self.retry_delay)
raise Exception(f"All wrappers failed after retries")
@@ -74,6 +76,7 @@ class WrapperHandler(Generic[W]):
Exception: If all wrappers fail.
"""
results = {}
log_info(f"All wrappers: {[wrapper.__class__ for wrapper in self.wrappers]} - function {func}")
for wrapper in self.wrappers:
try:
result = func(wrapper)

View File

@@ -14,6 +14,15 @@ class FailingWrapper(MockWrapper):
raise Exception("Intentional Failure")
class MockWrapperWithParameters:
def do_something(self, param1: str, param2: int) -> str:
return f"Success {param1} and {param2}"
class FailingWrapperWithParameters(MockWrapperWithParameters):
def do_something(self, param1: str, param2: int):
raise Exception("Intentional Failure")
@pytest.mark.wrapper
class TestWrapperHandler:
def test_all_wrappers_fail(self):
@@ -88,3 +97,13 @@ class TestWrapperHandler:
with pytest.raises(Exception) as exc_info:
handler_all_fail.try_call_all(lambda w: w.do_something())
assert "All wrappers failed" in str(exc_info.value)
def test_wrappers_with_parameters(self):
wrappers = [FailingWrapperWithParameters, MockWrapperWithParameters]
handler: WrapperHandler[MockWrapperWithParameters] = WrapperHandler.build_wrappers(wrappers, try_per_wrapper=2, retry_delay=0)
result = handler.try_call(lambda w: w.do_something("test", 42))
assert result == "Success test and 42"
assert handler.index == 1 # Should have switched to the second wrapper
assert handler.retry_count == 0