Vai al contenuto principale

Jan-V1: una guida con progetto demo

Impara a creare un Deep Research Assistant usando le capacità di ragionamento agentico di Jan-v1, inclusi deployment locale, sviluppo di un'app Streamlit e altro ancora.
· 12 min leggi

Scopri con l'IA

Il team di Jan ha rilasciato di recente Jan-v1, un avanzato modello di ragionamento agentico che introduce sofisticate capacità di automazione della ricerca grazie a un uso migliorato degli strumenti e alla risoluzione di problemi in più passaggi.

In questo tutorial mi concentrerò sulle peculiari capacità di ragionamento agentico di Jan-v1, mostrando come possa gestire un workflow di ricerca complesso tramite un'interfaccia Streamlit eseguita in locale e basata su llama.cpp.

In questo tutorial ti spiegherò come:

  • Eseguire Jan-v1 in locale con quantizzazione GGUF per prestazioni e privacy ottimali
  • Creare un'app Streamlit per la ricerca automatizzata con tracciamento in tempo reale dell'avanzamento
  • Implementare un'integrazione intelligente della ricerca web con generazione dinamica delle query
  • Creare una sintesi in formato report professionale con strutturazione dell'output specifica per il formato
  • Applicare un'elaborazione testuale avanzata per garantire documentazione pulita e pronta per il business

Alla fine, la tua app apparirà così:

Demo del Deep Research Assistant

Che cos'è Jan-v1?

Jan-v1 è un modello da 4 miliardi di parametri, costruito sulle fondamenta di Lucy e Qwen3-4B-thinking, progettato per workflow agentici come l'uso di tool, il ragionamento multi-step e la ricerca automatizzata. A differenza degli LLM convenzionali, Jan-v1 eccelle nel coordinare strumenti, fonti dati e passaggi di ragionamento tramite uno scaffolding strutturato e ricompense allineate al comportamento.

Ecco alcune caratteristiche chiave di questo modello:

  • Architettura agent-ready: Il modello è ottimizzato per l'integrazione nel mondo reale e gestisce ricerca web, sintesi e generazione di report strutturati usando ragionamento avanzato e API di tool.
  • Efficienza: Nonostante le dimensioni ridotte, Jan-v1 offre prestazioni tipiche di modelli molto più grandi, grazie a un addestramento RL mirato che enfatizza output focalizzati e orientati all'obiettivo.
  • Deployment privacy-first: Il formato GGUF compatto abilita un'inferenza rapida in locale, ideale per attività di ricerca a bassa latenza e in privacy su laptop o CPU.
  • Training ottimizzato per la ricerca: Jan-v1 usa funzioni di ricompensa innovative che bilanciano informatività e sintesi, cruciali per compiti ad alto impatto come analisi e reportistica, riducendo al minimo allucinazioni e verbosità.
  • Basato su Qwen3-4B-thinking: Il modello eredita un robusto chain-of-thought e controllo degli strumenti dalla fusione in modalità "thinking" di Qwen3, risultando particolarmente efficace per pipeline di automazione della ricerca.

Configurare Jan-v1 in locale

Per questo progetto, eseguiremo Jan-v1 in locale usando Llama.cpp con la versione quantizzata GGUF per prestazioni ottimali ed efficienza delle risorse.

Passo 1: Installa le dipendenze

Per prima cosa, assicurati di avere i pacchetti Python richiesti:

pip install llama-cpp-python==0.3.15
pip install streamlit aiohttp 

Questo comando installa tutte le dipendenze principali necessarie per l'integrazione del modello AI, la creazione dell'interfaccia web e le operazioni web asincrone.

Passo 2: Scarica la versione GGUF di Jan-v1

Prima di scaricare il modello, crea una nuova directory chiamata models/ con il seguente comando:

 mkdir -p models

Poi scarica il modello con il seguente comando:

curl -L https://huggingface.co/janhq/Jan-v1-4B-GGUF/resolve/main/Jan-v1-4B-Q4_K_M.gguf -o Jan-v1-4B-Q4_K_M.gguf

Completando questo passaggio, hai scaricato correttamente il modello Jan-v1 4B in formato GGUF e lo hai collocato nella directory models/. Questa versione quantizzata offre un buon compromesso tra prestazioni ed efficienza della memoria, rendendola ideale per l'inferenza locale su CPU o Colab.

