Skip to content

Commit c907cdd

Browse files
committed
feat: short answer AI XBlock handle attachment encoding and errors
1 parent bc83b80 commit c907cdd

File tree

3 files changed

+27
-5
lines changed

3 files changed

+27
-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 traceback
56
import urllib.parse
@@ -144,6 +145,15 @@ def validate_field_data(self, validation, data):
144145
)
145146
)
146147

148+
try:
149+
self._get_attachments(data.attachment_urls)
150+
except Exception:
151+
validation.add(
152+
ValidationMessage(
153+
ValidationMessage.ERROR, _("Error downloading attachments"),
154+
)
155+
)
156+
147157
def student_view(self, context=None):
148158
"""
149159
The primary view of the ShortAnswerAIEvalXBlock, shown to students
@@ -180,15 +190,17 @@ def student_view(self, context=None):
180190

181191
def _download_attachment(self, url):
182192
with urllib.request.urlopen(url) as f:
183-
return f.read().decode('utf-8')
193+
data = f.read()
194+
encoding = chardet.detect(data)['encoding']
195+
return data.decode(encoding)
184196

185197
def _filename_for_url(self, url):
186198
return urllib.parse.urlparse(url).path.split('/')[-1]
187199

188-
def _get_attachments(self):
200+
def _get_attachments(self, attachment_urls):
189201
pool = Pool(self.ATTACHMENT_PARALLEL_DOWNLOADS)
190-
attachments = pool.map(self._download_attachment, self.attachment_urls)
191-
filenames = map(self._filename_for_url, self.attachment_urls)
202+
attachments = pool.map(self._download_attachment, attachment_urls)
203+
filenames = map(self._filename_for_url, attachment_urls)
192204
return zip(filenames, attachments)
193205

194206
@XBlock.json_handler
@@ -197,7 +209,7 @@ def get_response(self, data, suffix=""): # pylint: disable=unused-argument
197209
user_submission = str(data["user_input"])
198210

199211
attachments = []
200-
for filename, contents in self._get_attachments():
212+
for filename, contents in self._get_attachments(self.attachment_urls):
201213
attachments.append(f"""
202214
<attachment>
203215
<filename>{saxutils.escape(filename)}</filename>

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
@@ -145,6 +146,14 @@ def test_shortanswer_attachments(shortanswer_block_data):
145146
assert "<contents>file contents &lt;&amp;&gt;</contents>" in prompt
146147

147148

149+
def test_shortanswer_attachments_encoding(shortanswer_block_data):
150+
"""Test attachments for ShortAnswerAIEvalXBlock."""
151+
block = ShortAnswerAIEvalXBlock(ToyRuntime(), DictFieldData(shortanswer_block_data), None)
152+
urllib.request.urlopen = Mock(return_value=io.BytesIO("á".encode('latin-1')))
153+
contents = block._download_attachment("http://example.com/1.txt")
154+
assert contents == "á"
155+
156+
148157
def test_multiagent_block_finished():
149158
"""Test the MultiAgentAIEvalXBlock for not allowing input after finished."""
150159
data = {

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def package_data(pkg, roots):
3232
install_requires=[
3333
"XBlock",
3434
"celery",
35+
"chardet",
3536
"litellm>=1.42",
3637
],
3738
entry_points={

0 commit comments

Comments
 (0)