WebashalarForML commited on
Commit
f85b1a5
·
verified ·
1 Parent(s): 4b90701

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -29
app.py CHANGED
@@ -36,19 +36,18 @@ if not GROQ_API_KEY:
36
  # raise ValueError("GROQ_API_KEY not set in environment")
37
  exit(1)
38
 
39
- # --- Flask app setup (MOVED HERE) ---
40
  BASE_DIR = Path(__file__).resolve().parent
41
  static_folder = BASE_DIR / "static"
42
 
43
- # The 'app' object MUST be defined before its first use, e.g., in @app.route
44
  app = Flask(__name__, static_folder=str(static_folder), static_url_path="/static")
45
  CORS(app)
 
46
  # --- LLM setup ---
47
- # Using a model that's good for coding tasks
48
  llm = ChatGroq(
49
- model=os.getenv("LLM_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct"), # Use the supported model
50
- temperature=0,
51
- # max_tokens=2048,
52
  api_key=GROQ_API_KEY,
53
  )
54
 
@@ -60,35 +59,39 @@ You are an expert programming assistant. Your role is to provide code suggestion
60
  - **Language Adaptation:** Adjust your suggestions, code, and explanations to the programming language specified in the 'language' field of the 'AssistantState'.
61
 
62
  STRICT OUTPUT FORMAT (JSON ONLY):
63
- Return a single JSON object with the following keys:
64
- - assistant_reply: string // a natural language reply TO THE USER, INCLUDING ANY REQUESTED CODE BLOCK(S).
 
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
 
68
  Rules:
 
69
  - ALWAYS include `assistant_reply` as a non-empty string.
