20 % de descuento en todos los planes de pago con el código

Python · Agentes de IA · Amazon

Dale a un agente de IA datos de producto en directo

Un modelo de lenguaje solo sabe lo que había en sus datos de entrenamiento. Pregúntale cuánto cuesta hoy un producto y lo adivinará o dirá que no lo sabe. La solución es dejar que el modelo pida la respuesta a tu código mientras escribe: describes unas pocas “herramientas” (buscar en una tienda, consultar un producto) y, cuando el modelo necesita datos en directo, pide a tu programa que ejecute una de ellas.

Tu programa llama entonces a everydata.io, obtiene los datos actuales de la página en directo y se los devuelve al modelo, que responde con precios y disponibilidad reales. Funciona igual con OpenAI, Anthropic y la mayoría de los demás proveedores; solo cambia un poco el envoltorio. Esta guía mantiene las herramientas neutrales respecto al proveedor y muestra ambos envoltorios.

Actualizado el · por everydata.io

Lo que tendrás al final

  • Dos funciones de Python que obtienen resultados de búsqueda y detalles de productos de Amazon en directo.
  • Una descripción neutral de las herramientas, convertida en definiciones al estilo de OpenAI y de Anthropic.
  • Un bucle de pregunta y respuesta que funciona con cada proveedor.

Lo que necesitas

  • Python 3.10 o posterior, pip install requests y el SDK de tu proveedor de modelos (pip install openai o pip install anthropic).
  • Una clave de API gratuita de everydata.io (créala aquí) en EVERYDATA_API_KEY.
  • Una clave de API de tu proveedor de modelos en OPENAI_API_KEY o ANTHROPIC_API_KEY.

01Cómo funciona el tool calling, en cuatro pasos

  • Envías la pregunta junto con una lista de herramientas: un nombre, una frase sobre lo que hace cada una y qué entradas recibe.
  • En lugar de responder directamente, el modelo contesta “ejecuta search_amazon con keyword ‘usb c charger 20w’”.
  • Tu código ejecuta esa función (aquí, una solicitud a everydata.io) y devuelve el resultado.
  • El modelo lee el resultado y escribe la respuesta final. Si necesita más, pide antes otra herramienta.

El modelo nunca ve tu clave de everydata.io ni llama a la API por su cuenta. Tu código decide qué se ejecuta realmente.

02Escribe las funciones que obtienen los datos

Son funciones de Python normales. Cada una hace una solicitud y conserva solo los campos que necesita el modelo: una página de producto completa tiene decenas de campos, y cada campo de más cuesta tokens sin mejorar la respuesta.

tools.py
"""Two tools an AI model can call: search Amazon and look up one product – live, through everydata.io."""
import json
import os
import re
import time

import requests

API = "https://api.everydata.io"
KEY = os.environ["EVERYDATA_API_KEY"]


def _get(path: str, params: dict) -> dict:
    """One everydata.io request. Errors come back as data, so the model can explain them instead of crashing."""
    retried = False
    for _ in range(4):
        try:
            resp = requests.get(f"{API}{path}", params=params, headers={"x-api-key": KEY}, timeout=60)
        except requests.RequestException as exc:
            return {"error": "network", "detail": str(exc)[:300]}
        if resp.status_code == 429:
            # Too many requests at once. The answer says how long to wait: "... Try again in 12 seconds."
            wait = re.search(r"in (\d+) seconds", resp.text)
            time.sleep(int(wait.group(1)) + 1 if wait else 60)
            continue
        if resp.status_code >= 500 and not retried:
            retried = True  # the page could not be loaded – try once more (5xx answers are not counted)
            time.sleep(5)
            continue
        if resp.status_code >= 400:
            return {"error": resp.status_code, "detail": resp.text[:300]}
        return resp.json()
    return {"error": 429, "detail": "Still rate limited after waiting. Try again in a minute."}


def search_amazon(keyword: str, domainCode: str = "com") -> dict:
    data = _get("/amz/amazon-search-by-keyword-asin",
                {"keyword": keyword, "domainCode": domainCode, "excludeSponsored": "true"})
    if "error" in data:
        return data
    keep = ("asin", "productDescription", "price", "currency", "productRating", "countReview")
    # Only the first five results and only the fields the model needs: fewer tokens, better answers.
    return {"results": [{k: p.get(k) for k in keep} for p in data.get("searchProductDetails", [])[:5]]}


