From 1206972bb67f004b7d27e921c584a9c186a8cff8 Mon Sep 17 00:00:00 2001 From: trojanhorse47 Date: Tue, 30 Sep 2025 14:14:24 +0200 Subject: [PATCH 1/6] Refactor market agent and toolkit to support batch price retrieval --- src/app/agents/market_agent.py | 21 ++++++++++----------- src/app/toolkits/market_toolkit.py | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/app/agents/market_agent.py b/src/app/agents/market_agent.py index 94d01d5..060a0c4 100644 --- a/src/app/agents/market_agent.py +++ b/src/app/agents/market_agent.py @@ -71,17 +71,16 @@ class MarketAgent(Agent): results = [] products: List[ProductInfo] = [] - for sym in symbols: - try: - product = self.toolkit.get_current_price(sym) # supponiamo ritorni un ProductInfo o simile - if isinstance(product, list): - products.extend(product) - else: - products.append(product) - - results.append(f"{sym}: {product.price if hasattr(product, 'price') else product}") - except Exception as e: - results.append(f"{sym}: errore ({e})") + try: + products.extend(self.toolkit.get_current_prices(symbols)) # supponiamo ritorni un ProductInfo o simile + # Usa list comprehension per iterare symbols e products insieme + results.extend([ + f"{symbol}: ${product.price:.2f}" if hasattr(product, + 'price') and product.price else f"{symbol}: N/A" + for symbol, product in zip(symbols, products) + ]) + except Exception as e: + results.extend(f"Errore: impossibile recuperare i dati di mercato\n {e}") # 4. Preparo output leggibile + metadati strutturati output_text = "📊 Dati di mercato:\n" + "\n".join(results) diff --git a/src/app/toolkits/market_toolkit.py b/src/app/toolkits/market_toolkit.py index f0aef93..06bcaf0 100644 --- a/src/app/toolkits/market_toolkit.py +++ b/src/app/toolkits/market_toolkit.py @@ -16,15 +16,15 @@ class MarketToolkit(Toolkit): name="Market Toolkit", tools=[ self.get_historical_data, - self.get_current_price, + self.get_current_prices, ], ) def get_historical_data(self, symbol: str): return self.market_api.get_historical_prices(symbol) - def get_current_price(self, symbol: str): - return self.market_api.get_products(symbol) + def get_current_prices(self, symbols: list): + return self.market_api.get_products(symbols) def prepare_inputs(): pass -- 2.49.1 From 6e1c11f6aa5ec4edeab0d75cc507351fdcbfde45 Mon Sep 17 00:00:00 2001 From: trojanhorse47 Date: Wed, 1 Oct 2025 16:07:41 +0200 Subject: [PATCH 2/6] 1. Correzione del modello base del Team: inizializzato con qwen3:latest 2. Modifica dell'interfaccia e inserimento di un ChatManager per gestire interazione, salvataggio e caricamento della chat. --- src/app.py | 79 +++++++++++----- src/app/chat_manager.py | 78 +++++++++++++++ src/app/markets/__init__.py | 12 +-- src/app/pipeline.py | 168 +++++++++++++++++++++------------ tests/agents/test_predictor.py | 2 +- 5 files changed, 252 insertions(+), 87 deletions(-) create mode 100644 src/app/chat_manager.py diff --git a/src/app.py b/src/app.py index 8477149..39ac09c 100644 --- a/src/app.py +++ b/src/app.py @@ -1,47 +1,82 @@ import gradio as gr - -from dotenv import load_dotenv -from app.pipeline import Pipeline from agno.utils.log import log_info +from dotenv import load_dotenv + +from app.chat_manager import ChatManager ######################################## -# MAIN APP & GRADIO INTERFACE +# MAIN APP & GRADIO CHAT INTERFACE ######################################## if __name__ == "__main__": - ###################################### - # DA FARE PRIMA DI ESEGUIRE L'APP - # qui carichiamo le variabili d'ambiente dal file .env - # una volta fatto, possiamo usare le API keys senza problemi - # quindi non è necessario richiamare load_dotenv() altrove + # Carica variabili d’ambiente (.env) load_dotenv() - ###################################### - pipeline = Pipeline() + # Inizializza ChatManager + chat = ChatManager() + ######################################## + # Funzioni Gradio + ######################################## + def respond(message, history): + response = chat.send_message(message) + history.append({"role": "user", "content": message}) + history.append({"role": "assistant", "content": response}) + return history, history + + def save_current_chat(): + chat.save_chat("chat.json") + return "💾 Chat salvata in chat.json" + + def load_previous_chat(): + chat.load_chat("chat.json") + history = [] + for m in chat.get_history(): + history.append({"role": m["role"], "content": m["content"]}) + return history, history + + def reset_chat(): + chat.reset_chat() + return [], [] + + ######################################## + # Interfaccia Gradio + ######################################## with gr.Blocks() as demo: - gr.Markdown("# 🤖 Agente di Analisi e Consulenza Crypto") + gr.Markdown("# 🤖 Agente di Analisi e Consulenza Crypto (Chat)") + # Dropdown provider e stile with gr.Row(): provider = gr.Dropdown( - choices=pipeline.list_providers(), + choices=chat.list_providers(), type="index", label="Modello da usare" ) - provider.change(fn=pipeline.choose_provider, inputs=provider, outputs=None) + provider.change(fn=chat.choose_provider, inputs=provider, outputs=None) style = gr.Dropdown( - choices=pipeline.list_styles(), + choices=chat.list_styles(), type="index", label="Stile di investimento" ) - style.change(fn=pipeline.choose_style, inputs=style, outputs=None) + style.change(fn=chat.choose_style, inputs=style, outputs=None) - user_input = gr.Textbox(label="Richiesta utente") - output = gr.Textbox(label="Risultato analisi", lines=12) + 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?") - analyze_btn = gr.Button("🔎 Analizza") - analyze_btn.click(fn=pipeline.interact, inputs=[user_input], outputs=output) + with gr.Row(): + clear_btn = gr.Button("🗑️ Reset Chat") + save_btn = gr.Button("💾 Salva Chat") + load_btn = gr.Button("📂 Carica Chat") - server, port = ("0.0.0.0", 8000) - log_info(f"Starting UPO AppAI on http://{server}:{port}") + # Invio messaggio + msg.submit(respond, inputs=[msg, chatbot], outputs=[chatbot, chatbot]) + # Reset + clear_btn.click(reset_chat, inputs=None, outputs=[chatbot, chatbot]) + # Salvataggio + save_btn.click(save_current_chat, inputs=None, outputs=None) + # Caricamento + load_btn.click(load_previous_chat, inputs=None, outputs=[chatbot, chatbot]) + + server, port = ("127.0.0.1", 8000) + log_info(f"Starting UPO AppAI Chat on http://{server}:{port}") demo.launch(server_name=server, server_port=port, quiet=True) diff --git a/src/app/chat_manager.py b/src/app/chat_manager.py new file mode 100644 index 0000000..26ac268 --- /dev/null +++ b/src/app/chat_manager.py @@ -0,0 +1,78 @@ +import os +import json +from typing import List, Dict +from src.app.pipeline import Pipeline + +SAVE_DIR = os.path.join(os.path.dirname(__file__), "..", "saves") +os.makedirs(SAVE_DIR, exist_ok=True) + +class ChatManager: + """ + Gestisce la conversazione con la Pipeline: + - mantiene lo storico dei messaggi + - invoca la Pipeline per generare risposte + - salva e ricarica le chat + """ + + def __init__(self): + self.pipeline = Pipeline() + self.history: List[Dict[str, str]] = [] # [{"role": "user"/"assistant", "content": "..."}] + + def send_message(self, message: str) -> str: + """ + Aggiunge un messaggio utente, chiama la Pipeline e salva la risposta nello storico. + """ + # Aggiungi messaggio utente allo storico + self.history.append({"role": "user", "content": message}) + + # Pipeline elabora la query + response = self.pipeline.interact(message) + + # Aggiungi risposta assistente allo storico + self.history.append({"role": "assistant", "content": response}) + + return response + + def save_chat(self, filename: str = "chat.json") -> None: + """ + Salva la chat corrente in src/saves/. + """ + path = os.path.join(SAVE_DIR, filename) + with open(path, "w", encoding="utf-8") as f: + json.dump(self.history, f, ensure_ascii=False, indent=2) + + def load_chat(self, filename: str = "chat.json") -> None: + """ + Carica una chat salvata da src/saves/. + """ + path = os.path.join(SAVE_DIR, filename) + if not os.path.exists(path): + self.history = [] + return + with open(path, "r", encoding="utf-8") as f: + self.history = json.load(f) + + def reset_chat(self) -> None: + """ + Resetta lo storico della chat. + """ + self.history = [] + + def get_history(self) -> List[Dict[str, str]]: + """ + Restituisce lo storico completo della chat. + """ + return self.history + + # Facciamo pass-through di provider e style, così Gradio può usarli + def choose_provider(self, index: int): + self.pipeline.choose_provider(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() diff --git a/src/app/markets/__init__.py b/src/app/markets/__init__.py index 20e29ee..4741dbb 100644 --- a/src/app/markets/__init__.py +++ b/src/app/markets/__init__.py @@ -1,9 +1,9 @@ -from base import BaseWrapper -from app.markets.coinbase import CoinBaseWrapper -from app.markets.cryptocompare import CryptoCompareWrapper -from app.markets.binance import BinanceWrapper -from app.markets.binance_public import PublicBinanceAgent -from app.markets.error_handler import ProviderFallback, MarketAPIError, safe_execute +from .base import BaseWrapper +from .coinbase import CoinBaseWrapper +from .cryptocompare import CryptoCompareWrapper +from .binance import BinanceWrapper +from .binance_public import PublicBinanceAgent +from .error_handler import ProviderFallback, MarketAPIError, safe_execute from agno.utils.log import log_warning import logging diff --git a/src/app/pipeline.py b/src/app/pipeline.py index 9e0e2bd..279473c 100644 --- a/src/app/pipeline.py +++ b/src/app/pipeline.py @@ -1,84 +1,136 @@ -from typing import List - +from agno.run.agent import RunOutput from agno.team import Team -from agno.utils.log import log_info -from app.agents.market_agent import MarketAgent +from src.app.agents.market_agent import MarketAgent from src.app.agents.news_agent import NewsAgent from src.app.agents.social_agent import SocialAgent -from src.app.markets import MarketAPIs from src.app.models import AppModels -from src.app.predictor import PredictorStyle, PredictorInput, PredictorOutput, PREDICTOR_INSTRUCTIONS +from src.app.predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS class Pipeline: """ - Pipeline coordinata: esegue tutti gli agenti del Team, aggrega i risultati e invoca il Predictor. + Coordina gli agenti di servizio (Market, News, Social) e il Predictor finale. + Il Team è orchestrato da qwen3:latest (Ollama), mentre il Predictor è dinamico + e scelto dall'utente tramite i dropdown dell'interfaccia. """ - def __init__(self): - # Inizializza gli agenti + # === Membri del team === self.market_agent = MarketAgent() self.news_agent = NewsAgent() self.social_agent = SocialAgent() - # Crea il Team - self.team = Team(name="CryptoAnalysisTeam", members=[self.market_agent, self.news_agent, self.social_agent]) - - # Modelli disponibili e Predictor - self.available_models = AppModels.availables() - self.predictor_model = self.available_models[0] - self.predictor = self.predictor_model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type] - - # Stili - self.styles = list(PredictorStyle) - self.style = self.styles[0] - - def choose_provider(self, index: int): - self.predictor_model = self.available_models[index] - self.predictor = self.predictor_model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type] - - def choose_style(self, index: int): - self.style = self.styles[index] - - def interact(self, query: str) -> str: - """ - Esegue il Team (Market + News + Social), aggrega i risultati e invoca il Predictor. - """ - # Step 1: raccogli output del Team - team_results = self.team.run(query) - if isinstance(team_results, dict): # alcuni Team possono restituire dict - pieces = [str(v) for v in team_results.values()] - elif isinstance(team_results, list): - pieces = [str(r) for r in team_results] - else: - pieces = [str(team_results)] - aggregated_text = "\n\n".join(pieces) - - # Step 2: prepara input per Predictor - predictor_input = PredictorInput( - data=[], # TODO: mappare meglio i dati di mercato in ProductInfo - style=self.style, - sentiment=aggregated_text + # === Modello di orchestrazione del Team === + team_model = AppModels.OLLAMA_QWEN.get_model( + # TODO: migliorare le istruzioni del team + "Agisci come coordinatore: smista le richieste tra MarketAgent, NewsAgent e SocialAgent." + ) + + # === Team === + self.team = Team( + name="CryptoAnalysisTeam", + members=[self.market_agent, self.news_agent, self.social_agent], + model=team_model + ) + + # === Predictor === + self.available_models = AppModels.availables() + self.all_styles = list(PredictorStyle) + + # Scelte di default + self.chosen_model = self.available_models[0] if self.available_models else None + self.style = self.all_styles[0] if self.all_styles else None + + self._init_predictor() # Inizializza il predictor con il modello di default + + # ====================== + # Dropdown handlers + # ====================== + def choose_provider(self, index: int): + """ + Sceglie il modello LLM da usare per il Predictor. + """ + self.chosen_model = self.available_models[index] + self._init_predictor() + + def choose_style(self, index: int): + """ + Sceglie lo stile (conservativo/aggressivo) da usare per il Predictor. + """ + self.style = self.all_styles[index] + + # ====================== + # Helpers + # ====================== + def _init_predictor(self): + """ + Inizializza (o reinizializza) il Predictor in base al modello scelto. + """ + if not self.chosen_model: + return + self.predictor = self.chosen_model.get_agent( + PREDICTOR_INSTRUCTIONS, + output=PredictorOutput, # type: ignore + ) + + def list_providers(self) -> list[str]: + """ + Restituisce la lista dei nomi dei modelli disponibili. + """ + return [model.name for model in self.available_models] + + def list_styles(self) -> list[str]: + """ + Restituisce la lista degli stili di previsione disponibili. + """ + return [style.value for style in self.all_styles] + + # ====================== + # Core interaction + # ====================== + def interact(self, query: str) -> str: + """ + 1. Raccoglie output dai membri del Team + 2. Aggrega output strutturati + 3. Invoca Predictor + 4. Restituisce la strategia finale + """ + if not self.predictor or not self.style: + return "⚠️ Devi prima selezionare un modello e una strategia validi dagli appositi menu." + + # Step 1: raccolta output dai membri del Team + team_outputs = self.team.run(query) + + # Step 2: aggregazione output strutturati + all_products = [] + sentiments = [] + + for agent_output in team_outputs.member_responses: + if isinstance(agent_output, RunOutput): + if "products" in agent_output.metadata: + all_products.extend(agent_output.metadata["products"]) + if "sentiment_news" in agent_output.metadata: + sentiments.append(agent_output.metadata["sentiment_news"]) + if "sentiment_social" in agent_output.metadata: + sentiments.append(agent_output.metadata["sentiment_social"]) + + aggregated_sentiment = "\n".join(sentiments) + + # Step 3: invocazione Predictor + predictor_input = PredictorInput( + data=all_products, + style=self.style, + sentiment=aggregated_sentiment ) - # Step 3: chiama Predictor result = self.predictor.run(predictor_input) prediction: PredictorOutput = result.content - # Step 4: formatta output finale + # Step 4: restituzione strategia finale portfolio_lines = "\n".join( [f"{item.asset} ({item.percentage}%): {item.motivation}" for item in prediction.portfolio] ) - output = ( + return ( f"📊 Strategia ({self.style.value}): {prediction.strategy}\n\n" f"💼 Portafoglio consigliato:\n{portfolio_lines}" ) - - return output - - def list_providers(self) -> List[str]: - return [m.name for m in self.available_models] - - def list_styles(self) -> List[str]: - return [s.value for s in self.styles] diff --git a/tests/agents/test_predictor.py b/tests/agents/test_predictor.py index baf488d..94c4648 100644 --- a/tests/agents/test_predictor.py +++ b/tests/agents/test_predictor.py @@ -4,7 +4,7 @@ from app.markets.base import ProductInfo from app.models import AppModels def unified_checks(model: AppModels, input): - llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) + llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type] result = llm.run(input) content = result.content -- 2.49.1 From ba44f6e902bbf06ffd8fdddbcc472865d010f1c5 Mon Sep 17 00:00:00 2001 From: trojanhorse47 Date: Thu, 2 Oct 2025 11:05:05 +0200 Subject: [PATCH 3/6] * Fix degli import + Aggiunta cancellazione casella di input all'invio della richiesta dell'utente --- src/app.py | 6 +++--- src/app/agents/market_agent.py | 4 ++-- src/app/chat_manager.py | 2 +- src/app/markets/binance.py | 4 ++-- src/app/markets/binance_public.py | 4 ++-- src/app/markets/coinbase.py | 4 ++-- src/app/markets/cryptocompare.py | 4 ++-- src/app/pipeline.py | 10 +++++----- src/app/predictor.py | 2 +- src/app/toolkits/market_toolkit.py | 2 +- tests/agents/test_market.py | 12 ++++++------ 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/app.py b/src/app.py index 39ac09c..eb147f0 100644 --- a/src/app.py +++ b/src/app.py @@ -21,7 +21,7 @@ if __name__ == "__main__": response = chat.send_message(message) history.append({"role": "user", "content": message}) history.append({"role": "assistant", "content": response}) - return history, history + return history, history, "" def save_current_chat(): chat.save_chat("chat.json") @@ -69,7 +69,7 @@ if __name__ == "__main__": load_btn = gr.Button("📂 Carica Chat") # Invio messaggio - msg.submit(respond, inputs=[msg, chatbot], outputs=[chatbot, chatbot]) + msg.submit(respond, inputs=[msg, chatbot], outputs=[chatbot, chatbot, msg]) # Reset clear_btn.click(reset_chat, inputs=None, outputs=[chatbot, chatbot]) # Salvataggio @@ -78,5 +78,5 @@ if __name__ == "__main__": load_btn.click(load_previous_chat, inputs=None, outputs=[chatbot, chatbot]) server, port = ("127.0.0.1", 8000) - log_info(f"Starting UPO AppAI Chat on http://{server}:{port}") + log_info(f"Starting UPO AppAI Chat on http://{server}:{port}") # noqa demo.launch(server_name=server, server_port=port, quiet=True) diff --git a/src/app/agents/market_agent.py b/src/app/agents/market_agent.py index 060a0c4..d442751 100644 --- a/src/app/agents/market_agent.py +++ b/src/app/agents/market_agent.py @@ -4,8 +4,8 @@ from agno.models.message import Message from agno.run.agent import RunOutput, RunOutputEvent from pydantic import BaseModel -from src.app.toolkits.market_toolkit import MarketToolkit -from src.app.markets.base import ProductInfo # modello dati già definito nel tuo progetto +from app.toolkits.market_toolkit import MarketToolkit +from app.markets.base import ProductInfo # modello dati già definito nel tuo progetto class MarketAgent(Agent): diff --git a/src/app/chat_manager.py b/src/app/chat_manager.py index 26ac268..d7525aa 100644 --- a/src/app/chat_manager.py +++ b/src/app/chat_manager.py @@ -1,7 +1,7 @@ import os import json from typing import List, Dict -from src.app.pipeline import Pipeline +from pipeline import Pipeline SAVE_DIR = os.path.join(os.path.dirname(__file__), "..", "saves") os.makedirs(SAVE_DIR, exist_ok=True) diff --git a/src/app/markets/binance.py b/src/app/markets/binance.py index 8bc1101..bcd1ff8 100644 --- a/src/app/markets/binance.py +++ b/src/app/markets/binance.py @@ -2,8 +2,8 @@ import os from typing import Optional from datetime import datetime, timedelta from binance.client import Client -from .base import ProductInfo, BaseWrapper, Price -from .error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from base import ProductInfo, BaseWrapper, Price +from error_handler import retry_on_failure, handle_api_errors, MarketAPIError class BinanceWrapper(BaseWrapper): diff --git a/src/app/markets/binance_public.py b/src/app/markets/binance_public.py index 598840b..b059808 100644 --- a/src/app/markets/binance_public.py +++ b/src/app/markets/binance_public.py @@ -8,8 +8,8 @@ con l'architettura del modulo markets. from typing import Optional, Dict, Any from datetime import datetime, timedelta from binance.client import Client -from .base import BaseWrapper, ProductInfo, Price -from .error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from base import BaseWrapper, ProductInfo, Price +from error_handler import retry_on_failure, handle_api_errors, MarketAPIError class PublicBinanceAgent(BaseWrapper): diff --git a/src/app/markets/coinbase.py b/src/app/markets/coinbase.py index 7d2b2d2..0bba6e1 100644 --- a/src/app/markets/coinbase.py +++ b/src/app/markets/coinbase.py @@ -2,8 +2,8 @@ import os from typing import Optional from datetime import datetime, timedelta from coinbase.rest import RESTClient -from .base import ProductInfo, BaseWrapper, Price -from .error_handler import retry_on_failure, handle_api_errors, MarketAPIError, RateLimitError +from base import ProductInfo, BaseWrapper, Price +from error_handler import retry_on_failure, handle_api_errors, MarketAPIError, RateLimitError class CoinBaseWrapper(BaseWrapper): """ diff --git a/src/app/markets/cryptocompare.py b/src/app/markets/cryptocompare.py index 5b84843..51e4bc0 100644 --- a/src/app/markets/cryptocompare.py +++ b/src/app/markets/cryptocompare.py @@ -1,8 +1,8 @@ import os import requests from typing import Optional, Dict, Any -from .base import ProductInfo, BaseWrapper, Price -from .error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from base import ProductInfo, BaseWrapper, Price +from error_handler import retry_on_failure, handle_api_errors, MarketAPIError BASE_URL = "https://min-api.cryptocompare.com" diff --git a/src/app/pipeline.py b/src/app/pipeline.py index 279473c..15011a2 100644 --- a/src/app/pipeline.py +++ b/src/app/pipeline.py @@ -1,11 +1,11 @@ from agno.run.agent import RunOutput from agno.team import Team -from src.app.agents.market_agent import MarketAgent -from src.app.agents.news_agent import NewsAgent -from src.app.agents.social_agent import SocialAgent -from src.app.models import AppModels -from src.app.predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS +from agents.market_agent import MarketAgent +from agents.news_agent import NewsAgent +from agents.social_agent import SocialAgent +from models import AppModels +from predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS class Pipeline: diff --git a/src/app/predictor.py b/src/app/predictor.py index 101b4ac..ae6eec6 100644 --- a/src/app/predictor.py +++ b/src/app/predictor.py @@ -2,7 +2,7 @@ from enum import Enum from pydantic import BaseModel, Field -from src.app.markets.base import ProductInfo +from markets.base import ProductInfo class PredictorStyle(Enum): diff --git a/src/app/toolkits/market_toolkit.py b/src/app/toolkits/market_toolkit.py index 06bcaf0..e326e14 100644 --- a/src/app/toolkits/market_toolkit.py +++ b/src/app/toolkits/market_toolkit.py @@ -1,6 +1,6 @@ from agno.tools import Toolkit -from src.app.markets import MarketAPIs +from app.markets import MarketAPIs # TODO (?) in futuro fare in modo che la LLM faccia da sé per il mercato diff --git a/tests/agents/test_market.py b/tests/agents/test_market.py index 9fd5732..7ea0c9b 100644 --- a/tests/agents/test_market.py +++ b/tests/agents/test_market.py @@ -6,17 +6,17 @@ e verifica la conformità all'interfaccia definita in base.py. """ import os -import pytest -from unittest.mock import Mock, patch, MagicMock -from typing import Type, List +from unittest.mock import Mock, patch +import pytest + +from app.markets import MarketAPIs # Import delle classi da testare from app.markets.base import BaseWrapper, ProductInfo, Price -from app.markets.coinbase import CoinBaseWrapper -from app.markets.cryptocompare import CryptoCompareWrapper from app.markets.binance import BinanceWrapper from app.markets.binance_public import PublicBinanceAgent -from app.markets import MarketAPIs +from app.markets.coinbase import CoinBaseWrapper +from app.markets.cryptocompare import CryptoCompareWrapper class TestBaseWrapperInterface: -- 2.49.1 From 5e13287e0638d13382b51b61feb454f68a159475 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Fri, 3 Oct 2025 10:24:28 +0200 Subject: [PATCH 4/6] Riorganizzazione degli import per utilizzare il percorso corretto in tutti i moduli --- src/app/chat_manager.py | 2 +- src/app/markets/__init__.py | 1 + src/app/markets/binance.py | 4 ++-- src/app/markets/binance_public.py | 4 ++-- src/app/markets/coinbase.py | 4 ++-- src/app/markets/cryptocompare.py | 4 ++-- src/app/pipeline.py | 10 +++++----- src/app/predictor.py | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/chat_manager.py b/src/app/chat_manager.py index d7525aa..7928c95 100644 --- a/src/app/chat_manager.py +++ b/src/app/chat_manager.py @@ -1,7 +1,7 @@ import os import json from typing import List, Dict -from pipeline import Pipeline +from app.pipeline import Pipeline SAVE_DIR = os.path.join(os.path.dirname(__file__), "..", "saves") os.makedirs(SAVE_DIR, exist_ok=True) diff --git a/src/app/markets/__init__.py b/src/app/markets/__init__.py index 4741dbb..7fa61ca 100644 --- a/src/app/markets/__init__.py +++ b/src/app/markets/__init__.py @@ -28,6 +28,7 @@ class MarketAPIs(BaseWrapper): :return: Lista di istanze delle API di mercato disponibili """ wrapper_builders = [ + BinanceWrapper, CoinBaseWrapper, CryptoCompareWrapper, ] diff --git a/src/app/markets/binance.py b/src/app/markets/binance.py index bcd1ff8..9e736f2 100644 --- a/src/app/markets/binance.py +++ b/src/app/markets/binance.py @@ -2,8 +2,8 @@ import os from typing import Optional from datetime import datetime, timedelta from binance.client import Client -from base import ProductInfo, BaseWrapper, Price -from error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from app.markets.base import ProductInfo, BaseWrapper, Price +from app.markets.error_handler import retry_on_failure, handle_api_errors, MarketAPIError class BinanceWrapper(BaseWrapper): diff --git a/src/app/markets/binance_public.py b/src/app/markets/binance_public.py index b059808..d21a8e9 100644 --- a/src/app/markets/binance_public.py +++ b/src/app/markets/binance_public.py @@ -8,8 +8,8 @@ con l'architettura del modulo markets. from typing import Optional, Dict, Any from datetime import datetime, timedelta from binance.client import Client -from base import BaseWrapper, ProductInfo, Price -from error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from app.markets.base import BaseWrapper, ProductInfo, Price +from app.markets.error_handler import retry_on_failure, handle_api_errors, MarketAPIError class PublicBinanceAgent(BaseWrapper): diff --git a/src/app/markets/coinbase.py b/src/app/markets/coinbase.py index 0bba6e1..cc1cf75 100644 --- a/src/app/markets/coinbase.py +++ b/src/app/markets/coinbase.py @@ -2,8 +2,8 @@ import os from typing import Optional from datetime import datetime, timedelta from coinbase.rest import RESTClient -from base import ProductInfo, BaseWrapper, Price -from error_handler import retry_on_failure, handle_api_errors, MarketAPIError, RateLimitError +from app.markets.base import ProductInfo, BaseWrapper, Price +from app.markets.error_handler import retry_on_failure, handle_api_errors, MarketAPIError, RateLimitError class CoinBaseWrapper(BaseWrapper): """ diff --git a/src/app/markets/cryptocompare.py b/src/app/markets/cryptocompare.py index 51e4bc0..55a8ea7 100644 --- a/src/app/markets/cryptocompare.py +++ b/src/app/markets/cryptocompare.py @@ -1,8 +1,8 @@ import os import requests from typing import Optional, Dict, Any -from base import ProductInfo, BaseWrapper, Price -from error_handler import retry_on_failure, handle_api_errors, MarketAPIError +from app.markets.base import ProductInfo, BaseWrapper, Price +from app.markets.error_handler import retry_on_failure, handle_api_errors, MarketAPIError BASE_URL = "https://min-api.cryptocompare.com" diff --git a/src/app/pipeline.py b/src/app/pipeline.py index 15011a2..8e3aba9 100644 --- a/src/app/pipeline.py +++ b/src/app/pipeline.py @@ -1,11 +1,11 @@ from agno.run.agent import RunOutput from agno.team import Team -from agents.market_agent import MarketAgent -from agents.news_agent import NewsAgent -from agents.social_agent import SocialAgent -from models import AppModels -from predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS +from app.agents.market_agent import MarketAgent +from app.agents.news_agent import NewsAgent +from app.agents.social_agent import SocialAgent +from app.models import AppModels +from app.predictor import PredictorInput, PredictorOutput, PredictorStyle, PREDICTOR_INSTRUCTIONS class Pipeline: diff --git a/src/app/predictor.py b/src/app/predictor.py index ae6eec6..3fa1cfc 100644 --- a/src/app/predictor.py +++ b/src/app/predictor.py @@ -2,7 +2,7 @@ from enum import Enum from pydantic import BaseModel, Field -from markets.base import ProductInfo +from app.markets.base import ProductInfo class PredictorStyle(Enum): -- 2.49.1 From 0a0abc42bae694fd09018b72d447d06e23ff9f19 Mon Sep 17 00:00:00 2001 From: trojanhorse47 Date: Fri, 3 Oct 2025 11:31:25 +0200 Subject: [PATCH 5/6] Remove unused imports from __init__.py --- src/app/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pipeline.py b/src/app/pipeline.py index 8e3aba9..77df442 100644 --- a/src/app/pipeline.py +++ b/src/app/pipeline.py @@ -12,7 +12,7 @@ class Pipeline: """ Coordina gli agenti di servizio (Market, News, Social) e il Predictor finale. Il Team è orchestrato da qwen3:latest (Ollama), mentre il Predictor è dinamico - e scelto dall'utente tramite i dropdown dell'interfaccia. + e scelto dall'utente tramite i dropdown dell'interfaccia grafica. """ def __init__(self): # === Membri del team === -- 2.49.1 From 9a63ac2a3fd0a4ee1db48c6b98f0fc76550556b6 Mon Sep 17 00:00:00 2001 From: trojanhorse47 Date: Fri, 3 Oct 2025 11:38:56 +0200 Subject: [PATCH 6/6] Update __all__ in __init__.py to include MARKET_INSTRUCTIONS --- src/app/markets/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/markets/__init__.py b/src/app/markets/__init__.py index b782b8f..ef73f68 100644 --- a/src/app/markets/__init__.py +++ b/src/app/markets/__init__.py @@ -7,7 +7,7 @@ from .binance import BinanceWrapper from .cryptocompare import CryptoCompareWrapper from .yfinance import YFinanceWrapper -__all__ = [ "MarketAPIs", "BinanceWrapper", "CoinBaseWrapper", "CryptoCompareWrapper", "YFinanceWrapper" ] +__all__ = [ "MarketAPIsTool", "BinanceWrapper", "CoinBaseWrapper", "CryptoCompareWrapper", "YFinanceWrapper", "MARKET_INSTRUCTIONS" ] class MarketAPIsTool(BaseWrapper, Toolkit): -- 2.49.1