Skip to content

Commit 6d07151

Browse files
authored
Merge branch 'main' into athena/feature/model-choice
2 parents fc71eef + 4a0ed46 commit 6d07151

14 files changed

Lines changed: 687 additions & 26 deletions

File tree

.github/workflows/athena_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
echo "$HOME/.local/bin" >> $GITHUB_PATH
3838
poetry install
3939
poetry run install_all
40-
40+
4141
- name: Run tests
4242
working-directory: athena
4343
run: poetry run test_all

.github/workflows/hyperion_check-openapi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
fetch-depth: 0
2828

2929
- name: Set up Python 3.13
30-
uses: actions/setup-python@v2
30+
uses: actions/setup-python@v5
3131
with:
3232
python-version: 3.13
3333

.github/workflows/hyperion_lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- uses: actions/checkout@v4
1818

1919
- name: Set up Python 3.13
20-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v5
2121
with:
2222
python-version: 3.13
2323

athena/modules/text/module_text_llm/poetry.lock

Lines changed: 108 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

athena/modules/text/module_text_llm/pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@ nltk = "3.9.1"
1616
python-dotenv = "1.0.0"
1717
tiktoken = "0.7.0"
1818

19-
[tool.poetry.dev-dependencies]
20-
pydantic = "1.10.17"
21-
prospector = "^1.10.2"
22-
2319
[tool.poetry.scripts]
2420
module = "athena:run_module"
2521

2622
[tool.poetry.group.dev.dependencies]
23+
pydantic = "1.10.17"
24+
prospector = "^1.10.2"
2725
types-requests = "^2.31.0.8"
2826

2927
[build-system]

athena/poetry.lock

Lines changed: 96 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

athena/pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ package-mode = true
88
[tool.poetry.dependencies]
99
python = "3.11.*"
1010

11+
[tool.poetry.group.dev.dependencies]
12+
pytest = "^8.3.5"
13+
pytest-asyncio = "^0.26.0"
14+
1115
[tool.poetry.scripts]
1216
lint_all = "scripts.lint_modules:main"
1317
install_all = "scripts.install_modules:main"
1418
lock_all = "scripts.lock_modules:main"
1519
test_all = "scripts.test_modules:main"
1620

21+
[tool.pytest.ini_options]
22+
pythonpath = ["athena"]
23+
1724
[build-system]
1825
requires = ["poetry-core"]
1926
build-backend = "poetry.core.masonry.api"

athena/scripts/test_modules.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@ def main():
1919
]
2020

2121
success = True
22+
path_env = os.environ["PATH"]
23+
24+
for module in modules:
25+
# Check if test directory exists
26+
test_dir = f"tests/{module}"
27+
if not os.path.exists(test_dir):
28+
print(f"No tests found for {module}, skipping...")
29+
continue
30+
31+
# Get the module's virtual environment
32+
venv_path = os.path.join(os.getcwd(), module, ".venv")
33+
if not os.path.exists(venv_path):
34+
print(f"Virtual environment not found for {module} at {venv_path}")
35+
continue
36+
37+
# Set environment variables for the virtual environment
38+
os.environ["VIRTUAL_ENV"] = venv_path
39+
os.environ["PATH"] = os.path.join(venv_path, "bin") + os.pathsep + path_env
40+
python_path = os.path.join(venv_path, "bin", "python")
41+
pip_path = os.path.join(venv_path, "bin", "pip")
42+
43+
print(f"Using Python path: {python_path}")
44+
45+
try:
46+
# Install pytest in the virtual environment
47+
print(f"Installing pytest for {module}...")
48+
subprocess.run([pip_path, "install", "pytest"], check=True, capture_output=True, text=True)
49+
50+
# Run pytest using the module's virtual environment
51+
result = subprocess.run([python_path, "-m", "pytest", test_dir], capture_output=True, text=True)
52+
if result.returncode != 0:
53+
print(f"Tests failed for {module}:")
54+
print(result.stdout)
55+
print(result.stderr)
56+
success = False
57+
else:
58+
print(f"Tests passed for {module}")
59+
except Exception as e:
60+
print(f"Error running tests for {module}: {str(e)}")
61+
success = False
2262

