edsl 0.1.37.dev3__py3-none-any.whl → 0.1.37.dev5__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 (261) hide show
  1. edsl/Base.py +303 -303
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +48 -48
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +855 -804
  7. edsl/agents/AgentList.py +350 -345
  8. edsl/agents/Invigilator.py +222 -222
  9. edsl/agents/InvigilatorBase.py +284 -305
  10. edsl/agents/PromptConstructor.py +353 -312
  11. edsl/agents/__init__.py +3 -3
  12. edsl/agents/descriptors.py +99 -86
  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 +289 -289
  26. edsl/config.py +149 -149
  27. edsl/conjure/AgentConstructionMixin.py +160 -152
  28. edsl/conjure/Conjure.py +62 -62
  29. edsl/conjure/InputData.py +659 -659
  30. edsl/conjure/InputDataCSV.py +48 -48
  31. edsl/conjure/InputDataMixinQuestionStats.py +182 -182
  32. edsl/conjure/InputDataPyRead.py +91 -91
  33. edsl/conjure/InputDataSPSS.py +8 -8
  34. edsl/conjure/InputDataStata.py +8 -8
  35. edsl/conjure/QuestionOptionMixin.py +76 -76
  36. edsl/conjure/QuestionTypeMixin.py +23 -23
  37. edsl/conjure/RawQuestion.py +65 -65
  38. edsl/conjure/SurveyResponses.py +7 -7
  39. edsl/conjure/__init__.py +9 -9
  40. edsl/conjure/naming_utilities.py +263 -263
  41. edsl/conjure/utilities.py +201 -201
  42. edsl/conversation/Conversation.py +290 -238
  43. edsl/conversation/car_buying.py +58 -58
  44. edsl/conversation/chips.py +95 -0
  45. edsl/conversation/mug_negotiation.py +81 -81
  46. edsl/conversation/next_speaker_utilities.py +93 -93
  47. edsl/coop/PriceFetcher.py +54 -54
  48. edsl/coop/__init__.py +2 -2
  49. edsl/coop/coop.py +958 -824
  50. edsl/coop/utils.py +131 -131
  51. edsl/data/Cache.py +527 -527
  52. edsl/data/CacheEntry.py +228 -228
  53. edsl/data/CacheHandler.py +149 -149
  54. edsl/data/RemoteCacheSync.py +97 -97
  55. edsl/data/SQLiteDict.py +292 -292
  56. edsl/data/__init__.py +4 -4
  57. edsl/data/orm.py +10 -10
  58. edsl/data_transfer_models.py +73 -73
  59. edsl/enums.py +173 -173
  60. edsl/exceptions/BaseException.py +21 -0
  61. edsl/exceptions/__init__.py +54 -50
  62. edsl/exceptions/agents.py +38 -40
  63. edsl/exceptions/configuration.py +16 -16
  64. edsl/exceptions/coop.py +10 -10
  65. edsl/exceptions/data.py +14 -14
  66. edsl/exceptions/general.py +34 -34
  67. edsl/exceptions/jobs.py +33 -33
  68. edsl/exceptions/language_models.py +63 -63
  69. edsl/exceptions/prompts.py +15 -15
  70. edsl/exceptions/questions.py +91 -91
  71. edsl/exceptions/results.py +29 -26
  72. edsl/exceptions/scenarios.py +22 -0
  73. edsl/exceptions/surveys.py +37 -34
  74. edsl/inference_services/AnthropicService.py +87 -87
  75. edsl/inference_services/AwsBedrock.py +120 -115
  76. edsl/inference_services/AzureAI.py +217 -217
  77. edsl/inference_services/DeepInfraService.py +18 -18
  78. edsl/inference_services/GoogleService.py +156 -156
  79. edsl/inference_services/GroqService.py +20 -20
  80. edsl/inference_services/InferenceServiceABC.py +147 -147
  81. edsl/inference_services/InferenceServicesCollection.py +97 -74
  82. edsl/inference_services/MistralAIService.py +123 -123
  83. edsl/inference_services/OllamaService.py +18 -18
  84. edsl/inference_services/OpenAIService.py +224 -224
  85. edsl/inference_services/TestService.py +89 -89
  86. edsl/inference_services/TogetherAIService.py +170 -170
  87. edsl/inference_services/models_available_cache.py +118 -118
  88. edsl/inference_services/rate_limits_cache.py +25 -25
  89. edsl/inference_services/registry.py +39 -39
  90. edsl/inference_services/write_available.py +10 -10
  91. edsl/jobs/Answers.py +56 -56
  92. edsl/jobs/Jobs.py +1347 -1121
  93. edsl/jobs/__init__.py +1 -1
  94. edsl/jobs/buckets/BucketCollection.py +63 -63
  95. edsl/jobs/buckets/ModelBuckets.py +65 -65
  96. edsl/jobs/buckets/TokenBucket.py +248 -248
  97. edsl/jobs/interviews/Interview.py +661 -661
  98. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  99. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -182
  100. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  101. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  102. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  103. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  104. edsl/jobs/interviews/ReportErrors.py +66 -66
  105. edsl/jobs/interviews/interview_status_enum.py +9 -9
  106. edsl/jobs/runners/JobsRunnerAsyncio.py +338 -338
  107. edsl/jobs/runners/JobsRunnerStatus.py +332 -332
  108. edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
  109. edsl/jobs/tasks/TaskCreators.py +64 -64
  110. edsl/jobs/tasks/TaskHistory.py +442 -441
  111. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  112. edsl/jobs/tasks/task_status_enum.py +163 -163
  113. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  114. edsl/jobs/tokens/TokenUsage.py +34 -34
  115. edsl/language_models/KeyLookup.py +30 -0
  116. edsl/language_models/LanguageModel.py +706 -718
  117. edsl/language_models/ModelList.py +102 -102
  118. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  119. edsl/language_models/__init__.py +3 -2
  120. edsl/language_models/fake_openai_call.py +15 -15
  121. edsl/language_models/fake_openai_service.py +61 -61
  122. edsl/language_models/registry.py +137 -137
  123. edsl/language_models/repair.py +156 -156
  124. edsl/language_models/unused/ReplicateBase.py +83 -83
  125. edsl/language_models/utilities.py +64 -64
  126. edsl/notebooks/Notebook.py +259 -259
  127. edsl/notebooks/__init__.py +1 -1
  128. edsl/prompts/Prompt.py +357 -353
  129. edsl/prompts/__init__.py +2 -2
  130. edsl/questions/AnswerValidatorMixin.py +289 -289
  131. edsl/questions/QuestionBase.py +656 -616
  132. edsl/questions/QuestionBaseGenMixin.py +161 -161
  133. edsl/questions/QuestionBasePromptsMixin.py +234 -266
  134. edsl/questions/QuestionBudget.py +227 -227
  135. edsl/questions/QuestionCheckBox.py +359 -359
  136. edsl/questions/QuestionExtract.py +183 -183
  137. edsl/questions/QuestionFreeText.py +114 -114
  138. edsl/questions/QuestionFunctional.py +159 -159
  139. edsl/questions/QuestionList.py +231 -231
  140. edsl/questions/QuestionMultipleChoice.py +286 -286
  141. edsl/questions/QuestionNumerical.py +153 -153
  142. edsl/questions/QuestionRank.py +324 -324
  143. edsl/questions/Quick.py +41 -41
  144. edsl/questions/RegisterQuestionsMeta.py +71 -71
  145. edsl/questions/ResponseValidatorABC.py +174 -174
  146. edsl/questions/SimpleAskMixin.py +73 -73
  147. edsl/questions/__init__.py +26 -26
  148. edsl/questions/compose_questions.py +98 -98
  149. edsl/questions/decorators.py +21 -21
  150. edsl/questions/derived/QuestionLikertFive.py +76 -76
  151. edsl/questions/derived/QuestionLinearScale.py +87 -87
  152. edsl/questions/derived/QuestionTopK.py +91 -91
  153. edsl/questions/derived/QuestionYesNo.py +82 -82
  154. edsl/questions/descriptors.py +413 -418
  155. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  156. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  157. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  158. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  159. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  160. edsl/questions/prompt_templates/question_list.jinja +17 -17
  161. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  162. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  163. edsl/questions/question_registry.py +147 -147
  164. edsl/questions/settings.py +12 -12
  165. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  166. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  167. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  168. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  169. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  170. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  171. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  172. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  173. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  174. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  175. edsl/questions/templates/list/question_presentation.jinja +5 -5
  176. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  177. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  178. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  179. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  180. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  181. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  182. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  183. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  184. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  185. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  186. edsl/results/Dataset.py +293 -293
  187. edsl/results/DatasetExportMixin.py +717 -693
  188. edsl/results/DatasetTree.py +145 -145
  189. edsl/results/Result.py +450 -435
  190. edsl/results/Results.py +1071 -1160
  191. edsl/results/ResultsDBMixin.py +238 -238
  192. edsl/results/ResultsExportMixin.py +43 -43
  193. edsl/results/ResultsFetchMixin.py +33 -33
  194. edsl/results/ResultsGGMixin.py +121 -121
  195. edsl/results/ResultsToolsMixin.py +98 -98
  196. edsl/results/Selector.py +135 -118
  197. edsl/results/__init__.py +2 -2
  198. edsl/results/tree_explore.py +115 -115
  199. edsl/scenarios/FileStore.py +458 -458
  200. edsl/scenarios/Scenario.py +546 -510
  201. edsl/scenarios/ScenarioHtmlMixin.py +64 -59
  202. edsl/scenarios/ScenarioList.py +1112 -1101
  203. edsl/scenarios/ScenarioListExportMixin.py +52 -52
  204. edsl/scenarios/ScenarioListPdfMixin.py +261 -261
  205. edsl/scenarios/__init__.py +4 -4
  206. edsl/shared.py +1 -1
  207. edsl/study/ObjectEntry.py +173 -173
  208. edsl/study/ProofOfWork.py +113 -113
  209. edsl/study/SnapShot.py +80 -80
  210. edsl/study/Study.py +528 -528
  211. edsl/study/__init__.py +4 -4
  212. edsl/surveys/DAG.py +148 -148
  213. edsl/surveys/Memory.py +31 -31
  214. edsl/surveys/MemoryPlan.py +244 -244
  215. edsl/surveys/Rule.py +330 -324
  216. edsl/surveys/RuleCollection.py +387 -387
  217. edsl/surveys/Survey.py +1795 -1772
  218. edsl/surveys/SurveyCSS.py +261 -261
  219. edsl/surveys/SurveyExportMixin.py +259 -259
  220. edsl/surveys/SurveyFlowVisualizationMixin.py +121 -121
  221. edsl/surveys/SurveyQualtricsImport.py +284 -284
  222. edsl/surveys/__init__.py +3 -3
  223. edsl/surveys/base.py +53 -53
  224. edsl/surveys/descriptors.py +56 -56
  225. edsl/surveys/instructions/ChangeInstruction.py +47 -47
  226. edsl/surveys/instructions/Instruction.py +51 -51
  227. edsl/surveys/instructions/InstructionCollection.py +77 -77
  228. edsl/templates/error_reporting/base.html +23 -23
  229. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  230. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  231. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  232. edsl/templates/error_reporting/interview_details.html +115 -115
  233. edsl/templates/error_reporting/interviews.html +9 -9
  234. edsl/templates/error_reporting/overview.html +4 -4
  235. edsl/templates/error_reporting/performance_plot.html +1 -1
  236. edsl/templates/error_reporting/report.css +73 -73
  237. edsl/templates/error_reporting/report.html +117 -117
  238. edsl/templates/error_reporting/report.js +25 -25
  239. edsl/tools/__init__.py +1 -1
  240. edsl/tools/clusters.py +192 -192
  241. edsl/tools/embeddings.py +27 -27
  242. edsl/tools/embeddings_plotting.py +118 -118
  243. edsl/tools/plotting.py +112 -112
  244. edsl/tools/summarize.py +18 -18
  245. edsl/utilities/SystemInfo.py +28 -28
  246. edsl/utilities/__init__.py +22 -22
  247. edsl/utilities/ast_utilities.py +25 -25
  248. edsl/utilities/data/Registry.py +6 -6
  249. edsl/utilities/data/__init__.py +1 -1
  250. edsl/utilities/data/scooter_results.json +1 -1
  251. edsl/utilities/decorators.py +77 -77
  252. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  253. edsl/utilities/interface.py +627 -627
  254. edsl/utilities/repair_functions.py +28 -28
  255. edsl/utilities/restricted_python.py +70 -70
  256. edsl/utilities/utilities.py +409 -391
  257. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.dist-info}/LICENSE +21 -21
  258. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.dist-info}/METADATA +1 -1
  259. edsl-0.1.37.dev5.dist-info/RECORD +283 -0
  260. edsl-0.1.37.dev3.dist-info/RECORD +0 -279
  261. {edsl-0.1.37.dev3.dist-info → edsl-0.1.37.dev5.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)