def get_amazon_product(asin: str, domainCode: str = "com") -> dict:
    data = _get("/amz/amazon-lookup-product-by-asin", {"asin": asin, "domainCode": domainCode})
    if "error" in data:
        return data
    keep = ("productTitle", "asin", "price", "retailPrice", "currency",
            "productRating", "countReview", "warehouseAvailability", "features")
    return {k: data.get(k) for k in keep}


FUNCTIONS = {"search_amazon": search_amazon, "get_amazon_product": get_amazon_product}


def run_tool(name: str, args: dict) -> str:
    """Run the tool the model asked for and return the result as a JSON string."""
    fn = FUNCTIONS.get(name)
    if fn is None:
        return json.dumps({"error": f"unknown tool {name}"})
    try:
        return json.dumps(fn(**args), ensure_ascii=False)
    except Exception as exc:  # wrong arguments, unexpected data … – tell the model instead of crashing
        return json.dumps({"error": "tool failed", "detail": f"{type(exc).__name__}: {exc}"[:300]})

Esto es lo que devuelve el endpoint de búsqueda antes de que la función lo recorte (ejemplo documentado, reducido):

Respuesta de ejemplo · GET /amz/amazon-search-by-keyword-asin
{
  "responseStatus": "PRODUCT_FOUND_RESPONSE",
  "responseMessage": "Product successfully found!",
  "domainCode": "com",
  "keyword": "laptop",
  "numberOfProducts": 26,
  "searchProductDetails": [
    {
      "productDescription": "Acer Aspire 14 AI Copilot+ PC - Intel Core Ultra 5 226V & 14” WUXGA Touch - NPU: Up to 40 Tops - GPU: Up to 53 Tops | Intel Arc 130V | 16GB LPDDR5X | 1TB Gen 4 SSD | Wi-Fi 6E | A14-52MT-59DP",
      "asin": "B0FGJ14KRX",
      "countReview": 20,
      "price": 548.44,
      "productRating": "4.0 out of 5 stars",
      "currency": "USD"
    }
  ],
  "currentPage": 1,
  "lastPage": 20
}

03Describe las herramientas una vez y envuélvelas para cada proveedor

La descripción es lo que lee el modelo para decidir cuándo usar una herramienta, así que escríbela pensando en el modelo: qué devuelve y cuándo es útil. Las entradas se describen en JSON Schema, que entienden todos los grandes proveedores. Solo cambia el envoltorio.

tools.py (continuación)
# Add to tools.py – one description per tool, written for the model, in plain JSON Schema.
TOOL_SPECS = [
    {
        "name": "search_amazon",
        "description": "Search Amazon right now. Returns up to 5 products with ASIN, title, current price, "
                       "currency and rating. Use it when the user asks what is available or what something costs.",
        "parameters": {
            "type": "object",
            "properties": {
                "keyword": {"type": "string", "description": "What to search for, e.g. 'usb c charger 20w'"},
                "domainCode": {"type": "string", "description": "Amazon marketplace: com, de, co.uk, fr, it, es …"},
            },
            "required": ["keyword"],
        },
    },
    {
        "name": "get_amazon_product",
        "description": "Live details of one Amazon product by ASIN: title, price, list price, availability, "
                       "rating and key features. A price of 0 means the page shows no price right now.",
        "parameters": {
            "type": "object",
            "properties": {
                "asin": {"type": "string", "description": "10-character Amazon product ID, e.g. B07ZPKN6YR"},
                "domainCode": {"type": "string", "description": "Amazon marketplace: com, de, co.uk, fr, it, es …"},
            },
            "required": ["asin"],
        },
    },
]

# OpenAI-style (Chat Completions): the schema goes under "function" -> "parameters".
OPENAI_TOOLS = [{"type": "function", "function": spec} for spec in TOOL_SPECS]

# Anthropic-style (Messages API): the same schema is called "input_schema".
ANTHROPIC_TOOLS = [
    {"name": s["name"], "description": s["description"], "input_schema": s["parameters"]}
    for s in TOOL_SPECS
]

Los nombres de los parámetros coinciden con la API (keyword, asin, domainCode), así que los argumentos del modelo se pueden pasar directamente a las funciones del paso 2.