70
- - If the user is asking for code, the code MUST be enclosed in appropriate markdown code blocks (e.g., ```python\n...\n```) and placed within the `assistant_reply` string.
71
- - Do NOT produce any text outside the JSON object.
72
- - Be concise in the non-code parts of `assistant_reply`.
73
  """
74
 
75
-
76
  def extract_json_from_llm_response(raw_response: str) -> dict:
77
  default = {
78
- "assistant_reply": "I'm sorry I couldn't understand that. Could you please rephrase?",
 
79
  "state_updates": {"conversationSummary": "", "language": "Python"},
80
  "suggested_tags": [],
81
  }
82
- # Simplified JSON extraction logic
83
  if not raw_response or not isinstance(raw_response, str):
84
  return default
85
 
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
  first = json_string.find('{')
90
  last = json_string.rfind('}')
91
  candidate = json_string[first:last+1] if first != -1 and last != -1 and first < last else json_string
 
 
92
  candidate = re.sub(r',\s*(?=[}\]])', '', candidate)
93
 
94
  try:
@@ -97,11 +100,18 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
97
  logger.warning("Failed to parse JSON from LLM output: %s. Candidate: %s", e, candidate)
98
  return default
99
 
100
- if isinstance(parsed, dict) and "assistant_reply" in parsed and parsed["assistant_reply"].strip():
 
 
101
  parsed.setdefault("state_updates", {})
102
  parsed["state_updates"].setdefault("conversationSummary", "")
103
  parsed["state_updates"].setdefault("language", "Python")
104
  parsed.setdefault("suggested_tags", [])
 
 
 
 
 
105
  return parsed
106
  else:
107
  logger.warning("Parsed JSON missing 'assistant_reply' or invalid format. Returning default.")
@@ -120,7 +130,7 @@ def detect_language_from_text(text: str) -> Optional[str]:
120
  return None
121
 
122
  # --- Flask routes ---
123
- @app.route("/", methods=["GET"]) # <-- 'app' is now defined!
124
  def serve_frontend():
125
  try:
126
  return app.send_static_file("frontend.html")
@@ -133,7 +143,6 @@ def chat():
133
  if not isinstance(data, dict):
134
  return jsonify({"error": "invalid request body"}), 400
135
 
136
- # chat_history now receives the full conversation history from the corrected frontend
137
  chat_history: List[Dict[str, str]] = data.get("chat_history") or []
138
  assistant_state: AssistantState = data.get("assistant_state") or {}
139
 
@@ -164,15 +173,11 @@ def chat():
164
  state["language"] = detected_lang
165
 
166
  # 3. Inject Contextual Hint and State into the LAST user message
167
- # This ensures the LLM has immediate access to the *summarized* history and current language.
168
  context_hint = f"Current Language: {state['language']}. Conversation Summary so far: {state['conversationSummary']}"
169
 
170
- # Update the content of the last message in llm_messages
171
  if llm_messages and llm_messages[-1]["role"] == "user":
172
- # Overwrite the last user message to include the context hint
173
  llm_messages[-1]["content"] = f"USER MESSAGE: {last_user_message}\n\n[CONTEXT HINT: {context_hint}]"
174
  elif last_user_message:
175
- # Should not happen with the corrected frontend, but handles fresh start gracefully
176
  llm_messages.append({"role": "user", "content": f"USER MESSAGE: {last_user_message}\n\n[CONTEXT HINT: {context_hint}]"})
177
 
178
 
@@ -180,13 +185,12 @@ def chat():
180
  logger.info("Invoking LLM with full history and prepared prompt...")
181
  llm_response = llm.invoke(llm_messages)
182
  raw_response = llm_response.content if hasattr(llm_response, "content") else str(llm_response)
183
- print("llm_response",llm_response)
184
  logger.info(f"Raw LLM response: {raw_response}")
185
  parsed_result = extract_json_from_llm_response(raw_response)
186
 
187
  except Exception as e:
188
  logger.exception("LLM invocation failed")
189
- # CRITICAL FIX: The Groq model might still be the problem if environment is inconsistent.
190
  error_detail = str(e)
191
  if 'decommissioned' in error_detail:
192
  error_detail = "LLM Model Error: The model is likely decommissioned. Please check the 'LLM_MODEL' environment variable or the default model in app.py."
@@ -195,19 +199,28 @@ def chat():
195
  # 4. State Update from LLM
196
  updated_state_from_llm = parsed_result.get("state_updates", {})
197
 
198
- # CRUCIAL: Update state with the NEW summary generated by the LLM
199
  if 'conversationSummary' in updated_state_from_llm:
200
  state["conversationSummary"] = updated_state_from_llm["conversationSummary"]
201
  if 'language' in updated_state_from_llm:
202
  state["language"] = updated_state_from_llm["language"]
203
 
204
  assistant_reply = parsed_result.get("assistant_reply")
205
- if not assistant_reply or not isinstance(assistant_reply, str) or not assistant_reply.strip():
206
- assistant_reply = "I'm here to help with your code! What programming language are you using?"
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- # 5. Final Response Payload
209
  response_payload = {
210
- "assistant_reply": assistant_reply,
211
  "updated_state": state,
212
  "suggested_tags": parsed_result.get("suggested_tags", []),
213
  }
 
36
  # raise ValueError("GROQ_API_KEY not set in environment")
37
  exit(1)
38
 
39
+ # --- Flask app setup ---
40
  BASE_DIR = Path(__file__).resolve().parent
41
  static_folder = BASE_DIR / "static"
42
 
 
43
  app = Flask(__name__, static_folder=str(static_folder), static_url_path="/static")
44
  CORS(app)
45
+
46
  # --- LLM setup ---
 
47
  llm = ChatGroq(
48
+ model=os.getenv("LLM_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct"),
49
+ temperature=0.1, # Set a lower, deterministic temperature
50
+ max_tokens=2048, # Ensure max_tokens is set to avoid truncation
51
  api_key=GROQ_API_KEY,
52
  )
53
 
 
59
  - **Language Adaptation:** Adjust your suggestions, code, and explanations to the programming language specified in the 'language' field of the 'AssistantState'.
60
 
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
 
68
  Rules:
69
+ - ALWAYS include all four top-level keys: `assistant_reply`, `code_snippet`, `state_updates`, and `suggested_tags`.
70
  - ALWAYS include `assistant_reply` as a non-empty string.
71
+ - Do NOT produce any text outside the JSON block.
 
 
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": [],
80
  }
81
+
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:
 
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", {})
107
  parsed["state_updates"].setdefault("conversationSummary", "")
108
  parsed["state_updates"].setdefault("language", "Python")
109
  parsed.setdefault("suggested_tags", [])
110
+
111
+ # Ensure reply is not empty
112
+ if not parsed["assistant_reply"].strip():
113
+ parsed["assistant_reply"] = "I need a clearer instruction to provide a reply."
114
+
115
  return parsed
116
  else:
117
  logger.warning("Parsed JSON missing 'assistant_reply' or invalid format. Returning default.")
 
130
  return None
131
 
132
  # --- Flask routes ---
133
+ @app.route("/", methods=["GET"])
134
  def serve_frontend():
135
  try:
136
  return app.send_static_file("frontend.html")
 
143
  if not isinstance(data, dict):
144
  return jsonify({"error": "invalid request body"}), 400
145
 
 
146
  chat_history: List[Dict[str, str]] = data.get("chat_history") or []
147
  assistant_state: AssistantState = data.get("assistant_state") or {}
148
 
 
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:
 
181
  llm_messages.append({"role": "user", "content": f"USER MESSAGE: {last_user_message}\n\n[CONTEXT HINT: {context_hint}]"})
182
 
183
 
 
185
  logger.info("Invoking LLM with full history and prepared prompt...")
186
  llm_response = llm.invoke(llm_messages)
187
  raw_response = llm_response.content if hasattr(llm_response, "content") else str(llm_response)
188
+
189
  logger.info(f"Raw LLM response: {raw_response}")
190
  parsed_result = extract_json_from_llm_response(raw_response)
191
 
192
  except Exception as e:
193
  logger.exception("LLM invocation failed")
 
194
  error_detail = str(e)
195
  if 'decommissioned' in error_detail:
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."
 
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
218
+
219
+ if not final_reply_content.strip():
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
  }