diff --git a/pyproject.toml b/pyproject.toml index 35a3b6e..9822cbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "dotenv", # 🟡 per fare scraping di pagine web #"bs4", - # ✅ per fare una UI web semplice con input e output + # ✅ per fare una UI web semplice con user_input e output "gradio", # ✅ per costruire agenti (ovvero modelli che possono fare più cose tramite tool) https://github.com/agno-agi/agno diff --git a/src/app/agents/market.py b/src/app/agents/market.py index affa466..a561cb6 100644 --- a/src/app/agents/market.py +++ b/src/app/agents/market.py @@ -1,5 +1,5 @@ from agno.tools import Toolkit -from app.markets import MarketAPIs +from src.app.markets import MarketAPIs # TODO (?) in futuro fare in modo che la LLM faccia da sé per il mercato # Non so se può essere utile, per ora lo lascio qui diff --git a/src/app/agents/predictor.py b/src/app/agents/predictor.py index e811846..09d7a26 100644 --- a/src/app/agents/predictor.py +++ b/src/app/agents/predictor.py @@ -1,5 +1,5 @@ from enum import Enum -from app.markets.base import ProductInfo +from src.app.markets.base import ProductInfo from pydantic import BaseModel, Field class PredictorStyle(Enum): @@ -23,7 +23,7 @@ class PredictorOutput(BaseModel): PREDICTOR_INSTRUCTIONS = """ You are an **Allocation Algorithm (Crypto-Algo)** specialized in analyzing market data and sentiment to generate an investment strategy and a target portfolio. -Your sole objective is to process the input data and generate the strictly structured output as required by the response format. **You MUST NOT provide introductions, preambles, explanations, conclusions, or any additional comments that are not strictly required.** +Your sole objective is to process the user_input data and generate the strictly structured output as required by the response format. **You MUST NOT provide introductions, preambles, explanations, conclusions, or any additional comments that are not strictly required.** ## Processing Instructions (Absolute Rule) diff --git a/src/app/markets/__init__.py b/src/app/markets/__init__.py index 4bb3e9e..cb27b23 100644 --- a/src/app/markets/__init__.py +++ b/src/app/markets/__init__.py @@ -1,6 +1,6 @@ -from app.markets.base import BaseWrapper -from app.markets.coinbase import CoinBaseWrapper -from app.markets.cryptocompare import CryptoCompareWrapper +from src.app.markets.base import BaseWrapper +from src.app.markets.coinbase import CoinBaseWrapper +from src.app.markets.cryptocompare import CryptoCompareWrapper from agno.utils.log import log_warning @@ -30,8 +30,8 @@ class MarketAPIs(BaseWrapper): for wrapper in wrapper_builders: try: result.append(wrapper(currency=currency)) - except Exception as _: - log_warning(f"{wrapper} cannot be initialized, maybe missing API key?") + except Exception as e: + log_warning(f"{wrapper} cannot be initialized: {e}") assert result, "No market API keys set in environment variables." return result @@ -39,7 +39,9 @@ class MarketAPIs(BaseWrapper): def __init__(self, currency: str = "USD"): """ Inizializza la classe con la valuta di riferimento e la priorità dei provider. - :param currency: Valuta di riferimento (default "USD") + + Args: + currency: Valuta di riferimento (default "USD") """ self.currency = currency self.wrappers = MarketAPIs.get_list_available_market_apis(currency=currency) diff --git a/src/app/markets/coinbase.py b/src/app/markets/coinbase.py index aac556d..2e0e779 100644 --- a/src/app/markets/coinbase.py +++ b/src/app/markets/coinbase.py @@ -1,6 +1,6 @@ import os from coinbase.rest import RESTClient -from app.markets.base import ProductInfo, BaseWrapper, Price +from src.app.markets.base import ProductInfo, BaseWrapper, Price class CoinBaseWrapper(BaseWrapper): """ diff --git a/src/app/markets/cryptocompare.py b/src/app/markets/cryptocompare.py index 188a2c2..a6e50a0 100644 --- a/src/app/markets/cryptocompare.py +++ b/src/app/markets/cryptocompare.py @@ -1,6 +1,6 @@ import os import requests -from app.markets.base import ProductInfo, BaseWrapper, Price +from src.app.markets.base import ProductInfo, BaseWrapper, Price BASE_URL = "https://min-api.cryptocompare.com" @@ -8,7 +8,7 @@ class CryptoCompareWrapper(BaseWrapper): """ Wrapper per le API pubbliche di CryptoCompare. La documentazione delle API è disponibile qui: https://developers.coindesk.com/documentation/legacy/Price/SingleSymbolPriceEndpoint - !!ATTENZIONE!! sembra essere una API legacy e potrebbe essere deprecata in futuro. + !ATTENZIONE! Sembra essere una API legacy e potrebbe essere deprecata in futuro. """ def __init__(self, api_key:str = None, currency:str='USD'): if api_key is None: diff --git a/src/app/models.py b/src/app/models.py index 12aae9c..94f7da2 100644 --- a/src/app/models.py +++ b/src/app/models.py @@ -41,6 +41,7 @@ class AppModels(Enum): availables.append(AppModels.OLLAMA_QWEN) return availables + @staticmethod def availables_online() -> list['AppModels']: """ Controlla quali provider di modelli LLM online hanno le loro API keys disponibili @@ -49,9 +50,7 @@ class AppModels(Enum): if not os.getenv("GOOGLE_API_KEY"): log_warning("No GOOGLE_API_KEY set in environment variables.") return [] - availables = [] - availables.append(AppModels.GEMINI) - availables.append(AppModels.GEMINI_PRO) + availables = [AppModels.GEMINI, AppModels.GEMINI_PRO] return availables @staticmethod @@ -75,9 +74,13 @@ class AppModels(Enum): def extract_json_str_from_response(response: str) -> str: """ Estrae il JSON dalla risposta del modello. - response: risposta del modello (stringa). - Ritorna la parte JSON della risposta come stringa. - Se non viene trovato nessun JSON, ritorna una stringa vuota. + Args: + response: risposta del modello (stringa). + + Returns: + La parte JSON della risposta come stringa. + Se non viene trovato nessun JSON, ritorna una stringa vuota. + ATTENZIONE: questa funzione è molto semplice e potrebbe non funzionare in tutti i casi. Si assume che il JSON sia ben formato e che inizi con '{' e finisca con '}'. Quindi anche solo un json array farà fallire questa funzione. @@ -98,9 +101,15 @@ class AppModels(Enum): def get_model(self, instructions:str) -> Model: """ Restituisce un'istanza del modello specificato. - instructions: istruzioni da passare al modello (system prompt). - Ritorna un'istanza di BaseModel o una sua sottoclasse. - Raise ValueError se il modello non è supportato. + + Args: + instructions: istruzioni da passare al modello (system prompt). + + Returns: + Un'istanza di BaseModel o una sua sottoclasse. + + Raise: + ValueError se il modello non è supportato. """ name = self.value if self in {AppModels.GEMINI, AppModels.GEMINI_PRO}: @@ -113,8 +122,13 @@ class AppModels(Enum): def get_agent(self, instructions: str, name: str = "", output: BaseModel | None = None) -> Agent: """ Costruisce un agente con il modello e le istruzioni specificate. - instructions: istruzioni da passare al modello (system prompt). - Ritorna un'istanza di Agent. + Args: + instructions: istruzioni da passare al modello (system prompt) + name: nome dell'agente (opzionale) + output: schema di output opzionale (Pydantic BaseModel) + + Returns: + Un'istanza di Agent. """ return Agent( model=self.get_model(instructions), diff --git a/src/app/tool.py b/src/app/tool.py index d0b3ca0..f1ca560 100644 --- a/src/app/tool.py +++ b/src/app/tool.py @@ -1,8 +1,8 @@ -from app.agents.news_agent import NewsAgent -from app.agents.social_agent import SocialAgent -from app.agents.predictor import PredictorStyle, PredictorInput, PredictorOutput, PREDICTOR_INSTRUCTIONS -from app.markets import MarketAPIs -from app.models import AppModels +from src.app.agents.news_agent import NewsAgent +from src.app.agents.social_agent import SocialAgent +from src.app.agents.predictor import PredictorStyle, PredictorInput, PredictorOutput, PREDICTOR_INSTRUCTIONS +from src.app.markets import MarketAPIs +from src.app.models import AppModels from agno.utils.log import log_info class ToolAgent: @@ -14,6 +14,10 @@ class ToolAgent: """ Inizializza l'agente con i modelli disponibili, gli stili e l'API di mercato. """ + self.social_agent = None + self.news_agent = None + self.predictor = None + self.chosen_model = None self.available_models = AppModels.availables() self.all_styles = list(PredictorStyle) self.style = self.all_styles[0] # Default to the first style @@ -24,7 +28,9 @@ class ToolAgent: def choose_provider(self, index: int): """ Sceglie il modello LLM da utilizzare in base all'indice fornito. - index: indice del modello nella lista available_models. + + Args: + index: indice del modello nella lista available_models. """ # TODO Utilizzare AGNO per gestire i modelli... è molto più semplice e permette di cambiare modello facilmente # TODO https://docs.agno.com/introduction @@ -37,15 +43,18 @@ class ToolAgent: def choose_style(self, index: int): """ Sceglie lo stile di previsione da utilizzare in base all'indice fornito. - index: indice dello stile nella lista all_styles. + + Args: + index: indice dello stile nella lista all_styles. """ self.style = self.all_styles[index] def interact(self, query: str) -> str: """ Funzione principale che coordina gli agenti per rispondere alla richiesta dell'utente. - query: richiesta dell'utente (es. "Qual è la previsione per Bitcoin?") - style_index: indice dello stile di previsione nella lista all_styles. + + Args: + query: richiesta dell'utente (es. "Qual è la previsione per Bitcoin?") """ log_info(f"[model={self.chosen_model.name}] [style={self.style.name}] [query=\"{query.replace('"', "'")}\"]") diff --git a/src/app/utils/market_aggregator.py b/src/app/utils/market_aggregator.py index 2e89e7f..639bb9b 100644 --- a/src/app/utils/market_aggregator.py +++ b/src/app/utils/market_aggregator.py @@ -1,5 +1,5 @@ import statistics -from typing import Dict, List, Any +from typing import Dict, Any class MarketAggregator: """ @@ -65,6 +65,7 @@ class MarketAggregator: return float(v[:-1]) * 1_000 try: return float(v) - except Exception: + except Exception as e: + print(f"Errore nel parsing del volume: {e}") return 0.0 return 0.0 diff --git a/tests/agents/test_market.py b/tests/agents/test_market.py index 56931b3..cb6c390 100644 --- a/tests/agents/test_market.py +++ b/tests/agents/test_market.py @@ -1,10 +1,10 @@ import os import pytest -from app.agents.market import MarketToolkit -from app.markets.base import BaseWrapper -from app.markets.coinbase import CoinBaseWrapper -from app.markets.cryptocompare import CryptoCompareWrapper -from app.markets import MarketAPIs +from src.app.agents.market import MarketToolkit +from src.app.markets.base import BaseWrapper +from src.app.markets.coinbase import CoinBaseWrapper +from src.app.markets.cryptocompare import CryptoCompareWrapper +from src.app.markets import MarketAPIs class TestMarketSystem: """Test suite per il sistema di mercato (wrappers + toolkit)""" diff --git a/tests/agents/test_predictor.py b/tests/agents/test_predictor.py index c99104b..bf10837 100644 --- a/tests/agents/test_predictor.py +++ b/tests/agents/test_predictor.py @@ -1,11 +1,11 @@ import pytest -from app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle -from app.markets.base import ProductInfo -from app.models import AppModels +from src.app.agents.predictor import PREDICTOR_INSTRUCTIONS, PredictorInput, PredictorOutput, PredictorStyle +from src.app.markets.base import ProductInfo +from src.app.models import AppModels -def unified_checks(model: AppModels, input): - llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) - result = llm.run(input) +def unified_checks(model: AppModels, user_input): + llm = model.get_agent(PREDICTOR_INSTRUCTIONS, output=PredictorOutput) # type: ignore[arg-type] + result = llm.run(user_input) content = result.content assert isinstance(content, PredictorOutput)