edsl 0.1.33__py3-none-any.whl → 0.1.33.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. edsl/Base.py +3 -9
  2. edsl/__init__.py +0 -1
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +6 -6
  5. edsl/agents/Invigilator.py +3 -6
  6. edsl/agents/InvigilatorBase.py +27 -8
  7. edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +29 -101
  8. edsl/config.py +34 -26
  9. edsl/coop/coop.py +2 -11
  10. edsl/data_transfer_models.py +73 -26
  11. edsl/enums.py +0 -2
  12. edsl/inference_services/GoogleService.py +1 -1
  13. edsl/inference_services/InferenceServiceABC.py +13 -44
  14. edsl/inference_services/OpenAIService.py +4 -7
  15. edsl/inference_services/TestService.py +15 -24
  16. edsl/inference_services/registry.py +0 -2
  17. edsl/jobs/Jobs.py +8 -18
  18. edsl/jobs/buckets/BucketCollection.py +15 -24
  19. edsl/jobs/buckets/TokenBucket.py +10 -64
  20. edsl/jobs/interviews/Interview.py +47 -115
  21. edsl/jobs/interviews/InterviewExceptionEntry.py +0 -2
  22. edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +0 -16
  23. edsl/jobs/interviews/retry_management.py +39 -0
  24. edsl/jobs/runners/JobsRunnerAsyncio.py +170 -95
  25. edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
  26. edsl/jobs/tasks/TaskHistory.py +0 -17
  27. edsl/language_models/LanguageModel.py +31 -26
  28. edsl/language_models/registry.py +9 -13
  29. edsl/questions/QuestionBase.py +14 -63
  30. edsl/questions/QuestionBudget.py +41 -93
  31. edsl/questions/QuestionFreeText.py +0 -6
  32. edsl/questions/QuestionMultipleChoice.py +23 -8
  33. edsl/questions/QuestionNumerical.py +4 -5
  34. edsl/questions/ResponseValidatorABC.py +5 -6
  35. edsl/questions/derived/QuestionLinearScale.py +1 -4
  36. edsl/questions/derived/QuestionTopK.py +1 -4
  37. edsl/questions/derived/QuestionYesNo.py +2 -8
  38. edsl/results/DatasetExportMixin.py +1 -5
  39. edsl/results/Result.py +1 -1
  40. edsl/results/Results.py +1 -4
  41. edsl/scenarios/FileStore.py +10 -71
  42. edsl/scenarios/Scenario.py +21 -86
  43. edsl/scenarios/ScenarioImageMixin.py +2 -2
  44. edsl/scenarios/ScenarioList.py +0 -13
  45. edsl/scenarios/ScenarioListPdfMixin.py +4 -150
  46. edsl/study/Study.py +0 -32
  47. edsl/surveys/Rule.py +1 -10
  48. edsl/surveys/RuleCollection.py +3 -19
  49. edsl/surveys/Survey.py +0 -7
  50. edsl/templates/error_reporting/interview_details.html +1 -6
  51. edsl/utilities/utilities.py +1 -9
  52. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +1 -2
  53. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/RECORD +55 -61
  54. edsl/inference_services/TogetherAIService.py +0 -170
  55. edsl/jobs/runners/JobsRunnerStatus.py +0 -331
  56. edsl/questions/Quick.py +0 -41
  57. edsl/questions/templates/budget/__init__.py +0 -0
  58. edsl/questions/templates/budget/answering_instructions.jinja +0 -7
  59. edsl/questions/templates/budget/question_presentation.jinja +0 -7
  60. edsl/questions/templates/extract/__init__.py +0 -0
  61. edsl/questions/templates/rank/__init__.py +0 -0
  62. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
  63. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -5,10 +5,6 @@ import copy
5
5
  import base64
6
6
  import hashlib
7
7
  import os
8
- import reprlib
9
- import imghdr
10
-
11
-
12
8
  from collections import UserDict
13
9
  from typing import Union, List, Optional, Generator
14
10
  from uuid import uuid4
@@ -17,8 +13,6 @@ from edsl.scenarios.ScenarioImageMixin import ScenarioImageMixin
17
13
  from edsl.scenarios.ScenarioHtmlMixin import ScenarioHtmlMixin
18
14
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
19
15
 
20
- from edsl.data_transfer_models import ImageInfo
21
-
22
16
 
