edsl 0.1.33.dev1__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 (163) hide show
  1. edsl/TemplateLoader.py +24 -0
  2. edsl/__init__.py +8 -4
  3. edsl/agents/Agent.py +46 -14
  4. edsl/agents/AgentList.py +43 -0
  5. edsl/agents/Invigilator.py +125 -212
  6. edsl/agents/InvigilatorBase.py +140 -32
  7. edsl/agents/PromptConstructionMixin.py +43 -66
  8. edsl/agents/__init__.py +1 -0
  9. edsl/auto/AutoStudy.py +117 -0
  10. edsl/auto/StageBase.py +230 -0
  11. edsl/auto/StageGenerateSurvey.py +178 -0
  12. edsl/auto/StageLabelQuestions.py +125 -0
  13. edsl/auto/StagePersona.py +61 -0
  14. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  15. edsl/auto/StagePersonaDimensionValues.py +74 -0
  16. edsl/auto/StagePersonaDimensions.py +69 -0
  17. edsl/auto/StageQuestions.py +73 -0
  18. edsl/auto/SurveyCreatorPipeline.py +21 -0
  19. edsl/auto/utilities.py +224 -0
  20. edsl/config.py +38 -39
  21. edsl/coop/PriceFetcher.py +58 -0
  22. edsl/coop/coop.py +39 -5
  23. edsl/data/Cache.py +35 -1
  24. edsl/data_transfer_models.py +120 -38
  25. edsl/enums.py +2 -0
  26. edsl/exceptions/language_models.py +25 -1
  27. edsl/exceptions/questions.py +62 -5
  28. edsl/exceptions/results.py +4 -0
  29. edsl/inference_services/AnthropicService.py +13 -11
  30. edsl/inference_services/AwsBedrock.py +19 -17
  31. edsl/inference_services/AzureAI.py +37 -20
  32. edsl/inference_services/GoogleService.py +16 -12
  33. edsl/inference_services/GroqService.py +2 -0
  34. edsl/inference_services/InferenceServiceABC.py +24 -0
  35. edsl/inference_services/MistralAIService.py +120 -0
  36. edsl/inference_services/OpenAIService.py +41 -50
  37. edsl/inference_services/TestService.py +71 -0
  38. edsl/inference_services/models_available_cache.py +0 -6
  39. edsl/inference_services/registry.py +4 -0
  40. edsl/jobs/Answers.py +10 -12
  41. edsl/jobs/FailedQuestion.py +78 -0
  42. edsl/jobs/Jobs.py +18 -13
  43. edsl/jobs/buckets/TokenBucket.py +39 -14
  44. edsl/jobs/interviews/Interview.py +297 -77
  45. edsl/jobs/interviews/InterviewExceptionEntry.py +83 -19
  46. edsl/jobs/interviews/interview_exception_tracking.py +0 -70
  47. edsl/jobs/interviews/retry_management.py +3 -1
  48. edsl/jobs/runners/JobsRunnerAsyncio.py +116 -70
  49. edsl/jobs/runners/JobsRunnerStatusMixin.py +1 -1
  50. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  51. edsl/jobs/tasks/TaskHistory.py +131 -213
  52. edsl/language_models/LanguageModel.py +239 -129
  53. edsl/language_models/ModelList.py +2 -2
  54. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  55. edsl/language_models/fake_openai_call.py +15 -0
  56. edsl/language_models/fake_openai_service.py +61 -0
  57. edsl/language_models/registry.py +15 -2
  58. edsl/language_models/repair.py +0 -19
  59. edsl/language_models/utilities.py +61 -0
  60. edsl/prompts/Prompt.py +52 -2
  61. edsl/questions/AnswerValidatorMixin.py +23 -26
  62. edsl/questions/QuestionBase.py +273 -242
  63. edsl/questions/QuestionBaseGenMixin.py +133 -0
  64. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  65. edsl/questions/QuestionBudget.py +6 -0
  66. edsl/questions/QuestionCheckBox.py +227 -35
  67. edsl/questions/QuestionExtract.py +98 -27
  68. edsl/questions/QuestionFreeText.py +46 -29
  69. edsl/questions/QuestionFunctional.py +7 -0
  70. edsl/questions/QuestionList.py +141 -22
  71. edsl/questions/QuestionMultipleChoice.py +173 -64
  72. edsl/questions/QuestionNumerical.py +87 -46
  73. edsl/questions/QuestionRank.py +182 -24
  74. edsl/questions/RegisterQuestionsMeta.py +31 -12
  75. edsl/questions/ResponseValidatorABC.py +169 -0
  76. edsl/questions/__init__.py +3 -4
  77. edsl/questions/decorators.py +21 -0
  78. edsl/questions/derived/QuestionLikertFive.py +10 -5
  79. edsl/questions/derived/QuestionLinearScale.py +11 -1
  80. edsl/questions/derived/QuestionTopK.py +6 -0
  81. edsl/questions/derived/QuestionYesNo.py +16 -1
  82. edsl/questions/descriptors.py +43 -7
  83. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  84. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  85. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  86. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  87. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  88. edsl/questions/prompt_templates/question_list.jinja +17 -0
  89. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  90. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  91. edsl/questions/question_registry.py +6 -2
  92. edsl/questions/templates/__init__.py +0 -0
  93. edsl/questions/templates/checkbox/__init__.py +0 -0
  94. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  95. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  96. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  97. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  98. edsl/questions/templates/free_text/__init__.py +0 -0
  99. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  100. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  101. edsl/questions/templates/likert_five/__init__.py +0 -0
  102. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  103. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  104. edsl/questions/templates/linear_scale/__init__.py +0 -0
  105. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  106. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  107. edsl/questions/templates/list/__init__.py +0 -0
  108. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  109. edsl/questions/templates/list/question_presentation.jinja +5 -0
  110. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  111. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  112. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  113. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  114. edsl/questions/templates/numerical/__init__.py +0 -0
  115. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  116. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  117. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  118. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  119. edsl/questions/templates/top_k/__init__.py +0 -0
  120. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  121. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  122. edsl/questions/templates/yes_no/__init__.py +0 -0
  123. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  124. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  125. edsl/results/Dataset.py +20 -0
  126. edsl/results/DatasetExportMixin.py +41 -47
  127. edsl/results/DatasetTree.py +145 -0
  128. edsl/results/Result.py +32 -5
  129. edsl/results/Results.py +131 -45
  130. edsl/results/ResultsDBMixin.py +3 -3
  131. edsl/results/Selector.py +118 -0
  132. edsl/results/tree_explore.py +115 -0
  133. edsl/scenarios/Scenario.py +10 -4
  134. edsl/scenarios/ScenarioList.py +348 -39
  135. edsl/scenarios/ScenarioListExportMixin.py +9 -0
  136. edsl/study/SnapShot.py +8 -1
  137. edsl/surveys/RuleCollection.py +2 -2
  138. edsl/surveys/Survey.py +634 -315
  139. edsl/surveys/SurveyExportMixin.py +71 -9
  140. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  141. edsl/surveys/SurveyQualtricsImport.py +75 -4
  142. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  143. edsl/surveys/instructions/Instruction.py +34 -0
  144. edsl/surveys/instructions/InstructionCollection.py +77 -0
  145. edsl/surveys/instructions/__init__.py +0 -0
  146. edsl/templates/error_reporting/base.html +24 -0
  147. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  148. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  149. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  150. edsl/templates/error_reporting/interview_details.html +111 -0
  151. edsl/templates/error_reporting/interviews.html +10 -0
  152. edsl/templates/error_reporting/overview.html +5 -0
  153. edsl/templates/error_reporting/performance_plot.html +2 -0
  154. edsl/templates/error_reporting/report.css +74 -0
  155. edsl/templates/error_reporting/report.html +118 -0
  156. edsl/templates/error_reporting/report.js +25 -0
  157. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +4 -2
  158. edsl-0.1.33.dev2.dist-info/RECORD +289 -0
  159. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
  160. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  161. edsl-0.1.33.dev1.dist-info/RECORD +0 -209
  162. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
  163. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -6,22 +6,21 @@ from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
