Skip to content

AI-powered marking #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

Conversation

xxdydx
Copy link

@xxdydx xxdydx commented Apr 6, 2025

Description

Created a new feature to automate feedback generation of code submissions in Source Academy, with the help of LLMs. This will help save TAs some time in grading code submissions from students!

  • Added AI comment generation functionality in the backend.
  • Integrated OpenAI API for generating AI comments.
  • Implemented generate_ai_comments endpoint to fetch question details and generate AI-generated comments for submissions.
  • Added save_chosen_comments endpoint to save multiple chosen comments for a submission and question for logging purposes.
  • Added save_final_comment endpoint to save the final comment chosen for a submission for logging purposes.
  • Added new ai_comment_logs table to log various data points from inputs, original student's code, outputs generated by LLM, comments chosen, and final comment.
  • Added AIComments module to handle creation, retrieval, and updates for AI comments, including saving final and chosen comments.
  • Added AICodeAnalysisController to handle AI comment generation, saving final comments, and saving chosen comments.
  • Added test cases for generate_ai_comments, save_final_comment, and save_chosen_comments endpoints in AICodeAnalysisControllerTest.
  • Updated Swagger documentation for the new endpoints.
  • Added necessary migrations to update the database schema.
  • Added encryption and decryption logic for LLM API keys using AES-GCM.

Note: This may require changes to the DB diagram in README.md.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Code quality improvements

Checklist

  • I have tested this code
  • I have updated the documentation

@xxdydx xxdydx changed the title Feat/add ai generated comments grading AI-powered marking Apr 6, 2025
@xxdydx xxdydx self-assigned this Apr 6, 2025
@coveralls
Copy link

Coverage Status

coverage: 90.331% (-3.3%) from 93.607%
when pulling 81e5bf7 on feat/add-AI-generated-comments-grading
into bd37d04 on master.

@xxdydx xxdydx marked this pull request as ready for review April 9, 2025 05:06
@xxdydx xxdydx requested a review from GabrielCWT April 9, 2025 05:06
Copy link
Contributor

@GabrielCWT GabrielCWT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this feature! Quite a bit of comments. Please look through, resolve them. I do have some clarification comments as well so please answer those as well.

One question I have is when are these comments used? I couldn't find anytime in which the comments are returned/retrieved to/by the FE.

@doc """
Gets an AI comment by ID.
"""
def get_ai_comment!(id), do: Repo.get!(AIComment, id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we use get! instead of get

Retrieves an AI comment for a specific submission and question.
Returns `nil` if no comment exists.
"""
def get_ai_comments_for_submission(submission_id, question_id) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming implies you are getting all AI comments. Also what is the use case for getting only one of the comments?

"""
def update_ai_comment(id, attrs) do
id
|> get_ai_comment!()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could raise an error which isn't handled (id not found in DB)

Comment on lines +10 to +11
field(:submission_id, :integer)
field(:question_id, :integer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are FKs, you need to specify that in the schema

Comment on lines +2315 to +2321
answer_query =
if is_nil(question_id) do
base_query
else
base_query |> where(question_id: ^question_id)
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use case instead

@openai_api_url "https://api.openai.com/v1/chat/completions"
@model "gpt-4o"
# To set whether LLM grading is enabled across Source Academy
@default_llm_grading false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done in the DBMS as mentioned above


alias Cadet.{Assessments, AIComments, Courses}

@openai_api_url "https://api.openai.com/v1/chat/completions"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in the confg file (env variable). @model as well

answers_json: answers_json,
response: response,
error: error,
inserted_at: NaiveDateTime.utc_now()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be automatically handled my Ecto

Comment on lines +42 to +50
updated_attrs = Map.merge(Map.from_struct(existing_comment), attrs)

case AIComments.update_ai_comment(existing_comment.id, updated_attrs) do
{:ok, updated_comment} ->
{:ok, updated_comment}

{:error, changeset} ->
Logger.error("Failed to update AI comment in database: #{inspect(changeset.errors)}")
{:error, changeset}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to refactor this log_comment function. It is doing more than just logging.

Comment on lines +235 to +240
llm_prompt =
answers
|> List.first()
|> Map.get(:question)
|> Map.get(:question)
|> Map.get("llm_prompt")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we only use the first question's llm_prompt? What about the other questions' prompts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants