Merge branch 'main' into fix-query-input

This commit is contained in:
2025-11-04 14:40:06 +01:00
3 changed files with 159 additions and 65 deletions

View File

@@ -9,12 +9,12 @@ L'obiettivo è quello di creare un sistema di consulenza finanziaria basato su L
# **Indice**
- [Installazione](#installazione)
- [1. Variabili d'Ambiente](#1-variabili-dambiente)
- [1. Configurazioni](#1-configurazioni)
- [2. Ollama](#2-ollama)
- [3. Docker](#3-docker)
- [4. UV (solo per sviluppo locale)](#4-uv-solo-per-sviluppo-locale)
- [Applicazione](#applicazione)
- [Struttura del codice del Progetto](#struttura-del-codice-del-progetto)
- [Struttura del codice](#struttura-del-codice)
- [Tests](#tests)
# **Installazione**
@@ -42,7 +42,7 @@ cp configs.yaml.example configs.yaml
nano configs.yaml # esempio di modifica del file
```
Il file `configs.yaml.example` include tutte le configurazioni disponibili con tutti i wrapper e modelli abilitati. Puoi personalizzare il tuo `configs.yaml` locale in base ai modelli che hai scaricato con Ollama e ai provider API che intendi utilizzare. **Questo file non verrà tracciato da git**, quindi ogni sviluppatore può mantenere la propria configurazione locale senza interferire con gli altri.
Il file `configs.yaml.example` include tutte le configurazioni disponibili con tutti i wrapper e modelli abilitati. Puoi personalizzare il tuo `configs.yaml` locale in base ai modelli che hai scaricato con Ollama e ai provider API che intendi utilizzare.
#### **1.2 Variabili d'Ambiente**
@@ -58,7 +58,7 @@ Nel file [.env.example](.env.example) sono presenti tutte le variabili da compil
Le chiavi non sono necessarie per far partire l'applicazione, ma senza di esse alcune funzionalità non saranno disponibili o saranno limitate. Per esempio senza la chiave di NewsAPI non si potranno recuperare le ultime notizie sul mercato delle criptovalute. Ciononostante, l'applicazione usa anche degli strumenti che non richiedono chiavi API, come Yahoo Finance e GNews, che permettono di avere comunque un'analisi di base del mercato.
> [!NOTE]\
> Entrambi i file `.env` e `configs.yaml` non vengono tracciati da git, quindi puoi modificarli liberamente senza preoccuparti di fare commit accidentali delle tue configurazioni personali.
> Entrambi i file `.env` e `configs.yaml` non vengono tracciati da git, quindi possono essere modificati liberamente.
### **2. Ollama**
Per utilizzare modelli AI localmente, è necessario installare Ollama, un gestore di modelli LLM che consente di eseguire modelli direttamente sul proprio hardware. Si consiglia di utilizzare Ollama con il supporto GPU per prestazioni ottimali, ma è possibile eseguirlo anche solo con la CPU.
@@ -68,7 +68,13 @@ Per l'installazione scaricare Ollama dal loro [sito ufficiale](https://ollama.co
Dopo l'installazione, si possono iniziare a scaricare i modelli desiderati tramite il comando `ollama pull <model>:<tag>`.
I modelli usati dall'applicazione sono quelli specificati nella sezione `models` del file di configurazione `configs.yaml` (ad esempio `models.ollama`). Se in locale si hanno dei modelli diversi, è possibile modificare il file `configs.yaml` per usare quelli disponibili.
I modelli consigliati per questo progetto sono `qwen3:4b` e `qwen3:1.7b`.
I modelli consigliati per questo progetto sono:
- **Modelli leggeri** (per sviluppo/test): `qwen3:1.7b`, `phi4-mini:3.8b`
- **Modelli bilanciati** (raccomandati): `qwen3:4b`, `qwen3:8b`
- **Modelli pesanti** (per prestazioni ottimali): `qwen3:14b`, `qwen3:32b`
Il sistema assegna automaticamente ruoli diversi ai modelli: Team Leader (modello principale), Team Members (agenti specializzati), Query Analyzer e Report Generator. È possibile configurare modelli diversi per ogni ruolo nel file `configs.yaml`.
### **3. Docker**
Se si vuole solamente avviare il progetto, si consiglia di utilizzare [Docker](https://www.docker.com), dato che sono stati creati i files [Dockerfile](Dockerfile) e [docker-compose.yaml](docker-compose.yaml) per creare il container con tutti i file necessari e già in esecuzione.
@@ -106,40 +112,72 @@ uv run src/app
# **Applicazione**
> [!CAUTION]\
> ***L'applicazione è attualmente in fase di sviluppo.***
L'applicazione viene fatta partire tramite il file [src/app/\_\_main\_\_.py](src/app/__main__.py) che contiene il codice principale per l'inizializzazione e l'esecuzione del sistema di consulenza finanziaria basato su LLM Agents.
L'applicazione viene fatta partire tramite il file [src/app/\_\_main\_\_.py](src/app/__main__.py) che inizializza l'agente principale e gli agenti secondari.
In esso viene creato il server Gradio per l'interfaccia web (porta predefinita 8000, configurabile in `configs.yaml`) e viene anche inizializzato il bot di Telegram (se è stata inserita la chiave nel file `.env` ottenuta da [BotFather](https://core.telegram.org/bots/features#creating-a-new-bot)).
In esso viene creato il server `gradio` per l'interfaccia web e viene anche inizializzato il bot di Telegram (se è stata inserita la chiave nel file `.env` ottenuta da [BotFather](https://core.telegram.org/bots/features#creating-a-new-bot)).
L'utente può configurare inizialmente:
- **Modello AI**: Scegliere tra diversi provider (Ollama, OpenAI, Gemini, ecc.)
- **Strategia di investimento**:
- *Conservative*: Focus su investimenti stabili e a basso rischio
- *Balanced*: Mix di crescita e stabilità
- *Aggressive*: Investimenti ad alto rischio e alto rendimento
L'interazione è guidata, sia tramite l'interfaccia web che tramite il bot di Telegram; l'utente può scegliere prima di tutto delle opzioni generali (come il modello e la strategia di investimento), dopodiché può inviare un messaggio di testo libero per chiedere consigli o informazioni specifiche. Per esempio: "Qual è l'andamento attuale di Bitcoin?" o "Consigliami quali sono le migliori criptovalute in cui investire questo mese".
L'interazione è guidata, sia tramite l'interfaccia web che tramite il bot di Telegram; l'utente può inviare un messaggio di testo libero per chiedere consigli o informazioni specifiche. Per esempio: "Qual è l'andamento attuale di Bitcoin?" o "Consigliami quali sono le migliori criptovalute in cui investire questo mese".
L'applicazione, una volta ricevuta la richiesta, la passa al [Team](src/app/agents/team.py) di agenti che si occupano di raccogliere i dati necessari per rispondere in modo completo e ragionato.
L'applicazione, una volta ricevuta la richiesta, procede a fare le seguenti operazioni:
1. **Query Check**: Analizza la richiesta dell'utente per controllare che la query sia valida e relativa alle criptovalute.
2. **Team Initialization**: Inizializza il Team di agenti coordinato da un Team Leader per l'analisi del mercato.
3. **Data Collection**: Ogni agente del Team recupera i dati necessari dalle API esterne se richiesto:
- Dati di mercato (prezzi, volumi, capitalizzazione)
- Notizie e sentiment
- Discussioni sui social media
4. **Analysis & Synthesis**: Il Team Leader coordina gli agenti, recupera le risposte e genera una sintesi.
5. **Report Generation**: Un agente dedicato impagina la risposta finale in formato Markdown leggibile.
6. **Delivery**: La risposta viene inviata tramite l'interfaccia web o come allegato PDF dal bot di Telegram.
Gli agenti coinvolti nel Team sono:
- **Leader**: Coordina gli altri agenti e fornisce la risposta finale all'utente.
- **Market Agent**: Recupera i dati di mercato attuali delle criptovalute da Binance e Yahoo Finance.
- **News Agent**: Recupera le ultime notizie sul mercato delle criptovalute da NewsAPI e GNews.
- **Social Agent**: Recupera i dati dai social media (Reddit) per analizzare il sentiment del mercato.
Gli agenti coinvolti in questo processo sono:
- **Query Check**: Verifica che la richiesta dell'utente sia valida e riguardi le criptovalute.
- **Team Leader**: Coordina gli agenti del team, gestisce la pipeline di esecuzione e fornisce una risposta finale basata sui dati raccolti.
- **Market Agent**: Membro del Team, recupera i dati di mercato delle criptovalute da provider multipli (Binance, Coinbase, CryptoCompare, Yahoo Finance).
- **News Agent**: Membro del Team, recupera le ultime notizie sul mercato delle criptovalute da provider multipli (NewsAPI, GoogleNews, CryptoPanic, DuckDuckGo).
- **Social Agent**: Membro del Team, recupera i dati dai social media (Reddit, X/Twitter, 4chan) per analizzare il sentiment del mercato.
- **Report Generator**: Si occupa di impaginare la risposta finale in un formato Markdown leggibile e comprensibile per l'utente.
## Struttura del codice del Progetto
L'interazione di questa applicazione con l'utente finisce nell'istante in cui viene inviata la risposta finale.
Se l'utente vuole fare una nuova richiesta, deve inviarla nuovamente tramite l'interfaccia web o il bot di Telegram.
Ogni richiesta viene trattata come una nuova sessione, senza memoria delle interazioni precedenti, questo per garantire che ogni analisi sia basata esclusivamente sui dati attuali del mercato, siccome questo è un requisito fondamentale per un sistema di consulenza finanziaria affidabile.
Per quanto riguarda Telegram, all'utente vengono inviati i risultati tramite allegati PDF che rimangono disponibili indefinitamente nella chat con il bot, permettendo di mantenere uno storico delle analisi passate.
## Struttura del codice
```
src
└── app
├── __main__.py
├── config.py <-- Configurazioni app
├── agents <-- Agenti, Team, prompts e simili
├── api <-- Tutte le API esterne
│ ├── core <-- Classi core per le API
── markets <-- Market data provider (Es. Binance)
│ ├── news <-- News data provider (Es. NewsAPI)
│ ├── social <-- Social data provider (Es. Reddit)
── tools <-- Tools per agenti creati dalle API
└── interface <-- Interfacce utente
├── __main__.py <-- Entry point dell'applicazione
├── config.py <-- Configurazioni app e gestione modelli
├── agents <-- Agenti, Team, prompts e pipeline
│ ├── core.py <-- Classi base per agenti e input
│ ├── pipeline.py <-- Orchestrazione workflow agenti
── prompts/ <-- Istruzioni per ogni tipo di agente
├── api <-- Tutte le API esterne e wrapper
│ ├── core <-- Classi core per le API
── markets <-- Market data provider (Binance, Coinbase, ecc.)
│ ├── news <-- News data provider (NewsAPI, GoogleNews, ecc.)
│ ├── social <-- Social data provider (Reddit, X/Twitter, ecc.)
│ └── tools <-- Tools per agenti creati dalle API
└── interface <-- Interfacce utente (Gradio, Telegram)
```
### Caratteristiche Tecniche
- **Multi-Provider**: Ogni categoria (mercato, notizie, social) supporta provider multipli con fallback automatico
- **Aggregated Calls**: Possibilità di chiamare tutti i provider simultaneamente per dati più completi
- **Pipeline Events**: Sistema di logging dettagliato per tracciare l'esecuzione degli agenti
- **Configurazione Flessibile**: Modelli AI diversi per ruoli diversi (Team Leader vs Team Members)
- **Error Handling**: Gestione robusta degli errori con retry automatici
## Tests
Per eseguire i test, assicurati di aver configurato correttamente le variabili d'ambiente nel file `.env` come descritto sopra. Poi esegui il comando:

View File

@@ -1,5 +1,6 @@
import os
import json
from pathlib import Path
from typing import Any, Callable
import gradio as gr
from app.agents.action_registry import get_user_friendly_action
@@ -89,48 +90,61 @@ class ChatManager:
def gradio_build_interface(self) -> gr.Blocks:
with gr.Blocks(fill_height=True, fill_width=True) as interface:
gr.Markdown("# 🤖 Agente di Analisi e Consulenza Crypto (Chat)")
css_file_path = Path(__file__).parent / "styles" / "chat.css"
# --- Prepara le etichette di default per i dropdown
model_labels = self.inputs.list_models_names()
default_model_label = self.inputs.team_leader_model.label
if default_model_label not in model_labels:
default_model_label = model_labels[0] if model_labels else None
with gr.Blocks(fill_height=True, fill_width=True, css_paths=[str(css_file_path)]) as interface:
with gr.Column(elem_id="chat-wrapper-column"):
gr.Markdown("# 🤖 Agente di Analisi e Consulenza Crypto")
strategy_labels = self.inputs.list_strategies_names()
default_strategy_label = self.inputs.strategy.label
if default_strategy_label not in strategy_labels:
default_strategy_label = strategy_labels[0] if strategy_labels else None
# --- Prepara le etichette di default per i dropdown
model_labels = self.inputs.list_models_names()
default_model_label = self.inputs.team_leader_model.label
if default_model_label not in model_labels:
default_model_label = model_labels[0] if model_labels else None
# Dropdown provider e stile
with gr.Row():
provider = gr.Dropdown(
choices=model_labels,
value=default_model_label,
type="index",
label="Modello da usare"
strategy_labels = self.inputs.list_strategies_names()
default_strategy_label = self.inputs.strategy.label
if default_strategy_label not in strategy_labels:
default_strategy_label = strategy_labels[0] if strategy_labels else None
# Dropdown provider e stile
with gr.Row(elem_id="dropdown-row"):
provider = gr.Dropdown(
choices=model_labels,
value=default_model_label,
type="index",
label="Modello da usare"
)
provider.change(fn=self.inputs.choose_team_leader, inputs=provider, outputs=None)
style = gr.Dropdown(
choices=strategy_labels,
value=default_strategy_label,
type="index",
label="Stile di investimento"
)
style.change(fn=self.inputs.choose_strategy, inputs=style, outputs=None)
chat = gr.ChatInterface(
fn=self.gradio_respond,
chatbot=gr.Chatbot(
elem_classes=["borderless-chat"], # Applica la classe CSS
scale=1 # Mantiene l'altezza completa
),
# Aggiungi un placeholder
textbox=gr.Textbox(
placeholder="Dimmi come posso aiutarti con le criptovalute...",
scale=1, # Assicura che la textbox si allinei correttamente
render=False # Importante: dice a Gradio di non renderizzarla due volte
)
)
provider.change(fn=self.inputs.choose_team_leader, inputs=provider, outputs=None)
style = gr.Dropdown(
choices=strategy_labels,
value=default_strategy_label,
type="index",
label="Stile di investimento"
)
style.change(fn=self.inputs.choose_strategy, inputs=style, outputs=None)
with gr.Row():
clear_btn = gr.Button("🗑️ Reset Chat")
save_btn = gr.Button("💾 Salva Chat")
load_btn = gr.Button("📂 Carica Chat")
chat = gr.ChatInterface(
fn=self.gradio_respond
)
with gr.Row():
clear_btn = gr.Button("🗑️ Reset Chat")
save_btn = gr.Button("💾 Salva Chat")
load_btn = gr.Button("📂 Carica Chat")
clear_btn.click(self.gradio_clear, inputs=None, outputs=[chat.chatbot, chat.chatbot_state])
save_btn.click(self.gradio_save, inputs=None, outputs=None)
load_btn.click(self.gradio_load, inputs=None, outputs=[chat.chatbot, chat.chatbot_state])
clear_btn.click(self.gradio_clear, inputs=None, outputs=[chat.chatbot, chat.chatbot_state])
save_btn.click(self.gradio_save, inputs=None, outputs=None)
load_btn.click(self.gradio_load, inputs=None, outputs=[chat.chatbot, chat.chatbot_state])
return interface

View File

@@ -0,0 +1,42 @@
/* --- Contenitore Principale --- */
#chat-wrapper-column {width: 40% !important; max-width: 1200px !important; margin: 0 auto !important;}
/* --- Finestra Chat (Borderless) --- */
.borderless-chat {border: none !important; box-shadow: none !important; background: none !important;}
.borderless-chat .message.bot {border: none !important; box-shadow: none !important;}
/* --- Scrollbar --- */
.borderless-chat ::-webkit-scrollbar-button { display: none; }
.borderless-chat ::-webkit-scrollbar-track { background: #1f2937; border-radius: 4px; }
.borderless-chat ::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
.borderless-chat ::-webkit-scrollbar-thumb:hover { background: #6b7280; }
.borderless-chat ::-webkit-scrollbar, #chat-wrapper-column textarea::-webkit-scrollbar { display: none; }
.borderless-chat, #chat-wrapper-column textarea {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge (vecchio) */
}
#chat-wrapper-column textarea::-webkit-scrollbar-button { display: none; }
#chat-wrapper-column textarea::-webkit-scrollbar-track { background: #1f2937; border-radius: 4px; }
#chat-wrapper-column textarea::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
#chat-wrapper-column textarea::-webkit-scrollbar-thumb:hover { background: #6b7280; }
/* Rimuove i bordi dal gr.Group usato da ChatInterface per la textbox */
#chat-wrapper-column .gr-group {
border: none !important;
box-shadow: none !important;
background: none !important;
}
/* La SOLUZIONE per il separatore dei dropdown */
#dropdown-row > div {
border: none !important;
box-shadow: none !important;
}
/* Stile "Pillola" per la Textbox interna (figlia del gr.Group di ChatInterface) */
#chat-wrapper-column .gr-group textarea {
border: none !important;
box-shadow: none !important;
min-height: 56px !important;
padding: 18px 24px !important;
}