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.
Files changed (181) hide show
  1. edsl/Base.py +9 -3
  2. edsl/TemplateLoader.py +24 -0
  3. edsl/__init__.py +8 -3
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +40 -8
  6. edsl/agents/AgentList.py +43 -0
  7. edsl/agents/Invigilator.py +135 -219
  8. edsl/agents/InvigilatorBase.py +148 -59
  9. edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +138 -89
  10. edsl/agents/__init__.py +1 -0
  11. edsl/auto/AutoStudy.py +117 -0
  12. edsl/auto/StageBase.py +230 -0
  13. edsl/auto/StageGenerateSurvey.py +178 -0
  14. edsl/auto/StageLabelQuestions.py +125 -0
  15. edsl/auto/StagePersona.py +61 -0
  16. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  17. edsl/auto/StagePersonaDimensionValues.py +74 -0
  18. edsl/auto/StagePersonaDimensions.py +69 -0
  19. edsl/auto/StageQuestions.py +73 -0
  20. edsl/auto/SurveyCreatorPipeline.py +21 -0
  21. edsl/auto/utilities.py +224 -0
  22. edsl/config.py +47 -56
  23. edsl/coop/PriceFetcher.py +58 -0
  24. edsl/coop/coop.py +50 -7
  25. edsl/data/Cache.py +35 -1
  26. edsl/data_transfer_models.py +73 -38
  27. edsl/enums.py +4 -0
  28. edsl/exceptions/language_models.py +25 -1
  29. edsl/exceptions/questions.py +62 -5
  30. edsl/exceptions/results.py +4 -0
  31. edsl/inference_services/AnthropicService.py +13 -11
  32. edsl/inference_services/AwsBedrock.py +19 -17
  33. edsl/inference_services/AzureAI.py +37 -20
  34. edsl/inference_services/GoogleService.py +16 -12
  35. edsl/inference_services/GroqService.py +2 -0
  36. edsl/inference_services/InferenceServiceABC.py +58 -3
  37. edsl/inference_services/MistralAIService.py +120 -0
  38. edsl/inference_services/OpenAIService.py +48 -54
  39. edsl/inference_services/TestService.py +80 -0
  40. edsl/inference_services/TogetherAIService.py +170 -0
  41. edsl/inference_services/models_available_cache.py +0 -6
  42. edsl/inference_services/registry.py +6 -0
  43. edsl/jobs/Answers.py +10 -12
  44. edsl/jobs/FailedQuestion.py +78 -0
  45. edsl/jobs/Jobs.py +37 -22
  46. edsl/jobs/buckets/BucketCollection.py +24 -15
  47. edsl/jobs/buckets/TokenBucket.py +93 -14
  48. edsl/jobs/interviews/Interview.py +366 -78
  49. edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +14 -68
  50. edsl/jobs/interviews/InterviewExceptionEntry.py +85 -19
  51. edsl/jobs/runners/JobsRunnerAsyncio.py +146 -175
  52. edsl/jobs/runners/JobsRunnerStatus.py +331 -0
  53. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  54. edsl/jobs/tasks/TaskHistory.py +148 -213
  55. edsl/language_models/LanguageModel.py +261 -156
  56. edsl/language_models/ModelList.py +2 -2
  57. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  58. edsl/language_models/fake_openai_call.py +15 -0
  59. edsl/language_models/fake_openai_service.py +61 -0
  60. edsl/language_models/registry.py +23 -6
  61. edsl/language_models/repair.py +0 -19
  62. edsl/language_models/utilities.py +61 -0
  63. edsl/notebooks/Notebook.py +20 -2
  64. edsl/prompts/Prompt.py +52 -2
  65. edsl/questions/AnswerValidatorMixin.py +23 -26
  66. edsl/questions/QuestionBase.py +330 -249
  67. edsl/questions/QuestionBaseGenMixin.py +133 -0
  68. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  69. edsl/questions/QuestionBudget.py +99 -41
  70. edsl/questions/QuestionCheckBox.py +227 -35
  71. edsl/questions/QuestionExtract.py +98 -27
  72. edsl/questions/QuestionFreeText.py +52 -29
  73. edsl/questions/QuestionFunctional.py +7 -0
  74. edsl/questions/QuestionList.py +141 -22
  75. edsl/questions/QuestionMultipleChoice.py +159 -65
  76. edsl/questions/QuestionNumerical.py +88 -46
  77. edsl/questions/QuestionRank.py +182 -24
  78. edsl/questions/Quick.py +41 -0
  79. edsl/questions/RegisterQuestionsMeta.py +31 -12
  80. edsl/questions/ResponseValidatorABC.py +170 -0
  81. edsl/questions/__init__.py +3 -4
  82. edsl/questions/decorators.py +21 -0
  83. edsl/questions/derived/QuestionLikertFive.py +10 -5
  84. edsl/questions/derived/QuestionLinearScale.py +15 -2
  85. edsl/questions/derived/QuestionTopK.py +10 -1
  86. edsl/questions/derived/QuestionYesNo.py +24 -3
  87. edsl/questions/descriptors.py +43 -7
  88. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  89. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  90. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  91. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  92. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  93. edsl/questions/prompt_templates/question_list.jinja +17 -0
  94. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  95. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  96. edsl/questions/question_registry.py +6 -2
  97. edsl/questions/templates/__init__.py +0 -0
  98. edsl/questions/templates/budget/__init__.py +0 -0
  99. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  100. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  101. edsl/questions/templates/checkbox/__init__.py +0 -0
  102. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  103. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  104. edsl/questions/templates/extract/__init__.py +0 -0
  105. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  106. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  107. edsl/questions/templates/free_text/__init__.py +0 -0
  108. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  109. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  110. edsl/questions/templates/likert_five/__init__.py +0 -0
  111. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  112. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  113. edsl/questions/templates/linear_scale/__init__.py +0 -0
  114. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  115. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  116. edsl/questions/templates/list/__init__.py +0 -0
  117. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  118. edsl/questions/templates/list/question_presentation.jinja +5 -0
  119. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  120. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  121. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  122. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  123. edsl/questions/templates/numerical/__init__.py +0 -0
  124. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  125. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  126. edsl/questions/templates/rank/__init__.py +0 -0
  127. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  128. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  129. edsl/questions/templates/top_k/__init__.py +0 -0
  130. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  131. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  132. edsl/questions/templates/yes_no/__init__.py +0 -0
  133. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  134. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  135. edsl/results/Dataset.py +20 -0
  136. edsl/results/DatasetExportMixin.py +46 -48
  137. edsl/results/DatasetTree.py +145 -0
  138. edsl/results/Result.py +32 -5
  139. edsl/results/Results.py +135 -46
  140. edsl/results/ResultsDBMixin.py +3 -3
  141. edsl/results/Selector.py +118 -0
  142. edsl/results/tree_explore.py +115 -0
  143. edsl/scenarios/FileStore.py +71 -10
  144. edsl/scenarios/Scenario.py +96 -25
  145. edsl/scenarios/ScenarioImageMixin.py +2 -2
  146. edsl/scenarios/ScenarioList.py +361 -39
  147. edsl/scenarios/ScenarioListExportMixin.py +9 -0
  148. edsl/scenarios/ScenarioListPdfMixin.py +150 -4
  149. edsl/study/SnapShot.py +8 -1
  150. edsl/study/Study.py +32 -0
  151. edsl/surveys/Rule.py +10 -1
  152. edsl/surveys/RuleCollection.py +21 -5
  153. edsl/surveys/Survey.py +637 -311
  154. edsl/surveys/SurveyExportMixin.py +71 -9
  155. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  156. edsl/surveys/SurveyQualtricsImport.py +75 -4
  157. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  158. edsl/surveys/instructions/Instruction.py +34 -0
  159. edsl/surveys/instructions/InstructionCollection.py +77 -0
  160. edsl/surveys/instructions/__init__.py +0 -0
  161. edsl/templates/error_reporting/base.html +24 -0
  162. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  163. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  164. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  165. edsl/templates/error_reporting/interview_details.html +116 -0
  166. edsl/templates/error_reporting/interviews.html +10 -0
  167. edsl/templates/error_reporting/overview.html +5 -0
  168. edsl/templates/error_reporting/performance_plot.html +2 -0
  169. edsl/templates/error_reporting/report.css +74 -0
  170. edsl/templates/error_reporting/report.html +118 -0
  171. edsl/templates/error_reporting/report.js +25 -0
  172. edsl/utilities/utilities.py +9 -1
  173. {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/METADATA +5 -2
  174. edsl-0.1.33.dist-info/RECORD +295 -0
  175. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
  176. edsl/jobs/interviews/retry_management.py +0 -37
  177. edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
  178. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  179. edsl-0.1.32.dist-info/RECORD +0 -209
  180. {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
  181. {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(self, filename=None) -> Union["Document", None]:
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
- def to_scenario_list(self) -> "ScenarioList":
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 q in self._questions:
60
- d = q.to_dict()
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": "free_text", # not quite right, but for now
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
- def extract_questions_from_json(self):
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
- questions = survey_data["SurveyElements"]
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>
@@ -0,0 +1,2 @@
1
+ <h1>Performance Plot</h1>
2
+ {{ performance_plot_html }}