WebashalarForML commited on
Commit
6b2d153
·
verified ·
1 Parent(s): f85b1a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -20
app.py CHANGED
@@ -51,7 +51,10 @@ llm = ChatGroq(
51
  api_key=GROQ_API_KEY,
52
  )
53
 
54
- PROGRAMMING_ASSISTANT_PROMPT = """
 
 
 
55
  You are an expert programming assistant. Your role is to provide code suggestions, fix bugs, explain programming concepts, and offer contextual help based on the user's query and preferred programming language.
56
 
57
  **CONTEXT HANDLING RULES (Follow these strictly):**
@@ -61,7 +64,7 @@ You are an expert programming assistant. Your role is to provide code suggestion
61
  STRICT OUTPUT FORMAT (JSON ONLY):
62
  Return a single JSON object with the following keys. **The JSON object MUST be enclosed in a single ```json block.**
63
  - assistant_reply: string // A natural language reply to the user (short and helpful). Do NOT include code blocks here.
64
- - code_snippet: string // If suggesting code, provide it here in a markdown code block (e.g., ```python\\nprint('Hello')\\n```). If no code is required, use an empty string: "".
65
  - state_updates: object // updates to the internal state, must include: language, conversationSummary
66
  - suggested_tags: array of strings // a list of 1-3 relevant tags for the assistant_reply
67
 
@@ -72,8 +75,10 @@ Rules:
72
  """
73
 
74
  def extract_json_from_llm_response(raw_response: str) -> dict:
 
 
75
  default = {
76
- "assistant_reply": "I'm sorry, I couldn't process the response correctly. Could you please rephrase?",
77
  "code_snippet": "",
78
  "state_updates": {"conversationSummary": "", "language": "Python"},
79
  "suggested_tags": [],
@@ -82,25 +87,25 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
82
  if not raw_response or not isinstance(raw_response, str):
83
  return default
84
 
85
- # Use a non-greedy regex to find the JSON content inside the first code block
86
  m = re.search(r"```json\s*([\s\S]*?)\s*```", raw_response)
87
  json_string = m.group(1).strip() if m else raw_response
88
 
89
- # Further refine candidate to just the JSON object content
90
  first = json_string.find('{')
91
  last = json_string.rfind('}')
92
  candidate = json_string[first:last+1] if first != -1 and last != -1 and first < last else json_string
93
 
94
- # Remove trailing commas which can break JSON parsing
95
  candidate = re.sub(r',\s*(?=[}\]])', '', candidate)
96
 
97
  try:
98
  parsed = json.loads(candidate)
99
  except Exception as e:
100
- logger.warning("Failed to parse JSON from LLM output: %s. Candidate: %s", e, candidate)
101
  return default
102
 
103
- # Validate and clean up the parsed dictionary
104
  if isinstance(parsed, dict) and "assistant_reply" in parsed:
105
  parsed.setdefault("code_snippet", "")
106
  parsed.setdefault("state_updates", {})
@@ -114,7 +119,7 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
114
 
115
  return parsed
116
  else:
117
- logger.warning("Parsed JSON missing 'assistant_reply' or invalid format. Returning default.")
118
  return default
119
 
120
  def detect_language_from_text(text: str) -> Optional[str]:
@@ -146,18 +151,17 @@ def chat():
146
  chat_history: List[Dict[str, str]] = data.get("chat_history") or []
147
  assistant_state: AssistantState = data.get("assistant_state") or {}
148
 
149
- # Initialize/Clean up state
150
  state: AssistantState = {
151
  "conversationSummary": assistant_state.get("conversationSummary", ""),
152
  "language": assistant_state.get("language", "Python"),
153
  "taggedReplies": assistant_state.get("taggedReplies", []),
154
  }
155
 
156
- # 1. Prepare LLM Messages from Full History
157
  llm_messages = [{"role": "system", "content": PROGRAMMING_ASSISTANT_PROMPT}]
158
 
159
  last_user_message = ""
160
-
161
  for msg in chat_history:
162
  role = msg.get("role")
163
  content = msg.get("content")
@@ -166,15 +170,14 @@ def chat():
166
  if role == "user":
167
  last_user_message = content
168
 
169
- # 2. Language Detection & State Update
170
  detected_lang = detect_language_from_text(last_user_message)
171
  if detected_lang and detected_lang.lower() != state["language"].lower():
172
  logger.info("Detected new language: %s", detected_lang)
173
  state["language"] = detected_lang
174
 
175
- # 3. Inject Contextual Hint and State into the LAST user message
176
  context_hint = f"Current Language: {state['language']}. Conversation Summary so far: {state['conversationSummary']}"
177
-
178
  if llm_messages and llm_messages[-1]["role"] == "user":
179
  llm_messages[-1]["content"] = f"USER MESSAGE: {last_user_message}\n\n[CONTEXT HINT: {context_hint}]"
180
  elif last_user_message:
@@ -196,22 +199,36 @@ def chat():
196
  error_detail = "LLM Model Error: The model is likely decommissioned. Please check the 'LLM_MODEL' environment variable or the default model in app.py."
197
  return jsonify({"error": "LLM invocation failed", "detail": error_detail}), 500
198
 
199
- # 4. State Update from LLM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  updated_state_from_llm = parsed_result.get("state_updates", {})
201
 
202
  if 'conversationSummary' in updated_state_from_llm:
203
  state["conversationSummary"] = updated_state_from_llm["conversationSummary"]
204
- if 'language' in updated_state_from_llm:
 
205
  state["language"] = updated_state_from_llm["language"]
206
 
 
207
  assistant_reply = parsed_result.get("assistant_reply")
208
  code_snippet = parsed_result.get("code_snippet")
209
 
210
  # 5. Final Response Payload: Combine the reply and the code snippet
211
- # The frontend is expecting the code to be *in* the assistant_reply, so we stitch it back together.
212
  final_reply_content = assistant_reply
213
  if code_snippet and code_snippet.strip():
214
- # Add a newline for clean separation if the reply isn't just whitespace
215
  if final_reply_content.strip():
216
  final_reply_content += "\n\n"
217
  final_reply_content += code_snippet
@@ -220,7 +237,7 @@ def chat():
220
  final_reply_content = "I'm here to help with your code! What programming language are you using?"
221
 
222
  response_payload = {
223
- "assistant_reply": final_reply_content, # Send combined reply + code
224
  "updated_state": state,
225
  "suggested_tags": parsed_result.get("suggested_tags", []),
226
  }
 
51
  api_key=GROQ_API_KEY,
52
  )