04Ejecútalo con una API al estilo de Anthropic

El bucle envía la pregunta, ejecuta cada herramienta que pide el modelo, devuelve los resultados como bloques tool_result y se detiene cuando el modelo responde con texto.

ask_claude.py
import anthropic

from tools import ANTHROPIC_TOOLS, run_tool

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
MAX_ROUNDS = 5  # hard limit on tool rounds – every tool call is one everydata.io request
messages = [{"role": "user", "content": "What does a 20W USB-C charger cost on amazon.com right now?"}]

for _ in range(MAX_ROUNDS):
    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=16000,
        tools=ANTHROPIC_TOOLS,
        messages=messages,
    )
    if response.stop_reason != "tool_use":
        break
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [
            {"type": "tool_result", "tool_use_id": block.id, "content": run_tool(block.name, block.input)}
            for block in response.content
            if block.type == "tool_use"
        ],
    })
else:
    raise SystemExit(f"Stopped after {MAX_ROUNDS} tool rounds without a final answer. "
                     "Ask a more specific question or raise MAX_ROUNDS.")

print("".join(block.text for block in response.content if block.type == "text"))

05Ejecútalo con una API al estilo de OpenAI

El mismo bucle con otra forma de mensajes: las peticiones de herramientas llegan en tool_calls, con los argumentos como cadena JSON, y cada resultado vuelve como un mensaje con el rol tool. Muchos otros proveedores y servidores de modelos locales también aceptan este formato al estilo de OpenAI.

ask_openai.py
import json
import os

from openai import OpenAI

from tools import OPENAI_TOOLS, run_tool

client = OpenAI()  # reads OPENAI_API_KEY
MODEL = os.environ["OPENAI_MODEL"]  # any chat model that supports function calling
MAX_ROUNDS = 5  # hard limit on tool rounds – every tool call is one everydata.io request
messages = [{"role": "user", "content": "What does a 20W USB-C charger cost on amazon.com right now?"}]

for _ in range(MAX_ROUNDS):
    response = client.chat.completions.create(model=MODEL, messages=messages, tools=OPENAI_TOOLS)
    message = response.choices[0].message
    if not message.tool_calls:
        break
    messages.append(message)
    for call in message.tool_calls:
        try:
            args = json.loads(call.function.arguments)
        except json.JSONDecodeError:
            result = json.dumps({"error": "arguments were not valid JSON"})
        else:
            result = run_tool(call.function.name, args)
        messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
else:
    raise SystemExit(f"Stopped after {MAX_ROUNDS} tool rounds without a final answer. "
                     "Ask a more specific question or raise MAX_ROUNDS.")

print(message.content)

06Antes de ponerlo delante de usuarios

  • Mantén el límite estricto de rondas de herramientas. Cada llamada a una herramienta es una solicitud a everydata.io, así que un bucle sin límite puede agotar tu cuota. Cuando se alcanza el límite, los scripts de arriba se detienen con un mensaje claro en lugar de imprimir media respuesta.
  • Devuelve los errores como datos (_get y run_tool lo hacen). Así el modelo puede decirle al usuario que no se pudo cargar una página en lugar de fallar en silencio. _get espera cuando envías demasiadas solicitudes a la vez (429) y reintenta una vez una carga de página fallida (5xx); las respuestas 5xx no cuentan para tu cuota.
  • Recorta los resultados. Cinco resultados de búsqueda con seis campos cada uno suelen bastar para una buena respuesta.
  • Indica al modelo en la descripción de la herramienta que un precio de 0 significa “ahora mismo no se muestra precio”, para que no presente el producto como gratuito.

¿Quieres las mismas herramientas en Claude Desktop, Claude Code o ChatGPT en lugar de en tu propia app? Envuélvelas en un servidor MCP: consulta Crea una herramienta MCP para datos web en directo.

Endpoints utilizados en esta guía

GET/amz/amazon-search-by-keyword-asin

Search Products · API de Amazon

GET/amz/amazon-lookup-product-by-asin

Product Details by ASIN · API de Amazon

Todos los parámetros en la referencia de la API

Pruébalo con tus propios datos

El plan gratuito incluye 100 solicitudes al mes, utilizables en todas las plataformas: suficiente para seguir esta guía de principio a fin.