File size: 5,452 Bytes
ceaa691 441bbe1 ceaa691 4f26c25 441bbe1 2d33bc7 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 118da3d 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 118da3d |
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 |
#!/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: Guide the user on *how to achieve* their programming tasks. "
"Provide general explanations and pseudocode examples when appropriate. Avoid full detailed code unless necessary."
)
else: # teacher mode default
base += (
"You are in TEACHER MODE: Provide detailed suggestions, structured explanations, and full code examples."
)
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) |