6
6
  from edsl.questions.QuestionBase import QuestionBase
7
7
 
8
8
  # Core Questions
9
- from edsl.questions.QuestionBudget import QuestionBudget
10
9
  from edsl.questions.QuestionCheckBox import QuestionCheckBox
11
10
  from edsl.questions.QuestionExtract import QuestionExtract
12
11
  from edsl.questions.QuestionFreeText import QuestionFreeText
13
-
14
12
  from edsl.questions.QuestionFunctional import QuestionFunctional
15
13
  from edsl.questions.QuestionList import QuestionList
16
14
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
17
15
  from edsl.questions.QuestionNumerical import QuestionNumerical
16
+ from edsl.questions.QuestionBudget import QuestionBudget
18
17
  from edsl.questions.QuestionRank import QuestionRank
19
18
 
20
- # # Questions derived from core questions
19
+ # # # Questions derived from core questions
21
20
  from edsl.questions.derived.QuestionLikertFive import QuestionLikertFive
22
21
  from edsl.questions.derived.QuestionLinearScale import QuestionLinearScale
23
- from edsl.questions.derived.QuestionTopK import QuestionTopK
24
22
  from edsl.questions.derived.QuestionYesNo import QuestionYesNo
23
+ from edsl.questions.derived.QuestionTopK import QuestionTopK
25
24
 
