jashdoshi77 commited on
Commit
a267a62
Β·
1 Parent(s): c35deb5

added questionnaire

Browse files
Files changed (6) hide show
  1. README.md +18 -6
  2. backend/app.py +8 -12
  3. backend/graph.py +332 -197
  4. backend/prompts.py +122 -191
  5. frontend/app.js +1 -1
  6. frontend/index.html +12 -16
README.md CHANGED
@@ -7,12 +7,24 @@ sdk: docker
7
  pinned: false
8
  ---
9
 
10
- # AI Consultant Chatbot
11
 
12
- A domain-agnostic consulting chatbot powered by LangGraph + Groq (Llama 3.3 70B).
13
 
14
  ## Features
15
- - Multi-turn diagnostic conversations
16
- - 5-phase consulting flow (Discovery β†’ Exploration β†’ Constraints β†’ Solution β†’ Refinement)
17
- - Detailed, actionable final recommendations
18
- - Session management with SQLite persistence
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  pinned: false
8
  ---
9
 
10
+ # Stacklogix Consultant Chatbot
11
 
12
+ A consultant-style chatbot for **Stacklogix** that gathers client requirements and explains how Stacklogix will address their needs. Powered by LangGraph + Groq (Llama 3.3 70B). Designed to be embedded on the Stacklogix website where clients interact with it instead of a sales representative.
13
 
14
  ## Features
15
+
16
+ - **Dual mode**: Handles both **clear requests** (e.g. build a chatbot, build a dashboard) and **problems or vague situations** (e.g. inventory moving slowly, issues with an existing project).
17
+ - **Structured requirement collection**: Asks relevant questions (e.g. type of deliverable, data and format, behavior, look and feel, constraints, expectations) in a natural, consultant-like flow.
18
+ - **Consultant-style explanations**: Explains how the problem will be addressed, high-level implementation or solution steps, and what the company will do to deliver or resolve the issue (and, for problems, prevent recurrence).
19
+ - **Domain-agnostic**: Works across technology, operations, business processes, inventory, and any other domain.
20
+ - **5-phase flow**: Discovery β†’ Exploration β†’ Constraints β†’ Solution β†’ Refinement, with session management and SQLite persistence.
21
+
22
+ ## Flow
23
+
24
+ 1. **Discovery** β€” Understand what the client wants or what is going wrong.
25
+ 2. **Exploration** β€” Gather requirements: data, format, behavior, look, constraints, success criteria.
26
+ 3. **Constraints** β€” Confirm understanding and explain the proposed approach.
27
+ 4. **Solution** β€” Summarize needs, how you will address it, steps, and what you will deliver.
28
+ 5. **Refinement** β€” Full proposal, implementation steps, and next steps.
29
+
30
+ Clients can start with a clear request (β€œwe want to build X”) or a problem (β€œY isn’t working”); the chatbot adapts its questions and explanations accordingly.
backend/app.py CHANGED
@@ -40,15 +40,11 @@ from graph import run_consultation
40
  # ---------------------------------------------------------------------------
41
 
42
  GREETING_MESSAGE = (
43
- "**Welcome! I'm your AI consultant.** πŸ‘‹\n\n"
44
- "I help people think through complex problems β€” whether it's "
45
- "**business strategy, product design, technical architecture, operations, "
46
- "or anything else.**\n\n"
47
- "Here's how our conversation will work:\n"
48
- "- You describe your challenge or goal\n"
49
- "- I'll ask smart, targeted questions β€” and share insights along the way\n"
50
- "- Together, we'll converge on a clear, actionable solution\n\n"
51
- "**So β€” what's on your mind? What problem are you trying to solve?**"
52
  )
53
 
54
 
@@ -63,8 +59,8 @@ async def lifespan(app: FastAPI):
63
 
64
 
65
  app = FastAPI(
66
- title="Consultant Chatbot API",
67
- description="Domain-agnostic consulting chatbot powered by LangGraph + Groq",
68
  version="1.0.0",
69
  lifespan=lifespan,
70
  )
@@ -155,7 +151,7 @@ async def remove_session(session_id: str):
155
 
156
  @app.get("/health")
157
  async def health():
158
- return {"status": "healthy", "service": "consultant-chatbot"}
159
 
160
 
161
  # ---------------------------------------------------------------------------
 
40
  # ---------------------------------------------------------------------------
41
 
42
  GREETING_MESSAGE = (
43
+ "**Welcome to Stacklogix.** I'm your consultant for this session. πŸ‘‹\n\n"
44
+ "Whether you have a **clear request** (e.g. build a product or fix an issue) or a **problem** "
45
+ "(e.g. something isn't working as you'd like), I'll ask a few questions to understand your needs. "
46
+ "Our team will then follow up with you.\n\n"
47
+ "**What would you like to achieve or what do you need help with?**"
 
 
 
 
48
  )
49
 
50
 
 
59
 
60
 
61
  app = FastAPI(
62
+ title="Stacklogix Consultant Chatbot API",
63
+ description="Stacklogix requirement-collection chatbot β€” gathers client requirements and explains how Stacklogix will address them. LangGraph + Groq.",
64
  version="1.0.0",
65
  lifespan=lifespan,
66
  )
 
151
 
152
  @app.get("/health")
153
  async def health():
154
+ return {"status": "healthy", "service": "stacklogix-chatbot"}
155
 
156
 
157
  # ---------------------------------------------------------------------------
backend/graph.py CHANGED
@@ -1,14 +1,14 @@
1
- """LangGraph-based consultation state machine.
2
 
3
- Implements a 5-phase consulting flow controlled by a confidence score.
 
4
 
5
  CODE-LEVEL QUALITY ENFORCEMENT:
6
- - Simplified question category tracker (3 categories instead of 6)
7
- - Anti-repetition engine: detects sentence overlap and forces re-generation
8
- - Anti-hallucination check: verifies claims against conversation history
9
- - Deferral counter: tracks "I don't know" responses and switches to advice mode
10
- - Turn-aware instructions: adjusts behavior based on conversation stage
11
- - Understanding validation: ensures the LLM doesn't fabricate facts
12
  """
13
 
14
  from __future__ import annotations
@@ -22,7 +22,28 @@ from langchain_core.messages import HumanMessage, SystemMessage
22
 
23
  from llm import llm
24
  from models import Message, Phase, SessionState, PHASE_ORDER, PHASE_THRESHOLDS
25
- from prompts import PHASE_PROMPTS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
 
28
  # ---------------------------------------------------------------------------
@@ -39,79 +60,20 @@ class GraphState(TypedDict):
39
 
40
 
41
  # ---------------------------------------------------------------------------
42
- # Question Category Tracking System (SIMPLIFIED)
43
  # ---------------------------------------------------------------------------
44
 
45
- # Only 3 broad categories β€” enough to guide without forcing deep dives
46
- QUESTION_CATEGORIES = {
47
- "business_basics": {
48
- "label": "Business Basics",
49
- "description": "What they do, what they sell/offer, rough scale",
50
- "keywords": ["sell", "product", "service", "business", "store", "shop",
51
- "company", "revenue", "team", "employee", "people",
52
- "industry", "offer", "work", "startup", "project"],
53
- "prompt": "You could ask about their business basics β€” what they do, how big the operation is.",
54
- },
55
- "problem_details": {
56
- "label": "Problem Details",
57
- "description": "What exactly is going wrong, since when, how bad",
58
- "keywords": ["problem", "issue", "challenge", "slow", "decline",
59
- "month", "year", "since", "affect", "impact",
60
- "drop", "worse", "bad", "struggle", "difficult"],
61
- "prompt": "You could ask more about the problem β€” what exactly is happening, how long, how bad.",
62
- },
63
- "data_and_attempts": {
64
- "label": "Data & Past Attempts",
65
- "description": "Where their data lives and what they've tried already",
66
- "keywords": ["data", "excel", "spreadsheet", "track", "record",
67
- "tried", "attempt", "before", "already", "didn't work",
68
- "keep", "stored", "written", "noted", "logged"],
69
- "prompt": "IMPORTANT: You haven't asked where they keep their data yet. Ask how they track or record their business info (Excel, notebook, software, etc.).",
70
- },
71
- }
72
-
73
-
74
- def _detect_covered_categories(messages: list[Message]) -> dict[str, bool]:
75
- """Analyze conversation to determine which question categories have been covered."""
76
- covered = {cat: False for cat in QUESTION_CATEGORIES}
77
-
78
- all_text = " ".join(msg.content.lower() for msg in messages)
79
-
80
- for cat_id, cat_info in QUESTION_CATEGORIES.items():
81
- keyword_hits = sum(1 for kw in cat_info["keywords"] if kw in all_text)
82
- # Need at least 2 keyword hits to consider a category covered
83
- if keyword_hits >= 2:
84
- covered[cat_id] = True
85
-
86
- return covered
87
-
88
-
89
- def _build_category_guidance(covered: dict[str, bool], turn_number: int) -> str:
90
- """Build a SOFT guidance block β€” suggestions, not demands."""
91
- uncovered = [
92
- QUESTION_CATEGORIES[cat]["prompt"]
93
- for cat, is_covered in covered.items()
94
- if not is_covered
95
- ]
96
-
97
- if not uncovered:
98
- return "\nYou've gathered enough info. Focus on giving RECOMMENDATIONS now.\n"
99
-
100
- # Soft suggestion β€” max 1 at a time to avoid overwhelming
101
- guidance = "\nπŸ’‘ Suggestion β€” if relevant, you might want to ask about:\n"
102
- guidance += f" β†’ {uncovered[0]}\n"
103
-
104
- covered_list = [
105
- QUESTION_CATEGORIES[cat]["label"]
106
- for cat, is_covered in covered.items()
107
- if is_covered
108
- ]
109
- if covered_list:
110
- guidance += f"\n🚫 ALREADY COVERED (do NOT ask about these again): {', '.join(covered_list)}\n"
111
-
112
- guidance += "\nBut ONLY ask if it naturally follows from what the client just said. Do NOT force it.\n"
113
-
114
- return guidance
115
 
116
 
117
  # ---------------------------------------------------------------------------
@@ -184,8 +146,6 @@ def _has_asked_about_data(messages: list[Message]) -> bool:
184
  for msg in messages:
185
  if msg.role == "assistant" and "?" in msg.content:
186
  content_lower = msg.content.lower()
187
- # Check each question sentence specifically
188
- # Split on ? to get question fragments
189
  question_parts = content_lower.split("?")
190
  for part in question_parts:
191
  for pattern in DATA_STORAGE_QUESTION_PATTERNS:
@@ -194,6 +154,39 @@ def _has_asked_about_data(messages: list[Message]) -> bool:
194
  return False
