Refactoring architetturale e spostamento classi base

- Eliminazione del file __init__.py obsoleto che importava ChatManager e Pipeline
- Spostamento della classe Pipeline in agents/pipeline.py
- Spostamento della classe ChatManager in utils/chat_manager.py
- Aggiornamento di __main__.py per importare da app.utils e app.agents, e modifica della logica per utilizzare Pipeline invece di chat per la selezione di provider e stile
- Creazione della cartella base con classi base comuni: markets.py (ProductInfo, Price, MarketWrapper), news.py (Article, NewsWrapper), social.py (SocialPost, SocialComment, SocialWrapper)
- Aggiornamento di tutti gli import nel progetto (markets/, news/, social/, utils/, tests/) per utilizzare la nuova struttura base/
This commit is contained in:
2025-10-04 21:20:21 +02:00
parent 6cd97b2864
commit f0193b94bb
26 changed files with 41 additions and 59 deletions

View File

@@ -1,4 +0,0 @@
from app.chat_manager import ChatManager
from app.pipeline import Pipeline
__all__ = ["ChatManager", "Pipeline"]

View File

@@ -1,19 +1,23 @@
import gradio as gr import gradio as gr
from agno.utils.log import log_info #type: ignore
from dotenv import load_dotenv from dotenv import load_dotenv
from app import ChatManager from agno.utils.log import log_info #type: ignore
from app.utils import ChatManager
from app.agents import Pipeline
if __name__ == "__main__": if __name__ == "__main__":
# Inizializzazioni # Inizializzazioni
load_dotenv() load_dotenv()
pipeline = Pipeline()
chat = ChatManager() chat = ChatManager()
######################################## ########################################
# Funzioni Gradio # Funzioni Gradio
######################################## ########################################
def respond(message: str, history: list[dict[str, str]]) -> tuple[list[dict[str, str]], list[dict[str, str]], str]: def respond(message: str, history: list[dict[str, str]]) -> tuple[list[dict[str, str]], list[dict[str, str]], str]:
response = chat.send_message(message) chat.send_message(message)
response = pipeline.interact(message)
chat.receive_message(response)
history.append({"role": "user", "content": message}) history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": response}) history.append({"role": "assistant", "content": response})
return history, history, "" return history, history, ""
@@ -42,18 +46,18 @@ if __name__ == "__main__":
# Dropdown provider e stile # Dropdown provider e stile
with gr.Row(): with gr.Row():
provider = gr.Dropdown( provider = gr.Dropdown(
choices=chat.list_providers(), choices=pipeline.list_providers(),
type="index", type="index",
label="Modello da usare" label="Modello da usare"
) )
provider.change(fn=chat.choose_provider, inputs=provider, outputs=None) provider.change(fn=pipeline.choose_predictor, inputs=provider, outputs=None)
style = gr.Dropdown( style = gr.Dropdown(
choices=chat.list_styles(), choices=pipeline.list_styles(),
type="index", type="index",
label="Stile di investimento" label="Stile di investimento"
) )
style.change(fn=chat.choose_style, inputs=style, outputs=None) style.change(fn=pipeline.choose_style, inputs=style, outputs=None)
chatbot = gr.Chatbot(label="Conversazione", height=500, type="messages") chatbot = gr.Chatbot(label="Conversazione", height=500, type="messages")
msg = gr.Textbox(label="Scrivi la tua richiesta", placeholder="Es: Quali sono le crypto interessanti oggi?") msg = gr.Textbox(label="Scrivi la tua richiesta", placeholder="Es: Quali sono le crypto interessanti oggi?")

View File

@@ -1,5 +1,6 @@
from app.agents.models import AppModels from app.agents.models import AppModels
from app.agents.predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS from app.agents.predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS
from app.agents.team import create_team_with from app.agents.team import create_team_with
from app.agents.pipeline import Pipeline
__all__ = ["AppModels", "PredictorInput", "PredictorOutput", "PredictorStyle", "PREDICTOR_INSTRUCTIONS", "create_team_with"] __all__ = ["AppModels", "PredictorInput", "PredictorOutput", "PredictorStyle", "PREDICTOR_INSTRUCTIONS", "create_team_with", "Pipeline"]

View File

@@ -1,8 +1,8 @@
from agno.run.agent import RunOutput from agno.run.agent import RunOutput
from app.agents import AppModels from app.agents.models import AppModels
from app.agents.team import create_team_with from app.agents.team import create_team_with
from app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle from app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle
from app.markets.base import ProductInfo from app.base.markets import ProductInfo
class Pipeline: class Pipeline:

View File

@@ -1,6 +1,6 @@
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.markets.base import ProductInfo from app.base.markets import ProductInfo
class PredictorStyle(Enum): class PredictorStyle(Enum):

0
src/app/base/__init__.py Normal file
View File

