8.9TEMPLATE

Inventory And Reorder Agent (restaurant)

For a restaurant, but the structure works for any business that has inventory depleting against sales.

"""
Inventory and Reorder Agent (Restaurant)
- Reads yesterday's sales by item.
- Multiplies by recipe ingredients to compute consumption.
- Subtracts from current inventory.
- Flags items below reorder threshold.
- Drafts a reorder for manager approval.

Prerequisites:
- Sales data accessible (POS API, exported CSV, database).
- A recipes table mapping menu items to ingredient quantities.
- An inventory table with current stock and reorder thresholds.
- A vendor table mapping ingredients to suppliers.
"""
import json
from crewai import Agent, Task, Crew, Process
from crewai_tools import tool


# ---------- Stub tools (replace with real DB / API calls) ----------

@tool("get_yesterday_sales")
def get_yesterday_sales() -> str:
    """Return yesterday's sales by menu item.
    [{menu_item_id, item_name, quantity_sold}, ...]"""
    # Real version: SELECT FROM your POS database or call its API.
    return json.dumps([
        {"menu_item_id": 1, "item_name": "Cheeseburger",
         "quantity_sold": 47},
        {"menu_item_id": 2, "item_name": "Caesar Salad",
         "quantity_sold": 22},
        {"menu_item_id": 3, "item_name": "Fries (large)",
         "quantity_sold": 89},
    ])


@tool("get_recipes")
def get_recipes() -> str:
    """Return the recipe (ingredient breakdown) for each menu item.
    [{menu_item_id, ingredients: [{ingredient_id, name, qty,
    unit}]}, ...]"""
    return json.dumps([
        {"menu_item_id": 1, "ingredients": [
            {"ingredient_id": 101, "name": "Beef patty", "qty": 1,
             "unit": "patty"},
            {"ingredient_id": 102, "name": "Bun", "qty": 1,
             "unit": "bun"},
            {"ingredient_id": 103, "name": "Cheese slice", "qty": 1,
             "unit": "slice"},
        ]},
        {"menu_item_id": 2, "ingredients": [
            {"ingredient_id": 201, "name": "Romaine", "qty": 4,
             "unit": "oz"},
            {"ingredient_id": 202, "name": "Caesar dressing",
             "qty": 2, "unit": "oz"},
        ]},
        {"menu_item_id": 3, "ingredients": [
            {"ingredient_id": 301, "name": "Potatoes", "qty": 8,
             "unit": "oz"},
        ]},
    ])


@tool("get_current_inventory")
def get_current_inventory() -> str:
    """Return current inventory and reorder thresholds.
    [{ingredient_id, name, on_hand, unit, reorder_threshold,
    reorder_quantity, vendor_id}, ...]"""
    return json.dumps([
        {"ingredient_id": 101, "name": "Beef patty", "on_hand": 60,
         "unit": "patty", "reorder_threshold": 100,
         "reorder_quantity": 200, "vendor_id": 1},
        {"ingredient_id": 102, "name": "Bun", "on_hand": 75,
         "unit": "bun", "reorder_threshold": 100,
         "reorder_quantity": 144, "vendor_id": 2},
        {"ingredient_id": 103, "name": "Cheese slice", "on_hand": 200,
         "unit": "slice", "reorder_threshold": 150,
         "reorder_quantity": 480, "vendor_id": 1},
        {"ingredient_id": 301, "name": "Potatoes", "on_hand": 50,
         "unit": "oz", "reorder_threshold": 500,
         "reorder_quantity": 2000, "vendor_id": 3},
    ])


@tool("draft_purchase_order")
def draft_purchase_order(items_json: str) -> str:
    """Draft a purchase order. items_json is a JSON list of
    {ingredient_id, quantity, vendor_id}."""
    items = json.loads(items_json)
    print("\n=== DRAFTED PURCHASE ORDER (for manager review) ===")
    for it in items:
        print(f"  Ingredient {it['ingredient_id']}: "
              f"{it['quantity']} units, vendor {it['vendor_id']}")
    print("=== END ===\n")
    return "drafted"


# ---------- Agents ----------

analyst = Agent(
    role="Inventory Analyst",
    goal=(
        "Compute yesterday's ingredient consumption and update the "
        "running inventory."
    ),
    backstory=(
        "You are precise with numbers. You always reconcile units "
        "and flag any anomalies."
    ),
    tools=[get_yesterday_sales, get_recipes, get_current_inventory],
    verbose=True,
)

reorder_clerk = Agent(
    role="Reorder Clerk",
    goal=(
        "Identify ingredients below reorder threshold and draft a "
        "purchase order grouped by vendor."
    ),
    backstory=(
        "You group orders by vendor to minimize delivery overhead. "
        "You round up to typical case sizes. You flag anything "
        "unusual to the manager."
    ),
    tools=[draft_purchase_order],
    verbose=True,
)


# ---------- Tasks ----------

analysis_task = Task(
    description=(
        "1. Read yesterday's sales.\n"
        "2. Read the recipes.\n"
        "3. For each ingredient, compute consumption (sum across "
        "all menu items: quantity_sold * recipe_qty).\n"
        "4. Read current inventory.\n"
        "5. Compute new on-hand for each ingredient (current "
        "minus consumption).\n"
        "Output: JSON list of {ingredient_id, name, "
        "consumption, projected_on_hand, reorder_threshold, "
        "needs_reorder (bool)}."
    ),
    agent=analyst,
    expected_output="JSON inventory analysis.",
)

reorder_task = Task(
    description=(
        "From the analysis, identify ingredients where "
        "needs_reorder is true. For each, draft a purchase order "
        "line with the reorder_quantity from inventory. Group lines "
        "by vendor_id. Call draft_purchase_order with the result. "
        "Output: confirmation message for the manager."
    ),
    agent=reorder_clerk,
    expected_output="Manager-ready summary of drafted orders.",
)


crew = Crew(
    agents=[analyst, reorder_clerk],
    tasks=[analysis_task, reorder_task],
    process=Process.sequential,
    verbose=True,
)


if __name__ == "__main__":
    result = crew.kickoff()
    print("\n=== DAILY REORDER REVIEW ===")
    print(result)

Notes for adapting:

  • Replace the four stubbed tools with real database queries against your POS, recipe management, and inventory systems.
  • Add a "send_to_manager_for_approval" step so the manager confirms before any PO actually goes out to a vendor.
  • Schedule daily at 6 AM via launchd/systemd.
  • Hook the approval step to a Slack message, an email, or a simple web page where the manager clicks "Approve all" or modifies quantities.

================================================================================ PART 9 — KEEP GOING

Curriculum last updated 2026-04-30