edsl 0.1.32__py3-none-any.whl → 0.1.33__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 +9 -3
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +8 -3
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +40 -8
- edsl/agents/AgentList.py +43 -0
- edsl/agents/Invigilator.py +135 -219
- edsl/agents/InvigilatorBase.py +148 -59
- edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +138 -89
- 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 +47 -56
- edsl/coop/PriceFetcher.py +58 -0
- edsl/coop/coop.py +50 -7
- edsl/data/Cache.py +35 -1
- edsl/data_transfer_models.py +73 -38
- edsl/enums.py +4 -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 +58 -3
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OpenAIService.py +48 -54
- edsl/inference_services/TestService.py +80 -0
- edsl/inference_services/TogetherAIService.py +170 -0
- edsl/inference_services/models_available_cache.py +0 -6
- edsl/inference_services/registry.py +6 -0
- edsl/jobs/Answers.py +10 -12
- edsl/jobs/FailedQuestion.py +78 -0
- edsl/jobs/Jobs.py +37 -22
- edsl/jobs/buckets/BucketCollection.py +24 -15
- edsl/jobs/buckets/TokenBucket.py +93 -14
- edsl/jobs/interviews/Interview.py +366 -78
- edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +14 -68
- edsl/jobs/interviews/InterviewExceptionEntry.py +85 -19
- edsl/jobs/runners/JobsRunnerAsyncio.py +146 -175
- edsl/jobs/runners/JobsRunnerStatus.py +331 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
- edsl/jobs/tasks/TaskHistory.py +148 -213
- edsl/language_models/LanguageModel.py +261 -156
- 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 +23 -6
- edsl/language_models/repair.py +0 -19
- edsl/language_models/utilities.py +61 -0
- edsl/notebooks/Notebook.py +20 -2
- edsl/prompts/Prompt.py +52 -2
- edsl/questions/AnswerValidatorMixin.py +23 -26
- edsl/questions/QuestionBase.py +330 -249
- edsl/questions/QuestionBaseGenMixin.py +133 -0
- edsl/questions/QuestionBasePromptsMixin.py +266 -0
- edsl/questions/QuestionBudget.py +99 -41
- edsl/questions/QuestionCheckBox.py +227 -35
- edsl/questions/QuestionExtract.py +98 -27
- edsl/questions/QuestionFreeText.py +52 -29
- edsl/questions/QuestionFunctional.py +7 -0
- edsl/questions/QuestionList.py +141 -22
- edsl/questions/QuestionMultipleChoice.py +159 -65
- edsl/questions/QuestionNumerical.py +88 -46
- edsl/questions/QuestionRank.py +182 -24
- edsl/questions/Quick.py +41 -0
- edsl/questions/RegisterQuestionsMeta.py +31 -12
- edsl/questions/ResponseValidatorABC.py +170 -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 +15 -2
- edsl/questions/derived/QuestionTopK.py +10 -1
- edsl/questions/derived/QuestionYesNo.py +24 -3
- 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/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -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/__init__.py +0 -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/__init__.py +0 -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 +46 -48
- edsl/results/DatasetTree.py +145 -0
- edsl/results/Result.py +32 -5
- edsl/results/Results.py +135 -46
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/results/Selector.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/FileStore.py +71 -10
- edsl/scenarios/Scenario.py +96 -25
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +361 -39
- edsl/scenarios/ScenarioListExportMixin.py +9 -0
- edsl/scenarios/ScenarioListPdfMixin.py +150 -4
- edsl/study/SnapShot.py +8 -1
- edsl/study/Study.py +32 -0
- edsl/surveys/Rule.py +10 -1
- edsl/surveys/RuleCollection.py +21 -5
- edsl/surveys/Survey.py +637 -311
- 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 +116 -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/utilities/utilities.py +9 -1
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/METADATA +5 -2
- edsl-0.1.33.dist-info/RECORD +295 -0
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
- edsl/jobs/interviews/retry_management.py +0 -37
- edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
- edsl/utilities/gcp_bucket/simple_example.py +0 -9
- edsl-0.1.32.dist-info/RECORD +0 -209
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/WHEEL +0 -0
@@ -2,6 +2,27 @@
|
|
2
2
|
|
3
3
|
from typing import Union, Optional
|
4
4
|
|
5
|
+
import subprocess
|
6
|
+
import platform
|
7
|
+
import os
|
8
|
+
import tempfile
|
9
|
+
|
10
|
+
|
11
|
+
def open_docx(file_path):
|
12
|
+
"""
|
13
|
+
Open a docx file using the default application in a cross-platform manner.
|
14
|
+
|
15
|
+
:param file_path: str, path to the docx file
|
16
|
+
"""
|
17
|
+
file_path = os.path.abspath(file_path)
|
18
|
+
|
19
|
+
if platform.system() == "Darwin": # macOS
|
20
|
+
subprocess.call(("open", file_path))
|
21
|
+
elif platform.system() == "Windows": # Windows
|
22
|
+
os.startfile(file_path)
|
23
|
+
else: # linux variants
|
24
|
+
subprocess.call(("xdg-open", file_path))
|
25
|
+
|
5
26
|
|
6
27
|
class SurveyExportMixin:
|
7
28
|
"""A mixin class for exporting surveys to different formats."""
|
@@ -25,7 +46,12 @@ class SurveyExportMixin:
|
|
25
46
|
)
|
26
47
|
return q.run().select("description").first()
|
27
48
|
|
28
|
-
def docx(
|
49
|
+
def docx(
|
50
|
+
self,
|
51
|
+
return_document_object: bool = False,
|
52
|
+
filename: Optional[str] = None,
|
53
|
+
open_file: bool = False,
|
54
|
+
) -> Union["Document", None]:
|
29
55
|
"""Generate a docx document for the survey."""
|
30
56
|
from docx import Document
|
31
57
|
|
@@ -45,19 +71,55 @@ class SurveyExportMixin:
|
|
45
71
|
if hasattr(question, "question_options"):
|
46
72
|
for option in getattr(question, "question_options", []):
|
47
73
|
doc.add_paragraph(str(option), style="ListBullet")
|
48
|
-
if filename:
|
49
|
-
doc.save(filename)
|
50
|
-
print("The survey has been saved to", filename)
|
51
|
-
return
|
52
|
-
return doc
|
53
74
|
|
54
|
-
|
75
|
+
if return_document_object and filename is None:
|
76
|
+
return doc
|
77
|
+
|
78
|
+
if filename is None:
|
79
|
+
with tempfile.NamedTemporaryFile(
|
80
|
+
"w", delete=False, suffix=".docx", dir=os.getcwd()
|
81
|
+
) as f:
|
82
|
+
filename = f.name
|
83
|
+
|
84
|
+
doc.save(filename)
|
85
|
+
print("The survey has been saved to", filename)
|
86
|
+
if open_file:
|
87
|
+
open_docx(filename)
|
88
|
+
return
|
89
|
+
|
90
|
+
def show(self):
|
91
|
+
self.to_scenario_list(questions_only=False, rename=True).print(format="rich")
|
92
|
+
|
93
|
+
def to_scenario_list(
|
94
|
+
self, questions_only: bool = True, rename=False
|
95
|
+
) -> "ScenarioList":
|
55
96
|
from edsl import ScenarioList, Scenario
|
56
97
|
|
98
|
+
# from edsl.questions import QuestionBase
|
99
|
+
|
100
|
+
if questions_only:
|
101
|
+
to_iterate_over = self._questions
|
102
|
+
else:
|
103
|
+
to_iterate_over = self.recombined_questions_and_instructions()
|
104
|
+
|
105
|
+
if rename:
|
106
|
+
renaming_dict = {
|
107
|
+
"name": "identifier",
|
108
|
+
"question_name": "identifier",
|
109
|
+
"question_text": "text",
|
110
|
+
}
|
111
|
+
else:
|
112
|
+
renaming_dict = {}
|
113
|
+
|
57
114
|
all_keys = set([])
|
58
115
|
scenarios = ScenarioList()
|
59
|
-
for
|
60
|
-
d =
|
116
|
+
for item in to_iterate_over:
|
117
|
+
d = item.to_dict()
|
118
|
+
if item.__class__.__name__ == "Instruction":
|
119
|
+
d["question_type"] = "NA / instruction"
|
120
|
+
for key in renaming_dict:
|
121
|
+
if key in d:
|
122
|
+
d[renaming_dict[key]] = d.pop(key)
|
61
123
|
all_keys.update(d.keys())
|
62
124
|
scenarios.append(Scenario(d))
|
63
125
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""A mixin for visualizing the flow of a survey."""
|
2
2
|
|
3
|
+
from typing import Optional
|
3
4
|
from edsl.surveys.base import RulePriority, EndOfSurvey
|
4
5
|
import tempfile
|
5
6
|
|
@@ -7,7 +8,7 @@ import tempfile
|
|
7
8
|
class SurveyFlowVisualizationMixin:
|
8
9
|
"""A mixin for visualizing the flow of a survey."""
|
9
10
|
|
10
|
-
def show_flow(self, filename: str = None):
|
11
|
+
def show_flow(self, filename: Optional[str] = None):
|
11
12
|
"""Create an image showing the flow of users through the survey."""
|
12
13
|
# Create a graph object
|
13
14
|
import pydot
|
@@ -9,7 +9,7 @@ qualtrics_codes = {
|
|
9
9
|
"TE": "free_text",
|
10
10
|
"MC": "multiple_choice",
|
11
11
|
"Matrix": "matrix",
|
12
|
-
"DB": "
|
12
|
+
"DB": "instruction", # not quite right, but for now
|
13
13
|
"Timing": "free_text", # not quite right, but for now
|
14
14
|
}
|
15
15
|
# TE (Text Entry): Allows respondents to input a text response.
|
@@ -84,6 +84,11 @@ class QualtricsQuestion:
|
|
84
84
|
return None
|
85
85
|
|
86
86
|
def to_edsl(self):
|
87
|
+
if self.question_type == "instruction":
|
88
|
+
from edsl import Instruction
|
89
|
+
|
90
|
+
return [Instruction(text=self.question_text, name=self.question_name)]
|
91
|
+
|
87
92
|
if self.question_type == "free_text":
|
88
93
|
try:
|
89
94
|
q = Question(
|
@@ -187,11 +192,14 @@ class SurveyQualtricsImport:
|
|
187
192
|
questions.extend(qualtrics_questions.to_edsl())
|
188
193
|
return Survey(questions)
|
189
194
|
|
190
|
-
|
195
|
+
@property
|
196
|
+
def survey_data(self):
|
191
197
|
with open(self.qsf_file_name, "r") as f:
|
192
198
|
survey_data = json.load(f)
|
199
|
+
return survey_data
|
193
200
|
|
194
|
-
|
201
|
+
def extract_questions_from_json(self):
|
202
|
+
questions = self.survey_data["SurveyElements"]
|
195
203
|
|
196
204
|
extracted_questions = []
|
197
205
|
|
@@ -201,11 +209,74 @@ class SurveyQualtricsImport:
|
|
201
209
|
|
202
210
|
return extracted_questions
|
203
211
|
|
212
|
+
def extract_blocks_from_json(self):
|
213
|
+
blocks = []
|
214
|
+
|
215
|
+
for element in self.survey_data["SurveyElements"]:
|
216
|
+
if element["Element"] == "BL":
|
217
|
+
for block_payload in element["Payload"]:
|
218
|
+
block_elements = [
|
219
|
+
BlockElement(be["Type"], be["QuestionID"])
|
220
|
+
for be in block_payload["BlockElements"]
|
221
|
+
]
|
222
|
+
options_data = block_payload.get("Options", {})
|
223
|
+
options = BlockOptions(
|
224
|
+
options_data.get("BlockLocking", "false"),
|
225
|
+
options_data.get("RandomizeQuestions", "false"),
|
226
|
+
options_data.get("BlockVisibility", "Collapsed"),
|
227
|
+
)
|
228
|
+
|
229
|
+
block = SurveyBlock(
|
230
|
+
block_payload["Type"],
|
231
|
+
block_payload["Description"],
|
232
|
+
block_payload["ID"],
|
233
|
+
block_elements,
|
234
|
+
options,
|
235
|
+
)
|
236
|
+
blocks.append(block)
|
237
|
+
|
238
|
+
return blocks
|
239
|
+
|
240
|
+
|
241
|
+
class SurveyBlock:
|
242
|
+
def __init__(self, block_type, description, block_id, block_elements, options):
|
243
|
+
self.block_type = block_type
|
244
|
+
self.description = description
|
245
|
+
self.block_id = block_id
|
246
|
+
self.block_elements = block_elements
|
247
|
+
self.options = options
|
248
|
+
|
249
|
+
def __repr__(self):
|
250
|
+
return f"SurveyBlock(type={self.block_type}, description={self.description}, id={self.block_id})"
|
251
|
+
|
252
|
+
|
253
|
+
class BlockElement:
|
254
|
+
def __init__(self, element_type, question_id):
|
255
|
+
self.element_type = element_type
|
256
|
+
self.question_id = question_id
|
257
|
+
|
258
|
+
def __repr__(self):
|
259
|
+
return f"BlockElement(type={self.element_type}, question_id={self.question_id})"
|
260
|
+
|
261
|
+
|
262
|
+
class BlockOptions:
|
263
|
+
def __init__(self, block_locking, randomize_questions, block_visibility):
|
264
|
+
self.block_locking = block_locking
|
265
|
+
self.randomize_questions = randomize_questions
|
266
|
+
self.block_visibility = block_visibility
|
267
|
+
|
268
|
+
def __repr__(self):
|
269
|
+
return (
|
270
|
+
f"BlockOptions(block_locking={self.block_locking}, "
|
271
|
+
f"randomize_questions={self.randomize_questions}, "
|
272
|
+
f"block_visibility={self.block_visibility})"
|
273
|
+
)
|
274
|
+
|
204
275
|
|
205
276
|
if __name__ == "__main__":
|
206
277
|
survey_creator = SurveyQualtricsImport("example.qsf")
|
207
278
|
# print(survey_creator.question_data)
|
208
|
-
survey = survey_creator.create_survey()
|
279
|
+
# survey = survey_creator.create_survey()
|
209
280
|
# info = survey.push()
|
210
281
|
# print(info)
|
211
282
|
# questions = survey.extract_questions_from_json()
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
4
|
+
|
5
|
+
|
6
|
+
class ChangeInstruction:
|
7
|
+
def __init__(
|
8
|
+
self,
|
9
|
+
keep: Optional[List[str]] = None,
|
10
|
+
drop: Optional[List[str]] = None,
|
11
|
+
):
|
12
|
+
if keep is None and drop is None:
|
13
|
+
raise ValueError("Keep and drop cannot both be None")
|
14
|
+
|
15
|
+
self.keep = keep or []
|
16
|
+
self.drop = drop or []
|
17
|
+
|
18
|
+
def include_instruction(self, instruction_name) -> bool:
|
19
|
+
return (instruction_name in self.keep) or (not instruction_name in self.drop)
|
20
|
+
|
21
|
+
def add_name(self, index) -> None:
|
22
|
+
self.name = "change_instruction_{}".format(index)
|
23
|
+
|
24
|
+
def __str__(self):
|
25
|
+
return self.text
|
26
|
+
|
27
|
+
def _to_dict(self):
|
28
|
+
return {
|
29
|
+
"keep": self.keep,
|
30
|
+
"drop": self.drop,
|
31
|
+
"edsl_class_name": "ChangeInstruction",
|
32
|
+
}
|
33
|
+
|
34
|
+
@add_edsl_version
|
35
|
+
def to_dict(self):
|
36
|
+
return self._to_dict()
|
37
|
+
|
38
|
+
def __hash__(self) -> int:
|
39
|
+
"""Return a hash of the question."""
|
40
|
+
from edsl.utilities.utilities import dict_hash
|
41
|
+
|
42
|
+
return dict_hash(self._to_dict())
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
@remove_edsl_version
|
46
|
+
def from_dict(cls, data):
|
47
|
+
return cls(data["keep"], data["drop"])
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from typing import Union, Optional, List, Generator, Dict
|
2
|
+
from edsl.questions import QuestionBase
|
3
|
+
|
4
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
5
|
+
|
6
|
+
|
7
|
+
class Instruction:
|
8
|
+
def __init__(self, name, text):
|
9
|
+
self.name = name
|
10
|
+
self.text = text
|
11
|
+
|
12
|
+
def __str__(self):
|
13
|
+
return self.text
|
14
|
+
|
15
|
+
def __repr__(self):
|
16
|
+
return """Instruction(name="{}", text="{}")""".format(self.name, self.text)
|
17
|
+
|
18
|
+
def _to_dict(self):
|
19
|
+
return {"name": self.name, "text": self.text, "edsl_class_name": "Instruction"}
|
20
|
+
|
21
|
+
@add_edsl_version
|
22
|
+
def to_dict(self):
|
23
|
+
return self._to_dict()
|
24
|
+
|
25
|
+
def __hash__(self) -> int:
|
26
|
+
"""Return a hash of the question."""
|
27
|
+
from edsl.utilities.utilities import dict_hash
|
28
|
+
|
29
|
+
return dict_hash(self._to_dict())
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
@remove_edsl_version
|
33
|
+
def from_dict(cls, data):
|
34
|
+
return cls(data["name"], data["text"])
|
@@ -0,0 +1,77 @@
|
|
1
|
+
from edsl.surveys.instructions.Instruction import Instruction
|
2
|
+
from edsl.surveys.instructions.ChangeInstruction import ChangeInstruction
|
3
|
+
from edsl.questions import QuestionBase
|
4
|
+
from typing import Union, Optional, List, Generator, Dict
|
5
|
+
|
6
|
+
|
7
|
+
from collections import UserDict
|
8
|
+
|
9
|
+
|
10
|
+
class InstructionCollection(UserDict):
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
instruction_names_to_instruction: Dict[str, Instruction],
|
14
|
+
questions: List[QuestionBase],
|
15
|
+
):
|
16
|
+
self.instruction_names_to_instruction = instruction_names_to_instruction
|
17
|
+
self.questions = questions
|
18
|
+
data = {}
|
19
|
+
for question in self.questions:
|
20
|
+
# just add both the question and the question name
|
21
|
+
data[question.name] = list(self.relevant_instructions(question))
|
22
|
+
# data[question] = list(self.relevant_instructions(question))
|
23
|
+
super().__init__(data)
|
24
|
+
|
25
|
+
def __getitem__(self, key):
|
26
|
+
# in case the person uses question instead of the name
|
27
|
+
if isinstance(key, QuestionBase):
|
28
|
+
key = key.name
|
29
|
+
return self.data[key]
|
30
|
+
|
31
|
+
@property
|
32
|
+
def question_names(self):
|
33
|
+
return [q.name for q in self.questions]
|
34
|
+
|
35
|
+
def question_index(self, question_name):
|
36
|
+
return self.question_names.index(question_name)
|
37
|
+
|
38
|
+
def _entries_before(
|
39
|
+
self, question_name
|
40
|
+
) -> tuple[List[Instruction], List[ChangeInstruction]]:
|
41
|
+
if question_name not in self.question_names:
|
42
|
+
raise ValueError(
|
43
|
+
f"Question name not found in the list of questions: got '{question_name}'; list is {self.question_names}"
|
44
|
+
)
|
45
|
+
instructions, changes = [], []
|
46
|
+
|
47
|
+
index = self.question_index(question_name)
|
48
|
+
for instruction in self.instruction_names_to_instruction.values():
|
49
|
+
if instruction.pseudo_index < index:
|
50
|
+
if isinstance(instruction, Instruction):
|
51
|
+
instructions.append(instruction)
|
52
|
+
elif isinstance(instruction, ChangeInstruction):
|
53
|
+
changes.append(instruction)
|
54
|
+
return instructions, changes
|
55
|
+
|
56
|
+
def relevant_instructions(
|
57
|
+
self, question: Union[str, QuestionBase]
|
58
|
+
) -> Generator[Instruction, None, None]:
|
59
|
+
## Find all the questions that are after a given instruction
|
60
|
+
if isinstance(question, str):
|
61
|
+
question_name = question
|
62
|
+
elif isinstance(question, QuestionBase):
|
63
|
+
question_name = question.name
|
64
|
+
|
65
|
+
instructions_before, changes_before = self._entries_before(question_name)
|
66
|
+
keep_list = []
|
67
|
+
drop_list = []
|
68
|
+
for change in changes_before:
|
69
|
+
keep_list.extend(change.keep)
|
70
|
+
drop_list.extend(change.drop)
|
71
|
+
|
72
|
+
for instruction in instructions_before:
|
73
|
+
if instruction.name in keep_list or instruction.name not in drop_list:
|
74
|
+
yield instruction
|
75
|
+
|
76
|
+
def __len__(self):
|
77
|
+
return len(self.instruction_names_to_instruction)
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Exception Details</title>
|
7
|
+
<style>
|
8
|
+
{{ css }}
|
9
|
+
</style>
|
10
|
+
|
11
|
+
<script>
|
12
|
+
{{ javascript }}
|
13
|
+
</script>
|
14
|
+
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
{% include 'overview.html' %}
|
18
|
+
{% include 'exceptions_by_type.html' %}
|
19
|
+
{% include 'exceptions_by_model.html' %}
|
20
|
+
{% include 'exceptions_by_question_name.html' %}
|
21
|
+
{% include 'interviews.html' %}
|
22
|
+
{% include 'performance_plot.html' %}
|
23
|
+
</body>
|
24
|
+
</html>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<h2>Exceptions by Model</h2>
|
2
|
+
<table border="1">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Model</th>
|
6
|
+
<th>Number</th>
|
7
|
+
</tr>
|
8
|
+
</thead>
|
9
|
+
<tbody>
|
10
|
+
{% for model, exceptions in exceptions_by_model.items() %}
|
11
|
+
<tr>
|
12
|
+
<td>{{ model }}</td>
|
13
|
+
<td>{{ exceptions }}</td>
|
14
|
+
</tr>
|
15
|
+
{% endfor %}
|
16
|
+
</tbody>
|
17
|
+
</table>
|
18
|
+
|
19
|
+
<h2>Exceptions by Service</h2>
|
20
|
+
<table border="1">
|
21
|
+
<thead>
|
22
|
+
<tr>
|
23
|
+
<th>Service</th>
|
24
|
+
<th>Number</th>
|
25
|
+
</tr>
|
26
|
+
</thead>
|
27
|
+
<tbody>
|
28
|
+
{% for service, exceptions in exceptions_by_service.items() %}
|
29
|
+
<tr>
|
30
|
+
<td>{{ service }}</td>
|
31
|
+
<td>{{ exceptions }}</td>
|
32
|
+
</tr>
|
33
|
+
{% endfor %}
|
34
|
+
</tbody>
|
35
|
+
</table>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h2>Exceptions by Question Name</h2>
|
2
|
+
<table border="1">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Question Name</th>
|
6
|
+
<th>Number of Exceptions</th>
|
7
|
+
</tr>
|
8
|
+
</thead>
|
9
|
+
<tbody>
|
10
|
+
{% for question_name, exception_count in exceptions_by_question_name.items() %}
|
11
|
+
<tr>
|
12
|
+
<td>{{ question_name }}</td>
|
13
|
+
<td>{{ exception_count }}</td>
|
14
|
+
</tr>
|
15
|
+
{% endfor %}
|
16
|
+
</tbody>
|
17
|
+
</table>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h2>Exceptions by Type</h2>
|
2
|
+
<table border="1">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Exception Type</th>
|
6
|
+
<th>Number</th>
|
7
|
+
</tr>
|
8
|
+
</thead>
|
9
|
+
<tbody>
|
10
|
+
{% for exception_type, exceptions in exceptions_by_type.items() %}
|
11
|
+
<tr>
|
12
|
+
<td>{{ exception_type }}</td>
|
13
|
+
<td>{{ exceptions }}</td>
|
14
|
+
</tr>
|
15
|
+
{% endfor %}
|
16
|
+
</tbody>
|
17
|
+
</table>
|
@@ -0,0 +1,116 @@
|
|
1
|
+
<div class="question">question_name: {{ question }}</div>
|
2
|
+
|
3
|
+
|
4
|
+
<h2>Exception details</h2>
|
5
|
+
|
6
|
+
{% for exception_message in exceptions %}
|
7
|
+
<div class="exception-detail">
|
8
|
+
<div class="exception-header">
|
9
|
+
<span class="exception-exception">Exception: {{ exception_message.name }}</span>
|
10
|
+
<button class="toggle-btn">▼</button>
|
11
|
+
</div>
|
12
|
+
<div class="exception-content">
|
13
|
+
<table border="1">
|
14
|
+
<tr>
|
15
|
+
<th>Key</th>
|
16
|
+
<th>Value</th>
|
17
|
+
</tr>
|
18
|
+
<tr>
|
19
|
+
<td>Interview ID (index in results)</td>
|
20
|
+
<td>{{ index }}</td>
|
21
|
+
</tr>
|
22
|
+
<tr>
|
23
|
+
<td>Question name (question_name)</td>
|
24
|
+
<td>{{ question }}</td>
|
25
|
+
</tr>
|
26
|
+
|
27
|
+
<tr>
|
28
|
+
<td>Question type (question_type)</td>
|
29
|
+
<td>{{ exception_message.question_type }}</td>
|
30
|
+
</tr>
|
31
|
+
|
32
|
+
<tr>
|
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>
|
40
|
+
</tr>
|
41
|
+
<tr>
|
42
|
+
<td>Scenario</td>
|
43
|
+
<td>{{ interview.scenario._repr_html_() }}</td>
|
44
|
+
</tr>
|
45
|
+
<tr>
|
46
|
+
<td>Agent</td>
|
47
|
+
<td>{{ interview.agent._repr_html_() }}</td>
|
48
|
+
</tr>
|
49
|
+
<tr>
|
50
|
+
<td>Model name</td>
|
51
|
+
<td>{{ interview.model.model }}</td>
|
52
|
+
</tr>
|
53
|
+
<tr>
|
54
|
+
<td>Inference service</td>
|
55
|
+
<td>{{ interview.model._inference_service_ }}</td>
|
56
|
+
</tr>
|
57
|
+
<tr>
|
58
|
+
<td>Model parameters</td>
|
59
|
+
<td>{{ interview.model._repr_html_() }}</td>
|
60
|
+
</tr>
|
61
|
+
<tr>
|
62
|
+
<td>User Prompt</td>
|
63
|
+
<td><pre>{{ exception_message.rendered_prompts['user_prompt'] }}</pre></td>
|
64
|
+
</tr>
|
65
|
+
<tr>
|
66
|
+
<td>System Prompt</td>
|
67
|
+
<td><pre>{{ exception_message.rendered_prompts['system_prompt'] }}</pre></td>
|
68
|
+
</tr>
|
69
|
+
<tr>
|
70
|
+
<td>Raw model response</td>
|
71
|
+
<td><pre>{{ exception_message.raw_model_response }}</pre>
|
72
|
+
</td>
|
73
|
+
</tr>
|
74
|
+
<tr>
|
75
|
+
<td>Generated token string (at {{ exception_message.key_sequence }}) in raw response</td>
|
76
|
+
<td><pre>{{ exception_message.generated_token_string }}</pre>
|
77
|
+
</td>
|
78
|
+
</tr>
|
79
|
+
<tr>
|
80
|
+
<td>Code to (likely) reproduce the error</td>
|
81
|
+
<td>
|
82
|
+
<textarea id="codeToCopy" rows="10" cols="90">{{ exception_message.code_to_reproduce }}</textarea>
|
83
|
+
<button onclick="copyCode()">Copy</button>
|
84
|
+
</td>
|
85
|
+
</tr>
|
86
|
+
|
87
|
+
</table>
|
88
|
+
|
89
|
+
|
90
|
+
{% if exception_message.exception.__class__.__name__ == 'QuestionAnswerValidationError' %}
|
91
|
+
<h3>Answer validation details</h3>
|
92
|
+
<table border="1">
|
93
|
+
<tr>
|
94
|
+
<th>Field</th>
|
95
|
+
<th>Value</th>
|
96
|
+
</tr>
|
97
|
+
{% for field, (explanation, open_tag, close_tag, value) in exception_message.exception.to_html_dict().items() %}
|
98
|
+
|
99
|
+
<tr>
|
100
|
+
<td>{{ field }}: ({{ explanation }})</td>
|
101
|
+
<td><{{open_tag}}> {{ value | escape }} <{{close_tag}}></td>
|
102
|
+
</tr>
|
103
|
+
{% endfor %}
|
104
|
+
</table>
|
105
|
+
{% endif %}
|
106
|
+
|
107
|
+
<div class="exception-time">Time: {{ exception_message.time }}</div>
|
108
|
+
<div class="exception-traceback">Traceback:
|
109
|
+
<text>
|
110
|
+
<pre>{{ exception_message.traceback }}</pre>
|
111
|
+
</text>
|
112
|
+
</div>
|
113
|
+
</div>
|
114
|
+
</div>
|
115
|
+
|
116
|
+
{% endfor %}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{% for index, interview in interviews.items() %}
|
2
|
+
{% if interview.exceptions != {} %}
|
3
|
+
<div class="interview">Interview: {{ index }} </div>
|
4
|
+
Model: {{ interview.model.model }}
|
5
|
+
<h1>Failing questions</h1>
|
6
|
+
{% endif %}
|
7
|
+
{% for question, exceptions in interview.exceptions.items() %}
|
8
|
+
{% include 'interview_details.html' %}
|
9
|
+
{% endfor %}
|
10
|
+
{% endfor %}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<h1>Overview</h1>
|
2
|
+
<p>There were {{ interviews|length }} total interview(s). An 'interview' is the result of one survey, taken by one agent, with one model, with one scenario.</p>
|
3
|
+
The number of interviews with any exceptions was {{ num_exceptions }}.</p>
|
4
|
+
<p>For advice on dealing with exceptions on Expected Parrot,
|
5
|
+
see <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">here</a>.</p>
|