QuantumChecker 0.3.0__tar.gz → 0.3.1__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.1}/PKG-INFO +2 -3
- quantumchecker-0.3.1/QuantumCheck/main.py +138 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/powerbi_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/python_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/sql_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/ssis_evaluator.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumChecker.egg-info/PKG-INFO +2 -3
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/README.md +1 -2
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/setup.py +1 -1
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/tests/test2.py +12 -2
- quantumchecker-0.3.0/QuantumCheck/main.py +0 -230
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/__init__.py +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumCheck/prompts.py +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumChecker.egg-info/SOURCES.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumChecker.egg-info/dependency_links.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumChecker.egg-info/requires.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/QuantumChecker.egg-info/top_level.txt +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/setup.cfg +0 -0
- {quantumchecker-0.3.0 → quantumchecker-0.3.1}/tests/test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuantumChecker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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 < 30:
|
|
84
|
+
await asyncio.sleep(30 - 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.1
|
|
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.1",
|
|
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.",
|
|
@@ -11,14 +11,20 @@ async def main():
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
answer_path = "answer/python1.zip"
|
|
14
|
-
api_keys = []
|
|
15
14
|
|
|
16
15
|
question_type = "python"
|
|
17
16
|
|
|
18
17
|
result = await evaluator.evaluate_from_content(
|
|
19
18
|
question_content=question_content,
|
|
20
19
|
answer_path=answer_path,
|
|
21
|
-
|
|
20
|
+
api_key="AIzaSyC2B_Q38DkCl6O8y4b5hAWEpb6aJHW6FcY",
|
|
21
|
+
question_type=question_type
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
result2 = await evaluator.evaluate_from_content(
|
|
25
|
+
question_content=question_content,
|
|
26
|
+
answer_path=answer_path,
|
|
27
|
+
api_key="AIzaSyC2B_Q38DkCl6O8y4b5hAWEpb6aJHW6FcY",
|
|
22
28
|
question_type=question_type
|
|
23
29
|
)
|
|
24
30
|
|
|
@@ -26,6 +32,10 @@ async def main():
|
|
|
26
32
|
print(result["score"])
|
|
27
33
|
print(result["feedback"])
|
|
28
34
|
|
|
35
|
+
print("Evaluation Result:")
|
|
36
|
+
print(result2["score"])
|
|
37
|
+
print(result2["feedback"])
|
|
38
|
+
|
|
29
39
|
|
|
30
40
|
if __name__ == "__main__":
|
|
31
41
|
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
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|