Graph Tools Specification
Graph Tools Specification
Section titled “Graph Tools Specification”Overview
Section titled “Overview”This document defines a composable tool set for AI agents to interact with the consumer knowledge graph. Each tool is optimized for speed, cacheability, and safe composition.
Design Principles:
- ✅ User-scoped (all queries filter by user_id)
- ✅ Fast (
<50msfor most queries) - ✅ Cacheable (with appropriate TTLs)
- ✅ Limited results (prevent unbounded queries)
- ✅ Composable (tools can be combined)
Graph Schema Overview
Section titled “Graph Schema Overview”- User:
{user_id, name, zip} - Product:
{product_id, name, category, brand} - Offer:
{offer_id, title, description, points, priority, start, end} - Retailer:
{retailer_id, name, address, city, state, zip}
Relationships
Section titled “Relationships”- User -[PURCHASED {last, times, avg_interval_days, total_spent}]-> Product
- Tracks what products user buys and repurchase patterns
- User -[PURCHASED_AT {last, times, total_spent}]-> Retailer
- Tracks where user shops (retailer known at purchase time)
- User -[ELIGIBLE {start}]-> Offer
- Which offers user can access
- Offer -[APPLIES_TO]-> Product
- Which products are included in offer
- Offer -[AVAILABLE_AT]-> Retailer
- Which retailers honor the offer (retailer-specific deals)
- Product -[SIMILAR_TO {score}]-> Product
- Product similarity graph
Key Design Notes
Section titled “Key Design Notes”- Products are universal: Not tied to specific retailers
- Purchases link to both Product AND Retailer: User buys “Product X” at “Retailer Y”
- Offers can be retailer-specific: Some offers only valid at certain stores
- No geographic search: Retailers are tracked but proximity queries are not supported
Tool Categories
Section titled “Tool Categories”- User Context Tools (5 tools) - Understanding user behavior
- Product Discovery Tools (3 tools) - Finding products
- Temporal Tools (2 tools) - Time-based predictions
- Offer Tools (3 tools) - Rewards and promotions
- Brand Tools (3 tools) - Brand relationships and preferences
- Retailer Tools (2 tools) - Store preferences and offer availability
- Collaborative Tools (2 tools) - Social recommendations and trends
- Composition Tool (1 tool) - Deep product context
Total: 20 tools
1. User Context Tools
Section titled “1. User Context Tools”1.1 get_user_purchase_history
Section titled “1.1 get_user_purchase_history”Purpose: Get user’s recent purchases with full details
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE p.last >= datetime() - duration({days: $lookback_days})RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, last_purchase: p.last, times_purchased: p.times, avg_interval_days: p.avg_interval_days, total_spent: p.total_spent} AS purchaseORDER BY p.last DESCLIMIT $limitParameters:
user_id(string, required) - User identifierlookback_days(integer, default: 180) - How far back to looklimit(integer, default: 50, max: 100) - Max results
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "last_purchase": "2025-01-05T10:30:00Z", "times_purchased": 12, "avg_interval_days": 28.5, "total_spent": 155.88 }]Performance:
- Latency: 5-15ms
- Cacheable: Yes (5 min TTL)
- Cache key:
purchase_history:{user_id}:{lookback_days}:{limit}
Use Cases:
- “What have I bought recently?”
- “Show me my coffee purchases”
- Understanding user preferences
1.2 get_user_categories
Section titled “1.2 get_user_categories”Purpose: Aggregate user’s purchasing behavior by category
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE p.last >= datetime() - duration({days: $lookback_days})RETURN { category: prod.category, product_count: count(DISTINCT prod), total_purchases: sum(p.times), total_spent: sum(p.total_spent), last_purchase: max(p.last), avg_interval: avg(p.avg_interval_days)} AS category_statsORDER BY total_purchases DESCParameters:
user_id(string, required)lookback_days(integer, default: 180)
Returns:
[ { "category": "Coffee", "product_count": 3, "total_purchases": 45, "total_spent": 389.55, "last_purchase": "2025-01-05T10:30:00Z", "avg_interval": 28.3 }, { "category": "Yogurt", "product_count": 2, "total_purchases": 28, "total_spent": 112.00, "last_purchase": "2025-01-02T14:15:00Z", "avg_interval": 14.0 }]Performance:
- Latency: 10-20ms
- Cacheable: Yes (15 min TTL)
- Cache key:
user_categories:{user_id}:{lookback_days}
Use Cases:
- “What categories do I buy from most?”
- “How much do I spend on coffee?”
- Category-level insights
1.3 get_user_brands
Section titled “1.3 get_user_brands”Purpose: User’s brand preferences and loyalty
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE p.last >= datetime() - duration({days: $lookback_days})RETURN { brand: prod.brand, product_count: count(DISTINCT prod), total_purchases: sum(p.times), total_spent: sum(p.total_spent), categories: collect(DISTINCT prod.category), last_purchase: max(p.last)} AS brand_statsORDER BY total_purchases DESCLIMIT $limitParameters:
user_id(string, required)lookback_days(integer, default: 180)limit(integer, default: 20, max: 50)
Returns:
[ { "brand": "Peet's Coffee", "product_count": 2, "total_purchases": 18, "total_spent": 215.82, "categories": ["Coffee"], "last_purchase": "2025-01-05T10:30:00Z" }, { "brand": "Fage", "product_count": 3, "total_purchases": 24, "total_spent": 95.76, "categories": ["Yogurt", "Dairy"], "last_purchase": "2025-01-02T14:15:00Z" }]Performance:
- Latency: 10-20ms
- Cacheable: Yes (15 min TTL)
- Cache key:
user_brands:{user_id}:{lookback_days}:{limit}
Use Cases:
- “What are my favorite brands?”
- “Do I buy Fage products?”
- Brand loyalty analysis
1.4 get_purchase_patterns
Section titled “1.4 get_purchase_patterns”Purpose: Identify user’s regular repurchase patterns
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE p.times >= $min_purchases AND p.avg_interval_days > 0 AND p.last >= datetime() - duration({days: $lookback_days})RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, avg_interval_days: p.avg_interval_days, times_purchased: p.times, last_purchase: p.last, next_expected: p.last + duration({days: toInteger(p.avg_interval_days)}), is_regular: p.times >= 3, total_spent: p.total_spent} AS patternORDER BY p.times DESCLIMIT $limitParameters:
user_id(string, required)min_purchases(integer, default: 2) - Minimum purchase countlookback_days(integer, default: 365) - Historical windowlimit(integer, default: 50, max: 100)
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "avg_interval_days": 28.5, "times_purchased": 12, "last_purchase": "2025-01-05T10:30:00Z", "next_expected": "2025-02-02T10:30:00Z", "is_regular": true, "total_spent": 143.88 }]Performance:
- Latency: 5-10ms
- Cacheable: Yes (10 min TTL)
- Cache key:
purchase_patterns:{user_id}:{min_purchases}:{lookback_days}:{limit}
Use Cases:
- “What do I buy regularly?”
- “What are my staple products?”
- Understanding routine purchases
1.5 get_category_brand_affinity
Section titled “1.5 get_category_brand_affinity”Purpose: User’s brand preferences within specific categories
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product {category: $category})WHERE p.last >= datetime() - duration({days: $lookback_days})
WITH prod.brand AS brand, count(DISTINCT prod) AS product_count, sum(p.times) AS total_purchases, sum(p.total_spent) AS total_spent, max(p.last) AS last_purchase
RETURN { brand: brand, product_count: product_count, total_purchases: total_purchases, total_spent: total_spent, last_purchase: last_purchase, share_of_category: toFloat(total_purchases) / (SELECT sum(p2.times) FROM (u)-[p2:PURCHASED]->(:Product {category: $category}))} AS brand_affinityORDER BY total_purchases DESCLIMIT $limitParameters:
user_id(string, required)category(string, required) - Product category to analyzelookback_days(integer, default: 180)limit(integer, default: 10, max: 20)
Returns:
[ { "brand": "Peet's Coffee", "product_count": 2, "total_purchases": 18, "total_spent": 215.82, "last_purchase": "2025-01-05T10:30:00Z", "share_of_category": 0.67 }, { "brand": "Starbucks", "product_count": 1, "total_purchases": 9, "total_spent": 107.91, "last_purchase": "2024-12-28T09:15:00Z", "share_of_category": 0.33 }]Performance:
- Latency: 15-25ms
- Cacheable: Yes (20 min TTL)
- Cache key:
category_brand_affinity:{user_id}:{category}:{lookback_days}
Use Cases:
- “What coffee brands do I prefer?”
- “Am I loyal to one brand in this category?”
- Brand switching analysis
2. Product Discovery Tools
Section titled “2. Product Discovery Tools”2.1 search_products
Section titled “2.1 search_products”Purpose: Find products with filters and user context
Query:
MATCH (p:Product)WHERE ($category IS NULL OR p.category = $category) AND ($brand IS NULL OR p.brand = $brand) AND ($name_pattern IS NULL OR toLower(p.name) CONTAINS toLower($name_pattern))
OPTIONAL MATCH (u:User {user_id: $user_id})-[purchased:PURCHASED]->(p)
RETURN { product_id: p.product_id, name: p.name, category: p.category, brand: p.brand, user_purchased: purchased IS NOT NULL, user_purchase_count: CASE WHEN purchased IS NOT NULL THEN purchased.times ELSE 0 END, user_last_purchase: CASE WHEN purchased IS NOT NULL THEN purchased.last ELSE null END} AS productORDER BY user_purchased DESC, p.name ASCLIMIT $limitParameters:
user_id(string, required) - For user contextcategory(string, optional) - Filter by categorybrand(string, optional) - Filter by brandname_pattern(string, optional) - Search in product name (case-insensitive)limit(integer, default: 50, max: 100)
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "user_purchased": true, "user_purchase_count": 12, "user_last_purchase": "2025-01-05T10:30:00Z" }, { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "brand": "Peet's Coffee", "user_purchased": false, "user_purchase_count": 0, "user_last_purchase": null }]Performance:
- Latency: 20-50ms
- Cacheable: Yes (1 hour TTL, varies by user context)
- Cache key:
search_products:{user_id}:{category}:{brand}:{name_pattern}:{limit}
Use Cases:
- “Show me all coffee products”
- “What Fage yogurt products are available?”
- “Search for ‘organic bread‘“
2.2 find_similar_products
Section titled “2.2 find_similar_products”Purpose: Get products similar to a seed product
Query:
MATCH (seed:Product {product_id: $product_id})-[s:SIMILAR_TO]->(similar:Product)WHERE s.score >= $min_similarity
OPTIONAL MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(similar)
RETURN { product_id: similar.product_id, name: similar.name, category: similar.category, brand: similar.brand, similarity_score: s.score, user_purchased: p IS NOT NULL, user_purchase_count: CASE WHEN p IS NOT NULL THEN p.times ELSE 0 END, user_last_purchase: CASE WHEN p IS NOT NULL THEN p.last ELSE null END} AS similar_productORDER BY s.score DESCLIMIT $limitParameters:
product_id(string, required) - Seed productuser_id(string, required) - For user contextmin_similarity(float, default: 0.6, range: 0.0-1.0) - Minimum similarity thresholdlimit(integer, default: 20, max: 50)
Returns:
[ { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "brand": "Peet's Coffee", "similarity_score": 0.89, "user_purchased": false, "user_purchase_count": 0, "user_last_purchase": null }, { "product_id": "COFFEE_SUM", "name": "Sumatra Dark Roast", "category": "Coffee", "brand": "Peet's Coffee", "similarity_score": 0.82, "user_purchased": true, "user_purchase_count": 3, "user_last_purchase": "2024-11-15T08:00:00Z" }]Performance:
- Latency: 5-10ms
- Cacheable: Yes (1 hour TTL)
- Cache key:
similar_products:{product_id}:{user_id}:{min_similarity}:{limit}
Use Cases:
- “Find products like this coffee”
- “Show me alternatives to my usual yogurt”
- Product recommendations
2.3 find_alternatives_in_category
Section titled “2.3 find_alternatives_in_category”Purpose: Find products user hasn’t tried in a category, prioritized by similarity
Query:
MATCH (p:Product {category: $category})WHERE NOT EXISTS((u:User {user_id: $user_id})-[:PURCHASED]->(p)) AND ($brand IS NULL OR p.brand = $brand)
OPTIONAL MATCH (p)<-[s:SIMILAR_TO]-(tried:Product)<-[:PURCHASED]-(u)WITH p, max(s.score) AS similarity_to_tried
RETURN { product_id: p.product_id, name: p.name, category: p.category, brand: p.brand, similarity_to_tried: COALESCE(similarity_to_tried, 0.0), relevance_score: COALESCE(similarity_to_tried, 0.0)} AS alternativeORDER BY relevance_score DESC, p.name ASCLIMIT $limitParameters:
user_id(string, required)category(string, required)brand(string, optional) - Filter to specific brandlimit(integer, default: 20, max: 50)
Returns:
[ { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "brand": "Peet's Coffee", "similarity_to_tried": 0.89, "relevance_score": 0.89 }, { "product_id": "COFFEE_KON", "name": "Kona Blend", "category": "Coffee", "brand": "Hawaiian Gold", "similarity_to_tried": 0.65, "relevance_score": 0.65 }]Performance:
- Latency: 20-40ms
- Cacheable: Yes (30 min TTL)
- Cache key:
alternatives:{user_id}:{category}:{brand}:{limit}
Use Cases:
- “What coffee haven’t I tried yet?”
- “Show me new products in yogurt category”
- Exploration and discovery
3. Temporal Tools
Section titled “3. Temporal Tools”3.1 predict_repurchases
Section titled “3.1 predict_repurchases”Purpose: Predict when user will need to buy products again
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE p.times >= $min_intervals AND p.avg_interval_days > 0
WITH prod, p, p.last + duration({days: toInteger(p.avg_interval_days)}) AS next_expected, duration.inDays(datetime(), p.last + duration({days: toInteger(p.avg_interval_days)})).days AS days_until
WHERE days_until <= $prediction_window AND days_until >= $min_days
RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, next_expected_date: next_expected, days_until: days_until, avg_interval_days: p.avg_interval_days, times_purchased: p.times, last_purchase: p.last, confidence: CASE WHEN p.times >= 5 THEN 'high' WHEN p.times >= 3 THEN 'medium' ELSE 'low' END} AS predictionORDER BY days_until ASCLIMIT $limitParameters:
user_id(string, required)prediction_window(integer, default: 14) - Days ahead to predictmin_days(integer, default: 0) - Minimum days (0 = include overdue)min_intervals(integer, default: 2) - Minimum purchase countlimit(integer, default: 20, max: 50)
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "next_expected_date": "2025-01-09T10:30:00Z", "days_until": 2, "avg_interval_days": 28.5, "times_purchased": 12, "last_purchase": "2025-01-05T10:30:00Z", "confidence": "high" }, { "product_id": "YOGURT_002", "name": "Fage Greek Yogurt 0%", "category": "Yogurt", "brand": "Fage", "next_expected_date": "2025-01-09T14:15:00Z", "days_until": 2, "avg_interval_days": 14.0, "times_purchased": 8, "last_purchase": "2025-01-02T14:15:00Z", "confidence": "high" }]Performance:
- Latency: 5-15ms
- Cacheable: Yes (15 min TTL)
- Cache key:
repurchase_predictions:{user_id}:{prediction_window}:{min_days}:{min_intervals}:{limit}
Use Cases:
- “What should I buy this week?”
- “What am I running low on?”
- Shopping list generation
3.2 get_purchase_timeline
Section titled “3.2 get_purchase_timeline”Purpose: Historical timeline of user’s purchases
Query:
MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product)WHERE ($category IS NULL OR prod.category = $category) AND ($brand IS NULL OR prod.brand = $brand) AND p.last >= datetime() - duration({days: $lookback_days})
RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, purchase_date: p.last, times_purchased: p.times, avg_interval_days: p.avg_interval_days} AS timeline_eventORDER BY p.last DESCLIMIT $limitParameters:
user_id(string, required)category(string, optional) - Filter by categorybrand(string, optional) - Filter by brandlookback_days(integer, default: 180)limit(integer, default: 100, max: 200)
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "purchase_date": "2025-01-05T10:30:00Z", "times_purchased": 12, "avg_interval_days": 28.5 }, { "product_id": "YOGURT_002", "name": "Fage Greek Yogurt 0%", "category": "Yogurt", "brand": "Fage", "purchase_date": "2025-01-02T14:15:00Z", "times_purchased": 8, "avg_interval_days": 14.0 }]Performance:
- Latency: 10-20ms
- Cacheable: Yes (5 min TTL)
- Cache key:
purchase_timeline:{user_id}:{category}:{brand}:{lookback_days}:{limit}
Use Cases:
- “When did I last buy coffee?”
- “Show my purchase history for yogurt”
- Temporal analysis
4. Offer Tools
Section titled “4. Offer Tools”4.1 get_active_offers
Section titled “4.1 get_active_offers”Purpose: Get offers user is currently eligible for
Query:
MATCH (u:User {user_id: $user_id})-[e:ELIGIBLE]->(o:Offer)WHERE o.start <= datetime() AND datetime() <= o.end AND e.start <= datetime()
OPTIONAL MATCH (o)-[:APPLIES_TO]->(p:Product)WHERE ($category IS NULL OR p.category = $category)
WITH o, collect(DISTINCT p.product_id) AS applicable_productsWHERE size(applicable_products) > 0 OR $category IS NULL
RETURN { offer_id: o.offer_id, title: o.title, description: o.description, points: o.points, priority: o.priority, start_date: o.start, end_date: o.end, days_remaining: duration.inDays(datetime(), o.end).days, applicable_product_ids: applicable_products, product_count: size(applicable_products)} AS offerORDER BY o.points DESC, o.priority DESC, o.end ASCLIMIT $limitParameters:
user_id(string, required)category(string, optional) - Filter to offers in specific categorylimit(integer, default: 20, max: 50)
Returns:
[ { "offer_id": "OFFER_001", "title": "500 bonus points on coffee", "description": "Earn 500 points on any coffee purchase", "points": 500, "priority": 1, "start_date": "2025-01-05T00:00:00Z", "end_date": "2025-01-10T23:59:59Z", "days_remaining": 3, "applicable_product_ids": ["COFFEE_001", "COFFEE_002", "COFFEE_ETH"], "product_count": 3 }, { "offer_id": "OFFER_015", "title": "Double points on dairy", "description": "2x points on all dairy products", "points": 200, "priority": 2, "start_date": "2025-01-07T00:00:00Z", "end_date": "2025-01-14T23:59:59Z", "days_remaining": 7, "applicable_product_ids": ["YOGURT_002", "MILK_001", "CHEESE_003"], "product_count": 3 }]Performance:
- Latency: 10-30ms
- Cacheable: Yes (5 min TTL)
- Cache key:
active_offers:{user_id}:{category}:{limit}
Use Cases:
- “What offers do I have?”
- “Are there any coffee deals?”
- Offer discovery
4.2 match_products_to_offers
Section titled “4.2 match_products_to_offers”Purpose: Check which specific products have active offers
Query:
MATCH (u:User {user_id: $user_id})UNWIND $product_ids AS product_id
MATCH (p:Product {product_id: product_id})
OPTIONAL MATCH (u)-[:ELIGIBLE]->(o:Offer)-[:APPLIES_TO]->(p)WHERE o.start <= datetime() AND datetime() <= o.end
RETURN { product_id: p.product_id, name: p.name, category: p.category, brand: p.brand, has_offer: o IS NOT NULL, offer: CASE WHEN o IS NOT NULL THEN { offer_id: o.offer_id, title: o.title, points: o.points, end_date: o.end, days_remaining: duration.inDays(datetime(), o.end).days } ELSE null END} AS product_offerORDER BY has_offer DESC, p.name ASCParameters:
user_id(string, required)product_ids(array of strings, required) - Products to check
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "has_offer": true, "offer": { "offer_id": "OFFER_001", "title": "500 bonus points on coffee", "points": 500, "end_date": "2025-01-10T23:59:59Z", "days_remaining": 3 } }, { "product_id": "BREAD_005", "name": "Dave's Killer Bread - 21 Grain", "category": "Bread", "brand": "Dave's Killer Bread", "has_offer": false, "offer": null }]Performance:
- Latency: 5-10ms
- Cacheable: No (product_ids are dynamic)
Use Cases:
- “Does my coffee have an offer?”
- “Which items in my list have rewards?”
- Shopping list optimization
4.3 search_offers
Section titled “4.3 search_offers”Purpose: Search all available offers with filters
Query:
MATCH (u:User {user_id: $user_id})-[e:ELIGIBLE]->(o:Offer)WHERE o.start <= datetime() AND datetime() <= o.end AND e.start <= datetime() AND ($min_points IS NULL OR o.points >= $min_points) AND ($search_term IS NULL OR toLower(o.title) CONTAINS toLower($search_term) OR toLower(o.description) CONTAINS toLower($search_term))
// Optional retailer filterOPTIONAL MATCH (o)-[:AVAILABLE_AT]->(r:Retailer)WHERE ($retailer_id IS NULL OR r.retailer_id = $retailer_id)
WITH o, collect(DISTINCT r.retailer_id) AS retailer_idsWHERE $retailer_id IS NULL OR $retailer_id IN retailer_ids
// Get applicable productsOPTIONAL MATCH (o)-[:APPLIES_TO]->(p:Product)WHERE ($category IS NULL OR p.category = $category) AND ($brand IS NULL OR p.brand = $brand)
WITH o, retailer_ids, collect(DISTINCT { product_id: p.product_id, name: p.name, category: p.category, brand: p.brand}) AS applicable_products
WHERE size(applicable_products) > 0 OR ($category IS NULL AND $brand IS NULL)
RETURN { offer_id: o.offer_id, title: o.title, description: o.description, points: o.points, priority: o.priority, start_date: o.start, end_date: o.end, days_remaining: duration.inDays(datetime(), o.end).days, applicable_products: applicable_products, product_count: size(applicable_products), retailer_ids: retailer_ids, is_retailer_specific: size(retailer_ids) > 0} AS offerORDER BY o.points DESC, o.priority DESC, o.end ASCLIMIT $limitParameters:
user_id(string, required)category(string, optional) - Filter to categorybrand(string, optional) - Filter to brandretailer_id(string, optional) - Filter to specific retailermin_points(integer, optional) - Minimum points thresholdsearch_term(string, optional) - Search in title/descriptionlimit(integer, default: 20, max: 50)
Returns:
[ { "offer_id": "OFFER_001", "title": "500 bonus points on coffee", "description": "Earn 500 points on any coffee purchase from Peet's, Starbucks, or local brands", "points": 500, "priority": 1, "start_date": "2025-01-05T00:00:00Z", "end_date": "2025-01-10T23:59:59Z", "days_remaining": 3, "applicable_products": [ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee" }, { "product_id": "COFFEE_002", "name": "French Roast", "category": "Coffee", "brand": "Peet's Coffee" } ], "product_count": 2, "retailer_ids": ["WALMART_001", "TARGET_005"], "is_retailer_specific": true }]Performance:
- Latency: 15-40ms
- Cacheable: Yes (5 min TTL)
- Cache key:
search_offers:{user_id}:{category}:{brand}:{retailer_id}:{min_points}:{search_term}:{limit}
Use Cases:
- “Show me all high-value offers”
- “Find offers with ‘bonus’ in the title”
- “What Peet’s Coffee offers are available?”
- “What offers are valid at Walmart?”
- Advanced offer discovery
5. Brand Tools
Section titled “5. Brand Tools”5.1 get_brand_products
Section titled “5.1 get_brand_products”Purpose: Get all products from a specific brand
Query:
MATCH (p:Product {brand: $brand})WHERE ($category IS NULL OR p.category = $category)
OPTIONAL MATCH (u:User {user_id: $user_id})-[purchased:PURCHASED]->(p)
RETURN { product_id: p.product_id, name: p.name, category: p.category, user_purchased: purchased IS NOT NULL, user_purchase_count: CASE WHEN purchased IS NOT NULL THEN purchased.times ELSE 0 END, user_last_purchase: CASE WHEN purchased IS NOT NULL THEN purchased.last ELSE null END, user_total_spent: CASE WHEN purchased IS NOT NULL THEN purchased.total_spent ELSE 0 END} AS productORDER BY user_purchased DESC, p.category ASC, p.name ASCLIMIT $limitParameters:
brand(string, required) - Brand nameuser_id(string, required) - For user contextcategory(string, optional) - Filter to categorylimit(integer, default: 50, max: 100)
Returns:
[ { "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "user_purchased": true, "user_purchase_count": 12, "user_last_purchase": "2025-01-05T10:30:00Z", "user_total_spent": 143.88 }, { "product_id": "COFFEE_002", "name": "French Roast", "category": "Coffee", "user_purchased": true, "user_purchase_count": 6, "user_last_purchase": "2024-12-20T11:00:00Z", "user_total_spent": 71.94 }, { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "user_purchased": false, "user_purchase_count": 0, "user_last_purchase": null, "user_total_spent": 0 }]Performance:
- Latency: 15-30ms
- Cacheable: Yes (30 min TTL)
- Cache key:
brand_products:{brand}:{user_id}:{category}:{limit}
Use Cases:
- “Show me all Peet’s Coffee products”
- “What Fage products are available?”
- Brand exploration
5.2 compare_brands
Section titled “5.2 compare_brands”Purpose: Compare user’s relationship with multiple brands
Query:
MATCH (u:User {user_id: $user_id})UNWIND $brands AS brand
OPTIONAL MATCH (u)-[p:PURCHASED]->(prod:Product {brand: brand})WHERE ($category IS NULL OR prod.category = $category)
WITH brand, count(DISTINCT prod) AS product_count, sum(p.times) AS total_purchases, sum(p.total_spent) AS total_spent, max(p.last) AS last_purchase, avg(p.avg_interval_days) AS avg_interval
RETURN { brand: brand, product_count: COALESCE(product_count, 0), total_purchases: COALESCE(total_purchases, 0), total_spent: COALESCE(total_spent, 0.0), last_purchase: last_purchase, avg_interval: avg_interval, is_purchased: product_count > 0} AS brand_comparisonORDER BY total_purchases DESCParameters:
user_id(string, required)brands(array of strings, required) - Brands to comparecategory(string, optional) - Filter to category
Returns:
[ { "brand": "Peet's Coffee", "product_count": 2, "total_purchases": 18, "total_spent": 215.82, "last_purchase": "2025-01-05T10:30:00Z", "avg_interval": 28.5, "is_purchased": true }, { "brand": "Starbucks", "product_count": 1, "total_purchases": 9, "total_spent": 107.91, "last_purchase": "2024-12-28T09:15:00Z", "avg_interval": 32.0, "is_purchased": true }, { "brand": "Lavazza", "product_count": 0, "total_purchases": 0, "total_spent": 0.0, "last_purchase": null, "avg_interval": null, "is_purchased": false }]Performance:
- Latency: 20-40ms
- Cacheable: Yes (15 min TTL)
- Cache key:
compare_brands:{user_id}:{brands_hash}:{category}
Use Cases:
- “Compare Peet’s vs Starbucks for my purchases”
- “Which yogurt brand do I buy more?”
- Brand preference analysis
5.3 discover_brands_in_category
Section titled “5.3 discover_brands_in_category”Purpose: Find all brands in a category, ranked by user preference
Query:
MATCH (p:Product {category: $category})
WITH DISTINCT p.brand AS brandORDER BY brand
OPTIONAL MATCH (u:User {user_id: $user_id})-[purchased:PURCHASED]->(prod:Product {brand: brand, category: $category})
WITH brand, count(DISTINCT prod) AS products_purchased, sum(purchased.times) AS total_purchases, sum(purchased.total_spent) AS total_spent, max(purchased.last) AS last_purchase
RETURN { brand: brand, products_purchased: COALESCE(products_purchased, 0), total_purchases: COALESCE(total_purchases, 0), total_spent: COALESCE(total_spent, 0.0), last_purchase: last_purchase, user_loyalty: CASE WHEN total_purchases >= 10 THEN 'high' WHEN total_purchases >= 3 THEN 'medium' WHEN total_purchases >= 1 THEN 'low' ELSE 'never_purchased' END} AS brandORDER BY total_purchases DESC NULLS LAST, brand ASCLIMIT $limitParameters:
user_id(string, required)category(string, required)limit(integer, default: 20, max: 50)
Returns:
[ { "brand": "Peet's Coffee", "products_purchased": 2, "total_purchases": 18, "total_spent": 215.82, "last_purchase": "2025-01-05T10:30:00Z", "user_loyalty": "high" }, { "brand": "Starbucks", "products_purchased": 1, "total_purchases": 9, "total_spent": 107.91, "last_purchase": "2024-12-28T09:15:00Z", "user_loyalty": "medium" }, { "brand": "Lavazza", "products_purchased": 0, "total_purchases": 0, "total_spent": 0.0, "last_purchase": null, "user_loyalty": "never_purchased" }]Performance:
- Latency: 25-45ms
- Cacheable: Yes (20 min TTL)
- Cache key:
discover_brands:{user_id}:{category}:{limit}
Use Cases:
- “What coffee brands are available?”
- “Show me yogurt brands I haven’t tried”
- Category brand exploration
6. Retailer Tools
Section titled “6. Retailer Tools”6.1 get_user_retailers
Section titled “6.1 get_user_retailers”Purpose: Get retailers where user has shopped, ranked by frequency
Query:
MATCH (u:User {user_id: $user_id})-[pa:PURCHASED_AT]->(r:Retailer)WHERE pa.last >= datetime() - duration({days: $lookback_days})
RETURN { retailer_id: r.retailer_id, name: r.name, address: r.address, city: r.city, state: r.state, zip: r.zip, times_visited: pa.times, last_visit: pa.last, total_spent: pa.total_spent} AS retailerORDER BY pa.times DESC, pa.last DESCLIMIT $limitParameters:
user_id(string, required)lookback_days(integer, default: 365)limit(integer, default: 20, max: 50)
Returns:
[ { "retailer_id": "WALMART_001", "name": "Walmart Supercenter", "address": "123 Main St", "city": "Springfield", "state": "IL", "zip": "62701", "times_visited": 45, "last_visit": "2025-01-05T18:30:00Z", "total_spent": 1250.45 }, { "retailer_id": "TARGET_005", "name": "Target", "address": "456 Oak Ave", "city": "Springfield", "state": "IL", "zip": "62702", "times_visited": 28, "last_visit": "2024-12-30T14:15:00Z", "total_spent": 845.20 }]Performance:
- Latency: 15-30ms
- Cacheable: Yes (15 min TTL)
- Cache key:
user_retailers:{user_id}:{lookback_days}:{limit}
Use Cases:
- “Where do I usually shop?”
- “What’s my favorite store?”
- Understanding shopping patterns
6.2 get_retailer_offers
Section titled “6.2 get_retailer_offers”Purpose: Get active offers at a specific retailer
Query:
MATCH (u:User {user_id: $user_id})-[:ELIGIBLE]->(o:Offer)-[:AVAILABLE_AT]->(r:Retailer {retailer_id: $retailer_id})WHERE o.start <= datetime() AND datetime() <= o.end
OPTIONAL MATCH (o)-[:APPLIES_TO]->(p:Product)WHERE ($category IS NULL OR p.category = $category)
WITH o, collect(DISTINCT p.product_id) AS applicable_productsWHERE size(applicable_products) > 0 OR $category IS NULL
RETURN { offer_id: o.offer_id, title: o.title, description: o.description, points: o.points, priority: o.priority, start_date: o.start, end_date: o.end, days_remaining: duration.inDays(datetime(), o.end).days, applicable_product_ids: applicable_products, product_count: size(applicable_products)} AS offerORDER BY o.points DESC, o.priority DESC, o.end ASCLIMIT $limitParameters:
user_id(string, required)retailer_id(string, required)category(string, optional) - Filter to categorylimit(integer, default: 20, max: 50)
Returns:
[ { "offer_id": "OFFER_WALMART_500", "title": "500 points on coffee at Walmart", "description": "Exclusive Walmart offer - earn 500 points on any coffee purchase", "points": 500, "priority": 1, "start_date": "2025-01-05T00:00:00Z", "end_date": "2025-01-10T23:59:59Z", "days_remaining": 3, "applicable_product_ids": ["COFFEE_001", "COFFEE_002", "COFFEE_ETH"], "product_count": 3 }]Performance:
- Latency: 15-35ms
- Cacheable: Yes (5 min TTL)
- Cache key:
retailer_offers:{user_id}:{retailer_id}:{category}:{limit}
Use Cases:
- “What offers are available at Walmart?”
- “Show me Target coffee deals”
- Store-specific offer discovery
7. Collaborative Tools
Section titled “7. Collaborative Tools”7.1 get_collaborative_recommendations
Section titled “7.1 get_collaborative_recommendations”Purpose: Find products that similar users have purchased (“people like me”)
Query:
MATCH (me:User {user_id: $user_id})
// Step 1: Find products I've bought (optionally in specific categories)MATCH (me)-[:PURCHASED]->(my_products:Product)WHERE ($categories IS NULL OR my_products.category IN $categories)
// Step 2: Find other users who bought those same productsMATCH (similar_user:User)-[:PURCHASED]->(my_products)WHERE similar_user.user_id <> $user_id AND ($same_zip_only = false OR similar_user.zip = me.zip)
// Step 3: Count how many products we have in commonWITH me, similar_user, count(DISTINCT my_products) AS shared_productsWHERE shared_products >= $min_shared_products
// Step 4: Find what they bought that I haven'tMATCH (similar_user)-[p:PURCHASED]->(their_product:Product)WHERE NOT EXISTS((me)-[:PURCHASED]->(their_product)) AND ($category IS NULL OR their_product.category = $category)
// Step 5: Aggregate by product, weight by user similarityWITH their_product, count(DISTINCT similar_user) AS buyer_count, sum(shared_products) AS similarity_score, avg(p.times) AS avg_repurchases
RETURN { product_id: their_product.product_id, name: their_product.name, category: their_product.category, brand: their_product.brand, buyer_count: buyer_count, similarity_score: similarity_score, avg_repurchases: avg_repurchases, recommendation_strength: buyer_count * similarity_score} AS recommendationORDER BY recommendation_strength DESC, buyer_count DESCLIMIT $limitParameters:
user_id(string, required)categories(array of strings, optional) - Focus similarity on specific categoriessame_zip_only(boolean, default: false) - Only include users from same zip codemin_shared_products(integer, default: 2) - Minimum overlap to consider users similarcategory(string, optional) - Only recommend products in this categorylimit(integer, default: 20, max: 50)
Returns:
[ { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "brand": "Peet's Coffee", "buyer_count": 18, "similarity_score": 84, "avg_repurchases": 3.2, "recommendation_strength": 1512 }, { "product_id": "YOGURT_005", "name": "Chobani Greek Yogurt", "category": "Yogurt", "brand": "Chobani", "buyer_count": 12, "similarity_score": 58, "avg_repurchases": 5.1, "recommendation_strength": 696 }]Performance:
- Latency: 50-150ms (depends on user base size and overlap)
- Cacheable: Yes (20 min TTL)
- Cache key:
collab_recs:{user_id}:{categories_hash}:{same_zip_only}:{category}:{limit}
Use Cases:
- “What do people like me buy?”
- “Show me products similar shoppers purchase”
- “What coffee do people with my taste buy?”
- Personalized discovery
Performance Notes:
- Can be expensive at scale (cross-user traversals)
- Consider caching aggressively
- May want to limit to users with recent activity
- Consider adding time filter on purchases for similarity matching
7.2 get_local_trending_products
Section titled “7.2 get_local_trending_products”Purpose: Find popular products in user’s zip code that they haven’t tried
Query:
MATCH (me:User {user_id: $user_id})
MATCH (local_user:User {zip: me.zip})-[p:PURCHASED]->(prod:Product)WHERE local_user.user_id <> $user_id AND ($category IS NULL OR prod.category = $category) AND p.last >= datetime() - duration({days: $lookback_days})
// Exclude products the requesting user has boughtOPTIONAL MATCH (me)-[:PURCHASED]->(prod)
WITH prod, count(DISTINCT local_user) AS local_buyers, meWHERE me IS NULL // Haven't bought it
RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, local_buyers: local_buyers, popularity_score: local_buyers} AS trendingORDER BY local_buyers DESCLIMIT $limitParameters:
user_id(string, required)category(string, optional) - Filter to specific categorylookback_days(integer, default: 90) - Time window for “trending”limit(integer, default: 20, max: 50)
Returns:
[ { "product_id": "COFFEE_LOCAL", "name": "Local Roasters House Blend", "category": "Coffee", "brand": "Springfield Roasters", "local_buyers": 45, "popularity_score": 45 }, { "product_id": "BREAD_ARTISAN", "name": "Artisan Sourdough", "category": "Bread", "brand": "Local Bakery", "local_buyers": 32, "popularity_score": 32 }]Performance:
- Latency: 30-80ms (depends on zip code user density)
- Cacheable: Yes (30 min TTL)
- Cache key:
local_trending:{zip}:{category}:{lookback_days}:{limit}
Use Cases:
- “What’s popular in my area?”
- “What do people in my neighborhood buy?”
- “Show me local favorites”
- Geographic discovery
Performance Notes:
- Much faster than collaborative filtering
- Can be cached at zip code level (shared across users)
- Consider indexing User.zip for better performance
8. Composition Tool
Section titled “8. Composition Tool”8.1 get_product_context
Section titled “8.1 get_product_context”Purpose: Get comprehensive context about a product for a user
Query:
MATCH (p:Product {product_id: $product_id})
OPTIONAL MATCH (u:User {user_id: $user_id})-[purchased:PURCHASED]->(p)
OPTIONAL MATCH (p)-[s:SIMILAR_TO]->(similar:Product)WHERE s.score >= $similarity_thresholdWITH p, purchased, collect({ product_id: similar.product_id, name: similar.name, category: similar.category, brand: similar.brand, score: s.score})[0..$max_similar] AS similar_products
OPTIONAL MATCH (u:User {user_id: $user_id})-[:ELIGIBLE]->(o:Offer)-[:APPLIES_TO]->(p)WHERE o.start <= datetime() AND datetime() <= o.endWITH p, purchased, similar_products, collect({ offer_id: o.offer_id, title: o.title, description: o.description, points: o.points, end_date: o.end, days_remaining: duration.inDays(datetime(), o.end).days}) AS active_offers
RETURN { product_id: p.product_id, name: p.name, category: p.category, brand: p.brand, user_history: CASE WHEN purchased IS NOT NULL THEN { times_purchased: purchased.times, last_purchase: purchased.last, avg_interval_days: purchased.avg_interval_days, total_spent: purchased.total_spent, next_expected: CASE WHEN purchased.avg_interval_days > 0 THEN purchased.last + duration({days: toInteger(purchased.avg_interval_days)}) ELSE null END, days_until_next: CASE WHEN purchased.avg_interval_days > 0 THEN duration.inDays(datetime(), purchased.last + duration({days: toInteger(purchased.avg_interval_days)})).days ELSE null END } ELSE null END, similar_products: similar_products, active_offers: active_offers, has_active_offers: size(active_offers) > 0} AS product_contextParameters:
product_id(string, required)user_id(string, required)similarity_threshold(float, default: 0.7)max_similar(integer, default: 5)
Returns:
{ "product_id": "COFFEE_001", "name": "Colombian Medium Roast Coffee", "category": "Coffee", "brand": "Peet's Coffee", "user_history": { "times_purchased": 12, "last_purchase": "2025-01-05T10:30:00Z", "avg_interval_days": 28.5, "total_spent": 143.88, "next_expected": "2025-02-02T10:30:00Z", "days_until_next": 26 }, "similar_products": [ { "product_id": "COFFEE_ETH", "name": "Ethiopian Medium Roast", "category": "Coffee", "brand": "Peet's Coffee", "score": 0.89 }, { "product_id": "COFFEE_SUM", "name": "Sumatra Dark Roast", "category": "Coffee", "brand": "Peet's Coffee", "score": 0.82 } ], "active_offers": [ { "offer_id": "OFFER_001", "title": "500 bonus points on coffee", "description": "Earn 500 points on any coffee purchase", "points": 500, "end_date": "2025-01-10T23:59:59Z", "days_remaining": 3 } ], "has_active_offers": true}Performance:
- Latency: 15-30ms
- Cacheable: Yes (30 min TTL)
- Cache key:
product_context:{product_id}:{user_id}:{similarity_threshold}:{max_similar}
Use Cases:
- “Tell me everything about this coffee”
- “Should I buy this product?”
- Deep product exploration
Tool Composition Examples
Section titled “Tool Composition Examples”Example 1: “What should I buy this week?”
Section titled “Example 1: “What should I buy this week?””# Step 1: Get repurchase predictionspredictions = tools.predict_repurchases( user_id='USER123', prediction_window=7, min_days=0)
# Step 2: Check which products have offersproduct_ids = [p['product_id'] for p in predictions]products_with_offers = tools.match_products_to_offers( user_id='USER123', product_ids=product_ids)
# Step 3: Combine resultsshopping_list = []for product in products_with_offers: pred = next(p for p in predictions if p['product_id'] == product['product_id']) shopping_list.append({ **product, 'days_until': pred['days_until'], 'priority': 'high' if pred['days_until'] <= 2 else 'medium' })
# Sort by urgency and offer valueshopping_list.sort(key=lambda x: ( x['days_until'], -x.get('offer', {}).get('points', 0)))Example 2: “Show me alternatives to my usual coffee”
Section titled “Example 2: “Show me alternatives to my usual coffee””# Step 1: Get user's coffee purchase historyhistory = tools.get_user_purchase_history( user_id='USER123', lookback_days=90)
# Filter to coffee categorycoffee_purchases = [h for h in history if h['category'] == 'Coffee']
# Find most purchasedusual_coffee = max(coffee_purchases, key=lambda x: x['times_purchased'])
# Step 2: Find similar productssimilar = tools.find_similar_products( product_id=usual_coffee['product_id'], user_id='USER123', min_similarity=0.75)
# Step 3: Check for offerssimilar_ids = [s['product_id'] for s in similar if not s['user_purchased']]with_offers = tools.match_products_to_offers( user_id='USER123', product_ids=similar_ids)
# Combinealternatives = []for product in with_offers: sim = next(s for s in similar if s['product_id'] == product['product_id']) alternatives.append({ **product, 'similarity_score': sim['similarity_score'], 'recommendation_reason': f"{int(sim['similarity_score']*100)}% similar to {usual_coffee['name']}" })
# Sort by offer points, then similarityalternatives.sort(key=lambda x: ( -x.get('offer', {}).get('points', 0), -x['similarity_score']))Example 3: “Compare my spending on Peet’s vs Starbucks”
Section titled “Example 3: “Compare my spending on Peet’s vs Starbucks””# Use compare_brands toolcomparison = tools.compare_brands( user_id='USER123', brands=['Peet\'s Coffee', 'Starbucks'], category='Coffee')
# Get brand products for contextpeets_products = tools.get_brand_products( brand='Peet\'s Coffee', user_id='USER123', category='Coffee')
starbucks_products = tools.get_brand_products( brand='Starbucks', user_id='USER123', category='Coffee')
# Synthesizereport = { 'comparison': comparison, 'details': { 'peets': { 'stats': comparison[0], 'products': peets_products }, 'starbucks': { 'stats': comparison[1], 'products': starbucks_products } }}Example 4: “Build shopping list optimized for rewards”
Section titled “Example 4: “Build shopping list optimized for rewards””# Step 1: Get predictions for next 2 weekspredictions = tools.predict_repurchases( user_id='USER123', prediction_window=14)
# Step 2: Get all active offersoffers = tools.get_active_offers( user_id='USER123')
# Step 3: Match predictions to offerspredicted_ids = [p['product_id'] for p in predictions]matched = tools.match_products_to_offers( user_id='USER123', product_ids=predicted_ids)
# Step 4: Calculate value scoreshopping_list = []for product in matched: pred = next(p for p in predictions if p['product_id'] == product['product_id'])
# Value score: urgency + offer points urgency_score = max(0, 14 - pred['days_until']) / 14 # 0-1 offer_score = product.get('offer', {}).get('points', 0) / 500 # normalize value_score = (urgency_score * 0.6) + (offer_score * 0.4)
shopping_list.append({ **product, 'days_until': pred['days_until'], 'urgency': 'high' if pred['days_until'] <= 3 else 'medium', 'value_score': value_score })
# Sort by value scoreshopping_list.sort(key=lambda x: -x['value_score'])
# Calculate total pointstotal_points = sum(p.get('offer', {}).get('points', 0) for p in shopping_list)Performance Summary
Section titled “Performance Summary”| Tool | Avg Latency | Cacheable | Cache TTL | Max Results |
|---|---|---|---|---|
get_user_purchase_history | 5-15ms | ✅ | 5 min | 100 |
get_user_categories | 10-20ms | ✅ | 15 min | 20 |
get_user_brands | 10-20ms | ✅ | 15 min | 50 |
get_purchase_patterns | 5-10ms | ✅ | 10 min | 100 |
get_category_brand_affinity | 15-25ms | ✅ | 20 min | 20 |
search_products | 20-50ms | ✅ | 1 hour | 100 |
find_similar_products | 5-10ms | ✅ | 1 hour | 50 |
find_alternatives_in_category | 20-40ms | ✅ | 30 min | 50 |
predict_repurchases | 5-15ms | ✅ | 15 min | 50 |
get_purchase_timeline | 10-20ms | ✅ | 5 min | 200 |
get_active_offers | 10-30ms | ✅ | 5 min | 50 |
match_products_to_offers | 5-10ms | ❌ | - | 50 |
search_offers | 15-40ms | ✅ | 5 min | 50 |
get_brand_products | 15-30ms | ✅ | 30 min | 100 |
compare_brands | 20-40ms | ✅ | 15 min | 10 |
discover_brands_in_category | 25-45ms | ✅ | 20 min | 50 |
get_user_retailers | 15-30ms | ✅ | 15 min | 50 |
get_retailer_offers | 15-35ms | ✅ | 5 min | 50 |
get_collaborative_recommendations | 50-150ms | ✅ | 20 min | 50 |
get_local_trending_products | 30-80ms | ✅ | 30 min | 50 |
get_product_context | 15-30ms | ✅ | 30 min | 1 |
Implementation Template
Section titled “Implementation Template”package tools
import ( "context" "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j")
type GraphTools struct { driver neo4j.DriverWithContext cache CacheClient userID string}
func NewGraphTools(driver neo4j.DriverWithContext, cache CacheClient, userID string) *GraphTools { return &GraphTools{ driver: driver, cache: cache, userID: userID, }}
// Tool: get_user_purchase_historyfunc (gt *GraphTools) GetUserPurchaseHistory(ctx context.Context, lookbackDays int, limit int) ([]PurchaseHistoryItem, error) { // Set defaults if lookbackDays == 0 { lookbackDays = 180 } if limit == 0 || limit > 100 { limit = 50 }
// Check cache cacheKey := fmt.Sprintf("purchase_history:%s:%d:%d", gt.userID, lookbackDays, limit) if cached, found := gt.cache.Get(cacheKey); found { return cached.([]PurchaseHistoryItem), nil }
// Execute query query := ` MATCH (u:User {user_id: $user_id})-[p:PURCHASED]->(prod:Product) WHERE p.last >= datetime() - duration({days: $lookback_days}) RETURN { product_id: prod.product_id, name: prod.name, category: prod.category, brand: prod.brand, last_purchase: p.last, times_purchased: p.times, avg_interval_days: p.avg_interval_days, total_spent: p.total_spent } AS purchase ORDER BY p.last DESC LIMIT $limit `
session := gt.driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) defer session.Close(ctx)
result, err := session.Run(ctx, query, map[string]interface{}{ "user_id": gt.userID, "lookback_days": lookbackDays, "limit": limit, }) if err != nil { return nil, err }
var items []PurchaseHistoryItem for result.Next(ctx) { record := result.Record() purchase, _ := record.Get("purchase") // Parse into PurchaseHistoryItem struct // ... items = append(items, item) }
// Cache result gt.cache.Set(cacheKey, items, 5*time.Minute)
return items, nil}
// Implement remaining 15 tools...Safety Constraints
Section titled “Safety Constraints”All tools enforce:
- User scoping: Every query filters by
$user_id - Result limits: All queries have
LIMITclauses - Timeout: 5-second query timeout
- Parameter validation: Required params checked, ranges enforced
- Cache invalidation: Triggered by purchase events
Next Steps
Section titled “Next Steps”- Implement in Go: Create
pkg/graph/tools/package with all 20 tools - Add caching: Integrate Redis for caching layer with tool-specific TTLs
- Agent integration: Expose as function-calling tools to LLM
- Monitoring: Track tool usage, latency, cache hit rates
- Optimization: Add indexes based on actual query patterns
- Index on User.zip for collaborative filtering and trending queries
- Consider composite indexes for category + brand lookups
- Schema updates: Add Retailer nodes and PURCHASED_AT, AVAILABLE_AT relationships
- Collaborative filtering optimization: Monitor performance at scale, consider pre-computation if needed