Building a Shopping Agent
This guide walks you through building a complete AI shopping assistant using Lorn AI’s APIs.
Overview
A shopping agent helps users:
- Discover products using natural language
- Compare options and get recommendations
- Build a cart with selected items
- Complete purchases with full checkout
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ │ │ │ │ │ │ │
│ "Find │────▶│ Search │────▶│ Create │────▶│ Complete │
│ running │ │ Products │ │ Checkout │ │ Order │
│ shoes" │ │ │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘Agent Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ Shopping Agent │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ User │ │ LLM │ │ Tool │ │ Lorn AI │ │
│ │ Input │──▶ Reasoning │──▶ Execution │──▶ APIs │ │
│ │ │ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ▲ │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ Response │
│ │
└─────────────────────────────────────────────────────────────────────────┘Tool Definitions
Define these tools for your LLM to use:
search_products
{
"name": "search_products",
"description": "Search for products using natural language. Use this to find products matching user intent like 'warm winter jacket' or 'gift for a runner'. Returns products ranked by relevance.",
"parameters": {
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "Natural language search query"
},
"category": {
"type": "string",
"description": "Filter by category (e.g., 'apparel', 'electronics')"
},
"min_price": {
"type": "number",
"description": "Minimum price filter"
},
"max_price": {
"type": "number",
"description": "Maximum price filter"
},
"page_size": {
"type": "integer",
"description": "Number of results (default: 10, max: 100)"
}
}
}
}get_product
{
"name": "get_product",
"description": "Get complete details for a specific product including all variants, images, and inventory.",
"parameters": {
"type": "object",
"properties": {
"product_id": {
"type": "string",
"description": "The product ID from search results"
}
},
"required": ["product_id"]
}
}create_checkout
{
"name": "create_checkout",
"description": "Create a new checkout session with items. Returns session ID, pricing breakdown, and totals.",
"parameters": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"product_id": {"type": "string"},
"variant_sku": {"type": "string"},
"quantity": {"type": "integer", "default": 1}
},
"required": ["product_id"]
},
"description": "Items to add to cart"
},
"shipping_address": {
"type": "object",
"properties": {
"name": {"type": "string"},
"line1": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"postal_code": {"type": "string"},
"country": {"type": "string"}
}
},
"customer": {
"type": "object",
"properties": {
"email": {"type": "string"},
"name": {"type": "string"}
}
}
},
"required": ["items"]
}
}update_checkout
{
"name": "update_checkout",
"description": "Update an existing checkout session. Use to modify cart items, add shipping address, or update customer info.",
"parameters": {
"type": "object",
"properties": {
"session_id": {
"type": "string",
"description": "Checkout session ID"
},
"items": {
"type": "array",
"description": "Replace cart items (omit to keep existing)"
},
"shipping_address": {
"type": "object",
"description": "Update shipping address"
},
"customer": {
"type": "object",
"description": "Update customer info"
}
},
"required": ["session_id"]
}
}complete_checkout
{
"name": "complete_checkout",
"description": "Finalize and complete the checkout. Returns completed order confirmation.",
"parameters": {
"type": "object",
"properties": {
"session_id": {
"type": "string",
"description": "Checkout session ID to complete"
}
},
"required": ["session_id"]
}
}Python Implementation
Setup
import os
import json
import requests
from openai import OpenAI
# Configuration
LORN_BASE_URL = "https://{{YOUR_STORE_URL}}"
LORN_API_KEY = "{{YOUR_API_KEY}}"
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# Initialize clients
openai_client = OpenAI(api_key=OPENAI_API_KEY)
def lorn_request(method: str, endpoint: str, **kwargs) -> dict:
"""Make a request to Lorn AI API."""
headers = {
"X-ACP-API-Key": LORN_API_KEY,
"Content-Type": "application/json"
}
url = f"{LORN_BASE_URL}{endpoint}"
response = requests.request(method, url, headers=headers, **kwargs)
response.raise_for_status()
return response.json()Tool Implementations
def search_products(q: str = None, category: str = None,
min_price: float = None, max_price: float = None,
page_size: int = 10) -> dict:
"""Search for products."""
params = {"page_size": page_size}
if q:
params["q"] = q
if category:
params["category"] = category
if min_price is not None:
params["min_price"] = min_price
if max_price is not None:
params["max_price"] = max_price
return lorn_request("GET", "/acp/products", params=params)
def get_product(product_id: str) -> dict:
"""Get product details."""
return lorn_request("GET", f"/acp/products/{product_id}")
def create_checkout(items: list, shipping_address: dict = None,
customer: dict = None) -> dict:
"""Create a checkout session."""
payload = {"items": items}
if shipping_address:
payload["shipping_address"] = shipping_address
if customer:
payload["customer"] = customer
return lorn_request("POST", "/checkout_sessions", json=payload)
def update_checkout(session_id: str, items: list = None,
shipping_address: dict = None, customer: dict = None) -> dict:
"""Update a checkout session."""
payload = {}
if items is not None:
payload["items"] = items
if shipping_address:
payload["shipping_address"] = shipping_address
if customer:
payload["customer"] = customer
return lorn_request("PATCH", f"/checkout_sessions/{session_id}", json=payload)
def complete_checkout(session_id: str) -> dict:
"""Complete a checkout session."""
return lorn_request("POST", f"/checkout_sessions/{session_id}/complete")Tool Dispatcher
TOOLS = {
"search_products": search_products,
"get_product": get_product,
"create_checkout": create_checkout,
"update_checkout": update_checkout,
"complete_checkout": complete_checkout,
}
def execute_tool(tool_name: str, arguments: dict) -> str:
"""Execute a tool and return results as string."""
if tool_name not in TOOLS:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
try:
result = TOOLS[tool_name](**arguments)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps({"error": str(e)})Agent Loop
SYSTEM_PROMPT = """You are a helpful shopping assistant. You help users find products,
compare options, and complete purchases.
When searching for products:
- Use natural language queries that describe what the user wants
- Show key details: title, price, and why it matches their needs
- Offer to show more details or add items to cart
When handling checkout:
- Confirm items and quantities before checkout
- Ask for shipping address if not provided
- Show price breakdown before completing
Always be helpful and guide users through the shopping experience."""
TOOL_DEFINITIONS = [
# ... (tool definitions from above)
]
def run_agent(user_message: str, conversation_history: list = None):
"""Run the shopping agent."""
if conversation_history is None:
conversation_history = []
# Add user message
conversation_history.append({"role": "user", "content": user_message})
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
*conversation_history
]
while True:
# Get LLM response
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOL_DEFINITIONS,
tool_choice="auto"
)
assistant_message = response.choices[0].message
# Check if we need to call tools
if assistant_message.tool_calls:
# Add assistant message with tool calls
messages.append(assistant_message)
# Execute each tool call
for tool_call in assistant_message.tool_calls:
tool_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"[Calling {tool_name}...]")
result = execute_tool(tool_name, arguments)
# Add tool result
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
else:
# No tool calls, return the response
conversation_history.append({
"role": "assistant",
"content": assistant_message.content
})
return assistant_message.content, conversation_history
# Interactive loop
def main():
print("Shopping Assistant: Hi! How can I help you shop today?")
conversation = []
while True:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() in ["quit", "exit", "bye"]:
print("Shopping Assistant: Thanks for shopping! Goodbye!")
break
response, conversation = run_agent(user_input, conversation)
print(f"\nShopping Assistant: {response}")
if __name__ == "__main__":
main()TypeScript Implementation
import OpenAI from 'openai';
const LORN_BASE_URL = "https://{{YOUR_STORE_URL}}";
const LORN_API_KEY = "{{YOUR_API_KEY}}";
const openai = new OpenAI();
// API client
async function lornRequest(method: string, endpoint: string, options: any = {}) {
const url = `${LORN_BASE_URL}${endpoint}`;
const response = await fetch(url, {
method,
headers: {
"X-ACP-API-Key": LORN_API_KEY,
"Content-Type": "application/json",
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
});
if (!response.ok) {
throw new Error(`API error: ${response.statusText}`);
}
return response.json();
}
// Tool implementations
const tools: Record<string, Function> = {
search_products: async (params: any) => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) searchParams.append(key, String(value));
});
return lornRequest("GET", `/acp/products?${searchParams}`);
},
get_product: async ({ product_id }: { product_id: string }) => {
return lornRequest("GET", `/acp/products/${product_id}`);
},
create_checkout: async (params: any) => {
return lornRequest("POST", "/checkout_sessions", { body: params });
},
update_checkout: async ({ session_id, ...updates }: any) => {
return lornRequest("PATCH", `/checkout_sessions/${session_id}`, { body: updates });
},
complete_checkout: async ({ session_id }: { session_id: string }) => {
return lornRequest("POST", `/checkout_sessions/${session_id}/complete`);
}
};
// Tool definitions for OpenAI
const toolDefinitions = [
{
type: "function" as const,
function: {
name: "search_products",
description: "Search for products using natural language",
parameters: {
type: "object",
properties: {
q: { type: "string", description: "Search query" },
max_price: { type: "number", description: "Maximum price" },
page_size: { type: "integer", description: "Number of results" }
}
}
}
},
// ... other tool definitions
];
// Agent function
async function runAgent(userMessage: string, history: any[] = []) {
const messages = [
{ role: "system" as const, content: "You are a helpful shopping assistant." },
...history,
{ role: "user" as const, content: userMessage }
];
while (true) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages,
tools: toolDefinitions
});
const message = response.choices[0].message;
if (message.tool_calls) {
messages.push(message);
for (const toolCall of message.tool_calls) {
const toolName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(`[Calling ${toolName}...]`);
const result = await tools[toolName](args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
} else {
return message.content;
}
}
}
// Usage
async function main() {
const response = await runAgent("Find me comfortable running shoes under $150");
console.log(response);
}
main();Example Conversation
User: I need running shoes under $100
Agent: [Calling search_products with {q: "running shoes", max_price: 100}]
I found several running shoes under $100:
1. **Brooks Ghost 14** - $89.99
Great cushioning for daily training runs. Available in multiple sizes.
2. **ASICS Gel-Contend 8** - $74.99
Entry-level running shoe with good support.
3. **New Balance Fresh Foam 680v7** - $84.99
Comfortable cushioning, good for beginners.
Would you like more details on any of these, or should I add one to your cart?
---
User: Tell me more about the Brooks
Agent: [Calling get_product with {product_id: "brooks-ghost-14"}]
**Brooks Ghost 14** - $89.99
The Ghost 14 is one of the most popular neutral running shoes, known for its
smooth transitions and reliable cushioning.
**Features:**
- DNA LOFT cushioning for soft landings
- Segmented crash pad for smooth heel-to-toe transitions
- Engineered mesh upper for breathability
- 12mm drop
**Available Sizes:** 8, 8.5, 9, 9.5, 10, 10.5, 11, 12
**Colors:** Black/Grey, White/Blue, Navy/Red
**In Stock:** Yes (15 units available)
Would you like to add this to your cart? If so, what size do you need?
---
User: Add size 10 in black to my cart
Agent: [Calling create_checkout with {items: [{product_id: "brooks-ghost-14", variant_sku: "brooks-ghost-14-black-10", quantity: 1}]}]
I've added the Brooks Ghost 14 (Black, Size 10) to your cart.
**Order Summary:**
- Brooks Ghost 14 (Black/Size 10) - $89.99
**Subtotal:** $89.99
**Tax (8%):** $7.20
**Shipping:** $7.99
**Total:** $105.18
To complete your purchase, I'll need your shipping address.
Where should I send these?
---
User: Ship to John Smith, 123 Main St, San Francisco CA 94102
Agent: [Calling update_checkout with shipping address]
I've added your shipping address.
**Ship To:**
John Smith
123 Main St
San Francisco, CA 94102
**Order Total:** $105.18
Everything looks good! Would you like me to complete this order?
---
User: Yes, complete it
Agent: [Calling complete_checkout]
🎉 **Order Confirmed!**
Your order for the Brooks Ghost 14 has been placed successfully.
**Order Details:**
- Order ID: cs_demo_xyz123
- Status: Completed
- Total Charged: $105.18
Your running shoes will be on their way soon! Is there anything else
I can help you with?Best Practices
1. Confirm Before Checkout
Always show the cart summary before completing:
def format_cart_summary(session: dict) -> str:
cs = session["checkout_session"]
items = "\n".join([
f"- {item['title']} x{item['quantity']}: ${item['subtotal']}"
for item in cs["line_items"]
])
return f"""
Order Summary:
{items}
Subtotal: ${cs['amounts']['subtotal']}
Tax: ${cs['amounts']['tax']}
Shipping: ${cs['amounts']['shipping']}
Total: ${cs['amounts']['total']}
"""2. Handle Errors Gracefully
def execute_tool(tool_name: str, arguments: dict) -> str:
try:
result = TOOLS[tool_name](**arguments)
return json.dumps(result)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
return json.dumps({"error": "Product not found"})
return json.dumps({"error": f"API error: {e.response.text}"})
except Exception as e:
return json.dumps({"error": str(e)})3. Persist Conversation State
Store session IDs for multi-turn conversations:
class ShoppingSession:
def __init__(self):
self.conversation_history = []
self.checkout_session_id = None
self.cart_items = []4. Provide Context
Include relevant context in tool results:
def format_product_for_agent(product: dict) -> dict:
return {
"id": product["id"],
"title": product["title"],
"price": product["price"],
"in_stock": product["availability"]["status"] == "in_stock",
"variants_summary": f"{len(product['variants'])} options available",
"similarity_score": product.get("similarity_score")
}Next Steps
- API Reference — Complete endpoint documentation
- Semantic Search — Optimize search queries
- Going to Production — Launch checklist