# Crea un agente de investigación profunda en Python con una API de búsqueda web

> Crea un agente de investigación profunda en 60 líneas de Python: planifica consultas, busca en la página y escribe una respuesta citada con cualquier LLM.

- URL: https://llmwebsearch.com/es/guides/build-a-deep-research-agent/
- Última actualización: 2026-10-22
- Editor: LLMWebSearch (https://llmwebsearch.com)

La investigación profunda es un bucle, no un modelo. Esta guía construye uno que puedes leer en unos minutos: planifica consultas, busca en la web con el texto de la página incluido, guarda cada fuente una sola vez, busca huecos y escribe una respuesta con citas numeradas.

**Respuesta rápida — ¿Cómo creo un agente de investigación profunda en Python?**

Escribe un bucle que pida a un LLM consultas de búsqueda, pase cada una por una API de búsqueda web que devuelva el texto de la página, guarde cada fuente una sola vez, pida consultas de seguimiento y luego escriba una respuesta citada. Con **LLMWebSearch**, cada búsqueda devuelve las 3 primeras páginas en Markdown por un crédito; el script de abajo hace seis búsquedas por pregunta. Desde 1,20 US$/1K búsquedas, sin KYC, con criptomonedas.

## ¿Qué hará el agente?

1. **Plan** — Pide al modelo cuatro consultas cortas que en conjunto cubran la pregunta.
2. **Buscar** — Ejecuta cada consulta con read=3, para que las tres primeras páginas vuelvan en Markdown.
3. **Guarda cada fuente una sola vez** — Almacena los resultados por URL y omite las páginas ya recogidas o sin texto.
4. **Busca huecos** — Muestra al modelo los títulos encontrados hasta ahora y pide dos consultas sobre lo que falta.
5. **Escribe** — Numera hasta 12 fuentes y pide una respuesta que las cite como [1], [2].

## ¿Qué necesito antes de empezar?

Tres cosas:

- **Una clave API de LLMWebSearch** en la variable de entorno `LLMWEBSEARCH_API_KEY`. Las claves aparecen en el panel tras tu primer pack.
- **Cualquier endpoint de chat compatible con OpenAI**, configurado con `LLM_BASE_URL`, `LLM_API_KEY` y `LLM_MODEL`. Funcionan tanto modelos alojados como servidores locales.
- **Python 3.9 o posterior** con el paquete `requests`.

El script usa HTTP simple para ambos servicios, así que puedes cambiar el modelo sin tocar el código de búsqueda.

*Python: deep_research.py*

```python
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
SEARCH_HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
# Any OpenAI-compatible chat endpoint works here: set your own base URL, key and model name.
LLM_URL = os.environ.get("LLM_BASE_URL", "https://llm.example.com/v1") + "/chat/completions"
LLM_HEADERS = {"Authorization": "Bearer " + os.environ.get("LLM_API_KEY", "")}
MODEL = os.environ.get("LLM_MODEL", "your-model-name")


def ask_llm(prompt):
    body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}]}
    resp = requests.post(LLM_URL, headers=LLM_HEADERS, json=body, timeout=120)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


def plan_queries(question, n=4):
    text = ask_llm(f"Write {n} short web search queries, one per line, to answer: {question}")
    queries = [line.strip("-*0123456789. ").strip() for line in text.splitlines()]
    return [q for q in queries if q][:n]


def search(query):
    body = {"q": query, "max_results": 5, "read": 3, "max_chars": 5000}
    resp = requests.post(SEARCH, headers=SEARCH_HEADERS, json=body, timeout=60)
    resp.raise_for_status()
    return resp.json()["results"]


def research(question, rounds=2):
    sources = {}  # url -> result, so the same page is never cited twice
    queries = plan_queries(question)
    for _ in range(rounds):
        for query in queries:
            for r in search(query):
                if r.get("content_status") == "ok" and r["url"] not in sources:
                    sources[r["url"]] = r
        notes = "\n".join(f"- {r['title']}" for r in sources.values())
        queries = plan_queries(f"{question}\nAlready found:\n{notes}\nWhat is still missing?", n=2)
    numbered = list(sources.values())[:12]
    context = "\n\n".join(
        f"[{i}] {r['title']} ({r['url']})\n{r['content']}" for i, r in enumerate(numbered, 1)
    )
    prompt = (
        "Answer the question using only the sources below. Cite them as [1], [2]. "
        "Say so when the sources disagree or do not cover a point.\n\n"
        f"Question: {question}\n\nSources:\n{context}"
    )
    return ask_llm(prompt), [r["url"] for r in numbered]


if __name__ == "__main__":
    answer, urls = research("What changed in the EU rules for general-purpose AI models in 2025?")
    print(answer)
    for i, url in enumerate(urls, 1):
        print(f"[{i}] {url}")
```

## ¿Cómo funciona cada parte?

`plan_queries` pide al modelo consultas cortas, una por línea, y elimina los marcadores de lista. Las consultas cortas encajan mejor con cómo los motores de búsqueda clasifican las páginas que las preguntas completas.

`search` llama a `POST https://llmwebsearch.com/api/v1/search` con `max_results=5`, `read=3` y `max_chars=5000`. La respuesta lista cinco resultados; los tres primeros llevan el texto de su página en `content`.

`research` ejecuta las rondas. Guarda las fuentes en un diccionario indexado por URL, así una página encontrada por dos consultas cuenta una vez. Solo se conservan los resultados con `content_status` igual a `ok`, porque la respuesta debe apoyarse en texto que el modelo pueda leer.

Tras cada ronda, el modelo ve los títulos recopilados hasta el momento y propone dos consultas nuevas para lo que falta. El prompt final numera hasta doce fuentes y fija las reglas de citación.

## ¿Cuánto cuesta un informe?

Seis búsquedas por pregunta con la configuración por defecto: cuatro en la primera ronda y dos en la segunda. Con `read` en 3, son seis créditos. Así, 25.000 búsquedas a 41,25 US$ cubre más de 4.000 informes.

Las llamadas al modelo cuestan aparte en tu factura de LLM: tres llamadas de planificación y una llamada larga de respuesta por informe. La llamada de respuesta lleva hasta doce páginas de 5.000 caracteres, así que baja `max_chars` si tu modelo es caro.

## ¿Cómo hago que vaya más a fondo?

Cambia los números, no la estructura:

- **Más rondas.** `rounds=4` permite al modelo perseguir dos conjuntos de vacíos más.
- **Más consultas por ronda.** Sube `n` en la llamada de búsqueda de vacíos de 2 a 4.
- **Fuentes más concretas.** Añade `include_domains` al cuerpo de la búsqueda para sitios oficiales o académicos.
- **Solo material reciente.** Añade `"time_range": "month"` o `"topic": "news"`.
- **Más páginas por consulta.** `read=5` lee cinco páginas, a 2 créditos por búsqueda.

Añade una regla de parada antes de subir los límites: termina el bucle cuando una ronda traiga menos de dos fuentes nuevas.

## ¿Cómo mantengo al agente seguro y educado?

Trata cada página como texto no confiable. El prompt final le dice al modelo que use las fuentes solo como evidencia; añade una línea explícita de que las instrucciones dentro de las fuentes deben ignorarse. Nunca dejes que una fuente active una llamada a herramienta o una escritura de archivo.

En el lado web, Nuestro lector respeta robots.txt: se identifica como LLMWebSearch, sigue las reglas que un sitio escribe para él o para todos los robots, y deja fuera el contenido de cualquier página que no tenga permitido leer. Las páginas que rechazan el acceso vuelven sin contenido y el script simplemente las omite. Cuando un sitio rechaza a nuestro lector, el resultado vuelve sin contenido y con el estado del sitio: nunca resolvemos CAPTCHAs, iniciamos sesión en cuentas ni sorteamos un bloqueo o un paywall.

## ¿Qué puede salir mal?

En las pruebas aparecen tres cosas:

- **Consultas vagas.** Si el plan es débil, cada ronda encuentra las mismas páginas. Pídele al modelo consultas que usen nombres y fechas concretos.
- **Muy pocas fuentes con texto.** Algunos temas están detrás de muros de pago. Amplía las consultas o permite más resultados.
- **Prompts demasiado largos.** Doce páginas largas pueden superar el contexto de un modelo pequeño. Baja `max_chars` o quédate con ocho fuentes.

Registra cada consulta y las URL que devolvió. Leer ese registro es la forma más rápida de mejorar el prompt del plan.

## Agente de investigación profunda: preguntas frecuentes

### ¿Puedo ejecutarlo con un modelo local?

Sí, si el servidor local habla el formato chat-completions de OpenAI. Pon `LLM_BASE_URL` con su dirección y `LLM_MODEL` con el nombre del modelo.

### ¿Puedo convertirlo en una herramienta MCP?

Sí. Envuelve `research()` en una herramienta MCP, o dale a tu agente la herramienta de búsqueda simple de la [página MCP](https://llmwebsearch.com/es/docs/mcp/) y deja que ejecute el bucle por sí mismo.

### ¿Por qué no pedirle una respuesta directamente a la API de búsqueda?

Porque LLMWebSearch no escribe respuestas. La API devuelve fuentes, no una respuesta redactada: tu propio modelo lee las páginas, escribe la respuesta y cita las URL.

### ¿Cómo veo cuánto costó cada búsqueda?

Cada respuesta incluye `credits_used`, y `GET https://llmwebsearch.com/api/v1/credits` muestra tu saldo restante sin coste.

---

LLMWebSearch: LLMWebSearch es una API de búsqueda web para agentes de IA que devuelve resultados clasificados con el contenido de las mejores páginas en Markdown, se paga con criptomonedas y sin subir documentos de identidad. Los créditos prepago desde 1,20 US$/1K búsquedas nunca caducan. Sin KYC: sin ID, sin selfie, sin documentos. Paga con criptomonedas.
