Skip to content

Commit 29f2c66

Browse files
committed
feat: short answer AI XBlock handle attachment encoding and errors
1 parent ed19550 commit 29f2c66

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 hashlib
56
import urllib.parse
@@ -146,6 +147,15 @@ def validate_field_data(self, validation, data):
146147
)
147148
)
148149

150+
try:
151+
self._get_attachments(data.attachment_urls)
152+
except Exception:
153+
validation.add(
154+
ValidationMessage(
155+
ValidationMessage.ERROR, _("Error downloading attachments"),
156+
)
157+
)
158+
149159
def student_view(self, context=None):
150160
"""
151161
The primary view of the ShortAnswerAIEvalXBlock, shown to students
@@ -182,15 +192,17 @@ def student_view(self, context=None):
182192

183193
def _download_attachment(self, url):
184194
with urllib.request.urlopen(url) as f:
185-
return f.read().decode('utf-8')
195+
data = f.read()
196+
encoding = chardet.detect(data)['encoding']
197+
return data.decode(encoding)
186198

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

190-
def _get_attachments(self):
202+
def _get_attachments(self, attachment_urls):
191203
pool = Pool(self.ATTACHMENT_PARALLEL_DOWNLOADS)
192-
attachments = pool.map(self._download_attachment, self.attachment_urls)
193-
filenames = map(self._filename_for_url, self.attachment_urls)
204+
attachments = pool.map(self._download_attachment, attachment_urls)
205+
filenames = map(self._filename_for_url, attachment_urls)
194206
return zip(filenames, attachments)
195207

196208
@XBlock.json_handler
@@ -200,7 +212,7 @@ def get_response(self, data, suffix=""): # pylint: disable=unused-argument
200212

201213
attachments = []
202214
attachment_hash_inputs = []
203-
for filename, contents in self._get_attachments():
215+
for filename, contents in self._get_attachments(self.attachment_urls):
204216
# Build system prompt attachment section (HTML-like) as before
205217
attachments.append(f"""
206218
<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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def package_data(pkg, roots):
3333
install_requires=[
3434
"XBlock",
3535
"celery",
36+
"chardet",
3637
"litellm>=0.14,<1.0",
3738
],
3839
entry_points={

0 commit comments

Comments
 (0)