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 app.utils.wrapper_handler import WrapperHandler
|
||||||
from .base import NewsWrapper, Article
|
from .base import NewsWrapper, Article
|
||||||
from .news_api import NewsApiWrapper
|
from .news_api import NewsApiWrapper
|
||||||
from .gnews_api import GoogleNewsWrapper
|
from .googlenews import GoogleNewsWrapper
|
||||||
from .cryptopanic_api import CryptoPanicWrapper
|
from .cryptopanic_api import CryptoPanicWrapper
|
||||||
from .duckduckgo import DuckDuckGoWrapper
|
from .duckduckgo import DuckDuckGoWrapper
|
||||||
|
|
||||||
@@ -24,6 +24,15 @@ class NewsAPIsTool(NewsWrapper, Toolkit):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
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]
|
wrappers = [GoogleNewsWrapper, DuckDuckGoWrapper, NewsApiWrapper, CryptoPanicWrapper]
|
||||||
self.wrapper_handler: WrapperHandler[NewsWrapper] = WrapperHandler.build_wrappers(wrappers)
|
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 = ""
|
description: str = ""
|
||||||
comments: list["SocialComment"] = []
|
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):
|
class SocialComment(BaseModel):
|
||||||
time: str = ""
|
time: str = ""
|
||||||
description: 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:
|
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"),
|
("social", "marks tests that use social media"),
|
||||||
("limited", "marks tests that have limited execution due to API constraints"),
|
("limited", "marks tests that have limited execution due to API constraints"),
|
||||||
("wrapper", "marks tests for wrapper handler"),
|
("wrapper", "marks tests for wrapper handler"),
|
||||||
|
("tools", "marks tests for tools"),
|
||||||
]
|
]
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
line = f"{marker[0]}: {marker[1]}"
|
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