195
 
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  def _check_repetition(new_reply: str, previous_replies: list[str]) -> float:
198
  """Check sentence-level overlap with previous replies. Returns 0.0-1.0."""
199
  if not previous_replies:
@@ -235,39 +228,37 @@ def _check_repetition(new_reply: str, previous_replies: list[str]) -> float:
235
  # Reply quality enforcement (code-level, can't be bypassed by LLM)
236
  # ---------------------------------------------------------------------------
237
 
238
- # Patterns that indicate the bot is asking the client to solve their own problem
239
- SELF_SOLVING_PATTERNS = [
240
- r"what do you think is (?:the |)(?:main |primary |root |)(?:reason|cause|issue)",
241
- r"what do you (?:think|believe|feel) (?:is |might be |could be |)",
242
- r"how do you think .+ could help",
243
- r"what would you (?:like|want|suggest|recommend)",
244
- r"do you have any (?:idea|thoughts|theories) (?:on |about |why )",
245
- r"why do you think",
246
- r"what do you attribute .+ to",
247
- r"have you (?:considered|thought about) (?:the role of|how|why|whether)",
248
- r"have you (?:considered|thought about) (?:offering|changing|trying|implementing)",
249
- # Catch ANY question with "you think" in it
250
- r"\?.*you think",
251
- r"you think.+\?",
252
- r"(?:reason|cause)\s+you\s+think",
253
- r"what role (?:do you think |does |)",
254
- # "What are your thoughts/take/opinion on" patterns
255
- r"what (?:are |)your (?:thoughts|take|opinion) on",
256
- r"your thoughts on .+\?",
257
  ]
258
 
259
- # Patterns that suggest causes disguised as questions
260
- CAUSE_SUGGESTING_PATTERNS = [
261
- r"have you noticed any changes in .+(?:demand|market|trend|preference|competition)",
262
- r"could (?:it|this) be (?:related to|because of|due to)",
263
- r"do you think .+(?:might be|could be) (?:causing|contributing|leading)",
264
- r"is it possible that .+(?:demand|pricing|competition|market)",
265
- r"(?:the role of|due to) (?:pricing|marketing|product presentation|competition)",
266
- r"(?:external factors|market trends|consumer behavior).+(?:impact|affect|contribut)",
267
- r"(?:changes in|shifts in) (?:consumer|customer|market|buying) (?:behavior|preference|demand|pattern)",
268
- # Listing generic causes
269
- r"(?:such as|like|including) (?:low |high |ineffective |poor ).*(?:demand|pric|market|competit)",
270
- r"(?:due to|because of) (?:various|several|multiple) factors",
 
 
 
 
 
 
271
  ]
272
 
273
  # Filler SENTENCES that should be stripped from ANYWHERE in the response
