edsl 0.1.37.dev6__py3-none-any.whl → 0.1.38__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 (271) hide show
  1. edsl/Base.py +332 -303
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +49 -48
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +867 -855
  7. edsl/agents/AgentList.py +413 -350
  8. edsl/agents/Invigilator.py +233 -222
  9. edsl/agents/InvigilatorBase.py +265 -284
  10. edsl/agents/PromptConstructor.py +354 -353
  11. edsl/agents/__init__.py +3 -3
  12. edsl/agents/descriptors.py +99 -99
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +279 -289
  26. edsl/config.py +157 -149
  27. edsl/conversation/Conversation.py +290 -290
  28. edsl/conversation/car_buying.py +58 -58
  29. edsl/conversation/chips.py +95 -95
  30. edsl/conversation/mug_negotiation.py +81 -81
  31. edsl/conversation/next_speaker_utilities.py +93 -93
  32. edsl/coop/PriceFetcher.py +54 -54
  33. edsl/coop/__init__.py +2 -2
  34. edsl/coop/coop.py +1028 -958
  35. edsl/coop/utils.py +131 -131
  36. edsl/data/Cache.py +555 -527
  37. edsl/data/CacheEntry.py +233 -228
  38. edsl/data/CacheHandler.py +149 -149
  39. edsl/data/RemoteCacheSync.py +78 -97
  40. edsl/data/SQLiteDict.py +292 -292
  41. edsl/data/__init__.py +4 -4
  42. edsl/data/orm.py +10 -10
  43. edsl/data_transfer_models.py +73 -73
  44. edsl/enums.py +175 -173
  45. edsl/exceptions/BaseException.py +21 -21
  46. edsl/exceptions/__init__.py +54 -54
  47. edsl/exceptions/agents.py +42 -38
  48. edsl/exceptions/cache.py +5 -0
  49. edsl/exceptions/configuration.py +16 -16
  50. edsl/exceptions/coop.py +10 -10
  51. edsl/exceptions/data.py +14 -14
  52. edsl/exceptions/general.py +34 -34
  53. edsl/exceptions/jobs.py +33 -33
  54. edsl/exceptions/language_models.py +63 -63
  55. edsl/exceptions/prompts.py +15 -15
  56. edsl/exceptions/questions.py +91 -91
  57. edsl/exceptions/results.py +29 -29
  58. edsl/exceptions/scenarios.py +22 -22
  59. edsl/exceptions/surveys.py +37 -37
  60. edsl/inference_services/AnthropicService.py +87 -87
  61. edsl/inference_services/AwsBedrock.py +120 -120
  62. edsl/inference_services/AzureAI.py +217 -217
  63. edsl/inference_services/DeepInfraService.py +18 -18
  64. edsl/inference_services/GoogleService.py +148 -156
  65. edsl/inference_services/GroqService.py +20 -20
  66. edsl/inference_services/InferenceServiceABC.py +147 -147
  67. edsl/inference_services/InferenceServicesCollection.py +97 -97
  68. edsl/inference_services/MistralAIService.py +123 -123
  69. edsl/inference_services/OllamaService.py +18 -18
  70. edsl/inference_services/OpenAIService.py +224 -224
  71. edsl/inference_services/PerplexityService.py +163 -0
  72. edsl/inference_services/TestService.py +89 -89
  73. edsl/inference_services/TogetherAIService.py +170 -170
  74. edsl/inference_services/models_available_cache.py +118 -118
  75. edsl/inference_services/rate_limits_cache.py +25 -25
  76. edsl/inference_services/registry.py +41 -39
  77. edsl/inference_services/write_available.py +10 -10
  78. edsl/jobs/Answers.py +56 -56
  79. edsl/jobs/Jobs.py +898 -1347
  80. edsl/jobs/JobsChecks.py +147 -0
  81. edsl/jobs/JobsPrompts.py +268 -0
  82. edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
  83. edsl/jobs/__init__.py +1 -1
  84. edsl/jobs/buckets/BucketCollection.py +63 -63
  85. edsl/jobs/buckets/ModelBuckets.py +65 -65
  86. edsl/jobs/buckets/TokenBucket.py +251 -248
  87. edsl/jobs/interviews/Interview.py +661 -661
  88. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  89. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  90. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  91. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  92. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  93. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  94. edsl/jobs/interviews/ReportErrors.py +66 -66
  95. edsl/jobs/interviews/interview_status_enum.py +9 -9
  96. edsl/jobs/runners/JobsRunnerAsyncio.py +466 -338
  97. edsl/jobs/runners/JobsRunnerStatus.py +330 -332
  98. edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
  99. edsl/jobs/tasks/TaskCreators.py +64 -64
  100. edsl/jobs/tasks/TaskHistory.py +450 -442
  101. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  102. edsl/jobs/tasks/task_status_enum.py +163 -163
  103. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  104. edsl/jobs/tokens/TokenUsage.py +34 -34
  105. edsl/language_models/KeyLookup.py +30 -30
  106. edsl/language_models/LanguageModel.py +668 -706
  107. edsl/language_models/ModelList.py +155 -102
  108. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  109. edsl/language_models/__init__.py +3 -3
  110. edsl/language_models/fake_openai_call.py +15 -15
  111. edsl/language_models/fake_openai_service.py +61 -61
  112. edsl/language_models/registry.py +190 -137
  113. edsl/language_models/repair.py +156 -156
  114. edsl/language_models/unused/ReplicateBase.py +83 -83
  115. edsl/language_models/utilities.py +64 -64
  116. edsl/notebooks/Notebook.py +258 -259
  117. edsl/notebooks/__init__.py +1 -1
  118. edsl/prompts/Prompt.py +362 -357
  119. edsl/prompts/__init__.py +2 -2
  120. edsl/questions/AnswerValidatorMixin.py +289 -289
  121. edsl/questions/QuestionBase.py +664 -656
  122. edsl/questions/QuestionBaseGenMixin.py +161 -161
  123. edsl/questions/QuestionBasePromptsMixin.py +217 -234
  124. edsl/questions/QuestionBudget.py +227 -227
  125. edsl/questions/QuestionCheckBox.py +359 -359
  126. edsl/questions/QuestionExtract.py +182 -183
  127. edsl/questions/QuestionFreeText.py +114 -114
  128. edsl/questions/QuestionFunctional.py +166 -159
  129. edsl/questions/QuestionList.py +231 -231
  130. edsl/questions/QuestionMultipleChoice.py +286 -286
  131. edsl/questions/QuestionNumerical.py +153 -153
  132. edsl/questions/QuestionRank.py +324 -324
  133. edsl/questions/Quick.py +41 -41
  134. edsl/questions/RegisterQuestionsMeta.py +71 -71
  135. edsl/questions/ResponseValidatorABC.py +174 -174
  136. edsl/questions/SimpleAskMixin.py +73 -73
  137. edsl/questions/__init__.py +26 -26
  138. edsl/questions/compose_questions.py +98 -98
  139. edsl/questions/decorators.py +21 -21
  140. edsl/questions/derived/QuestionLikertFive.py +76 -76
  141. edsl/questions/derived/QuestionLinearScale.py +87 -87
  142. edsl/questions/derived/QuestionTopK.py +93 -91
  143. edsl/questions/derived/QuestionYesNo.py +82 -82
  144. edsl/questions/descriptors.py +413 -413
  145. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  146. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  147. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  148. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  149. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  150. edsl/questions/prompt_templates/question_list.jinja +17 -17
  151. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  152. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  153. edsl/questions/question_registry.py +177 -147
  154. edsl/questions/settings.py +12 -12
  155. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  156. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  157. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  158. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  159. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  160. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  161. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  162. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  163. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  164. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  165. edsl/questions/templates/list/question_presentation.jinja +5 -5
  166. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  167. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  168. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  169. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  170. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  171. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  172. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  173. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  174. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  176. edsl/results/CSSParameterizer.py +108 -0
  177. edsl/results/Dataset.py +424 -293
  178. edsl/results/DatasetExportMixin.py +731 -717
  179. edsl/results/DatasetTree.py +275 -145
  180. edsl/results/Result.py +465 -450
  181. edsl/results/Results.py +1165 -1071
  182. edsl/results/ResultsDBMixin.py +238 -238
  183. edsl/results/ResultsExportMixin.py +43 -43
  184. edsl/results/ResultsFetchMixin.py +33 -33
  185. edsl/results/ResultsGGMixin.py +121 -121
  186. edsl/results/ResultsToolsMixin.py +98 -98
  187. edsl/results/Selector.py +135 -135
  188. edsl/results/TableDisplay.py +198 -0
  189. edsl/results/__init__.py +2 -2
  190. edsl/results/table_display.css +78 -0
  191. edsl/results/tree_explore.py +115 -115
  192. edsl/scenarios/FileStore.py +632 -458
  193. edsl/scenarios/Scenario.py +601 -546
  194. edsl/scenarios/ScenarioHtmlMixin.py +64 -64
  195. edsl/scenarios/ScenarioJoin.py +127 -0
  196. edsl/scenarios/ScenarioList.py +1287 -1112
  197. edsl/scenarios/ScenarioListExportMixin.py +52 -52
  198. edsl/scenarios/ScenarioListPdfMixin.py +261 -261
  199. edsl/scenarios/__init__.py +4 -4
  200. edsl/shared.py +1 -1
  201. edsl/study/ObjectEntry.py +173 -173
  202. edsl/study/ProofOfWork.py +113 -113
  203. edsl/study/SnapShot.py +80 -80
  204. edsl/study/Study.py +528 -528
  205. edsl/study/__init__.py +4 -4
  206. edsl/surveys/DAG.py +148 -148
  207. edsl/surveys/Memory.py +31 -31
  208. edsl/surveys/MemoryPlan.py +244 -244
  209. edsl/surveys/Rule.py +326 -330
  210. edsl/surveys/RuleCollection.py +387 -387
  211. edsl/surveys/Survey.py +1801 -1795
  212. edsl/surveys/SurveyCSS.py +261 -261
  213. edsl/surveys/SurveyExportMixin.py +259 -259
  214. edsl/surveys/SurveyFlowVisualizationMixin.py +179 -121
  215. edsl/surveys/SurveyQualtricsImport.py +284 -284
  216. edsl/surveys/__init__.py +3 -3
  217. edsl/surveys/base.py +53 -53
  218. edsl/surveys/descriptors.py +56 -56
  219. edsl/surveys/instructions/ChangeInstruction.py +49 -47
  220. edsl/surveys/instructions/Instruction.py +65 -51
  221. edsl/surveys/instructions/InstructionCollection.py +77 -77
  222. edsl/templates/error_reporting/base.html +23 -23
  223. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  224. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  225. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  226. edsl/templates/error_reporting/interview_details.html +115 -115
  227. edsl/templates/error_reporting/interviews.html +19 -10
  228. edsl/templates/error_reporting/overview.html +4 -4
  229. edsl/templates/error_reporting/performance_plot.html +1 -1
  230. edsl/templates/error_reporting/report.css +73 -73
  231. edsl/templates/error_reporting/report.html +117 -117
  232. edsl/templates/error_reporting/report.js +25 -25
  233. edsl/tools/__init__.py +1 -1
  234. edsl/tools/clusters.py +192 -192
  235. edsl/tools/embeddings.py +27 -27
  236. edsl/tools/embeddings_plotting.py +118 -118
  237. edsl/tools/plotting.py +112 -112
  238. edsl/tools/summarize.py +18 -18
  239. edsl/utilities/SystemInfo.py +28 -28
  240. edsl/utilities/__init__.py +22 -22
  241. edsl/utilities/ast_utilities.py +25 -25
  242. edsl/utilities/data/Registry.py +6 -6
  243. edsl/utilities/data/__init__.py +1 -1
  244. edsl/utilities/data/scooter_results.json +1 -1
  245. edsl/utilities/decorators.py +77 -77
  246. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  247. edsl/utilities/interface.py +627 -627
  248. edsl/{conjure → utilities}/naming_utilities.py +263 -263
  249. edsl/utilities/repair_functions.py +28 -28
  250. edsl/utilities/restricted_python.py +70 -70
  251. edsl/utilities/utilities.py +424 -409
  252. {edsl-0.1.37.dev6.dist-info → edsl-0.1.38.dist-info}/LICENSE +21 -21
  253. {edsl-0.1.37.dev6.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
  254. edsl-0.1.38.dist-info/RECORD +277 -0
  255. edsl/conjure/AgentConstructionMixin.py +0 -160
  256. edsl/conjure/Conjure.py +0 -62
  257. edsl/conjure/InputData.py +0 -659
  258. edsl/conjure/InputDataCSV.py +0 -48
  259. edsl/conjure/InputDataMixinQuestionStats.py +0 -182
  260. edsl/conjure/InputDataPyRead.py +0 -91
  261. edsl/conjure/InputDataSPSS.py +0 -8
  262. edsl/conjure/InputDataStata.py +0 -8
  263. edsl/conjure/QuestionOptionMixin.py +0 -76
  264. edsl/conjure/QuestionTypeMixin.py +0 -23
  265. edsl/conjure/RawQuestion.py +0 -65
  266. edsl/conjure/SurveyResponses.py +0 -7
  267. edsl/conjure/__init__.py +0 -9
  268. edsl/conjure/examples/placeholder.txt +0 -0
  269. edsl/conjure/utilities.py +0 -201
  270. edsl-0.1.37.dev6.dist-info/RECORD +0 -283
  271. {edsl-0.1.37.dev6.dist-info → edsl-0.1.38.dist-info}/WHEEL +0 -0
edsl/questions/Quick.py CHANGED
@@ -1,41 +1,41 @@
1
- from edsl import (
2
- QuestionFreeText,
3
- QuestionMultipleChoice,
4
- Survey,
5
- QuestionList,
6
- Question,
7
- )
8
-
9
-
10
- def Quick(question_text):
11
- q_type = QuestionMultipleChoice(
12
- question_text=f"A researcher is asking a language model this: {question_text}. What is the most appropriate type of question to ask?",
13
- question_name="potential_question_type",
14
- question_options=["multiple_choice", "list", "free_text"],
15
- )
16
-
17
- q_name = QuestionFreeText(
18
- question_text=f"A researcher is asking a language model this: {question_text}. What is a good name for this question that's a valid python identifier? Just return the proposed identifer",
19
- question_name="potential_question_name",
20
- )
21
-
22
- q_options = QuestionList(
23
- question_text=f"A research is asking this question: { question_text }. What are the possible options for this question?",
24
- question_name="potential_question_options",
25
- )
26
-
27
- survey = Survey([q_type, q_name, q_options]).add_skip_rule(
28
- q_options, "{{ potential_question_type }} != 'multiple_choice'"
29
- )
30
- return survey
31
- # results = survey.run()
32
- # question_type = results.select("potential_question_type").first()
33
- # question_options = results.select("potential_question_options").first()
34
- # question_name = results.select("potential_question_name").first()
35
- # print("Question Type: ", question_type)
36
- # print("Question Name: ", question_name)
37
- # print("Question Options: ", question_options)
38
- # if question_options == None:
39
- # return Question(question_type, question_name = question_name)
40
- # else:
41
- # return Question(question_type, question_name = question_name, question_options = question_options)
1
+ from edsl import (
2
+ QuestionFreeText,
3
+ QuestionMultipleChoice,
4
+ Survey,
5
+ QuestionList,
6
+ Question,
7
+ )
8
+
9
+
10
+ def Quick(question_text):
11
+ q_type = QuestionMultipleChoice(
12
+ question_text=f"A researcher is asking a language model this: {question_text}. What is the most appropriate type of question to ask?",
13
+ question_name="potential_question_type",
14
+ question_options=["multiple_choice", "list", "free_text"],
15
+ )
16
+
17
+ q_name = QuestionFreeText(
18
+ question_text=f"A researcher is asking a language model this: {question_text}. What is a good name for this question that's a valid python identifier? Just return the proposed identifer",
19
+ question_name="potential_question_name",
20
+ )
21
+
22
+ q_options = QuestionList(
23
+ question_text=f"A research is asking this question: { question_text }. What are the possible options for this question?",
24
+ question_name="potential_question_options",
25
+ )
26
+
27
+ survey = Survey([q_type, q_name, q_options]).add_skip_rule(
28
+ q_options, "{{ potential_question_type }} != 'multiple_choice'"
29
+ )
30
+ return survey
31
+ # results = survey.run()
32
+ # question_type = results.select("potential_question_type").first()
33
+ # question_options = results.select("potential_question_options").first()
34
+ # question_name = results.select("potential_question_name").first()
35
+ # print("Question Type: ", question_type)
36
+ # print("Question Name: ", question_name)
37
+ # print("Question Options: ", question_options)
38
+ # if question_options == None:
39
+ # return Question(question_type, question_name = question_name)
40
+ # else:
41
+ # return Question(question_type, question_name = question_name, question_options = question_options)
@@ -1,71 +1,71 @@
1
- from __future__ import annotations
2
- from abc import ABCMeta
3
-
4
- from edsl.enums import QuestionType
5
- from edsl.exceptions.questions import QuestionMissingTypeError, QuestionBadTypeError
6
-
7
- import inspect
8
-
9
-
10
- class RegisterQuestionsMeta(ABCMeta):
11
- """Metaclass to register output elements in a registry i.e., those that have a parent."""
12
-
13
- _registry = {} # Initialize the registry as a dictionary
14
-
15
- def __init__(cls, name, bases, dct):
16
- """Initialize the class and adds it to the registry if it's not the base class."""
17
- super(RegisterQuestionsMeta, cls).__init__(name, bases, dct)
18
- if (
19
- name != "QuestionBase"
20
- and name != "QuestionFunctional"
21
- and name != "QuestionAddTwoNumbers"
22
- ):
23
- ## Enforce that all questions have a question_type class attribute
24
- ## and it comes from our enum of valid question types.
25
- required_attributes = [
26
- "question_type",
27
- "_response_model",
28
- "response_validator_class",
29
- ]
30
- for attr in required_attributes:
31
- if not hasattr(cls, attr):
32
- raise QuestionMissingTypeError(
33
- f"Question must have a {attr} class attribute"
34
- )
35
-
36
- init_signature = inspect.signature(cls.__init__)
37
- init_params = [p for p in init_signature.parameters if p != "self"]
38
- required_params = [
39
- "question_presentation",
40
- "answering_instructions",
41
- "question_name",
42
- "question_text",
43
- ]
44
- for param in required_params:
45
- if param not in init_params:
46
- raise QuestionBadTypeError(
47
- f"Question type {name} must have a question_presentation parameter in its __init__ method"
48
- )
49
-
50
- if name != "QuestionBase":
51
- RegisterQuestionsMeta._registry[name] = cls
52
-
53
- @classmethod
54
- def get_registered_classes(cls):
55
- """Return the registry of registered classes."""
56
- return cls._registry
57
-
58
- @classmethod
59
- def question_types_to_classes(
60
- cls,
61
- ):
62
- """Return a dictionary of question types to classes."""
63
- d = {}
64
- for classname, cls in cls._registry.items():
65
- if hasattr(cls, "question_type"):
66
- d[cls.question_type] = cls
67
- else:
68
- raise Exception(
69
- f"Class {classname} does not have a question_type class attribute"
70
- )
71
- return d
1
+ from __future__ import annotations
2
+ from abc import ABCMeta
3
+
4
+ from edsl.enums import QuestionType
5
+ from edsl.exceptions.questions import QuestionMissingTypeError, QuestionBadTypeError
6
+
7
+ import inspect
8
+
9
+
10
+ class RegisterQuestionsMeta(ABCMeta):
11
+ """Metaclass to register output elements in a registry i.e., those that have a parent."""
12
+
13
+ _registry = {} # Initialize the registry as a dictionary
14
+
15
+ def __init__(cls, name, bases, dct):
16
+ """Initialize the class and adds it to the registry if it's not the base class."""
17
+ super(RegisterQuestionsMeta, cls).__init__(name, bases, dct)
18
+ if (
19
+ name != "QuestionBase"
20
+ and name != "QuestionFunctional"
21
+ and name != "QuestionAddTwoNumbers"
22
+ ):
23
+ ## Enforce that all questions have a question_type class attribute
24
+ ## and it comes from our enum of valid question types.
25
+ required_attributes = [
26
+ "question_type",
27
+ "_response_model",
28
+ "response_validator_class",
29
+ ]
30
+ for attr in required_attributes:
31
+ if not hasattr(cls, attr):
32
+ raise QuestionMissingTypeError(
33
+ f"Question must have a {attr} class attribute"
34
+ )
35
+
36
+ init_signature = inspect.signature(cls.__init__)
37
+ init_params = [p for p in init_signature.parameters if p != "self"]
38
+ required_params = [
39
+ "question_presentation",
40
+ "answering_instructions",
41
+ "question_name",
42
+ "question_text",
43
+ ]
44
+ for param in required_params:
45
+ if param not in init_params:
46
+ raise QuestionBadTypeError(
47
+ f"Question type {name} must have a question_presentation parameter in its __init__ method"
48
+ )
49
+
50
+ if name != "QuestionBase":
51
+ RegisterQuestionsMeta._registry[name] = cls
52
+
53
+ @classmethod
54
+ def get_registered_classes(cls):
55
+ """Return the registry of registered classes."""
56
+ return cls._registry
57
+
58
+ @classmethod
59
+ def question_types_to_classes(
60
+ cls,
61
+ ):
62
+ """Return a dictionary of question types to classes."""
63
+ d = {}
64
+ for classname, cls in cls._registry.items():
65
+ if hasattr(cls, "question_type"):
66
+ d[cls.question_type] = cls
67
+ else:
68
+ raise Exception(
69
+ f"Class {classname} does not have a question_type class attribute"
70
+ )
71
+ return d
@@ -1,174 +1,174 @@
1
- from abc import ABC, abstractmethod
2
- from pydantic import BaseModel, Field, field_validator
3
-
4
- # from decimal import Decimal
5
- from typing import Optional, Any, List, TypedDict
6
-
7
- from edsl.exceptions import QuestionAnswerValidationError
8
- from pydantic import ValidationError
9
-
10
-
11
- class BaseResponse(BaseModel):
12
- answer: Any
13
- comment: Optional[str] = None
14
- generated_tokens: Optional[str] = None
15
-
16
-
17
- class ResponseValidatorABC(ABC):
18
- required_params: List[str] = []
19
-
20
- def __init_subclass__(cls, **kwargs):
21
- super().__init_subclass__(**kwargs)
22
- required_class_vars = ["required_params", "valid_examples", "invalid_examples"]
23
- for var in required_class_vars:
24
- if not hasattr(cls, var):
25
- raise ValueError(f"Class {cls.__name__} must have a '{var}' attribute.")
26
-
27
- def __init__(
28
- self,
29
- response_model: type[BaseModel],
30
- exception_to_throw: Optional[Exception] = None,
31
- override_answer: Optional[dict] = None,
32
- **kwargs,
33
- ):
34
- self.response_model = response_model
35
- self.exception_to_throw = exception_to_throw # for testing
36
- self.override_answer = override_answer # for testing
37
- self.original_exception = None
38
-
39
- # Validate required parameters
40
- missing_params = [
41
- param for param in self.required_params if param not in kwargs
42
- ]
43
- if missing_params:
44
- raise ValueError(
45
- f"Missing required parameters: {', '.join(missing_params)}"
46
- )
47
-
48
- # Set attributes
49
- for key, value in kwargs.items():
50
- setattr(self, key, value)
51
-
52
- if not hasattr(self, "permissive"):
53
- self.permissive = False
54
-
55
- self.fixes_tried = 0
56
-
57
- class RawEdslAnswerDict(TypedDict):
58
- answer: Any
59
- comment: Optional[str]
60
- generated_tokens: Optional[str]
61
-
62
- def _preprocess(self, data: RawEdslAnswerDict) -> RawEdslAnswerDict:
63
- """This is for testing purposes. A question can be given an exception to throw or an answer to always return.
64
-
65
- >>> rv = ResponseValidatorABC.example()
66
- >>> rv.override_answer = {"answer": 42}
67
- >>> rv.validate({"answer": 23})
68
- {'answer': 42, 'comment': None, 'generated_tokens': None}
69
- """
70
- if self.exception_to_throw:
71
- raise self.exception_to_throw
72
- return self.override_answer if self.override_answer else data
73
-
74
- def _base_validate(self, data: RawEdslAnswerDict) -> BaseModel:
75
- """This is the main validation function. It takes the response_model and checks the data against it, returning the instantiated model.
76
-
77
- >>> rv = ResponseValidatorABC.example("numerical")
78
- >>> rv._base_validate({"answer": 42})
79
- ConstrainedNumericResponse(answer=42, comment=None, generated_tokens=None)
80
- """
81
- try:
82
- return self.response_model(**data)
83
- except ValidationError as e:
84
- raise QuestionAnswerValidationError(e, data=data, model=self.response_model)
85
-
86
- def post_validation_answer_convert(self, data):
87
- return data
88
-
89
- class EdslAnswerDict(TypedDict):
90
- answer: Any
91
- comment: Optional[str]
92
- generated_tokens: Optional[str]
93
-
94
- def validate(
95
- self,
96
- raw_edsl_answer_dict: RawEdslAnswerDict,
97
- fix=False,
98
- verbose=False,
99
- replacement_dict: dict = None,
100
- ) -> EdslAnswerDict:
101
- """This is the main validation function.
102
-
103
- >>> rv = ResponseValidatorABC.example("numerical")
104
- >>> rv.validate({"answer": 42})
105
- {'answer': 42, 'comment': None, 'generated_tokens': None}
106
- >>> rv.max_value
107
- 86.7
108
- >>> rv.validate({"answer": "120"})
109
- Traceback (most recent call last):
110
- ...
111
- edsl.exceptions.questions.QuestionAnswerValidationError:...
112
- >>> from edsl import QuestionNumerical
113
- >>> q = QuestionNumerical.example()
114
- >>> q.permissive = True
115
- >>> rv = q.response_validator
116
- >>> rv.validate({"answer": "120"})
117
- {'answer': 120, 'comment': None, 'generated_tokens': None}
118
- >>> rv.validate({"answer": "poo"})
119
- Traceback (most recent call last):
120
- ...
121
- edsl.exceptions.questions.QuestionAnswerValidationError:...
122
- """
123
- proposed_edsl_answer_dict = self._preprocess(raw_edsl_answer_dict)
124
- try:
125
- pydantic_edsl_answer: BaseModel = self._base_validate(
126
- proposed_edsl_answer_dict
127
- )
128
- edsl_answer_dict = self._extract_answer(pydantic_edsl_answer)
129
- return self._post_process(edsl_answer_dict)
130
- except QuestionAnswerValidationError as e:
131
- if verbose:
132
- print(f"Failed to validate {raw_edsl_answer_dict}; {str(e)}")
133
- return self._handle_exception(e, raw_edsl_answer_dict)
134
-
135
- def _handle_exception(self, e: Exception, raw_edsl_answer_dict) -> EdslAnswerDict:
136
- if self.fixes_tried == 0:
137
- self.original_exception = e
138
-
139
- if self.fixes_tried == 0 and hasattr(self, "fix"):
140
- self.fixes_tried += 1
141
- fixed_data = self.fix(raw_edsl_answer_dict)
142
- try:
143
- return self.validate(fixed_data, fix=True)
144
- except Exception as e:
145
- pass # we don't log failed fixes
146
-
147
- raise QuestionAnswerValidationError(
148
- self.original_exception,
149
- data=raw_edsl_answer_dict,
150
- model=self.response_model,
151
- )
152
-
153
- def _check_constraints(self, pydantic_edsl_answer: BaseModel) -> dict:
154
- pass
155
-
156
- def _extract_answer(self, response: BaseModel) -> EdslAnswerDict:
157
- return response.model_dump()
158
-
159
- def _post_process(self, edsl_answer_dict: EdslAnswerDict) -> EdslAnswerDict:
160
- return edsl_answer_dict
161
-
162
- @classmethod
163
- def example(cls, question_type="numerical"):
164
- from edsl import Question
165
-
166
- q = Question.example(question_type)
167
- return q.response_validator
168
-
169
-
170
- # Example usage
171
- if __name__ == "__main__":
172
- import doctest
173
-
174
- doctest.testmod(optionflags=doctest.ELLIPSIS)
1
+ from abc import ABC, abstractmethod
2
+ from pydantic import BaseModel, Field, field_validator
3
+
4
+ # from decimal import Decimal
5
+ from typing import Optional, Any, List, TypedDict
6
+
7
+ from edsl.exceptions import QuestionAnswerValidationError
8
+ from pydantic import ValidationError
9
+
10
+
11
+ class BaseResponse(BaseModel):
12
+ answer: Any
13
+ comment: Optional[str] = None
14
+ generated_tokens: Optional[str] = None
15
+
16
+
17
+ class ResponseValidatorABC(ABC):
18
+ required_params: List[str] = []
19
+
20
+ def __init_subclass__(cls, **kwargs):
21
+ super().__init_subclass__(**kwargs)
22
+ required_class_vars = ["required_params", "valid_examples", "invalid_examples"]
23
+ for var in required_class_vars:
24
+ if not hasattr(cls, var):
25
+ raise ValueError(f"Class {cls.__name__} must have a '{var}' attribute.")
26
+
27
+ def __init__(
28
+ self,
29
+ response_model: type[BaseModel],
30
+ exception_to_throw: Optional[Exception] = None,
31
+ override_answer: Optional[dict] = None,
32
+ **kwargs,
33
+ ):
34
+ self.response_model = response_model
35
+ self.exception_to_throw = exception_to_throw # for testing
36
+ self.override_answer = override_answer # for testing
37
+ self.original_exception = None
38
+
39
+ # Validate required parameters
40
+ missing_params = [
41
+ param for param in self.required_params if param not in kwargs
42
+ ]
43
+ if missing_params:
44
+ raise ValueError(
45
+ f"Missing required parameters: {', '.join(missing_params)}"
46
+ )
47
+
48
+ # Set attributes
49
+ for key, value in kwargs.items():
50
+ setattr(self, key, value)
51
+
52
+ if not hasattr(self, "permissive"):
53
+ self.permissive = False
54
+
55
+ self.fixes_tried = 0
56
+
57
+ class RawEdslAnswerDict(TypedDict):
58
+ answer: Any
59
+ comment: Optional[str]
60
+ generated_tokens: Optional[str]
61
+
62
+ def _preprocess(self, data: RawEdslAnswerDict) -> RawEdslAnswerDict:
63
+ """This is for testing purposes. A question can be given an exception to throw or an answer to always return.
64
+
65
+ >>> rv = ResponseValidatorABC.example()
66
+ >>> rv.override_answer = {"answer": 42}
67
+ >>> rv.validate({"answer": 23})
68
+ {'answer': 42, 'comment': None, 'generated_tokens': None}
69
+ """
70
+ if self.exception_to_throw:
71
+ raise self.exception_to_throw
72
+ return self.override_answer if self.override_answer else data
73
+
74
+ def _base_validate(self, data: RawEdslAnswerDict) -> BaseModel:
75
+ """This is the main validation function. It takes the response_model and checks the data against it, returning the instantiated model.
76
+
77
+ >>> rv = ResponseValidatorABC.example("numerical")
78
+ >>> rv._base_validate({"answer": 42})
79
+ ConstrainedNumericResponse(answer=42, comment=None, generated_tokens=None)
80
+ """
81
+ try:
82
+ return self.response_model(**data)
83
+ except ValidationError as e:
84
+ raise QuestionAnswerValidationError(e, data=data, model=self.response_model)
85
+
86
+ def post_validation_answer_convert(self, data):
87
+ return data
88
+
89
+ class EdslAnswerDict(TypedDict):
90
+ answer: Any
91
+ comment: Optional[str]
92
+ generated_tokens: Optional[str]
93
+
94
+ def validate(
95
+ self,
96
+ raw_edsl_answer_dict: RawEdslAnswerDict,
97
+ fix=False,
98
+ verbose=False,
99
+ replacement_dict: dict = None,
100
+ ) -> EdslAnswerDict:
101
+ """This is the main validation function.
102
+
103
+ >>> rv = ResponseValidatorABC.example("numerical")
104
+ >>> rv.validate({"answer": 42})
105
+ {'answer': 42, 'comment': None, 'generated_tokens': None}
106
+ >>> rv.max_value
107
+ 86.7
108
+ >>> rv.validate({"answer": "120"})
109
+ Traceback (most recent call last):
110
+ ...
111
+ edsl.exceptions.questions.QuestionAnswerValidationError:...
112
+ >>> from edsl import QuestionNumerical
113
+ >>> q = QuestionNumerical.example()
114
+ >>> q.permissive = True
115
+ >>> rv = q.response_validator
116
+ >>> rv.validate({"answer": "120"})
117
+ {'answer': 120, 'comment': None, 'generated_tokens': None}
118
+ >>> rv.validate({"answer": "poo"})
119
+ Traceback (most recent call last):
120
+ ...
121
+ edsl.exceptions.questions.QuestionAnswerValidationError:...
122
+ """
123
+ proposed_edsl_answer_dict = self._preprocess(raw_edsl_answer_dict)
124
+ try:
125
+ pydantic_edsl_answer: BaseModel = self._base_validate(
126
+ proposed_edsl_answer_dict
127
+ )
128
+ edsl_answer_dict = self._extract_answer(pydantic_edsl_answer)
129
+ return self._post_process(edsl_answer_dict)
130
+ except QuestionAnswerValidationError as e:
131
+ if verbose:
132
+ print(f"Failed to validate {raw_edsl_answer_dict}; {str(e)}")
133
+ return self._handle_exception(e, raw_edsl_answer_dict)
134
+
135
+ def _handle_exception(self, e: Exception, raw_edsl_answer_dict) -> EdslAnswerDict:
136
+ if self.fixes_tried == 0:
137
+ self.original_exception = e
138
+
139
+ if self.fixes_tried == 0 and hasattr(self, "fix"):
140
+ self.fixes_tried += 1
141
+ fixed_data = self.fix(raw_edsl_answer_dict)
142
+ try:
143
+ return self.validate(fixed_data, fix=True)
144
+ except Exception as e:
145
+ pass # we don't log failed fixes
146
+
147
+ raise QuestionAnswerValidationError(
148
+ self.original_exception,
149
+ data=raw_edsl_answer_dict,
150
+ model=self.response_model,
151
+ )
152
+
153
+ def _check_constraints(self, pydantic_edsl_answer: BaseModel) -> dict:
154
+ pass
155
+
156
+ def _extract_answer(self, response: BaseModel) -> EdslAnswerDict:
157
+ return response.model_dump()
158
+
159
+ def _post_process(self, edsl_answer_dict: EdslAnswerDict) -> EdslAnswerDict:
160
+ return edsl_answer_dict
161
+
162
+ @classmethod
163
+ def example(cls, question_type="numerical"):
164
+ from edsl import Question
165
+
166
+ q = Question.example(question_type)
167
+ return q.response_validator
168
+
169
+
170
+ # Example usage
171
+ if __name__ == "__main__":
172
+ import doctest
173
+
174
+ doctest.testmod(optionflags=doctest.ELLIPSIS)