Python · AI agents · Amazon

Give an AI agent live product data

A language model only knows what was in its training data. Ask it what a product costs today and it will guess, or say it does not know. The fix is to let the model ask your code for the answer while it is writing: you describe a few “tools” – search a shop, look up a product – and when the model needs live data, it asks your program to run one of them.

Your program then calls everydata.io, gets the current data from the live page and hands it back to the model, which answers with real prices and real availability. This works the same way with OpenAI, Anthropic and most other providers; only the wrapping differs slightly. This guide keeps the tools provider-neutral and shows both wrappings.

What you will have at the end

  • Two Python functions that fetch live Amazon search results and product details.
  • One neutral tool description, converted to OpenAI-style and Anthropic-style tool definitions.
  • A working question-and-answer loop for each provider.

What you need

  • Python 3.10 or newer, pip install requests plus the SDK of your model provider (pip install openai or pip install anthropic).
  • A free everydata.io API key – create one here – in EVERYDATA_API_KEY.
  • An API key of your model provider in OPENAI_API_KEY or ANTHROPIC_API_KEY.

01How tool calling works, in four steps

  • You send the question together with a list of tools: a name, a sentence about what each one does, and which inputs it takes.
  • Instead of answering right away, the model replies “please run search_amazon with keyword ‘usb c charger 20w’”.
  • Your code runs that function – here: one request to everydata.io – and sends the result back.
  • The model reads the result and writes the final answer. If it needs more, it asks for another tool first.

The model never sees your everydata.io key and never calls the API itself. Your code decides what actually runs.

02Write the functions that fetch the data

These are plain Python functions. Each one makes one request and keeps only the fields the model needs – a full product page has dozens of fields, and every extra field costs tokens without improving the answer.

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]})

This is what the search endpoint returns before the function trims it (shortened documented example):

Example response · 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 the tools once, then wrap them per provider

The description is what the model reads to decide when to use a tool, so write it for the model: what it returns and when it is useful. The inputs are described in JSON Schema, which every major provider understands. Only the envelope around it differs.

tools.py (continued)
# 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
]

Parameter names match the API (keyword, asin, domainCode), so the model's arguments can be passed straight through to the functions from step 2.

04Run it with an Anthropic-style API

The loop sends the question, runs every tool the model asks for, returns the results as tool_result blocks and stops when the model answers in text.

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"))

05Run it with an OpenAI-style API

Same loop, different message shapes: tool requests arrive in tool_calls, with the arguments as a JSON string, and each result goes back as a message with the role tool. Many other providers and local model servers accept this OpenAI-style format as well.

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)

06Before you put it in front of users

  • Keep the hard limit on tool rounds. Every tool call is one everydata.io request, so a loop without a limit can use up your quota. When the limit is hit, the scripts above stop with a clear message instead of printing half an answer.
  • Return errors as data (_get and run_tool do this). The model can then tell the user that a page could not be loaded instead of failing silently. _get waits when you send too many requests at once (429) and tries a failed page load (5xx) once more; 5xx answers are not counted against your quota.
  • Trim results. Five search results with six fields each are usually enough for a good answer.
  • Tell the model in the tool description that a price of 0 means “no price shown right now”, so it does not report the product as free.

Want the same tools inside Claude Desktop, Claude Code or ChatGPT instead of your own app? Wrap them in an MCP server – see Build an MCP tool for live web data.

Endpoints used in this guide

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

Search Products · Amazon API

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

Product Details by ASIN · Amazon API

All parameters in the API reference

Try it with your own data

The free plan includes 100 requests a month, usable on every platform – enough to follow this guide end to end.