26
25
  # # Compose Questions
27
26
  # from edsl.questions.compose_questions import compose_questions
@@ -0,0 +1,21 @@
1
+ from typing import Optional, Callable, TypeVar
2
+
3
+ T = TypeVar("T")
4
+
5
+
6
+ def inject_exception(func: Callable[..., T]) -> Callable[..., T]:
7
+ def wrapper(
8
+ cls,
9
+ exception_to_throw: Optional[Exception] = None,
10
+ override_answer: Optional[dict] = None,
11
+ *args,
12
+ **kwargs
13
+ ) -> T:
14
+ base_instance = func(cls, *args, **kwargs)
15
+ if exception_to_throw:
16
+ base_instance.exception_to_throw = exception_to_throw
17
+ if override_answer:
18
+ base_instance.override_answer = override_answer
19
+ return base_instance
20
+
21
+ return wrapper
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
  from typing import Optional
3
3
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
4
4
 
5
+ from edsl.questions.decorators import inject_exception
6
+
5
7
 
6
8
  class QuestionLikertFive(QuestionMultipleChoice):
7
9
  """This question prompts the agent to respond to a statement on a 5-point Likert scale."""
@@ -14,31 +16,34 @@ class QuestionLikertFive(QuestionMultipleChoice):
14
16
  "Agree",
15
17
  "Strongly agree",
16
18
  ]
17
- # default_instructions = QuestionMultipleChoice.default_instructions
18
19
 
19
20
  def __init__(
20
21
  self,
21
22
  question_name: str,
22
23
  question_text: str,
23
24
  question_options: Optional[list[str]] = likert_options,
25
+ answering_instructions: Optional[str] = None,
26
+ question_presentation: Optional[str] = None,
27
+ include_comment: bool = True,
24
28
  ):
25
29
  """Initialize the question.
26
30
 
27
31
  :param question_name: The name of the question.
28
32
  :param question_text: The text of the question.
29
33
  :param question_options: The options the respondent should select from (list of strings). If not provided, the default Likert options are used (['Strongly disagree', 'Disagree', 'Neutral', 'Agree', 'Strongly agree']). To view them, run `QuestionLikertFive.likert_options`.
30
- :param instructions: Instructions for the question. If not provided, the default instructions are used. To view them, run `QuestionLikertFive.default_instructions`.
31
34
  """
32
35
  super().__init__(
33
36
  question_name=question_name,
34
37
  question_text=question_text,
35
38
  question_options=question_options,
39
+ use_code=False,
40
+ include_comment=include_comment,
41
+ answering_instructions=answering_instructions,
42
+ question_presentation=question_presentation,
36
43
  )
37
44
 
38
- ################
39
- # Helpful
40
- ################
41
45
  @classmethod
46
+ @inject_exception
42
47
  def example(cls) -> QuestionLikertFive:
43
48
  """Return an example question."""
44
49
  return cls(
@@ -4,6 +4,8 @@ from typing import Optional
4
4
  from edsl.questions.descriptors import QuestionOptionsDescriptor, OptionLabelDescriptor
5
5
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
6
6
 
7
+ from edsl.questions.decorators import inject_exception
8
+
7
9
 
8
10
  class QuestionLinearScale(QuestionMultipleChoice):
9
11
  """This question prompts the agent to respond to a statement on a linear scale."""
@@ -18,6 +20,8 @@ class QuestionLinearScale(QuestionMultipleChoice):
18
20
  question_text: str,
19
21
  question_options: list[int],
20
22
  option_labels: Optional[dict[int, str]] = None,
23
+ answering_instructions: Optional[str] = None,
24
+ question_presentation: Optional[str] = None,
21
25
  ):
