Repository navigation
CodeTailor: call UMGPT when the course's token is for it - #1636
aspadiyath wants to merge 6 commits into
Conversation
No behavior change. Explain in Plain English questions (RunestoneInteractive#1627) need the same question, token and JOBE plumbing CodeTailor has, so move it out of coach.py and evaluate_fixed_code.py: - activecode_questions.py: a question's starter code, tests and data files, plus find_question (base course first, then a global match) and question_test_code (question_json.suffix_code or the textarea tests) - openai_token.py: the course's OpenAI token and the dev-course key - jobe.py: the JOBE session, data file upload, and load_and_run_tests, now split around run_python_program, which returns a run's output Adds tests for the moved helpers, which had none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeTailor only looked up a course's "openai" token and always called api.openai.com. Courses at U-M can now save a "umgpt" token instead, and CodeTailor sends those requests to U-M's gateway, the same one peer instruction uses. The token lookup now returns the key with its provider; a table in openai_token.py gives each provider's base URL and model. OpenAI is tried first, so a course with both keeps the model it used. UMGPT uses gpt-4o-mini, and verbosity is only sent to GPT-5 models. Also adds UMGPT to the instructor add-token dropdown, and stops the parsons_scaffolding debug print from writing the full key to stdout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instructors enter a UMGPT token through "Other" as "umgpt". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I think the new files should be in components/rsptx/XXX maybe we need a new component for |
…onents The course LLM token lookup (openai_token.py, now api_token.py) goes in ai_helpers. The question source/test loading and JOBE runner go in code_runner, since they aren't AI-specific. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Moved all three into components, split by purpose:
Happy to rename either. |
bnmnetp
left a comment
There was a problem hiding this comment.
Thanks for this. Moving the token lookup and JOBE helpers into shared components is a good direction. Below are the issues I found, most important first. Line numbers are approximate.
Bugs (both existed before this PR, but they're in functions it touches)
- Failed Java test runs count as passing (
evaluate_fixed_code.py~L112). When a file upload to JOBE fails, or JOBE's reply isn't JSON,load_and_run_java_testsreturns(False, {...}), andjava_unittest_evaluationpasses that tuple back as the pass/fail result. A non-empty tuple is truthy, soif unittest_result:treats untested LLM code as passing, and CodeTailor shows students an unverified "fixed" solution. courseis never set on the dev path (coach.py~L520). Whenuses_dev_api_key(course_name)is true,fetch_coursenever runs, sogetattr(course, "base_course", None)raisesUnboundLocalError. Theexcept Exceptionturns that into a 400 "could not fetch test code", so the dev/test path never reaches the LLM.
Duplication and design
- A second provider table (
components/rsptx/ai_helpers/api_token.py~L20).LLM_PROVIDERSrepeatsLLM_BASE_URLS/_llm_provider_asyncinassignment_server_api/routers/peer.py, and the two now disagree. Peer instruction tries UMGPT first, while CodeTailor tries OpenAI first. Couldpeer.pyuse the new shared helper so there's only one copy? - The UMGPT model is hardcoded (
api_token.py~L24). It's fixed atgpt-4o-miniand ignoresPI_OPENAI_MODEL, which peer instruction uses for the same service. When that model is retired there, every CodeTailor call on UMGPT will fail. verbosityis chosen by model name (get_personalized_solution.py~L168). Deciding withmodel.startswith("gpt-5")is a guess. Whether to send it would be better stored with each provider inLLM_PROVIDERS(e.g. anextra_paramsentry).- The Java path still has its own JOBE code (
evaluate_fixed_code.py~L82).load_and_run_java_testsbuilds its own session, md5 file IDs and base64 encoding instead of usingjobe.jobe_session(),jobe.runestone_file_id()andjobe.b64_text_utf8(). Any change to JOBE auth or file IDs would have to be made twice. - Dead code was edited instead of deleted (
evaluate_fixed_code.py~L15)._ensure_file_on_jobe,PUSH_PROXYandCHECK_PROXYhave no callers, so I'd remove them rather than update them.
Smaller
- Short keys aren't masked (
api_token.py~L54).masked()showstoken[:4]andtoken[-4:], so an 8-character key prints in full and shorter ones leak completely, in both the logs andrepr(). Please mask more for short tokens, or show only a fixed-length prefix. - Two ways to load JOBE settings (
code_runner/jobe.py~L13). It imports settings fromrsptx.configuration, while the Java path still usesfrom ..rsproxy import settings. Both resolve to the same object today, but they can drift apart.
The PR description is also out of date: it mentions openai_token.py and test/bases/.../test_openai_token.py, but the diff adds components/rsptx/ai_helpers/api_token.py, with tests under test/components/rsptx/ai_helpers/.
Suggested before merging: #1, #2 and #8, which are all small. Ideally also #3–4, so the two features can't drift apart on providers.
🤖 Generated with Claude Code
- A Java run whose file push fails or whose JOBE reply isn't JSON now returns False instead of a (False, info) tuple, which callers read as passing. - Set course to None before the dev-key branch so the dev path can look up the question instead of raising UnboundLocalError. - masked() shows fewer characters for short keys, so a short key is no longer printed in full. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed 1, 2 and 8:
I'll do 3 and 4 (one shared provider table for PI and CodeTailor, and the UMGPT model setting) as separate PRs after discussing with @sethbern, since they change PI's behavior too. |
CodeTailor only used a course's
openaitoken and always called api.openai.com. Courses can now save aumgpttoken instead (entered through "Other" on the add-token page), and CodeTailor sends those requests to U-M's gateway (https://api.toolkit.umgpt.umich.edu/v1), the same one async peer instruction now uses (35d10da).Changes
openai_token.py: the token lookup returns the key together with its provider. A smallLLM_PROVIDERStable gives each provider's base URL and model.get_personalized_solution.py: both LLM calls take their client and model from the token.verbosity="low"is only sent to GPT-5 models, because other models reject it.coach.py: logs which provider was used. The debugprintinparsons_scaffoldingwas writing the full API key to stdout; it and the log line now show it masked asabcd****wxyz(first and last four characters).Behavior to be aware of
gpt-4o-mini, the same default as peer instruction. On OpenAI it stays ongpt-5-nano.Testing
test/bases/rsptx/book_server_api/test_openai_token.pycover provider selection, client URL and model, and key masking.AI_personalizedfix (generated by the model, passing the question's unit tests), and the log showedprovider: umgpt.🤖 Generated with Claude Code