@@ -279,16 +270,15 @@ FILLER_SENTENCES = [
279
  # "I'd love to" filler
280
  r"i'd love to (?:learn|help|explore|understand|hear|know|dig)",
281
  r"i'd like to (?:learn|help|explore|understand|hear|know|dig)",
282
- # Restating the problem robotically
283
- r"a slow[- ]moving inventory (?:of|can be)",
284
  r"(?:can be|is) a (?:challenging|significant|difficult|tough|particularly challenging|common) (?:issue|problem|situation|challenge)",
285
  r"(?:this|it) can be a (?:common|challenging|significant) (?:issue|problem|situation)",
286
- r"especially (?:given|with) the (?:high value|constantly changing|luxury|competitive)",
287
- r"especially given the high value and luxury nature",
288
- r"(?:diamond|jewelry) (?:rings? (?:are|is)|market can be) (?:often |)(?:purchased|subject to|a high)",
289
- r"are often purchased for special occasions",
290
- r"the quality and characteristics of the diamonds",
291
- r"the diamond (?:market|ring market|industry) can be",
292
  r"it's likely that this is affecting",
293
  r"this (?:can|may|might) (?:be|also be) (?:affecting|impacting|taking)",
294
  # Robotic transitions
@@ -308,6 +298,11 @@ FILLER_SENTENCES = [
308
  r"given the (?:specifics|broad range|unique aspects|diverse nature|complexity)",
309
  r"to better understand (?:the scope|your situation|the issue|your)",
310
  r"this (?:is a good starting point|indicates you have)",
 
 
 
 
 
311
  r"i'm excited to learn more",
312
  r"i'm looking forward to",
313
  r"understanding (?:your|the) specific",
@@ -336,6 +331,14 @@ FILLER_SENTENCES = [
336
  r"it(?:'s| is) great that you(?:'ve| have)",
337
  # "You've just started our conversation"
338
  r"you(?:'ve| have) just started",
 
 
 
 
 
 
 
 
339
  ]
340
 
341
  # Section headers that create robotic formatting
@@ -347,31 +350,28 @@ RECAP_HEADERS = [
347
 
348
 
349
  def _detect_bad_question_patterns(reply: str) -> str | None:
350
- """Detect self-solving and cause-suggesting questions.
351
 
352
  Returns a correction instruction if bad patterns found, None otherwise.
353
  """
354
  reply_lower = reply.lower()
355
 
356
- for pattern in SELF_SOLVING_PATTERNS:
 
357
  if re.search(pattern, reply_lower):
358
  return (
359
- "⚠️ You asked the client to diagnose their own problem. "
360
- "YOU are the consultant β€” YOU figure out the cause. "
361
- "Instead, ask a SPECIFIC diagnostic question that helps you "
362
- "narrow down the cause yourself. For example, ask about "
363
- "specifics of the problem, not what they think is causing it."
364
  )
365
-
366
- for pattern in CAUSE_SUGGESTING_PATTERNS:
367
  if re.search(pattern, reply_lower):
368
  return (
369
- "⚠️ You suggested possible causes IN your question. "
370
- "Don't lead the client toward causes you've assumed. "
371
- "Ask a NEUTRAL question that lets them describe their "
372
- "situation without your bias."
373
  )
374
-
375
  return None
376
 
377
 
@@ -469,6 +469,85 @@ def _break_wall_of_text(reply: str) -> str:
469
  return "\n\n".join(result)
470
 
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  def _format_questions(reply: str) -> str:
473
  """Ensure each question is on its own paragraph with blank lines around it.
474
 
@@ -618,12 +697,17 @@ def _build_message_list(state: SessionState, user_message: str) -> list:
618
  # Keep the conversation history but mark the new topic
619
  state.understanding = state.understanding + f"\n\n--- NEW TOPIC: {user_message} ---\n" if state.understanding else ""
620
 
621
- # --- HARD PHASE FORCING β€” guarantee we reach recommendations ---
622
- # Use RELATIVE turn count (turns since current topic started)
623
  topic_turns = turn_number - state.topic_start_turn
624
  current_phase = state.phase
625
  current_confidence = state.confidence
626
- if topic_turns >= 12 and current_phase != Phase.REFINEMENT:
 
 
 
 
 
 
627
  current_phase = Phase.REFINEMENT
628
  current_confidence = max(current_confidence, 0.85)
629
  state.phase = current_phase
@@ -640,56 +724,66 @@ def _build_message_list(state: SessionState, user_message: str) -> list:
640
  phase_prompt = TURN_1_PROMPT
641
  else:
642
  phase_prompt = PHASE_PROMPTS[current_phase].format(confidence=current_confidence)
 
 
 
 
 
 
643
 
644
  # Add understanding context (validated)
645
  if state.understanding:
646
  phase_prompt += (
647
- f"\n\nCLIENT INFO (for YOUR reference only β€” do NOT summarize or repeat this back to the client):"
648
  f"\n{state.understanding}\n"
649
- f"IMPORTANT: Do NOT start your response by restating what you know about the client. "
650
- f"Jump STRAIGHT into new insights, analysis, or questions.\n"
651
  )
 
 
 
652
 
653
  # --- CONVERSATION STATS ---
654
  stats = f"\nTurn {turn_number} | {questions_asked} questions asked so far | {deferral_count} deferrals\n"
 
 
 
655
  phase_prompt += stats
656
 
657
- # --- HARD DATA QUESTION ENFORCEMENT ---
658
- # If the bot hasn't asked about data storage by turn 2, force it
659
- if turn_number >= 3 and not _has_asked_about_data(state.messages):
 
 
 
 
 
 
660
  phase_prompt += """
661
- ⚠️ MANDATORY: You have NOT yet asked where the client keeps their business data.
662
- ONE of your questions MUST be about how they track or record their business
663
- information. For example: "How do you keep track of all this β€” in a spreadsheet,
664
- a notebook, or some software?" This is critical for your recommendations.
665
- """
666
- elif turn_number > 1:
667
- covered = _detect_covered_categories(state.messages)
668
- category_guidance = _build_category_guidance(covered, turn_number)
669
- phase_prompt += category_guidance
670
 
671
  # --- DYNAMIC BEHAVIORAL OVERRIDES ---
672
  if deferral_count >= 2:
673
  phase_prompt += """
674
- ⚠️ THE USER SAID "I DON'T KNOW" MULTIPLE TIMES. They cannot answer more questions.
675
- SWITCH TO ADVICE MODE IMMEDIATELY:
676
- - STOP asking diagnostic questions. Give at most 1 simple yes/no question.
677
- - LEAD with YOUR specific recommendations: "Based on what you've told me, here's what I'd suggest..."
678
- - Make reasonable assumptions and state them.
679
- - Be SPECIFIC: name tools, methods, steps. No generic advice like "review your pricing".
680
- - You are the EXPERT β€” the client is paying YOU to figure it out.
681
  """
682
 
683
  # Question count limits only apply during diagnostic phases
684
  # In solution/final phases, the prompt itself handles question limits
685
  if state.phase in (Phase.DISCOVERY, Phase.EXPLORATION):
686
  if questions_asked >= 20:
687
- phase_prompt += "\n⚠️ STOP ASKING. Give your recommendation NOW. Zero questions.\n"
688
  elif questions_asked >= 14:
689
- phase_prompt += "\nYou've asked enough questions. Shift to mostly advice, at most 1 question.\n"
690
 
691
  if turn_number >= 8:
692
- phase_prompt += "\n⚠️ Turn 8+. Give CONCRETE recommendations now. Reference details they shared.\n"
693
 
694
  messages = [SystemMessage(content=phase_prompt)]
695
 
@@ -794,6 +888,38 @@ def _determine_phase(confidence: float) -> Phase:
794
  return Phase.DISCOVERY
795
 
796
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
  # ---------------------------------------------------------------------------
798
  # Main graph execution
799
  # ---------------------------------------------------------------------------
@@ -801,6 +927,19 @@ def _determine_phase(confidence: float) -> Phase:
801
  async def run_consultation(session_state: SessionState, user_message: str) -> GraphState:
802
  """Run one turn of the consultation with quality enforcement."""
803
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  # 1. Build messages with intelligence injection
805
  messages = _build_message_list(session_state, user_message)
806
  turn_number = _get_turn_number(session_state.messages)
@@ -809,17 +948,17 @@ async def run_consultation(session_state: SessionState, user_message: str) -> Gr
809
  response = await llm.ainvoke(messages)
810
  parsed = _parse_llm_output(response.content)
811
 
812
- # 3. Anti-repetition check
813
  previous_replies = _get_previous_bot_responses(session_state.messages)
814
  overlap = _check_repetition(parsed["reply"], previous_replies)
 
815
 
816
- if overlap > 0.35 and previous_replies:
817
  anti_repeat_msg = SystemMessage(content=(
818
- "⚠️ YOUR RESPONSE IS TOO SIMILAR TO A PREVIOUS ONE. The user will see "
819
- "repeated content. Write something COMPLETELY DIFFERENT:\n"
820
- "- Different opening, different observations, different questions\n"
821
- "- Share a NEW insight you haven't mentioned\n"
822
- "- Ask about a DIFFERENT topic entirely"
823
  ))
824
  messages.insert(-1, anti_repeat_msg)
825
  response = await llm.ainvoke(messages)
@@ -865,36 +1004,21 @@ async def run_consultation(session_state: SessionState, user_message: str) -> Gr
865
  non_q_lines = [line for line in reply_text.split("\n") if "?" not in line]
866
  non_q_content = "\n".join(non_q_lines).strip()
867
 
868
- # SOLUTION/REFINEMENT phases need MUCH longer responses (detailed action plans)
869
  is_final_phase = session_state.phase in (Phase.SOLUTION, Phase.REFINEMENT)
870
- min_length = 500 if is_final_phase else 200
871
 
872
  if len(non_q_content) < min_length:
873
  if is_final_phase:
874
  expand_msg = SystemMessage(content=(
875
- "⚠️ Your response is FAR TOO SHORT for a final recommendation.\n"
876
- "The client has been answering your questions β€” now DELIVER VALUE.\n"
877
- "Your response MUST include ALL of these:\n"
878
- "1. **Problem Summary** β€” 2-3 sentences about their specific situation\n"
879
- "2. **Root Cause Analysis** β€” what you believe is causing the problem\n"
880
- "3. **Step-by-Step Action Plan** with NUMBERED steps organized by timeline:\n"
881
- " - **Immediate (This Week):** 2-3 quick wins\n"
882
- " - **Short-Term (Next 2-4 Weeks):** 3-4 tactical changes\n"
883
- " - **Medium-Term (1-3 Months):** 2-3 strategic initiatives\n"
884
- "4. **Expected Outcomes** β€” what results they can expect\n"
885
- "Each step must name SPECIFIC tools, methods, platforms. NOT vague advice.\n"
886
- "This response should be LONG and DETAILED β€” at least 300 words."
887
  ))
888
  else:
889
  expand_msg = SystemMessage(content=(
890
- "⚠️ Your response is TOO SHORT. The client is paying for expert analysis.\n"
891
- "EXPAND your response to include:\n"
892
- "- 1-2 opening sentences reacting to what they said\n"
893
- "- A **bold header** like '**Key Observations**' followed by 3-4 bullet points\n"
894
- " with specific insights, industry knowledge, or patterns you've noticed\n"
895
- "- 1-2 sentences of concrete recommendation or context\n"
896
- "- THEN your question(s)\n"
897
- "Total response before questions should be at least 5-8 sentences."
898
  ))
899
  messages.insert(-1, expand_msg)
900
  response = await llm.ainvoke(messages)
@@ -912,10 +1036,16 @@ async def run_consultation(session_state: SessionState, user_message: str) -> Gr
912
  # 3.9. Strip leading filler sentences (no LLM call)
913
  parsed["reply"] = _strip_filler_sentences(parsed["reply"])
914
 
915
- # 3.10. Break wall-of-text responses into readable paragraphs
 
 
 
 
 
 
916
  parsed["reply"] = _break_wall_of_text(parsed["reply"])
917
 
918
- # 3.11. Ensure questions are properly formatted for frontend box styling
919
  parsed["reply"] = _format_questions(parsed["reply"])
920
 
921
  # 4. Confidence progression
@@ -933,12 +1063,17 @@ async def run_consultation(session_state: SessionState, user_message: str) -> Gr
933
  # 5. Route phase
934
  new_phase = _determine_phase(new_confidence)
935
 
936
- # Phase forcing β€” use topic-relative turn count
937
  topic_turns = turn_number - session_state.topic_start_turn
938
- if topic_turns >= 12:
 
 
 
 
 
939
  new_phase = Phase.REFINEMENT
940
  new_confidence = max(new_confidence, 0.85)
941
- elif topic_turns >= 10 and new_phase in (Phase.DISCOVERY, Phase.EXPLORATION):
942
  new_phase = Phase.SOLUTION
943
  new_confidence = max(new_confidence, 0.65)
944
  # 6. Update understanding (with validation)
 
1
+ """LangGraph-based requirement-collection consultation state machine.
2
 
3
+ Implements a 5-phase flow (Discovery β†’ Exploration β†’ Constraints β†’ Solution β†’ Refinement)
4
+ controlled by confidence. Supports both clear requests (e.g. build X) and problems (e.g. Y is wrong).
5
 
6
  CODE-LEVEL QUALITY ENFORCEMENT:
7
+ - Requirement-oriented question categories (scope, what they have, behavior/look, success)
8
+ - Anti-repetition: sentence overlap detection and re-generation
9
+ - Anti-hallucination: verifies claims against conversation history
10
+ - Deferral counter: "I don't know" β†’ switch to proposal mode
11
+ - Turn-aware instructions and understanding validation
 
12
  """
13
 
14
  from __future__ import annotations
 
22
 
23
  from llm import llm
24
  from models import Message, Phase, SessionState, PHASE_ORDER, PHASE_THRESHOLDS
25
+ from prompts import PHASE_PROMPTS, QUESTIONNAIRE_SECTIONS, CURRENT_SECTION_PLACEHOLDER
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Questionnaire-driven section progression (same intent β†’ same question order)
30
+ # ---------------------------------------------------------------------------
31
+
32
+ def _current_section_index(questions_asked: int) -> int:
33
+ """Section index 0..10 from number of questions asked. ~2 questions per section."""
34
+ return min(questions_asked // 2, len(QUESTIONNAIRE_SECTIONS) - 1)
35
+
36
+
37
+ def _build_section_block(section_index: int) -> str:
38
+ """Build the 'Current section' block to inject into the prompt."""
39
+ num, title, intents = QUESTIONNAIRE_SECTIONS[section_index]
40
+ intents_str = "\n".join(f"- {i}" for i in intents)
41
+ return (
42
+ f"Current section: Section {num} β€” {title}\n"
43
+ f"Question intents to cover (adapt to domain and conversation):\n"
44
+ f"{intents_str}\n"
45
+ f"If the client has ALREADY answered these intents earlier in the conversation, skip this section or ask only what's missing, then move to the next section.\n"
46
+ )
47
 
48
 
49
  # ---------------------------------------------------------------------------
 
60
 
61
 
62
  # ---------------------------------------------------------------------------
63
+ # Wrap-up guidance (when to ask "Is there anything else?")
64
  # ---------------------------------------------------------------------------
65
 
66
+ def _build_wrap_up_guidance(questions_asked: int, messages: list[Message], section_index: int) -> str:
67
+ """Tell the bot when to keep gathering vs when to ask 'Is there anything else?'"""
68
+ if _can_wrap_up(questions_asked, messages):
69
+ return "\nYou've gathered enough info. Ask: 'Is there anything else you'd like to add?' Only that β€” no proposal or solution.\n"
70
+ section_num = section_index + 1
71
+ total_sections = len(QUESTIONNAIRE_SECTIONS)
72
+ return (
73
+ f"\nKeep gathering. You are on section {section_num} of {total_sections} (follow the questionnaire in order). "
74
+ f"Do NOT ask 'Is there anything else?' until you have gone through ALL 11 sections (through Section 11 - Open Ends) "
75
+ f"and asked at least {MIN_QUESTIONS_BEFORE_WRAP_UP} questions. You have asked {questions_asked} so far.\n"
76
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
 
79
  # ---------------------------------------------------------------------------
 
146
  for msg in messages:
147
  if msg.role == "assistant" and "?" in msg.content:
148
  content_lower = msg.content.lower()
 
 
149
  question_parts = content_lower.split("?")
150
  for part in question_parts:
151
  for pattern in DATA_STORAGE_QUESTION_PATTERNS:
 
154
  return False
155
 
156
 
157
+ # Require ALL 11 sections before wrap-up
158
+ MIN_QUESTIONS_BEFORE_WRAP_UP = 22 # ~2 per section so we reach Section 11
159
+ MIN_SECTION_BEFORE_WRAP_UP = 10 # Section 11 (Open Ends) β€” must complete all sections
160
+
161
+
162
+ def _can_wrap_up(questions_asked: int, messages: list[Message]) -> bool:
163
+ """True only after we've gone through ALL 11 questionnaire sections (no early exit)."""
164
+ section_index = _current_section_index(questions_asked)
165
+ # Must reach Section 11 (Open Ends, index 10) and ask at least 22 questions
166
+ return (
167
+ questions_asked >= MIN_QUESTIONS_BEFORE_WRAP_UP
168
+ and section_index >= MIN_SECTION_BEFORE_WRAP_UP
169
+ )
170
+
171
+
172
+ def _user_has_said_where_data_is(messages: list[Message]) -> bool:
173
+ """True if the user has already said where they keep their data (spreadsheet, database, etc.)."""
174
+ data_location_words = [
175
+ "spreadsheet", "database", "excel", "notebook", "paper", "software",
176
+ "app", "system", "cloud", "computer", "file", "files",
177
+ ]
178
+ for msg in messages:
179
+ if msg.role != "user":
180
+ continue
181
+ lower = msg.content.lower().strip()
182
+ # Short reply that looks like an answer (e.g. "spreadsheet", "database")
183
+ if len(lower) < 80 and any(w in lower for w in data_location_words):
184
+ return True
185
+ if any(w in lower for w in data_location_words) and ("keep" in lower or "store" in lower or "track" in lower or "use" in lower or "in " in lower):
186
+ return True
187
+ return False
188
+
189
+
190
  def _check_repetition(new_reply: str, previous_replies: list[str]) -> float:
191
  """Check sentence-level overlap with previous replies. Returns 0.0-1.0."""
192
  if not previous_replies:
 
228
  # Reply quality enforcement (code-level, can't be bypassed by LLM)
229
  # ---------------------------------------------------------------------------
230
 
231
+ # Budget / timeline β€” NEVER ask (policy)
232
+ BUDGET_TIMELINE_PATTERNS = [
233
+ r"budget",
234
+ r"timeline",
235
+ r"when (?:do you |would you |)need (?:it|this|the)",
236
+ r"deadline",
237
+ r"how (?:much |long |)(?:time|money)",
238
+ r"expected (?:timeline|date|duration)",
239
+ r"time (?:frame|line|scale)",
240
+ r"(?:within|in) (?:how many|what) (?:weeks|months|days)",
241
+ r"expectations? (?:for|about) (?:the )?timeline",
 
 
 
 
 
 
 
 
242
  ]
243
 
244
+ # Policy-only checks: budget/timeline and tech. All other behavior is guided by prompts and LLM context.
245
+ # Non-tech client: no "involvement", "development", "implementation", "progress updates", "system design"
246
+ TECH_PROCESS_PATTERNS = [
247
+ r"involved in (?:the |)(?:development|implementation)",
248
+ r"involvement in (?:development|implementation|design)",
249
+ r"progress updates?",
250
+ r"regular (?:meetings?|updates?)",
251
+ r"system design",
252
+ r"input on (?:system |)(?:design|implementation)",
253
+ r"project management (?:portal|tool)",
254
+ r"preferred method for (?:receiving|getting) (?:updates?|progress)",
255
+ r"receiving (?:progress )?updates?",
256
+ r"providing input on",
257
+ r"implementation (?:of the |)(?:system|solution)",
258
+ r"development and implementation",
259
+ r"first meeting to discuss (?:project|details|expectations)",
260
+ r"ready to proceed",
261
+ r"prioritize .*(?:goals?|objectives?)",
262
  ]
263
 
264
  # Filler SENTENCES that should be stripped from ANYWHERE in the response
 
270
  # "I'd love to" filler
271
  r"i'd love to (?:learn|help|explore|understand|hear|know|dig)",
272
  r"i'd like to (?:learn|help|explore|understand|hear|know|dig)",
273
+ # Restating the problem robotically (domain-agnostic)
 
274
  r"(?:can be|is) a (?:challenging|significant|difficult|tough|particularly challenging|common) (?:issue|problem|situation|challenge)",
275
  r"(?:this|it) can be a (?:common|challenging|significant) (?:issue|problem|situation)",
276
+ r"especially (?:given|with) the (?:high value|constantly changing|competitive)",
277
+ r"especially given the (?:high value|unique|complex) (?:nature|aspects)",
278
+ r"(?:market|industry) can be (?:often |)(?:subject to|a (?:high|competitive))",
279
+ r"are often (?:purchased|used|needed) for (?:special |)(?:occasions|reasons)",
280
+ r"the (?:quality and )?characteristics of the",
281
+ r"the (?:market|industry) can be",
282
  r"it's likely that this is affecting",
283
  r"this (?:can|may|might) (?:be|also be) (?:affecting|impacting|taking)",
284
  # Robotic transitions
 
298
  r"given the (?:specifics|broad range|unique aspects|diverse nature|complexity)",
299
  r"to better understand (?:the scope|your situation|the issue|your)",
300
  r"this (?:is a good starting point|indicates you have)",
301
+ r"it'?s possible that .+(?:could be|might be|is)",
302
+ r"(?:could be|might be) (?:impacting|affecting|contributing)",
303
+ r"(?:understanding|this information) (?:the specifics|helps us understand)",
304
+ r"can (?:help|provide) (?:identify|valuable insights)",
305
+ r"understanding the (?:current state|specifics) .+ (?:can provide|can help)",
306
  r"i'm excited to learn more",
307
  r"i'm looking forward to",
308
  r"understanding (?:your|the) specific",
 
331
  r"it(?:'s| is) great that you(?:'ve| have)",
332
  # "You've just started our conversation"
333
  r"you(?:'ve| have) just started",
334
+ # Solution pitching β€” requirement gathering only
335
+ r"our approach (?:at |)stacklogix (?:involves|would involve|will involve)",
336
+ r"stacklogix will (?:develop|implement|provide|deliver)",
337
+ r"how we will address (?:it|this)",
338
+ r"what we will deliver",
339
+ r"implementation (?:of a |)(?:robust |)(?:management |)system",
340
+ r"key components? of the solution",
341
+ r"we will (?:develop|implement|provide|schedule|work closely)",
342
  ]
343
 
344
  # Section headers that create robotic formatting
 
350
 
351
 
352
  def _detect_bad_question_patterns(reply: str) -> str | None:
353
+ """Detect self-solving, cause-suggesting, budget/timeline, or technical process questions.
354
 
355
  Returns a correction instruction if bad patterns found, None otherwise.
356
  """
357
  reply_lower = reply.lower()
358
 
359
+ # Only enforce hard policy via patterns; let the LLM handle the rest from conversation context.
360
+ for pattern in BUDGET_TIMELINE_PATTERNS:
361
  if re.search(pattern, reply_lower):
362
  return (
363
+ "⚠️ You must NOT ask about budget or timeline. "
364
+ "Remove any question about when they need it, deadlines, or cost. Ask something else from the questionnaire in plain words."
 
 
 
365
  )
366
+
367
+ for pattern in TECH_PROCESS_PATTERNS:
368
  if re.search(pattern, reply_lower):
369
  return (
370
+ "⚠️ The client is non-technical. Do NOT ask about involvement in development, "
371
+ "progress updates, system design, project management, or implementation. "
372
+ "Ask a simple, plain-language question from the current section."
 
373
  )
374
+
375
  return None
376
 
377
 
 
469
  return "\n\n".join(result)
470
 
471
 
472
+ def _strip_observation_blocks(reply: str) -> str:
473
+ """Remove 'Key Observations', 'Understanding the situation', and similar blocks with bullet lists.
474
+
475
+ Keep only short acknowledgment and questions. Skip until we see a question line.
476
+ """
477
+ lines = reply.split("\n")
478
+ result = []
479
+ in_observation_block = False
480
+
481
+ for line in lines:
482
+ stripped = line.strip()
483
+ # Start of an observation/recap block (bold header, no question)
484
+ if re.match(r"^\*\*(?:key observations?|understanding the situation|key points?|current (?:understanding|situation)|summary)\s*", stripped, re.IGNORECASE) and "?" not in stripped:
485
+ in_observation_block = True
486
+ continue
487
+ # While in block: skip bullets and any non-question line (transition sentences, etc.)
488
+ if in_observation_block:
489
+ if stripped.startswith("-") or stripped.startswith("*") or re.match(r"^[\d]+[.)]\s", stripped):
490
+ continue
491
+ if "?" not in stripped:
492
+ continue
493
+ in_observation_block = False
494
+ result.append(line)
495
+
496
+ text = "\n".join(result)
497
+ text = re.sub(r"\n{3,}", "\n\n", text)
498
+ return text.strip()
499
+
500
+
501
+ def _extract_question_sentences(text: str) -> list[str]:
502
+ """Get lines or sentence fragments that contain a question mark (normalized for comparison)."""
503
+ questions = []
504
+ for line in text.split("\n"):
505
+ part = line.strip().lower()
506
+ if "?" in part and len(part) > 15:
507
+ questions.append(part)
508
+ return questions
509
+
510
+
511
+ def _check_question_repetition(new_reply: str, previous_replies: list[str]) -> bool:
512
+ """True if the new reply asks something very similar to a previous reply's question."""
513
+ new_qs = _extract_question_sentences(new_reply)
514
+ if not new_qs:
515
+ return False
516
+ all_prev_qs = []
517
+ for prev in previous_replies:
518
+ all_prev_qs.extend(_extract_question_sentences(prev))
519
+ if not all_prev_qs:
520
+ return False
521
+ for nq in new_qs:
522
+ for pq in all_prev_qs:
523
+ if SequenceMatcher(None, nq, pq).ratio() > 0.55:
524
+ return True
525
+ return False
526
+
527
+
528
+ def _ensure_text_before_questions(reply: str) -> str:
529
+ """If the reply starts with a question (no preceding acknowledgment), prepend a short line."""
530
+ stripped = reply.strip()
531
+ if not stripped:
532
+ return reply
533
+ # Find first line that contains ?
534
+ lines = stripped.split("\n")
535
+ first_question_idx = None
536
+ for i, line in enumerate(lines):
537
+ if "?" in line and len(line.strip()) > 5:
538
+ first_question_idx = i
539
+ break
540
+ if first_question_idx is None:
541
+ return reply
542
+ # Is there any non-empty, non-question text before the first question?
543
+ text_before = "\n".join(lines[:first_question_idx]).strip()
544
+ text_before = re.sub(r"\*\*[^*]+\*\*", "", text_before).strip() # remove bold markers for length check
545
+ if len(text_before) < 50: # require at least ~2 sentences of context before first question
546
+ lines.insert(0, "Thanks for sharing that.")
547
+ return "\n".join(lines).strip()
548
+ return reply
549
+
550
+
551
  def _format_questions(reply: str) -> str:
552
  """Ensure each question is on its own paragraph with blank lines around it.
553
 
 
697
  # Keep the conversation history but mark the new topic
698
  state.understanding = state.understanding + f"\n\n--- NEW TOPIC: {user_message} ---\n" if state.understanding else ""
699
 
700
+ # --- HARD PHASE FORCING β€” require minimum 10 questions (or lots of client info) before wrap-up ---
 
701
  topic_turns = turn_number - state.topic_start_turn
702
  current_phase = state.phase
703
  current_confidence = state.confidence
704
+ if not _can_wrap_up(questions_asked, state.messages) and current_phase in (Phase.SOLUTION, Phase.REFINEMENT):
705
+ # Keep in EXPLORATION until we've asked at least 10 questions (or client gave lots of info)
706
+ current_phase = Phase.EXPLORATION
707
+ current_confidence = min(current_confidence, 0.5)
708
+ state.phase = current_phase
709
+ state.confidence = current_confidence
710
+ elif topic_turns >= 12 and current_phase != Phase.REFINEMENT:
711
  current_phase = Phase.REFINEMENT
712
  current_confidence = max(current_confidence, 0.85)
713
  state.phase = current_phase
 
724
  phase_prompt = TURN_1_PROMPT
725
  else:
726
  phase_prompt = PHASE_PROMPTS[current_phase].format(confidence=current_confidence)
727
+ # Inject current questionnaire section so the same intent leads to the same question order
728
+ section_index = _current_section_index(questions_asked)
729
+ if current_phase in (Phase.DISCOVERY, Phase.EXPLORATION, Phase.CONSTRAINTS):
730
+ phase_prompt = phase_prompt.replace(CURRENT_SECTION_PLACEHOLDER, _build_section_block(section_index))
731
+ else:
732
+ phase_prompt = phase_prompt.replace(CURRENT_SECTION_PLACEHOLDER, "(Wrapping up β€” ask if there is anything else.)")
733
 
734
  # Add understanding context (validated)
735
  if state.understanding:
736
  phase_prompt += (
737
+ f"\n\nCLIENT INFO (for YOUR reference only β€” do NOT copy this back to the client):"
738
  f"\n{state.understanding}\n"
739
+ f"Do NOT list 'Key Points', 'Summary', or 'How we will address it'. Just ask your next question(s) in simple words.\n"
 
740
  )
741
+ # Remind LLM to use the full conversation (messages below) to adapt and improve
742
+ if turn_number > 1:
743
+ phase_prompt += "\nThe messages below are the full conversation. Use them to understand what the client said and what you already asked; avoid repeating, infer context, and choose the next 1–2 questions that add the most value.\n"
744
 
745
  # --- CONVERSATION STATS ---
746
  stats = f"\nTurn {turn_number} | {questions_asked} questions asked so far | {deferral_count} deferrals\n"
747
+ sec_idx = _current_section_index(questions_asked)
748
+ if not _can_wrap_up(questions_asked, state.messages):
749
+ stats += f"Follow the questionnaire: you are on section {sec_idx + 1} of {len(QUESTIONNAIRE_SECTIONS)}. Do NOT ask 'Is there anything else?' until you have completed ALL 11 sections and asked at least {MIN_QUESTIONS_BEFORE_WRAP_UP} questions.\n"
750
  phase_prompt += stats
751
 
752
+ # --- ANTI-REPEAT REMINDER --- Follow the current section only; do not repeat past topics
753
+ if turn_number >= 4:
754
+ phase_prompt += "\n⚠️ Do NOT repeat questions from earlier sections. Ask only from the current section above. If the client already answered or said 'I don't know', move to the next intent in the section or the next section.\n"
755
+ # --- "I ALREADY ANSWERED" --- Do not re-ask; move to next intent or section
756
+ if re.search(r"already (?:answered|said|mentioned|told)|i (?:already )?said (?:that )?(?:already )?|said (?:that )?already", user_msg_lower):
757
+ phase_prompt += "\n⚠️ The client said they already answered. Acknowledge briefly (e.g. 'Got it.') and move to the NEXT intent or next section. Do NOT ask the same or a similar question again.\n"
758
+
759
+ # --- DATA: do not repeat once they've said where data is (questionnaire Section 4 covers this) ---
760
+ if _user_has_said_where_data_is(state.messages):
761
  phase_prompt += """
762
+ ⚠️ The client has ALREADY said where they keep their data (e.g. spreadsheet, database, notebook). Do NOT ask again about where it's stored, what it looks like, or where the file is. Continue with the current section or move on.\n"""
763
+ # --- WRAP-UP GUIDANCE (questionnaire sections drive what to ask; this only says when to end) ---
764
+ if turn_number > 1:
765
+ sec_idx = _current_section_index(questions_asked)
766
+ wrap_guidance = _build_wrap_up_guidance(questions_asked, state.messages, sec_idx)
767
+ phase_prompt += wrap_guidance
 
 
 
768
 
769
  # --- DYNAMIC BEHAVIORAL OVERRIDES ---
770
  if deferral_count >= 2:
771
  phase_prompt += """
772
+ ⚠️ THE USER SAID "I DON'T KNOW" MULTIPLE TIMES. Move on β€” do not keep asking that.
773
+ - Skip that topic. Ask about a DIFFERENT requirement (e.g. where they keep their data, or what they want in simple terms).
774
+ - Do NOT pitch a solution or explain "how we will address it". Just gather what you can and then ask "Is there anything else?"
 
 
 
 
775
  """
776
 
777
  # Question count limits only apply during diagnostic phases
778
  # In solution/final phases, the prompt itself handles question limits
779
  if state.phase in (Phase.DISCOVERY, Phase.EXPLORATION):
780
  if questions_asked >= 20:
781
+ phase_prompt += "\n⚠️ STOP ASKING. Ask only: 'Is there anything else you'd like to add?' Nothing else. No proposal.\n"
782
  elif questions_asked >= 14:
783
+ phase_prompt += "\nYou've asked enough. Ask: 'Is there anything else you'd like to add?' Only that β€” no solution or approach.\n"
784
 
785
  if turn_number >= 8:
786
+ phase_prompt += "\n⚠️ Turn 8+. Ask 'Is there anything else you'd like to add?' Do NOT give a proposal or solution.\n"
787
 
788
  messages = [SystemMessage(content=phase_prompt)]
789
 
 
888
  return Phase.DISCOVERY
889
 
890
 
891
+ # Thank-you message when client says "no" to "Is there anything else?"
892
+ THANK_YOU_MESSAGE = (
893
+ "Thank you. We have everything we need. Our team at Stacklogix will be in touch with you shortly."
894
+ )
895
+
896
+ # User said "no" / "nothing else" / "bye" to "Is there anything else?" β€” respond with thank-you ONCE, do not ask again
897
+ ANYTHING_ELSE_NO_PATTERNS = [
898
+ r"^\s*no\s*$", r"^\s*nope\s*$", r"^\s*nothing\s*$",
899
+ r"^\s*that'?s? (?:all|it)\s*$", r"^\s*that is all\s*$",
900
+ r"^\s*no\s*,?\s*thanks?\s*$", r"^\s*thanks?\s*,?\s*that'?s? all\s*$",
901
+ r"^\s*(?:we'?re|i'?m) good\s*$", r"^\s*all good\s*$",
902
+ r"^\s*not really\s*$", r"^\s*nothing else\s*$",
903
+ r"^\s*no\s+not\s+at\s+all\s*$", r"^\s*not\s+at\s+all\s*$",
904
+ r"^\s*bye\s*$", r"^\s*i'?m\s+good\s*$", r"^\s*that'?s\s+it\s*$",
905
+ ]
906
+
907
+
908
+ def _last_bot_asked_anything_else(messages: list[Message]) -> bool:
909
+ """True if the last assistant message asks 'anything else'."""
910
+ for msg in reversed(messages):
911
+ if msg.role == "assistant":
912
+ lower = msg.content.lower()
913
+ return "anything else" in lower or "anything more" in lower
914
+ return False
915
+
916
+
917
+ def _user_said_no_to_anything_else(user_message: str) -> bool:
918
+ """True if user is saying no / nothing else."""
919
+ stripped = user_message.strip().lower()
920
+ return any(re.search(p, stripped, re.IGNORECASE) for p in ANYTHING_ELSE_NO_PATTERNS)
921
+
922
+
923
  # ---------------------------------------------------------------------------
924
  # Main graph execution
925
  # ---------------------------------------------------------------------------
 
927
  async def run_consultation(session_state: SessionState, user_message: str) -> GraphState:
928
  """Run one turn of the consultation with quality enforcement."""
929
 
930
+ # 0. If bot just asked "Is there anything else?" and user said no β†’ thank you and end
931
+ if session_state.messages and _last_bot_asked_anything_else(session_state.messages) and _user_said_no_to_anything_else(user_message):
932
+ session_state.messages.append(Message(role="user", content=user_message))
933
+ session_state.messages.append(Message(role="assistant", content=THANK_YOU_MESSAGE))
934
+ return GraphState(
935
+ session_state=session_state,
936
+ user_message=user_message,
937
+ assistant_reply=THANK_YOU_MESSAGE,
938
+ new_confidence=1.0,
939
+ new_understanding=session_state.understanding,
940
+ new_phase=Phase.REFINEMENT,
941
+ )
942
+
943
  # 1. Build messages with intelligence injection
944
  messages = _build_message_list(session_state, user_message)
945
  turn_number = _get_turn_number(session_state.messages)
 
948
  response = await llm.ainvoke(messages)
949
  parsed = _parse_llm_output(response.content)
950
 
951
+ # 3. Anti-repetition check (content and questions)
952
  previous_replies = _get_previous_bot_responses(session_state.messages)
953
  overlap = _check_repetition(parsed["reply"], previous_replies)
954
+ question_repeat = _check_question_repetition(parsed["reply"], previous_replies)
955
 
956
+ if (overlap > 0.35 or question_repeat) and previous_replies:
957
  anti_repeat_msg = SystemMessage(content=(
958
+ "⚠️ You are REPEATING yourself. Either your content or your questions are too similar to what you already asked.\n"
959
+ "The client has already answered (or said 'I don't know') to topics you've covered. Do NOT ask about those again.\n"
960
+ "Reply with ONLY 1-2 short sentences, then ask about something you have NOT asked yet (from the current questionnaire section), OR ask: 'Is there anything else you'd like to add?'\n"
961
+ "No 'Key Observations' or bullet lists. Just a short acknowledgment and one new question."
 
962
  ))
963
  messages.insert(-1, anti_repeat_msg)
964
  response = await llm.ainvoke(messages)
 
1004
  non_q_lines = [line for line in reply_text.split("\n") if "?" not in line]
1005
  non_q_content = "\n".join(non_q_lines).strip()
1006
 
1007
+ # SOLUTION/REFINEMENT = only "Is there anything else?" β€” keep it short
1008
  is_final_phase = session_state.phase in (Phase.SOLUTION, Phase.REFINEMENT)
1009
+ min_length = 80 if is_final_phase else 120 # require at least 2-3 sentences before questions
1010
 
1011
  if len(non_q_content) < min_length:
1012
  if is_final_phase:
1013
  expand_msg = SystemMessage(content=(
1014
+ "⚠️ You must ONLY ask: 'Is there anything else you'd like to add?'\n"
1015
+ "Do NOT give a summary, proposal, or 'how we will address it'. Requirement gathering only. Short reply with that one question."
 
 
 
 
 
 
 
 
 
 
1016
  ))
1017
  else:
1018
  expand_msg = SystemMessage(content=(
1019
+ "⚠️ Your response is too short. You MUST include 2-3 sentences of acknowledgment before your question(s). "
1020
+ "Briefly reflect what they said and why it matters for understanding their needs. Then ask your question(s). "
1021
+ "Do NOT add 'Key Observations' or bullet lists. No single-line intros."
 
 
 
 
 
1022
  ))
1023
  messages.insert(-1, expand_msg)
1024
  response = await llm.ainvoke(messages)
 
1036
  # 3.9. Strip leading filler sentences (no LLM call)
1037
  parsed["reply"] = _strip_filler_sentences(parsed["reply"])
1038
 
1039
+ # 3.10. Strip "Key Observations" and similar observation/recap blocks
1040
+ parsed["reply"] = _strip_observation_blocks(parsed["reply"])
1041
+
1042
+ # 3.10b. Ensure there is some text before the first question (no bare questions)
1043
+ parsed["reply"] = _ensure_text_before_questions(parsed["reply"])
1044
+
1045
+ # 3.11. Break wall-of-text responses into readable paragraphs
1046
  parsed["reply"] = _break_wall_of_text(parsed["reply"])
1047
 
1048
+ # 3.12. Ensure questions are properly formatted for frontend box styling
1049
  parsed["reply"] = _format_questions(parsed["reply"])
1050
 
1051
  # 4. Confidence progression
 
1063
  # 5. Route phase
1064
  new_phase = _determine_phase(new_confidence)
1065
 
1066
+ # Phase forcing β€” require at least 10 questions (or lots of client info) before wrap-up
1067
  topic_turns = turn_number - session_state.topic_start_turn
1068
+ questions_after_this_turn = _count_bot_questions(session_state.messages) + parsed["reply"].count("?")
1069
+ messages_incl_current = session_state.messages + [Message(role="user", content=user_message)]
1070
+ if not _can_wrap_up(questions_after_this_turn, messages_incl_current) and new_phase in (Phase.SOLUTION, Phase.REFINEMENT):
1071
+ new_phase = Phase.EXPLORATION
1072
+ new_confidence = min(new_confidence, 0.5)
1073
+ elif topic_turns >= 12 and _can_wrap_up(questions_after_this_turn, messages_incl_current):
1074
  new_phase = Phase.REFINEMENT
1075
  new_confidence = max(new_confidence, 0.85)
1076
+ elif topic_turns >= 10 and _can_wrap_up(questions_after_this_turn, messages_incl_current) and new_phase in (Phase.DISCOVERY, Phase.EXPLORATION):
1077
  new_phase = Phase.SOLUTION
1078
  new_confidence = max(new_confidence, 0.65)
1079
  # 6. Update understanding (with validation)
backend/prompts.py CHANGED
@@ -1,106 +1,100 @@
1
- """System prompts for each consultation phase.
2
 
3
  DESIGN PRINCIPLES:
4
- - Keep prompts short β€” the LLM follows fewer rules better
5
- - Ground every question in what the CLIENT actually said
6
- - Never let the bot invent facts the client didn't mention
7
- - Let the LLM generate questions naturally β€” no hardcoded examples
 
8
  """
9
 
10
  from __future__ import annotations
11
 
12
  # ---------------------------------------------------------------------------
13
- # Core persona β€” short, strict, grounded
14
  # ---------------------------------------------------------------------------
15
 
16
- _PERSONA = """You are an expert business consultant β€” sharp, insightful, warm.
17
-
18
- STRICT RULES (violating ANY of these = failure):
19
-
20
- 1. GROUNDING β€” NEVER invent, assume, or state facts the client hasn't told you.
21
- If you don't know something, ASK β€” don't guess.
22
-
23
- 2. QUESTION PRIORITY β€” Focus on understanding the PROBLEM first.
24
- Before asking about operations, team, or processes, make sure you deeply
25
- understand WHAT is going wrong and WHY. Dig into the problem itself:
26
- - WHICH specific items/products/areas are affected?
27
- - HOW MUCH β€” quantities, amounts, scale of the issue?
28
- - SINCE WHEN β€” how long has this been happening?
29
- - WHAT CHANGED β€” did something trigger it?
30
- Ask these specifics BEFORE asking about how they track data or run operations.
31
-
32
- 3. ASK SIMPLE QUESTIONS β€” Your questions must be easy for a non-technical person
33
- to answer. Avoid jargon, technical terms, or metrics they wouldn't know.
34
- Base your questions naturally on what the client just told you.
35
-
36
- 4. NEVER ask the client to solve their own problem. YOU are the consultant β€”
37
- it's YOUR job to diagnose, analyze, and recommend. Don't ask things like
38
- "what do you think is causing this?" or "how do you think X could help?"
39
-
40
- 5. Ask EXACTLY 1-2 questions per response. NEVER more than 2 question marks total.
41
-
42
- 6. BANNED PHRASES β€” NEVER use these (instant failure):
43
- - "I'm glad you shared" / "I'm glad you told me"
44
- - "I'd love to learn more" / "I'd love to help"
45
- - "a challenging issue to tackle"
46
- - "affecting your business's overall performance"
47
- - "get your business back on track"
48
- - "I'm thinking that it might be related to"
49
- Instead: JUMP STRAIGHT into your insight. Your first sentence should
50
- contain specific knowledge or a sharp observation.
51
-
52
- 7. FORMATTING β€” Your response MUST be RICH and well-structured:
53
-
54
- RESPONSE STRUCTURE (follow this every time):
55
- a) Start with 1-2 conversational sentences reacting to what they said
56
- b) Then a **bold observation header** followed by 3-4 bullet points:
57
- - Each bullet should be a specific insight, observation, or piece of
58
- context relevant to their situation
59
- - Draw on industry knowledge, common patterns, or what they've shared
60
- - Be concrete: mention specific approaches, tools, strategies
61
- c) Optionally: 1-2 sentences of brief recommendation or industry context
62
- d) End with your question(s)
63
-
64
- Your response BEFORE the questions should be at LEAST 5-8 sentences total.
65
- Short replies with just 2-3 sentences are NOT acceptable.
66
-
67
- QUESTION FORMAT (critical for display):
68
- Each question MUST be on its own separate paragraph with a BLANK LINE before it.
69
- Example:
70
-
71
- Your conversational text here...
72
-
73
- **Key Observations**
74
- - Specific insight about their situation
75
- - Another observation based on what they shared
76
- - Industry context or pattern you've noticed
77
-
78
- Brief recommendation or context...
79
-
80
- **Your first question here?**
81
-
82
- **Your second question here?**
83
-
84
- NEVER put two questions on the same line or consecutive lines without a blank line.
85
- NEVER use headers like "Key Points:", "Current Understanding:", "Summary:".
86
-
87
- 8. NEVER list generic causes or possibilities. ASK to find out.
88
-
89
- 9. When user says "I don't know" β†’ stop asking, give your recommendation instead.
90
-
91
- 10. MOVE ON after user answers β€” Once the user answers a question, that topic is DONE.
92
- Do NOT ask follow-up questions about the SAME thing. For example:
93
- - If they said "spreadsheet" β†’ DO NOT ask "what challenges with your spreadsheet?"
94
- - If they said "online" β†’ DO NOT ask "how do you sell online?"
95
- Instead, move to a COMPLETELY DIFFERENT aspect of their business.
96
-
97
- 11. NEVER repeat content from previous turns.
98
-
99
- 12. Each question MUST cover a DIFFERENT aspect of the problem. Never ask two
100
- questions about the same topic. Vary across: operations, finances, customers,
101
- team, strategy, goals, market, competitors.
102
-
103
- 13. Be SPECIFIC in advice: name actual methods, steps, approaches. No vague advice.
104
  """
105
 
106
  _OUTPUT_FORMAT = """
@@ -110,38 +104,34 @@ Respond with ONLY a JSON object:
110
  "confidence": <float 0.0-1.0>,
111
  "understanding": "Cumulative summary of ONLY what the client has explicitly stated. Do NOT add interpretations."
112
  }}
113
- Confidence: start 0.0, max +0.10/turn. Output ONLY the JSON.
114
  """
115
 
116
  # ---------------------------------------------------------------------------
117
  # TURN 1 β€” first response
118
  # ---------------------------------------------------------------------------
119
 
 
 
 
120
  TURN_1_PROMPT = (
121
  _PERSONA
122
  + """
123
  THIS IS THE USER'S VERY FIRST MESSAGE.
124
 
125
- IMPORTANT: If the user just says "hi" or "hello" or a simple greeting WITHOUT
126
- describing any problem, give a SHORT warm greeting (1-2 sentences max) and ask
127
- what they'd like help with. Do NOT generate generic observations or filler.
128
 
129
- If they DO describe a problem, respond with:
130
- 1. Start with a short, warm reaction to what they said (1 sentence)
131
- 2. Ask 2 natural follow-up questions about their situation β€” one about their
132
- business/domain and one about the SPECIFIC details of the problem.
133
- For example: "Which products are affected?" or "How long has this been happening?"
134
 
135
- Keep it to 2 questions max. Both must be **bolded** and on their own line.
136
- Do NOT lecture. Do NOT list possible causes. Do NOT assume anything.
137
- Do NOT create any "Key Points" or "Summary" sections.
138
- Do NOT generate generic observations like "every business challenge is unique".
139
  """
140
  + _OUTPUT_FORMAT
141
  )
142
 
143
  # ---------------------------------------------------------------------------
144
- # Phase prompts β€” used from turn 2 onwards
145
  # ---------------------------------------------------------------------------
146
 
147
  DISCOVERY_PROMPT = (
@@ -149,19 +139,11 @@ DISCOVERY_PROMPT = (
149
  + """
150
  PHASE: Discovery | Confidence: {confidence:.0%}
151
 
152
- You're still learning about their situation. Your #1 priority is to understand
153
- the SPECIFIC DETAILS of the problem before anything else.
154
-
155
- DIG INTO THE PROBLEM:
156
- - If they mention a problem (e.g., slow inventory, low sales, high costs),
157
- ask WHICH specific products/items/areas are affected.
158
- - Ask about quantities, amounts, timelines β€” the concrete details.
159
- - Ask what changed or when it started getting worse.
160
-
161
- DO NOT ask about operations, data storage, team size, or processes yet.
162
- Those come AFTER you understand exactly what's going wrong.
163
 
164
- 1-2 natural follow-up questions based on what they just told you.
165
  """
166
  + _OUTPUT_FORMAT
167
  )
@@ -171,19 +153,11 @@ EXPLORATION_PROMPT = (
171
  + """
172
  PHASE: Exploration | Confidence: {confidence:.0%}
173
 
174
- You understand the basics. Now dig into the ROOT CAUSE.
175
-
176
- Your priority order:
177
- 1. ROOT CAUSE β€” What specifically is causing the problem? Narrow it down.
178
- Ask about: what changed, what they've already tried, which specific
179
- parts of the business are most affected and why.
180
- 2. PAST ATTEMPTS β€” What have they tried to fix it? What happened?
181
- 3. DATA (only if not asked yet) β€” How do they track their business info?
182
-
183
- Do NOT ask generic questions about team size, goals, or sales channels.
184
- Stay focused on understanding WHY the problem exists.
185
 
186
- Give brief insights where you can. 1-2 questions max.
187
  """
188
  + _OUTPUT_FORMAT
189
  )
@@ -193,90 +167,47 @@ CONSTRAINTS_PROMPT = (
193
  + """
194
  PHASE: Constraints | Confidence: {confidence:.0%}
195
 
196
- SHIFT to primarily ADVISING. You have enough info.
197
- - State your diagnosis clearly
198
- - Propose specific solutions with clear steps
199
- - Reference ONLY details the client actually shared
200
- - At most 1 question
201
 
202
- Be specific and concrete. No vague advice.
203
  """
204
  + _OUTPUT_FORMAT
205
  )
206
 
 
207
  SOLUTION_PROMPT = (
208
  _PERSONA
209
  + """
210
- PHASE: Solution | Confidence: {confidence:.0%}
211
-
212
- You now have enough information to deliver a DETAILED, ACTIONABLE solution.
213
 
214
- YOUR RESPONSE MUST INCLUDE:
215
 
216
- 1. **Problem Summary** β€” 2-3 sentences summarizing what you've learned about
217
- their specific situation (reference details THEY told you).
218
 
219
- 2. **Root Cause Analysis** β€” What you believe is causing the problem, based on
220
- what they've shared. Be specific, not generic.
221
 
222
- 3. **Step-by-Step Action Plan** β€” A NUMBERED list of concrete steps they should
223
- take, organized by timeline:
224
- - **Immediate (This Week):** 2-3 quick wins they can start today
225
- - **Short-Term (Next 2-4 Weeks):** 3-4 tactical changes
226
- - **Medium-Term (1-3 Months):** 2-3 strategic initiatives
227
-
228
- Each step must be SPECIFIC: name actual tools, methods, platforms, strategies.
229
- NOT "improve your marketing" but "Create an Instagram business account and
230
- post 3-4 high-quality product photos per week with relevant hashtags."
231
 
232
- 4. **Expected Outcomes** β€” What results they can expect if they follow the plan.
233
-
234
- Keep this response LONG and DETAILED β€” this is the value they came for.
235
- At most 1 question to clarify implementation details.
236
  """
237
  + _OUTPUT_FORMAT
238
  )
239
 
 
240
  REFINEMENT_PROMPT = (
241
  _PERSONA
242
  + """
243
  PHASE: Final | Confidence: {confidence:.0%}
244
 
245
- Deliver the COMPLETE, FINAL action plan. This is the most important response
246
- in the entire conversation β€” make it worth their time.
247
-
248
- YOUR RESPONSE MUST INCLUDE:
249
-
250
- 1. **Executive Summary** β€” 3-4 sentences summarizing the problem and your
251
- recommended approach. Reference specific details they shared.
252
-
253
- 2. **Detailed Action Pipeline** β€” A comprehensive NUMBERED step-by-step plan:
254
-
255
- **Phase 1: Immediate Actions (This Week)**
256
- - Step 1: [Specific action with details]
257
- - Step 2: [Specific action with details]
258
- - Step 3: [Specific action with details]
259
-
260
- **Phase 2: Short-Term Strategy (Weeks 2-4)**
261
- - Step 4: [Specific action with details]
262
- - Step 5: [Specific action with details]
263
-
264
- **Phase 3: Medium-Term Growth (Month 2-3)**
265
- - Step 6: [Specific action with details]
266
- - Step 7: [Specific action with details]
267
-
268
- 3. **Tools & Resources** β€” Specific tools, platforms, or resources they should
269
- use (name actual products, websites, methods).
270
-
271
- 4. **Expected Results** β€” What outcomes to expect and rough timelines.
272
 
273
- 5. **Key Metrics to Track** β€” 3-4 specific numbers they should monitor to
274
- know if the plan is working.
275
 
276
- Make this response VERY detailed and thorough. This is the deliverable.
277
 
278
- End your response by asking:
279
- "Is there anything else you'd like help with, or any other challenges you're facing?"
280
  """
281
  + _OUTPUT_FORMAT
282
  )
 
1
+ """System prompts for requirement gathering only (no solution pitching).
2
 
3
  DESIGN PRINCIPLES:
4
+ - Pure requirement gathering: ask questions only. Do NOT explain approach, solution, or what Stacklogix will do.
5
+ - Always cover DATA when relevant: where it is or would be stored, what form it is in (e.g. list, spreadsheet, paper, software) β€” adapt to the client's situation (any domain).
6
+ - Never ask about budget or timeline.
7
+ - Client is typically non-technical: ask simple, plain-language questions only. No "system design", "implementation", "progress updates", "involved in development".
8
+ - When you have gathered enough, ask "Is there anything else you'd like to add?" If they say no, a thank-you will be sent.
9
  """
10
 
11
  from __future__ import annotations
12
 
13
  # ---------------------------------------------------------------------------
14
+ # Universal questionnaire β€” fixed order (no budget/timeline). Same intent β†’ same flow.
15
  # ---------------------------------------------------------------------------
16
 
17
+ # Principle-based sections: adapt to any domain from conversation context. No hardcoded examples.
18
+ QUESTIONNAIRE_SECTIONS = [
19
+ (1, "Intent & Context", [
20
+ "Infer from the client's words whether this is new or existing; do not ask them.",
21
+ "What they are trying to achieve, or for a problem ask about the subject in concrete terms that match what they said β€” stick to their topic, do not introduce unrelated angles.",
22
+ "Who it is for or one-sentence context, only if not already clear.",
23
+ ]),
24
+ (2, "Object / Subject Definition", [
25
+ "What is the thing they are referring to β€” concrete questions adapted to their domain.",
26
+ "Which specific parts are affected or underperforming, if relevant.",
27
+ "Main purpose or what problem or need it addresses.",
28
+ ]),
29
+ (3, "Current State (if it exists)", [
30
+ "If they already have something: how it is handled today, what works, what does not.",
31
+ "If they are building something new: do not ask what they 'currently' use; at most ask once if anything today would be replaced. If they say no/none, move on.",
32
+ ]),
33
+ (4, "Data & Information", [
34
+ "Whether data is involved; what kind, where it is or would live, how often updated.",
35
+ "Access, privacy, or regulatory constraints when relevant.",
36
+ ]),
37
+ (5, "Problem Details (if applicable)", [
38
+ "If they have an existing problem: what is going wrong, how often, who is affected.",
39
+ "If building something new: skip or ask what would make it succeed or what to avoid; do not ask them to fix the problem they came with.",
40
+ ]),
41
+ (6, "Workflow & Operations", [
42
+ "If existing process: workflow, manual vs automated, friction, volume.",
43
+ "If new: how they imagine it would be used, by whom, at what scale.",
44
+ ]),
45
+ (7, "Targeted audience", [
46
+ "Who is your targeted audience β€” one simple question if relevant. Do not ask about who will oversee, maintain, or support; do not ask about stakeholders or maintenance.",
47
+ ]),
48
+ (8, "Goals & Success", [
49
+ "What would success look like for them in general terms.",
50
+ "Do not ask the client what would fix or improve the specific problem they described β€” that is why they came; we gather requirements. Ask what a good outcome looks like in general, or move on.",
51
+ "How they would measure success, what matters most. If already stated, move to Section 9.",
52
+ ]),
53
+ (9, "Constraints (non-financial, non-time, non-technical)", [
54
+ "Risks or outcomes they want to avoid; rules or standards the solution should follow, in plain language. No technical limitations, storage, or infrastructure β€” client is non-technical.",
55
+ ]),
56
+ (10, "Future & Evolution", [
57
+ "If this works well, what should happen next; growth or usage expectations; likely future changes.",
58
+ ]),
59
+ (11, "Open Ends", [
60
+ "Anything important not yet mentioned; assumptions to avoid; anything they want to explicitly avoid.",
61
+ ]),
62
+ ]
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Core persona β€” questionnaire-driven, consistent flow
66
+ # ---------------------------------------------------------------------------
67
+
68
+ _PERSONA = """You are a professional requirement-gathering consultant for Stacklogix.
69
+ You use a FIXED QUESTIONNAIRE structure. Work through the sections IN ORDER. You do NOT pitch solutions or describe what Stacklogix will do.
70
+
71
+ USE THE FULL CONVERSATION: Read every client message and every reply you have already sent. From that context you must: (1) understand what the client has already said and what you have already asked, (2) avoid repeating questions or topics, (3) infer whether their situation is new or existing and what the core problem or request is, (4) avoid asking them to fix or improve the very problem they came with β€” that is why they are here, (5) skip sections or intents they have already covered, (6) phrase your next 1–2 questions so they fit their domain and the conversation so far. Rely on your understanding of the dialogue; adapt to any domain and situation.
72
+
73
+ CONSISTENCY: The system tells you "Current section: Section N". Ask ONLY 1–2 questions from that section. Do not jump ahead; do not mix sections in one reply. If the client has already given information that covers this section, skip or ask only what is missing, then move to the next section. Strict order: 1 β†’ 2 β†’ … β†’ 11.
74
+
75
+ ADAPT: Turn the section's intents into 1–2 natural questions that fit what the client said and the conversation so far. Use simple, non-technical language. Infer "new vs existing" from their words; do not ask. For a problem they described, ask about the subject in concrete terms that match what they said; do not ask them what would fix that problem.
76
+
77
+ STRICT RULES:
78
+
79
+ 1. FOLLOW THE SECTION β€” Ask only from the current section, adapted to the client. If they already answered this section's intents, skip or ask only gaps, then move on. No mixing sections. Order: 1 β†’ 11.
80
+
81
+ 2. NEVER ASK ABOUT BUDGET OR TIMELINE. Do not ask about support, maintenance, or how they want to be notified β€” Stacklogix handles that.
82
+
83
+ 3. NON-TECHNICAL β€” Plain language only. Do not ask about implementation, data storage, processing, infrastructure, stakeholders, who oversees or is responsible, or technical limitations. You gather what they want; Stacklogix builds and maintains the solution.
84
+
85
+ 4. GROUNDING β€” State only facts the client told you. Do not interpret or speculate. One short, neutral acknowledgment then your question(s).
86
+
87
+ 5. Ask EXACTLY 1–2 questions per response. Never more than 2 question marks.
88
+
89
+ 6. BANNED β€” No "Key Points", "Key Observations", bullet lists. No "our approach", "how we will address it".
90
+
91
+ 7. FORMAT β€” Short acknowledgment (one sentence). Do not say "this helps us understand" or "can provide insights". Then your question(s). Each question on its own line with a blank line before it.
92
+
93
+ 8. "I don't know" β†’ treat topic as done; next intent or next section. Do not pitch a solution.
94
+
95
+ 9. "I already answered" / "already mentioned" β†’ acknowledge briefly and move to next intent or section. Do not re-ask the same or similar question.
96
+
97
+ 10. Do not ask the client to solve their own problem. If they came because something is wrong, do not ask what would make that thing better or fix it. Ask what success would look like in general, or move on.
 
 
 
 
 
 
 
98
  """
99
 
100
  _OUTPUT_FORMAT = """
 
104
  "confidence": <float 0.0-1.0>,
105
  "understanding": "Cumulative summary of ONLY what the client has explicitly stated. Do NOT add interpretations."
106
  }}
107
+ Confidence: increase as you gather more (what they want, data location/form, current methods). Output ONLY the JSON.
108
  """
109
 
110
  # ---------------------------------------------------------------------------
111
  # TURN 1 β€” first response
112
  # ---------------------------------------------------------------------------
113
 
114
+ # Placeholder for section injection (graph.py replaces this)
115
+ CURRENT_SECTION_PLACEHOLDER = "__CURRENT_SECTION_INJECT__"
116
+
117
  TURN_1_PROMPT = (
118
  _PERSONA
119
  + """
120
  THIS IS THE USER'S VERY FIRST MESSAGE.
121
 
122
+ Use Section 1 β€” Intent & Context. Infer from their message whether this is a problem (something not working as they want) or a request (something new they want to build); do not ask "is this new or existing?".
 
 
123
 
124
+ - If they only say "hi" or "hello": Short greeting, then ask what they'd like to achieve or what they need help with.
 
 
 
 
125
 
126
+ - If they describe a problem or a request: Acknowledge briefly in one short sentence. Then ask 1–2 concrete questions about the subject they mentioned, adapted to their words. Do not ask them to fix the problem they described; ask about the thing itself so you understand it. Each question on its own line.
127
+
128
+ Do NOT give "Key Points" or "Our approach". Do NOT ask about budget or timeline.
 
129
  """
130
  + _OUTPUT_FORMAT
131
  )
132
 
133
  # ---------------------------------------------------------------------------
134
+ # Phase prompts β€” requirement gathering only, no solution content
135
  # ---------------------------------------------------------------------------
136
 
137
  DISCOVERY_PROMPT = (
 
139
  + """
140
  PHASE: Discovery | Confidence: {confidence:.0%}
141
 
142
+ """
143
+ + CURRENT_SECTION_PLACEHOLDER
144
+ + """
 
 
 
 
 
 
 
 
145
 
146
+ Start with 2–3 sentences that acknowledge what they said. Then ask 1–2 questions ONLY from the current section above, adapted to the client's situation and conversation. Do NOT skip sections or jump ahead. Do NOT ask about budget or timeline.
147
  """
148
  + _OUTPUT_FORMAT
149
  )
 
153
  + """
154
  PHASE: Exploration | Confidence: {confidence:.0%}
155
 
156
+ """
157
+ + CURRENT_SECTION_PLACEHOLDER
158
+ + """
 
 
 
 
 
 
 
 
159
 
160
+ Start with 2–3 sentences that acknowledge what they said. Then ask 1–2 questions ONLY from the current section above, adapted to the client's situation. Do NOT ask from a later section yet. Do NOT ask about budget or timeline.
161
  """
162
  + _OUTPUT_FORMAT
163
  )
 
167
  + """
168
  PHASE: Constraints | Confidence: {confidence:.0%}
169
 
170
+ """
171
+ + CURRENT_SECTION_PLACEHOLDER
172
+ + """
 
 
173
 
174
+ Ask at most 1–2 questions from the current section above to confirm or fill gaps. Use plain language. Do NOT summarize "how we will address it". Do NOT ask about budget or timeline.
175
  """
176
  + _OUTPUT_FORMAT
177
  )
178
 
179
+ # Solution phase = only ask "Is there anything else?" (no proposal)
180
  SOLUTION_PROMPT = (
181
  _PERSONA
182
  + """
183
+ PHASE: Wrapping up | Confidence: {confidence:.0%}
 
 
184
 
185
+ You have gathered the main requirements. Do NOT give a summary of the approach or solution. Do NOT list "how we will address it" or "what we will deliver".
186
 
187
+ Your ONLY job now: ask if there is anything else.
 
188
 
189
+ Reply with a very short acknowledgment (1–2 sentences) of what you've understood, then ask exactly:
 
190
 
191
+ **Is there anything else you'd like to add?**
 
 
 
 
 
 
 
 
192
 
193
+ Nothing else. No proposal, no steps, no deliverables.
 
 
 
194
  """
195
  + _OUTPUT_FORMAT
196
  )
197
 
198
+ # Refinement = same: only "anything else?" or thank-you handled by system
199
  REFINEMENT_PROMPT = (
200
  _PERSONA
201
  + """
202
  PHASE: Final | Confidence: {confidence:.0%}
203
 
204
+ If you have not yet asked "Is there anything else?", do so now: short acknowledgment, then ask:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
+ **Is there anything else you'd like to add?**
 
207
 
208
+ If you already asked that and the user added more β€” gather that with 1–2 simple questions, then ask "Is there anything else?" again when done.
209
 
210
+ Do NOT provide summaries of approach, implementation steps, or what Stacklogix will deliver. Requirement gathering only.
 
211
  """
212
  + _OUTPUT_FORMAT
213
  )
frontend/app.js CHANGED
@@ -1,5 +1,5 @@
1
  /* ═══════════════════════════════════════════════════════════════
2
- ConsultAI β€” Chat Client (Vanilla JS)
3
  ═══════════════════════════════════════════════════════════════ */
4
 
5
  const API_BASE = window.location.origin;
 
1
  /* ═══════════════════════════════════════════════════════════════
2
+ Stacklogix β€” Chat Client (Vanilla JS)
3
  ═══════════════════════════════════════════════════════════════ */
4
 
5
  const API_BASE = window.location.origin;
frontend/index.html CHANGED
@@ -4,9 +4,9 @@
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>ConsultAI β€” Intelligent Problem-Solving</title>
8
  <meta name="description"
9
- content="AI-powered consultant chatbot for structured problem-solving across any domain." />
10
  <link rel="preconnect" href="https://fonts.googleapis.com" />
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
@@ -20,9 +20,9 @@
20
  <div class="sidebar-header">
21
  <div class="logo">
22
  <span class="logo-icon">β—†</span>
23
- <span class="logo-text">ConsultAI</span>
24
  </div>
25
- <button class="btn-new-chat" id="btnNewChat" title="New Consultation">
26
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
27
  <line x1="12" y1="5" x2="12" y2="19" />
28
  <line x1="5" y1="12" x2="19" y2="12" />
@@ -33,7 +33,7 @@
33
  <!-- Sessions populated by JS -->
34
  </div>
35
  <div class="sidebar-footer">
36
- <div class="sidebar-footer-text">Powered by Groq + LangGraph</div>
37
  </div>
38
  </aside>
39
 
@@ -48,7 +48,7 @@
48
  <line x1="3" y1="18" x2="21" y2="18" />
49
  </svg>
50
  </button>
51
- <div class="topbar-title">ConsultAI</div>
52
  </header>
53
 
54
  <!-- Messages -->
@@ -57,19 +57,15 @@
57
  <!-- Welcome -->
58
  <div class="welcome-screen" id="welcomeScreen">
59
  <div class="welcome-icon">β—†</div>
60
- <h1>Welcome to ConsultAI</h1>
61
- <p>I'm your AI consultant. Describe any problem or challenge, and I'll guide you through a
62
- structured analysis to find the best solution.</p>
63
  <div class="welcome-suggestions">
64
  <button class="suggestion-chip"
65
- data-msg="I'm building a SaaS product and struggling with user retention after the free trial ends.">SaaS
66
- Retention</button>
67
  <button class="suggestion-chip"
68
- data-msg="Our supply chain has major inefficiencies and we're losing money on logistics.">Supply
69
- Chain</button>
70
  <button class="suggestion-chip"
71
- data-msg="I want to launch an online education platform but I'm not sure about the best approach.">EdTech
72
- Launch</button>
73
  </div>
74
  </div>
75
  </div>
@@ -78,7 +74,7 @@
78
  <!-- Input -->
79
  <div class="input-area">
80
  <div class="input-wrapper">
81
- <textarea id="userInput" placeholder="Describe your problem or respond to my questions…" rows="1"
82
  autofocus></textarea>
83
  <button class="btn-send" id="btnSend" title="Send message" disabled>
84
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Stacklogix β€” Requirement Collection & Consulting</title>
8
  <meta name="description"
9
+ content="Stacklogix consultant chatbot β€” we gather your requirements and explain how we'll address your request or problem." />
10
  <link rel="preconnect" href="https://fonts.googleapis.com" />
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
 
20
  <div class="sidebar-header">
21
  <div class="logo">
22
  <span class="logo-icon">β—†</span>
23
+ <span class="logo-text">Stacklogix</span>
24
  </div>
25
+ <button class="btn-new-chat" id="btnNewChat" title="New conversation">
26
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
27
  <line x1="12" y1="5" x2="12" y2="19" />
28
  <line x1="5" y1="12" x2="19" y2="12" />
 
33
  <!-- Sessions populated by JS -->
34
  </div>
35
  <div class="sidebar-footer">
36
+ <div class="sidebar-footer-text">Stacklogix</div>
37
  </div>
38
  </aside>
39
 
 
48
  <line x1="3" y1="18" x2="21" y2="18" />
49
  </svg>
50
  </button>
51
+ <div class="topbar-title">Stacklogix</div>
52
  </header>
53
 
54
  <!-- Messages -->
 
57
  <!-- Welcome -->
58
  <div class="welcome-screen" id="welcomeScreen">
59
  <div class="welcome-icon">β—†</div>
60
+ <h1>Welcome to Stacklogix</h1>
61
+ <p>I'm your Stacklogix consultant. Tell me what you'd like to build or what problem you're facing β€” I'll gather the details and explain how we'll address it.</p>
 
62
  <div class="welcome-suggestions">
63
  <button class="suggestion-chip"
64
+ data-msg="We want to build a chatbot for our website to answer customer questions.">Build a chatbot</button>
 
65
  <button class="suggestion-chip"
66
+ data-msg="We need a dashboard to track sales and inventory in real time.">Build a dashboard</button>
 
67
  <button class="suggestion-chip"
68
+ data-msg="Our inventory is moving too slowly and we're not sure why.">Inventory moving slowly</button>
 
69
  </div>
70
  </div>
71
  </div>
 
74
  <!-- Input -->
75
  <div class="input-area">
76
  <div class="input-wrapper">
77
+ <textarea id="userInput" placeholder="Describe what you need or respond to my questions…" rows="1"
78
  autofocus></textarea>
79
  <button class="btn-send" id="btnSend" title="Send message" disabled>
80
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">