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