File size: 8,088 Bytes
ceaa691 441bbe1 ceaa691 4f26c25 441bbe1 51c2ea7 441bbe1 118da3d 441bbe1 4f26c25 4807519 441bbe1 ceaa691 42e73f2 4f26c25 4807519 42e73f2 4f26c25 441bbe1 4f26c25 4807519 4f26c25 4807519 7a6087c 441bbe1 ceaa691 7a6087c 118da3d 7a6087c 118da3d 7a6087c 99eddc5 118da3d 18453d3 118da3d 18453d3 99eddc5 118da3d 18453d3 118da3d 18453d3 118da3d 99eddc5 118da3d 18453d3 441bbe1 ceaa691 7a6087c 42e73f2 4f26c25 34bf502 441bbe1 34bf502 118da3d 7a6087c 441bbe1 118da3d 34bf502 441bbe1 7a6087c 118da3d 7a6087c 441bbe1 34bf502 441bbe1 7a6087c 34bf502 441bbe1 34bf502 118da3d 34bf502 441bbe1 2d33bc7 ceaa691 4f26c25 ecb34f8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
#!/usr/bin/env python3
import os
import json
import logging
import re
from typing import Dict, List, Optional
from pathlib import Path
from flask import Flask, request, jsonify
from flask_cors import CORS
from dotenv import load_dotenv
from langchain_groq import ChatGroq
from typing_extensions import TypedDict
# --- Type Definitions ---
class AssistantState(TypedDict):
conversationSummary: str
language: str
mode: str # "teacher" or "student"
# --- Logging ---
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("code-assistant")
# --- Load environment ---
load_dotenv()
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
if not GROQ_API_KEY:
logger.error("GROQ_API_KEY not set in environment")
raise RuntimeError("GROQ_API_KEY not set in environment")
# --- Flask app setup ---
BASE_DIR = Path(__file__).resolve().parent
static_folder = BASE_DIR / "static"
app = Flask(__name__, static_folder=str(static_folder), static_url_path="/static")
CORS(app)
# --- LLM setup ---
llm = ChatGroq(
model=os.getenv("LLM_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct"),
temperature=0.1,
max_tokens=2048,
api_key=GROQ_API_KEY,
)
# --- Helper functions ---
def detect_language_from_text(text: str) -> Optional[str]:
if not text:
return None
lower = text.lower()
known_languages = ["python", "javascript", "java", "c++", "c#", "go", "ruby", "php", "typescript", "swift"]
lang_match = re.search(r'\b(in|using|for)\s+(' + '|'.join(known_languages) + r')\b', lower)
if lang_match:
return lang_match.group(2).capitalize()
return None
def update_summary(chat_history: List[Dict[str, str]]) -> str:
"""
Simple heuristic summary: last 6 messages concatenated.
Replace with your own summarization chain if desired.
"""
recent_msgs = chat_history[-6:]
summary = " | ".join(f"{m['role']}: {m['content'][:50].replace('\n',' ')}" for m in recent_msgs)
return summary
# def build_system_prompt(language: str, conversation_summary: str, mode: str) -> str:
# """
# Build system prompt dynamically based on mode.
# """
# base = f"You are a helpful programming assistant. Current language: {language}. Conversation summary: {conversation_summary}\n\n"
# if mode == "student":
# base += (
# "You are in STUDENT MODE: Your goal is to engage the user in problem-solving and learning. "
# "Do NOT give complete answers or full code. Instead:\n"
# " - Ask guiding questions to make the user think.\n"
# " - Give hints, small examples, or pseudocode to help the user discover the solution.\n"
# " - Encourage step-by-step problem solving and curiosity.\n"
# " - Only provide full solutions as a last resort if the user is completely stuck."
# )
# else: # teacher mode default
# base += (
# "You are in TEACHER MODE: Provide detailed suggestions, structured explanations, and full code examples. "
# "Explain reasoning clearly and comprehensively."
# )
# return base
def build_system_prompt(language: str, conversation_summary: str, mode: str) -> str:
"""
Build system prompt dynamically based on mode, restricting to code/problem-solving only.
"""
base = (
f"You are a helpful programming assistant. "
f"Your sole purpose is to assist with coding, programming, debugging, and technical problem solving. "
f"Current language: {language}. "
f"Conversation summary: {conversation_summary}\n\n"
)
if mode == "student":
base += (
"You are in STUDENT MODE.\n"
"Your goal is to engage the user ONLY in programming, coding, and problem-solving tasks.\n"
"STRICT RULES:\n"
" - ❌ Do NOT give complete answers or full code unless the user is completely stuck.\n"
" - ❌ Do NOT answer general knowledge, personal, or unrelated questions (e.g., names, trivia, history, etc.).\n"
" - ❌ Politely refuse any out-of-context or non-programming queries by replying: "
"\"I'm here only to help with programming or technical problem-solving questions.\"\n"
" - ✅ ALWAYS guide the user through the problem-solving process instead of directly giving an answer.\n"
" - ✅ Ask guiding questions to make the user think about coding problems.\n"
" - ✅ Give hints, small examples, or pseudocode to help the user discover the solution.\n"
" - ✅ Encourage step-by-step problem solving and curiosity.\n"
)
else: # teacher mode default
base += (
"You are in TEACHER MODE.\n"
"Your goal is to provide detailed explanations, structured reasoning, and complete code examples when needed.\n"
"STRICT RULES:\n"
" - ❌ Only answer questions related to programming, coding, or technical problem solving.\n"
" - ❌ Politely refuse any unrelated, personal, or general knowledge questions by replying: "
"\"I'm here only to help with programming or technical problem-solving questions.\"\n"
" - ✅ Provide clear reasoning, best practices, and full working examples for programming tasks.\n"
)
return base
return base
# --- Routes ---
@app.route("/", methods=["GET"])
def serve_frontend():
try:
return app.send_static_file("frontend.html")
except Exception:
return "<h3>frontend.html not found in static/ — please add your frontend.html there.</h3>", 404
@app.route("/chat", methods=["POST"])
def chat():
data = request.get_json(force=True)
chat_history = data.get("chat_history", [])
assistant_state = data.get("assistant_state", {})
conversation_summary = assistant_state.get("conversationSummary", "")
language = assistant_state.get("language", "Python")
mode = assistant_state.get("mode", "teacher").lower()
if mode not in ("teacher", "student"):
mode = "teacher"
# Detect language from last user message
last_user_msg = ""
for msg in reversed(chat_history):
if msg.get("role") == "user" and msg.get("content"):
last_user_msg = msg["content"]
break
detected_lang = detect_language_from_text(last_user_msg)
if detected_lang and detected_lang.lower() != language.lower():
logger.info(f"Detected new language: {detected_lang}")
language = detected_lang
# Build system prompt based on mode
system_prompt = build_system_prompt(language, conversation_summary, mode)
messages = [{"role": "system", "content": system_prompt}]
messages.extend(chat_history)
try:
llm_response = llm.invoke(messages)
assistant_reply = llm_response.content if hasattr(llm_response, "content") else str(llm_response)
except Exception as e:
logger.exception("LLM invocation failed")
return jsonify({
"assistant_reply": "Sorry, the assistant is currently unavailable. Please try again later.",
"updated_state": {
"conversationSummary": conversation_summary,
"language": language,
"mode": mode,
},
"chat_history": chat_history,
}), 500
# Append assistant reply to chat history
chat_history.append({"role": "assistant", "content": assistant_reply})
# Update conversation summary
conversation_summary = update_summary(chat_history)
return jsonify({
"assistant_reply": assistant_reply,
"updated_state": {
"conversationSummary": conversation_summary,
"language": language,
"mode": mode,
},
"chat_history": chat_history,
})
@app.route("/ping", methods=["GET"])
def ping():
return jsonify({"status": "ok"})
if __name__ == "__main__":
port = int(os.getenv("PORT", 7860))
app.run(host="0.0.0.0", port=port, debug=True)
|