Curso
El equipo de Jan ha publicado recientemente Jan-v1, un modelo avanzado de razonamiento agencial que introduce sofisticadas capacidades de automatización de la investigación mediante una mejor utilización de las herramientas y la resolución de problemas en varios pasos.
En este tutorial me centraré en las capacidades únicas de razonamiento agencial de Jan-v1, demostrando cómo actúa como un complejo flujo de trabajo de investigación a través de una interfaz Streamlit implementada localmente y alimentada por llama.cpp.
En este tutorial, explicaré cómo:
- Implementa Jan-v1 localmente con cuantificación GGUF para obtener un rendimiento y una privacidad óptimos.
- Crea una aplicación Streamlit para la investigación automatizada con seguimiento del progreso en tiempo real.
- Implementa la integración inteligente de búsquedas web con generación dinámica de consultas.
- Crea un informe profesional con una estructura específica para cada formato.
- Aplica un procesamiento de texto avanzado para garantizar una documentación limpia y lista para su uso empresarial.
Al final, tu aplicación tendrá este aspecto:
¿Qué es Jan-v1?
Jan-v1 es un modelo de 4B parámetros basado en los fundamentos de Lucy y Qwen3-4B-thinking, diseñado para flujos de trabajo agenticos como el uso de herramientas, el razonamiento en varios pasos y la investigación automatizada. A diferencia de los LLM convencionales, Jan-v1 destaca en la coordinación entre herramientas, fuentes de datos y pasos de razonamiento mediante un andamiaje estructurado y recompensas alineadas con el comportamiento.
Estas son algunas de las características principales de este modelo:
- Arquitectura preparada para agentes: Este modelo está optimizado para la integración en el mundo real y gestiona la búsqueda web, la síntesis y la generación de informes estructurados mediante un razonamiento mejorado y API de herramientas.
- Eficiencia: A pesar de su pequeño tamaño, Jan-v1 ofrece un rendimiento típico de modelos mucho más grandes, gracias a un entrenamiento RL específico que hace hincapié en resultados centrados y orientados a objetivos.
- Implementación con prioridad en la privacidad: Su formato GGUF compacto permite una inferencia rápida y local, lo que resulta ideal para tareas de investigación privadas y de baja latencia en ordenadores portátiles o CPU.
- Entrenamiento basado en la investigación: Jan-v1 utiliza funciones de recompensa novedosas que equilibran la informatividad con la brevedad, lo cual es fundamental para tareas de alto riesgo como el análisis y la elaboración de informes, minimizando así las alucinaciones y la verbosidad.
- Basado en Qwen3-4B-thinking: Este modelo hereda el sólido razonamiento en cadena y el control de herramientas de la fusión del modo de pensamiento de Qwen3, lo que lo hace especialmente eficaz para los procesos de automatización de la investigación.
Configuración de Jan-v1 localmente
Para este proyecto, implementaremos Jan-v1 localmente utilizando Llama.cpp
con la versión cuantificada GGUF para obtener un rendimiento óptimo y una eficiencia de recursos.
Paso 1: Instala las dependencias
En primer lugar, asegúrate de que tienes los paquetes Python necesarios:
pip install llama-cpp-python==0.3.15
pip install streamlit aiohttp
Este comando instala todas las dependencias básicas necesarias para la integración del modelo de IA, la creación de la interfaz web y las operaciones web asíncronas.
Paso 2: Descargar la versión GGUF de enero-v1
Antes de descargar el modelo, crea un nuevo directorio llamado « models/
» con el siguiente comando:
mkdir -p models
A continuación, descarga el modelo utilizando el siguiente 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
Al completar este paso, has descargado correctamente el modelo Jan-v1 4B en formato GGUF y lo has colocado en el directorio models/
. Esta versión cuantificada ofrece un equilibrio entre rendimiento y eficiencia de memoria, lo que la hace ideal para la inferencia local en CPU o Colab.
Paso 3: Configura la clave API de Serper
Regístrate o inicia sesión en tu cuenta Serper: https://serper.dev/api-keys y selecciona la pestaña «Claves API». A continuación, copia la clave API predeterminada para esta demostración. Configuremos esta clave como variable de entorno de la siguiente manera:
export SERPER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxx
Ya estás listo para cargar e interactuar con el modelo en tu canalización de asistente de investigación.
Demostración: Asistente de investigación profunda con Jan-v1
Construyamos un aplicación Streamlit que demuestre las capacidades avanzadas de investigación de Jan-v1, con generación inteligente de consultas, búsqueda web automatizada (utilizando Serper) y síntesis de informes profesionales.
Paso 1: Importa las bibliotecas.
Comenzamos importando todas las bibliotecas necesarias para nuestro asistente de investigación. Estos soportan todo, desde la creación de interfaces web hasta la búsqueda web asíncrona y la integración de modelos de IA locales.
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
Importamos todas las bibliotecas principales utilizadas en toda la aplicación. Entre ellos se incluyen componentes de la interfaz de usuario (Streamlit), herramientas de concurrencia (asyncio, aiohttp) y el backend LLM (llama_cpp). También incorporaremos utilidades para el análisis sintáctico, la tipificación y el manejo de fechas.
Paso 2: Funciones auxiliares
Ahora implementaremos algunas funciones auxiliares que se encargarán del formato de los informes y la extracción de contenido. Estas funciones garantizan que los informes generados mantengan una calidad profesional y una estructura coherente.
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
A continuación se explica cómo encaja cada función en el proceso:
sections_for_format()
función: Esta función genera encabezados de sección adaptados al formato de informe seleccionado (por ejemplo, ejecutivo, detallado, académico). Esto garantiza que cada informe cumpla con los estándares profesionales y satisfaga las expectativas de los usuarios.extract_final_block()
función: Este paso de posprocesamiento aísla el contenido principal del informe. Extrae el texto entre las etiquetas «» y elimina los preámbulos, las frases de relleno y los artefactos de marcado. También detecta el encabezado estándar más temprano (por ejemplo, «INTRODUCCIÓN», «RESUMEN») para eliminar el texto introductorio irrelevante, lo que garantiza que el resultado final sea limpio, estructurado y listo para su uso.
Estas funciones auxiliares son fundamentales para mantener una salida coherente y de alta calidad en diferentes formatos de informes y escenarios de investigación para un modelo de pensamiento como Jan-v1.
Paso 3: Configuración de los ajustes
Establezcamos el sistema de configuración básico que gestiona los parámetros del modelo, los ajustes de la API y las preferencias de investigación. Este enfoque modular garantiza una fácil personalización y un rendimiento óptimo.
Paso 3.1: Configuración de la investigación
Este paso define un sistema de configuración y un cargador de modelos para una inferencia local eficiente con el modelo Jan-v1. El sistema está diseñado para admitir tareas de investigación reproducibles en entornos que solo utilizan CPU, como ordenadores portátiles o Colab Pro, al tiempo que mantiene la flexibilidad y la seguridad.
@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"
A continuación se ofrece una descripción general de la configuración del código anterior:
- Parámetros centralizados:
ResearchConfig
es un panel de control (@dataclass
) que centraliza todas las configuraciones de LLM y del motor de búsqueda, lo que permite un fácil ajuste para diferentes escenarios de investigación. - Hiperparámetros de inferencia: Parámetros como «
temperature
» (creatividad), «top_p
» (conocimiento) y «top_k
» (conocimiento) se establecen para equilibrar la creatividad con la base factual, lo cual es importante para los resultados orientados a la investigación. - Memoria y contexto: El tamaño máximo de la pila (
context_length
) está establecido en 4096 tokens, lo que está optimizado para las capacidades de razonamiento de formato largo de Jan-v1. - Integración API:
search_api_key
permite la integración con Serper para mejorar las búsquedas web en tiempo real.
Paso 3.2: Asistente de investigación profunda
En este paso, definimos una clase Deep Research assistant que actúa como controlador central de todas las tareas de investigación, abstrayendo el modelo de bajo nivel y la gestión de la configuración, al tiempo que expone interfaces de alto nivel para la generación, la búsqueda y la síntesis.
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
La clase DeepResearchAssistant
es el principal coordinador que gestiona la carga de modelos, la validación de la configuración y proporciona una interfaz limpia para las operaciones de investigación. Este sistema de configuración proporciona gestión de errores y validación, lo que garantiza que el modelo se cargue correctamente y esté listo para las tareas de investigación. Estas son las características principales de esta clase:
- Inicializar modelo: El método «
load_model()
» inicializa de forma segura el modelo Jan-v1 GGUF utilizando «llama-cpp-python
», optimizado para la inferencia de la CPU local. - Comprobaciones de validación: En primer lugar, verificamos la ruta del modelo y el tamaño mínimo del archivo para detectar archivos que faltan o están dañados.
- Mapeo de memoria: A continuación, utilizamos
use_mmap=True
para habilitar una carga eficiente respaldada por disco sin consumir toda la RAM. - Inferencia optimizada: También configuramos subprocesos, tamaño de lote y habilitamos la optimización de memoria (
f16_kv=True
) para reducir el uso de memoria. El parámetro «n_gpu_layers=0
» garantiza la ejecución solo en la CPU. - Prueba de cordura: Realizamos una prueba de integridad que envía un mensaje ligero después de la carga para confirmar que el modelo responde correctamente.
Esta configuración garantiza una inferencia local rápida y ligera, ideal para ejecutar Jan-v1 en ordenadores portátiles o Colab sin GPU.
Paso 3.3: Generar una respuesta
A continuación, implementaremos el sistema central de generación de respuestas junto con las capacidades de búsqueda web que impulsan nuestra automatización de la investigación. Para ello, utilizamos los servicios de la API de Serper, tal y como se muestra a continuación:
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]
El código anterior desglosa la funcionalidad principal de la siguiente manera:
generate_response()
función: Esta función gestiona todas las interacciones del modelo Jan-v1 con un manejo adecuado de los errores y secuencias de parada configurables. Esto garantiza una generación de texto coherente y de alta calidad para la síntesis de investigaciones.search_web()
y las funcionessearch_serper()
: Estas funciones implementan búsquedas web asíncronas mediante la API Serper, lo que permite realizar búsquedas rápidas y paralelas en varias consultas. El diseño asíncrono evita que la interfaz de usuario se bloquee durante las operaciones de investigación.generate_search_queries()
función: Para crear consultas de búsqueda diversas basadas en la profundidad de la investigación y el tema central, implementamos la función «generate_search_queries()
» (Búsqueda avanzada). Garantiza una cobertura exhaustiva del tema de investigación, desde descripciones generales y enfoques técnicos hasta casos prácticos, normativas y análisis de mercado. Permite al usuario escalar el número de consultas de 5 a 25 en función de la profundidad elegida (superficial, moderada, profunda o exhaustiva).
Este sistema de generación de respuestas constituye el núcleo de nuestra automatización de la investigación, ya que combina las capacidades de razonamiento de Jan-v1 con la búsqueda web en tiempo real para crear informes de investigación.
Paso 3.4: Sintetizar la investigación
A continuación, implementaremos un motor de síntesis de investigación que transforma los resultados de búsqueda sin procesar en informes pulidos y profesionales.
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
El código anterior aprovecha las capacidades avanzadas de razonamiento de Jan-v1 para transformar los resultados sin procesar de una búsqueda web en informes profesionales que normalmente requerirían horas de investigación y redacción manuales. Veamos en detalle las capacidades clave:
- Incrustación contextual: El código anterior transforma los resultados de búsqueda no estructurados en notas de investigación textuales estructuradas, concatenando títulos y resúmenes en un formato numerado. Esto garantiza una alta densidad de información y minimiza la fragmentación del contexto cuando se transfiere al modelo.
- Construcción dinámica de mensajes: Mediante la función «
sections_for_format()
», generamos encabezados de sección del informe adaptados al formato de salida seleccionado, que se insertan en el mensaje con reglas de generación estrictas para limitar el comportamiento del modelo. - Mitigación de alucinaciones: Al imponer el uso de etiquetas de
...
y prohibir los andamios conversacionales, se evita que el modelo introduzca texto irrelevante, resúmenes especulativos o enmarcamientos redundantes. - Postprocesamiento y normalización: Después de la decodificación, el informe se limpia con filtros de
regex
para eliminar artefactos de marcado, marcadores de lista y formateo erróneo. La función también garantiza que la salida comience desde el primer encabezado de sección válido para mejorar la coherencia estructural.
Paso 4: Flujo principal de la aplicación Streamlit
Por último, crearemos la interfaz de usuario completa que reunirá todas nuestras capacidades de investigación en una aplicación interactiva.
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()
El código anterior se divide en capas, desde la carga del modelo y la recopilación de datos del usuario hasta la generación de consultas, la búsqueda web y la síntesis del informe final. Analicemos estas capas en detalle:
- Capa de interfaz de usuario (frontend Streamlit): La aplicación cuenta con un encabezado con estilo degradado y un diseño adaptable para ofrecer una experiencia de usuario impecable. La interfaz sigue siendo intuitiva y ofrece un control preciso de los parámetros de búsqueda, lo que la hace ideal tanto para usuarios técnicos como no técnicos.
- Pipeline configurable: Los usuarios pueden personalizar el flujo de trabajo de investigación con selectores de profundidad, área de interés (general, académica, empresarial), periodo de tiempo y estilo de resultado (ejecutivo, académico, etc.).
- Gestión de agentes con estado: El objeto de caché de modelo (
DeepResearchAssistant
) se inicializa una vez por sesión mediantest.session_state
, lo que conserva el estado del modelo y evita que se cargue repetidamente. - Comentarios en tiempo real y búsqueda asíncrona: El progreso se supervisa con barras de progreso dinámicas de Streamlit y actualizaciones de estado. La búsqueda web se ejecuta de forma asíncrona a través de
asyncio
, lo que garantiza la ejecución paralela de las consultas sin bloquear la capacidad de respuesta de la interfaz de usuario. - Bucle de consulta y síntesis: Las consultas de búsqueda se generan automáticamente en función del tema y la profundidad utilizando una estrategia de ingeniería de indicaciones. Los resultados obtenidos se agregan y se envían al modelo Jan-v1 para generar un informe estructurado.
- Visualización y exportación de resultados: El informe final se presenta en un bloque Markdown con citas ampliables. Los usuarios pueden exportar el informe en formato
TXT
oJSON
, o volver a ejecutar el flujo de trabajo con un solo clic.
Para probarlo tú mismo, guarda el código como app.py
y ejecútalo:
streamlit run app.py
Conclusión
Este asistente de investigación profunda demuestra el poder práctico de las capacidades avanzadas de razonamiento de Jan-v1 para automatizar flujos de trabajo de investigación complejos. El sistema combina con éxito:
- Automatización inteligente: Permite realizar investigaciones integrales, desde la generación de consultas hasta la síntesis de informes profesionales, lo que elimina horas de trabajo manual.
- Diseño escalable: La función de profundidad de investigación configurable se adapta desde resúmenes rápidos hasta análisis exhaustivos en función de las necesidades de los usuarios.
- de calidad profesional: Ayuda a generar informes adecuados para reuniones ejecutivas, investigaciones académicas y documentación técnica.
- Accesibilidad del usuario: Esta interfaz pone a disposición de los usuarios, independientemente de sus conocimientos técnicos, funciones avanzadas de investigación en IA.
La aplicación muestra cómo la precisión del 91,1 % de SimpleQA de Jan-v1 y sus capacidades de razonamiento mejoradas pueden utilizarse para crear herramientas listas para la producción que aportan un valor inmediato en los flujos de trabajo de investigación, análisis y creación de contenidos.

Soy una Google Developers Expert en ML(Gen AI), una Kaggle 3x Expert y una Women Techmakers Ambassador con más de 3 años de experiencia en tecnología. Cofundé una startup de tecnología sanitaria en 2020 y estoy cursando un máster en informática en Georgia Tech, especializándome en aprendizaje automático.