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
| 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.
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.
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 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 (2)
- Function calling — OpenAI API docs (read 2026-10-22)
- Herramientas (OpenAI Agents SDK) — OpenAI Agents SDK docs (read 2026-10-22)