Skip to content

Commit 1388362

Browse files
committed
feat: short answer AI XBlock handle attachment encoding and errors
1 parent 30e6c2b commit 1388362

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

ai_eval/shortanswer.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Short answers Xblock with AI evaluation."""
22

3+
import chardet
34
import logging
45
import hashlib
56
import urllib.parse
@@ -128,6 +129,15 @@ def validate_field_data(self, validation, data):
128129
)
129130
)
130131

132+
try:
133+
self._get_attachments(data.attachment_urls)
134+
except Exception:
135+
validation.add(
136+
ValidationMessage(
137+
ValidationMessage.ERROR, _("Error downloading attachments"),
138+
)
139+
)
140+
131141
def student_view(self, context=None):
132142
"""
133143
The primary view of the ShortAnswerAIEvalXBlock, shown to students
@@ -164,15 +174,17 @@ def student_view(self, context=None):
164174

165175
def _download_attachment(self, url):
166176
with urllib.request.urlopen(url) as f:
167-
return f.read().decode('utf-8')
177+
data = f.read()
178+
encoding = chardet.detect(data)['encoding']
179+
return data.decode(encoding)
168180

169181
def _filename_for_url(self, url):
170182
return urllib.parse.urlparse(url).path.split('/')[-1]
171183

172-
def _get_attachments(self):
184+
def _get_attachments(self, attachment_urls):
173185
pool = Pool(self.ATTACHMENT_PARALLEL_DOWNLOADS)
174-
attachments = pool.map(self._download_attachment, self.attachment_urls)
175-
filenames = map(self._filename_for_url, self.attachment_urls)
186+
attachments = pool.map(self._download_attachment, attachment_urls)
187+
filenames = map(self._filename_for_url, attachment_urls)
176188
return zip(filenames, attachments)
177189

178190
@XBlock.json_handler
@@ -182,7 +194,7 @@ def get_response(self, data, suffix=""): # pylint: disable=unused-argument
182194

183195
attachments = []
184196
attachment_hash_inputs = []
185-
for filename, contents in self._get_attachments():
197+
for filename, contents in self._get_attachments(self.attachment_urls):
186198
# Build system prompt attachment section (HTML-like) as before
187199
attachments.append(f"""
188200
<attachment>

ai_eval/tests/test_ai_eval.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
# pylint: disable=redefined-outer-name,protected-access
55

6+
import urllib.request
67
from unittest.mock import Mock, patch
78

89
import pytest
@@ -159,6 +160,14 @@ def test_shortanswer_attachments(shortanswer_block_data):
159160
assert "<contents>file contents &lt;&amp;&gt;</contents>" in prompt
160161

161162

163+
def test_shortanswer_attachments_encoding(shortanswer_block_data):
164+
"""Test attachments for ShortAnswerAIEvalXBlock."""
165+
block = ShortAnswerAIEvalXBlock(ToyRuntime(), DictFieldData(shortanswer_block_data), None)
166+
urllib.request.urlopen = Mock(return_value=io.BytesIO("á".encode('latin-1')))
167+
contents = block._download_attachment("http://example.com/1.txt")
168+
assert contents == "á"
169+
170+
162171
def test_multiagent_block_finished():
163172
"""Test the MultiAgentAIEvalXBlock for not allowing input after finished."""
164173
data = {

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def package_data(pkg, roots):
3232
),
3333
install_requires=[
3434
"XBlock",
35+
"celery",
36+
"chardet",
3537
"litellm>=0.14,<1.0",
3638
],
3739
entry_points={

0 commit comments

Comments
 (0)