23
17
  class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
24
18
  """A Scenario is a dictionary of keys/values.
@@ -55,39 +49,6 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
55
49
  self._has_image = False
56
50
  return self._has_image
57
51
 
58
- @property
59
- def has_jinja_braces(self) -> bool:
60
- """Return whether the scenario has jinja braces. This matters for rendering.
61
-
62
- >>> s = Scenario({"food": "I love {{wood chips}}"})
63
- >>> s.has_jinja_braces
64
- True
65
- """
66
- for key, value in self.items():
67
- if "{{" in str(value) and "}}" in value:
68
- return True
69
- return False
70
-
71
- def convert_jinja_braces(
72
- self, replacement_left="<<", replacement_right=">>"
73
- ) -> Scenario:
74
- """Convert Jinja braces to some other character.
75
-
76
- >>> s = Scenario({"food": "I love {{wood chips}}"})
77
- >>> s.convert_jinja_braces()
78
- Scenario({'food': 'I love <<wood chips>>'})
79
-
80
- """
81
- new_scenario = Scenario()
82
- for key, value in self.items():
83
- if isinstance(value, str):
84
- new_scenario[key] = value.replace("{{", replacement_left).replace(
85
- "}}", replacement_right
86
- )
87
- else:
88
- new_scenario[key] = value
89
- return new_scenario
90
-
91
52
  @has_image.setter
92
53
  def has_image(self, value):
93
54
  self._has_image = value
@@ -181,7 +142,6 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
181
142
  print_json(json.dumps(self.to_dict()))
182
143
 
183
144
  def __repr__(self):
184
- # return "Scenario(" + reprlib.repr(self.data) + ")"
185
145
  return "Scenario(" + repr(self.data) + ")"
186
146
 
187
147
  def _repr_html_(self):
@@ -236,48 +196,26 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
236
196
  return cls({"url": url, field_name: text})
237
197
 
238
198
  @classmethod
239
- def from_image(
240
- cls, image_path: str, image_name: Optional[str] = None
241
- ) -> "Scenario":
242
- """
243
- Creates a scenario with a base64 encoding of an image.
244
-
245
- Args:
246
- image_path (str): Path to the image file.
247
-
248
- Returns:
249
- Scenario: A new Scenario instance with image information.
199
+ def from_image(cls, image_path: str) -> str:
200
+ """Creates a scenario with a base64 encoding of an image.
250
201
 
251
202
  Example:
203
+
252
204
  >>> s = Scenario.from_image(Scenario.example_image())
253
205
  >>> s
254
- Scenario({'logo': ...})
206
+ Scenario({'file_path': '...', 'encoded_image': '...'})
255
207
  """
256
- if not os.path.exists(image_path):
257
- raise FileNotFoundError(f"Image file not found: {image_path}")
258
-
259
208
  with open(image_path, "rb") as image_file:
260
- file_content = image_file.read()
261
-
262
- file_name = os.path.basename(image_path)
263
- file_size = os.path.getsize(image_path)
264
- image_format = imghdr.what(image_path) or "unknown"
265
-
266
- if image_name is None:
267
- image_name = file_name.split(".")[0]
268
-
269
- image_info = ImageInfo(
270
- file_path=image_path,
271
- file_name=file_name,
272
- image_format=image_format,
273
- file_size=file_size,
274
- encoded_image=base64.b64encode(file_content).decode("utf-8"),
275
- )
276
-
277
- scenario_data = {image_name: image_info}
278
- s = cls(scenario_data)
279
- s.has_image = True
280
- return s
209
+ s = cls(
210
+ {
211
+ "file_path": image_path,
212
+ "encoded_image": base64.b64encode(image_file.read()).decode(
213
+ "utf-8"
214
+ ),
215
+ }
216
+ )
217
+ s.has_image = True
218
+ return s
281
219
 
282
220
  @classmethod
283
221
  def from_pdf(cls, pdf_path):
@@ -491,21 +429,18 @@ class Scenario(Base, UserDict, ScenarioImageMixin, ScenarioHtmlMixin):
491
429
  return table
492
430
 
493
431
  @classmethod
494
- def example(cls, randomize: bool = False, has_image=False) -> Scenario:
432
+ def example(cls, randomize: bool = False) -> Scenario:
495
433
  """
496
434
  Returns an example Scenario instance.
497
435
 
498
436
  :param randomize: If True, adds a random string to the value of the example key.
499
437
  """
