Veronica Chatbot - Sistema AI WordPress con Tool-based Retrieval ai-projects

Link Progetto

Come ho trasformato un esperimento del corso di Ed Donner in un chatbot professionale integrato nel mio sito WordPress

📖 Il Punto di Partenza: Agenti AI Costruiti a Mano

La settimana scorsa ho seguito il corso di Ed Donner sulla costruzione di agenti AI, dove abbiamo imparato a creare agenti "from scratch"

Qui il link per leggere l'articolo completo!

Era affascinante vedere il meccanismo di reasoning esposto, ma mi sono resa conto di due limitazioni:

  1. Scalabilità: Ogni nuovo tool richiedeva modifiche manuali al loop
  2. Robustezza: Gestione errori e edge cases complessa da implementare
  3. Manutenzione: Codice verboso e difficile da estendere

L'Idea di Evoluzione

Navigando il mio sito web, mi sono chiesta: "E se il mio chatbot potesse rispondere in modo intelligente su tutti i miei contenuti?"

Il problema da risolvere:

  • Avevo già un sito WordPress con progetti, articoli, certificazioni
  • Volevo un assistente che mi rappresentasse professionalmente
  • Serviva qualcosa di più robusto degli esperimenti del corso

La visione:

Un sistema AI con tool-based retrieval che trasformi il mio sito in una conversazione intelligente

🧠 L'Architettura: Da Manuale a LangGraph

Perché LangGraph invece del Loop Manuale

Il pattern ReAct (Reasoning-Acting) di LangGraph risolve elegantemente i problemi del corso:

# LangGraph: Gestione automatica del pattern ReAct
def should_continue(state: State) -> Literal["tools", "__end__"]:
    """Decide se continuare con i tools o terminare"""
    messages = state["messages"]
    last_message = messages[-1]

    if last_message.tool_calls:
        return "tools"
    return "__end__"

def create_graph():
    builder = StateGraph(State, input=InputState, config_schema=Configuration)
    
    # Core nodes
    builder.add_node("agent", call_model)      # 🧠 Reasoning
    builder.add_node("tools", ToolNode(TOOLS)) # 🛠️ Actions
    
    # ReAct flow automatico
    builder.add_conditional_edges("agent", should_continue, {
        "tools": "tools",
        "__end__": "__end__"
    })
    builder.add_edge("tools", "agent")  # Loop automatico
    
    return builder.compile(checkpointer=MemorySaver())Code language: PHP (php)

Vantaggi immediati:

  • Loop automatico: Il framework gestisce il flusso ReAct
  • Scaling semplice: Aggiungi tools, LangGraph li orchestra
  • Memory integrata: Conversation persistence built-in
  • Error handling: Robusto e standardizzato
  • Observability: LangSmith tracing automatico

🔗 WordPress REST API vs Scraping: La Scelta Architetturale

Perché Non Scraping

Nel corso usavamo spesso scraping per recuperare dati, ma per il mio sito ho scelto un approccio diverso:

# ✅ WordPress REST API (robusto)
class OptimizedWordPressClient:
    def get_posts(self, params: Dict = None) -> List[Dict]:
        """Recupera post del blog ottimizzato"""
        return self._make_request("posts", params) or []
    
    def get_projects(self, params: Dict = None) -> List[Dict]:
        """Recupera progetti del portfolio ottimizzato"""
        return self._make_request("projects", params) or []

Vantaggi dell'API:

  • 📊 Dati strutturati: JSON invece di HTML parsing
  • 🔧 Custom Post Types: Accesso a progetti, certificazioni, tools
  • 🏷️ ACF Fields: Metadati ricchi (repository, external_url, rating)
  • 🔄 Aggiornamenti automatici: Nuovi contenuti disponibili subito
  • 🛡️ Standardizzazione: Endpoint WordPress consolidati

Design dei Custom Post Types

Ho progettato una struttura dati ottimizzata per il chatbot:

self.field_configs = {
    "projects": {
        "fields": "id,date,title,content,excerpt,link,acf,project-category",
        "acf_fields": [
            "project_external_url", "project_preview_text",
            "project_repository", "project_frontend"
        ],
        "description": "Progetti portfolio con ACF"
    },
    "certifications": {
        "fields": "id,date,title,content,link,acf",
        "acf_fields": [
            "ente_certificazione", "descrizione_certificazione",
            "start_corso", "end_corso", "link_corso"
        ],
        "description": "Certificazioni e formazione"
    },
    "tools": {
        "acf_fields": [
            "tool_url", "tool_category", "tool_description", "tool_rating"
        ],
        "description": "Strumenti e stack tecnologico"
    }
}Code language: PHP (php)

Ogni content type ha:

  • Campi base: Title, content, date, link
  • Metadati specializzati: ACF fields per context specifico
  • Processing ottimizzato: Estrazione automatica del contenuto rilevante

🛠️ Backend: Sistema AI con Tool-based Retrieval

Architettura del Sistema Tool-based

Il mio sistema implementa un tool-based retrieval completo:

@tool
def search_all_content(query: str, limit_per_type: int = 3) -> str:
    """
    Ricerca generale nei contenuti di Veronica (articoli, progetti, certificazioni, etc.).
    """
    try:
        config = Configuration()
        wp_client = OptimizedWordPressClient(config.wordpress_base_url)
        
        results = {"search_query": query, "results": {}}
        
        # 🔍 RETRIEVAL: Multi-source search
        posts = wp_client.get_posts({"search": query, "per_page": limit_per_type})
        projects = wp_client.get_projects({"search": query, "per_page": limit_per_type})
        certifications = wp_client.get_certifications({"search": query, "per_page": limit_per_type})
        
        # 📝 PROCESSING: Content extraction and cleaning
        if posts:
            results["results"]["articles"] = [
                ContentProcessor.process_post(p) for p in posts]
        if projects:
            results["results"]["projects"] = [
                ContentProcessor.process_project(p) for p in projects]
        
        # 🤖 PROCESSING: Structured data for LLM context
        return json.dumps(results)Code language: PHP (php)

9 Tools Specializzati per Content Retrieval

Invece di un tool generico, ho creato tools specializzati per ogni tipo di contenuto:

TOOLS = [
    search_blog_posts,          # 📝 Articoli del blog
    get_latest_blog_post,       # 🆕 Ultimo articolo pubblicato
    get_portfolio_projects,     # 💼 Progetti del portfolio
    get_certifications,         # 🎓 Certificazioni e formazione
    get_work_experience,        # 👔 Esperienze lavorative
    get_books_and_reading,      # 📚 Libri letti e recensiti
    get_tools_and_stack,        # 🔧 Stack tecnologico
    search_all_content,         # 🔍 Ricerca globale
    get_contact_info           # 📞 Informazioni contatto
]Code language: PHP (php)

Ogni tool:

  • È ottimizzato per il suo content type specifico
  • Processa i dati in modo context-aware
  • Restituisce JSON strutturato per l'LLM

Content Processing Intelligente

class ContentProcessor:
    @staticmethod
    def process_project(project: Dict) -> Dict:
        """Processa un progetto del portfolio"""
        title = project.get("title", {}).get("rendered", "")
        content = project.get("content", {}).get("rendered", "")
        
        # Estrai campi ACF specifici per progetti
        acf = project.get("acf", {})
        
        return {
            "title": title,
            "description": ContentProcessor.clean_html(content)[:400] + "...",
            "repository": acf.get("project_repository", ""),
            "external_url": acf.get("project_external_url", ""),
            "frontend_url": acf.get("project_frontend", ""),
            "preview_text": acf.get("project_preview_text", ""),
            "type": "project"
        }

Features del processing:

  • 🧹 HTML cleaning: Rimozione tag e entità HTML
  • ✂️ Content truncation: Limite lunghezza per token efficiency
  • 🏷️ Metadata extraction: ACF fields strutturati
  • 🎯 Type classification: Ogni content ha il suo tipo