2363
if success:
2464
sys.exit(0)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Import OpenAI mocks first to ensure they're in place before any other imports
2+
from tests.utils.mock_openai import mock_openai, mock_openai_client
3+
4+
import pytest
5+
from tests.utils.mock_llm import MockLanguageModel, MockStructuredMockLanguageModel, MockAssessmentModel
6+
from tests.utils.mock_config import MockApproachConfig, MockModelConfig
7+
from tests.utils.mock_env import mock_sent_tokenize
8+
9+
10+
@pytest.fixture
11+
def mock_llm():
12+
"""Fixture providing a basic mock language model."""
13+
return MockLanguageModel()
14+
15+
16+
@pytest.fixture
17+
def mock_structured_llm():
18+
"""Fixture providing a structured mock language model."""
19+
return MockStructuredMockLanguageModel()
20+
21+
22+
@pytest.fixture
23+
def mock_assessment_model():
24+
"""Fixture providing a mock assessment model."""
25+
return MockAssessmentModel()
26+
27+
28+
@pytest.fixture
29+
def mock_config():
30+
"""Create a mock configuration for testing."""
31+
return MockApproachConfig(
32+
max_input_tokens=5000,
33+
model=MockModelConfig(),
34+
type="basic"
35+
)
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import pytest
2+
from module_text_llm.basic_approach.generate_suggestions import generate_suggestions
3+
from athena.text import Exercise, Submission, Feedback
4+
from athena.schemas.exercise_type import ExerciseType
5+
from tests.utils.mock_env import mock_sent_tokenize
6+
from tests.utils.mock_llm import MockLanguageModel, MockAssessmentModel, MockFeedbackModel
7+
8+
9+
@pytest.fixture
10+
def mock_exercise():
11+
"""Create a mock exercise for testing."""
12+
return Exercise(
13+
id=1,
14+
title="Test Exercise",
15+
type=ExerciseType.text,
16+
max_points=10,
17+
bonus_points=2,
18+
grading_instructions="Test grading instructions",
19+
problem_statement="Test problem statement",
20+
example_solution="Test example solution",
21+
grading_criteria=[]
22+
)
23+
24+
25+
@pytest.fixture
26+
def mock_submission(mock_exercise):
27+
"""Create a mock submission for testing."""
28+
return Submission(
29+
id=1,
30+
exerciseId=mock_exercise.id,
31+
text="This is a test submission.\nIt has multiple lines.\nFor testing purposes."
32+
)
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_generate_suggestions_basic(
37+
mock_exercise, mock_submission, mock_config):
38+
"""Test basic feedback generation with a simple submission."""
39+
mock_model = MockLanguageModel(return_value=MockAssessmentModel(feedbacks=[
40+
MockFeedbackModel(
41+
title="Test Feedback",
42+
description="Test description",
43+
line_start=1,
44+
line_end=2,
45+
credits=5.0
46+
)
47+
]))
48+
mock_config.model.get_model = lambda: mock_model
49+
mock_sent_tokenize.return_value = [
50+
"This is a test submission.",
51+
"It has multiple lines.",
52+
"For testing purposes."
53+
]
54+
55+
feedbacks = await generate_suggestions(
56+
exercise=mock_exercise,
57+
submission=mock_submission,
58+
config=mock_config,
59+
debug=False,
60+
is_graded=True
61+
)
62+
63+
assert isinstance(feedbacks, list)
64+
assert all(isinstance(feedback, Feedback) for feedback in feedbacks)
65+
assert all(feedback.exercise_id ==
66+
mock_exercise.id for feedback in feedbacks)
67+
assert all(feedback.submission_id ==
68+
mock_submission.id for feedback in feedbacks)
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_generate_suggestions_empty_submission(
73+
mock_exercise, mock_config):
74+
"""Test feedback generation with an empty submission."""
75+
empty_submission = Submission(
76+
id=2,
77+
exerciseId=mock_exercise.id,
78+
text=""
79+
)
80+
mock_model = MockLanguageModel(
81+
return_value=MockAssessmentModel(
82+
feedbacks=[]))
83+
mock_config.model.get_model = lambda: mock_model
84+
mock_sent_tokenize.return_value = []
85+
86+
feedbacks = await generate_suggestions(
87+
exercise=mock_exercise,
88+
submission=empty_submission,
89+
config=mock_config,
90+
debug=False,
91+
is_graded=True
92+
)
93+
94+
assert isinstance(feedbacks, list)
95+
assert len(feedbacks) == 0
96+
97+
98+
@pytest.mark.asyncio
99+
async def test_generate_suggestions_long_input(mock_exercise, mock_config):
100+
"""Test feedback generation with a long submission."""
101+
long_submission = Submission(
102+
id=3,
103+
exerciseId=mock_exercise.id,
104+
text="Test " * 1000
105+
)
106+
mock_model = MockLanguageModel(return_value=MockAssessmentModel(feedbacks=[
107+
MockFeedbackModel(
108+
title="Test Long Input Feedback",
109+
description="Test description for long input",
110+
line_start=1,
111+
line_end=100,
112+
credits=7.0
113+
)
114+
]))
115+
mock_config.model.get_model = lambda: mock_model
116+
mock_sent_tokenize.return_value = ["Test " * 100 for _ in range(10)]
117+
118+
feedbacks = await generate_suggestions(
119+
exercise=mock_exercise,
120+
submission=long_submission,
121+
config=mock_config,
122+
debug=False,
123+
is_graded=True
124+
)
125+
126+
assert isinstance(feedbacks, list)
127+
assert all(isinstance(feedback, Feedback) for feedback in feedbacks)
128+
assert all(feedback.exercise_id ==
129+
mock_exercise.id for feedback in feedbacks)
130+
assert all(feedback.submission_id ==
131+
long_submission.id for feedback in feedbacks)

0 commit comments

Comments
 (0)