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.
- edsl/Base.py +3 -9
- edsl/__init__.py +0 -1
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +6 -6
- edsl/agents/Invigilator.py +3 -6
- edsl/agents/InvigilatorBase.py +27 -8
- edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +29 -101
- edsl/config.py +34 -26
- edsl/coop/coop.py +2 -11
- edsl/data_transfer_models.py +73 -26
- edsl/enums.py +0 -2
- edsl/inference_services/GoogleService.py +1 -1
- edsl/inference_services/InferenceServiceABC.py +13 -44
- edsl/inference_services/OpenAIService.py +4 -7
- edsl/inference_services/TestService.py +15 -24
- edsl/inference_services/registry.py +0 -2
- edsl/jobs/Jobs.py +8 -18
- edsl/jobs/buckets/BucketCollection.py +15 -24
- edsl/jobs/buckets/TokenBucket.py +10 -64
- edsl/jobs/interviews/Interview.py +47 -115
- edsl/jobs/interviews/InterviewExceptionEntry.py +0 -2
- edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +0 -16
- edsl/jobs/interviews/retry_management.py +39 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +170 -95
- edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
- edsl/jobs/tasks/TaskHistory.py +0 -17
- edsl/language_models/LanguageModel.py +31 -26
- edsl/language_models/registry.py +9 -13
- edsl/questions/QuestionBase.py +14 -63
- edsl/questions/QuestionBudget.py +41 -93
- edsl/questions/QuestionFreeText.py +0 -6
- edsl/questions/QuestionMultipleChoice.py +23 -8
- edsl/questions/QuestionNumerical.py +4 -5
- edsl/questions/ResponseValidatorABC.py +5 -6
- edsl/questions/derived/QuestionLinearScale.py +1 -4
- edsl/questions/derived/QuestionTopK.py +1 -4
- edsl/questions/derived/QuestionYesNo.py +2 -8
- edsl/results/DatasetExportMixin.py +1 -5
- edsl/results/Result.py +1 -1
- edsl/results/Results.py +1 -4
- edsl/scenarios/FileStore.py +10 -71
- edsl/scenarios/Scenario.py +21 -86
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +0 -13
- edsl/scenarios/ScenarioListPdfMixin.py +4 -150
- edsl/study/Study.py +0 -32
- edsl/surveys/Rule.py +1 -10
- edsl/surveys/RuleCollection.py +3 -19
- edsl/surveys/Survey.py +0 -7
- edsl/templates/error_reporting/interview_details.html +1 -6
- edsl/utilities/utilities.py +1 -9
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +1 -2
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/RECORD +55 -61
- edsl/inference_services/TogetherAIService.py +0 -170
- edsl/jobs/runners/JobsRunnerStatus.py +0 -331
- edsl/questions/Quick.py +0 -41
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +0 -7
- edsl/questions/templates/budget/question_presentation.jinja +0 -7
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
edsl/scenarios/Scenario.py
CHANGED
@@ -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
|
-
|
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({'
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
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
|
501
|
-
|
502
|
-
|
503
|
-
{
|
504
|
-
|
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', '
|
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({'
|
36
|
+
Scenario({'file_path': '...', 'encoded_image': '...'})
|
37
37
|
"""
|
38
38
|
|
39
39
|
if image_path.startswith("http://") or image_path.startswith("https://"):
|
edsl/scenarios/ScenarioList.py
CHANGED
@@ -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,
|
125
|
-
|
126
|
-
|
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
|
-
|
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(
|
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)
|
edsl/surveys/RuleCollection.py
CHANGED
@@ -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
|
-
#
|
187
|
-
|
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>
|
edsl/utilities/utilities.py
CHANGED
@@ -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
|
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
|
|