{"cells":[{"cell_type":"markdown","metadata":{"id":"VMQ24_JB-V6n"},"source":["

\n"," \n","

\n","\n","
Kartify Order Query ChatBot
"]},{"cell_type":"markdown","metadata":{"id":"DYHpGx_vRzV8"},"source":["# **Problem Statement**"]},{"cell_type":"markdown","metadata":{"id":"MDb2jYQASw51"},"source":["## Business Context"]},{"cell_type":"markdown","metadata":{"id":"NhEm2C1TSw8b"},"source":["In today’s fast-paced e-commerce environment, providing high-quality customer support is essential for enhancing customer satisfaction and loyalty. Kartify, a dynamic online retail platform, is committed to improving its customer service experience, particularly in handling order-related inquiries. However, with increasing order volumes and customer queries, the challenge of delivering timely and efficient responses has become increasingly complex.\n","\n","To address this challenge, Kartify recognizes the need for an AI-powered chatbot solution. By leveraging order and product information, the chatbot will be designed to deliver instant, accurate, and context-aware responses to customer inquiries. This enhancement aims not only to streamline customer interactions but also to alleviate the workload on the support team, allowing human agents to focus on more complex inquiries that require personalized attention.\n","\n","Through the implementation of this AI-driven solution, Kartify aims to create a seamless customer support experience that meets the expectations of modern consumers while optimizing operational efficiency."]},{"cell_type":"markdown","metadata":{"id":"fY-KHWBbIAAU"},"source":["## Problem Definition"]},{"cell_type":"markdown","metadata":{"id":"JTjBKHYISw-y"},"source":["Kartify faces the challenge of effectively managing customer inquiries related to orders and products. Current manual methods of addressing these queries can lead to delays in response times and inconsistencies in the quality of information provided. As a result, there is a pressing need to develop a smart support system that can handle common questions related to order status, product details, returns, and other pertinent topics.\n","\n","The key requirements for the chatbot solution include:\n","\n","1. **Instant Response Capabilities**: The chatbot must be able to provide immediate answers to frequently asked questions regarding order statuses, product specifications, and policies, eliminating the need for customers to wait for human intervention.\n","\n","2. **Context-Aware Responses**: The solution should utilize natural language processing (NLP) to understand the context of customer queries effectively, delivering personalized and relevant responses based on the customer's previous interactions and order history.\n","\n","By achieving these, Kartify aspires to revolutionize its customer support approach, positioning itself as a customer-centric brand in the competitive e-commerce landscape and contributing to its long-term success in the marketplace."]},{"cell_type":"markdown","metadata":{"id":"7OEEQv_sIAAV"},"source":["# **Data Gathering**"]},{"cell_type":"markdown","metadata":{"id":"YYsbCtNgSxBE"},"source":["In a full-scale deployment, a **Smart Order Query Assistant Bot** is expected to handle diverse customer queries, ranging from order status and shipping updates to returns and replacements, across multiple platforms. To ensure real-time, accurate, and personalized responses, such a system typically integrates with multiple external services like payment gateways, courier APIs, product catalogs, and customer support tools.\n"]},{"cell_type":"markdown","metadata":{"id":"PvewCZmBSxGF"},"source":["In the current scenario, we are building a **Smart Order Query Assistant Bot** designed to efficiently handle customer queries related to order status, delivery timelines, returns, replacements, and shipping details.\n","\n","The system relies on a **single structured database** that contains all the essential information required for accurate query resolution, including:\n","\n","* **Customer Information**: `customer_id`, `name`, `email`\n","* **Order Items Information**: `order_id`, `product_id`, `quantity`, `price_at_purchase`\n","* **Orders Information**: `order_id`, `order_date`, `status`, `delivery_date`, `total_amount`, `shipping_address`, `payment_method`, `customer_id`\n","* **Order Items Information**: `order_id`, `product_id`, `quantity`, `price_at_purchase`\n","* **Products Information**: `product_id`, `\tname`, `description`, `price`, `warranty_period`, `return_policy`, `battery_life`, `connectivity`, `weight`, `water_resistance`, `display`, `category`, `stock_quantity`\n"]},{"cell_type":"markdown","metadata":{"id":"1X8w_kFmSxIx"},"source":["For the scope of this session, we are going to export a **sample of Kartify’s order database** to demonstrate the end-to-end Agentic AI solution.\n","\n","We'll load this sample data into a Python notebook for illustration and simulate how the chatbot uses it to respond to real-world customer queries.\n"]},{"cell_type":"markdown","metadata":{"id":"H8G5A0Z1SxMU"},"source":["## Data Loading\n","\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"UHFe8yuN-NlB","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1767075273795,"user_tz":-330,"elapsed":3642288,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"3fb8e5e7-8e58-45e5-f2f2-2d7768ee640b"},"outputs":[{"output_type":"stream","name":"stdout","text":["\u001b[31mERROR: Exception:\n","Traceback (most recent call last):\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_internal/cli/base_command.py\", line 179, in exc_logging_wrapper\n"," status = run_func(*args)\n"," ^^^^^^^^^^^^^^^\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_internal/cli/req_command.py\", line 67, in wrapper\n"," return func(self, options, args)\n"," ^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_internal/commands/install.py\", line 377, in run\n"," requirement_set = resolver.resolve(\n"," ^^^^^^^^^^^^^^^^^\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_internal/resolution/resolvelib/resolver.py\", line 95, in resolve\n"," result = self._result = resolver.resolve(\n"," ^^^^^^^^^^^^^^^^^\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_vendor/resolvelib/resolvers.py\", line 546, in resolve\n"," state = resolution.resolve(requirements, max_rounds=max_rounds)\n"," ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/usr/local/lib/python3.12/dist-packages/pip/_vendor/resolvelib/resolvers.py\", line 457, in resolve\n"," raise ResolutionTooDeep(max_rounds)\n","pip._vendor.resolvelib.resolvers.ResolutionTooDeep: 200000\u001b[0m\u001b[31m\n","\u001b[0m"]}],"source":["pip install requests==2.32.4 langgraph langchain langchain-openai langchain-core langchain-community grandalf -q --upgrade"]},{"cell_type":"code","metadata":{"id":"6e7f186d","colab":{"base_uri":"https://localhost:8080/","height":304},"executionInfo":{"status":"error","timestamp":1767075395399,"user_tz":-330,"elapsed":121559,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"be9ac4e2-7c39-49f8-e35a-8d8635b944b4"},"source":["from google.colab import drive\n","drive.mount('/content/drive')"],"execution_count":null,"outputs":[{"output_type":"error","ename":"ValueError","evalue":"mount failed","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-1408506528.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mgoogle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolab\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mdrive\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mdrive\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmount\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'/content/drive'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/drive.py\u001b[0m in \u001b[0;36mmount\u001b[0;34m(mountpoint, force_remount, timeout_ms, readonly)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmount\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmountpoint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mforce_remount\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout_ms\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m120000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreadonly\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;34m\"\"\"Mount your Google Drive at the specified mountpoint path.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m return _mount(\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0mmountpoint\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0mforce_remount\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mforce_remount\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/drive.py\u001b[0m in \u001b[0;36m_mount\u001b[0;34m(mountpoint, force_remount, timeout_ms, ephemeral, readonly)\u001b[0m\n\u001b[1;32m 270\u001b[0m \u001b[0;34m'https://research.google.com/colaboratory/faq.html#drive-timeout'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 271\u001b[0m )\n\u001b[0;32m--> 272\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'mount failed'\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mextra_reason\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 273\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mcase\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 274\u001b[0m \u001b[0;31m# Terminate the DriveFS binary before killing bash.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mValueError\u001b[0m: mount failed"]}]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":556},"id":"726e43f6","executionInfo":{"status":"ok","timestamp":1767021983521,"user_tz":-330,"elapsed":972,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"cc1c4270-849b-4cf9-acc8-6cbae63c553c"},"source":["# Query for fetching 10 lines from the Database\n","query = \"SELECT * FROM orders LIMIT 10;\"\n","\n","# Path to your SQLite database file in Google Drive\n","db_path = \"/content/drive/MyDrive/PG_Dip_AI/Agentic AI/orders.db\"\n","\n","# Connect and read into DataFrame\n","with sqlite3.connect(db_path) as conn:\n"," df = pd.read_sql_query(query, conn)\n","\n","df"],"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":[" order_id order_date status delivery_date total_amount \\\n","0 ORD1001 2025-10-05 Processing None 1598.99 \n","1 ORD1002 2025-08-24 Processing None 1249.98 \n","2 ORD1003 2025-08-27 Processing None 449.98 \n","3 ORD1004 2025-10-01 Returned None 49.99 \n","4 ORD1005 2025-09-18 Cancelled None 1448.98 \n","5 ORD1006 2025-08-09 Cancelled None 1599.96 \n","6 ORD1007 2025-10-13 Returned None 350.00 \n","7 ORD1008 2025-10-24 Shipped None 299.99 \n","8 ORD1009 2025-08-23 Shipped None 299.99 \n","9 ORD1010 2025-10-10 Cancelled None 848.98 \n","\n"," shipping_address payment_method customer_id \n","0 789 Pine Ln, Somewhere, USA Bank Transfer 5 \n","1 123 Main St, Anytown, USA Bank Transfer 4 \n","2 101 Elm Rd, Noplace, USA PayPal 2 \n","3 101 Elm Rd, Noplace, USA Credit Card 3 \n","4 101 Elm Rd, Noplace, USA PayPal 3 \n","5 202 Maple Dr, Everywhere, USA PayPal 1 \n","6 789 Pine Ln, Somewhere, USA Bank Transfer 5 \n","7 456 Oak Ave, Othercity, USA Bank Transfer 3 \n","8 456 Oak Ave, Othercity, USA Credit Card 1 \n","9 123 Main St, Anytown, USA PayPal 4 "],"text/html":["\n","
\n","
\n","\n","\n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n","
order_idorder_datestatusdelivery_datetotal_amountshipping_addresspayment_methodcustomer_id
0ORD10012025-10-05ProcessingNone1598.99789 Pine Ln, Somewhere, USABank Transfer5
1ORD10022025-08-24ProcessingNone1249.98123 Main St, Anytown, USABank Transfer4
2ORD10032025-08-27ProcessingNone449.98101 Elm Rd, Noplace, USAPayPal2
3ORD10042025-10-01ReturnedNone49.99101 Elm Rd, Noplace, USACredit Card3
4ORD10052025-09-18CancelledNone1448.98101 Elm Rd, Noplace, USAPayPal3
5ORD10062025-08-09CancelledNone1599.96202 Maple Dr, Everywhere, USAPayPal1
6ORD10072025-10-13ReturnedNone350.00789 Pine Ln, Somewhere, USABank Transfer5
7ORD10082025-10-24ShippedNone299.99456 Oak Ave, Othercity, USABank Transfer3
8ORD10092025-08-23ShippedNone299.99456 Oak Ave, Othercity, USACredit Card1
9ORD10102025-10-10CancelledNone848.98123 Main St, Anytown, USAPayPal4
\n","
\n","
\n","\n","
\n"," \n","\n"," \n","\n"," \n","
\n","\n","\n","
\n"," \n","\n","\n","\n"," \n","
\n","\n","
\n"," \n"," \n"," \n","
\n","\n","
\n","
\n"],"application/vnd.google.colaboratory.intrinsic+json":{"type":"dataframe","variable_name":"df","repr_error":"Out of range float values are not JSON compliant: nan"}},"metadata":{},"execution_count":17}]},{"cell_type":"markdown","source":["# **Agentic AI Chatbot Setup**"],"metadata":{"id":"KvDztC-gJod4"}},{"cell_type":"code","execution_count":null,"metadata":{"id":"pyG_D1MV6RJc"},"outputs":[],"source":["\"\"\"\n","Kartify Order Query ChatBot - Multi-Agent System with SQL Agent\n","Uses SQLite database instead of mock data\n","\"\"\"\n","\n","# ============================================================================\n","# IMPORTS\n","# ============================================================================\n","from typing import TypedDict, Annotated, Sequence, Literal\n","from langgraph.graph import StateGraph, END\n","from langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n","from langchain_openai import ChatOpenAI\n","from langchain_core.prompts import ChatPromptTemplate\n","from langchain_community.utilities import SQLDatabase\n","from langchain_community.agent_toolkits import create_sql_agent\n","import operator\n","from datetime import datetime, timedelta\n","import json\n","import os\n","import sqlite3\n","import json"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"M46-McuL6N7e","colab":{"base_uri":"https://localhost:8080/","height":211},"executionInfo":{"status":"error","timestamp":1767020707545,"user_tz":-330,"elapsed":57,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"1718cbba-cbb6-4228-9f36-d40d47306e43"},"outputs":[{"output_type":"error","ename":"FileNotFoundError","evalue":"[Errno 2] No such file or directory: 'config.json'","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-3216342540.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Load the JSON file and extract values\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mfile_name\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'config.json'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'r'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mfile\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mconfig\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mjson\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menviron\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'OPENAI_API_KEY'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"API_KEY\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Loading the API Key\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'config.json'"]}],"source":["# Load the JSON file and extract values\n","file_name = 'config.json'\n","with open(file_name, 'r') as file:\n"," config = json.load(file)\n"," os.environ['OPENAI_API_KEY'] = config.get(\"API_KEY\") # Loading the API Key\n"," os.environ[\"OPENAI_API_BASE\"] = config.get(\"OPENAI_API_BASE\") # Loading the API Base Url\n"," os.environ[\"OPENAI_API_TYPE\"] = \"openai\""]},{"cell_type":"code","execution_count":1,"metadata":{"id":"GDnjEPzDIhkD","colab":{"base_uri":"https://localhost:8080/","height":211},"executionInfo":{"status":"error","timestamp":1768477501540,"user_tz":-330,"elapsed":98,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"f5340ca7-8258-4a63-8688-d43890a2b0d4"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'TypedDict' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-2412177559.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;31m# STATE DEFINITION\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# ============================================================================\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTypedDict\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0;34m\"\"\"State shared across all agents\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmessages\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mAnnotated\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mSequence\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mBaseMessage\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moperator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'TypedDict' is not defined"]}],"source":["\n","# ============================================================================\n","# STATE DEFINITION\n","# ============================================================================\n","class ChatBotState(TypedDict):\n"," \"\"\"State shared across all agents\"\"\"\n"," messages: Annotated[Sequence[BaseMessage], operator.add]\n"," customer_query: str\n"," customer_id: str | None\n"," order_list: list[str] | None\n"," order_id: str | None\n"," order_data: dict | None\n"," product_data: dict | None\n"," quality_check_result: dict | None\n"," replacement_result: dict | None\n"," next_agent: str\n"," final_response: str | None\n"," executed_nodes: Annotated[list[str], operator.add] # New field to track executed nodes\n","\n","\n","\n","# ============================================================================\n","# SQL DATABASE SERVICES\n","# ============================================================================\n","class SQLOrderService:\n"," \"\"\"Service for querying orders from SQLite database\"\"\"\n","\n"," def __init__(self, db_path='orders.db'):\n"," self.db_path = db_path\n"," self.db = SQLDatabase.from_uri(f\"sqlite:///{db_path}\")\n"," self.llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n","\n"," def get_order(self, order_id: str) -> dict | None:\n"," \"\"\"Get order details using SQL query\"\"\"\n"," try:\n"," conn = sqlite3.connect(self.db_path)\n"," conn.row_factory = sqlite3.Row\n"," cursor = conn.cursor()\n","\n"," # Query order with customer info\n"," cursor.execute('''\n"," SELECT\n"," o.order_id,\n"," o.order_date,\n"," o.status,\n"," o.delivery_date,\n"," o.total_amount,\n"," o.shipping_address,\n"," o.payment_method,\n"," c.name as customer_name,\n"," c.email as customer_email\n"," FROM orders o\n"," JOIN customers c ON o.customer_id = c.customer_id\n"," WHERE o.order_id = ?\n"," ''', (order_id,))\n","\n"," order_row = cursor.fetchone()\n","\n"," if not order_row:\n"," conn.close()\n"," return None\n","\n"," # Query order items\n"," cursor.execute('''\n"," SELECT\n"," oi.product_id,\n"," p.name,\n"," oi.quantity,\n"," oi.price_at_purchase\n"," FROM order_items oi\n"," JOIN products p ON oi.product_id = p.product_id\n"," WHERE oi.order_id = ?\n"," ''', (order_id,))\n","\n"," items_rows = cursor.fetchall()\n","\n"," # Build order dictionary\n"," order_data = {\n"," \"order_id\": order_row[\"order_id\"],\n"," \"customer_name\": order_row[\"customer_name\"],\n"," \"customer_email\": order_row[\"customer_email\"],\n"," \"order_date\": order_row[\"order_date\"],\n"," \"status\": order_row[\"status\"],\n"," \"delivery_date\": order_row[\"delivery_date\"],\n"," \"total\": order_row[\"total_amount\"],\n"," \"shipping_address\": order_row[\"shipping_address\"],\n"," \"payment_method\": order_row[\"payment_method\"],\n"," \"items\": [\n"," {\n"," \"product_id\": row[\"product_id\"],\n"," \"name\": row[\"name\"],\n"," \"quantity\": row[\"quantity\"],\n"," \"price\": row[\"price_at_purchase\"]\n"," }\n"," for row in items_rows\n"," ]\n"," }\n","\n"," conn.close()\n"," return order_data\n","\n"," except Exception as e:\n"," print(f\"Error querying order: {str(e)}\")\n"," return None\n","\n"," def search_orders_by_customer(self, customer_name: str) -> list:\n"," \"\"\"Search orders by customer name\"\"\"\n"," try:\n"," conn = sqlite3.connect(self.db_path)\n"," conn.row_factory = sqlite3.Row\n"," cursor = conn.cursor()\n","\n"," cursor.execute('''\n"," SELECT o.order_id, o.status, o.order_date, o.total_amount\n"," FROM orders o\n"," JOIN customers c ON o.customer_id = c.customer_id\n"," WHERE c.name LIKE ?\n"," ORDER BY o.order_date DESC\n"," ''', (f'%{customer_name}%',))\n","\n"," orders = [dict(row) for row in cursor.fetchall()]\n"," conn.close()\n"," return orders\n","\n"," except Exception as e:\n"," print(f\"Error searching orders: {str(e)}\")\n"," return []\n","\n","\n","class SQLProductService:\n"," \"\"\"Service for querying products from SQLite database\"\"\"\n","\n"," def __init__(self, db_path='orders.db'):\n"," self.db_path = db_path\n","\n"," def get_product(self, product_id: str) -> dict | None:\n"," \"\"\"Get product details using SQL query\"\"\"\n"," try:\n"," conn = sqlite3.connect(self.db_path)\n"," conn.row_factory = sqlite3.Row\n"," cursor = conn.cursor()\n","\n"," cursor.execute('''\n"," SELECT\n"," product_id,\n"," name,\n"," description,\n"," price,\n"," warranty_period,\n"," return_policy,\n"," battery_life,\n"," connectivity,\n"," weight,\n"," water_resistance,\n"," display,\n"," category,\n"," stock_quantity\n"," FROM products\n"," WHERE product_id = ?\n"," ''', (product_id,))\n","\n"," row = cursor.fetchone()\n"," conn.close()\n","\n"," if not row:\n"," return None\n","\n"," # Build specifications dict\n"," specs = {}\n"," if row[\"battery_life\"]:\n"," specs[\"battery_life\"] = row[\"battery_life\"]\n"," if row[\"connectivity\"]:\n"," specs[\"connectivity\"] = row[\"connectivity\"]\n"," if row[\"weight\"]:\n"," specs[\"weight\"] = row[\"weight\"]\n"," if row[\"water_resistance\"]:\n"," specs[\"water_resistance\"] = row[\"water_resistance\"]\n"," if row[\"display\"]:\n"," specs[\"display\"] = row[\"display\"]\n","\n"," return {\n"," \"product_id\": row[\"product_id\"],\n"," \"name\": row[\"name\"],\n"," \"description\": row[\"description\"],\n"," \"price\": row[\"price\"],\n"," \"warranty\": row[\"warranty_period\"],\n"," \"return_policy\": row[\"return_policy\"],\n"," \"category\": row[\"category\"],\n"," \"stock_quantity\": row[\"stock_quantity\"],\n"," \"specifications\": specs\n"," }\n","\n"," except Exception as e:\n"," print(f\"Error querying product: {str(e)}\")\n"," return None\n","\n","\n","class ReplacementService:\n"," \"\"\"Service for creating replacement orders\"\"\"\n","\n"," def __init__(self, db_path='orders.db'):\n"," self.db_path = db_path\n","\n"," def create_replacement(self, order_id: str, reason: str) -> dict:\n"," \"\"\"Create a replacement order in the database\"\"\"\n"," replacement_id = f\"REP{order_id[3:]}\"\n","\n"," try:\n"," conn = sqlite3.connect(self.db_path)\n"," cursor = conn.cursor()\n","\n"," # In a real system, you would:\n"," # 1. Create a new order record\n"," # 2. Link it to the original order\n"," # 3. Update inventory\n"," # 4. Send notifications\n","\n"," # For now, we'll just return the replacement info\n"," conn.close()\n","\n"," return {\n"," \"replacement_id\": replacement_id,\n"," \"original_order_id\": order_id,\n"," \"status\": \"Initiated\",\n"," \"estimated_delivery\": (datetime.now() + timedelta(days=5)).strftime(\"%Y-%m-%d\"),\n"," \"tracking_number\": f\"TRK{replacement_id}\",\n"," \"reason\": reason\n"," }\n","\n"," except Exception as e:\n"," print(f\"Error creating replacement: {str(e)}\")\n"," return None\n"]},{"cell_type":"code","execution_count":2,"metadata":{"id":"aaQR1tFWIAAb","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477502228,"user_tz":-330,"elapsed":80,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"acac21c0-1602-4313-f3ac-2a1ff91aa267"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-670775971.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mOrchestratorAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"LLM-driven orchestrator for Kartify support — handles customer ID + order ID.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mllm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-670775971.py\u001b[0m in \u001b[0;36mOrchestratorAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 44\u001b[0m ])\n\u001b[1;32m 45\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 46\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 47\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"orchestrator\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 48\u001b[0m \u001b[0mquery\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"customer_query\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstrip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class OrchestratorAgent:\n"," \"\"\"LLM-driven orchestrator for Kartify support — handles customer ID + order ID.\"\"\"\n","\n"," def __init__(self, llm):\n"," self.llm = llm\n"," self.prompt = ChatPromptTemplate.from_messages([\n"," (\n"," \"system\",\n"," \"\"\"You are the orchestrator for Kartify's support workflow.\n"," Your job is to route queries to the correct agent.\n","\n"," Agents:\n"," - customer_order_lookup → Use when user provides or asks about customer ID\n"," - order_retrieval → Fetch SINGLE order info (only after customer chose order)\n"," - product_info → Fetch product details\n"," - quality_check → Check damage/replacement eligibility\n"," - replacement_processing → Create a replacement order\n"," - response_generation → Final response to customer\n","\n"," Rules:\n"," - If message contains a customer ID (like \"my customer id is 5\", \"id 3\", \"customer 10\")\n"," → ALWAYS route to customer_order_lookup.\n"," - Never call order_retrieval if order_data already exists.\n"," - If order_data is present and the user's message is simple or not task-specific → response_generation.\n"," - If only user confirms a replacement → replacement_processing.\n"," - If unsure → response_generation.\n","\n"," Output ONLY the agent name.\n"," \"\"\"\n"," ),\n"," (\n"," \"human\",\n"," \"\"\"Customer Query:\n"," {query}\n","\n"," Context:\n"," - Has order data: {has_order}\n"," - Has product data: {has_product}\n"," - Has quality: {has_quality}\n"," - Has replacement: {has_replacement}\n","\n"," Next agent:\"\"\"\n"," )\n"," ])\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"orchestrator\") # Track execution\n"," query = state.get(\"customer_query\", \"\").strip().lower()\n"," has_order = state.get(\"order_data\") is not None\n"," has_product = state.get(\"product_data\") is not None\n"," has_quality = state.get(\"quality_check_result\") is not None\n"," has_replacement = state.get(\"replacement_result\") is not None\n","\n"," # -----------------------------------------------------\n"," # 1. Detect CUSTOMER ID\n"," # -----------------------------------------------------\n"," if any(kw in query for kw in [\"customer id\", \"customerid\", \"customer\", \"cid\", \"cust id\"]):\n"," digits = \"\".join(ch for ch in query if ch.isdigit())\n"," if digits:\n"," state[\"customer_id\"] = digits\n"," state[\"order_data\"] = None\n"," state[\"order_list\"] = None\n"," state[\"order_id\"] = None\n"," state[\"next_agent\"] = \"customer_order_lookup\"\n"," return state\n","\n"," # -----------------------------------------------------\n"," # 2. Detect ORDER ID selection (like \"OR3001\")\n"," # -----------------------------------------------------\n"," if query.startswith(\"or\") and any(ch.isdigit() for ch in query):\n"," state[\"order_id\"] = query.upper()\n"," state[\"next_agent\"] = \"order_retrieval\"\n"," return state\n","\n"," # -----------------------------------------------------\n"," # 3. Confirmations\n"," # -----------------------------------------------------\n"," confirm_words = [\"yes\", \"sure\", \"okay\", \"ok\", \"go ahead\", \"do it\", \"proceed\"]\n"," if any(word in query for word in confirm_words):\n"," state[\"next_agent\"] = \"replacement_processing\"\n"," return state\n","\n"," # -----------------------------------------------------\n"," # 4. Call LLM for everything else\n"," # -----------------------------------------------------\n"," chain = self.prompt | self.llm\n","\n"," try:\n"," response = chain.invoke({\n"," \"query\": query,\n"," \"has_order\": has_order,\n"," \"has_product\": has_product,\n"," \"has_quality\": has_quality,\n"," \"has_replacement\": has_replacement\n"," })\n"," next_agent = response.content.strip().lower().replace(\" \", \"_\")\n"," except Exception:\n"," next_agent = \"response_generation\"\n","\n"," # -----------------------------------------------------\n"," # 5. ALLOW customer_order_lookup in the list!\n"," # -----------------------------------------------------\n"," allowed_agents = [\n"," \"customer_order_lookup\", # <-- fix\n"," \"order_retrieval\",\n"," \"product_info\",\n"," \"quality_check\",\n"," \"replacement_processing\",\n"," \"response_generation\"\n"," ]\n"," if next_agent not in allowed_agents:\n"," next_agent = \"response_generation\"\n","\n"," state[\"next_agent\"] = next_agent\n"," return state\n"]},{"cell_type":"code","execution_count":3,"metadata":{"id":"NZeKX7vfIAAn","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477502866,"user_tz":-330,"elapsed":56,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"301ef9f2-8155-40a2-8488-7adf87bf07f8"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-516332947.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mOrderRetrievalAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Retrieves order information from SQL database\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mllm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-516332947.py\u001b[0m in \u001b[0;36mOrderRetrievalAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 13\u001b[0m ])\n\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 15\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 16\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"order_retrieval\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0mchain\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprompt\u001b[0m \u001b[0;34m|\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mllm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class OrderRetrievalAgent:\n"," \"\"\"Retrieves order information from SQL database\"\"\"\n","\n"," def __init__(self, llm):\n"," self.llm = llm\n"," self.order_service = SQLOrderService()\n"," self.prompt = ChatPromptTemplate.from_messages([\n"," (\"system\", \"\"\"Extract the order ID from the customer query.\n"," Look for patterns like ORD followed by numbers (e.g., ORD12345).\n"," Return ONLY the order ID in format ORDXXXXX or the word 'NOT_FOUND' if no order ID is present.\n"," Do not include any other text.\"\"\"),\n"," (\"human\", \"{query}\")\n"," ])\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"order_retrieval\") # Track execution\n"," chain = self.prompt | self.llm\n"," response = chain.invoke({\"query\": state[\"customer_query\"]})\n"," order_id = response.content.strip()\n","\n"," if order_id != \"NOT_FOUND\" and \"ORD\" in order_id.upper():\n"," order_data = self.order_service.get_order(order_id.upper())\n"," state[\"order_id\"] = order_id.upper()\n"," state[\"order_data\"] = order_data\n","\n"," state[\"next_agent\"] = \"orchestrator\"\n"," return state\n"]},{"cell_type":"code","execution_count":4,"metadata":{"id":"EBGSts9uIAAn","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477503535,"user_tz":-330,"elapsed":68,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"b9cd00f4-d39b-4d70-f9a9-0187f6e57ed6"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-3046507751.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mProductInfoAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Retrieves product information from SQL database\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproduct_service\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mSQLProductService\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-3046507751.py\u001b[0m in \u001b[0;36mProductInfoAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproduct_service\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mSQLProductService\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"product_info\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"order_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class ProductInfoAgent:\n"," \"\"\"Retrieves product information from SQL database\"\"\"\n","\n"," def __init__(self):\n"," self.product_service = SQLProductService()\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"product_info\") # Track execution\n"," if state.get(\"order_data\"):\n"," items = state[\"order_data\"].get(\"items\", [])\n"," product_data = []\n","\n"," for item in items:\n"," product_id = item.get(\"product_id\")\n"," if product_id:\n"," product = self.product_service.get_product(product_id)\n"," if product:\n"," product_data.append(product)\n","\n"," state[\"product_data\"] = product_data\n","\n"," state[\"next_agent\"] = \"orchestrator\"\n"," return state\n"]},{"cell_type":"code","execution_count":5,"metadata":{"id":"eMm5vXn_IAAo","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477504213,"user_tz":-330,"elapsed":76,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"386b08e8-adf1-4942-ab59-d8a3277818a7"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-401127879.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mQualityCheckAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Checks if order qualifies for replacement\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mllm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-401127879.py\u001b[0m in \u001b[0;36mQualityCheckAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 11\u001b[0m ])\n\u001b[1;32m 12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"quality_check\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"order_data\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class QualityCheckAgent:\n"," \"\"\"Checks if order qualifies for replacement\"\"\"\n","\n"," def __init__(self, llm):\n"," self.llm = llm\n"," self.prompt = ChatPromptTemplate.from_messages([\n"," (\"system\", \"\"\"Analyze if the customer query mentions any product issues.\n"," Look for keywords like: damaged, defective, broken, wrong item, not working, faulty, quality issues.\n"," Respond with only 'YES' if issues are mentioned, or 'NO' if not.\"\"\"),\n"," (\"human\", \"{query}\")\n"," ])\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"quality_check\") # Track execution\n"," if not state.get(\"order_data\"):\n"," state[\"quality_check_result\"] = {\n"," \"eligible\": False,\n"," \"reason\": \"No order data available\",\n"," \"issues\": []\n"," }\n"," else:\n"," order_data = state[\"order_data\"]\n"," is_delivered = order_data.get(\"status\") == \"Delivered\"\n","\n"," delivery_date_str = order_data.get(\"delivery_date\")\n"," within_window = False\n"," if delivery_date_str:\n"," delivery_date = datetime.strptime(delivery_date_str, \"%Y-%m-%d\")\n"," days_since_delivery = (datetime.now() - delivery_date).days\n"," within_window = days_since_delivery <= 30\n","\n"," chain = self.prompt | self.llm\n"," response = chain.invoke({\"query\": state[\"customer_query\"]})\n"," has_valid_reason = response.content.strip().upper() == \"YES\"\n","\n"," eligible = is_delivered and within_window and has_valid_reason\n","\n"," issues = []\n"," if has_valid_reason:\n"," query_lower = state[\"customer_query\"].lower()\n"," if \"damaged\" in query_lower or \"damage\" in query_lower:\n"," issues.append(\"Product damaged\")\n"," if \"defective\" in query_lower or \"broken\" in query_lower:\n"," issues.append(\"Product defective\")\n"," if \"wrong\" in query_lower:\n"," issues.append(\"Wrong item received\")\n"," if not issues:\n"," issues.append(\"Quality issue reported\")\n","\n"," state[\"quality_check_result\"] = {\n"," \"eligible\": eligible,\n"," \"reason\": f\"Delivered: {is_delivered}, Within window: {within_window}, Valid reason: {has_valid_reason}\",\n"," \"issues\": issues,\n"," \"days_since_delivery\": (datetime.now() - delivery_date).days if delivery_date_str else None\n"," }\n","\n"," state[\"next_agent\"] = \"orchestrator\"\n"," return state\n"]},{"cell_type":"code","execution_count":6,"metadata":{"id":"Sq__aRonIAAo","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477504908,"user_tz":-330,"elapsed":77,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"8769c338-a113-4761-d2d1-d99bb070aefc"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-19418754.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mReplacementProcessingAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Processes replacement or reorder requests safely\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreplacement_service\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mReplacementService\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-19418754.py\u001b[0m in \u001b[0;36mReplacementProcessingAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreplacement_service\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mReplacementService\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"replacement_processing\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;31m# --- Retrieve current state data safely ---\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class ReplacementProcessingAgent:\n"," \"\"\"Processes replacement or reorder requests safely\"\"\"\n","\n"," def __init__(self):\n"," self.replacement_service = ReplacementService()\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"replacement_processing\") # Track execution\n"," # --- Retrieve current state data safely ---\n"," quality_check = state.get(\"quality_check_result\") or {}\n"," order_data = state.get(\"order_data\")\n"," order_id = state.get(\"order_id\")\n","\n"," # --- Guard: ensure order details exist ---\n"," if not order_data or not order_id:\n"," state[\"final_response\"] = (\n"," \"I couldn’t find your order details. Could you please provide your order ID so I can help with the replacement?\"\n"," )\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n","\n"," # --- Determine reason and eligibility ---\n"," eligible = isinstance(quality_check, dict) and quality_check.get(\"eligible\", False)\n"," reason = \", \".join(quality_check.get(\"issues\", [])) if quality_check else \"Customer requested reorder\"\n","\n"," # --- Create replacement or reorder record ---\n"," if eligible or \"reorder\" in state.get(\"customer_query\", \"\").lower() or \"assist\" in state.get(\"customer_query\", \"\").lower():\n"," replacement_result = self.replacement_service.create_replacement(order_id, reason)\n"," state[\"replacement_result\"] = replacement_result\n"," else:\n"," # Not eligible — fallback message\n"," state[\"replacement_result\"] = {\n"," \"replacement_id\": None,\n"," \"status\": \"Not Eligible\",\n"," \"reason\": reason\n"," }\n"," state[\"final_response\"] = (\n"," \"It looks like this order may not be eligible for a replacement. \"\n"," \"Could you please confirm if you'd like to reorder these items instead?\"\n"," )\n","\n"," # --- Always proceed to response generation ---\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n"]},{"cell_type":"code","execution_count":7,"metadata":{"id":"AqGZ-y4jIAAo","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477505622,"user_tz":-330,"elapsed":97,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"2d5cca74-b6dc-40c3-8e2b-9adcee4ce33e"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-227464530.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mResponseGenerationAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \"\"\"Generates the final customer-facing response for Kartify support.\n\u001b[1;32m 3\u001b[0m \u001b[0mAlways\u001b[0m \u001b[0mproduces\u001b[0m \u001b[0ma\u001b[0m \u001b[0mhelpful\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mempathetic\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mcomplete\u001b[0m \u001b[0manswer\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m–\u001b[0m \u001b[0meven\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mminimal\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \"\"\"\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-227464530.py\u001b[0m in \u001b[0;36mResponseGenerationAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 37\u001b[0m ])\n\u001b[1;32m 38\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 39\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 40\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"response_generation\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;34m\"\"\"Generate a friendly, fallback-safe customer response.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class ResponseGenerationAgent:\n"," \"\"\"Generates the final customer-facing response for Kartify support.\n"," Always produces a helpful, empathetic, and complete answer – even with minimal data.\n"," \"\"\"\n","\n"," def __init__(self, llm):\n"," self.llm = llm\n"," self.prompt = ChatPromptTemplate.from_messages([\n"," (\n"," \"system\",\n"," \"\"\"You are a warm, professional customer support representative for Kartify.\n"," Your goal is to provide a clear, empathetic, and helpful response based on all available context.\n","\n"," Guidelines:\n"," - Be natural, polite, and solution-oriented.\n"," - Always acknowledge the customer's concern.\n"," - If information is incomplete or unclear, politely ask for clarification.\n"," - If the query is vague (e.g., greetings or thanks), respond appropriately and keep it brief.\n"," - If the issue is resolved (replacement or confirmation given), close the conversation positively.\n"," - NEVER say you are an AI – respond as a human Kartify support agent.\n"," - Always end on a reassuring, customer-friendly note, but DO NOT include a personal name or signature line.\n","\n"," Information you can use:\n"," - Customer Query: {query}\n"," - Order Data: {order_data}\n"," - Product Data: {product_data}\n"," - Quality Check Result: {quality_check}\n"," - Replacement Result: {replacement_result}\n"," - Order List: {order_list}\n"," \"\"\"\n"," ),\n"," (\n"," \"human\",\n"," \"\"\"Based on the above context, write a clear, kind, and helpful response for the customer.\n"," The message should sound natural, empathetic, and directly address their concern.\"\"\"\n"," )\n"," ])\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"response_generation\") # Track execution\n"," \"\"\"Generate a friendly, fallback-safe customer response.\"\"\"\n","\n"," # Check if we already have a pre-formatted response (e.g., from CustomerOrderListAgent)\n"," if state.get(\"final_response\"):\n"," # Already has a response, just return it\n"," return state\n","\n"," query = state.get(\"customer_query\", \"\").strip()\n"," order_data = str(state.get(\"order_data\", \"No order data\"))\n"," product_data = str(state.get(\"product_data\", \"No product data\"))\n"," quality_check = str(state.get(\"quality_check_result\", \"No quality check performed\"))\n"," replacement_result = str(state.get(\"replacement_result\", \"No replacement created\"))\n"," order_list = state.get(\"order_list\", [])\n","\n"," # 🛡️ Handle vague or empty queries before calling LLM\n"," vague_terms = [\"hi\", \"hello\", \"hey\", \"thanks\", \"thank you\", \"ok\", \"okay\"]\n"," if not query or query.lower().strip() in vague_terms:\n"," response_text = \"Hi there! 😊 How can I assist you with your Kartify order today?\"\n"," else:\n"," try:\n"," chain = self.prompt | self.llm\n"," response = chain.invoke({\n"," \"query\": query,\n"," \"order_data\": order_data,\n"," \"product_data\": product_data,\n"," \"quality_check\": quality_check,\n"," \"replacement_result\": replacement_result,\n"," \"order_list\": order_list if order_list else \"No order list\"\n"," })\n"," response_text = response.content.strip()\n"," except Exception as e:\n"," # 🧩 Safe fallback if LLM call fails\n"," response_text = (\n"," \"I'm sorry, something went wrong while preparing your response. \"\n"," \"Could you please rephrase or provide a bit more detail about your concern?\"\n"," )\n","\n"," # 🧠 Ensure a final response always exists\n"," if not response_text or response_text.strip() == \"\":\n"," response_text = \"I'm here to help with your Kartify order. Could you please clarify your request?\"\n","\n"," # 💬 Save the final message and signal end of flow\n"," state[\"final_response\"] = response_text\n"," state[\"next_agent\"] = None # 'None' or 'end' → terminate the graph safely\n","\n"," return state\n"]},{"cell_type":"code","execution_count":8,"metadata":{"id":"9RKLeoEvBQ2L","colab":{"base_uri":"https://localhost:8080/","height":332},"executionInfo":{"status":"error","timestamp":1768477506359,"user_tz":-330,"elapsed":103,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}},"outputId":"5080ab90-cbd0-4168-ffa8-1d0fbfb226a5"},"outputs":[{"output_type":"error","ename":"NameError","evalue":"name 'ChatBotState' is not defined","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-2884538547.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mCustomerOrderListAgent\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Retrieves all order IDs for a given customer ID.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mllm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mllm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipython-input-2884538547.py\u001b[0m in \u001b[0;36mCustomerOrderListAgent\u001b[0;34m()\u001b[0m\n\u001b[1;32m 22\u001b[0m ])\n\u001b[1;32m 23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 24\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mChatBotState\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 25\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"executed_nodes\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"customer_order_lookup\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Track execution\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0mquery\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"customer_query\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mNameError\u001b[0m: name 'ChatBotState' is not defined"]}],"source":["class CustomerOrderListAgent:\n"," \"\"\"Retrieves all order IDs for a given customer ID.\"\"\"\n","\n"," def __init__(self, llm):\n"," self.llm = llm\n"," self.order_service = SQLOrderService()\n","\n"," self.prompt = ChatPromptTemplate.from_messages([\n"," (\n"," \"system\",\n"," \"\"\"Extract the CUSTOMER ID from the message.\n"," Customer IDs may look like:\n"," - CUST123\n"," - C123\n"," - 123\n"," - CID555\n"," ALWAYS return ONLY the ID you detect.\n"," If none found, return NOT_FOUND.\n"," No explanations.\"\"\"\n"," ),\n"," (\"human\", \"{query}\")\n"," ])\n","\n"," def process(self, state: ChatBotState) -> ChatBotState:\n"," state[\"executed_nodes\"].append(\"customer_order_lookup\") # Track execution\n"," query = state.get(\"customer_query\", \"\")\n","\n"," # Extract customer ID using LLM\n"," chain = self.prompt | self.llm\n"," response = chain.invoke({\"query\": query})\n"," detected_customer_id = response.content.strip()\n","\n"," if detected_customer_id == \"NOT_FOUND\":\n"," state[\"final_response\"] = (\n"," \"I couldn't detect a valid customer ID. Could you please provide your customer ID?\"\n"," )\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n","\n"," # Save customer ID\n"," state[\"customer_id\"] = detected_customer_id\n","\n"," # Query SQLite directly\n"," try:\n"," conn = sqlite3.connect(self.order_service.db_path)\n"," cursor = conn.cursor()\n","\n"," cursor.execute(\"\"\"\n"," SELECT order_id\n"," FROM orders\n"," WHERE customer_id = ?\n"," \"\"\", (detected_customer_id,))\n","\n"," order_rows = cursor.fetchall()\n"," conn.close()\n","\n"," order_list = [row[0] for row in order_rows]\n","\n"," if not order_list:\n"," state[\"final_response\"] = (\n"," f\"No orders were found for customer ID {detected_customer_id}. \"\n"," \"Please check the ID and try again.\"\n"," )\n"," state[\"order_list\"] = []\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n","\n"," # ✅ FIX: Store order list and prepare response\n"," state[\"order_list\"] = order_list\n","\n"," # ✅ FIX: Create a formatted response showing the orders with clear selection prompt\n"," if len(order_list) == 1:\n"," # Only one order - still ask for confirmation\n"," state[\"final_response\"] = (\n"," f\"I found 1 order for customer ID {detected_customer_id}:\\n\\n\"\n"," f\" 📦 {order_list[0]}\\n\\n\"\n"," f\"Would you like to inquire about order {order_list[0]}? \"\n"," f\"Please type the order ID to continue.\"\n"," )\n"," else:\n"," # Multiple orders - numbered list for easy selection\n"," order_list_str = \"\\n\".join([f\" {i+1}. 📦 {order_id}\" for i, order_id in enumerate(order_list)])\n"," state[\"final_response\"] = (\n"," f\"I found {len(order_list)} orders for customer ID {detected_customer_id}:\\n\\n\"\n"," f\"{order_list_str}\\n\\n\"\n"," f\"Please select an order by typing the order ID (e.g., {order_list[0]}) \"\n"," f\"to view its details or ask your question about it.\"\n"," )\n","\n"," # ✅ FIX: Go directly to response_generation instead of orchestrator\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n","\n"," except Exception as e:\n"," state[\"final_response\"] = (\n"," f\"Something went wrong while retrieving orders for {detected_customer_id}. \"\n"," f\"Error: {str(e)}\"\n"," )\n"," state[\"next_agent\"] = \"response_generation\"\n"," return state\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"8NEvvpNlDJI2"},"outputs":[],"source":["from langgraph.graph import StateGraph, END\n","from langchain_openai import ChatOpenAI\n","from langchain_core.prompts import ChatPromptTemplate\n","from langchain_core.messages import BaseMessage\n","from langchain_community.utilities import SQLDatabase\n","from typing import Annotated, Sequence, TypedDict\n","import sqlite3, operator\n","from datetime import datetime, timedelta\n","\n","\n","# ================================================================\n","# Initialize LLM and agents\n","# ================================================================\n","llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n","\n","orchestrator = OrchestratorAgent(llm)\n","customer_order_list_agent = CustomerOrderListAgent(llm) # ✅ NEW\n","order_agent = OrderRetrievalAgent(llm)\n","product_agent = ProductInfoAgent()\n","quality_agent = QualityCheckAgent(llm)\n","replacement_agent = ReplacementProcessingAgent()\n","response_agent = ResponseGenerationAgent(llm)\n","\n","\n","# ================================================================\n","# Define node functions\n","# ================================================================\n","def orchestrator_node(state: ChatBotState):\n"," return orchestrator.process(state)\n","\n","def customer_order_list_node(state: ChatBotState):\n"," return customer_order_list_agent.process(state)\n","\n","def order_node(state: ChatBotState):\n"," return order_agent.process(state)\n","\n","def product_node(state: ChatBotState):\n"," return product_agent.process(state)\n","\n","def quality_node(state: ChatBotState):\n"," return quality_agent.process(state)\n","\n","def replacement_node(state: ChatBotState):\n"," return replacement_agent.process(state)\n","\n","def response_node(state: ChatBotState):\n"," return response_agent.process(state)\n","\n","\n","# ================================================================\n","# Build the LangGraph workflow\n","# ================================================================\n","workflow = StateGraph(ChatBotState)\n","\n","# -------- Nodes --------\n","workflow.add_node(\"orchestrator\", orchestrator_node)\n","workflow.add_node(\"customer_order_lookup\", customer_order_list_node)\n","workflow.add_node(\"order_retrieval\", order_node)\n","workflow.add_node(\"product_info\", product_node)\n","workflow.add_node(\"quality_check\", quality_node)\n","workflow.add_node(\"replacement_processing\", replacement_node)\n","workflow.add_node(\"response_generation\", response_node)\n","\n","\n","# -------- Static Edges --------\n","workflow.add_edge(\"customer_order_lookup\", \"response_generation\")\n","workflow.add_edge(\"order_retrieval\", \"response_generation\")\n","workflow.add_edge(\"product_info\", \"response_generation\")\n","workflow.add_edge(\"quality_check\", \"response_generation\")\n","workflow.add_edge(\"replacement_processing\", \"response_generation\")\n","workflow.add_edge(\"response_generation\", END)\n","\n","\n","# -------- Dynamic Routing --------\n","def route_next(state: ChatBotState):\n"," return state[\"next_agent\"]\n","\n","workflow.add_conditional_edges(\n"," \"orchestrator\",\n"," route_next,\n"," {\n"," \"customer_order_lookup\": \"customer_order_lookup\", # ✅ NEW\n"," \"order_retrieval\": \"order_retrieval\",\n"," \"product_info\": \"product_info\",\n"," \"quality_check\": \"quality_check\",\n"," \"replacement_processing\": \"replacement_processing\",\n"," \"response_generation\": \"response_generation\",\n"," \"end\": END\n"," }\n",")\n","\n","\n","# -------- Entry Point --------\n","workflow.set_entry_point(\"orchestrator\")\n","\n","# -------- Compile Graph --------\n","graph = workflow.compile()\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"eR4D1AlFHsyf","outputId":"2ebb880a-16dd-42db-ad8e-c9ae40d81c9f"},"outputs":[{"data":{"image/png":"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","text/plain":[""]},"metadata":{},"output_type":"display_data"}],"source":["from IPython.display import Image, display\n","\n","# Render the workflow diagram for your compiled LangGraph\n","try:\n"," display(Image(graph.get_graph().draw_mermaid_png()))\n","except Exception as e:\n"," print(\"Diagram rendering failed:\", e)\n"]},{"cell_type":"markdown","source":["# **Agentic AI Chatbot in Action**"],"metadata":{"id":"gs-OSLYzJzzJ"}},{"cell_type":"code","execution_count":9,"metadata":{"id":"nidrPy3x3ftl","outputId":"f9f27831-87eb-4ed2-b2bf-18464a245d12","colab":{"base_uri":"https://localhost:8080/","height":383},"executionInfo":{"status":"error","timestamp":1768477511815,"user_tz":-330,"elapsed":4833,"user":{"displayName":"Bindhu Balasubramanian","userId":"11381459626823880669"}}},"outputs":[{"output_type":"error","ename":"ModuleNotFoundError","evalue":"No module named 'langchain_openai'","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipython-input-510867712.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlanggraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merrors\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mGraphRecursionError\u001b[0m \u001b[0;31m# ✅ Added this import\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlangchain_core\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmessages\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mBaseMessage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mHumanMessage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mAIMessage\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlangchain_openai\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mChatOpenAI\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlangchain_core\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprompts\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mChatPromptTemplate\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlangchain_community\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mutilities\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSQLDatabase\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'langchain_openai'","","\u001b[0;31m---------------------------------------------------------------------------\u001b[0;32m\nNOTE: If your import is failing due to a missing package, you can\nmanually install dependencies using either !pip or !apt.\n\nTo view examples of installing some common dependencies, click the\n\"Open Examples\" button below.\n\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n"],"errorDetails":{"actions":[{"action":"open_url","actionText":"Open Examples","url":"/notebooks/snippets/importing_libraries.ipynb"}]}}],"source":["# ============================================================\n","# COMPLETE IMPORTS\n","# ============================================================\n","from typing import TypedDict, Annotated, Sequence, Literal\n","from langgraph.graph import StateGraph, END\n","from langgraph.errors import GraphRecursionError # ✅ Added this import\n","from langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n","from langchain_openai import ChatOpenAI\n","from langchain_core.prompts import ChatPromptTemplate\n","from langchain_community.utilities import SQLDatabase\n","from langchain_community.agent_toolkits import create_sql_agent\n","import operator\n","from datetime import datetime, timedelta\n","import json\n","import os\n","import sqlite3\n","\n","# ============================================================\n","# ENHANCED INTERACTIVE CHATBOT LOOP\n","# ============================================================\n","\n","# Initialize chatbot state\n","chatbot_state: ChatBotState = {\n"," \"messages\": [],\n"," \"customer_query\": None,\n"," \"customer_id\": None,\n"," \"order_id\": None,\n"," \"order_list\": None,\n"," \"order_data\": None,\n"," \"product_data\": None,\n"," \"quality_check_result\": None,\n"," \"replacement_result\": None,\n"," \"next_agent\": \"orchestrator\",\n"," \"final_response\": None,\n"," \"executed_nodes\": [] # Initialize new field\n","}\n","\n","print(\"=\" * 70)\n","print(\"🤖 WELCOME TO KARTIFY SUPPORT CHATBOT\")\n","print(\"=\" * 70)\n","print(\"💡 Tips:\")\n","print(\" • Start by providing your customer ID (e.g., 'My customer id is 5')\")\n","print(\" • Then select an order ID from the list\")\n","print(\" • Ask questions about your order\")\n","print(\" • Type 'quit', 'exit', or 'bye' to end the conversation\")\n","print(\"=\" * 70)\n","\n","conversation_turn = 0\n","\n","while True:\n"," conversation_turn += 1\n","\n","\n","\n"," customer_query = input(\"\\n🗣️ You: \").strip()\n","\n"," # Exit condition\n"," if customer_query.lower() in [\"quit\", \"exit\", \"bye\", \"goodbye\"]:\n"," print(\"\\n👋 Chatbot: Thank you for contacting Kartify Support! Have a great day!\")\n"," break\n","\n"," if not customer_query:\n"," print(\"\\n⚠️ Please enter a message.\")\n"," continue\n","\n"," # --------------------------------------------------------\n"," # 2. Update state and invoke graph\n"," # --------------------------------------------------------\n"," chatbot_state[\"customer_query\"] = customer_query\n"," chatbot_state[\"next_agent\"] = \"orchestrator\"\n"," chatbot_state[\"final_response\"] = None # Reset for new turn\n"," chatbot_state[\"executed_nodes\"] = [] # Reset executed nodes for new turn\n","\n"," try:\n"," # Invoke the graph\n"," chatbot_state = graph.invoke(chatbot_state)\n","\n"," # --------------------------------------------------------\n"," # 3. Display response\n"," # --------------------------------------------------------\n"," bot_reply = chatbot_state.get(\"final_response\", \"I'm here to help!\")\n"," print(f\"\\n🤖 Chatbot:\\n{bot_reply}\")\n","\n"," # --------------------------------------------------------\n"," # 4. Display context information (optional debug info)\n"," # --------------------------------------------------------\n"," if chatbot_state.get(\"customer_id\"):\n"," print(f\"\\n📋 Context: Customer ID = {chatbot_state['customer_id']}\", end=\"\")\n","\n"," if chatbot_state.get(\"order_list\"):\n"," print(f\" | Orders Found = {len(chatbot_state['order_list'])}\", end=\"\")\n","\n"," if chatbot_state.get(\"order_id\"):\n"," print(f\" | Selected Order = {chatbot_state['order_id']}\", end=\"\")\n","\n"," # Display executed nodes\n"," if chatbot_state.get(\"executed_nodes\"):\n"," print(f\" | Executed Nodes = {', '.join(chatbot_state['executed_nodes'])}\", end=\"\")\n","\n"," # Add newline if any context was printed\n"," if chatbot_state.get(\"customer_id\") or chatbot_state.get(\"order_list\") or chatbot_state.get(\"order_id\") or chatbot_state.get(\"executed_nodes\"):\n"," print()\n","\n"," except GraphRecursionError as e:\n"," print(f\"\\n❌ System Error: Recursion limit reached\")\n"," print(f\" This usually means the chatbot is stuck in a loop.\")\n"," print(f\" Please try rephrasing your question or restart the conversation.\")\n"," print(f\" Technical details: {str(e)}\")\n","\n"," except Exception as e:\n"," print(f\"\\n❌ Error: {type(e).__name__}: {e}\")\n"," print(f\" Please try again or contact support if the issue persists.\")\n","\n","print(\"\\n\" + \"=\" * 70)\n","print(\"Session ended. Thank you for using Kartify Support!\")\n","print(\"=\" * 70)"]},{"cell_type":"markdown","source":["Power Ahead!\n","___"],"metadata":{"id":"E-ue_j57JUEF"}}],"metadata":{"colab":{"provenance":[],"collapsed_sections":["DYHpGx_vRzV8","MDb2jYQASw51","fY-KHWBbIAAU","7OEEQv_sIAAV","H8G5A0Z1SxMU","KvDztC-gJod4","gs-OSLYzJzzJ"]},"kernelspec":{"display_name":"env1","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.13.5"}},"nbformat":4,"nbformat_minor":0}