edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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 (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -1,23 +1,21 @@
1
1
  from abc import ABC, abstractmethod
2
2
  import asyncio
3
- from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
3
+ from typing import Coroutine, Dict, Any, Optional
4
4
 
5
+ from edsl.prompts.Prompt import Prompt
5
6
  from edsl.utilities.decorators import jupyter_nb_handler
6
7
  from edsl.data_transfer_models import AgentResponseDict
7
8
 
8
- if TYPE_CHECKING:
9
- from edsl.prompts.Prompt import Prompt
10
- from edsl.data.Cache import Cache
11
- from edsl.questions.QuestionBase import QuestionBase
12
- from edsl.scenarios.Scenario import Scenario
13
- from edsl.surveys.MemoryPlan import MemoryPlan
14
- from edsl.language_models.LanguageModel import LanguageModel
15
- from edsl.surveys.Survey import Survey
16
- from edsl.agents.Agent import Agent
17
- from edsl.language_models.key_management.KeyLookup import KeyLookup
9
+ from edsl.data.Cache import Cache
10
+
11
+ from edsl.questions.QuestionBase import QuestionBase
12
+ from edsl.scenarios.Scenario import Scenario
13
+ from edsl.surveys.MemoryPlan import MemoryPlan
14
+ from edsl.language_models.LanguageModel import LanguageModel
18
15
 
19
16
  from edsl.data_transfer_models import EDSLResultObjectInput
20
17
  from edsl.agents.PromptConstructor import PromptConstructor
18
+
21
19
  from edsl.agents.prompt_helpers import PromptPlan
22
20
 
23
21
 
@@ -31,23 +29,25 @@ class InvigilatorBase(ABC):
31
29
  'Failed to get response'
32
30
 
33
31
  This returns an empty prompt because there is no memory the agent needs to have at q0.
32
+
33
+
34
34
  """
35
35
 
36
36
  def __init__(
37
37
  self,
38
38
  agent: "Agent",
39
- question: "QuestionBase",
40
- scenario: "Scenario",
41
- model: "LanguageModel",
42
- memory_plan: "MemoryPlan",
39
+ question: QuestionBase,
40
+ scenario: Scenario,
41
+ model: LanguageModel,
42
+ memory_plan: MemoryPlan,
43
43
  current_answers: dict,
44
44
  survey: Optional["Survey"],
45
- cache: Optional["Cache"] = None,
45
+ cache: Optional[Cache] = None,
46
46
  iteration: Optional[int] = 1,
47
47
  additional_prompt_data: Optional[dict] = None,
48
+ sidecar_model: Optional[LanguageModel] = None,
48
49
  raise_validation_errors: Optional[bool] = True,
49
50
  prompt_plan: Optional["PromptPlan"] = None,
50
- key_lookup: Optional["KeyLookup"] = None,
51
51
  ):
52
52
  """Initialize a new Invigilator."""
53
53
  self.agent = agent
@@ -59,24 +59,24 @@ class InvigilatorBase(ABC):
59
59
  self.iteration = iteration
60
60
  self.additional_prompt_data = additional_prompt_data
61
61
  self.cache = cache
62
+ self.sidecar_model = sidecar_model
62
63
  self.survey = survey
63
64
  self.raise_validation_errors = raise_validation_errors
64
- self.key_lookup = key_lookup
65
-
66
65
  if prompt_plan is None:
67
66
  self.prompt_plan = PromptPlan()
68
67
  else:
69
68
  self.prompt_plan = prompt_plan
70
69
 
71
- # placeholder to store the raw model response
72
- self.raw_model_response = None
70
+ self.raw_model_response = (
71
+ None # placeholder for the raw response from the model
72
+ )
73
73
 
74
74
  @property
75
75
  def prompt_constructor(self) -> PromptConstructor:
76
76
  """Return the prompt constructor."""
77
77
  return PromptConstructor(self, prompt_plan=self.prompt_plan)
78
78
 
79
- def to_dict(self, include_cache=False) -> Dict[str, Any]:
79
+ def to_dict(self):
80
80
  attributes = [
81
81
  "agent",
82
82
  "question",
@@ -86,10 +86,10 @@ class InvigilatorBase(ABC):
86
86
  "current_answers",
87
87
  "iteration",
88
88
  "additional_prompt_data",
89
+ "cache",
90
+ "sidecar_model",
89
91
  "survey",
90
92
  ]
91
- if include_cache:
92
- attributes.append("cache")
93
93
 
94
94
  def serialize_attribute(attr):
95
95
  value = getattr(self, attr)
@@ -104,37 +104,43 @@ class InvigilatorBase(ABC):
104
104
  return {attr: serialize_attribute(attr) for attr in attributes}
105
105
 
106
106
  @classmethod
107
- def from_dict(cls, data) -> "InvigilatorBase":
107
+ def from_dict(cls, data):
108
108
  from edsl.agents.Agent import Agent
109
109
  from edsl.questions import QuestionBase
110
110
  from edsl.scenarios.Scenario import Scenario
111
111
  from edsl.surveys.MemoryPlan import MemoryPlan
112
112
  from edsl.language_models.LanguageModel import LanguageModel
113
113
  from edsl.surveys.Survey import Survey
114
- from edsl.data.Cache import Cache
115
-
116
- attributes_to_classes = {
117
- "agent": Agent,
118
- "question": QuestionBase,
119
- "scenario": Scenario,
120
- "model": LanguageModel,
121
- "memory_plan": MemoryPlan,
122
- "survey": Survey,
123
- "cache": Cache,
124
- }
125
- d = {}
126
- for attr, cls_ in attributes_to_classes.items():
127
- if attr in data and data[attr] is not None:
128
- if attr not in data:
129
- d[attr] = {}
130
- else:
131
- d[attr] = cls_.from_dict(data[attr])
132
114
 
133
- d["current_answers"] = data["current_answers"]
134
- d["iteration"] = data["iteration"]
135
- d["additional_prompt_data"] = data["additional_prompt_data"]
115
+ agent = Agent.from_dict(data["agent"])
116
+ question = QuestionBase.from_dict(data["question"])
117
+ scenario = Scenario.from_dict(data["scenario"])
118
+ model = LanguageModel.from_dict(data["model"])
119
+ memory_plan = MemoryPlan.from_dict(data["memory_plan"])
120
+ survey = Survey.from_dict(data["survey"])
121
+ current_answers = data["current_answers"]
122
+ iteration = data["iteration"]
123
+ additional_prompt_data = data["additional_prompt_data"]
124
+ cache = Cache.from_dict(data["cache"])
125
+
126
+ if data["sidecar_model"] is None:
127
+ sidecar_model = None
128
+ else:
129
+ sidecar_model = LanguageModel.from_dict(data["sidecar_model"])
136
130
 
137
- d = cls(**d)
131
+ return cls(
132
+ agent=agent,
133
+ question=question,
134
+ scenario=scenario,
135
+ model=model,
136
+ memory_plan=memory_plan,
137
+ current_answers=current_answers,
138
+ survey=survey,
139
+ iteration=iteration,
140
+ additional_prompt_data=additional_prompt_data,
141
+ cache=cache,
142
+ sidecar_model=sidecar_model,
143
+ )
138
144
 
139
145
  def __repr__(self) -> str:
140
146
  """Return a string representation of the Invigilator.
@@ -143,9 +149,9 @@ class InvigilatorBase(ABC):
143
149
  'InvigilatorExample(...)'
144
150
 
145
151
  """
146
- return f"{self.__class__.__name__}(agent={repr(self.agent)}, question={repr(self.question)}, scneario={repr(self.scenario)}, model={repr(self.model)}, memory_plan={repr(self.memory_plan)}, current_answers={repr(self.current_answers)}, iteration{repr(self.iteration)}, additional_prompt_data={repr(self.additional_prompt_data)}, cache={repr(self.cache)})"
152
+ return f"{self.__class__.__name__}(agent={repr(self.agent)}, question={repr(self.question)}, scneario={repr(self.scenario)}, model={repr(self.model)}, memory_plan={repr(self.memory_plan)}, current_answers={repr(self.current_answers)}, iteration{repr(self.iteration)}, additional_prompt_data={repr(self.additional_prompt_data)}, cache={repr(self.cache)}, sidecarmodel={repr(self.sidecar_model)})"
147
153
 
148
- def get_failed_task_result(self, failure_reason: str) -> EDSLResultObjectInput:
154
+ def get_failed_task_result(self, failure_reason) -> EDSLResultObjectInput:
149
155
  """Return an AgentResponseDict used in case the question-asking fails.
150
156
 
151
157
  Possible reasons include:
@@ -166,9 +172,8 @@ class InvigilatorBase(ABC):
166
172
  }