View File

@@ -1,5 +1,5 @@
from agno.tools import Toolkit from agno.tools import Toolkit
from app.markets.base import MarketWrapper, Price, ProductInfo from app.base.markets import MarketWrapper, Price, ProductInfo
from app.markets.binance import BinanceWrapper from app.markets.binance import BinanceWrapper
from app.markets.coinbase import CoinBaseWrapper from app.markets.coinbase import CoinBaseWrapper
from app.markets.cryptocompare import CryptoCompareWrapper from app.markets.cryptocompare import CryptoCompareWrapper

View File

@@ -1,7 +1,7 @@
import os import os
from typing import Any from typing import Any
from binance.client import Client # type: ignore from binance.client import Client # type: ignore
from app.markets.base import ProductInfo, MarketWrapper, Price from app.base.markets import ProductInfo, MarketWrapper, Price
def extract_product(currency: str, ticker_data: dict[str, Any]) -> ProductInfo: def extract_product(currency: str, ticker_data: dict[str, Any]) -> ProductInfo:

View File

@@ -3,7 +3,7 @@ from enum import Enum
from datetime import datetime, timedelta from datetime import datetime, timedelta
from coinbase.rest import RESTClient # type: ignore from coinbase.rest import RESTClient # type: ignore
from coinbase.rest.types.product_types import Candle, GetProductResponse, Product # type: ignore from coinbase.rest.types.product_types import Candle, GetProductResponse, Product # type: ignore
from app.markets.base import ProductInfo, MarketWrapper, Price from app.base.markets import ProductInfo, MarketWrapper, Price
def extract_product(product_data: GetProductResponse | Product) -> ProductInfo: def extract_product(product_data: GetProductResponse | Product) -> ProductInfo:

View File

@@ -1,7 +1,7 @@
import os import os
from typing import Any from typing import Any
import requests import requests
from app.markets.base import ProductInfo, MarketWrapper, Price from app.base.markets import ProductInfo, MarketWrapper, Price
def extract_product(asset_data: dict[str, Any]) -> ProductInfo: def extract_product(asset_data: dict[str, Any]) -> ProductInfo:

View File

@@ -1,6 +1,6 @@
import json import json
from agno.tools.yfinance import YFinanceTools from agno.tools.yfinance import YFinanceTools
from app.markets.base import MarketWrapper, ProductInfo, Price from app.base.markets import MarketWrapper, ProductInfo, Price
def extract_product(stock_data: dict[str, str]) -> ProductInfo: def extract_product(stock_data: dict[str, str]) -> ProductInfo:

View File

@@ -1,6 +1,6 @@
from agno.tools import Toolkit from agno.tools import Toolkit
from app.utils import WrapperHandler from app.utils import WrapperHandler
from app.news.base import NewsWrapper, Article from app.base.news import NewsWrapper, Article
from app.news.news_api import NewsApiWrapper from app.news.news_api import NewsApiWrapper
from app.news.googlenews import GoogleNewsWrapper from app.news.googlenews import GoogleNewsWrapper
from app.news.cryptopanic_api import CryptoPanicWrapper from app.news.cryptopanic_api import CryptoPanicWrapper

View File

@@ -2,7 +2,7 @@ import os
from typing import Any from typing import Any
import requests import requests
from enum import Enum from enum import Enum
from app.news.base import NewsWrapper, Article from app.base.news import NewsWrapper, Article
class CryptoPanicFilter(Enum): class CryptoPanicFilter(Enum):

View File

@@ -1,7 +1,7 @@
import json import json
from typing import Any from typing import Any
from agno.tools.duckduckgo import DuckDuckGoTools from agno.tools.duckduckgo import DuckDuckGoTools
from app.news.base import Article, NewsWrapper from app.base.news import Article, NewsWrapper
def extract_article(result: dict[str, Any]) -> Article: def extract_article(result: dict[str, Any]) -> Article:

View File

@@ -1,6 +1,6 @@
from typing import Any from typing import Any
from gnews import GNews # type: ignore from gnews import GNews # type: ignore
from app.news.base import Article, NewsWrapper from app.base.news import Article, NewsWrapper
def extract_article(result: dict[str, Any]) -> Article: def extract_article(result: dict[str, Any]) -> Article:

View File

@@ -1,7 +1,7 @@
import os import os
from typing import Any from typing import Any
import newsapi # type: ignore import newsapi # type: ignore
from app.news.base import Article, NewsWrapper from app.base.news import Article, NewsWrapper
def extract_article(result: dict[str, Any]) -> Article: def extract_article(result: dict[str, Any]) -> Article:

View File