53
 
54
+ # --- Define the standard error message for failed parsing
55
+ LLM_PARSE_ERROR_MESSAGE = "I'm sorry, I couldn't process the last response correctly due to a formatting issue. Could you please rephrase or try a simpler query?"
56
+
57
+ PROGRAMMING_ASSISTANT_PROMPT = f"""
58
  You are an expert programming assistant. Your role is to provide code suggestions, fix bugs, explain programming concepts, and offer contextual help based on the user's query and preferred programming language.
59
 
60
  **CONTEXT HANDLING RULES (Follow these strictly):**
 
64
  STRICT OUTPUT FORMAT (JSON ONLY):
65
  Return a single JSON object with the following keys. **The JSON object MUST be enclosed in a single ```json block.**
66
  - assistant_reply: string // A natural language reply to the user (short and helpful). Do NOT include code blocks here.
67
+ - code_snippet: string // If suggesting code, provide it here in a markdown code block. **CRITICALLY, you must escape all internal newlines as '\\n' and backslashes as '\\\\'** to keep the string value valid JSON. If no code is required, use an empty string: "".
68
  - state_updates: object // updates to the internal state, must include: language, conversationSummary
69
  - suggested_tags: array of strings // a list of 1-3 relevant tags for the assistant_reply
70
 
 
75
  """
76
 
77
  def extract_json_from_llm_response(raw_response: str) -> dict:
