Skip to content

Commit 8bb5cf7

Browse files
committed
Merge branch 'feature/model-update/25-09'
2 parents 4f1b9d4 + f4235a0 commit 8bb5cf7

File tree

12 files changed

+257
-77
lines changed

12 files changed

+257
-77
lines changed

.dockerignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
*
22
!Dockerfile
33
!entrypoint.py
4-
!requirements.txt
5-
!prompt.py
4+
!llm_client.py
5+
!llm_configs.py
66
!locale/*.json
7+
!prompt.py
8+
!requirements.txt

.github/workflows/build.yml

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# begin build.yml
1+
# begin .github/workflows/build.yml
2+
23
name: build
34

45
on:
@@ -25,7 +26,7 @@ jobs:
2526
uses: astral-sh/setup-uv@v6
2627
with:
2728
version: "latest"
28-
pyproject-file: "pyproject.toml"
29+
version-file: "pyproject.toml"
2930
python-version: 3.11
3031
enable-cache: true
3132

@@ -40,37 +41,113 @@ jobs:
4041
- name: Verify that the Docker image for the action builds
4142
run: docker build . --file Dockerfile
4243

43-
- name: Integration test
44+
integration-tests-action:
45+
needs: build
46+
runs-on: ubuntu-latest
47+
strategy:
48+
matrix:
49+
include:
50+
- model: gemini-2.5-flash
51+
secret: GOOGLE_API_KEY
52+
- model: grok-code-fast
53+
secret: XAI_API_KEY
54+
- model: claude-sonnet-4-20250514
55+
secret: CLAUDE_API_KEY
56+
- model: google/gemma-2-9b-it
57+
secret: NVIDIA_NIM_API_KEY
58+
- model: sonar
59+
secret: PERPLEXITY_API_KEY
60+
fail-fast: false
61+
steps:
62+
- uses: actions/checkout@v5
63+
64+
- name: Install uv package manager
65+
uses: astral-sh/setup-uv@v6
66+
with:
67+
version: "latest"
68+
version-file: "pyproject.toml"
69+
python-version: 3.11
70+
enable-cache: true
71+
72+
- run: uv sync
73+
74+
- name: Integration test ${{ matrix.model }}
4475
id: integration
4576
uses: ./
4677
with:
4778
report-files: tests/sample_report.json,tests/json_dict_div_zero_try_except.json
4879
student-files: tests/sample_code.py
49-
api-key: ${{ secrets.GOOGLE_API_KEY }}
80+
api-key: ${{ secrets[matrix.secret] }}
5081
readme-path: tests/sample_readme.md
82+
model: ${{ matrix.model }}
5183
explanation-in: Korean
5284
fail-expected: True
5385
timeout-minutes: 5
86+
- name: Output and Verify
87+
run: |
88+
echo "${{ matrix.model }} ${{ steps.integration.outputs.feedback }}"
89+
echo "feedback_${{ matrix.model }}=${{ steps.integration.outputs.feedback }}" >> $GITHUB_OUTPUT
90+
- name: Verify integration test results
91+
run: |
92+
. .venv/bin/activate
93+
uv pip list
94+
echo "feedback_${{ matrix.model }}=${{ steps.integration.outputs.feedback }}" >> $GITHUB_OUTPUT
95+
python3 -u -m pytest tests/integration.py
5496
55-
- name: Integration test XAI
56-
id: integration-xai
57-
uses: ./
97+
integration-tests-env:
98+
needs: build
99+
runs-on: ubuntu-latest
100+
strategy:
101+
matrix:
102+
model: [
103+
gemini-2.5-flash,
104+
grok-code-fast,
105+
claude-sonnet-4-20250514,
106+
google/gemma-2-9b-it,
107+
sonar
108+
]
109+
fail-fast: false
110+
steps:
111+
- uses: actions/checkout@v5
112+
113+
- name: Install uv package manager
114+
uses: astral-sh/setup-uv@v6
58115
with:
59-
report-files: tests/sample_report.json,tests/json_dict_div_zero_try_except.json
60-
student-files: tests/sample_code.py
61-
api-key: ${{ secrets.XAI_API_KEY }}
62-
readme-path: tests/sample_readme.md
63-
model: grok
116+
version: "latest"
117+
version-file: "pyproject.toml"
118+
python-version: 3.11
119+
enable-cache: true
120+
121+
- run: uv sync
122+
123+
- name: Integration test ${{ matrix.model }}
124+
id: integration
125+
env:
126+
INPUT_REPORT-FILES: tests/sample_report.json,tests/json_dict_div_zero_try_except.json
127+
INPUT_STUDENT-FILES: tests/sample_code.py
128+
INPUT_README-PATH: tests/sample_readme.md
129+
INPUT_CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
130+
INPUT_GEMINI-API-KEY: ${{ secrets.GOOGLE_API_KEY }}
131+
INPUT_GROK-API-KEY: ${{ secrets.XAI_API_KEY }}
132+
INPUT_NVIDIA-API-KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
133+
INPUT_PERPLEXITY-API-KEY: ${{ secrets.PERPLEXITY_API_KEY }}
134+
INPUT_MODEL: ${{ matrix.model }}
64135
explanation-in: Korean
65136
fail-expected: True
137+
run: |
138+
. .venv/bin/activate
139+
uv pip list
140+
python3 entrypoint.py
66141
timeout-minutes: 5
67-
68-
- name: Output the outputs of the integration test of the action
142+
- name: Output and Verify
69143
run: |
70-
echo "${{ steps.integration.outputs.feedback }}"
71-
144+
echo "${{ matrix.model }} ${{ steps.integration.outputs.feedback }}"
145+
echo "feedback_${{ matrix.model }}=${{ steps.integration.outputs.feedback }}" >> $GITHUB_OUTPUT
72146
- name: Verify integration test results
73147
run: |
74-
echo "feedback=${{ steps.integration.outputs.feedback }}" >> $GITHUB_OUTPUT
148+
. .venv/bin/activate
149+
uv pip list
150+
echo "feedback_${{ matrix.model }}=${{ steps.integration.outputs.feedback }}" >> $GITHUB_OUTPUT
75151
python3 -u -m pytest tests/integration.py
76-
# end build.yml
152+
153+
# end .github/workflows/build.yml

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
### Fixed
1818

19+
## [v0.3.12] - 2025-09-07
20+
21+
### Added
22+
- Support for multiple LLM providers (Claude, Gemini, Grok, NVIDIA NIM, Perplexity) with dedicated configuration classes in `llm_configs.py`.
23+
- `llm_client.py` for robust API interaction with retry, timeout, and error handling.
24+
- Comprehensive integration tests in `build.yml` for multiple models (`gemini-2.5-flash`, `grok-code-fast`, `claude-sonnet-4-20250514`, `google/gemma-2-9b-it`, `sonar`) using both action and environment variable inputs.
25+
- Model-to-provider mapping in `get_model_key_from_env()` to handle precise model IDs.
26+
27+
### Changed
28+
- Updated `entrypoint.py` to support flexible model selection with fallback to `gemini-2.5-flash`.
29+
- Modified `.dockerignore` and `Dockerfile` to include `llm_client.py` and `llm_configs.py`.
30+
- Updated `action.yml` inputs: `report-files`, `student-files`, `readme-path` now required; `model` defaults to `gemini`; added `fail-expected`.
31+
- Updated `GeminiConfig` default model to `gemini-2.5-flash` from `gemini-2.0-flash`.
32+
- Updated `GrokConfig` default model to `grok-code-fast` from `grok-2-1212`.
33+
- Enhanced `tests/test_entrypoint.py` to align with new fallback model (`gemini-2.5-flash`) and added test for `INPUT_API-KEY`.
34+
- Improved logging checks in `tests/test_llm_client.py` for robustness.
35+
- Added file markers in `tests/test_integration.py` and `tests/test_prompt.py`.
36+
37+
### Fixed
38+
- Resolved 404 errors in integration tests for `google/gemma-2-9b-it` and `sonar` by adding model-to-provider mapping in `get_model_key_from_env()`.
39+
- Fixed test failures in `test_get_model_key_from_env__fallback_gemini` and `test_get_model_key_from_env__no_model_fallback_gemini` by updating expected model to `gemini-2.5-flash`.
40+
1941
## [v0.3.7] - 2025-08-01
2042

2143
### Added

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ FROM cicirello/pyaction:4
1616
COPY entrypoint.py /entrypoint.py
1717
COPY requirements.txt /requirements.txt
1818
COPY prompt.py /prompt.py
19+
COPY llm_client.py /llm_client.py
20+
COPY llm_configs.py /llm_configs.py
1921
COPY locale/ /locale/
2022

2123
RUN python3 -m pip install --upgrade pip

action.yml

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,31 @@
11
# begin action.yml
2-
# Make sure to change the action name here (must be unique).
3-
# Update the rest as needed.
4-
name: 'gemini-python-tutor'
5-
description: 'Gemini Python Tutor for Github Classroom Assignments'
6-
branding: # Look at documentation for branding options/requirements. These are my favorites.
7-
icon: 'book-open'
8-
color: 'green'
2+
name: 'Gemini Python Tutor'
3+
description: 'Action to generate feedback using LLMs'
94
inputs:
105
report-files:
11-
description: 'Comma-separeted list of json report file paths'
6+
description: 'Comma-separated list of test report file paths'
7+
required: true
8+
student-files:
9+
description: 'Comma-separated list of student code file paths'
1210
required: true
13-
default: 'report.json'
14-
type: list
1511
api-key:
16-
description: 'API token for AI'
12+
description: 'API key for the selected LLM'
1713
required: true
18-
type: string
19-
model: # New input for the model
20-
description: 'The Gemini model to use (e.g., gemini-2.0-flash)'
21-
required: false
22-
default: 'gemini-2.0-flash'
23-
type: string
24-
student-files:
25-
description: "Comma-separated list of student's Python file paths or a glob pattern"
14+
model:
15+
description: 'LLM model to use (e.g., gemini, grok)'
2616
required: false
27-
default: 'exercise.py'
28-
type: list
17+
default: 'gemini'
2918
readme-path:
30-
description: 'assignment instructions path'
31-
required: false
32-
default: 'README.md'
33-
type: string
19+
description: 'Path to the README file with assignment instructions'
20+
required: true
3421
explanation-in:
35-
description: 'Generate explanations in the specified language'
22+
description: 'Language for explanations (e.g., English, Korean)'
3623
required: false
3724
default: 'English'
38-
type: string
39-
outputs:
40-
feedback:
41-
description: 'Feedback from the tutor'
25+
fail-expected:
26+
description: 'Whether test failures are expected (true/false)'
27+
required: false
28+
default: 'false'
4229
runs:
4330
using: 'docker'
4431
image: 'Dockerfile'

entrypoint.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@
88

99
from typing import Any, Dict, Tuple
1010

11+
12+
sys.path.insert(
13+
0,
14+
str(pathlib.Path(__file__).parent.resolve())
15+
)
16+
17+
1118
from llm_client import LLMAPIClient
1219
from llm_configs import ClaudeConfig, GeminiConfig, GrokConfig, NvidiaNIMConfig, PerplexityConfig
1320

1421
import prompt
1522

23+
1624
logging.basicConfig(level=logging.INFO)
1725

1826

@@ -84,46 +92,69 @@ def get_startwith(key:str, dictionary:dict) -> Any:
8492
def get_model_key_from_env() -> Tuple[str, str]:
8593
"""
8694
Extracts the LLM model and API key from environment variables with flexible selection.
95+
- Uses INPUT_API-KEY if provided, especially with a specified model.
96+
- Falls back to model-specific API keys if INPUT_API-KEY is not set.
8797
- Raises ValueError if no API keys are available.
88-
- Uses single available API key if only one is set.
89-
- Prefers specified model's API key if available.
90-
- Falls back to Gemini if its API key is available.
98+
- Uses model-to-provider mapping for precise model IDs.
9199
"""
92100
api_key_dict = get_api_key_dict_from_env()
93101
valid_keys_dict = {k: v for k, v in api_key_dict.items() if v and v.strip()}
94102

103+
model = os.getenv('INPUT_MODEL', '').lower()
104+
general_api_key = os.getenv('INPUT_API-KEY', '').strip()
105+
106+
# Model-to-provider mapping for precise model IDs
107+
model_to_provider = {
108+
'google/gemma-2-9b-it': 'nvidia_nim',
109+
'sonar': 'perplexity',
110+
'gemini-2.5-flash': 'gemini',
111+
'grok-code-fast': 'grok',
112+
'claude-sonnet-4-20250514': 'claude'
113+
}
114+
115+
# Case 1: Use INPUT_API-KEY if provided
116+
if general_api_key:
117+
selected_model = model or 'gemini-2.5-flash' # Default to specific Gemini model
118+
logging.info(f"Using INPUT_API-KEY for model: {selected_model}")
119+
return selected_model, general_api_key
120+
121+
# Case 2: No INPUT_API-KEY, check model-specific keys
95122
if not valid_keys_dict:
96123
raise ValueError(
97124
"No API keys provided. Set at least one of:\n"
125+
"\tINPUT_API-KEY\n"
98126
"\tINPUT_CLAUDE_API_KEY\n"
99127
"\tINPUT_GEMINI-API-KEY\n"
100128
"\tINPUT_GROK-API-KEY\n"
101129
"\tINPUT_NVIDIA-API-KEY\n"
102130
"\tINPUT_PERPLEXITY-API-KEY\n"
103131
)
104132

105-
model = os.getenv('INPUT_MODEL', '').lower()
106-
107-
# Case 1: Only one API key is available
133+
# Case 3: Only one API key available
108134
if len(valid_keys_dict) == 1:
109135
selected_model, api_key = next(iter(valid_keys_dict.items()))
110136
logging.info(f"Using single available model: {selected_model}")
111137
return selected_model, api_key.strip()
112138

113-
# Case 2: Multiple API keys available
139+
# Case 4: Use model-to-provider mapping for specified model
140+
provider = model_to_provider.get(model, None)
141+
if model and provider and provider in valid_keys_dict:
142+
logging.info(f"Using mapped model: {model} with provider: {provider}")
143+
return model, valid_keys_dict[provider].strip()
144+
145+
# Case 5: Fallback to provider-based matching
114146
if model:
115-
# Check if specified model's API key is available
116147
api_key = get_startwith(model, valid_keys_dict)
117148
if api_key:
118-
logging.info(f"Using specified model: {model}")
149+
logging.info(f"Using specified model with provider matching: {model}")
119150
return model, api_key.strip()
120151

121-
# Case 3: Fallback to Gemini if available
152+
# Case 6: Fallback to Gemini if available
122153
if 'gemini' in valid_keys_dict:
123154
logging.info("Falling back to Gemini model")
124-
return 'gemini', valid_keys_dict['gemini'].strip()
155+
return 'gemini-2.5-flash', valid_keys_dict['gemini'].strip()
125156

126-
# Case 4: Specified model not available, and Gemini not available
157+
# Case 7: No matching model or Gemini
127158
raise ValueError(
128159
f"No API key provided for specified model '{model}' and Gemini not available. "
129160
f"Available models: {', '.join(valid_keys_dict.keys())}"
@@ -150,10 +181,15 @@ def get_config_class_dict() -> Dict[str, type]:
150181
"""
151182
return {
152183
'claude': ClaudeConfig,
184+
'claude-sonnet-4-20250514': ClaudeConfig, # Add specific model
153185
'gemini': GeminiConfig,
186+
'gemini-2.5-flash': GeminiConfig,
154187
'grok': GrokConfig,
188+
'grok-code-fast': GrokConfig,
155189
'nvidia_nim': NvidiaNIMConfig,
190+
'google/gemma-2-9b-it': NvidiaNIMConfig, # Add specific model
156191
'perplexity': PerplexityConfig,
192+
'sonar': PerplexityConfig # Add specific model
157193
}
158194

159195

0 commit comments

Comments
 (0)