500
- if not has_image:
501
- addition = "" if not randomize else str(uuid4())
502
- return cls(
503
- {
504
- "persona": f"A reseacher studying whether LLMs can be used to generate surveys.{addition}",
505
- }
506
- )
507
- else:
508
- return cls.from_image(cls.example_image())
438
+ addition = "" if not randomize else str(uuid4())
439
+ return cls(
440
+ {
441
+ "persona": f"A reseacher studying whether LLMs can be used to generate surveys.{addition}",
442
+ }
443
+ )
509
444
 
510
445
  def code(self) -> List[str]:
511
446
  """Return the code for the scenario."""
@@ -13,7 +13,7 @@ class ScenarioImageMixin:
13
13
  >>> from edsl.scenarios.Scenario import Scenario
14
14
  >>> s = Scenario({"food": "wood chips"})
15
15
  >>> s.add_image(Scenario.example_image())
16
- Scenario({'food': 'wood chips', 'logo': ...})
16
+ Scenario({'food': 'wood chips', 'file_path': '...', 'encoded_image': '...'})
17
17
  """
18
18
  new_scenario = self.from_image(image_path)
19
19
  return self + new_scenario
@@ -33,7 +33,7 @@ class ScenarioImageMixin:
33
33
  >>> from edsl.scenarios.Scenario import Scenario
34
34
  >>> s = Scenario.from_image(Scenario.example_image())
35
35
  >>> s
36
- Scenario({'logo': ...})
36
+ Scenario({'file_path': '...', 'encoded_image': '...'})
37
37
  """
38
38
 
39
39
  if image_path.startswith("http://") or image_path.startswith("https://"):
@@ -39,15 +39,6 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
39
39
  super().__init__([])
40
40
  self.codebook = codebook or {}
41
41
 
42
- @property
43
- def has_jinja_braces(self) -> bool:
44
- """Check if the ScenarioList has Jinja braces."""
45
- return any([scenario.has_jinja_braces for scenario in self])
46
-
47
- def convert_jinja_braces(self) -> ScenarioList:
48
- """Convert Jinja braces to Python braces."""
49
- return ScenarioList([scenario.convert_jinja_braces() for scenario in self])
50
-
51
42
  def give_valid_names(self) -> ScenarioList:
52
43
  """Give valid names to the scenario keys.
53
44
 
@@ -282,10 +273,6 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
282
273
  for s in data["scenarios"]:
283
274
  _ = s.pop("edsl_version")
284
275
  _ = s.pop("edsl_class_name")
285
- for scenario in data["scenarios"]:
286
- for key, value in scenario.items():
287
- if hasattr(value, "to_dict"):
288
- data[key] = value.to_dict()
289
276
  return data_to_html(data)
290
277
 
291
278
  def tally(self, field) -> dict:
@@ -1,161 +1,15 @@
1
1
  import fitz # PyMuPDF
2
2
  import os
3
- import copy
4
3
  import subprocess
5
- import requests
6
- import tempfile
7
- import os
8
-
9
- # import urllib.parse as urlparse
10
- from urllib.parse import urlparse
11
4
 
12
5
  # from edsl import Scenario
13
6
 
14
- import requests
15
- import re
16
- import tempfile
17
- import os
18
- import atexit
19
- from urllib.parse import urlparse, parse_qs
20
-
21
-
22
- class GoogleDriveDownloader:
23
- _temp_dir = None
24
- _temp_file_path = None
25
-
26
- @classmethod
27
- def fetch_from_drive(cls, url, filename=None):
28
- # Extract file ID from the URL
29
- file_id = cls._extract_file_id(url)
30
- if not file_id:
31
- raise ValueError("Invalid Google Drive URL")
32
-
33
- # Construct the download URL
34
- download_url = f"https://drive.google.com/uc?export=download&id={file_id}"
35
-
36
- # Send a GET request to the URL
37
- session = requests.Session()
38
- response = session.get(download_url, stream=True)
39
- response.raise_for_status()
40
-
41
- # Check for large file download prompt
42
- for key, value in response.cookies.items():
43
- if key.startswith("download_warning"):
44
- params = {"id": file_id, "confirm": value}
45
- response = session.get(download_url, params=params, stream=True)
46
- break
47
-
48
- # Create a temporary file to save the download
49
- if not filename:
50
- filename = "downloaded_file"
51
-
52
- if cls._temp_dir is None:
53
- cls._temp_dir = tempfile.TemporaryDirectory()
54
- atexit.register(cls._cleanup)
55
-
56
- cls._temp_file_path = os.path.join(cls._temp_dir.name, filename)
57
-
58
- # Write the content to the temporary file
59
- with open(cls._temp_file_path, "wb") as f:
60
- for chunk in response.iter_content(32768):
61
- if chunk:
62
- f.write(chunk)
63
-
64
- print(f"File saved to: {cls._temp_file_path}")
65
-
66
- return cls._temp_file_path
67
-
68
- @staticmethod
69
- def _extract_file_id(url):
70
- # Try to extract file ID from '/file/d/' format
71
- file_id_match = re.search(r"/d/([a-zA-Z0-9-_]+)", url)
72
- if file_id_match:
73
- return file_id_match.group(1)
74
-
75
- # If not found, try to extract from 'open?id=' format
76
- parsed_url = urlparse(url)
77
- query_params = parse_qs(parsed_url.query)
78
- if "id" in query_params:
79
- return query_params["id"][0]
80
-
81
- return None
82
-
83
- @classmethod
84
- def _cleanup(cls):
85
- if cls._temp_dir:
86
- cls._temp_dir.cleanup()
87
-
88
- @classmethod
89
- def get_temp_file_path(cls):
90
- return cls._temp_file_path
91
-
92
-
93
- def fetch_and_save_pdf(url, filename):
94
- # Send a GET request to the URL
95
- response = requests.get(url)
96
-
97
- # Check if the request was successful
98
- response.raise_for_status()
99
-
100
- # Create a temporary directory
101
- with tempfile.TemporaryDirectory() as temp_dir:
102
- # Construct the full path for the file
103
- temp_file_path = os.path.join(temp_dir, filename)
104
-
105
- # Write the content to the temporary file
106
- with open(temp_file_path, "wb") as file:
107
- file.write(response.content)
108
-
109
- print(f"PDF saved to: {temp_file_path}")
110
-
111
- # Here you can perform operations with the file
112
- # The file will be automatically deleted when you exit this block
113
-
114
- return temp_file_path
115
-
116
-
117
- # Example usage:
118
- # url = "https://example.com/sample.pdf"
119
- # fetch_and_save_pdf(url, "sample.pdf")
120
-
121
7
 
122
8
  class ScenarioListPdfMixin:
123
9
  @classmethod
124
- def from_pdf(cls, filename_or_url, collapse_pages=False):
125
- # Check if the input is a URL
126
- if cls.is_url(filename_or_url):
127
- # Check if it's a Google Drive URL
128
- if "drive.google.com" in filename_or_url:
129
- temp_filename = GoogleDriveDownloader.fetch_from_drive(
130
- filename_or_url, "temp_pdf.pdf"
131
- )
132
- else:
133
- # For other URLs, use the previous fetch_and_save_pdf function
134
- temp_filename = fetch_and_save_pdf(filename_or_url, "temp_pdf.pdf")
135
-
136
- scenarios = list(cls.extract_text_from_pdf(temp_filename))
137
- else:
138
- # If it's not a URL, assume it's a local file path
139
- scenarios = list(cls.extract_text_from_pdf(filename_or_url))
140
- if not collapse_pages:
141
- return cls(scenarios)
142
- else:
143
- txt = ""
144
- for scenario in scenarios:
145
- txt += scenario["text"]
146
- from edsl.scenarios import Scenario
147
-
148
- base_scenario = copy.copy(scenarios[0])
149
- base_scenario["text"] = txt
150
- return base_scenario
151
-
152
- @staticmethod
153
- def is_url(string):
154
- try:
155
- result = urlparse(string)
156
- return all([result.scheme, result.netloc])
157
- except ValueError:
158
- return False
10
+ def from_pdf(cls, filename):
11
+ scenarios = list(cls.extract_text_from_pdf(filename))
12
+ return cls(scenarios)
159
13
 
160
14
  @classmethod
161
15
  def _from_pdf_to_image(cls, pdf_path, image_format="jpeg"):
@@ -184,7 +38,7 @@ class ScenarioListPdfMixin:
184
38
  scenario = Scenario._from_filepath_image(image_path)
185
39
  scenarios.append(scenario)
186
40
 
187
- # print(f"Saved {len(images)} pages as images in {output_folder}")
41
+ print(f"Saved {len(images)} pages as images in {output_folder}")
188
42
  return cls(scenarios)
189
43
 
190
44
  @staticmethod
edsl/study/Study.py CHANGED
@@ -469,38 +469,6 @@ class Study:
469
469
  coop = Coop()
470
470
  return coop.create(self, description=self.description)
471
471
 
472
- def delete_object(self, identifier: Union[str, UUID]):
473
- """
474
- Delete an EDSL object from the study.
475
-
476
- :param identifier: Either the variable name or the hash of the object to delete
477
- :raises ValueError: If the object is not found in the study
478
- """
479
- if isinstance(identifier, str):
480
- # If identifier is a variable name or a string representation of UUID
481
- for hash, obj_entry in list(self.objects.items()):
482
- if obj_entry.variable_name == identifier or hash == identifier:
483
- del self.objects[hash]
484
- self._create_mapping_dicts() # Update internal mappings
485
- if self.verbose:
486
- print(f"Deleted object with identifier: {identifier}")
487
- return
488
- raise ValueError(f"No object found with identifier: {identifier}")
489
- elif isinstance(identifier, UUID):
490
- # If identifier is a UUID object
491
- hash_str = str(identifier)
492
- if hash_str in self.objects:
493
- del self.objects[hash_str]
494
- self._create_mapping_dicts() # Update internal mappings
495
- if self.verbose:
496
- print(f"Deleted object with hash: {hash_str}")
497
- return
498
- raise ValueError(f"No object found with hash: {hash_str}")
499
- else:
500
- raise TypeError(
501
- "Identifier must be either a string (variable name or hash) or a UUID object"
502
- )
503
-
504
472
  @classmethod
505
473
  def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
506
474
  """Pull the object from coop."""
edsl/surveys/Rule.py CHANGED
@@ -18,7 +18,6 @@ with a low (-1) priority.
18
18
  """
