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.
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +8 -4
- edsl/agents/Agent.py +46 -14
- edsl/agents/AgentList.py +43 -0
- edsl/agents/Invigilator.py +125 -212
- edsl/agents/InvigilatorBase.py +140 -32
- edsl/agents/PromptConstructionMixin.py +43 -66
- edsl/agents/__init__.py +1 -0
- edsl/auto/AutoStudy.py +117 -0
- edsl/auto/StageBase.py +230 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +73 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +224 -0
- edsl/config.py +38 -39
- edsl/coop/PriceFetcher.py +58 -0
- edsl/coop/coop.py +39 -5
- edsl/data/Cache.py +35 -1
- edsl/data_transfer_models.py +120 -38
- edsl/enums.py +2 -0
- edsl/exceptions/language_models.py +25 -1
- edsl/exceptions/questions.py +62 -5
- edsl/exceptions/results.py +4 -0
- edsl/inference_services/AnthropicService.py +13 -11
- edsl/inference_services/AwsBedrock.py +19 -17
- edsl/inference_services/AzureAI.py +37 -20
- edsl/inference_services/GoogleService.py +16 -12
- edsl/inference_services/GroqService.py +2 -0
- edsl/inference_services/InferenceServiceABC.py +24 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OpenAIService.py +41 -50
- edsl/inference_services/TestService.py +71 -0
- edsl/inference_services/models_available_cache.py +0 -6
- edsl/inference_services/registry.py +4 -0
- edsl/jobs/Answers.py +10 -12
- edsl/jobs/FailedQuestion.py +78 -0
- edsl/jobs/Jobs.py +18 -13
- edsl/jobs/buckets/TokenBucket.py +39 -14
- edsl/jobs/interviews/Interview.py +297 -77
- edsl/jobs/interviews/InterviewExceptionEntry.py +83 -19
- edsl/jobs/interviews/interview_exception_tracking.py +0 -70
- edsl/jobs/interviews/retry_management.py +3 -1
- edsl/jobs/runners/JobsRunnerAsyncio.py +116 -70
- edsl/jobs/runners/JobsRunnerStatusMixin.py +1 -1
- edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
- edsl/jobs/tasks/TaskHistory.py +131 -213
- edsl/language_models/LanguageModel.py +239 -129
- edsl/language_models/ModelList.py +2 -2
- edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/registry.py +15 -2
- edsl/language_models/repair.py +0 -19
- edsl/language_models/utilities.py +61 -0
- edsl/prompts/Prompt.py +52 -2
- edsl/questions/AnswerValidatorMixin.py +23 -26
- edsl/questions/QuestionBase.py +273 -242
- edsl/questions/QuestionBaseGenMixin.py +133 -0
- edsl/questions/QuestionBasePromptsMixin.py +266 -0
- edsl/questions/QuestionBudget.py +6 -0
- edsl/questions/QuestionCheckBox.py +227 -35
- edsl/questions/QuestionExtract.py +98 -27
- edsl/questions/QuestionFreeText.py +46 -29
- edsl/questions/QuestionFunctional.py +7 -0
- edsl/questions/QuestionList.py +141 -22
- edsl/questions/QuestionMultipleChoice.py +173 -64
- edsl/questions/QuestionNumerical.py +87 -46
- edsl/questions/QuestionRank.py +182 -24
- edsl/questions/RegisterQuestionsMeta.py +31 -12
- edsl/questions/ResponseValidatorABC.py +169 -0
- edsl/questions/__init__.py +3 -4
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +10 -5
- edsl/questions/derived/QuestionLinearScale.py +11 -1
- edsl/questions/derived/QuestionTopK.py +6 -0
- edsl/questions/derived/QuestionYesNo.py +16 -1
- edsl/questions/descriptors.py +43 -7
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_registry.py +6 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/Dataset.py +20 -0
- edsl/results/DatasetExportMixin.py +41 -47
- edsl/results/DatasetTree.py +145 -0
- edsl/results/Result.py +32 -5
- edsl/results/Results.py +131 -45
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/results/Selector.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/Scenario.py +10 -4
- edsl/scenarios/ScenarioList.py +348 -39
- edsl/scenarios/ScenarioListExportMixin.py +9 -0
- edsl/study/SnapShot.py +8 -1
- edsl/surveys/RuleCollection.py +2 -2
- edsl/surveys/Survey.py +634 -315
- edsl/surveys/SurveyExportMixin.py +71 -9
- edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
- edsl/surveys/SurveyQualtricsImport.py +75 -4
- edsl/surveys/instructions/ChangeInstruction.py +47 -0
- edsl/surveys/instructions/Instruction.py +34 -0
- edsl/surveys/instructions/InstructionCollection.py +77 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +111 -0
- edsl/templates/error_reporting/interviews.html +10 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +4 -2
- edsl-0.1.33.dev2.dist-info/RECORD +289 -0
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
- edsl/utilities/gcp_bucket/simple_example.py +0 -9
- edsl-0.1.33.dev1.dist-info/RECORD +0 -209
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
edsl/questions/__init__.py
CHANGED
@@ -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 =
|
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] = ["
|
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)
|
edsl/questions/descriptors.py
CHANGED
@@ -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
|
-
|
210
|
-
|
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(
|
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
|
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:
|
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
|
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,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
|
-
['
|
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
|
-
|
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 @@
|
|
1
|
+
{{question_text}}
|
File without changes
|
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
|
File without changes
|
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
|