QuantumChecker 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/PKG-INFO +2 -3
- quantumchecker-0.3.2/QuantumCheck/main.py +138 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/powerbi_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/python_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/sql_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/ssis_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumChecker.egg-info/PKG-INFO +2 -3
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumChecker.egg-info/SOURCES.txt +1 -2
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/README.md +1 -2
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/setup.py +1 -1
- quantumchecker-0.3.2/tests/test.py +82 -0
- quantumchecker-0.3.0/QuantumCheck/main.py +0 -230
- quantumchecker-0.3.0/tests/test.py +0 -135
- quantumchecker-0.3.0/tests/test2.py +0 -31
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/__init__.py +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumCheck/prompts.py +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumChecker.egg-info/dependency_links.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumChecker.egg-info/requires.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/QuantumChecker.egg-info/top_level.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuantumChecker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.
|
|
5
5
|
Author: Qobiljon
|
|
6
6
|
Author-email: qobiljonkhayrullayev@gmail.com
|
|
@@ -37,13 +37,12 @@ Q1: What is a Python list? Explain with an example.
|
|
|
37
37
|
Q2: Write an SQL query to select all records from a table named 'students'.
|
|
38
38
|
"""
|
|
39
39
|
answer_path = "sample_submissions/student1_answer.py"
|
|
40
|
-
api_keys = ["your_api_key_1", "your_api_key_2"]
|
|
41
40
|
question_type = "python"
|
|
42
41
|
|
|
43
42
|
result = await evaluator.evaluate_from_content(
|
|
44
43
|
question_content=question_content,
|
|
45
44
|
answer_path=answer_path,
|
|
46
|
-
|
|
45
|
+
api_key="your_api_key",
|
|
47
46
|
question_type=question_type
|
|
48
47
|
)
|
|
49
48
|
print(result)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import zipfile
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import List, Dict
|
|
6
|
+
from .python_evaluator import PythonEvaluator
|
|
7
|
+
from .sql_evaluator import SQLEvaluator
|
|
8
|
+
from .powerbi_evaluator import PowerBIEvaluator
|
|
9
|
+
from .ssis_evaluator import SSISEvaluator
|
|
10
|
+
import asyncio
|
|
11
|
+
|
|
12
|
+
_logger_cache = {}
|
|
13
|
+
|
|
14
|
+
class HomeworkEvaluator:
|
|
15
|
+
EVALUATOR_REGISTRY = {
|
|
16
|
+
"python": PythonEvaluator,
|
|
17
|
+
"sql": SQLEvaluator,
|
|
18
|
+
"powerbi": PowerBIEvaluator,
|
|
19
|
+
"ssis": SSISEvaluator
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
EXTENSION_TO_TYPE = {
|
|
23
|
+
".py": "python",
|
|
24
|
+
".sql": "sql",
|
|
25
|
+
".pbit": "powerbi",
|
|
26
|
+
".pdf": "powerbi",
|
|
27
|
+
".dtsx": "ssis",
|
|
28
|
+
".DTSX": "ssis",
|
|
29
|
+
".txt": "text",
|
|
30
|
+
".md": "text"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self, log_level: int = logging.INFO):
|
|
34
|
+
self.log_level = log_level
|
|
35
|
+
self._lock = asyncio.Lock()
|
|
36
|
+
self._last_request_time = None
|
|
37
|
+
|
|
38
|
+
def _get_logger(self, log_type: str) -> logging.Logger:
|
|
39
|
+
log_name = f"{log_type}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
|
|
40
|
+
if log_name not in _logger_cache:
|
|
41
|
+
logger = logging.getLogger(log_name)
|
|
42
|
+
logger.setLevel(self.log_level)
|
|
43
|
+
if not logger.handlers:
|
|
44
|
+
handler = logging.StreamHandler()
|
|
45
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
|
|
46
|
+
logger.addHandler(handler)
|
|
47
|
+
_logger_cache[log_name] = logger
|
|
48
|
+
return _logger_cache[log_name]
|
|
49
|
+
|
|
50
|
+
def parse_questions(self, content: str) -> List[str]:
|
|
51
|
+
logger = self._get_logger("QuantumCheck.main")
|
|
52
|
+
questions = [q.strip() for q in content.split("\n\n") if q.strip()]
|
|
53
|
+
if not questions:
|
|
54
|
+
raise ValueError("No valid questions found in content")
|
|
55
|
+
return questions
|
|
56
|
+
|
|
57
|
+
def _detect_zip_content_type(self, zip_path: str, logger: logging.Logger) -> str:
|
|
58
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
59
|
+
extensions = {os.path.splitext(name)[1].lower() for name in zip_ref.namelist()}
|
|
60
|
+
file_types = [self.EXTENSION_TO_TYPE.get(ext, "text") for ext in extensions if ext]
|
|
61
|
+
if "python" in file_types:
|
|
62
|
+
return "python"
|
|
63
|
+
elif "sql" in file_types:
|
|
64
|
+
return "sql"
|
|
65
|
+
elif "powerbi" in file_types:
|
|
66
|
+
return "powerbi"
|
|
67
|
+
elif "ssis" in file_types:
|
|
68
|
+
return "ssis"
|
|
69
|
+
else:
|
|
70
|
+
return "text"
|
|
71
|
+
|
|
72
|
+
async def evaluate_from_content(
|
|
73
|
+
self,
|
|
74
|
+
question_content: str,
|
|
75
|
+
answer_path: str,
|
|
76
|
+
api_key: str,
|
|
77
|
+
question_type: str
|
|
78
|
+
) -> Dict[str, any]:
|
|
79
|
+
async with self._lock:
|
|
80
|
+
now = datetime.now()
|
|
81
|
+
if self._last_request_time:
|
|
82
|
+
elapsed = (now - self._last_request_time).total_seconds()
|
|
83
|
+
if elapsed < 5:
|
|
84
|
+
await asyncio.sleep(5 - elapsed)
|
|
85
|
+
self._last_request_time = datetime.now()
|
|
86
|
+
|
|
87
|
+
logger = self._get_logger("QuantumCheck.main")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
questions = self.parse_questions(question_content)
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
return {
|
|
93
|
+
"score": 0,
|
|
94
|
+
"feedback": f"Error parsing question content: {str(e)}",
|
|
95
|
+
"issues": [str(e)],
|
|
96
|
+
"recommendations": []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
answer_path = answer_path.strip()
|
|
100
|
+
_, ext = os.path.splitext(answer_path)
|
|
101
|
+
ext = ext.lower()
|
|
102
|
+
|
|
103
|
+
if ext == ".zip":
|
|
104
|
+
logger = self._get_logger("zip")
|
|
105
|
+
file_type = self._detect_zip_content_type(answer_path, logger)
|
|
106
|
+
else:
|
|
107
|
+
file_type = self.EXTENSION_TO_TYPE.get(ext, "text")
|
|
108
|
+
logger = self._get_logger(file_type)
|
|
109
|
+
|
|
110
|
+
eval_type = question_type if question_type in self.EVALUATOR_REGISTRY else file_type
|
|
111
|
+
|
|
112
|
+
if not os.path.exists(answer_path):
|
|
113
|
+
return {
|
|
114
|
+
"score": 0,
|
|
115
|
+
"feedback": f"Answer file not found: {answer_path}",
|
|
116
|
+
"issues": [f"Answer file not found: {answer_path}"],
|
|
117
|
+
"recommendations": []
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
evaluator_class = self.EVALUATOR_REGISTRY.get(eval_type, PythonEvaluator)
|
|
121
|
+
evaluator = evaluator_class(api_key)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
evaluation = evaluator.evaluate(questions, answer_path, temp_dir=f"temp_extract_{os.getpid()}")
|
|
125
|
+
return {
|
|
126
|
+
"score": evaluation.get("score", 0),
|
|
127
|
+
"feedback": evaluation.get("feedback", "No feedback provided"),
|
|
128
|
+
"issues": evaluation.get("issues", []),
|
|
129
|
+
"recommendations": evaluation.get("recommendations", [])
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
return {
|
|
134
|
+
"score": 0,
|
|
135
|
+
"feedback": f"Evaluation failed: {str(e)}",
|
|
136
|
+
"issues": [str(e)],
|
|
137
|
+
"recommendations": []
|
|
138
|
+
}
|
|
@@ -37,7 +37,7 @@ logging.basicConfig(
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class GeminiFlashModel:
|
|
40
|
-
def __init__(self, api_key: str, model_name: str = "gemini-
|
|
40
|
+
def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
|
|
41
41
|
api_key = os.getenv("GEMINI_API_KEY") or api_key
|
|
42
42
|
if not api_key:
|
|
43
43
|
raise ValueError("API key not found in .env file or environment variables.")
|
|
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class GeminiFlashModel:
|
|
22
|
-
def __init__(self, api_key: str, model_name: str = "gemini-
|
|
22
|
+
def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
|
|
23
23
|
if not api_key:
|
|
24
24
|
raise ValueError("API key is required.")
|
|
25
25
|
self.api_key = api_key
|
|
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class GeminiFlashModel:
|
|
22
|
-
def __init__(self, api_key: str, model_name: str = "gemini-
|
|
22
|
+
def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
|
|
23
23
|
if not api_key:
|
|
24
24
|
raise ValueError("API key is required.")
|
|
25
25
|
self.api_key = api_key
|
|
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class GeminiFlashModel:
|
|
19
|
-
def __init__(self, api_key: str, model_name: str = "gemini-
|
|
19
|
+
def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
|
|
20
20
|
if not api_key:
|
|
21
21
|
raise ValueError("API key is required.")
|
|
22
22
|
self.api_key = api_key
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuantumChecker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.
|
|
5
5
|
Author: Qobiljon
|
|
6
6
|
Author-email: qobiljonkhayrullayev@gmail.com
|
|
@@ -37,13 +37,12 @@ Q1: What is a Python list? Explain with an example.
|
|
|
37
37
|
Q2: Write an SQL query to select all records from a table named 'students'.
|
|
38
38
|
"""
|
|
39
39
|
answer_path = "sample_submissions/student1_answer.py"
|
|
40
|
-
api_keys = ["your_api_key_1", "your_api_key_2"]
|
|
41
40
|
question_type = "python"
|
|
42
41
|
|
|
43
42
|
result = await evaluator.evaluate_from_content(
|
|
44
43
|
question_content=question_content,
|
|
45
44
|
answer_path=answer_path,
|
|
46
|
-
|
|
45
|
+
api_key="your_api_key",
|
|
47
46
|
question_type=question_type
|
|
48
47
|
)
|
|
49
48
|
print(result)
|
|
@@ -11,13 +11,12 @@ Q1: What is a Python list? Explain with an example.
|
|
|
11
11
|
Q2: Write an SQL query to select all records from a table named 'students'.
|
|
12
12
|
"""
|
|
13
13
|
answer_path = "sample_submissions/student1_answer.py"
|
|
14
|
-
api_keys = ["your_api_key_1", "your_api_key_2"]
|
|
15
14
|
question_type = "python"
|
|
16
15
|
|
|
17
16
|
result = await evaluator.evaluate_from_content(
|
|
18
17
|
question_content=question_content,
|
|
19
18
|
answer_path=answer_path,
|
|
20
|
-
|
|
19
|
+
api_key="your_api_key",
|
|
21
20
|
question_type=question_type
|
|
22
21
|
)
|
|
23
22
|
print(result)
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="QuantumChecker",
|
|
5
|
-
version="0.3.
|
|
5
|
+
version="0.3.2",
|
|
6
6
|
author="Qobiljon",
|
|
7
7
|
author_email="qobiljonkhayrullayev@gmail.com",
|
|
8
8
|
description="A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from QuantumCheck import HomeworkEvaluator
|
|
3
|
+
|
|
4
|
+
question_sets = {
|
|
5
|
+
"python_beginner": "Write a Python function to calculate factorial.\nWrite a Python script to reverse a string.",
|
|
6
|
+
"power_bi": "Create a Power BI report with a bar chart.\nExplain DAX measures for sales analysis.",
|
|
7
|
+
"sql": "Write a SQL query to join two tables.\nWrite a SQL query for aggregate functions.",
|
|
8
|
+
"ssis": "Design an SSIS package for data import.\nExplain SSIS control flow tasks."
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
answer_paths = {
|
|
12
|
+
"python": ["../tests/answer/python1.zip"],
|
|
13
|
+
"powerbi": ["../tests/answer/real.zip"],
|
|
14
|
+
"sql": ["../tests/answer/sql3.zip"],
|
|
15
|
+
"ssis": ["../tests/answer/answer.dtsx"]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
question_type_mapping = {
|
|
19
|
+
"python_beginner": "python",
|
|
20
|
+
"power_bi": "powerbi",
|
|
21
|
+
"sql": "sql",
|
|
22
|
+
"ssis": "ssis"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_score(score):
|
|
27
|
+
if score >= 90:
|
|
28
|
+
return f"🟢 Excellent ({score}⭐)"
|
|
29
|
+
elif score >= 75:
|
|
30
|
+
return f"🟡 Good ({score})"
|
|
31
|
+
elif score >= 50:
|
|
32
|
+
return f"🟠 Pass ({score})"
|
|
33
|
+
else:
|
|
34
|
+
return f"🔴 Fail ({score})"
|
|
35
|
+
API_KEY = "<KEY>"
|
|
36
|
+
async def run_evaluation(evaluator, q_key, q_content, question_type, answer_path, index):
|
|
37
|
+
try:
|
|
38
|
+
evaluation = await evaluator.evaluate_from_content(
|
|
39
|
+
question_content=q_content,
|
|
40
|
+
answer_path=answer_path,
|
|
41
|
+
api_key=API_KEY,
|
|
42
|
+
question_type=question_type
|
|
43
|
+
)
|
|
44
|
+
score = evaluation.get("score", 0)
|
|
45
|
+
return (q_key, index, "success", score)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
return (q_key, index, "error", str(e))
|
|
48
|
+
|
|
49
|
+
async def main():
|
|
50
|
+
evaluator = HomeworkEvaluator()
|
|
51
|
+
tasks = []
|
|
52
|
+
|
|
53
|
+
for q_key, q_content in question_sets.items():
|
|
54
|
+
question_type = question_type_mapping[q_key]
|
|
55
|
+
paths = answer_paths.get(question_type, [])
|
|
56
|
+
if not paths:
|
|
57
|
+
print(f"⚠️ No answer paths found for question type '{question_type}'")
|
|
58
|
+
continue
|
|
59
|
+
for i in range(10): # run each set 10 times
|
|
60
|
+
for path in paths:
|
|
61
|
+
task = run_evaluation(evaluator, q_key, q_content, question_type, path, i + 1)
|
|
62
|
+
tasks.append(task)
|
|
63
|
+
|
|
64
|
+
results = await asyncio.gather(*tasks)
|
|
65
|
+
|
|
66
|
+
# Group results by question key
|
|
67
|
+
grouped = {}
|
|
68
|
+
for q_key, index, status, output in results:
|
|
69
|
+
if q_key not in grouped:
|
|
70
|
+
grouped[q_key] = []
|
|
71
|
+
grouped[q_key].append((index, status, output))
|
|
72
|
+
|
|
73
|
+
# Sort and print all at once, grouped by question
|
|
74
|
+
for q_key in grouped:
|
|
75
|
+
print(f"\n📘 {q_key.upper()} Results")
|
|
76
|
+
for index, status, output in sorted(grouped[q_key], key=lambda x: x[0]):
|
|
77
|
+
if status == "success":
|
|
78
|
+
print(f" ⏱️ Run {index:02}: {format_score(output)}")
|
|
79
|
+
else:
|
|
80
|
+
print(f" ⏱️ Run {index:02}: ❌ Error - {output}")
|
|
81
|
+
|
|
82
|
+
asyncio.run(main())
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
import zipfile
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import List, Dict
|
|
6
|
-
from .python_evaluator import PythonEvaluator
|
|
7
|
-
from .sql_evaluator import SQLEvaluator
|
|
8
|
-
from .powerbi_evaluator import PowerBIEvaluator
|
|
9
|
-
from .ssis_evaluator import SSISEvaluator
|
|
10
|
-
import asyncio
|
|
11
|
-
|
|
12
|
-
_logger_cache = {}
|
|
13
|
-
|
|
14
|
-
class HomeworkEvaluator:
|
|
15
|
-
EVALUATOR_REGISTRY = {
|
|
16
|
-
"python": PythonEvaluator,
|
|
17
|
-
"sql": SQLEvaluator,
|
|
18
|
-
"powerbi": PowerBIEvaluator,
|
|
19
|
-
"ssis": SSISEvaluator
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
EXTENSION_TO_TYPE = {
|
|
23
|
-
".py": "python",
|
|
24
|
-
".sql": "sql",
|
|
25
|
-
".pbit": "powerbi",
|
|
26
|
-
".pdf": "powerbi",
|
|
27
|
-
".dtsx": "ssis",
|
|
28
|
-
".DTSX": "ssis",
|
|
29
|
-
".txt": "text",
|
|
30
|
-
".md": "text"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
API_NAME_MAPPING = {
|
|
34
|
-
"python": "Google Gemini API",
|
|
35
|
-
"sql": "Google Gemini API",
|
|
36
|
-
"powerbi": "Google Gemini API",
|
|
37
|
-
"ssis": "Google Gemini API",
|
|
38
|
-
"text": "Google Gemini API"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
def __init__(self, log_level: int = logging.INFO):
|
|
42
|
-
self.log_level = log_level
|
|
43
|
-
self._successful_key_cache = {}
|
|
44
|
-
self._rate_limit_delay = {}
|
|
45
|
-
self._invalid_key_cache = set()
|
|
46
|
-
self._lock = asyncio.Lock()
|
|
47
|
-
self._last_request_time = None
|
|
48
|
-
|
|
49
|
-
def _get_logger(self, log_type: str) -> logging.Logger:
|
|
50
|
-
log_name = f"{log_type}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
|
|
51
|
-
if log_name not in _logger_cache:
|
|
52
|
-
logger = logging.getLogger(log_name)
|
|
53
|
-
logger.setLevel(self.log_level)
|
|
54
|
-
if not logger.handlers:
|
|
55
|
-
handler = logging.StreamHandler()
|
|
56
|
-
handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
|
|
57
|
-
logger.addHandler(handler)
|
|
58
|
-
_logger_cache[log_name] = logger
|
|
59
|
-
return _logger_cache[log_name]
|
|
60
|
-
|
|
61
|
-
def parse_questions(self, content: str) -> List[str]:
|
|
62
|
-
logger = self._get_logger("QuantumCheck.main")
|
|
63
|
-
questions = [q.strip() for q in content.split("\n\n") if q.strip()]
|
|
64
|
-
if not questions:
|
|
65
|
-
raise ValueError("No valid questions found in content")
|
|
66
|
-
return questions
|
|
67
|
-
|
|
68
|
-
def _detect_zip_content_type(self, zip_path: str, logger: logging.Logger) -> str:
|
|
69
|
-
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
70
|
-
extensions = {os.path.splitext(name)[1].lower() for name in zip_ref.namelist()}
|
|
71
|
-
file_types = [self.EXTENSION_TO_TYPE.get(ext, "text") for ext in extensions if ext]
|
|
72
|
-
if "python" in file_types:
|
|
73
|
-
return "python"
|
|
74
|
-
elif "sql" in file_types:
|
|
75
|
-
return "sql"
|
|
76
|
-
elif "powerbi" in file_types:
|
|
77
|
-
return "powerbi"
|
|
78
|
-
elif "ssis" in file_types:
|
|
79
|
-
return "ssis"
|
|
80
|
-
else:
|
|
81
|
-
return "text"
|
|
82
|
-
|
|
83
|
-
async def evaluate_from_content(
|
|
84
|
-
self,
|
|
85
|
-
question_content: str,
|
|
86
|
-
answer_path: str,
|
|
87
|
-
api_keys: List[str],
|
|
88
|
-
question_type: str,
|
|
89
|
-
retry_count: int = 0
|
|
90
|
-
) -> Dict[str, any]:
|
|
91
|
-
async with self._lock:
|
|
92
|
-
now = datetime.now()
|
|
93
|
-
if self._last_request_time:
|
|
94
|
-
elapsed = (now - self._last_request_time).total_seconds()
|
|
95
|
-
if elapsed < 30:
|
|
96
|
-
await asyncio.sleep(30 - elapsed)
|
|
97
|
-
self._last_request_time = datetime.now()
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
questions = self.parse_questions(question_content)
|
|
101
|
-
except ValueError as e:
|
|
102
|
-
logger = self._get_logger("QuantumCheck.main")
|
|
103
|
-
return {
|
|
104
|
-
"score": 0,
|
|
105
|
-
"feedback": f"Error parsing question content: {str(e)}",
|
|
106
|
-
"issues": [str(e)],
|
|
107
|
-
"recommendations": [],
|
|
108
|
-
"used_api_key_index": None,
|
|
109
|
-
"used_api_name": None
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
answer_path = answer_path.strip()
|
|
113
|
-
_, ext = os.path.splitext(answer_path)
|
|
114
|
-
ext = ext.lower()
|
|
115
|
-
|
|
116
|
-
if ext == ".zip":
|
|
117
|
-
logger = self._get_logger("zip")
|
|
118
|
-
file_type = self._detect_zip_content_type(answer_path, logger)
|
|
119
|
-
else:
|
|
120
|
-
file_type = self.EXTENSION_TO_TYPE.get(ext, "text")
|
|
121
|
-
logger = self._get_logger(file_type)
|
|
122
|
-
|
|
123
|
-
eval_type = question_type if question_type in self.EVALUATOR_REGISTRY else file_type
|
|
124
|
-
|
|
125
|
-
if not os.path.exists(answer_path):
|
|
126
|
-
return {
|
|
127
|
-
"score": 0,
|
|
128
|
-
"feedback": f"Answer file not found: {answer_path}",
|
|
129
|
-
"issues": [f"Answer file not found: {answer_path}"],
|
|
130
|
-
"recommendations": [],
|
|
131
|
-
"used_api_key_index": None,
|
|
132
|
-
"used_api_name": None
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
evaluator_class = self.EVALUATOR_REGISTRY.get(eval_type, PythonEvaluator)
|
|
136
|
-
last_error_messages = []
|
|
137
|
-
|
|
138
|
-
available_keys = [(i + 1, key) for i, key in enumerate(api_keys) if key not in self._invalid_key_cache]
|
|
139
|
-
|
|
140
|
-
cached_key_idx = self._successful_key_cache.get(eval_type)
|
|
141
|
-
if cached_key_idx is not None and cached_key_idx < len(api_keys):
|
|
142
|
-
cached_key = api_keys[cached_key_idx]
|
|
143
|
-
if cached_key not in self._invalid_key_cache:
|
|
144
|
-
available_keys.insert(0, (cached_key_idx + 1, cached_key))
|
|
145
|
-
|
|
146
|
-
if not available_keys:
|
|
147
|
-
return {
|
|
148
|
-
"score": 0,
|
|
149
|
-
"feedback": "No valid API keys available.",
|
|
150
|
-
"issues": ["All API keys are invalid or rate-limited."],
|
|
151
|
-
"recommendations": [],
|
|
152
|
-
"used_api_key_index": None,
|
|
153
|
-
"used_api_name": None
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
for idx, key in available_keys:
|
|
157
|
-
if key in self._rate_limit_delay:
|
|
158
|
-
delay_until = self._rate_limit_delay[key]
|
|
159
|
-
current_time = datetime.now()
|
|
160
|
-
delay_until_time = datetime.fromtimestamp(delay_until)
|
|
161
|
-
if current_time < delay_until_time:
|
|
162
|
-
continue
|
|
163
|
-
else:
|
|
164
|
-
del self._rate_limit_delay[key]
|
|
165
|
-
|
|
166
|
-
evaluator = evaluator_class(key)
|
|
167
|
-
api_name = getattr(evaluator, 'get_api_name', lambda: self.API_NAME_MAPPING.get(eval_type, "Unknown API"))()
|
|
168
|
-
|
|
169
|
-
try:
|
|
170
|
-
evaluation = evaluator.evaluate(questions, answer_path, temp_dir=f"temp_extract_{os.getpid()}_{idx}")
|
|
171
|
-
feedback = evaluation.get("feedback", "").lower()
|
|
172
|
-
issues = " ".join(evaluation.get("issues", [])).lower()
|
|
173
|
-
|
|
174
|
-
if any(phrase in feedback or phrase in issues for phrase in ["api key not valid", "api_key_invalid"]):
|
|
175
|
-
last_error_messages.append(f"API key #{idx} invalid.")
|
|
176
|
-
self._invalid_key_cache.add(key)
|
|
177
|
-
continue
|
|
178
|
-
|
|
179
|
-
if any(phrase in feedback or phrase in issues for phrase in ["429", "too many requests", "rate limit"]):
|
|
180
|
-
last_error_messages.append(f"API key #{idx} rate limited.")
|
|
181
|
-
self._rate_limit_delay[key] = datetime.now().timestamp() + 300
|
|
182
|
-
continue
|
|
183
|
-
|
|
184
|
-
if any(phrase in feedback or phrase in issues for phrase in ["503", "service unavailable"]):
|
|
185
|
-
last_error_messages.append(f"API key #{idx} service unavailable.")
|
|
186
|
-
self._rate_limit_delay[key] = datetime.now().timestamp() + 7200
|
|
187
|
-
continue
|
|
188
|
-
|
|
189
|
-
if evaluation.get("score", 0) == 0 and "evaluation not returned" in feedback:
|
|
190
|
-
last_error_messages.append(f"API key #{idx} returned invalid evaluation.")
|
|
191
|
-
continue
|
|
192
|
-
|
|
193
|
-
self._successful_key_cache[eval_type] = idx - 1
|
|
194
|
-
return {
|
|
195
|
-
"score": evaluation.get("score", 0),
|
|
196
|
-
"feedback": evaluation.get("feedback", "No feedback provided"),
|
|
197
|
-
"issues": evaluation.get("issues", []),
|
|
198
|
-
"recommendations": evaluation.get("recommendations", []),
|
|
199
|
-
"used_api_key_index": idx,
|
|
200
|
-
"used_api_name": api_name
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
except Exception as e:
|
|
204
|
-
last_error_messages.append(f"Exception with key #{idx}: {str(e)}")
|
|
205
|
-
if "429" in str(e) or "rate limit" in str(e).lower():
|
|
206
|
-
self._rate_limit_delay[key] = datetime.now().timestamp() + 300
|
|
207
|
-
elif "503" in str(e) or "service unavailable" in str(e).lower():
|
|
208
|
-
self._rate_limit_delay[key] = datetime.now().timestamp() + 7200
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
if retry_count < 3 and self._rate_limit_delay:
|
|
212
|
-
next_available_ts = min(self._rate_limit_delay.values())
|
|
213
|
-
wait_time = max(0, next_available_ts - datetime.now().timestamp())
|
|
214
|
-
await asyncio.sleep(wait_time + 1)
|
|
215
|
-
return await self.evaluate_from_content(
|
|
216
|
-
question_content=question_content,
|
|
217
|
-
answer_path=answer_path,
|
|
218
|
-
api_keys=api_keys,
|
|
219
|
-
question_type=question_type,
|
|
220
|
-
retry_count=retry_count + 1
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
"score": 0,
|
|
225
|
-
"feedback": "Evaluation failed with all API keys." if retry_count >= 3 else "All API keys are temporarily unavailable.",
|
|
226
|
-
"issues": last_error_messages if last_error_messages else ["All API keys failed to evaluate the submission."],
|
|
227
|
-
"recommendations": [],
|
|
228
|
-
"used_api_key_index": None,
|
|
229
|
-
"used_api_name": None
|
|
230
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
import random
|
|
4
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
-
from typing import List, Dict
|
|
6
|
-
from QuantumCheck import HomeworkEvaluator
|
|
7
|
-
from backoff import on_exception, expo
|
|
8
|
-
from google.api_core.exceptions import TooManyRequests
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
logging.basicConfig(
|
|
12
|
-
level=logging.INFO,
|
|
13
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
14
|
-
handlers=[
|
|
15
|
-
logging.FileHandler("evaluation.log", encoding="utf-8"),
|
|
16
|
-
logging.StreamHandler()
|
|
17
|
-
]
|
|
18
|
-
)
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
question_sets = {
|
|
23
|
-
"python_beginner": "Your Python beginner questions content here...",
|
|
24
|
-
"power_bi": "Your Power BI questions content here...",
|
|
25
|
-
"sql": "Your SQL questions content here...",
|
|
26
|
-
"ssis": "Your SSIS questions content here..."
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
answer_paths = {
|
|
30
|
-
"python": ["../tests/answer/python1.zip"],
|
|
31
|
-
"power_bi": ["../tests/answer/real.zip"],
|
|
32
|
-
"sql": ["../tests/answer/sql3.zip"],
|
|
33
|
-
"ssis":["../tests/answer/answer.dtsx"]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
question_type_mapping = {
|
|
37
|
-
"python_beginner": "python",
|
|
38
|
-
"power_bi": "power_bi",
|
|
39
|
-
"sql": "sql",
|
|
40
|
-
"ssis": "ssis"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
api_keys = []
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
evaluator = HomeworkEvaluator(log_level=logging.INFO)
|
|
47
|
-
|
|
48
|
-
@on_exception(expo, TooManyRequests, max_tries=5, max_time=300)
|
|
49
|
-
async def evaluate_async(question_text: str, answer_path: str, api_keys: List[str], question_type: str) -> Dict:
|
|
50
|
-
"""Wrap the async evaluate function with exponential backoff for rate limit errors."""
|
|
51
|
-
try:
|
|
52
|
-
return await evaluator.evaluate_from_content(
|
|
53
|
-
question_content=question_text,
|
|
54
|
-
answer_path=answer_path,
|
|
55
|
-
api_keys=api_keys,
|
|
56
|
-
question_type=question_type
|
|
57
|
-
)
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.error(f"Evaluation failed: {str(e)}")
|
|
60
|
-
return {
|
|
61
|
-
"score": 0,
|
|
62
|
-
"feedback": f"Evaluation failed: {str(e)}",
|
|
63
|
-
"issues": [str(e)],
|
|
64
|
-
"recommendations": [],
|
|
65
|
-
"used_api_key_index": None,
|
|
66
|
-
"used_api_name": None
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async def main(num_requests: int = 10, max_workers: int = 6):
|
|
70
|
-
resource_exhausted_count = 0
|
|
71
|
-
logger.info(f"Starting evaluation with {num_requests} requests and {max_workers} workers")
|
|
72
|
-
|
|
73
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
74
|
-
loop = asyncio.get_running_loop()
|
|
75
|
-
tasks = []
|
|
76
|
-
|
|
77
|
-
for i in range(num_requests):
|
|
78
|
-
question_key = random.choice(list(question_sets.keys()))
|
|
79
|
-
question_text = question_sets[question_key]
|
|
80
|
-
question_type = question_type_mapping.get(question_key, "python")
|
|
81
|
-
answer_path = random.choice(answer_paths.get(question_type, answer_paths["python"]))
|
|
82
|
-
|
|
83
|
-
logger.info(f"Scheduling Request #{i + 1} for '{question_key}' with '{answer_path}'")
|
|
84
|
-
task = loop.create_task(evaluate_async(question_text, answer_path, api_keys, question_type))
|
|
85
|
-
tasks.append((i + 1, task))
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
results = []
|
|
89
|
-
for i, task in tasks:
|
|
90
|
-
try:
|
|
91
|
-
result = await task
|
|
92
|
-
if "resource exhausted" in str(result.get("feedback", "")).lower():
|
|
93
|
-
resource_exhausted_count += 1
|
|
94
|
-
results.append((i, result))
|
|
95
|
-
except Exception as e:
|
|
96
|
-
logger.error(f"Request #{i} failed with error: {str(e)}")
|
|
97
|
-
if "resource exhausted" in str(e).lower():
|
|
98
|
-
resource_exhausted_count += 1
|
|
99
|
-
results.append((i, {
|
|
100
|
-
"score": 0,
|
|
101
|
-
"feedback": f"Request failed: {str(e)}",
|
|
102
|
-
"issues": [str(e)],
|
|
103
|
-
"recommendations": [],
|
|
104
|
-
"used_api_key_index": None,
|
|
105
|
-
"used_api_name": None
|
|
106
|
-
}))
|
|
107
|
-
|
|
108
|
-
logger.info("All evaluations completed")
|
|
109
|
-
|
|
110
|
-
for i, result in results:
|
|
111
|
-
score = result.get("score", 0)
|
|
112
|
-
feedback = result.get("feedback", "")
|
|
113
|
-
used_key_index = result.get("used_api_key_index", "N/A")
|
|
114
|
-
used_api_name = result.get("used_api_name", "N/A")
|
|
115
|
-
|
|
116
|
-
feedback_words = feedback.split()
|
|
117
|
-
partial_feedback = " ".join(feedback_words[:40]) + ("..." if len(feedback_words) > 10 else "")
|
|
118
|
-
|
|
119
|
-
if score == 0 and "failed" in feedback.lower():
|
|
120
|
-
logger.error(f"Request #{i} failed with error: {feedback}")
|
|
121
|
-
print(f"X Request #{i} failed with error: {feedback}")
|
|
122
|
-
else:
|
|
123
|
-
logger.info(f"Request #{i} succeeded: Score = {score}, API Index = {used_key_index}, API Name = {used_api_name}")
|
|
124
|
-
print(f"O Request #{i} succeeded: Score = {score}, API Index = {used_key_index}, API Name = {used_api_name}")
|
|
125
|
-
print(f" Feedback preview: {partial_feedback}\n")
|
|
126
|
-
|
|
127
|
-
logger.info(f"Total 'resource exhausted' errors encountered: {resource_exhausted_count}")
|
|
128
|
-
print(f"Total 'resource exhausted' errors encountered: {resource_exhausted_count}")
|
|
129
|
-
|
|
130
|
-
if __name__ == "__main__":
|
|
131
|
-
try:
|
|
132
|
-
asyncio.run(main(num_requests=100, max_workers=20))
|
|
133
|
-
except Exception as e:
|
|
134
|
-
logger.error(f"Fatal error in main: {e}")
|
|
135
|
-
print(f"Fatal error in main: {e}")
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from QuantumCheck import HomeworkEvaluator
|
|
3
|
-
|
|
4
|
-
async def main():
|
|
5
|
-
evaluator = HomeworkEvaluator()
|
|
6
|
-
|
|
7
|
-
question_content = """
|
|
8
|
-
Q1: Write a Python function that calculates the factorial of a number.
|
|
9
|
-
|
|
10
|
-
Q2: What is the difference between a list and a tuple in Python?
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
answer_path = "answer/python1.zip"
|
|
14
|
-
api_keys = []
|
|
15
|
-
|
|
16
|
-
question_type = "python"
|
|
17
|
-
|
|
18
|
-
result = await evaluator.evaluate_from_content(
|
|
19
|
-
question_content=question_content,
|
|
20
|
-
answer_path=answer_path,
|
|
21
|
-
api_keys=api_keys,
|
|
22
|
-
question_type=question_type
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
print("Evaluation Result:")
|
|
26
|
-
print(result["score"])
|
|
27
|
-
print(result["feedback"])
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if __name__ == "__main__":
|
|
31
|
-
asyncio.run(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|