meltmind-ai / meltmind_engine.py
Haricharan Vallem
Codex: prepare MeltMind for Hugging Face Spaces
58fddaa
Raw
History Blame Contribute Delete
63.7 kB
import copy
import json
import os
import re
import time
import urllib.error
import urllib.request
from pathlib import Path
ROOT = Path(__file__).parent
DATA = ROOT / "data"
MIND = DATA / "meltmind"
def load_json(path: Path):
return json.loads(path.read_text())
def slug(value: str) -> str:
return re.sub(r"[^a-z0-9]+", "_", value.lower()).strip("_")
def price(value) -> int:
match = re.search(r"\d+", str(value))
return int(match.group()) if match else 0
def tokens(value: str) -> set[str]:
return {word for word in re.findall(r"[a-z0-9]+", value.lower()) if len(word) > 2}
class MeltMindEngine:
def __init__(self):
self.menu = load_json(DATA / "menu.json")
self.product_knowledge = load_json(MIND / "product_knowledge.json")["products"]
self.faqs = load_json(MIND / "faq.json")["faqs"]
self.ingredients = load_json(MIND / "ingredients.json")
self.business = load_json(MIND / "business_profile.json")
self.policies = load_json(MIND / "operations_and_policies.json")
self.runtime = load_json(ROOT / "config" / "model_runtime.json")
self.llm_disabled_until = 0.0
self.chat_cache = {}
self.design_cache = {}
self.knowledge_chunks = self._build_knowledge_chunks()
self.knowledge_by_id = {item["product_id"]: item for item in self.product_knowledge}
self.products = {}
for item in self.menu:
product_id = slug(item["name"])
knowledge = self.knowledge_by_id.get(product_id, {})
serves = item.get("can_be_served_for", "1 person")
serves_numbers = [int(value) for value in re.findall(r"\d+", serves)]
self.products[product_id] = {
"id": product_id,
"name": item["name"],
"category": item["category"],
"price": price(item["price"]),
"image": item.get("image", ""),
"short": item.get("one_line_description") or item.get("description", ""),
"description": item.get("detailed_description") or item.get("description", ""),
"tagline": item.get("tagline", ""),
"serves": max(serves_numbers) if serves_numbers else 1,
"serves_text": serves,
"flavours": item.get("taste_profile", {}).get("flavours", []),
"textures": item.get("taste_profile", {}).get("textures", []),
"best_for": item.get("taste_profile", {}).get("best_for", []),
"tags": item.get("tags", []),
"allergens": item.get("dietary_information", {}).get("allergens", []),
"available": item.get("ordering_information", {}).get("currently_available", "Unknown") == "Yes",
"premium": item.get("ordering_information", {}).get("premium_item", "Unknown") == "Yes",
"knowledge": knowledge,
}
def _build_knowledge_chunks(self) -> list[dict]:
chunks = []
source_paths = [DATA / "menu.json", *sorted(MIND.glob("*.json")), *sorted(MIND.glob("*.md"))]
for path in source_paths:
text = path.read_text(errors="ignore")
if path.suffix == ".json":
try:
value = json.loads(text)
entries = []
if isinstance(value, list):
entries = value
elif isinstance(value, dict):
for key, content in value.items():
if isinstance(content, list):
entries.extend({"section": key, "content": item} for item in content)
else:
entries.append({"section": key, "content": content})
for index, entry in enumerate(entries):
serialized = json.dumps(entry, ensure_ascii=False, separators=(",", ":"))
chunks.append({
"source": str(path.relative_to(ROOT)),
"section": str(index),
"text": serialized,
"tokens": tokens(serialized),
})
continue
except json.JSONDecodeError:
pass
sections = re.split(r"\n(?=#{1,4}\s)", text)
for index, section in enumerate(sections):
cleaned = section.strip()
if cleaned:
chunks.append({
"source": str(path.relative_to(ROOT)),
"section": str(index),
"text": cleaned,
"tokens": tokens(cleaned),
})
return chunks
def retrieve_knowledge_chunks(self, query: str, limit: int | None = None) -> list[dict]:
query_tokens = tokens(query)
source_priority = {
"data/menu.json": 12,
"data/meltmind/product_knowledge.json": 12,
"data/meltmind/operations_and_policies.json": 12,
"data/meltmind/ingredients.json": 11,
"data/meltmind/add_ons.json": 11,
"data/meltmind/business_profile.json": 11,
"data/meltmind/faq.json": 10,
"data/meltmind/recommendation_rules.json": 9,
"data/meltmind/language_dictionary.json": 8,
"data/meltmind/customer_examples.json": 2,
"data/meltmind/evaluation_cases.json": 1,
}
ranked = sorted(
self.knowledge_chunks,
key=lambda chunk: (
len(query_tokens & chunk["tokens"]) * 10 + source_priority.get(chunk["source"], 4),
source_priority.get(chunk["source"], 4),
),
reverse=True,
)
maximum = limit or self.runtime["context"]["maximum_retrieved_chunks"]
selected = [chunk for chunk in ranked if query_tokens & chunk["tokens"]][:maximum]
return [{"source": chunk["source"], "section": chunk["section"], "content": chunk["text"]} for chunk in selected]
def compact_menu_catalogue(self) -> list[dict]:
return [{
"id": item["id"],
"name": item["name"],
"category": item["category"],
"price_inr": item["price"],
"available": item["available"],
"serving_guidance": item["serves_text"],
"flavours": item["flavours"],
"textures": item["textures"],
"allergens": item["allergens"],
"premium": item["premium"],
} for item in self.products.values()]
def runtime_status(self, check_connection: bool = False) -> dict:
model = self.runtime["model"]
connected = None
if check_connection:
try:
with urllib.request.urlopen(
self._llm_base_url() + self.runtime["server"]["health_path"],
timeout=2,
) as response:
connected = response.status < 400
except (TimeoutError, urllib.error.URLError):
connected = False
return {
"provider": self.runtime["provider"],
"model": model["variant"],
"model_repo": model["hugging_face_repo"],
"configured": bool(self._llm_base_url()),
"connected": connected,
"response_architecture": "llm_led_with_deterministic_validation",
"knowledge_sources": len({chunk["source"] for chunk in self.knowledge_chunks}),
"knowledge_chunks": len(self.knowledge_chunks),
"openbmb_challenge_eligible": model["openbmb_challenge_eligible"],
"tiny_titan_eligible": model["tiny_titan_eligible"],
}
def _llm_base_url(self) -> str:
return os.getenv("MELTMIND_LLM_BASE_URL", self.runtime["server"]["base_url"]).rstrip("/")
def _llm_model(self) -> str:
return os.getenv(
"MELTMIND_LLM_MODEL",
self.runtime["model"]["hugging_face_repo"] or self.runtime["model"]["variant"],
)
def _llm_completion(self, messages: list[dict], max_tokens: int) -> str | None:
if time.monotonic() < self.llm_disabled_until:
return None
server = self.runtime["server"]
payload = {
"model": self._llm_model(),
"messages": messages,
"temperature": self.runtime["generation"]["temperature"],
"top_p": self.runtime["generation"]["top_p"],
"max_tokens": max_tokens,
"cache_prompt": True,
"stream": False,
}
request = urllib.request.Request(
self._llm_base_url() + server["chat_completions_path"],
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.getenv('MELTMIND_LLM_API_KEY', 'none')}",
},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=server["request_timeout_seconds"]) as response:
result = json.loads(response.read().decode("utf-8"))
content = result["choices"][0]["message"]["content"]
if isinstance(content, list):
content = " ".join(part.get("text", "") for part in content if isinstance(part, dict))
cleaned = re.sub(r"\s+", " ", str(content)).strip()
return cleaned or None
except (KeyError, IndexError, TypeError, ValueError, TimeoutError, urllib.error.URLError):
self.llm_disabled_until = time.monotonic() + 30
return None
@staticmethod
def _currency_values(text: str) -> set[int]:
return {
int(value)
for value in re.findall(r"(?:₹|inr|rs\.?|price_inr|total_inr|remaining_budget_inr)\D{0,5}(\d{1,6})", text.lower())
}
def _grounded_answer_is_valid(self, answer: str, deterministic_answer: str, context: dict, handoff: bool) -> bool:
serialized = json.dumps(context, ensure_ascii=False)
supported_currency = self._currency_values(deterministic_answer + " " + serialized)
if not self._currency_values(answer).issubset(supported_currency):
return False
if handoff and not any(value in answer.lower() for value in ("contact", "confirm", "whatsapp", "meltroom team", "meltroom staff")):
return False
forbidden = (
"guaranteed safe",
"allergen-free",
"guaranteed delivery",
"guaranteed refund",
"healthier",
"healthy option",
"suitable for those avoiding",
"safe for people with",
)
return not any(value in answer.lower() for value in forbidden)
@staticmethod
def _rank_records(query: str, records: list[tuple[str, dict]], limit: int) -> list[dict]:
query_tokens = tokens(query)
ranked = sorted(records, key=lambda record: len(query_tokens & tokens(record[0])), reverse=True)
return [record for text, record in ranked if len(query_tokens & tokens(text)) > 0][:limit]
def retrieve_context(self, message: str, products: list[dict] | None = None) -> dict:
selected = self.find_products(message) if products is None else products
product_facts = []
for item in selected:
knowledge = item["knowledge"]
identity = knowledge.get("identity", {})
sensory = knowledge.get("sensory_profile", {})
product_facts.append({
"name": item["name"],
"category": item["category"],
"price_inr": item["price"],
"currently_available": item["available"],
"serving_guidance": item["serves_text"],
"menu_description": item["short"],
"flavours": item["flavours"],
"textures": item["textures"],
"best_for": item["best_for"],
"allergens": item["allergens"],
"plain_explanation": identity.get("plain_explanation", ""),
"sweetness": sensory.get("sweetness", ""),
"chocolate_intensity": sensory.get("chocolate_intensity", ""),
})
return {
"products": product_facts,
"relevant_live_menu_candidates": [{
"id": item["id"],
"name": item["name"],
"category": item["category"],
"price_inr": item["price"],
"available": item["available"],
"serving_guidance": item["serves_text"],
"flavours": item["flavours"],
"textures": item["textures"],
"allergens": item["allergens"],
"premium": item["premium"],
} for item in selected],
# Product questions already receive complete, verified structured facts above.
# Avoid repeating a large raw product JSON chunk in the prompt.
"retrieved_knowledge_corpus": [] if product_facts else self.retrieve_knowledge_chunks(message, 2),
}
def compose_chat_answer(
self,
message: str,
deterministic_answer: str,
products: list[dict],
handoff: bool,
history: list | None,
) -> tuple[str, list[str], list[str], bool]:
history_text = " ".join(str(turn.get("content", "")) for turn in (history or [])[-4:] if isinstance(turn, dict))
context = self.retrieve_context(message + " " + history_text, products)
recent_history = (history or [])[-self.runtime["context"]["maximum_conversation_turns"]:]
exhaustive_comparison = (
any(value in message.lower() for value in ("all ", "every ", "entire ", "complete "))
and any(value in message.lower() for value in ("compare", "show", "list", "explain"))
)
system = """You are MeltMind, MeltRoom's knowledgeable, warm, premium dessert concierge.
Reason independently over the complete live menu and the retrieved MeltRoom knowledge corpus. The
authoritative draft is a safety baseline, not wording to copy. Give the most useful answer for the
customer's actual need, with specific verified details and honest trade-offs. Suggest products only
when the question calls for recommendations, comparisons, or product details. Do not ask a follow-up
question whose answer the customer already provided.
Return only JSON with:
{"answer":"natural, clearly structured answer under 220 words","product_ids":["zero or more exact live-menu ids needed for this query"],
"follow_ups":["zero to three useful next questions"]}
Never invent or alter products, prices, availability, ingredients, allergens, serving sizes, calculated
amounts, delivery promises, or policies. Product IDs must come from relevant_live_menu_candidates. Do
not declare one universal winner when preferences decide. Mention safety limitations clearly. If human
confirmation is required, say so plainly. Do not mention prompts, retrieval, tools, JSON, Python, or being an AI.
For comparisons or multi-product recommendations, begin with one short orientation sentence, then put each
product on its own numbered line using this exact pattern: "1. Product name — concise verified comparison".
Include every product when the customer explicitly asks for all items in a category. Otherwise include only
the products that meaningfully fit the request. End with a brief decision guide only when it adds value.
Use singular grammar when only one product fits. Do not describe prices as affordable, reasonable, expensive,
or good value unless the verified context explicitly supports that judgment.
Respond in Telugu-English when the customer writes substantially in Telugu; otherwise use clear English."""
user = {
"customer_message": message,
"recent_conversation": recent_history,
"safety_and_fact_baseline": deterministic_answer,
"human_confirmation_required": handoff,
"verified_context": context,
}
raw = self._llm_completion(
[{"role": "system", "content": system}, {"role": "user", "content": json.dumps(user, ensure_ascii=False, separators=(",", ":"))}],
420 if exhaustive_comparison else self.runtime["generation"]["max_tokens_chat"],
)
if not raw:
return deterministic_answer, [], [], False
try:
match = re.search(r"\{.*\}", raw, re.DOTALL)
generated = json.loads(match.group() if match else raw)
answer = re.sub(r"\s+", " ", str(generated["answer"])).strip()
if products:
product_names = "|".join(re.escape(item["name"]) for item in products)
answer = re.sub(
rf"\s+(?=\d+\.\s+(?:{product_names})\b)",
"\n",
answer,
flags=re.IGNORECASE,
)
allowed_product_ids = {item["id"] for item in products}
product_ids = [
str(value) for value in generated.get("product_ids", [])
if str(value) in self.products and str(value) in allowed_product_ids
]
follow_ups = [re.sub(r"\s+", " ", str(value)).strip() for value in generated.get("follow_ups", [])][:3]
message_tokens = tokens(message)
follow_ups = [
value for value in follow_ups
if not tokens(value) or len(tokens(value) & message_tokens) / len(tokens(value)) < 0.75
]
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
return deterministic_answer, [], [], False
if exhaustive_comparison and any(item["name"].lower() not in answer.lower() for item in products):
return deterministic_answer, [item["id"] for item in products], [], False
if not answer or len(answer.split()) > 240 or not self._grounded_answer_is_valid(answer, deterministic_answer, context, handoff):
return deterministic_answer, [], [], False
return answer, product_ids, follow_ups, True
def compose_plan_explanations(self, result: dict) -> tuple[dict, bool]:
compact_plans = [{
"index": index,
"title": plan["title"],
"items": [{
"name": item["name"],
"quantity": item["quantity"],
"unit_price_inr": item["price"],
"serving_guidance": item["serves_text"],
"verified_description": self.products[item["id"]]["short"],
"verified_flavours": self.products[item["id"]]["flavours"],
"verified_textures": self.products[item["id"]]["textures"],
"verified_best_for": self.products[item["id"]]["best_for"],
"verified_allergens": self.products[item["id"]]["allergens"],
} for item in plan["items"]],
"total_inr": plan["total"],
"remaining_budget_inr": plan["remaining_budget"],
"calculated_servings": plan["servings"],
} for index, plan in enumerate(result["plans"])]
prompt = {
"customer_preferences": result["preferences"],
"authoritative_plans": compact_plans,
"relevant_meltroom_knowledge": self.retrieve_knowledge_chunks(
str(result["preferences"].get("request", "")) + " "
+ " ".join(result["preferences"].get("flavours", [])) + " "
+ " ".join(result["preferences"].get("textures", [])),
2,
),
}
system = """You explain deterministic MeltRoom dessert plans. Return only a JSON object with a
"plans" array. Each entry must have the original integer "index" and a "summary" of 45-90 words.
Explain the plan as a complete combination: name every listed product, explain how their explicitly
verified flavours and textures complement each other, and describe the useful trade-off between this
plan and the other directions. Do not mention numbers, prices, totals, remaining budget, or serving
counts in the summary. Do not write serving notes. Do not invent taste attributes; desserts are not
savory. Do not change or invent items, quantities, ingredients, allergens, or policy facts. Never call
a dessert medically safe."""
raw = self._llm_completion(
[{"role": "system", "content": system}, {"role": "user", "content": json.dumps(prompt, ensure_ascii=False, separators=(",", ":"))}],
self.runtime["generation"]["max_tokens_designer_explanation"],
)
if os.getenv("MELTMIND_DEBUG_LLM") == "1":
print("MELTMIND_DESIGNER_RAW:", raw, flush=True)
if not raw:
return result, False
try:
match = re.search(r"\{.*\}", raw, re.DOTALL)
generated = json.loads(match.group() if match else raw)
generated_plans = generated["plans"]
if len(generated_plans) != len(result["plans"]):
return result, False
by_index = {}
for position, item in enumerate(generated_plans):
supplied = int(item.get("index", position))
index = supplied - 1 if supplied not in range(len(result["plans"])) and supplied - 1 in range(len(result["plans"])) else supplied
by_index[index] = item
if set(by_index) != set(range(len(result["plans"]))):
by_index = {index: item for index, item in enumerate(generated_plans)}
accepted = 0
for index, plan in enumerate(result["plans"]):
summary = re.sub(r"\s+", " ", str(by_index[index].get("summary") or by_index[index].get("explanation") or "")).strip()
safe_sentences = [
sentence for sentence in re.split(r"(?<=[.!?])\s+", summary)
if sentence
and not re.search(r"\b\d+(?:\.\d+)?\b|₹|\binr\b|\brs\.?\b", sentence.lower())
and not any(value in sentence.lower() for value in ("savory", "savoury", "total cost", "remaining budget"))
]
safe_summary = " ".join(safe_sentences).strip()
mentioned = [item["name"] for item in plan["items"] if item["name"].lower() in safe_summary.lower()]
required_mentions = min(2, len(plan["items"]))
if safe_summary and len(mentioned) >= required_mentions:
omitted = [item["name"] for item in plan["items"] if item["name"] not in mentioned]
if omitted:
safe_summary += f" The complete direction also includes {', '.join(omitted)}."
plan["summary"] = safe_summary
accepted += 1
return result, accepted > 0
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
return result, False
def extract_preferences(self, text: str) -> dict:
lower = text.lower()
budget_match = re.search(
r"(?:₹|rs\.?|inr|under|within|up\s+to|max(?:imum)?(?:\s+of)?|budget(?:\s+of)?)\s*(\d{2,5})",
lower,
)
if not budget_match:
budget_match = re.search(r"(\d{2,5})\s*(?:rupees?|rs\.?|inr)\b", lower)
if not budget_match and ("లోపు" in text or "బడ్జెట్" in text):
budget_match = re.search(r"(\d{2,5})", text)
group_match = re.search(r"(\d{1,2})\s*(?:friends|people|persons|members|guests)", lower)
group_size = int(group_match.group(1)) if group_match else (
2 if "two friends" in lower or "ఇద్దరి" in text or "ఇద్దరు" in text else
3 if "ముగ్గురు" in text else
4 if "నలుగురు" in text else 1
)
flavours = [
value for value, words in {
"chocolate": ("chocolate", "chocolaty", "chocoholic", "fudge", "చాక్లెట్"),
"pistachio": ("pista", "pistachio", "nutty", "kunafa"),
"mango": ("mango",),
"banana": ("banana",),
"cream": ("cream", "creamy", "vanilla"),
}.items() if any(word in lower for word in words)
]
texture_aliases = {
"gooey": ("gooey", "goey", "melty", "molten", "melted centre", "melted center"),
"fudgy": ("fudgy", "fudgey", "fudgie"),
"crunchy": ("crunchy", "crunch"),
"crispy": ("crispy", "crisp"),
"creamy": ("creamy",),
"soft": ("soft",),
"chewy": ("chewy",),
}
textures = [
texture for texture, aliases in texture_aliases.items()
if any(alias in lower for alias in aliases)
]
return {
"budget_inr": int(budget_match.group(1)) if budget_match else 500,
"group_size": group_size,
"flavours": flavours,
"textures": textures,
"premium": "premium" in lower or "luxury" in lower,
"variety": "variety" in lower or "different" in lower or group_size >= 3,
"less_sweet": any(value in lower for value in ("not too sweet", "less sweet", "balanced sweetness"))
or ("balanced" in lower and any(value in lower for value in ("sweet", "chocolate", "chocolaty"))),
"warm": any(value in lower for value in ("warm", "freshly heated", "served hot")),
"request": text,
}
def special_policy_answer(self, message: str) -> dict | None:
lower = message.lower()
preferences = self.extract_preferences(message)
common = {
"products": [],
"preferences": preferences,
"source": "Verified MeltRoom policy and ingredient knowledge",
}
delivery_match = re.search(r"(?:rapido|delivery)[^\d₹]{0,20}₹?\s*(\d{2,5})", lower)
if delivery_match:
charge = int(delivery_match.group(1))
contribution = round(charge * 0.4) if charge > 100 else 0
customer = charge - contribution
answer = (
f"For a ₹{charge} delivery charge, MeltRoom contributes ₹{contribution} and you pay ₹{customer}. "
if charge > 100 else
f"For a ₹{charge} delivery charge, you pay ₹{customer}; MeltRoom's 40% contribution applies only when the charge is above ₹100. "
)
answer += "The dessert subtotal is separate, and the final Rapido charge is confirmed in chat."
return {**common, "answer": answer, "follow_ups": ["Compare with pickup from KLN Reddy Colony"], "handoff": False}
if any(value in lower for value in ("diabetes", "diabetic", "pregnant", "pregnancy", "medical")):
return {
**common,
"answer": "MeltMind cannot recommend a dessert as medically safe. MeltRoom brownies are made without refined sugar, maida, or eggs, but they are still desserts. Please use the verified ingredient facts and consult a qualified professional for medical guidance.",
"follow_ups": ["Contact MeltRoom for verified ingredient details"],
"handoff": True,
}
if "guilt-free" in lower or "guilt free" in lower:
return {
**common,
"answer": "Guilt-free is MeltRoom's brand description for its brownies made without refined sugar, maida, or eggs. They are gluten-free brownies, but the phrase does not mean calorie-free, unlimited, low-sugar, or medically suitable.",
"follow_ups": ["Ask about verified ingredients", "Compare sweetness directions"],
"handoff": False,
}
if "gluten" in lower:
return {
**common,
"answer": "MeltRoom brownies are gluten-free. For a strict gluten-free requirement, the complete order still needs checking because fillings, toppings, add-ons, substitutions, and cross-contact can matter.",
"follow_ups": ["Check a specific customization"],
"handoff": False,
}
milk_request = "milk" in lower and any(value in lower for value in ("replace", "substitut", "allerg", "oat", "almond"))
if milk_request:
severe = "severe" in lower or "allerg" in lower
answer = "Oat milk or almond milk may replace milk where compatible. Oat milk is the milder direction; almond milk contains tree nuts and adds a light nutty profile. The exact dessert and all components must be checked"
answer += ", and MeltRoom staff must confirm severe allergies and cross-contact handling." if severe else "."
return {
**common,
"answer": answer,
"follow_ups": ["Tell me which dessert you want to customize"],
"handoff": severe,
}
ingredient_intent = any(value in lower for value in (
"ingredient", "what do you use", "what is used", "made with",
))
if ingredient_intent:
ingredient_records = self.ingredients["ingredients"]
ingredient_by_id = {item["ingredient_id"]: item for item in ingredient_records}
mentioned_products = [
product for product in self.products.values()
if product["name"].lower() in lower
or product["name"].lower().removeprefix("the ") in lower
]
if mentioned_products:
product = mentioned_products[0]
composition = product["knowledge"].get("composition", {})
verified_ingredients = [
ingredient_by_id[ingredient_id]
for ingredient_id in composition.get("ingredient_ids", [])
if ingredient_id in ingredient_by_id
]
details = []
for index, ingredient in enumerate(verified_ingredients, start=1):
explanation = ingredient["plain_explanation"].rstrip(".")
purpose = ingredient.get("why_meltroom_uses_it", "").rstrip(".")
detail = f"{index}. {ingredient['name']}{explanation}."
if purpose:
detail += f" It {purpose[:1].lower() + purpose[1:]}."
details.append(detail)
answer = f"{product['name']} uses these verified core ingredients:\n" + "\n".join(details)
preparation = composition.get("preparation_summary", "").strip()
if preparation:
answer += f"\nTogether, they create the item as follows: {preparation}"
return {
**common,
"answer": answer,
"follow_ups": [],
"handoff": False,
"source": f"Verified {product['name']} composition",
"fast_path": True,
}
mentioned = [
item for item in ingredient_records
if item["name"].lower() in lower or item["ingredient_id"].replace("_", " ") in lower
]
if mentioned:
details = []
for item in mentioned[:3]:
flavours = ", ".join(item.get("flavour_contribution", [])[:3])
textures = ", ".join(item.get("texture_contribution", [])[:3])
explanation = item["plain_explanation"].strip()
explanation = explanation[:1].lower() + explanation[1:] if explanation else "a verified MeltRoom ingredient"
detail = f"{item['name']} is {explanation}"
if flavours:
detail += f" It contributes {flavours} flavour"
if textures:
detail += f" and a {textures} texture direction"
details.append(detail + ".")
answer = " ".join(details)
else:
answer = (
"MeltRoom desserts are built around a signature fudgy brownie base, premium dark and milk chocolate, "
"chocolate chips, creams, fillings, and texture-led toppings. Pistachio cream and kunafa create rich, "
"nutty and crispy directions, while fresh mango and banana add fruity contrast. Banana Bliss also uses "
"oats powder, honey, and coconut crumbs. MeltRoom's brownie base is gluten-free, eggless, and made "
"without maida or refined sugar."
)
answer += (
" Selected chocolates, creams, and fillings contain milk, while pistachio and almond milk contain tree nuts. "
"Oat or almond milk may replace milk where compatible, but the complete dessert must be confirmed for allergy requirements."
)
return {
**common,
"answer": answer,
"follow_ups": ["Ask about a specific ingredient", "Ask what is used in a specific dessert"],
"handoff": False,
}
if any(value in lower for value in ("sent my meltbasket", "sent my order", "is it confirmed", "order confirmed")):
return {
**common,
"answer": "Sending the MeltBasket on WhatsApp does not confirm the order. MeltRoom reviews it, shares the owner's payment QR, and the order is confirmed only after payment is received.",
"follow_ups": ["Contact MeltRoom about payment confirmation"],
"handoff": True,
}
if any(value in lower for value in ("how does ordering", "how to order", "ordering process", "payment and delivery", "payment, delivery")):
return {
**common,
"answer": "Build your MeltBasket and send it to MeltRoom through WhatsApp, or order through Instagram DM. MeltRoom reviews the order and shares the owner's payment QR in chat. The order is confirmed only after payment is received. Regular menu orders are generally targeted for delivery through Rapido within 45 minutes after confirmation, while nearby customers may pick up from KLN Reddy Colony.",
"follow_ups": ["Ask about delivery charges", "Ask about large or custom orders"],
"handoff": False,
}
if any(value in lower for value in ("cancel", "cancellation")):
return {
**common,
"answer": "You may stop or revise the order before payment and confirmation. Once payment is received and the order is confirmed, it cannot be cancelled. MeltRoom cannot promise an exception or refund.",
"follow_ups": ["Contact MeltRoom immediately"],
"handoff": True,
}
if any(value in lower for value in ("damaged", "spilled", "incorrect order", "missing item", "replacement", "refund")):
return {
**common,
"answer": "Report the issue to MeltRoom on the same calendar day as delivery with the order details, clear photos, a useful video, and a short explanation. MeltRoom will review the evidence; compensation or redelivery depends on that review.",
"follow_ups": ["Open WhatsApp support"],
"handoff": True,
}
return None
def score_product(self, product: dict, preferences: dict) -> float:
searchable = " ".join([
product["name"], product["category"], product["short"],
*product["flavours"], *product["textures"], *product["best_for"], *product["tags"],
json.dumps(product["knowledge"].get("sensory_profile", {}), ensure_ascii=False),
json.dumps(product["knowledge"].get("serving", {}), ensure_ascii=False),
]).lower()
sensory = product["knowledge"].get("sensory_profile", {})
flavour_identity = " ".join([
product["name"],
str(sensory.get("primary_flavour", "")),
*sensory.get("secondary_flavours", []),
*product["flavours"],
]).lower()
score = 0.0
for flavour in preferences.get("flavours", []):
score += 36 if flavour in flavour_identity else (14 if flavour in searchable else -8)
if preferences.get("flavours") == ["chocolate"]:
competing = ("pistachio", "pista", "mango", "banana")
score -= 20 if any(value in flavour_identity for value in competing) else 0
for texture in preferences.get("textures", []):
score += 22 if texture in searchable else 0
if preferences.get("warm"):
score += 24 if "warm" in searchable else -4
if preferences.get("premium") and product["premium"]:
score += 12
if preferences.get("group_size", 1) > 1:
score += min(product["serves"], preferences["group_size"]) * 10
if product["category"] in ("Melt Creation", "Brownie Bowl"):
score += 8
elif product["serves"] == 1:
score += 10
if preferences.get("less_sweet"):
sweetness = str(product["knowledge"].get("sensory_profile", {}).get("sweetness", ""))
score += 10 if sweetness and sweetness not in ("9", "10") else -8
request_overlap = len(tokens(preferences.get("request", "")) & tokens(searchable))
score += request_overlap * 7
direct_description = " ".join([product["name"], product["short"], *product["tags"]]).lower()
if "melty" in preferences.get("request", "").lower() and "melty" in direct_description:
score += 30
score += max(0, 8 - product["price"] / max(preferences.get("budget_inr", 500), 1) * 8)
return score
def recommend(self, preferences: dict) -> dict:
preferences = dict(preferences)
request = str(preferences.get("request", ""))
lower_request = request.lower()
parsed = self.extract_preferences(request)
preferences["flavours"] = list(dict.fromkeys([*preferences.get("flavours", []), *parsed["flavours"]]))
preferences["textures"] = list(dict.fromkeys([*preferences.get("textures", []), *parsed["textures"]]))
preferences["premium"] = bool(preferences.get("premium") or parsed["premium"])
preferences["variety"] = bool(preferences.get("variety") or parsed["variety"])
preferences["less_sweet"] = bool(preferences.get("less_sweet") or parsed["less_sweet"])
preferences["warm"] = bool(preferences.get("warm") or parsed["warm"])
allergens = set(preferences.get("allergen_ids", []))
if "allerg" in lower_request and ("milk" in lower_request or "dairy" in lower_request):
allergens.add("milk")
if any(value in lower_request for value in ("tree-nut allerg", "tree nut allerg", "nut allerg", "almond allerg")):
allergens.add("tree_nuts")
preferences["allergen_ids"] = sorted(allergens)
preferences["severe_allergy"] = bool(preferences.get("severe_allergy") or "severe" in lower_request)
budget = max(99, int(preferences.get("budget_inr") or 500))
group_size = max(1, int(preferences.get("group_size") or 1))
candidates = [
item for item in self.products.values()
if item["available"] and item["price"] <= budget and not allergens.intersection(item["allergens"])
]
if not candidates:
reason = (
"No plan can be finalized from the verified menu after applying the confirmed allergen exclusions. "
"MeltRoom staff must confirm compatible substitutions, every component, and cross-contact handling."
if allergens else
"No currently available menu item fits within this budget."
)
return {"preferences": preferences, "plans": [], "notice": reason, "handoff": bool(allergens)}
candidates.sort(key=lambda item: self.score_product(item, preferences), reverse=True)
plans = []
styles = (
("Closest match", candidates),
("More variety", sorted(candidates, key=lambda item: (item["category"] != "Melt Creation", -self.score_product(item, preferences)))),
("Premium direction", sorted(candidates, key=lambda item: (not item["premium"], -self.score_product(item, preferences)))),
)
for style_index, (title, ordered) in enumerate(styles):
ordered = ordered[style_index:] + ordered[:style_index]
chosen, total, servings = [], 0, 0
used_ids, used_categories = set(), set()
unit_limit = min(24, max(3, budget // min(item["price"] for item in candidates)))
def add_item(item: dict):
nonlocal total, servings
existing = next((choice for choice in chosen if choice["id"] == item["id"]), None)
if existing:
existing["quantity"] += 1
else:
chosen.append({**item, "quantity": 1})
total += item["price"]
servings += item["serves"]
used_ids.add(item["id"])
used_categories.add(item["category"])
if ordered:
add_item(ordered[0])
while len([None for item in chosen for _ in range(item["quantity"])]) < unit_limit:
remaining = budget - total
affordable = [item for item in candidates if item["price"] <= remaining]
if not affordable:
break
def complement_score(item: dict) -> float:
score = self.score_product(item, preferences)
if item["id"] not in used_ids:
score += 18
else:
score -= 16
if item["category"] not in used_categories:
score += 14
if title == "More variety" and item["id"] not in used_ids:
score += 18
if title == "Premium direction" and item["premium"]:
score += 18
# Prefer choices that use the remaining budget cleanly.
leftover = remaining - item["price"]
if leftover == 0:
score += 30
elif not any(candidate["price"] <= leftover for candidate in candidates):
score -= min(22, leftover / 5)
else:
score += item["price"] / max(remaining, 1) * 10
return score
add_item(max(affordable, key=complement_score))
if not chosen and ordered:
chosen = [{**ordered[0], "quantity": 1}]
total, servings = ordered[0]["price"], ordered[0]["serves"]
plans.append({
"title": title,
"summary": self.plan_summary(chosen, preferences),
"items": [{key: item[key] for key in ("id", "name", "category", "price", "image", "short", "serves_text", "quantity")} for item in chosen],
"total": total,
"remaining_budget": budget - total,
"servings": servings,
"serving_note": f"Current menu guidance covers approximately {servings} serving(s) for a group of {group_size}.",
})
notice = ""
if allergens:
notice = "Known matching allergens were excluded. Staff confirmation is still required for the complete order and cross-contact handling."
return {"preferences": preferences, "plans": plans, "notice": notice, "handoff": bool(allergens)}
def plan_summary(self, chosen: list[dict], preferences: dict) -> str:
names = ", ".join(f"{item['quantity']} × {item['name']}" if item.get("quantity", 1) > 1 else item["name"] for item in chosen)
reasons = []
if preferences.get("flavours"):
reasons.append("/".join(preferences["flavours"]) + " flavour")
if preferences.get("textures"):
reasons.append("/".join(preferences["textures"]) + " texture")
if preferences.get("variety"):
reasons.append("variety")
if preferences.get("premium"):
reasons.append("a premium finish")
if not reasons:
fit = "a balanced MeltRoom experience"
elif len(reasons) == 1:
fit = reasons[0]
elif len(reasons) == 2:
fit = " and ".join(reasons)
else:
fit = ", ".join(reasons[:-1]) + f", and {reasons[-1]}"
return f"This plan combines {names} to deliver {fit}. Every quantity and cost stays within the confirmed budget."
def find_products(self, text: str) -> list[dict]:
lower = text.lower()
exact = [item for item in self.products.values() if item["name"].lower() in lower]
if exact:
return exact
category = None
if "bowl" in lower:
category = "Brownie Bowl"
elif "melt creation" in lower or "creations" in lower:
category = "Melt Creation"
elif "sandwich" in lower or "meltwich" in lower:
category = "Dessert Sandwich"
elif "brownies" in lower or "brownie category" in lower:
category = "Brownie"
if category:
return [item for item in self.products.values() if item["category"] == category]
query_tokens = tokens(text)
ranked = sorted(
self.products.values(),
key=lambda item: len(query_tokens & tokens(" ".join([item["name"], item["short"], *item["tags"]]))),
reverse=True,
)
return [item for item in ranked if len(query_tokens & tokens(" ".join([item["name"], item["short"], *item["tags"]]))) > 0][:6]
def fast_conversation_response(self, message: str) -> dict | None:
normalized = re.sub(r"[^a-z]+", " ", message.lower()).strip()
responses = {
"hi": "Hi, welcome to MeltRoom. I am MeltMind. I can explain any dessert, compare flavours and textures, answer ingredient or ordering questions, or design a complete Melt within your budget. What would you like to explore?",
"hello": "Hello, welcome to MeltRoom. I am MeltMind. Ask me about the menu, ingredients, flavours, ordering, or tell me the kind of dessert moment you want to create.",
"hey": "Hey, welcome to MeltRoom. Tell me what you are curious about, or describe your craving and I will help you find the right direction.",
"good morning": "Good morning. Welcome to MeltRoom. I can help you understand the menu, compare desserts, or design a Melt for your mood, group, and budget.",
"good afternoon": "Good afternoon. Welcome to MeltRoom. Ask me anything about our desserts, ingredients, ordering, or your next perfect Melt.",
"good evening": "Good evening. Welcome to MeltRoom. I can help with dessert questions, recommendations, and complete Melt planning.",
"thanks": "You are welcome. Your preferences and MeltBasket will stay connected whenever you are ready to continue.",
"thank you": "You are welcome. Your preferences and MeltBasket will stay connected whenever you are ready to continue.",
"bye": "See you soon. MeltMind will be here when the next craving arrives.",
"goodbye": "See you soon. MeltMind will be here when the next craving arrives.",
"what can you do": "I can explain MeltRoom items and ingredients, compare flavours and textures, answer ordering questions, recommend desserts when you ask, and design complete combinations for a group, occasion, or budget.",
"help": "Ask me about any MeltRoom item, ingredient, flavour, texture, serving size, ordering policy, or budget. You can also describe a craving and I will help design your Perfect Melt.",
}
answer = responses.get(normalized)
if not answer:
return None
return {
"answer": answer,
"products": [],
"follow_ups": [],
"handoff": False,
"preferences": self.extract_preferences(message),
"source": "Instant MeltMind concierge",
"fast_path": True,
}
def deterministic_chat(self, message: str, history: list | None = None) -> dict:
fast_response = self.fast_conversation_response(message)
if fast_response:
return fast_response
lower = message.lower()
recommendation_intent = any(value in lower for value in (
"recommend", "suggest", "what should", "what can", "which dessert",
"which item", "best option", "best item", "show me", "i want",
"i need", "craving", "within budget", "under ₹", "under rs",
"order for", "something chocolate", "something chocolaty",
"something sweet", "something crunchy", "something gooey",
"something warm", "something melty",
)) or any(value in message for value in ("రికమెండ్", "సజెస్ట్", "లోపు", "కావాలి", "ఏదైనా"))
product_detail_intent = any(value in lower for value in (
"tell me", "explain", "compare", "difference", "share", "serve",
"price", "cost", "how much", "texture", "taste",
))
exact_product_mentioned = any(item["name"].lower() in lower for item in self.products.values())
comparison_intent = any(value in lower for value in ("compare", "difference", "versus", " vs ", "which"))
special = self.special_policy_answer(message)
if special:
return special
handoff = any(value in lower for value in ("severe allergy", "refund", "return", "payment issue", "damaged", "custom cake", "birthday cake"))
if handoff:
return {
"answer": self.policies["chatbot_boundaries"]["human_handoff_message"],
"products": [],
"follow_ups": ["Open WhatsApp support"],
"handoff": True,
"preferences": self.extract_preferences(message),
"source": "Verified MeltRoom policy",
}
preferences = self.extract_preferences(message)
has_group_request = preferences["group_size"] > 1 or any(
value in lower for value in ("friends", "people", "persons", "members", "guests", "group", "party")
)
has_budget_request = bool(
re.search(r"(?:₹|rs\.?|inr)\s*\d{2,5}", lower)
or re.search(r"\b(?:under|within|budget|up\s+to|max(?:imum)?)\b[^0-9]{0,20}\d{2,5}", lower)
or re.search(r"\d{2,5}\s*(?:rupees?|rs\.?|inr)\b", lower)
)
planning_request = recommendation_intent or any(
value in lower for value in ("plan", "order", "combination", "combo", "for us", "sharing")
)
if has_budget_request and planning_request:
audience = (
f" for {preferences['group_size']} people"
if has_group_request
else ""
)
return {
"answer": (
"Perfect Melt is the fastest place to build this complete order. "
f"I have carried your craving{audience} and ₹{preferences['budget_inr']} budget into it."
),
"products": [],
"follow_ups": [],
"handoff": False,
"preferences": preferences,
"source": "Instant Perfect Melt handoff",
"fast_path": True,
"route_to_designer": True,
}
if "severe" in lower and ("nut" in lower or "pistachio" in lower):
return {
"answer": "Pistachio products contain tree nuts and are not suitable for a severe nut allergy. Cross-contact cannot be ruled out, so please confirm the complete order directly with MeltRoom before ordering.",
"products": [],
"follow_ups": ["Contact MeltRoom about allergy requirements"],
"handoff": True,
"preferences": self.extract_preferences(message),
"source": "Verified allergen policy",
}
if recommendation_intent and preferences["less_sweet"] and "chocolate" in preferences["flavours"]:
ranked = sorted(
[item for item in self.products.values() if item["available"]],
key=lambda item: self.score_product(item, preferences),
reverse=True,
)[:3]
names = ", ".join(item["name"] for item in ranked)
return {
"answer": f"MeltRoom's menu is indulgent and has no verified low-sweetness item. For a more balanced chocolate direction, the closest current options are {names}; they are still desserts, not low-sugar products.",
"products": [self.public_product(item) for item in ranked],
"follow_ups": ["Compare these chocolate directions", "Design a complete order"],
"handoff": False,
"preferences": preferences,
"source": "Verified sensory profiles and live menu",
}
if recommendation_intent and (preferences["textures"] or preferences["warm"]):
ranked = sorted(
[item for item in self.products.values() if item["available"]],
key=lambda item: self.score_product(item, preferences),
reverse=True,
)[:3]
first = ranked[0]
sensory = first["knowledge"].get("sensory_profile", {})
texture_text = ", ".join(sensory.get("textures", [])[:3]) or ", ".join(first["textures"][:3])
alternatives = ", ".join(item["name"] for item in ranked[1:])
direction = "warm and melty" if preferences["warm"] and "gooey" in preferences["textures"] else "/".join(preferences["textures"]) or "warm"
answer = (
f"For a {direction} craving, {first['name']} is the closest match. "
f"{first['short']} Its verified texture direction is {texture_text}, and it is best enjoyed warm. "
f"For nearby alternatives, explore {alternatives}."
)
return {
"answer": answer,
"products": [self.public_product(item) for item in ranked],
"follow_ups": ["Compare these warm directions", "Design a complete order"],
"handoff": False,
"preferences": preferences,
"source": "Verified serving and sensory profiles",
}
products = self.find_products(message)
if products and product_detail_intent and (exact_product_mentioned or comparison_intent or recommendation_intent):
descriptions = []
for index, item in enumerate(products, start=1):
sensory = item["knowledge"].get("sensory_profile", {})
identity = item["knowledge"].get("identity", {})
texture = ", ".join(sensory.get("textures", [])[:3])
explanation = (identity.get("plain_explanation") or item["short"]).strip()
explanation = explanation[:1].lower() + explanation[1:] if explanation else "described in the live menu"
descriptions.append(
f"{index}. {item['name']}{explanation} Its texture direction is {texture or 'described in the live menu'}. "
f"It is listed for {item['serves_text']} at ₹{item['price']}."
)
answer = "Here is a verified side-by-side view of the relevant MeltRoom options:\n" + "\n".join(descriptions)
if any("tree_nuts" in item["allergens"] for item in products):
answer += "\nThe pistachio-led options contain tree nuts."
return {
"answer": answer,
"products": [self.public_product(item) for item in products],
"follow_ups": ["Compare another direction", "Design a complete order"],
"handoff": False,
"preferences": self.extract_preferences(message),
"source": "Verified menu and product knowledge",
"needs_llm": comparison_intent,
}
best_faq = max(
self.faqs,
key=lambda faq: len(tokens(message) & tokens(" ".join([faq["question"], *faq["alternate_questions"]]))),
)
faq_overlap = len(tokens(message) & tokens(" ".join([best_faq["question"], *best_faq["alternate_questions"]])))
if faq_overlap >= 2 and not (recommendation_intent and (preferences["textures"] or preferences["flavours"] or preferences["warm"])):
related = [self.products[item_id] for item_id in best_faq["related_product_ids"] if item_id in self.products]
return {
"answer": best_faq["approved_answer"],
"products": [self.public_product(item) for item in related[:3]] if recommendation_intent else [],
"follow_ups": best_faq["follow_up_suggestions"][:2],
"handoff": best_faq["requires_human_handoff"],
"preferences": self.extract_preferences(message),
"source": "Verified MeltRoom FAQ",
}
if not recommendation_intent:
return {
"answer": "I can help with MeltRoom's menu, ingredients, flavours, textures, serving guidance, ordering process, and dessert planning. Ask naturally, and I will only suggest menu items when your question calls for recommendations.",
"products": [],
"follow_ups": ["Ask about an item", "Request a recommendation"],
"handoff": False,
"preferences": self.extract_preferences(message),
"source": "Verified MeltRoom knowledge",
}
recommendation = self.recommend(self.extract_preferences(message))
first = recommendation["plans"][0]
return {
"answer": first["summary"] + " I can move these preferences into Design Your Perfect Melt for a complete plan.",
"products": [self.public_product(self.products[item["id"]]) for item in first["items"]],
"follow_ups": ["Continue in Designer", "Make it cheaper", "Show a different flavour"],
"handoff": False,
"preferences": recommendation["preferences"],
"source": "Deterministic MeltMind recommendation engine",
}
def chat(self, message: str, history: list | None = None) -> dict:
result = self.deterministic_chat(message, history)
result.pop("needs_llm", None)
if result.get("fast_path"):
result["model"] = {
**self.runtime_status(),
"used_for_response": False,
"grounded": True,
"fast_path": True,
"routing": (
"instant_perfect_melt_handoff"
if result.get("route_to_designer")
else "instant_social_conversation"
),
}
return result
exhaustive_comparison = (
len(result.get("products", [])) > 4
and any(value in message.lower() for value in ("all ", "every ", "entire ", "complete "))
and any(value in message.lower() for value in ("compare", "show", "list", "explain"))
)
if exhaustive_comparison:
result["source"] = f"{result['source']} · complete live-catalogue comparison"
result["model"] = {
**self.runtime_status(),
"used_for_response": False,
"grounded": True,
"fast_path": True,
"routing": "instant_exhaustive_catalogue_comparison",
}
return result
cache_key = json.dumps(
{"message": message.strip().lower(), "history": (history or [])[-4:]},
ensure_ascii=False,
sort_keys=True,
)
if cache_key in self.chat_cache:
cached = copy.deepcopy(self.chat_cache[cache_key])
cached["model"]["cached"] = True
return cached
selected = [self.products[item["id"]] for item in result["products"] if item["id"] in self.products]
answer, llm_product_ids, llm_follow_ups, used_llm = self.compose_chat_answer(
message,
result["answer"],
selected,
result["handoff"],
history,
)
result["answer"] = answer
if used_llm:
preferences = result["preferences"]
confirmed_allergens = set(preferences.get("allergen_ids", []))
allow_cards = bool(result["products"]) and not result["handoff"]
valid_ids = [
item_id for item_id in llm_product_ids
if allow_cards
and self.products[item_id]["available"]
and self.products[item_id]["price"] <= preferences.get("budget_inr", 500)
and not confirmed_allergens.intersection(self.products[item_id]["allergens"])
]
result["products"] = [self.public_product(self.products[item_id]) for item_id in valid_ids]
result["follow_ups"] = llm_follow_ups
result["source"] = (
"MiniCPM4-8B · full MeltRoom knowledge corpus · validated live facts"
if used_llm else f"{result['source']} · MiniCPM fallback"
)
result["model"] = {
**self.runtime_status(),
"used_for_response": used_llm,
"grounded": True,
"routing": "rag_minicpm_with_deterministic_validation",
}
if used_llm:
if len(self.chat_cache) >= 128:
self.chat_cache.pop(next(iter(self.chat_cache)))
self.chat_cache[cache_key] = copy.deepcopy(result)
return result
def design(self, preferences: dict) -> dict:
cache_key = json.dumps(preferences, ensure_ascii=False, sort_keys=True)
if cache_key in self.design_cache:
cached = copy.deepcopy(self.design_cache[cache_key])
cached["model"]["cached"] = True
return cached
result = self.recommend(preferences)
if not result["plans"]:
result["model"] = {**self.runtime_status(), "used_for_response": False, "grounded": True}
return result
result, used_llm = self.compose_plan_explanations(result)
result["model"] = {
**self.runtime_status(),
"used_for_response": used_llm,
"grounded": True,
"calculation_authority": "Deterministic MeltMind recommendation engine",
}
if used_llm:
if len(self.design_cache) >= 64:
self.design_cache.pop(next(iter(self.design_cache)))
self.design_cache[cache_key] = copy.deepcopy(result)
return result
@staticmethod
def public_product(item: dict) -> dict:
return {key: item[key] for key in ("id", "name", "category", "price", "image", "short", "serves_text")}