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 requestsplus the SDK of your model provider (pip install openaiorpip 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_KEYorANTHROPIC_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_amazonwith 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.
"""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):
{
"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.
# 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.
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.
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 (
_getandrun_tooldo this). The model can then tell the user that a page could not be loaded instead of failing silently._getwaits 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
0means “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
Search Products · Amazon API
Product Details by ASIN · Amazon API
What else you can do with this data
Any Amazon product page as structured JSON: title, brand, price, rating, images, bullets, specs, variations and top reviews. Realtime, by URL or ASIN.
Query Amazon search results by keyword as JSON: ASINs, prices, ratings, Prime flag, sales volume and sponsored markers per page, on any marketplace.
Track Amazon prices in realtime across 15+ marketplaces. Live buy-box price, list price, stock and a price-history endpoint – one JSON call per ASIN.
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.