docsGuidesBuilding a Shopping Agent

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:

  1. Discover products using natural language
  2. Compare options and get recommendations
  3. Build a cart with selected items
  4. 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