19
19
 
20
20
  import ast
21
- import random
22
21
  from typing import Any, Union, List
23
22
 
24
23
  from jinja2 import Template
@@ -255,16 +254,8 @@ class Rule:
255
254
  msg = f"""Exception in evaluation: {e}. The expression is: {self.expression}. The current info env trying to substitute in is: {current_info_env}. After the substition, the expression was: {to_evaluate}."""
256
255
  raise SurveyRuleCannotEvaluateError(msg)
257
256
 
258
- random_functions = {
259
- "randint": random.randint,
260
- "choice": random.choice,
261
- "random": random.random,
262
- "uniform": random.uniform,
263
- # Add any other random functions you want to allow
264
- }
265
-
266
257
  try:
267
- return EvalWithCompoundTypes(functions=random_functions).eval(to_evaluate)
258
+ return EvalWithCompoundTypes().eval(to_evaluate)
268
259
  except Exception as e:
269
260
  msg = f"""Exception in evaluation: {e}. The expression is: {self.expression}. The current info env trying to substitute in is: {current_info_env}. After the substition, the expression was: {to_evaluate}."""
270
261
  raise SurveyRuleCannotEvaluateError(msg)
@@ -172,8 +172,7 @@ class RuleCollection(UserList):
172
172
 
173
173
  def next_question(self, q_now: int, answers: dict[str, Any]) -> NextQuestion:
174
174
  """Find the next question by index, given the rule collection.
175
-
176
- This rule is applied after the question is answered.
175
+ This rule is applied after the question is asked.
177
176
 
178
177
  :param q_now: The current question index.
179
178
  :param answers: The answers to the survey questions so far, including the current question.
@@ -183,17 +182,8 @@ class RuleCollection(UserList):
183
182
  NextQuestion(next_q=3, num_rules_found=2, expressions_evaluating_to_true=1, priority=1)
184
183
 
185
184
  """
186
- # # is this the first question? If it is, we need to check if it should be skipped.
187
- # if q_now == 0:
188
- # if self.skip_question_before_running(q_now, answers):
189
- # return NextQuestion(
190
- # next_q=q_now + 1,
191
- # num_rules_found=0,
192
- # expressions_evaluating_to_true=0,
193
- # priority=-1,
194
- # )
195
-
196
- # breakpoint()
185
+ # What rules apply at the current node?
186
+
197
187
  expressions_evaluating_to_true = 0