Passo 3: Configura la chiave API di Serper

Registrati o accedi al tuo account Serper: https://serper.dev/api-keys e seleziona la scheda API Keys, quindi copia la chiave API predefinita per questa demo. Impostiamo ora questa chiave come variabile d'ambiente come segue:

export SERPER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxx

Serper API Key

Ora sei pronto per caricare e interagire con il modello nella tua pipeline di assistente alla ricerca.

Demo: Deep Research Assistant con Jan-v1

Costruiamo un'applicazione Streamlit che mostri le avanzate capacità di ricerca di Jan-v1, con generazione intelligente delle query, ricerca web automatizzata (usando Serper) e sintesi professionale del report.

Passo 1: Importa le librerie

Iniziamo importando tutte le librerie necessarie per il nostro assistente alla ricerca. Supportano tutto, dalla creazione dell'interfaccia web alla ricerca web asincrona e all'integrazione del modello AI locale.

import streamlit as st
import json
import time
import re
from datetime import datetime
from typing import List, Dict, Optional
import asyncio
import aiohttp
from llama_cpp import Llama
import os
from dataclasses import dataclass

Importiamo tutte le librerie principali usate nell'app. Includono componenti UI (Streamlit), strumenti di concorrenza (asyncio, aiohttp) e il backend LLM (llama_cpp). Aggiungiamo anche utility per parsing, typing e gestione delle date.

Passo 2: Funzioni helper

Ora implementeremo alcune funzioni di supporto che gestiscono il formato del report e l'estrazione dei contenuti. Queste funzioni garantiscono che i report generati mantengano qualità professionale e una struttura coerente.

def sections_for_format(fmt: str) -> list[str]:
    fmt = (fmt or "").strip().lower()
    if fmt == "executive":
        return ["EXECUTIVE SUMMARY"]
    if fmt == "detailed":
        return [
            "INTRODUCTION",
            "DETAILED ANALYSIS",
            "CURRENT TRENDS AND DEVELOPMENTS",
            "IMPLICATIONS AND RECOMMENDATIONS",
            "CONCLUSION",
        ]
    if fmt == "academic":
        return [
            "ABSTRACT",
            "INTRODUCTION",
            "METHODOLOGY",
            "FINDINGS",
            "DISCUSSION",
            "CONCLUSION",
        ]
    if fmt == "presentation":
        return [
            "OVERVIEW",
            "KEY INSIGHTS",
            "RECOMMENDATIONS",
            "NEXT STEPS",
            "CONCLUSION",
        ]
    return ["INTRODUCTION", "DETAILED ANALYSIS", "CONCLUSION"]
def extract_final_block(text: str) -> str:
    m = re.search(r"<final>([\s\S]*?)</final>", text, flags=re.IGNORECASE)
    if m:
        cleaned_text = m.group(1).strip()
    else:
        cleaned_text = text
    preamble_patterns = [
        r"^(?:note:|okay,|hmm,|internal|let me|i (?:will|’ll)|as an ai|thinking|plan:|here is your report|the following is|i have prepared|i am presenting|based on the provided information|below is the report|i hope this meets your requirements|this report outlines|this is the final report).*?$",
        r"^(?:Here is the report|I have compiled the report|The report is provided below|This is the requested report).*?$", # More specific preambles
        r"^(?:Please find the report below|Here's the report).*?$"
    ]
    for pattern in preamble_patterns:
        cleaned_text = re.sub(pattern, "", cleaned_text, flags=re.IGNORECASE | re.MULTILINE).strip()
    cleaned_text = re.sub(r"(?m)^\s*[-*•]\s+", "", cleaned_text)
    cleaned_text = re.sub(r"[#`*_]{1,3}", "", cleaned_text)
    headers = [
        "EXECUTIVE SUMMARY","INTRODUCTION","DETAILED ANALYSIS","CURRENT TRENDS AND DEVELOPMENTS",
        "IMPLICATIONS AND RECOMMENDATIONS","CONCLUSION","ABSTRACT","METHODOLOGY","FINDINGS",
        "DISCUSSION","OVERVIEW","KEY INSIGHTS","RECOMMENDATIONS","NEXT STEPS"
    ]
    sorted_headers = sorted(headers, key=len, reverse=True)
    first_pos = -1
    for h in sorted_headers:
        match = re.search(r'\b' + re.escape(h) + r'\b', cleaned_text, flags=re.IGNORECASE)
        if match:
            if first_pos == -1 or match.start() < first_pos:
                first_pos = match.start()
    if first_pos >= 0:
        cleaned_text = cleaned_text[first_pos:].strip()
    return cleaned_text