🧠 System Prompt Engineering: L'Arte dell'Identità AI

La Sfida dell'Impersonificazione Professionale

Una delle parti più complesse è stata creare un system prompt che faccia sentire l'AI come "Veronica digitale". Il prompt finale è lungo 200+ righe e rappresenta il vero "cervello" del sistema:

def create_system_prompt() -> str:
    """Crea il system prompt professionale"""
    personal_summary = load_personal_summary()

    return f"""Tu sei Veronica Schembri, AI Engineer e Data Scientist.

PERSONALITÀ E BACKGROUND:
Rappresenti Veronica sul suo sito web per potenziali clienti e datori di lavoro.
Il tuo background include:
- Percorso dall'illustrazione → programmazione → AI
- Specializzazione in Data Science, LLM e agenti AI
- Esperienza in web development e design visivo
- Metodo di lavoro strutturato (sistemi organizzativi, Building a Second Brain)
- Passioni: serie TV, fumetti, Magic: The Gathering, Lego

INFORMAZIONI DI CONTATTO:
📧 Email principale: veronicaschembri@gmail.com
🌐 Sito web: https://www.veronicaschembri.com
💼 LinkedIn: https://www.linkedin.com/in/veronicaschembri/
🐙 GitHub: https://github.com/Pandagan-85/


"""Code language: PHP (php)

Tool Orchestration via Prompt

Il prompt mappa esplicitamente ogni tipo di query ai tools appropriati:

STRUMENTI DISPONIBILI:
1. search_blog_posts(query, limit): cerca negli articoli del blog per argomenti specifici
2. get_latest_blog_post(): ottieni l'ultimo articolo pubblicato con contenuto completo
3. get_portfolio_projects(category, limit): ottieni progetti del portfolio
4. get_certifications(limit): ottieni tutte le certificazioni e formazione

PRIORITÀ NELLE RISPOSTE:
1. USA SEMPRE i tool per domande su:
✅ "Quali certificazioni hai?" → get_certifications()
✅ "Di cosa parla l'ultimo articolo?" → get_latest_blog_post()
✅ "Progetti con tecnologia X" → get_portfolio_projects()
✅ "Articoli su argomento Y" → search_blog_posts("Y")

ESEMPI DI QUERY DA GESTIRE CON I TOOL:
- "Quali certificazioni hai conseguito?" → get_certifications()
- "Hai formazione su AI?" → get_certifications()
- "Di cosa parla il tuo ultimo articolo?" → get_latest_blog_post()
- "Progetti di machine learning?" → search_all_content("machine learning")
- "Come posso contattarti?" → get_contact_info()Code language: PHP (php)

Boundary Setting Professionale

Parte critica: definire cosa l'AI può e non può fare per mantenere il focus professionale:

REGOLE FONDAMENTALI:
🚫 RISPONDI SOLO SU ARGOMENTI RIGUARDANTI VERONICA SCHEMBRI:
- Il suo background professionale e percorso
- I suoi progetti, articoli e competenze
- Le sue esperienze e tecnologie utilizzate

🚫 NON RISPONDERE A DOMANDE GENERICHE come:
- "Come creare un sito WordPress?"
- "Tutorial su tecnologie generiche"
- "Spiegazioni teoriche non legate alla tua esperienza"

✅ INVECE, PER DOMANDE GENERICHE, REINDIRIZZA COSÌ:
"Interessante! Nel mio blog e nei miei progetti affronto spesso argomenti tecnici come questo. 
Ti suggerisco di esplorare i miei contenuti per vedere la mia prospettiva specifica, 
oppure contattami se vuoi discutere di una collaborazione su progetti simili!"

COMPORTAMENTO:
- Sii professionale ma coinvolgente
- Rimani sempre focalizzata su Veronica e il suo lavoro
- Integra i risultati dei tool in modo naturale nella conversazione
- Mostra curiosità per le esigenze del visitatore in relazione al lavoro di Veronica
- Incoraggia collaborazioni e contatti professionali
- NON INVENTARE MAI informazioni - se non sai, usa i tool appropriatiCode language: PHP (php)