198
188
  next_q = None
199
189
  highest_priority = -2 # start with -2 to 'pick up' the default rule added
@@ -215,12 +205,6 @@ class RuleCollection(UserList):
215
205
  f"No rules found for question {q_now}"
216
206
  )
217
207
 
218
- # breakpoint()
219
- ## Now we need to check if the *next question* has any 'before; rules that we should follow
220
- for rule in self.applicable_rules(next_q, before_rule=True):
221
- if rule.evaluate(answers): # rule evaluates to True
222
- return self.next_question(next_q, answers)
223
-
224
208
  return NextQuestion(
225
209
  next_q, num_rules_found, expressions_evaluating_to_true, highest_priority
226
210
  )
edsl/surveys/Survey.py CHANGED
@@ -866,7 +866,6 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
866
866
 
867
867
  def clear_non_default_rules(self) -> Survey:
868
868
  """Remove all non-default rules from the survey.
869
-
870
869
  >>> Survey.example().show_rules()
871
870
  ┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┓
872
871
  ┃ current_q ┃ expression ┃ next_q ┃ priority ┃ before_rule ┃
@@ -1174,15 +1173,9 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
1174
1173
  Question('multiple_choice', question_name = \"""q0\""", question_text = \"""Do you like school?\""", question_options = ['yes', 'no'])
1175
1174
  >>> i2.send({"q0": "no"})
1176
1175
  Question('multiple_choice', question_name = \"""q1\""", question_text = \"""Why not?\""", question_options = ['killer bees in cafeteria', 'other'])
1177
-
1178
-
1179
1176
  """
1180
1177
  self.answers = {}
1181
1178
  question = self._questions[0]
1182
- # should the first question be skipped?
1183
- if self.rule_collection.skip_question_before_running(0, self.answers):
1184
- question = self.next_question(question, self.answers)
1185
-
1186
1179
  while not question == EndOfSurvey:
1187
1180
  # breakpoint()
1188
1181
  answer = yield question
@@ -31,12 +31,7 @@
31
31
 
32
32
  <tr>
33
33
  <td>Human-readable question</td>
34
- <td>{{ interview.survey.get_question(question).html(
35
- scenario = interview.scenario,
36
- agent = interview.agent,
37
- answers = exception_message.answers)
38
-
39
- }}</td>
34
+ <td>{{ interview.survey.get_question(question).html(scenario = interview.scenario, agent = interview.agent) }}</td>
40
35
  </tr>
41
36
  <tr>
42
37
  <td>Scenario</td>
@@ -20,14 +20,6 @@ from html import escape
20
20
  from typing import Callable, Union
21
21
 
22
22
 
23
- class CustomEncoder(json.JSONEncoder):
24
- def default(self, obj):
25
- try:
26
- return json.JSONEncoder.default(self, obj)
27
- except TypeError:
28
- return str(obj)
29
-
30
-
31
23
  def time_it(func):
32
24
  @wraps(func)
33
25
  def wrapper(*args, **kwargs):
@@ -132,7 +124,7 @@ def data_to_html(data, replace_new_lines=False):
132
124
  from pygments.formatters import HtmlFormatter
133
125
  from IPython.display import HTML
134
126
 
135
- json_str = json.dumps(data, indent=4, cls=CustomEncoder)
127
+ json_str = json.dumps(data, indent=4)
136
128
  formatted_json = highlight(
137
129
  json_str,
138
130
  JsonLexer(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edsl
3
- Version: 0.1.33
3
+ Version: 0.1.33.dev2
4
4
  Summary: Create and analyze LLM-based surveys
5
5
  Home-page: https://www.expectedparrot.com/
6
6
  License: MIT
@@ -45,7 +45,6 @@ Requires-Dist: setuptools (<72.0)
45
45
  Requires-Dist: simpleeval (>=0.9.13,<0.10.0)
46
46
  Requires-Dist: sqlalchemy (>=2.0.23,<3.0.0)
47
47
  Requires-Dist: tenacity (>=8.2.3,<9.0.0)
48
- Requires-Dist: urllib3 (>=1.25.4,<1.27)
49
48
  Project-URL: Documentation, https://docs.expectedparrot.com
50
49
  Description-Content-Type: text/markdown
51
50