Spaces:
Running
Running
| # from flask import Flask, Blueprint, jsonify, send_file, abort, make_response, request | |
| from flask import Flask, Blueprint, jsonify, send_file, abort, make_response, request, current_app | |
| from flask_cors import CORS | |
| import os | |
| print(f"GOOGLE_APPLICATION_CREDENTIALS: {os.getenv('GOOGLE_APPLICATION_CREDENTIALS')}") | |
| import io | |
| import uuid | |
| import requests | |
| import re | |
| questions_bp = Blueprint("questions", __name__) | |
| app = Flask(__name__) | |
| CORS(app) | |
| def home(): | |
| return "Welcome to the Flask app! The server is running." | |
| # read from env as a fallback (works for local .env and HF Secrets) | |
| COHERE_API_KEY = os.getenv("COHERE_API_KEY", "") | |
| def _cohere_headers(): | |
| """ | |
| Prefer a key set on the Flask app (e.g., in verification.py: app.config["COHERE_API_KEY"]), | |
| otherwise fall back to the environment variable COHERE_API_KEY. | |
| """ | |
| api_key = current_app.config.get("COHERE_API_KEY") or COHERE_API_KEY | |
| if not api_key: | |
| return None | |
| return { | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json", | |
| } | |
| # (1) UPDATED: Cohere v2 Chat endpoint | |
| COHERE_API_URL = 'https://api.cohere.com/v2/chat' | |
| def _extract_text_v2(resp_json: dict) -> str: | |
| """ | |
| v2 /chat returns: | |
| { "message": { "content": [ { "type": "text", "text": "..." } ] } } | |
| """ | |
| msg = resp_json.get("message", {}) | |
| content = msg.get("content", []) | |
| if isinstance(content, list) and content: | |
| block = content[0] | |
| if isinstance(block, dict): | |
| return (block.get("text") or "").strip() | |
| return "" | |
| def validate_topic(topic): | |
| validation_prompt = f""" | |
| ou are a highly knowledgeable AI grammar expert. Your task is to evaluate whether the given topic relates to **English grammar** or not. | |
| **Input Topic:** "{topic}" | |
| ### **Instructions:** | |
| - If the input **exactly refers to** grammar concepts (such as **parts of speech**, **verb tenses**, **sentence structure**, **grammar rules**, etc.), respond with `"Grammar"`. | |
| - If the input **seems to be a general question or concept** that is **not directly related to grammar**, such as general knowledge, science, history, or unrelated fields, respond with `"Not Grammar"`. | |
| - If the input is in the form of a **question** (e.g., "What is subject-verb agreement?"), respond with `"ask grammar topics"`. | |
| - If the topic refers to a **specific grammar concept** (e.g., **noun**, **verb**, **preposition**, **past tense**, etc.), always classify it as `"Grammar"`. | |
| - **Do not include any explanations or examples**. Your answer must only be `"Grammar"`, `"Not Grammar"`, or `"ask grammar topics"`, depending on whether the topic is relevant to grammar. | |
| - If the input is **unclear**, err on the side of classifying it as `"Not Grammar"` rather than `"Grammar"`. | |
| Your response must only be one of these three options: | |
| - `"Grammar"` | |
| - `"Not Grammar"` | |
| - `"ask grammar topics"` | |
| No extra text or explanation. | |
| """ | |
| headers = _cohere_headers() | |
| if not headers: | |
| return "Error: COHERE_API_KEY not set" | |
| # (2) UPDATED: messages payload | |
| payload = { | |
| 'model': 'command-r-08-2024', | |
| 'messages': [ | |
| {'role': 'user', 'content': validation_prompt} | |
| ], | |
| 'max_tokens': 5 | |
| } | |
| try: | |
| response = requests.post(COHERE_API_URL, json=payload, headers=headers) | |
| # (3) UPDATED: v2 parsing | |
| validation_result = _extract_text_v2(response.json()) | |
| if validation_result not in ["Grammar", "Not Grammar", "ask grammar topics"]: | |
| return "Not Grammar" | |
| return validation_result | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def generate_questions_test(): | |
| try: | |
| data = request.get_json() | |
| topic = data.get('topic', '').strip() | |
| validation_result = validate_topic(topic) | |
| if validation_result != "Grammar": | |
| return jsonify({"message": "Please enter a valid **grammar topic**, not a general word or unrelated question."}), 400 | |
| difficulty = data.get('difficulty', 'basic') | |
| print(f"Generating {difficulty} questions for topic: {topic}") | |
| if difficulty == 'basic': | |
| prompt = f""" | |
| Generate five **completely new and unique** very basic-level fill-in-the-blank grammar questions **every time** on the topic '{topic}'. | |
| ### Rules: | |
| - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'. | |
| - Each question must have exactly one blank represented by '_______' (not two blanks or underscores inside the sentence). | |
| - Each question must have a different theme for variety. | |
| - Use different sentence structures; avoid predictable patterns. | |
| - Avoid long words or abstract concepts. | |
| - Focus on the topic '{topic}', and ensure the blank is the key part of speech. | |
| - Each question must include the correct answer in parentheses at the end. | |
| - Do not include any explanations or instructions—only the five questions. | |
| """ | |
| elif difficulty == 'intermediate': | |
| prompt = f""" | |
| Generate five **completely new and unique** intermediate-level fill-in-the-blank grammar questions **every time** on the topic '{topic}'. | |
| ### Rules: | |
| - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'. | |
| - Each question must have exactly one blank represented by '_______'. | |
| - Slightly more challenging than basic-level; use a wider range of sentence structures and vocabulary. | |
| - Each question must have a different theme. | |
| - Sentences should be longer and include more detail. | |
| - Focus on the topic '{topic}', and ensure the blank is the key part of speech. | |
| - Each question must include the correct answer in parentheses at the end. | |
| - Do not include any explanations or instructions—only the five questions. | |
| """ | |
| elif difficulty == 'expert': | |
| prompt = f""" | |
| Generate five **completely new and unique** advanced-level (C1) fill-in-the-blank grammar questions **every time** on the topic '{topic}'. | |
| ### Rules: | |
| - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'. | |
| - Each question must have exactly one blank represented by '_______'. | |
| - More challenging than intermediate (C1); require expert-level mastery of grammar and context. | |
| - Ensure varied and sophisticated vocabulary; avoid basic words. | |
| - Each question should require nuanced comprehension; test advanced grammar patterns. | |
| - The blank must be the key part of the sentence (not an obvious answer). | |
| - Each question must include the correct answer in parentheses at the end. | |
| - Do not include any explanations or instructions—only the five questions. | |
| """ | |
| else: | |
| return jsonify({"error": "Invalid difficulty level"}), 400 | |
| headers = _cohere_headers() | |
| if not headers: | |
| return jsonify({"error": "COHERE_API_KEY not set on the server"}), 500 | |
| # (2) UPDATED: messages payload | |
| payload = { | |
| 'model': 'command-r-08-2024', | |
| 'messages': [ | |
| {'role': 'user', 'content': prompt} | |
| ], | |
| 'max_tokens': 1000 | |
| } | |
| response = requests.post(COHERE_API_URL, json=payload, headers=headers) | |
| print("Response status code:", response.status_code) | |
| print("Response content:", response.text) | |
| if response.status_code == 200: | |
| # (3) UPDATED: v2 parsing | |
| text = _extract_text_v2(response.json()) | |
| # Return same style as before but adapted (text content) | |
| return jsonify({"text": text}) | |
| else: | |
| return jsonify({"error": "Failed to fetch questions", "details": response.text}), 500 | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def validate_answer(): | |
| try: | |
| data = request.get_json() | |
| topic = data.get('topic', '') | |
| question = data.get('question', '') | |
| user_answer = data.get('user_answer', '') | |
| if not question or not user_answer or not topic: | |
| return jsonify({'error': 'Topic, question, and user answer are required'}), 400 | |
| prompt = f""" | |
| You are a highly knowledgeable grammar assistant. Validate whether the user's answer to the following question is correct or not based on {topic}. If the answer is incorrect, provide a helpful hint. | |
| Topic: {topic} | |
| Question: "{question}" | |
| User's Answer: "{user_answer}" | |
| Is the answer correct? If not, please explain why and give a hint. | |
| """ | |
| headers = _cohere_headers() | |
| if not headers: | |
| return jsonify({"error": "COHERE_API_KEY not set on the server"}), 500 | |
| # (2) UPDATED: messages payload | |
| payload = { | |
| 'model': 'command-r-08-2024', | |
| 'messages': [ | |
| {'role': 'user', 'content': prompt} | |
| ], | |
| 'max_tokens': 100, | |
| 'temperature': 0.7 | |
| } | |
| response = requests.post(COHERE_API_URL, headers=headers, json=payload) | |
| print(f"Status Code: {response.status_code}") | |
| print(f"Response Body: {response.text}") | |
| if response.status_code == 200: | |
| # (3) UPDATED: v2 parsing | |
| text = _extract_text_v2(response.json()) | |
| return jsonify({"text": text}) | |
| else: | |
| return jsonify({'error': 'Failed to fetch data from Cohere API'}), 500 | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| # Function to validate an individual answer using Cohere API | |
| def validate_answer_with_ai(topic, question, user_answer): | |
| try: | |
| prompt = f""" | |
| You are a highly knowledgeable grammar assistant. Validate whether the user's answer to the following question is correct or not based on {topic}. If the answer is incorrect, provide a helpful hint. | |
| Topic: {topic} | |
| Question: "{question}" | |
| User's Answer: "{user_answer}" | |
| Is the answer correct? If not, please explain why and give a hint. | |
| """ | |
| headers = _cohere_headers() | |
| if not headers: | |
| return "Error: COHERE_API_KEY not set" | |
| # (2) UPDATED: messages payload | |
| payload = { | |
| 'model': 'command-r-08-2024', | |
| 'messages': [ | |
| {'role': 'user', 'content': prompt} | |
| ], | |
| 'max_tokens': 200, | |
| 'temperature': 0.7 | |
| } | |
| # (1) UPDATED URL used here too | |
| response = requests.post(COHERE_API_URL, headers=headers, json=payload) | |
| if response.status_code == 200: | |
| # (3) UPDATED: v2 parsing | |
| return _extract_text_v2(response.json()) | |
| else: | |
| return f"Error: {response.status_code} - {response.text}" | |
| except Exception as e: | |
| return f"An error occurred: {str(e)}" | |
| def validate_all_answers(): | |
| try: | |
| data = request.get_json() | |
| questions = data.get('questions', []) | |
| if not questions: | |
| return jsonify({'error': 'No questions provided'}), 400 | |
| validation_results = [] | |
| for item in questions: | |
| topic = item.get('topic', '') | |
| question = item.get('question', '') | |
| user_answer = item.get('user_answer', '') | |
| if not topic or not question or not user_answer: | |
| validation_results.append({ | |
| 'question': question, | |
| 'error': 'Missing required fields (topic, question, or answer).' | |
| }) | |
| continue | |
| validation_response = validate_answer_with_ai(topic, question, user_answer) | |
| hint = None | |
| if isinstance(validation_response, str) and ( | |
| "incorrect" in validation_response.lower() or "not correct" in validation_response.lower() | |
| ): | |
| hint = generate_hint(topic, question, user_answer) | |
| validation_results.append({ | |
| 'question': question, | |
| 'user_answer': user_answer, | |
| 'validation_response': validation_response, | |
| 'hint': hint | |
| }) | |
| return jsonify({'results': validation_results}) | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| def generate_hint(topic, question, user_answer): | |
| try: | |
| prompt = f""" | |
| You are a highly skilled grammar assistant. Your task is to generate a helpful hint for the user to improve their answer based on the following question. | |
| Topic: {topic} | |
| Question: "{question}" | |
| User's Answer: "{user_answer}" | |
| If the user's answer is incorrect, provide a specific, actionable hint to help the user correct their answer. | |
| The hint should include: | |
| - Explanation of the error made by the user. | |
| - A hint on the correct grammatical structure or word form. | |
| - A hint on how to structure the sentence correctly **without revealing the exact answer**. | |
| Please make sure the hint is **clear** and **helpful** for the user, **without revealing the correct answer**. | |
| """ | |
| headers = _cohere_headers() | |
| if not headers: | |
| return "Error: COHERE_API_KEY not set" | |
| # (2) UPDATED: messages payload | |
| payload = { | |
| 'model': 'command-r-08-2024', | |
| 'messages': [ | |
| {'role': 'user', 'content': prompt} | |
| ], | |
| 'max_tokens': 250, | |
| 'temperature': 0.7 | |
| } | |
| response = requests.post(COHERE_API_URL, headers=headers, json=payload) | |
| if response.status_code == 200: | |
| # (3) UPDATED: v2 parsing | |
| return _extract_text_v2(response.json()) | |
| else: | |
| return f"Error: {response.status_code} - {response.text}" | |
| except Exception as e: | |
| return f"An error occurred: {str(e)}" | |
| if __name__ == '__main__': | |
| app.register_blueprint(questions_bp, url_prefix='') # local exposure | |
| app.run(host='0.0.0.0', port=5012, debug=True) | |