@@ -1,6 +1,6 @@
from agno.tools import Toolkit from agno.tools import Toolkit
from app.utils import WrapperHandler from app.utils import WrapperHandler
from app.social.base import SocialPost, SocialWrapper from app.base.social import SocialPost, SocialWrapper
from app.social.reddit import RedditWrapper from app.social.reddit import RedditWrapper
__all__ = ["SocialAPIsTool", "RedditWrapper", "SocialPost"] __all__ = ["SocialAPIsTool", "RedditWrapper", "SocialPost"]

View File

@@ -1,7 +1,7 @@
import os import os
from praw import Reddit # type: ignore from praw import Reddit # type: ignore
from praw.models import Submission, MoreComments # type: ignore from praw.models import Submission, MoreComments # type: ignore
from app.social.base import SocialWrapper, SocialPost, SocialComment from app.base.social import SocialWrapper, SocialPost, SocialComment
MAX_COMMENTS = 5 MAX_COMMENTS = 5

View File

@@ -1,4 +1,5 @@
from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info
from app.utils.wrapper_handler import WrapperHandler from app.utils.wrapper_handler import WrapperHandler
from app.utils.chat_manager import ChatManager
__all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler"] __all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler", "ChatManager"]

View File

@@ -1,10 +1,5 @@
import json import json
import os import os
from app import Pipeline
SAVE_DIR = os.path.join(os.path.dirname(__file__), "..", "saves")
os.makedirs(SAVE_DIR, exist_ok=True)
class ChatManager: class ChatManager:
""" """
@@ -15,19 +10,19 @@ class ChatManager:
""" """
def __init__(self): def __init__(self):
self.pipeline = Pipeline()
self.history: list[dict[str, str]] = [] # [{"role": "user"/"assistant", "content": "..."}] self.history: list[dict[str, str]] = [] # [{"role": "user"/"assistant", "content": "..."}]
def send_message(self, message: str) -> str: def send_message(self, message: str) -> None:
""" """
Aggiunge un messaggio utente, chiama la Pipeline e salva la risposta nello storico. Aggiunge un messaggio utente, chiama la Pipeline e salva la risposta nello storico.
""" """
# Aggiungi messaggio utente allo storico # Aggiungi messaggio utente allo storico
self.history.append({"role": "user", "content": message}) self.history.append({"role": "user", "content": message})
# Pipeline elabora la query def receive_message(self, response: str) -> str:
response = self.pipeline.interact(message) """
Riceve un messaggio dalla pipeline e lo aggiunge allo storico.
"""
# Aggiungi risposta assistente allo storico # Aggiungi risposta assistente allo storico
self.history.append({"role": "assistant", "content": response}) self.history.append({"role": "assistant", "content": response})
@@ -37,19 +32,17 @@ class ChatManager:
""" """
Salva la chat corrente in src/saves/<filename>. Salva la chat corrente in src/saves/<filename>.
""" """
path = os.path.join(SAVE_DIR, filename) with open(filename, "w", encoding="utf-8") as f:
with open(path, "w", encoding="utf-8") as f:
json.dump(self.history, f, ensure_ascii=False, indent=2) json.dump(self.history, f, ensure_ascii=False, indent=2)
def load_chat(self, filename: str = "chat.json") -> None: def load_chat(self, filename: str = "chat.json") -> None:
""" """
Carica una chat salvata da src/saves/<filename>. Carica una chat salvata da src/saves/<filename>.
""" """
path = os.path.join(SAVE_DIR, filename) if not os.path.exists(filename):
if not os.path.exists(path):
self.history = [] self.history = []
return return
with open(path, "r", encoding="utf-8") as f: with open(filename, "r", encoding="utf-8") as f:
self.history = json.load(f) self.history = json.load(f)
def reset_chat(self) -> None: def reset_chat(self) -> None:
@@ -63,16 +56,3 @@ class ChatManager:
Restituisce lo storico completo della chat. Restituisce lo storico completo della chat.
""" """
return self.history return self.history
# Facciamo pass-through di provider e style, così Gradio può usarli
def choose_provider(self, index: int):
self.pipeline.choose_predictor(index)
def choose_style(self, index: int):
self.pipeline.choose_style(index)
def list_providers(self) -> list[str]:
return self.pipeline.list_providers()
def list_styles(self) -> list[str]:
return self.pipeline.list_styles()

View File

@@ -1,5 +1,5 @@
import statistics import statistics
from app.markets.base import ProductInfo, Price from app.base.markets import ProductInfo, Price
def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]: def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]:

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from app.agents import AppModels from app.agents import AppModels
from app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle from app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle
from app.markets.base import ProductInfo from app.base.markets import ProductInfo
def unified_checks(model: AppModels, input): def unified_checks(model: AppModels, input):
llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type] llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type]

View File

@@ -1,5 +1,5 @@
import pytest import pytest
from app.markets.base import ProductInfo, Price from app.base.markets import ProductInfo, Price
from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info