Ecco come ogni funzione si inserisce nella pipeline:

  • sections_for_format() function: questa funzione genera intestazioni di sezione su misura per il formato del report selezionato (ad es. executive, detailed, academic). Così ogni report rispetta standard professionali e soddisfa le aspettative dell'utente.
  • extract_final_block() function: questo passaggio di post-processing isola il contenuto centrale del report. Estrae il testo tra i tag <final> e rimuove preamboli, frasi di riempimento e artefatti markdown. Rileva anche la prima intestazione standard (ad es. "INTRODUCTION", "SUMMARY") per eliminare testo introduttivo non pertinente, assicurando che l'output finale sia pulito, strutturato e pronto all'uso.

Queste funzioni di supporto sono fondamentali per mantenere output coerenti e di alta qualità tra diversi formati di report e scenari di ricerca per un modello "pensante" come Jan-v1.

Passo 3: Configurare le impostazioni

Definiamo ora il sistema di configurazione principale che gestisce i parametri del modello, le impostazioni delle API e le preferenze di ricerca. Questo approccio modulare garantisce personalizzazione facile e prestazioni ottimali.

Passo 3.1: Configurazione della ricerca

Questo passaggio definisce un sistema di configurazione e un caricatore di modelli per un'inferenza locale efficiente con il modello Jan-v1. Il sistema è progettato per supportare attività di ricerca riproducibili in ambienti solo CPU, come laptop o Colab Pro, mantenendo flessibilità e sicurezza.

@dataclass
class ResearchConfig:
    model_path: str = "models/Jan-v1-4B-Q4_K_M.gguf" #change as per your location
    max_tokens: int = 2048
    temperature: float = 0.6
    top_p: float = 0.95
    top_k: int = 20
    context_length: int = 4096
    search_api_key: str = os.getenv("SERPER_API_KEY", "")
    search_engine: str = "serper"  

Ecco una panoramica della configurazione del codice sopra:

  • Parametri centralizzati: ResearchConfig è una @dataclass che centralizza tutte le configurazioni dell'LLM e del motore di ricerca, consentendo un tuning semplice per scenari di ricerca diversi.
  • Iperparametri di inferenza: Parametri come temperature, top_p e top_k sono impostati per bilanciare creatività e aderenza ai fatti, importante per output orientati alla ricerca.
  • Memoria e contesto: context_length è impostato a 4096 token, ottimizzato per le capacità di ragionamento long-form di Jan-v1.
  • Integrazione API: search_api_key abilita l'integrazione con Serper per un arricchimento della ricerca web in tempo reale.

Passo 3.2: Deep Research assistant 

In questo passaggio definiamo una classe Deep Research assistant che funge da controller principale per tutte le attività di ricerca, astraendo la gestione di basso livello di modello e configurazioni ed esponendo interfacce di alto livello per generazione, ricerca e sintesi.