78
+ # The default object is only used if parsing fails, providing a clean error message.
79
+ # The actual state preservation logic is in the /chat route.
80
  default = {
81
+ "assistant_reply": LLM_PARSE_ERROR_MESSAGE,
82
  "code_snippet": "",
83
  "state_updates": {"conversationSummary": "", "language": "Python"},
84
  "suggested_tags": [],
 
87
  if not raw_response or not isinstance(raw_response, str):
88
  return default
89
 
90
+ # 1. Use regex to find the JSON content inside the first code block (```json)
91
  m = re.search(r"```json\s*([\s\S]*?)\s*```", raw_response)
92
  json_string = m.group(1).strip() if m else raw_response
93
 
94
+ # 2. Refine candidate to just the JSON object content
95
  first = json_string.find('{')
96
  last = json_string.rfind('}')
97
  candidate = json_string[first:last+1] if first != -1 and last != -1 and first < last else json_string
98
 
99
+ # 3. Remove trailing commas which can break JSON parsing
100
  candidate = re.sub(r',\s*(?=[}\]])', '', candidate)
101
 
102
  try:
103
  parsed = json.loads(candidate)
104
  except Exception as e:
105
+ logger.warning("Failed to parse JSON from LLM output: %s. Candidate: %s", e, candidate[:200]) # Truncate candidate for cleaner logs
106
  return default
107
 
108
+ # 4. Validate and clean up the parsed dictionary
109
  if isinstance(parsed, dict) and "assistant_reply" in parsed:
110
  parsed.setdefault("code_snippet", "")
111
  parsed.setdefault("state_updates", {})
 
119
 
120
  return parsed
121
  else:
122
+ logger.warning("Parsed JSON missing required keys or invalid format. Returning default.")
123
  return default
124
 
125
  def detect_language_from_text(text: str) -> Optional[str]:
 
151
  chat_history: List[Dict[str, str]] = data.get("chat_history") or []
152
  assistant_state: AssistantState = data.get("assistant_state") or {}
153
 
154
+ # Initialize state from input. This is the "safe" state.
155
  state: AssistantState = {
156
  "conversationSummary": assistant_state.get("conversationSummary", ""),
157
  "language": assistant_state.get("language", "Python"),
158
  "taggedReplies": assistant_state.get("taggedReplies", []),
159
  }
160
 
161
+ # 1. Prepare LLM Messages from Full History (same as before)
162
  llm_messages = [{"role": "system", "content": PROGRAMMING_ASSISTANT_PROMPT}]
163
 
164
  last_user_message = ""
 
165
  for msg in chat_history:
166
  role = msg.get("role")
167
  content = msg.get("content")
 
170
  if role == "user":
171
  last_user_message = content
172
 
173
+ # 2. Language Detection & State Update (same as before)
174
  detected_lang = detect_language_from_text(last_user_message)
175
  if detected_lang and detected_lang.lower() != state["language"].lower():
176
  logger.info("Detected new language: %s", detected_lang)
177
  state["language"] = detected_lang
178
 
179
+ # 3. Inject Contextual Hint and State into the LAST user message (same as before)
180
  context_hint = f"Current Language: {state['language']}. Conversation Summary so far: {state['conversationSummary']}"
 
181
  if llm_messages and llm_messages[-1]["role"] == "user":
182
  llm_messages[-1]["content"] = f"USER MESSAGE: {last_user_message}\n\n[CONTEXT HINT: {context_hint}]"
183
  elif last_user_message:
 
199
  error_detail = "LLM Model Error: The model is likely decommissioned. Please check the 'LLM_MODEL' environment variable or the default model in app.py."
200
  return jsonify({"error": "LLM invocation failed", "detail": error_detail}), 500
201
 
202
+ # 4. State Update from LLM (NEW ROBUST LOGIC)
203
+
204
+ # Check if parsing failed (by comparing the reply to the known error message)
205
+ if parsed_result.get("assistant_reply") == LLM_PARSE_ERROR_MESSAGE:
206
+ final_reply_content = LLM_PARSE_ERROR_MESSAGE
207
+
208
+ # State and tags remain as initialized (from the input assistant_state), fulfilling the user request.
209
+ response_payload = {
210
+ "assistant_reply": final_reply_content,
211
+ "updated_state": state, # Keep the original input state
212
+ "suggested_tags": [],
213
+ }
214
+ return jsonify(response_payload)
215
+
216
+ # Parsing was successful. Safely update the state.
217
  updated_state_from_llm = parsed_result.get("state_updates", {})
218
 
219
  if 'conversationSummary' in updated_state_from_llm:
220
  state["conversationSummary"] = updated_state_from_llm["conversationSummary"]
221
+ # We allow the language to be updated only if it's explicitly set by the LLM AND it's a valid change.
222
+ if 'language' in updated_state_from_llm and updated_state_from_llm['language'].strip():
223
  state["language"] = updated_state_from_llm["language"]
224
 
225
+
226
  assistant_reply = parsed_result.get("assistant_reply")
227
  code_snippet = parsed_result.get("code_snippet")
228
 
229
  # 5. Final Response Payload: Combine the reply and the code snippet
 
230
  final_reply_content = assistant_reply
231
  if code_snippet and code_snippet.strip():
 
232
  if final_reply_content.strip():
233
  final_reply_content += "\n\n"
234
  final_reply_content += code_snippet
 
237
  final_reply_content = "I'm here to help with your code! What programming language are you using?"
238
 
239
  response_payload = {
240
+ "assistant_reply": final_reply_content,
241
  "updated_state": state,
242
  "suggested_tags": parsed_result.get("suggested_tags", []),
243
  }