Refactor news & social modules
- update NewsAPIsTool - update SocialAPIsTool - add tests for NewsAPIsTool - added some missing docs
This commit is contained in:
@@ -2,7 +2,7 @@ from agno.tools import Toolkit
|
||||
from app.utils.wrapper_handler import WrapperHandler
|
||||
from .base import NewsWrapper, Article
|
||||
from .news_api import NewsApiWrapper
|
||||
from .gnews_api import GoogleNewsWrapper
|
||||
from .googlenews import GoogleNewsWrapper
|
||||
from .cryptopanic_api import CryptoPanicWrapper
|
||||
from .duckduckgo import DuckDuckGoWrapper
|
||||
|
||||
@@ -24,6 +24,15 @@ class NewsAPIsTool(NewsWrapper, Toolkit):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the NewsAPIsTool with multiple news API wrappers.
|
||||
The tool uses WrapperHandler to manage and invoke the different news API wrappers.
|
||||
The following wrappers are included in this order:
|
||||
- GoogleNewsWrapper.
|
||||
- DuckDuckGoWrapper.
|
||||
- NewsApiWrapper.
|
||||
- CryptoPanicWrapper.
|
||||
"""
|
||||
wrappers = [GoogleNewsWrapper, DuckDuckGoWrapper, NewsApiWrapper, CryptoPanicWrapper]
|
||||
self.wrapper_handler: WrapperHandler[NewsWrapper] = WrapperHandler.build_wrappers(wrappers)
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .base import SocialWrapper
|
||||
52
src/app/social/__init__.py
Normal file
52
src/app/social/__init__.py
Normal file
@@ -0,0 +1,52 @@
|
||||
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.
|
||||
This class supports retrieving top crypto-related posts by querying multiple sources:
|
||||
- RedditWrapper
|
||||
|
||||
By default, it returns results from the first successful wrapper.
|
||||
Optionally, it can be configured to collect posts from all wrappers.
|
||||
If no wrapper succeeds, an exception is raised.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the SocialAPIsTool with multiple social media API wrappers.
|
||||
The tool uses WrapperHandler to manage and invoke the different social media API wrappers.
|
||||
The following wrappers are included in this order:
|
||||
- RedditWrapper.
|
||||
"""
|
||||
|
||||
wrappers = [RedditWrapper]
|
||||
self.wrapper_handler: WrapperHandler[SocialWrapper] = WrapperHandler(wrappers)
|
||||
|
||||
Toolkit.__init__(
|
||||
self,
|
||||
name="Socials Toolkit",
|
||||
tools=[
|
||||
self.get_top_crypto_posts,
|
||||
],
|
||||
)
|
||||
|
||||
# TODO Pensare se ha senso restituire i post da TUTTI i wrapper o solo dal primo che funziona
|
||||
# la modifica è banale, basta usare try_call_all invece di try_call
|
||||
def get_top_crypto_posts(self, limit:int = 5) -> list[SocialPost]:
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
@@ -7,16 +7,23 @@ class SocialPost(BaseModel):
|
||||
description: str = ""
|
||||
comments: list["SocialComment"] = []
|
||||
|
||||
def __str__(self):
|
||||
return f"Title: {self.title}\nDescription: {self.description}\nComments: {len(self.comments)}\n[{" | ".join(str(c) for c in self.comments)}]"
|
||||
|
||||
class SocialComment(BaseModel):
|
||||
time: str = ""
|
||||
description: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"Time: {self.time}\nDescription: {self.description}"
|
||||
|
||||
# TODO IMPLEMENTARLO SE SI USANO PIU' WRAPPER (E QUINDI PIU' SOCIAL)
|
||||
class SocialWrapper:
|
||||
pass
|
||||
"""
|
||||
Base class for social media API wrappers.
|
||||
All social media API wrappers should inherit from this class and implement the methods.
|
||||
"""
|
||||
def get_top_crypto_posts(self, limit: int = 5) -> list[SocialPost]:
|
||||
"""
|
||||
Get top cryptocurrency-related posts, optionally limited by total.
|
||||
Args:
|
||||
limit (int): The maximum number of posts to return.
|
||||
Returns:
|
||||
list[SocialPost]: A list of SocialPost objects.
|
||||
"""
|
||||
raise NotImplementedError("This method should be overridden by subclasses")
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ def pytest_configure(config:pytest.Config):
|
||||
("social", "marks tests that use social media"),
|
||||
("limited", "marks tests that have limited execution due to API constraints"),
|
||||
("wrapper", "marks tests for wrapper handler"),
|
||||
("tools", "marks tests for tools"),
|
||||
]
|
||||
for marker in markers:
|
||||
line = f"{marker[0]}: {marker[1]}"
|
||||
|
||||
54
tests/tools/test_news_tool.py
Normal file
54
tests/tools/test_news_tool.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
from app.news import NewsAPIsTool
|
||||
|
||||
|
||||
@pytest.mark.limited
|
||||
@pytest.mark.tools
|
||||
@pytest.mark.news
|
||||
@pytest.mark.api
|
||||
class TestNewsAPITool:
|
||||
def test_news_api_tool(self):
|
||||
tool = NewsAPIsTool()
|
||||
assert tool is not None
|
||||
|
||||
def test_news_api_tool_get_top(self):
|
||||
tool = NewsAPIsTool()
|
||||
result = tool.wrapper_handler.try_call(lambda w: w.get_top_headlines(total=2))
|
||||
assert isinstance(result, list)
|
||||
assert len(result) > 0
|
||||
for article in result:
|
||||
assert article.title is not None
|
||||
assert article.source is not None
|
||||
|
||||
def test_news_api_tool_get_latest(self):
|
||||
tool = NewsAPIsTool()
|
||||
result = tool.wrapper_handler.try_call(lambda w: w.get_latest_news(query="crypto", total=2))
|
||||
assert isinstance(result, list)
|
||||
assert len(result) > 0
|
||||
for article in result:
|
||||
assert article.title is not None
|
||||
assert article.source is not None
|
||||
|
||||
def test_news_api_tool_get_top__all_results(self):
|
||||
tool = NewsAPIsTool()
|
||||
result = tool.wrapper_handler.try_call_all(lambda w: w.get_top_headlines(total=2))
|
||||
assert isinstance(result, dict)
|
||||
assert len(result.keys()) > 0
|
||||
print("Results from providers:", result.keys())
|
||||
for provider, articles in result.items():
|
||||
for article in articles:
|
||||
print(provider, article.title)
|
||||
assert article.title is not None
|
||||
assert article.source is not None
|
||||
|
||||
def test_news_api_tool_get_latest__all_results(self):
|
||||
tool = NewsAPIsTool()
|
||||
result = tool.wrapper_handler.try_call_all(lambda w: w.get_latest_news(query="crypto", total=2))
|
||||
assert isinstance(result, dict)
|
||||
assert len(result.keys()) > 0
|
||||
print("Results from providers:", result.keys())
|
||||
for provider, articles in result.items():
|
||||
for article in articles:
|
||||
print(provider, article.title)
|
||||
assert article.title is not None
|
||||
assert article.source is not None
|
||||
Reference in New Issue
Block a user