class DeepResearchAssistant:
    def __init__(self, config: ResearchConfig):
        self.config = config
        self.llm = None
        self.demo_mode = False
    def load_model(self):
        try:
            if not os.path.exists(self.config.model_path):
                print(f"Model file not found: {self.config.model_path}")
                return False
            file_size_gb = os.path.getsize(self.config.model_path) / (1024**3)
            if file_size_gb < 1.0:
                print(f"Model file too small ({file_size_gb:.1f} GB).")
                return False
            self.llm = Llama(
                model_path=self.config.model_path,
                n_ctx=self.config.context_length,
                verbose=False,
                n_threads=max(1, min(4, os.cpu_count() // 2)),
                n_gpu_layers=0,
                use_mmap=True,
                use_mlock=False,
                n_batch=128,
                f16_kv=True,
            )
            test = self.llm("Hi", max_tokens=3, temperature=0.1, echo=False)
            ok = bool(test and 'choices' in test)
            print("Model loaded " if ok else "Model loaded but test generation failed ")
            return ok
        except Exception as e:
            print(f"Model loading failed: {e}")
            return False

DeepResearchAssistant è l'orchestratore principale che gestisce il caricamento del modello, la validazione della configurazione e fornisce un'interfaccia pulita per le operazioni di ricerca. Questo sistema di configurazione include gestione degli errori e validazioni, assicurando che il modello venga caricato correttamente e sia pronto per i compiti di ricerca. Ecco le caratteristiche chiave della classe:

  • Inizializzazione del modello: il metodo load_model() inizializza in sicurezza il modello GGUF di Jan-v1 usando llama-cpp-python, ottimizzato per l'inferenza su CPU locale.
  • Controlli di validazione: verifichiamo anzitutto il percorso del modello e la dimensione minima del file per intercettare file mancanti o corrotti.
  • Memory mapping: usiamo use_mmap=True per abilitare un caricamento efficiente basato su disco senza consumare tutta la RAM.
  • Inferenza ottimizzata: configuriamo anche thread, dimensione batch e abilitiamo f16_kv=True per ridurre l'uso di memoria. Il parametro n_gpu_layers=0 garantisce l'esecuzione solo su CPU.
  • Sanity test: eseguiamo un test leggero inviando un prompt dopo il caricamento per confermare che il modello risponda correttamente.

Questa configurazione garantisce un'inferenza locale rapida e leggera, ideale per eseguire Jan-v1 su laptop o Colab senza GPU.

Passo 3.3: Generare una risposta

Successivamente, implementeremo il sistema centrale di generazione delle risposte insieme alle capacità di ricerca web che alimentano la nostra automazione della ricerca. Per questo usiamo i servizi API di Serper come mostrato di seguito:

def generate_response(self, prompt: str, max_tokens: int = None, extra_stops: Optional[List[str]] = None) -> str:
        if not self.llm:
            return "Model not loaded."
        stops = ["</s>", "<|im_end|>", "<|endoftext|>"]
        if extra_stops:
            stops.extend(extra_stops)
        mt = max_tokens or self.config.max_tokens
        try:
            resp = self.llm(
                prompt,
                max_tokens=mt,
                temperature=self.config.temperature,
                top_p=self.config.top_p,
                top_k=self.config.top_k,
                stop=stops,
                echo=False
            )
            return resp["choices"][0]["text"].strip()
        except Exception as e:
            return f"Error generating response: {str(e)}"
    async def search_web(self, query: str, num_results: int = 10) -> List[Dict]:
        if self.config.search_api_key:
            return await self.search_serper(query, num_results)
        return []
    async def search_serper(self, query: str, num_results: int) -> List[Dict]:
        url = "https://google.serper.dev/search"
        payload = {"q": query, "num": num_results}
        headers = {"X-API-KEY": self.config.search_api_key, "Content-Type": "application/json"}
        async with aiohttp.ClientSession() as session:
            async with session.post(url, json=payload, headers=headers) as response:
                data = await response.json()
                results = []
                for item in data.get('organic', []):
                    results.append({
                        'title': item.get('title', ''),
                        'url': item.get('link', ''),
                        'snippet': item.get('snippet', ''),
                        'source': 'web'
                    })
                return results
    def generate_search_queries(self, topic: str, focus_area: str, depth: str) -> List[str]:
        counts = {'surface': 5, 'moderate': 8, 'deep': 15, 'comprehensive': 25}
        n = counts.get(depth, 8)
        base = [
            f"{topic} overview",
            f"{topic} recent developments",
            f"{topic} academic studies",
            f"{topic} case studies",
            f"{topic} policy and regulation",
            f"{topic} technical approaches",
            f"{topic} market analysis",
            f"{topic} statistics and data",
        ]
        return base[:n]

Il codice sopra scompone la funzionalità principale come segue:

  • generate_response() function: questa funzione gestisce tutte le interazioni con il modello Jan-v1 con corretta gestione degli errori e sequenze di stop configurabili. Garantisce generazione di testo coerente e di alta qualità per la sintesi della ricerca.
  • search_web() e search_serper() functions: queste funzioni implementano la ricerca web asincrona usando l'API Serper, permettendo ricerche veloci e in parallelo su più query. Il design async evita blocchi dell'interfaccia durante le operazioni di ricerca.
  • generate_search_queries() function: per creare query di ricerca diversificate in base alla profondità della ricerca e al topic di interesse, implementiamo la funzione generate_search_queries(). Garantisce copertura completa del soggetto, dagli overview e approcci tecnici ai case study, regolamentazione e analisi di mercato. Consente di scalare il numero di query da 5 a 25 a seconda della profondità scelta (surface, moderate, deep o comprehensive).

Questo sistema di generazione delle risposte costituisce il nucleo della nostra automazione della ricerca, combinando le capacità di ragionamento di Jan-v1 con la ricerca web in tempo reale per creare report di ricerca.

Passo 3.4: Sintetizzare la ricerca

Implementeremo ora un motore di sintesi della ricerca che trasforma i risultati grezzi in report rifiniti e professionali.

def synthesize_research(self, topic: str, search_results: List[Dict], focus_area: str, report_format: str) -> str:
        context_lines = []
        for i, result in enumerate(search_results[:20]):
            title = result.get("title", "")
            snippet = result.get("snippet", "")
            context_lines.append(f"Source {i+1} Title: {title}\nSource {i+1} Summary: {snippet}")
        context = "\n".join(context_lines)
        sections = sections_for_format(report_format)
        sections_text = "\n".join(sections)
        synthesis_prompt = f"""
You are an expert research analyst. Write the final, polished report on: "{topic}" for a professional, real-world audience.
***CRITICAL INSTRUCTIONS:***
- Your entire response MUST be the final report, wrapped **EXACTLY** inside <final> and </final> tags.
- DO NOT output any text, thoughts, or commentary BEFORE the <final> tag or AFTER the </final> tag.
- DO NOT include any conversational filler, internal thoughts, or commentary about the generation process (e.g., "As an AI...", "I will now summarize...", "Here is your report:").
- DO NOT use markdown formatting (e.g., #, ##, *, -).
- DO NOT use bullet points or lists.
- Maintain a formal, academic/professional tone throughout.
- Ensure the report is complete and self-contained.
- Include the following section headers, in this order, and no others:
{sections_text}
Guidance:
- Base your writing strictly on the Research Notes provided below. If the notes lack specific data, write a careful, methodology-forward analysis without inventing facts or numbers.
Research Notes:
{context}
Now produce ONLY the final report:
<final>
...your report here...
</final>
"""
        raw = self.generate_response(synthesis_prompt, max_tokens=1800, extra_stops=["</final>"])
        final_report = extract_final_block(raw)
        final_report = re.sub(r"(?m)^\s*[-*•]\s+", "", final_report)
        final_report = re.sub(r"[#`*_]{1,3}", "", final_report)
        first = next((h for h in sections if h in final_report), None)
        if first:
            final_report = final_report[final_report.find(first):].strip()
        return final_report

Il codice sopra sfrutta le avanzate capacità di ragionamento di Jan-v1 per trasformare risultati di ricerca web grezzi in report professionali che normalmente richiederebbero ore di ricerca e scrittura manuale. Vediamo nel dettaglio le funzionalità chiave:

  • Incorporamento contestuale: il codice trasforma i risultati non strutturati in note di ricerca testuali strutturate concatenando titoli e sommari in formato numerato. Questo assicura alta densità informativa e riduce la frammentazione del contesto quando passato al modello.
  • Costruzione dinamica del prompt: usando la funzione sections_for_format() generiamo le intestazioni di sezione del report in base al formato selezionato, inserendole nel prompt con regole di generazione rigide per vincolare il comportamento del modello.
  • Riduzione delle allucinazioni: imponendo l'uso dei tag <final>...</final> e vietando impalcature conversazionali, si impedisce al modello di introdurre testo irrilevante, sintesi speculative o inquadramenti ridondanti.
  • Post-processing e normalizzazione: dopo il decoding, il report viene ripulito con filtri regex per rimuovere artefatti markdown, marcatori di lista e formattazioni spurie. La funzione assicura inoltre che l'output inizi dalla prima intestazione valida per migliorare la coerenza strutturale.

Passo 4: Flusso principale dell'app Streamlit

Infine, costruiremo l'interfaccia utente completa che riunisce tutte le nostre capacità di ricerca in un'applicazione interattiva.

def main():
    st.set_page_config(page_title="Deep Research Assistant", page_icon="", layout="wide")
    st.markdown("""
    <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.25rem; border-radius: 10px; color: white; text-align: center; margin-bottom: 1rem;">
        <h1 style="margin:0;"> Deep Research Assistant</h1>
    </div>
    """, unsafe_allow_html=True)
    if 'research_assistant' not in st.session_state:
        config = ResearchConfig()
        st.session_state.research_assistant = DeepResearchAssistant(config)
        st.session_state.model_loaded = st.session_state.research_assistant.load_model()
        st.session_state.research_results = None
    if not st.session_state.research_assistant.config.search_api_key:
        st.warning("SERPER_API_KEY not found in environment. Using fallback demo results.")
    st.header("Research Configuration")
    research_topic = st.text_area(
        "Research Topic",
        placeholder="e.g., Impact of artificial intelligence on healthcare efficiency and patient outcomes",
        height=100
    )
    col1a, col1b = st.columns(2)
    with col1a:
        research_depth = st.selectbox(
            "Research Depth",
            ["surface", "moderate", "deep", "comprehensive"],
            index=1,
            format_func=lambda x: {
                "surface": "Surface (5-8 sources)",
                "moderate": "Moderate (10-15)",
                "deep": "Deep Dive (20-30)",
                "comprehensive": "Comprehensive (40+)"
            }[x]
        )
        focus_area = st.selectbox("Focus Area", ["general", "academic", "business", "technical", "policy"], index=0, format_func=str.title)
    with col1b:
        time_frame = st.selectbox(
            "Time Frame",
            ["current", "recent", "comprehensive"],
            index=1,
            format_func=lambda x: {"current":"Current (≤6 months)","recent":"Recent (≤2 years)","comprehensive":"All time"}[x]
        )
        report_format = st.selectbox(
            "Report Format",
            ["executive", "detailed", "academic", "presentation"],
            index=1,
            format_func=lambda x: {
                "executive":"Executive Summary",
                "detailed":"Detailed Analysis",
                "academic":"Academic Style",
                "presentation":"Presentation Format"
            }[x]
        )
    if st.button("Start Deep Research", type="primary", use_container_width=True):
        if not st.session_state.model_loaded:
            st.error("Model not loaded. See terminal logs for details.")
        elif not research_topic.strip():
            st.error("Please enter a research topic.")
        else:
            start_research(research_topic, research_depth, focus_area, time_frame, report_format)
    if st.session_state.research_results:
        display_research_results(st.session_state.research_results)
def start_research(topic: str, depth: str, focus: str, timeframe: str, format_type: str):
    assistant = st.session_state.research_assistant
    progress_bar = st.progress(0)
    status_text = st.empty()
    try:
        status_text.text("Generating search queries...")
        progress_bar.progress(15)
        queries = assistant.generate_search_queries(topic, focus, depth)
        status_text.text("Searching sources...")
        progress_bar.progress(40)
        all_results = []
        for i, query in enumerate(queries):
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            results = loop.run_until_complete(assistant.search_web(query, 5))
            all_results.extend(results)
            loop.close()
            progress_bar.progress(40 + int(((i + 1) / max(1, len(queries))) * 30))
            time.sleep(0.05)
        status_text.text("Synthesizing report...")
        progress_bar.progress(80)
        research_report = assistant.synthesize_research(topic, all_results, focus, format_type)
        status_text.text("Done.")
        progress_bar.progress(100)
        st.session_state.research_results = {
            'topic': topic,
            'report': research_report,
            'sources': all_results,
            'queries': queries,
            'config': {'depth': depth, 'focus': focus, 'timeframe': timeframe, 'format': format_type},
            'timestamp': datetime.now()
        }
        time.sleep(0.3)
        status_text.empty()
        progress_bar.empty()
    except Exception as e:
        st.error(f"Research failed: {str(e)}")
        status_text.empty()
        progress_bar.empty()
def display_research_results(results: Dict):
    st.header("Research Report")
    st.subheader(f"Topic: {results['topic']}")
    st.markdown(
        f'<div style="background:#f8f9ff;padding:1rem;border-radius:10px;border:1px solid #e1e8ed;">{results["report"]}</div>',
        unsafe_allow_html=True,
    )
    with st.expander("Sources", expanded=False):
        for i, source in enumerate(results['sources'][:12]):
            st.markdown(f"""
            <div style="background:#fff;padding:0.75rem;border-radius:8px;border:1px solid #e1e8ed;margin:0.4rem 0;">
                <h4 style="margin:0 0 .25rem 0;">{source['title']}</h4>
                <p style="margin:0 0 .25rem 0;">{source['snippet']}</p>
                <small><a href="{source['url']}" target="_blank">{source['url']}</a></small>
            </div>
            """, unsafe_allow_html=True)
    st.markdown("### Export")
    c1, c2, c3 = st.columns(3)
    with c1:
        report_text = f"Research Report: {results['topic']}\n\n{results['report']}"
        st.download_button("Download Text", data=report_text, file_name=f"research_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", mime="text/plain")
    with c2:
        json_data = json.dumps(results, default=str, indent=2)
        st.download_button("Download JSON", data=json_data, file_name=f"research_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", mime="application/json")
    with c3:
        if st.button("Start New Research"):
            st.session_state.research_results = None
            st.experimental_rerun()
if __name__ == "__main__":
    main()

Il codice sopra è suddiviso in livelli, dal caricamento del modello e raccolta degli input utente alla generazione delle query, ricerca web e sintesi del report finale. Analizziamo questi livelli nel dettaglio:

  • Livello UI (frontend Streamlit): l'app presenta un'intestazione con gradiente e un layout responsivo per un'esperienza utente curata. L'interfaccia resta intuitiva pur offrendo controllo granulare sui parametri di ricerca, risultando adatta sia a utenti tecnici sia non tecnici.
  • Pipeline configurabile: gli utenti possono personalizzare il workflow di ricerca con selettori per profondità, area di focus (general, academic, business), finestra temporale e stile di output (executive, academic, ecc.).
  • Gestione stateful dell'agente: DeepResearchAssistant viene inizializzato una sola volta per sessione usando st.session_state, preservando lo stato del modello ed evitando ricaricamenti ripetuti. 
  • Feedback in tempo reale e ricerca asincrona: l'avanzamento è tracciato con barre di progresso dinamiche di Streamlit e aggiornamenti di stato. La ricerca web è eseguita in modo asincrono tramite asyncio, garantendo esecuzione parallela delle query senza bloccare la reattività della UI.
  • Ciclo di query e sintesi: le query di ricerca sono generate automaticamente in base a topic e profondità tramite una strategia di prompt engineering. I risultati recuperati sono aggregati e passati al modello Jan-v1 per la generazione di un report strutturato.
  • Visualizzazione ed export dell'output: il report finale è renderizzato in un blocco Markdown con citazioni espandibili. Gli utenti possono esportare il report in formato TXT o JSON o rieseguire il workflow con un clic.

Per provarlo, salva il codice come app.py e avvia:

streamlit run app.py

Conclusioni

Questo Deep Research Assistant dimostra la potenza pratica delle capacità avanzate di ragionamento di Jan-v1 nell'automazione di workflow di ricerca complessi. Il sistema combina con successo:

  • Automazione intelligente: consente una ricerca end-to-end, dalla generazione delle query alla sintesi del report professionale, eliminando ore di lavoro manuale.
  • Design scalabile: la funzione di profondità configurabile della ricerca si adatta da rapide panoramiche ad analisi complete in base alle esigenze dell'utente.
  • Qualità professionale: aiuta a generare report adatti a briefing esecutivi, ricerca accademica e documentazione tecnica.
  • Accessibilità per l'utente: questa interfaccia rende disponibili capacità avanzate di ricerca AI a utenti di ogni livello tecnico.

L'applicazione mostra come l'accuratezza SimpleQA del 91,1% di Jan-v1 e le sue capacità di ragionamento avanzate possano essere usate per costruire strumenti pronti per la produzione che portano valore immediato in workflow di ricerca, analisi e creazione di contenuti.

Aashi Dutt

Autore

Aashi Dutt

Sono una Google Developers Expert in ML (Gen AI), una Kaggle 3x Expert e una Women Techmakers Ambassador con oltre 3 anni di esperienza nel tech. Ho co-fondato una startup health-tech nel 2020 e sto conseguendo un master in informatica al Georgia Tech, con specializzazione in machine learning.

Argomenti

Correlato