22
26
  """Instantiate a new QuestionLinearScale.
23
27
 
@@ -31,14 +35,20 @@ class QuestionLinearScale(QuestionMultipleChoice):
31
35
  question_name=question_name,
32
36
  question_text=question_text,
33
37
  question_options=question_options,
38
+ use_code=False, # question linear scale will have it's own code
34
39
  )
35
40
  self.question_options = question_options
36
- self.option_labels = option_labels
41
+ self.option_labels = (
42
+ {int(k): v for k, v in option_labels.items()} if option_labels else {}
43
+ )
44
+ self.answering_instructions = answering_instructions
45
+ self.question_presentation = question_presentation
37
46
 
38
47
  ################
39
48
  # Helpful
40
49
  ################
41
50
  @classmethod
51
+ @inject_exception
42
52
  def example(cls) -> QuestionLinearScale:
43
53
  """Return an example of a linear scale question."""
44
54
  return cls(
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  from edsl.exceptions import QuestionCreationValidationError
5
5
  from edsl.questions.QuestionCheckBox import QuestionCheckBox
6
+ from edsl.questions.decorators import inject_exception
6
7
 
7
8
 
8
9
  class QuestionTopK(QuestionCheckBox):
@@ -17,6 +18,8 @@ class QuestionTopK(QuestionCheckBox):
17
18
  question_options: list[str],
18
19
  min_selections: int,
19
20
  max_selections: int,
21
+ question_presentation: Optional[str] = None,
22
+ answering_instructions: Optional[str] = None,
20
23
  ):
21
24
  """Initialize the question.
22
25
 
@@ -32,6 +35,8 @@ class QuestionTopK(QuestionCheckBox):
32
35
  question_options=question_options,
33
36
  min_selections=min_selections,
34
37
  max_selections=max_selections,
38
+ question_presentation=question_presentation,
39
+ answering_instructions=answering_instructions,
35
40
  )
36
41
  if min_selections != max_selections:
37
42
  raise QuestionCreationValidationError(
@@ -46,6 +51,7 @@ class QuestionTopK(QuestionCheckBox):
46
51
  # Helpful
47
52
  ################
48
53
  @classmethod
54
+ @inject_exception
49
55
  def example(cls) -> QuestionTopK:
50
56
  """Return an example question."""
51
57
  return cls(
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
+ from typing import Optional
2
3
  from edsl.questions.descriptors import QuestionOptionsDescriptor
3
4
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
4
5
 
6
+ from edsl.questions.decorators import inject_exception
7
+
5
8
 
6
9
  class QuestionYesNo(QuestionMultipleChoice):
7
10
  """This question prompts the agent to respond with 'Yes' or 'No'."""
@@ -13,7 +16,9 @@ class QuestionYesNo(QuestionMultipleChoice):
13
16
  self,
14
17
  question_name: str,
15
18
  question_text: str,
16
- question_options: list[str] = ["Yes", "No"],
19
+ question_options: list[str] = ["No", "Yes"],
20
+ answering_instructions: Optional[str] = None,
21
+ question_presentation: Optional[str] = None,
17
22
  ):
18
23
  """Instantiate a new QuestionYesNo.
19
24
 
@@ -25,6 +30,9 @@ class QuestionYesNo(QuestionMultipleChoice):
25
30
  question_name=question_name,
26
31
  question_text=question_text,
27
32
  question_options=question_options,
33
+ use_code=False,
34
+ answering_instructions=answering_instructions,
35
+ question_presentation=question_presentation,
28
36
  )
29
37
  self.question_options = question_options
30
38
 
@@ -32,6 +40,7 @@ class QuestionYesNo(QuestionMultipleChoice):
32
40
  # Helpful
33
41
  ################
34
42
  @classmethod
43
+ @inject_exception
35
44
  def example(cls) -> QuestionYesNo:
36
45
  """Return an example of a yes/no question."""
37
46
  return cls(question_name="is_it_equal", question_text="Is 5 + 5 equal to 11?")
@@ -59,3 +68,9 @@ def main():
59
68
  import doctest
60
69
 
61
70
  doctest.testmod(optionflags=doctest.ELLIPSIS)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ import doctest
75
+
76
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -206,12 +206,14 @@ class OptionLabelDescriptor(BaseDescriptor):
206
206
 
207
207
  def validate(self, value, instance):
208
208
  """Validate the value is a string."""
209
- if value is not None:
210
- if min(value.keys()) != min(instance.question_options):
209
+ # key_values = [int(v) for v in value.keys()]
210
+
211
+ if value and (key_values := [float(v) for v in value.keys()]) != []:
212
+ if min(key_values) != min(instance.question_options):
211
213
  raise QuestionCreationValidationError(
212
214
  f"First option needs a label (got {value})"
213
215
  )
214
- if max(value.keys()) != max(instance.question_options):
216
+ if max(key_values) != max(instance.question_options):
215
217
  raise QuestionCreationValidationError(
216
218
  f"Last option needs a label (got {value})"
217
219
  )
@@ -219,12 +221,17 @@ class OptionLabelDescriptor(BaseDescriptor):
219
221
  raise QuestionCreationValidationError(
220
222
  "Option labels must be strings (got {value})."
221
223
  )
222
- for key in value.keys():
224
+ for key in key_values:
223
225
  if key not in instance.question_options:
224
226
  raise QuestionCreationValidationError(
225
227
  f"Option label key ({key}) is not in question options ({instance.question_options})."
226
228
  )
227
229
 
230
+ if len(value.values()) != len(set(value.values())):
231
+ raise QuestionCreationValidationError(
232
+ f"Option labels must be unique (got {value})."
233
+ )
234
+
228
235
 
229
236
  class QuestionNameDescriptor(BaseDescriptor):
230
237
  """Validate that the `question_name` attribute is a valid variable name."""
@@ -233,6 +240,15 @@ class QuestionNameDescriptor(BaseDescriptor):
233
240
  """Validate the value is a valid variable name."""
234
241
  from edsl.utilities.utilities import is_valid_variable_name
235
242
 
243
+ if "{{" in value and "}}" in value:
244
+ # they're trying to use a dynamic question name - let's let this play out
245
+ return None
246
+
247
+ if value.endswith("_comment") or value.endswith("_generated_tokens"):
248
+ raise QuestionCreationValidationError(
249
+ f"`question_name` cannot end with '_comment' or '_generated_tokens - (got {value})."
250
+ )
251
+
236
252
  if not is_valid_variable_name(value):
237
253
  raise QuestionCreationValidationError(
238
254
  f"`question_name` is not a valid variable name (got {value})."
@@ -279,7 +295,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
279
295
  >>> _ = q_class("dynamic_options")
280
296
  Traceback (most recent call last):
281
297
  ...
282
- edsl.exceptions.questions.QuestionCreationValidationError: Dynamic question options must be of the form: '{{ question_options }}'.
298
+ edsl.exceptions.questions.QuestionCreationValidationError: ...
283
299
  """
284
300
  if isinstance(value, str):
285
301
  # Check if the string is a dynamic question option
@@ -287,7 +303,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
287
303
  return None
288
304
  else:
289
305
  raise QuestionCreationValidationError(
290
- "Dynamic question options must be of the form: '{{ question_options }}'."
306
+ f"Dynamic question options must have jina2 braces - instead received: {value}."
291
307
  )
292
308
  if not isinstance(value, list):
293
309
  raise QuestionCreationValidationError(
@@ -356,7 +372,21 @@ class QuestionOptionsDescriptor(BaseDescriptor):
356
372
 
357
373
 
358
374
  class QuestionTextDescriptor(BaseDescriptor):
359
- """Validate that the `question_text` attribute is a string."""
375
+ """Validate that the `question_text` attribute is a string.
376
+
377
+
378
+ >>> class TestQuestion:
379
+ ... question_text = QuestionTextDescriptor()
380
+ ... def __init__(self, question_text: str):
381
+ ... self.question_text = question_text
382
+
383
+ >>> _ = TestQuestion("What is the capital of France?")
384
+ >>> _ = TestQuestion("What is the capital of France? {{variable}}")
385
+ >>> _ = TestQuestion("What is the capital of France? {{variable name}}")
386
+ Traceback (most recent call last):
387
+ ...
388
+ edsl.exceptions.questions.QuestionCreationValidationError: Question text contains an invalid identifier: 'variable name'
389
+ """
360
390
 
361
391
  def validate(self, value, instance):
362
392
  """Validate the value is a string."""
@@ -373,6 +403,12 @@ class QuestionTextDescriptor(BaseDescriptor):
373
403
  f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
374
404
  UserWarning,
375
405
  )
406
+ # iterate through all doubles braces and check if they are valid python identifiers
407
+ for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
408
+ if " " in match.group(1).strip():
409
+ raise QuestionCreationValidationError(
410
+ f"Question text contains an invalid identifier: '{match.group(1)}'"
411
+ )
376
412
 
377
413
 
378
414
  if __name__ == "__main__":
@@ -0,0 +1,13 @@
1
+ You are being asked the following question: {{question_text}}
2
+ The options are:
3
+ {% for option in question_options %}
4
+ {{ loop.index0 }}: {{option}}
5
+ {% endfor %}
6
+ Return a valid JSON formatted as follows, with a dictionary for your "answer"
7
+ where the keys are the option numbers and the values are the amounts you want
8
+ to allocate to the options, and the sum of the values is {{budget_sum}}:
9
+
10
+ {"answer": {<put dict of option numbers and allocation amounts here>}, "comment": "<put explanation here>"}
11
+ Example response for a budget of 100 and 4 options:
12
+ {"answer": {"0": 25, "1": 25, "2": 25, "3": 25}, "comment": "I allocated 25 to each option."}
13
+ There must be an allocation listed for each item (including 0).
@@ -0,0 +1,32 @@
1
+ {# Question Presention #}
2
+ {{question_text}}
3
+ {% if use_code %}
4
+ {% for option in question_options %}
5
+ {{ loop.index0 }}: {{option}}
6
+ {% endfor %}
7
+ {% else %}
8
+ {% for option in question_options %}
9
+ {{ option }}
10
+ {% endfor %}
11
+ {% endif %}
12
+
13
+ {# Restrictions #}
14
+ {% if min_selections != None and max_selections != None and min_selections == max_selections %}
15
+ You must select exactly {{min_selections}} options.
16
+ {% elif min_selections != None and max_selections != None %}
17
+ Minimum number of options that must be selected: {{min_selections}}.
18
+ Maximum number of options that must be selected: {{max_selections}}.
19
+ {% elif min_selections != None %}
20
+ Minimum number of options that must be selected: {{min_selections}}.
21
+ {% elif max_selections != None %}
22
+ Maximum number of options that must be selected: {{max_selections}}.
23
+ {% endif %}
24
+
25
+ {# Answering Instructions #}
26
+ Please respond with valid JSON, formatted like so:
27
+ {% if include_comment %}
28
+ {"answer": [<put comma-separated list here>], "comment": "<put explanation here>"}
29
+ {% else %}
30
+ {"answer": [<put comma-separated list here>]}
31
+ {% endif %}
32
+
@@ -0,0 +1,11 @@
1
+ {{question_text}}
2
+
3
+ Create an ANSWER should be formatted like this:
4
+ {{ answer_template }}
5
+
6
+ It should have the same keys but values extracted from the input.
7
+ If the value of a key is not present in the input, fill with "null".
8
+
9
+ Return a valid JSON formatted like this:
10
+ {"answer": <put your ANSWER here>}
11
+ ONLY RETURN THE JSON, AND NOTHING ELSE.
@@ -0,0 +1,3 @@
1
+ {{question_text}}
2
+ Return a valid JSON formatted like this:
3
+ {"answer": "<put free text answer here>"}
@@ -0,0 +1,11 @@
1
+ {{question_text}}
2
+ {% for option in question_options %}
3
+ {{option}} : {{ option_labels.get(option, "") }}
4
+ {% endfor %}
5
+ Return a valid JSON formatted like this, selecting only the code of the option (codes start at 0):
6
+ {% if include_comment %}
7
+ {"answer": <put answer code here>, "comment": <comment>}
8
+ {% else %}
9
+ {"answer": <put answer here>}
10
+ {% endif %}
11
+ Only 1 option may be selected.
@@ -0,0 +1,17 @@
1
+ {{question_text}}
2
+
3
+ Your response should be only a valid JSON in the following format:
4
+ {% if include_comment %}
5
+ {
6
+ "answer": [<comma-separated list of responsive words or phrases as independent strings>],
7
+ "comment": "<put comment here>"
8
+ }
9
+ {% else %}
10
+ {
11
+ "answer": [<comma-separated list of responsive words or phrases as independent strings>],
12
+ }
13
+ {% endif %}
14
+
15
+ {% if max_list_items is not none %}
16
+ The list must not contain more than {{ max_list_items }} items.
17
+ {% endif %}
@@ -0,0 +1,33 @@
1
+ {# Question Presention #}
2
+ {{question_text}}
3
+
4
+ {% if use_code %}
5
+ {% for option in question_options %}
6
+ {{ loop.index0 }}: {{option}}
7
+ {% endfor %}
8
+ {% else %}
9
+ {% for option in question_options %}
10
+ {{option}}
11
+ {% endfor %}
12
+ {% endif %}
13
+
14
+ Only 1 option may be selected.
15
+
16
+ {# Answering Instructions #}
17
+ Return a valid JSON formatted like this:
18
+
19
+ {% if use_code %}
20
+ {% if include_comment %}
21
+ {"answer": <put answer code here>, "comment": "<put explanation here>"}
22
+ {% else %}
23
+ {"answer": <put answer code here>}
24
+ {% endif %}
25
+ {% else %}
26
+
27
+ {% if include_comment %}
28
+ {"answer": <text of option>, "comment": "<put explanation here>"}
29
+ {% else %}
30
+ {"answer": <put option here>}
31
+ {% endif %}
32
+
33
+ {% endif %}
@@ -0,0 +1,37 @@
1
+ You are being asked a question that requires a numerical response
2
+ in the form of an integer or decimal (e.g., -12, 0, 1, 2, 3.45, ...).
3
+
4
+ Your response must be in the following format:
5
+
6
+ {% if include_comment %}
7
+ {"answer": "<your numerical answer here>", "comment": "<your explanation here>"}
8
+ {% else %}
9
+ {"answer": "<your numerical answer here>"}
10
+ {% endif %}
11
+
12
+ You must only include an integer or decimal in the quoted "answer" part of your response.
13
+
14
+ Here is an example of a valid response:
15
+ {% if include_comment %}
16
+ {"answer": "100", "comment": "This is my explanation..."}
17
+ {% else %}
18
+ {"answer": "100"}
19
+ {% endif %}
20
+
21
+ Here is an example of a response that is invalid because the "answer" includes words:
22
+ {"answer": "I don't know.", ...}
23
+
24
+ If your response is equivalent to zero, your formatted response should look like this:
25
+ {% if include_comment %}
26
+ {"answer": "0", "comment": "This is my explanation..."}
27
+ {% else %}
28
+ {"answer": "0"}
29
+ {% endif %}
30
+
31
+ You are being asked the following question: {{question_text}}
32
+ {% if min_value is not none %}
33
+ Minimum answer value: {{min_value}}
34
+ {% endif %}
35
+ {% if max_value is not none %}
36
+ Maximum answer value: {{max_value}}
37
+ {% endif %}
@@ -100,12 +100,16 @@ class Question(metaclass=Meta):
100
100
 
101
101
  >>> from edsl import Question
102
102
  >>> Question.available()
103
- ['budget', 'checkbox', 'extract', 'free_text', 'functional', 'likert_five', 'linear_scale', 'list', 'multiple_choice', 'numerical', 'rank', 'top_k', 'yes_no']
103
+ ['checkbox', 'extract', 'free_text', 'functional', 'likert_five', 'linear_scale', 'list', 'multiple_choice', 'numerical', 'rank', 'top_k', 'yes_no']
104
104
  """
105
+ exclude = ["budget"]
105
106
  if show_class_names:
106
107
  return RegisterQuestionsMeta.question_types_to_classes()
107
108
  else:
108
- return sorted(set(RegisterQuestionsMeta.question_types_to_classes().keys()))
109
+ question_list = sorted(
110
+ set(RegisterQuestionsMeta.question_types_to_classes().keys())
111
+ )
112
+ return [q for q in question_list if q not in exclude]
109
113
 
110
114
 
111
115
  def get_question_class(question_type):
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ {# Answering Instructions #}
2
+ {% if use_code %}
3
+ Please respond only with a comma-separated list of the code of the options that apply, with square brackets. E.g., [0, 1, 3]
4
+ {% else %}
5
+ Please respond only with a comma-separated list of the options that apply, with square brackets. E.g., ['Good', 'Bad', 'Ugly']
6
+ {% endif %}
7
+ {% if include_comment %}
8
+ After the answer, you can put a comment explaining your choice on the next line.
9
+ {% endif %}
10
+
@@ -0,0 +1,22 @@
1
+ {{question_text}}
2
+ {% if use_code %}
3
+ {% for option in question_options %}
4
+ {{ loop.index0 }}: {{option}}
5
+ {% endfor %}
6
+ {% else %}
7
+ {% for option in question_options %}
8
+ {{ option }}
9
+ {% endfor %}
10
+ {% endif %}
11
+
12
+ {# Restrictions #}
13
+ {% if min_selections != None and max_selections != None and min_selections == max_selections %}
14
+ You must select exactly {{min_selections}} options.
15
+ {% elif min_selections != None and max_selections != None %}
16
+ Minimum number of options that must be selected: {{min_selections}}.
17
+ Maximum number of options that must be selected: {{max_selections}}.
18
+ {% elif min_selections != None %}
19
+ Minimum number of options that must be selected: {{min_selections}}.
20
+ {% elif max_selections != None %}
21
+ Maximum number of options that must be selected: {{max_selections}}.
22
+ {% endif %}
@@ -0,0 +1,7 @@
1
+ An ANSWER should be formatted like this:
2
+
3
+ {{ answer_template }}
4
+
5
+ It should have the same keys but values extracted from the input.
6
+ If the value of a key is not present in the input, fill with "null".
7
+ Put any comments in the next line after the answer.
@@ -0,0 +1 @@
1
+ {{question_text}}
File without changes
@@ -0,0 +1 @@
1
+ {{question_text}}
File without changes
@@ -0,0 +1,10 @@
1
+ {# Answering Instructions #}
2
+ {% if use_code %}
3
+ Respond only with the code corresponding to one of the options.
4
+ {% else %}
5
+ Respond only with a string corresponding to one of the options.
6
+ {% endif %}
7
+ {% if include_comment %}
8
+ After the answer, you can put a comment explaining why you chose that option on the next line.
9
+ {% endif %}
10
+
@@ -0,0 +1,12 @@
1
+ {# Question Presention #}
2
+ {{question_text}}
3
+ {% if use_code %}
4
+ {%- for option in question_options %}
5
+ {{ loop.index0 }}: {{option}}
6
+ {% endfor %}
7
+ {% else %}
8
+ {% for option in question_options %}
9
+ {{option}}
10
+ {% endfor %}
11
+ {% endif %}
12
+ Only 1 option may be selected.
File without changes
@@ -0,0 +1,5 @@
1
+ {# Answering Instructions #}
2
+ Respond only with the code corresponding to one of the options. E.g., "1" or "5" by itself.
3
+ {% if include_comment %}
4
+ After the answer, you can put a comment explaining why you chose that option on the next line.
5
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ {{question_text}}
2
+ {% for option in question_options %}
3
+ {{option}} : {{ option_labels.get(option, "") }}
4
+ {% endfor %}
5
+ Only 1 option may be selected.
File without changes
@@ -0,0 +1,4 @@
1
+ Return your answers on one line, in a comma-separated list of your responses, with square brackets and each answer in quotes E.g., ["A", "B", "C"]
2
+ {% if include_comment %}
3
+ After the answers, you can put a comment explaining your choice on the next line.
4
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ {{question_text}}
2
+
3
+ {% if max_list_items is not none %}
4
+ The list must not contain more than {{ max_list_items }} items.
5
+ {% endif %}
File without changes
@@ -0,0 +1,9 @@
1
+ {# Answering Instructions #}
2
+ {% if use_code %}
3
+ Respond only with the code corresponding to one of the options.
4
+ {% else %}
5
+ Respond only with a string corresponding to one of the options.
6
+ {% endif %}
7
+ {% if include_comment %}
8
+ After the answer, you can put a comment explaining why you chose that option on the next line.
9
+ {% endif %}
File without changes