Skip to content

Commit af395df

Browse files
committed
Abstract out + remove un-used field
1 parent 654c9f4 commit af395df

File tree

5 files changed

+85
-77
lines changed

5 files changed

+85
-77
lines changed

lib/cadet/ai_comments/ai_comment.ex

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ defmodule Cadet.AIComments.AIComment do
1111
field(:answers_json, :string)
1212
field(:response, :string)
1313
field(:error, :string)
14-
field(:comment_chosen, {:array, :string})
1514
field(:final_comment, :string)
1615

1716
belongs_to(:answer, Cadet.Assessments.Answer)
@@ -27,7 +26,6 @@ defmodule Cadet.AIComments.AIComment do
2726
:answers_json,
2827
:response,
2928
:error,
30-
:comment_chosen,
3129
:final_comment
3230
])
3331
|> validate_required([:answer_id, :raw_prompt, :answers_json])

lib/cadet/courses/course.ex

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Cadet.Courses.Course do
55
use Cadet, :model
66

77
alias Cadet.Courses.AssessmentConfig
8+
alias Cadet.AICommentsHelpers
89

910
@type t :: %__MODULE__{
1011
course_name: String.t(),
@@ -70,36 +71,10 @@ defmodule Cadet.Courses.Course do
7071
|> put_encrypted_llm_api_key()
7172
end
7273

73-
def encrypt_llm_api_key(llm_api_key) do
74-
secret = Application.get_env(:openai, :encryption_key)
75-
76-
if is_binary(secret) and byte_size(secret) >= 16 do
77-
# Use first 16 bytes for AES-128, 24 for AES-192, or 32 for AES-256
78-
key = binary_part(secret, 0, min(32, byte_size(secret)))
79-
# Use AES in GCM mode for encryption
80-
iv = :crypto.strong_rand_bytes(16)
81-
82-
{ciphertext, tag} =
83-
:crypto.crypto_one_time_aead(
84-
:aes_gcm,
85-
key,
86-
iv,
87-
llm_api_key,
88-
"",
89-
true
90-
)
91-
92-
# Store both the IV, ciphertext and tag
93-
encrypted = Base.encode64(iv <> tag <> ciphertext)
94-
else
95-
{:error, :invalid_encryption_key}
96-
end
97-
end
98-
9974
def put_encrypted_llm_api_key(changeset) do
10075
if llm_api_key = get_change(changeset, :llm_api_key) do
10176
if is_binary(llm_api_key) and llm_api_key != "" do
102-
encrypted = encrypt_llm_api_key(llm_api_key)
77+
encrypted = AICommentsHelpers.encrypt_llm_api_key(llm_api_key)
10378

10479
case encrypted do
10580
{:error, :invalid_encryption_key} ->

lib/cadet_web/controllers/generate_ai_comments.ex

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ defmodule CadetWeb.AICodeAnalysisController do
55
require Logger
66

77
alias Cadet.{Assessments, AIComments, Courses}
8+
alias CadetWeb.AICodeAnalysisController
9+
alias CadetWeb.AICommentsHelpers
810

911
# For logging outputs to both database and file
1012
defp save_comment(answer_id, raw_prompt, answers_json, response, error \\ nil) do
@@ -84,7 +86,7 @@ defmodule CadetWeb.AICodeAnalysisController do
8486
with {answer_id_parsed, ""} <- Integer.parse(answer_id),
8587
{:ok, course} <- Courses.get_course_config(course_id),
8688
{:ok} <- ensure_llm_enabled(course),
87-
{:ok, key} <- decrypt_llm_api_key(course.llm_api_key),
89+
{:ok, key} <- AICommentsHelpers.decrypt_llm_api_key(course.llm_api_key),
8890
{:ok} <-
8991
check_llm_grading_parameters(
9092
key,
@@ -123,7 +125,7 @@ defmodule CadetWeb.AICodeAnalysisController do
123125
{:decrypt_error, err} ->
124126
conn
125127
|> put_status(:internal_server_error)
126-
|> text("Failed to decrypt LLM API key: #{inspect(err)}")
128+
|> text("Failed to decrypt LLM API key")
127129

128130
# Errors for check_llm_grading_parameters
129131
{:parameter_error, error_msg} ->
@@ -152,25 +154,25 @@ defmodule CadetWeb.AICodeAnalysisController do
152154
"\n\n" <>
153155
(assessment_prompt || "") <>
154156
"\n\n" <>
155-
"""
156-
**Additional Instructions for this Question:**
157-
#{answer.question.question["llm_prompt"] || "N/A"}
157+
"""
158+
**Additional Instructions for this Question:**
159+
#{answer.question.question["llm_prompt"] || "N/A"}
158160
159-
**Question:**
160-
```
161-
#{answer.question.question["content"] || "N/A"}
162-
```
161+
**Question:**
162+
```
163+
#{answer.question.question["content"] || "N/A"}
164+
```
163165
164-
**Model Solution:**
165-
```
166-
#{answer.question.question["solution"] || "N/A"}
167-
```
166+
**Model Solution:**
167+
```
168+
#{answer.question.question["solution"] || "N/A"}
169+
```
168170
169-
**Autograding Status:** #{answer.autograding_status || "N/A"}
170-
**Autograding Results:** #{format_autograding_results(answer.autograding_results)}
171+
**Autograding Status:** #{answer.autograding_status || "N/A"}
172+
**Autograding Results:** #{format_autograding_results(answer.autograding_results)}
171173
172-
The student answer will be given below as part of the User Prompt.
173-
"""
174+
The student answer will be given below as part of the User Prompt.
175+
"""
174176
end
175177

176178
defp format_autograding_results(nil), do: "N/A"
@@ -365,35 +367,5 @@ defmodule CadetWeb.AICodeAnalysisController do
365367
}
366368
end
367369

368-
defp decrypt_llm_api_key(nil), do: nil
369370

370-
defp decrypt_llm_api_key(encrypted_key) do
371-
case Application.get_env(:openai, :encryption_key) do
372-
secret when is_binary(secret) and byte_size(secret) >= 16 ->
373-
key = binary_part(secret, 0, min(32, byte_size(secret)))
374-
375-
case Base.decode64(encrypted_key) do
376-
{:ok, decoded} ->
377-
iv = binary_part(decoded, 0, 16)
378-
tag = binary_part(decoded, 16, 16)
379-
ciphertext = binary_part(decoded, 32, byte_size(decoded) - 32)
380-
381-
case :crypto.crypto_one_time_aead(:aes_gcm, key, iv, ciphertext, "", tag, false) do
382-
plain_text when is_binary(plain_text) -> {:ok, plain_text}
383-
_ -> {:decrypt_error, :decryption_failed}
384-
end
385-
386-
_ ->
387-
Logger.error(
388-
"Failed to decode encrypted key, is it a valid AES-256 key of 16, 24 or 32 bytes?"
389-
)
390-
391-
{:decrypt_error, :decryption_failed}
392-
end
393-
394-
_ ->
395-
Logger.error("Encryption key not configured")
396-
{:decrypt_error, :invalid_encryption_key}
397-
end
398-
end
399371
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
defmodule CadetWeb.AICommentsHelpers do
2+
@moduledoc """
3+
Helper functions for Managing LLM related logic
4+
"""
5+
6+
def decrypt_llm_api_key(nil), do: nil
7+
8+
def decrypt_llm_api_key(encrypted_key) do
9+
case Application.get_env(:openai, :encryption_key) do
10+
secret when is_binary(secret) and byte_size(secret) >= 16 ->
11+
key = binary_part(secret, 0, min(32, byte_size(secret)))
12+
13+
case Base.decode64(encrypted_key) do
14+
{:ok, decoded} ->
15+
iv = binary_part(decoded, 0, 16)
16+
tag = binary_part(decoded, 16, 16)
17+
ciphertext = binary_part(decoded, 32, byte_size(decoded) - 32)
18+
19+
case :crypto.crypto_one_time_aead(:aes_gcm, key, iv, ciphertext, "", tag, false) do
20+
plain_text when is_binary(plain_text) -> {:ok, plain_text}
21+
_ -> {:decrypt_error, :decryption_failed}
22+
end
23+
24+
_ ->
25+
Logger.error(
26+
"Failed to decode encrypted key, is it a valid AES-256 key of 16, 24 or 32 bytes?"
27+
)
28+
29+
{:decrypt_error, :decryption_failed}
30+
end
31+
32+
_ ->
33+
Logger.error("Encryption key not configured")
34+
{:decrypt_error, :invalid_encryption_key}
35+
end
36+
end
37+
38+
def encrypt_llm_api_key(llm_api_key) do
39+
secret = Application.get_env(:openai, :encryption_key)
40+
41+
if is_binary(secret) and byte_size(secret) >= 16 do
42+
# Use first 16 bytes for AES-128, 24 for AES-192, or 32 for AES-256
43+
key = binary_part(secret, 0, min(32, byte_size(secret)))
44+
# Use AES in GCM mode for encryption
45+
iv = :crypto.strong_rand_bytes(16)
46+
47+
{ciphertext, tag} =
48+
:crypto.crypto_one_time_aead(
49+
:aes_gcm,
50+
key,
51+
iv,
52+
llm_api_key,
53+
"",
54+
true
55+
)
56+
57+
# Store both the IV, ciphertext and tag
58+
encrypted = Base.encode64(iv <> tag <> ciphertext)
59+
else
60+
{:error, :invalid_encryption_key}
61+
end
62+
end
63+
64+
end

priv/repo/migrations/20251022103623_create_ai_comments.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ defmodule Cadet.Repo.Migrations.CreateAiCommentLogs do
88
add(:answers_json, :text, null: false)
99
add(:response, :text)
1010
add(:error, :text)
11-
add(:comment_chosen, {:array, :text})
1211
add(:final_comment, :text)
1312
timestamps()
1413
end

0 commit comments

Comments
 (0)