Integrazione con il Summary Personale

Il prompt carica dinamicamente il mio profilo personale dal file me/summary.txt:

def load_personal_summary() -> str:
    """Carica il summary personale dalla cartella me/"""
    try:
        summary_path = "me/summary.txt"
        if os.path.exists(summary_path):
            with open(summary_path, "r", encoding="utf-8") as f:
                return f.read()Code language: JavaScript (javascript)

Questo permette di aggiornare la personalità dell'AI semplicemente modificando un file di testo, senza toccare il codice.

Risultato: Conversazioni Autentiche

Il prompt engineerging ha prodotto conversazioni che sembrano davvero parlare con me:

  • Prima persona: "Ho lavorato su...", "Nel mio progetto..."
  • Dettagli specifici: Cita progetti, tecnologie, esperienze reali
  • Boundaries professionali: Non diventa un ChatGPT generico
  • Call-to-action naturali: Incoraggia contatti per collaborazioni
  • Coerenza: Ogni risposta è allineata con il mio brand professionale

🚀 Deploy e Observability

Railway Deploy

Per il deploy ho scelto Railway per la sua semplicità.

Environment variables:

OPENAI_API_KEY=sk-...
WORDPRESS_URL=https://www.veronicaschembri.com
LANGSMITH_API_KEY=lsv2_... # Per monitoring
LANGSMITH_PROJECT=veronica-wordpress-chatbotCode language: PHP (php)

LangSmith Monitoring

Integrazione completa con LangSmith per osservabilità:

@traceable(name="wordpress_chatbot_request")
def process_chat_with_tracing(message: str, thread_id: str) -> tuple[str, Optional[str]]:
    """Processa la chat con tracing LangSmith"""
    if chatbot is None:
        raise Exception("Chatbot non inizializzato")
    
    # LangSmith traccia automaticamente:
    # - Ogni tool call
    # - Token usage
    # - Latency
    # - Error rate
    response = chatbot.chat(message, thread_id)
    
    return response, trace_urlCode language: PHP (php)

🎨 Frontend: React Widget con Sicurezza

React Component Architecture

Il frontend è un React component completamente autocontenuto:

function VeronicaChatbot() {
    // 💾 Storage manager per persistenza
    const [storageManager] = React.useState(() => new ChatStorageManager());
    
    // 🎯 Stato con persistenza cross-page
    const [session, setSession] = React.useState(() => 
        storageManager.getOrCreateSession()
    );
    const [messages, setMessages] = React.useState(() => 
        storageManager.loadMessages()
    );
    
    // 🔄 UI state persistente
    const [uiState, setUIState] = React.useState(() => 
        storageManager.loadUIState()
    );
}Code language: JavaScript (javascript)

La sicurezza è stata una priorità assoluta:

function validateInputSecure(input) {
    if (!input || typeof input !== "string") return false;
    if (input.length > 1000) return false;
    
    // 🛡️ Lista completa di pattern XSS
    const xssPatterns = [
        /<script/i, /<\/script>/i, /<iframe/i, /<object/i,
        /javascript:/i, /vbscript:/i, /on\w+\s*=/i,
        /&lt;script/i, /&#60;script/i, /alert\s*\(/i,
        /eval\s*\(/i, /document\./i, /window\./i
        // ... 20+ pattern di sicurezza
    ];
    
    const hasXSS = xssPatterns.some(pattern => pattern.test(input));
    if (hasXSS) {
        logSecurityEvent("xss_attempt_blocked", input);
        return false;
    }
    
    return true;
}Code language: PHP (php)

Livelli di sicurezza:

  1. Input validation: Pre-filtering rigoroso
  2. Sanitization: Rimozione completa HTML per input utente
  3. Safe rendering: Markdown processing sicuro per bot responses
  4. Security logging: Event tracking per monitoring

🔌 Integrazione WordPress: Plugin Architecture

Plugin WordPress Completo

Ho sviluppato un plugin WordPress completo per l'integrazione:

class VeronicaChatbotPlugin {
    public function __construct() {
        add_action('init', array($this, 'init'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('wp_footer', array($this, 'render_chatbot'));
    }
    
    public function enqueue_scripts() {
        // Load React da CDN
        wp_enqueue_script('react', 'https://unpkg.com/react@18/...');
        
        // Load chatbot con configurazione
        wp_localize_script('veronica-chatbot', 'veronicaChatbotConfig', array(
            'apiUrl' => esc_url($options['api_url']),
            'theme' => sanitize_text_field($options['theme']),
            'enablePersistence' => (bool)$options['enable_persistence']
        ));
    }
}Code language: PHP (php)

Admin Panel Avanzato

Il plugin include un pannello di amministrazione completo:

class VeronicaChatbotPlugin {
    public function __construct() {
        add_action('init', array($this, 'init'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('wp_footer', array($this, 'render_chatbot'));
    }
    
    public function enqueue_scripts() {
        // Load React da CDN
        wp_enqueue_script('react', 'https://unpkg.com/react@18/...');
        
        // Load chatbot con configurazione
        wp_localize_script('veronica-chatbot', 'veronicaChatbotConfig', array(
            'apiUrl' => esc_url($options['api_url']),
            'theme' => sanitize_text_field($options['theme']),
            'enablePersistence' => (bool)$options['enable_persistence']
        ));
    }
}Code language: PHP (php)

Admin Panel

⚙️ Configurazione API: URL backend, theme, posizione 📊 Statistiche usage: Conversazioni, messaggi, sessioni attive 🧪 Test connessione: Verifica API endpoint in tempo reale 🔧 Debug mode: Console debugging per sviluppo 💾 Gestione persistenza: Controlli durata sessione e timeout

Cross-Page Persistence

La conversazione rimane aperta mentre l'utente naviga tra pagine del sito, creando un'esperienza seamless.

💭 Riflessioni Finali

Questo progetto rappresenta il mio primo vero sistema full-stack, dove ho dovuto integrare tecnologie diverse e competenze multiple:

  • 🧠 AI/ML: LangGraph, tool-based retrieval, conversation memory, prompt engineering"g
  • ⚙️ Backend: FastAPI, async programming, API design, error handling
  • 🎨 Frontend: React, responsive design, security, state management
  • 🔌 Integration: WordPress plugin, REST API, environment management
  • 📊 Observability: Monitoring, logging, debugging, performance tracking
  • 🚀 DevOps: Railway deployment, CI/CD, production management

Il salto qualitativo rispetto agli esperimenti del corso è evidente: da prototype educativo a sistema in produzione utilizzato quotidianamente da visitatori reali.

Related Projects

Marzo 18, 2025
Chanteclair

• Sviluppo del sito web di Chanteclair utilizzando WordPress, sfruttando Oxygen Builder per un design personalizzato e flessibile, e Advanced Custom Fields (ACF) per la gestione dinamica del contenuto, migliorando sia l’aspetto che l’usabilità. • Questo progetto è stato realizzato mentre ricoprivo il ruolo di Head of Web Development presso DigitalMakers. Tecnologie utilizzate: WordPress, Oxygen […]

Aprile 6, 2025
AI Assistant with RAG

Che cos'è il RAG? La Retrieval-Augmented Generation (RAG) è un paradigma all'avanguardia nell'intelligenza artificiale che combina la potenza generativa dei modelli linguistici con la precisione del recupero di informazioni da fonti esterne. In parole semplici, invece di affidarsi esclusivamente alla conoscenza "memorizzata" durante l'addestramento, un sistema RAG cerca attivamente informazioni rilevanti in un database o […]

veronicaschembri
Copyright © Veronica Schembri

Privacy Policy
Cookie Policy
💬