Kartify / app.py
HarishMaths's picture
Update app.py
2096d39 verified
import streamlit as st
import sqlite3
import pandas as pd
import os
import json
from langchain.chat_models import ChatOpenAI
from langchain.agents import Tool, initialize_agent
from langchain.agents.agent_types import AgentType
from langchain_community.utilities.sql_database import SQLDatabase
from langchain_community.agent_toolkits import create_sql_agent
from langchain.schema import HumanMessage
# Load the JSON file and extract values
file_name = 'config.json'
with open(file_name, 'r') as file:
config = json.load(file)
API_KEY = config.get("API_KEY") # Loading the API Key
OPENAI_API_BASE = config.get("OPENAI_API_BASE") # Loading the API Base Url
# Set API keys and base
os.environ['OPENAI_API_KEY'] = API_KEY
os.environ['OPENAI_BASE_URL'] = OPENAI_API_BASE
llm = ChatOpenAI(model_name="openai/gpt-oss-120b")
connection = sqlite3.connect("kartify.db", check_same_thread=False)
kartify_db = SQLDatabase.from_uri("sqlite:///kartify.db")
sqlite_agent = create_sql_agent(llm, db=kartify_db, agent_type="openai-tools", verbose=False)
def policy_tool_func(input: str) -> str:
prompt = f"""Only respond about return or replacement if the user has explicitly asked about it in their query.
Use the following context from order, shipment, and product policy data:
{input}
Your task (only if return or replacement is mentioned):
1. Check eligibility based on `actual_delivery` and product policy:
- If `return_days_allowed` is 0, clearly state the product is not eligible for return.
- If within window, mention last date allowed for return and replacement.
- If the window has passed, say so.
2. Mention return conditions (e.g., “Sealed only”).
3. If `actual_delivery` is null, return/replacement is not yet applicable.
4. If any required info is missing, say so politely that i am connecting to human support.
If the query does **not** mention return or replacement, do **not** include any information about it in your response.
Respond clearly and briefly — no system or SQL steps."""
return llm([HumanMessage(content=prompt)]).content.strip()
def answer_generation_tool(input: str) -> str:
prompt = f"""You are a polite and formal customer assistant replying to a user query to customer about return, replacement, delivery, cancellation, or address change.
Use the context provided below:
{input}
Guidelines:
- Respond in a short, formal, and factual tone.
- Do **not** add extra details that were not asked in the user's query.
- Do **not** format like an email — avoid greetings, sign-offs, or explanations.
- Do not offer return/replacement windows unless directly asked.
- Do not mention cancellation or refund policies unless cancellation is requested.
- Do not mention address change policy until asked.
- If the product has **not been delivered**, mention that return/replacement cannot be processed yet.
- If the product **has been delivered**, calculate based on delivery date whether return or replacement is still allowed.
- If the order has already **shipped**, reply that address changes are not possible.
- If any required info is missing, say so politely that i am connecting to human support.
- If you do not what to answer now, say so politely that i am connecting to human support.
- Never ask for any bank details
Output:
- Return only a single, relevant customer-facing message — no system instructions, reasoning, or metadata.
"""
return llm([HumanMessage(content=prompt)]).content.strip()
def output_guard_check(model_output: str) -> str:
prompt = f"""
You are a content safety assistant. Your task is to classify if the assistant's response is appropriate.
If the message contains:
- Requests for bank details, OTPs, account numbers
- Harassment or offensive tone
- Privacy concerns or unsafe advice
- Misunderstanding and miscommunication word
- Phrases like "please contact customer service" or redirection to a human agent
- Escalated this to our support team
Return: BLOCK
Otherwise, return: SAFE
Response: {model_output}
Output:
"""
return llm.predict(prompt).strip()
def evaluate_response_quality(context: str, query: str, response: str) -> dict:
prompt = f"""Evaluate the assistant's response to a customer query using the provided order context.
Context: {context}
Customer Query: {query}
Assistant's Response: {response}
Instructions:
1. **Groundedness (0.0 to 1.0)**: Score based on how well the response is factually supported by the context.
- Score closer to 1 if all facts are accurate and derived from the context.
- Score closer to 0 if there is hallucination, guesswork, or any fabricated information.
2. **Precision (0.0 to 1.0)**: Score based on how directly and accurately the assistant addresses the query.
- Score closer to 1 if the response is concise, focused, and answers the exact user query.
- Score closer to 0 if it includes irrelevant details or misses the main point.
Output format (JSON only):
groundedness: float between 0 and 1 ,
precision: float between 0 and 1
Only return the JSON. No explanations.
"""
score = llm.predict(prompt).strip()
try:
return eval(score)
except:
return {"groundedness": 0.0, "precision": 0.0}
def conversation_guard_check(history) -> str:
chat_summary = "\n".join([f"Customer: {h['user']}\nAssistant: {h['assistant']}" for h in history])
prompt = f"""
You are a conversation monitor AI. Review the entire conversation and classify if the assistant:
- Repeatedly offered unnecessary return or replacement steps
- Gave more than what the user asked
- Missed signs of customer distress
- Ignored user's refusal of an option
If any of the above are TRUE, return BLOCK
Else, return SAFE
Conversation:
{chat_summary}
Output:
"""
return llm.predict(prompt).strip()
tools = [
Tool(name="PolicyChecker", func=policy_tool_func, description="Check return and replacement eligibility."),
Tool(name="AnswerGenerator", func=answer_generation_tool, description="Craft final response.")
]
order_agent = initialize_agent(tools=tools,llm=llm,agent="zero-shot-react-description",verbose=False,handle_parsing_errors=True,)
st.title("📦 Kartify Order Query Chatbot")
customer_id = st.text_input("Enter your Customer ID:")
if customer_id:
query = """
SELECT
order_id,
product_description
FROM
orders
WHERE
customer_id = ?
ORDER BY order_date DESC
"""
df = pd.read_sql_query(query, connection, params=(customer_id,))
if not df.empty:
selected_order = st.selectbox("Select your Order:", df["order_id"] + " - " + df["product_description"])
start_chat = st.button("Start Chat")
if start_chat:
# Reset chat state except customer ID and order ID
st.session_state.chat_history = []
st.session_state.order_id = selected_order.split(" - ")[0]
with st.spinner("Loading order details..."):
order_context_raw = sqlite_agent.invoke(f"Fetch all columns for order ID {st.session_state.order_id}")
st.session_state.order_context = f"Order ID: {st.session_state.order_id}\n{order_context_raw}\nToday's Date: 25 July"
if "order_context" in st.session_state:
st.markdown("### Chat with Assistant")
for msg in st.session_state.chat_history:
st.chat_message("user").write(msg["user"])
st.chat_message("assistant").write(msg["assistant"])
user_query = st.chat_input("How can I help you?")
if user_query:
intent_prompt = f"""You are an intent classifier for customer service queries. Your task is to classify the user's query into one of the following 3 categories based on tone, completeness, and content.
Return **only the numeric category ID (0, 1, 2 and 3)** as the output. Do not include any explanation or extra text.
### Categories:
0 — **Escalation**
- The user is very angry, frustrated, or upset.
- Uses strong emotional language (e.g., “This is unacceptable”, “Worst service ever”, “I’m tired of this”, “I want a human now”).
- Requires **immediate human handoff**.
- Escalation confidence must be very high (90% or more).
1 — **Exit**
- The user is ending the conversation or expressing satisfaction.
- Phrases like “Thanks”, “Got it”, “Okay”, “Resolved”, “Never mind”.
- No further action is required.
2 — **Process**
- The query is clear and well-formed.
- Contains enough detail to act on (e.g., mentions order ID, issue, date).
- Language is polite or neutral; the query is actionable.
- Proceed with normal handling.
3- **Random Question**
- If user asked something not related to order
example - What is NLP
---
Your job:
Read the user query and return just the category number (0, 1, 2, or 3). Do not include explanations, formatting, or any text beyond the number.
User Query: {user_query}"""
intent = llm.predict(intent_prompt).strip()
if intent == "0":
response = "Sorry for the inconvenience. A human agent will assist you shortly 1."
elif intent == "1":
response = "Thank you! I hope I was able to help."
elif intent == "3":
response = "Apologies, I’m currently only able to help with information about your placed orders. Please let me know how I can assist you with those!"
else:
full_prompt = f"""
Context:
{st.session_state.order_context}
Customer Query: {user_query}
Previous response: {st.session_state.chat_history}
Use tools to reply.
"""
with st.spinner("Generating response..."):
raw_response = order_agent.run(full_prompt)
# Step 1: Evaluate quality (Groundedness and Precision first)
scores = evaluate_response_quality(st.session_state.order_context, user_query, raw_response)
if scores["groundedness"] < 0.75 or scores["precision"] < 0.75:
regenerated_response = order_agent.run(full_prompt)
scores_retry = evaluate_response_quality(st.session_state.order_context, user_query, regenerated_response)
if scores_retry["groundedness"] >= 0.75 and scores_retry["precision"] >= 0.75:
response = regenerated_response
else:
response = "Your request is being forwarded to a customer support specialist. A human agent will assist you shortly."
else:
response = raw_response
# Step 2: Guard check (after passing quality check)
if response not in [
"Your request is being forwarded to a customer support specialist. A human agent will assist you shortly."
]:
guard = output_guard_check(response)
if guard == "BLOCK":
response = "Your request is being forwarded to a customer support specialist. A human agent will assist you shortly."
# Save chat history
st.session_state.chat_history.append({"user": user_query, "assistant": response})
# Step 3: Conversation-level safety
conv_check = conversation_guard_check(st.session_state.chat_history)
if conv_check == "BLOCK":
response = "Your request is being forwarded to a customer support specialist. A human agent will assist you shortly."
st.chat_message("user").write(user_query)
st.chat_message("assistant").write(response)
else:
st.info("Please enter a Customer ID to begin.")