167
173
  return EDSLResultObjectInput(**data)
168
174
 
169
- def get_prompts(self) -> Dict[str, "Prompt"]:
175
+ def get_prompts(self) -> Dict[str, Prompt]:
170
176
  """Return the prompt used."""
171
- from edsl.prompts.Prompt import Prompt
172
177
 
173
178
  return {
174
179
  "user_prompt": Prompt("NA"),
@@ -200,25 +205,24 @@ class InvigilatorBase(ABC):
200
205
  >>> InvigilatorBase.example()
201
206
  InvigilatorExample(...)
202
207
 
203
- >>> InvigilatorBase.example().answer_question()
204
- {'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
205
-
206
- >>> InvigilatorBase.example(throw_an_exception=True).answer_question()
207
- Traceback (most recent call last):
208
- ...
209
- Exception: This is a test error
210
208
  """
211
209
  from edsl.agents.Agent import Agent
210
+ from edsl.questions import QuestionMultipleChoice
212
211
  from edsl.scenarios.Scenario import Scenario
212
+ from edsl.language_models import LanguageModel
213
213
  from edsl.surveys.MemoryPlan import MemoryPlan
214
- from edsl.language_models.model import Model
215
- from edsl.surveys.Survey import Survey
214
+
215
+ from edsl.enums import InferenceServiceType
216
+
217
+ from edsl import Model
216
218
 
217
219
  model = Model("test", canned_response="SPAM!")
218
220
 
219
221
  if throw_an_exception:
220
- model.throw_exception = True
222
+ model.throw_an_exception = True
221
223
  agent = Agent.example()
224
+ # question = QuestionMultipleChoice.example()
225
+ from edsl.surveys import Survey
222
226
 
223
227
  if not survey:
224
228
  survey = Survey.example()
@@ -228,10 +232,14 @@ class InvigilatorBase(ABC):
228
232
 
229
233
  question = question or survey.questions[0]
230
234
  scenario = scenario or Scenario.example()
235
+ # memory_plan = None #memory_plan = MemoryPlan()
236
+ from edsl import Survey
237
+
231
238
  memory_plan = MemoryPlan(survey=survey)
232
239
  current_answers = None
240
+ from edsl.agents.PromptConstructor import PromptConstructor
233
241
 
234
- class InvigilatorExample(cls):
242
+ class InvigilatorExample(InvigilatorBase):
235
243
  """An example invigilator."""
236
244
 
237
245
  async def async_answer_question(self):