import os from pathlib import Path import streamlit as st from dotenv import load_dotenv from langchain_core.messages import AIMessage, HumanMessage load_dotenv() UPLOAD_DIR = Path("/tmp/agent_uploads") UPLOAD_DIR.mkdir(parents=True, exist_ok=True) os.environ["AGENT_UPLOAD_DIR"] = str(UPLOAD_DIR) from agent import build_agent, run_agent_stream # noqa: E402 # ── Page config ─────────────────────────────────────────────────────────────── st.set_page_config( page_title="Agente Autónomo IA", page_icon="🤖", layout="wide", initial_sidebar_state="expanded", ) # ── Custom CSS ──────────────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) GROQ_MODELS = { "LLaMA 3.3 70B (recomendado)": "llama-3.3-70b-versatile", "LLaMA 3.1 8B (rápido)": "llama-3.1-8b-instant", } EXAMPLES = [ "Precio del oro hoy en dólares", "¿Qué tiempo hace en Madrid ahora mismo?", "Calcula los primeros 20 números de Fibonacci", "Busca las últimas noticias sobre inteligencia artificial", "¿Cuánto es 15% de propina sobre 47.80 €?", "Haz una tabla markdown comparando GPT-4o, Claude 3.5 y Gemini 2.0", ] # ── Sidebar ─────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown('

🤖 Agente Autónomo IA

', unsafe_allow_html=True) st.markdown( '

Groq · LangChain · Tavily · Open-Meteo

', unsafe_allow_html=True, ) st.divider() # Model selector st.markdown("**Modelo**") model_label = st.selectbox( "modelo", list(GROQ_MODELS.keys()), index=0, label_visibility="collapsed", ) selected_model = GROQ_MODELS[model_label] st.divider() # API status groq_ok = bool(os.getenv("GROQ_API_KEY")) tavily_ok = bool(os.getenv("TAVILY_API_KEY")) st.markdown("**Estado de APIs**") st.markdown( f'Groq   {"activa" if groq_ok else "falta key"}', unsafe_allow_html=True, ) st.markdown( f'Tavily   {"activa" if tavily_ok else "falta key"}', unsafe_allow_html=True, ) st.divider() # File uploader st.markdown("**Subir archivos**") uploaded = st.file_uploader( "PDF, CSV, TXT o MD", type=["pdf", "csv", "txt", "md"], accept_multiple_files=True, label_visibility="collapsed", ) if uploaded: for f in uploaded: (UPLOAD_DIR / f.name).write_bytes(f.getvalue()) st.success(f"{len(uploaded)} archivo(s) listos para el agente") existing = [f.name for f in UPLOAD_DIR.iterdir() if f.is_file()] if existing: st.markdown("**Archivos disponibles:**") for name in existing: st.code(name, language=None) st.divider() # Tools st.markdown("**Herramientas activas**") tools_info = [ ("🌐", "web_search", "Búsqueda en tiempo real"), ("🐍", "python_repl", "Ejecuta código Python"), ("📄", "read_file", "Lee PDF / CSV / TXT"), ("🌤️", "get_weather", "Tiempo en cualquier ciudad"), ] for icon, name, desc in tools_info: st.markdown( f'
{icon} {name}
{desc}
', unsafe_allow_html=True, ) st.divider() if st.button("🗑️ Limpiar conversación", use_container_width=True): st.session_state.messages = [] if "agent" in st.session_state: del st.session_state["agent"] st.rerun() # ── Main area ───────────────────────────────────────────────────────────────── st.markdown("## Agente Autónomo con Herramientas") st.caption( "Describe tu objetivo y el agente razonará paso a paso, usando las herramientas necesarias para resolverlo." ) # Example buttons st.markdown('

Prueba un ejemplo

', unsafe_allow_html=True) cols = st.columns(3) for i, ex in enumerate(EXAMPLES): if cols[i % 3].button(ex, use_container_width=True, key=f"ex_{i}"): st.session_state["pending_query"] = ex st.rerun() st.divider() # ── Session state ───────────────────────────────────────────────────────────── if "messages" not in st.session_state: st.session_state.messages = [] # Rebuild agent if model changed if st.session_state.get("current_model") != selected_model: st.session_state.pop("agent", None) st.session_state["current_model"] = selected_model if "agent" not in st.session_state: if not groq_ok: st.warning("Configura `GROQ_API_KEY` en `.env` para empezar.", icon="⚠️") st.stop() try: st.session_state.agent = build_agent(model=selected_model, verbose=False) except Exception as e: st.error(f"Error inicializando el agente: {e}") st.stop() # ── Chat history ────────────────────────────────────────────────────────────── for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg.get("steps"): with st.expander(f"🔍 Razonamiento — {len(msg['steps'])} paso(s)", expanded=False): for step in msg["steps"]: st.markdown(f"**🔧 `{step['tool']}`**") if step.get("input"): raw = step["input"] display = next(iter(raw.values())) if isinstance(raw, dict) and len(raw) == 1 else str(raw) st.code(display, language="python" if step["tool"] == "python_repl" else "text") st.markdown("*Resultado:*") st.code(step["output"][:1500], language="text") st.divider() st.markdown(msg["content"]) # ── Input ───────────────────────────────────────────────────────────────────── pending = st.session_state.pop("pending_query", None) user_input = st.chat_input("Escribe tu pregunta u objetivo…") query = pending or user_input if query: st.session_state.messages.append({"role": "user", "content": query}) with st.chat_message("user"): st.markdown(query) # Last 6 messages as history chat_history = [] for m in st.session_state.messages[:-1][-6:]: cls = HumanMessage if m["role"] == "user" else AIMessage chat_history.append(cls(content=m["content"])) with st.chat_message("assistant"): reasoning_box = st.expander("🔍 Razonamiento en vivo", expanded=True) final_placeholder = st.empty() steps: list[dict] = [] final_output = "" live_spinner = None try: for event in run_agent_stream(st.session_state.agent, query, chat_history=chat_history): if event["type"] == "tool_start": with reasoning_box: st.markdown(f"**🔧 Usando `{event['tool']}`**") # Show the input value cleanly raw_input = event["input"] if isinstance(raw_input, dict): display = next(iter(raw_input.values())) if len(raw_input) == 1 else str(raw_input) else: display = str(raw_input) st.code(display, language="python" if event["tool"] == "python_repl" else "text") live_spinner = st.empty() live_spinner.info("⏳ ejecutando…") elif event["type"] == "tool_end": if live_spinner: live_spinner.empty() live_spinner = None with reasoning_box: st.markdown("*Resultado:*") st.code(event["output"][:1500], language="text") st.divider() steps.append( {"tool": event["tool"], "input": "", "output": event["output"]} ) elif event["type"] == "final": final_output = event["output"] final_placeholder.markdown(final_output) elif event["type"] == "error": final_output = f"❌ {event['output']}" final_placeholder.error(final_output) except Exception as e: final_output = f"❌ Error inesperado: {e}" final_placeholder.error(final_output) st.session_state.messages.append( {"role": "assistant", "content": final_output, "steps": steps} )