# OpenAI function calling: dale al modelo una herramienta search_web

> Búsqueda web con OpenAI function calling: esquema JSON de search_web, bucle de la Responses API en Python y la herramienta en el Agents SDK. Sin KYC.

- URL: https://llmwebsearch.com/es/docs/openai/
- Última actualización: 2026-10-22
- Editor: LLMWebSearch (https://llmwebsearch.com)

Describe al modelo una función `search_web`, ejecuta la llamada que pide contra nuestra API y devuelve las fuentes. El modelo responde entonces con citas. Esta página muestra el esquema JSON, el bucle de la Responses API y la versión más corta del OpenAI Agents SDK.

**Respuesta rápida — ¿Cómo añado búsqueda web a un modelo de OpenAI con function calling?**

Declara una función `search_web` con un esquema JSON y, cuando el modelo la llame, haz POST de la consulta al `https://llmwebsearch.com/api/v1/search` de **LLMWebSearch**. Devuelve las fuentes numeradas, con las 3 páginas principales como Markdown, en un `function_call_output`; el modelo responde y las cita. Una búsqueda cuesta 1 crédito, desde 1,20 US$/1K búsquedas a octubre de 2026. Sin KYC; paga con criptomonedas.

## Lo que necesitas

| Atributo | Valor |
| --- | --- |
| Paquetes | `openai` y `requests` (o `openai-agents` para la versión del Agents SDK) |
| Claves | `OPENAI_API_KEY` para el modelo, `LLMWEBSEARCH_API_KEY` para la búsqueda |
| Modelo | Cualquier modelo que admita function calling, definido en `OPENAI_MODEL` |
| API usada | Responses API: `client.responses.create` con `tools` y `input` |
| Facturación por nuestra parte | Un crédito paga una búsqueda: hasta 20 resultados clasificados, con las páginas de los 3 primeros leídas y devueltas como Markdown. Leer de 4 a 10 páginas pasa a 2 créditos. Una llamada que termina en error no cuesta nada |

## ¿Cómo es el esquema de la herramienta?

Una función herramienta tiene un `name`, una `description` y un esquema JSON `parameters`. Mantén la descripción concreta: qué devuelve la herramienta y que las fuentes deben citarse por número. Dos parámetros bastan para la mayoría de los agentes: la `query` y cuántos resultados principales `read` completos.

El modelo nunca ve tu clave API. Solo propone argumentos; tu código decide cómo llamar a la API y qué devolver.

## ¿Cómo funciona el bucle de llamadas?

Envía el mensaje del usuario con las herramientas. Si la respuesta contiene elementos de tipo `function_call`, parsea el JSON `arguments` de cada uno, ejecuta la búsqueda y añade un `function_call_output` con el mismo `call_id`. Después llama al modelo otra vez con la lista de entrada más larga. Cuando una respuesta no contiene ninguna llamada a función, su texto es la respuesta.

El bucle de abajo se detiene tras cinco rondas, una protección contra un modelo que no deja de buscar. La guía de function calling de OpenAI describe los mismos elementos `function_call` y `function_call_output`, leída el 22 de octubre de 2026.

*search_tool_loop.py (Responses API)*

```python
import json
import os

import requests
from openai import OpenAI

client = OpenAI()
MODEL = os.environ["OPENAI_MODEL"]  # any model that supports function calling
SEARCH = "https://llmwebsearch.com/api/v1/search"

tools = [
    {
        "type": "function",
        "name": "search_web",
        "description": "Search the live web. Returns numbered sources with URLs; the first `read` include page text. Cite sources as [1], [2].",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "What to search for, as a person would type it."},
                "read": {"type": "integer", "minimum": 0, "maximum": 10, "description": "How many top results to read in full."},
            },
            "required": ["query"],
            "additionalProperties": False,
        },
    }
]


def search_web(query, read=3):
    resp = requests.post(
        SEARCH,
        headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
        json={"q": query, "max_results": 5, "read": read, "max_chars": 5000},
        timeout=60,
    )
    if resp.status_code != 200:
        return "Search failed: " + resp.json().get("error", {}).get("code", str(resp.status_code))
    parts = []
    for i, r in enumerate(resp.json()["results"], start=1):
        text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
        parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
    return "\n\n".join(parts)


input_list = [{"role": "user", "content": "How should I size a Postgres connection pool?"}]
for _ in range(5):
    response = client.responses.create(model=MODEL, tools=tools, input=input_list)
    calls = [item for item in response.output if item.type == "function_call"]
    if not calls:
        print(response.output_text)
        break
    input_list += response.output
    for item in calls:
        args = json.loads(item.arguments)
        output = search_web(args["query"], int(args.get("read", 3)))
        input_list.append({"type": "function_call_output", "call_id": item.call_id, "output": output})

```

## ¿Hay una versión más corta con el Agents SDK?

Sí. El OpenAI Agents SDK convierte una función de Python en una herramienta con un decorador y ejecuta el bucle de llamadas por ti. Su documentación de herramientas, leída el 22 de octubre de 2026, muestra el decorador `@tool` de `agents.decorators`, con la descripción tomada del docstring y la descripción de cada argumento de su sección `Args`. La misma página también menciona la forma `@function_tool`.

`Runner.run_sync` ejecuta el agente hasta que produce una respuesta final, llamando a `search_web` tantas veces como el modelo pida.

*search_agent.py (OpenAI Agents SDK)*

```python
import os

import requests
from agents import Agent, Runner
from agents.decorators import tool


@tool
def search_web(query: str, read: int = 3) -> str:
    """Search the live web and return numbered sources with their URLs.

    Args:
        query: What to search for, as a person would type it.
        read: How many top results to read in full, from 0 to 10.
    """
    resp = requests.post(
        "https://llmwebsearch.com/api/v1/search",
        headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
        json={"q": query, "max_results": 5, "read": max(0, min(read, 10)), "max_chars": 5000},
        timeout=60,
    )
    resp.raise_for_status()
    parts = []
    for i, r in enumerate(resp.json()["results"], start=1):
        text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
        parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
    return "\n\n".join(parts)


agent = Agent(
    name="Researcher",
    instructions="Answer from search_web results only and cite them as [1], [2].",
    tools=[search_web],
)
result = Runner.run_sync(agent, "How should I size a Postgres connection pool?")
print(result.final_output)

```

## ¿Por qué usar una herramienta API en lugar de la búsqueda web integrada de OpenAI?

Sobre todo, por control sobre lo que lee el modelo y lo que cuesta. Nuestra herramienta entrega a tu código las fuentes en bruto, con las URL y el texto de la página, para que decidas qué entra en el contexto, puedas registrarlo y puedas guardarlo. No entrenamos ningún modelo con tus consultas ni con lo que devuelve la API, y no añadimos ninguna regla propia contra almacenar resultados; los derechos de los sitios cuyas páginas lees siguen aplicándose.

El precio también es fijo por búsqueda, en lugar de depender de cuánto contenido recuperado lee el modelo. La [comparativa de herramientas integradas](https://llmwebsearch.com/es/compare/built-in-web-search-tools/) pone lado a lado los precios por llamada de OpenAI, Anthropic, Google y otros, con fechas.

## ¿Cómo evito que el modelo busque demasiado?

Dilo en las instrucciones: busca una vez, lee las fuentes y vuelve a buscar solo si no coinciden o no dan en el clavo. Limita el número de rondas en tu propio bucle, como hace el script de arriba con cinco.

Mantén `max_chars` moderado. Tres páginas de 5.000 caracteres son unos 4.000 tokens por búsqueda, y cada ronda los suma a la entrada de la siguiente llamada al modelo.

## Function calling de OpenAI: preguntas frecuentes

### ¿Esto funciona con la API de Chat Completions?

Sí, con el formato antiguo `tools` de Chat Completions, donde la función va dentro de un objeto `function` y los resultados vuelven como mensajes `tool`. La función de búsqueda en sí no cambia.

### ¿Puede el modelo leer una página que encuentra en una fuente?

Añade una segunda función, `read_urls`, que envíe las URL a `https://llmwebsearch.com/api/v1/extract`. Leer páginas cuyas URL ya tienes cuesta 1 crédito por cada grupo iniciado de 3 páginas devueltas con contenido, hasta 20 URL por llamada; las URL rechazadas o fallidas no se cuentan.

### ¿Qué modelo debería usar?

Cualquier modelo que admita function calling. Prueba dos o tres con tus propias preguntas: la respuesta depende tanto de las fuentes como del modelo, y un modelo más pequeño cuesta menos por ronda.

## Fuentes

1. [Function calling](https://developers.openai.com/api/docs/guides/function-calling) — OpenAI API docs (read 2026-10-22)
2. [Herramientas (OpenAI Agents SDK)](https://openai.github.io/openai-agents-python/tools/) — OpenAI Agents SDK docs (read 2026-10-22)

---

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.
