edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__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.
- edsl/Base.py +413 -332
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +57 -49
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +1071 -867
- edsl/agents/AgentList.py +551 -413
- edsl/agents/Invigilator.py +284 -233
- edsl/agents/InvigilatorBase.py +257 -270
- edsl/agents/PromptConstructor.py +272 -354
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +2 -3
- edsl/agents/descriptors.py +99 -99
- edsl/agents/prompt_helpers.py +129 -129
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -117
- edsl/auto/StageBase.py +243 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +74 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +218 -224
- edsl/base/Base.py +279 -279
- edsl/config.py +177 -157
- edsl/conversation/Conversation.py +290 -290
- edsl/conversation/car_buying.py +59 -58
- edsl/conversation/chips.py +95 -95
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +1106 -1028
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +573 -555
- edsl/data/CacheEntry.py +230 -233
- edsl/data/CacheHandler.py +168 -149
- edsl/data/RemoteCacheSync.py +186 -78
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +5 -4
- edsl/data/hack.py +10 -0
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +74 -73
- edsl/enums.py +202 -175
- edsl/exceptions/BaseException.py +21 -21
- edsl/exceptions/__init__.py +54 -54
- edsl/exceptions/agents.py +54 -42
- edsl/exceptions/cache.py +5 -5
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +109 -91
- edsl/exceptions/results.py +29 -29
- edsl/exceptions/scenarios.py +29 -22
- edsl/exceptions/surveys.py +37 -37
- edsl/inference_services/AnthropicService.py +106 -87
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -120
- edsl/inference_services/AzureAI.py +215 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +143 -148
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +80 -147
- edsl/inference_services/InferenceServicesCollection.py +138 -97
- edsl/inference_services/MistralAIService.py +120 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +236 -224
- edsl/inference_services/PerplexityService.py +160 -163
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -89
- edsl/inference_services/TogetherAIService.py +172 -170
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +41 -41
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +43 -56
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +823 -898
- edsl/jobs/JobsChecks.py +172 -147
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +283 -251
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +396 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
- edsl/jobs/runners/JobsRunnerStatus.py +297 -330
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +470 -450
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +161 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +626 -668
- edsl/language_models/ModelList.py +164 -155
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +2 -3
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +156 -156
- edsl/language_models/utilities.py +65 -64
- edsl/notebooks/Notebook.py +263 -258
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +352 -362
- edsl/prompts/__init__.py +2 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -664
- edsl/questions/QuestionBasePromptsMixin.py +221 -217
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +180 -182
- edsl/questions/QuestionFreeText.py +113 -114
- edsl/questions/QuestionFunctional.py +166 -166
- edsl/questions/QuestionList.py +223 -231
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +330 -286
- edsl/questions/QuestionNumerical.py +151 -153
- edsl/questions/QuestionRank.py +314 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/SimpleAskMixin.py +74 -73
- edsl/questions/__init__.py +27 -26
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +90 -87
- edsl/questions/derived/QuestionTopK.py +93 -93
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +427 -413
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
- edsl/questions/question_registry.py +177 -177
- edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/CSSParameterizer.py +108 -108
- edsl/results/Dataset.py +587 -424
- edsl/results/DatasetExportMixin.py +594 -731
- edsl/results/DatasetTree.py +295 -275
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +557 -465
- edsl/results/Results.py +1183 -1165
- edsl/results/ResultsExportMixin.py +45 -43
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/TableDisplay.py +125 -198
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +2 -2
- edsl/results/file_exports.py +252 -0
- edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
- edsl/results/{Selector.py → results_selector.py} +145 -135
- edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +77 -77
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +511 -632
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +498 -601
- edsl/scenarios/ScenarioHtmlMixin.py +65 -64
- edsl/scenarios/ScenarioList.py +1458 -1287
- edsl/scenarios/ScenarioListExportMixin.py +45 -52
- edsl/scenarios/ScenarioListPdfMixin.py +239 -261
- edsl/scenarios/__init__.py +3 -4
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +521 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +327 -326
- edsl/surveys/RuleCollection.py +385 -387
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1280 -1801
- edsl/surveys/SurveyCSS.py +273 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +5 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +60 -56
- edsl/surveys/instructions/ChangeInstruction.py +48 -49
- edsl/surveys/instructions/Instruction.py +56 -65
- edsl/surveys/instructions/InstructionCollection.py +82 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +19 -19
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/test_h +1 -0
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/gcp_bucket/example.py +50 -0
- edsl/utilities/interface.py +627 -627
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -263
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +436 -424
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev4.dist-info/RECORD +361 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev3.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/WHEEL +0 -0
@@ -1,174 +1,188 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
from edsl.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
self.
|
35
|
-
self.
|
36
|
-
self.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
"""
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
"""
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
"""
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
>>>
|
105
|
-
|
106
|
-
>>>
|
107
|
-
|
108
|
-
>>> rv.validate({"answer": "120"})
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Optional, Any, List, TypedDict
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field, field_validator, ValidationError
|
5
|
+
|
6
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
7
|
+
from edsl.questions.ExceptionExplainer import ExceptionExplainer
|
8
|
+
|
9
|
+
from edsl.questions.data_structures import (
|
10
|
+
RawEdslAnswerDict,
|
11
|
+
EdslAnswerDict,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class ResponseValidatorABC(ABC):
|
16
|
+
required_params: List[str] = []
|
17
|
+
|
18
|
+
def __init_subclass__(cls, **kwargs):
|
19
|
+
"""This is a metaclass that ensures that all subclasses of ResponseValidatorABC have the required class variables."""
|
20
|
+
super().__init_subclass__(**kwargs)
|
21
|
+
required_class_vars = ["required_params", "valid_examples", "invalid_examples"]
|
22
|
+
for var in required_class_vars:
|
23
|
+
if not hasattr(cls, var):
|
24
|
+
raise ValueError(f"Class {cls.__name__} must have a '{var}' attribute.")
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
response_model: type[BaseModel],
|
29
|
+
exception_to_throw: Optional[Exception] = None,
|
30
|
+
override_answer: Optional[dict] = None,
|
31
|
+
**kwargs,
|
32
|
+
):
|
33
|
+
self.response_model = response_model
|
34
|
+
self.exception_to_throw = exception_to_throw # for testing
|
35
|
+
self.override_answer = override_answer # for testing
|
36
|
+
self.original_exception = None
|
37
|
+
|
38
|
+
# Validate required parameters
|
39
|
+
missing_params = [
|
40
|
+
param for param in self.required_params if param not in kwargs
|
41
|
+
]
|
42
|
+
if missing_params:
|
43
|
+
raise ValueError(
|
44
|
+
f"Missing required parameters: {', '.join(missing_params)}"
|
45
|
+
)
|
46
|
+
|
47
|
+
# Set attributes
|
48
|
+
for key, value in kwargs.items():
|
49
|
+
setattr(self, key, value)
|
50
|
+
|
51
|
+
if not hasattr(self, "permissive"):
|
52
|
+
self.permissive = False
|
53
|
+
|
54
|
+
self.fixes_tried = 0 # how many times we've tried to fix the answer
|
55
|
+
|
56
|
+
def _preprocess(self, data: RawEdslAnswerDict) -> RawEdslAnswerDict:
|
57
|
+
"""This is for testing purposes. A question can be given an exception to throw or an answer to always return.
|
58
|
+
|
59
|
+
>>> rv = ResponseValidatorABC.example()
|
60
|
+
>>> rv.override_answer = {"answer": 42}
|
61
|
+
>>> rv.validate({"answer": 23})
|
62
|
+
{'answer': 42, 'comment': None, 'generated_tokens': None}
|
63
|
+
"""
|
64
|
+
if self.exception_to_throw:
|
65
|
+
raise self.exception_to_throw
|
66
|
+
return self.override_answer if self.override_answer else data
|
67
|
+
|
68
|
+
def _base_validate(self, data: RawEdslAnswerDict) -> BaseModel:
|
69
|
+
"""This is the main validation function. It takes the response_model and checks the data against it,
|
70
|
+
returning the instantiated model.
|
71
|
+
|
72
|
+
>>> rv = ResponseValidatorABC.example("numerical")
|
73
|
+
>>> rv._base_validate({"answer": 42})
|
74
|
+
ConstrainedNumericResponse(answer=42, comment=None, generated_tokens=None)
|
75
|
+
"""
|
76
|
+
try:
|
77
|
+
return self.response_model(**data)
|
78
|
+
except ValidationError as e:
|
79
|
+
raise QuestionAnswerValidationError(
|
80
|
+
message=str(e), pydantic_error=e, data=data, model=self.response_model
|
81
|
+
)
|
82
|
+
|
83
|
+
def post_validation_answer_convert(self, data):
|
84
|
+
return data
|
85
|
+
|
86
|
+
def validate(
|
87
|
+
self,
|
88
|
+
raw_edsl_answer_dict: RawEdslAnswerDict,
|
89
|
+
fix=False,
|
90
|
+
verbose=False,
|
91
|
+
replacement_dict: dict = None,
|
92
|
+
) -> EdslAnswerDict:
|
93
|
+
"""This is the main validation function.
|
94
|
+
|
95
|
+
>>> rv = ResponseValidatorABC.example("numerical")
|
96
|
+
>>> rv.validate({"answer": 42})
|
97
|
+
{'answer': 42, 'comment': None, 'generated_tokens': None}
|
98
|
+
>>> rv.max_value
|
99
|
+
86.7
|
100
|
+
>>> rv.validate({"answer": "120"})
|
101
|
+
Traceback (most recent call last):
|
102
|
+
...
|
103
|
+
edsl.exceptions.questions.QuestionAnswerValidationError:...
|
104
|
+
>>> from edsl import QuestionNumerical
|
105
|
+
>>> q = QuestionNumerical.example()
|
106
|
+
>>> q.permissive = True
|
107
|
+
>>> rv = q.response_validator
|
108
|
+
>>> rv.validate({"answer": "120"})
|
109
|
+
{'answer': 120, 'comment': None, 'generated_tokens': None}
|
110
|
+
>>> rv.validate({"answer": "poo"})
|
111
|
+
Traceback (most recent call last):
|
112
|
+
...
|
113
|
+
edsl.exceptions.questions.QuestionAnswerValidationError:...
|
114
|
+
"""
|
115
|
+
proposed_edsl_answer_dict = self._preprocess(raw_edsl_answer_dict)
|
116
|
+
try:
|
117
|
+
pydantic_edsl_answer: BaseModel = self._base_validate(
|
118
|
+
proposed_edsl_answer_dict
|
119
|
+
)
|
120
|
+
edsl_answer_dict = self._extract_answer(pydantic_edsl_answer)
|
121
|
+
return self._post_process(edsl_answer_dict)
|
122
|
+
except QuestionAnswerValidationError as e:
|
123
|
+
return self._handle_exception(e, raw_edsl_answer_dict)
|
124
|
+
|
125
|
+
def human_explanation(self, e: QuestionAnswerValidationError):
|
126
|
+
explanation = ExceptionExplainer(e, model_response=e.data).explain()
|
127
|
+
return explanation
|
128
|
+
|
129
|
+
def _handle_exception(self, e: Exception, raw_edsl_answer_dict) -> EdslAnswerDict:
|
130
|
+
if self.fixes_tried == 0:
|
131
|
+
self.original_exception = e
|
132
|
+
|
133
|
+
if self.fixes_tried == 0 and hasattr(self, "fix"):
|
134
|
+
self.fixes_tried += 1
|
135
|
+
fixed_data = self.fix(raw_edsl_answer_dict)
|
136
|
+
try:
|
137
|
+
return self.validate(fixed_data, fix=True) # early return if validates
|
138
|
+
except Exception as e:
|
139
|
+
pass # we don't log failed fixes
|
140
|
+
|
141
|
+
# If the exception is already a QuestionAnswerValidationError, raise it
|
142
|
+
if isinstance(self.original_exception, QuestionAnswerValidationError):
|
143
|
+
raise self.original_exception
|
144
|
+
|
145
|
+
# If nothing worked, raise the original exception
|
146
|
+
raise QuestionAnswerValidationError(
|
147
|
+
message=self.original_exception,
|
148
|
+
pydantic_error=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
|
+
def main():
|
171
|
+
rv = ResponseValidatorABC.example()
|
172
|
+
print(rv.validate({"answer": 42}))
|
173
|
+
|
174
|
+
|
175
|
+
# Example usage
|
176
|
+
if __name__ == "__main__":
|
177
|
+
import doctest
|
178
|
+
|
179
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
180
|
+
|
181
|
+
rv = ResponseValidatorABC.example()
|
182
|
+
# print(rv.validate({"answer": 42}))
|
183
|
+
|
184
|
+
rv = ResponseValidatorABC.example()
|
185
|
+
try:
|
186
|
+
rv.validate({"answer": "120"})
|
187
|
+
except QuestionAnswerValidationError as e:
|
188
|
+
print(rv.human_explanation(e))
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from edsl.questions.data_structures import BaseModel
|
2
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
3
|
+
|
4
|
+
|
5
|
+
class ResponseValidatorFactory:
|
6
|
+
"""Factory class to create a response validator for a question."""
|
7
|
+
|
8
|
+
def __init__(self, question):
|
9
|
+
self.question = question
|
10
|
+
|
11
|
+
@property
|
12
|
+
def response_model(self) -> type["BaseModel"]:
|
13
|
+
if self.question._response_model is not None:
|
14
|
+
return self.question._response_model
|
15
|
+
else:
|
16
|
+
return self.question.create_response_model()
|
17
|
+
|
18
|
+
@property
|
19
|
+
def response_validator(self) -> "ResponseValidatorABC":
|
20
|
+
"""Return the response validator."""
|
21
|
+
params = (
|
22
|
+
{
|
23
|
+
"response_model": self.question.response_model,
|
24
|
+
}
|
25
|
+
| {k: getattr(self.question, k) for k in self.validator_parameters}
|
26
|
+
| {"exception_to_throw": getattr(self.question, "exception_to_throw", None)}
|
27
|
+
| {"override_answer": getattr(self.question, "override_answer", None)}
|
28
|
+
)
|
29
|
+
return self.question.response_validator_class(**params)
|
30
|
+
|
31
|
+
@property
|
32
|
+
def validator_parameters(self) -> list[str]:
|
33
|
+
"""Return the parameters required for the response validator."""
|
34
|
+
return self.question.response_validator_class.required_params
|
edsl/questions/settings.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
"""Settings for the questions module."""
|
2
|
-
|
3
|
-
|
4
|
-
class Settings:
|
5
|
-
"""Settings for the questions module."""
|
6
|
-
|
7
|
-
MAX_ANSWER_LENGTH = 2000
|
8
|
-
MAX_EXPRESSION_CONSTRAINT_LENGTH = 1000
|
9
|
-
MAX_NUM_OPTIONS = 200
|
10
|
-
MIN_NUM_OPTIONS = 2
|
11
|
-
MAX_OPTION_LENGTH = 10000
|
12
|
-
MAX_QUESTION_LENGTH = 100000
|
1
|
+
"""Settings for the questions module."""
|
2
|
+
|
3
|
+
|
4
|
+
class Settings:
|
5
|
+
"""Settings for the questions module."""
|
6
|
+
|
7
|
+
MAX_ANSWER_LENGTH = 2000
|
8
|
+
MAX_EXPRESSION_CONSTRAINT_LENGTH = 1000
|
9
|
+
MAX_NUM_OPTIONS = 200
|
10
|
+
MIN_NUM_OPTIONS = 2
|
11
|
+
MAX_OPTION_LENGTH = 10000
|
12
|
+
MAX_QUESTION_LENGTH = 100000
|
@@ -1,7 +1,7 @@
|
|
1
|
-
Return only a comma-separated list the values in the same order as the options, with 0s included, on one line, in square braces.
|
2
|
-
|
3
|
-
Example: if there are 4 options, the response should be "[25,25,25,25]" to allocate 25 to each option.
|
4
|
-
|
5
|
-
{% if include_comment %}
|
6
|
-
After the answer, you can put a comment explaining your choice on the next line.
|
7
|
-
{% endif %}
|
1
|
+
Return only a comma-separated list the values in the same order as the options, with 0s included, on one line, in square braces.
|
2
|
+
|
3
|
+
Example: if there are 4 options, the response should be "[25,25,25,25]" to allocate 25 to each option.
|
4
|
+
|
5
|
+
{% if include_comment %}
|
6
|
+
After the answer, you can put a comment explaining your choice on the next line.
|
7
|
+
{% endif %}
|
@@ -1,7 +1,7 @@
|
|
1
|
-
{{question_text}}
|
2
|
-
The options are
|
3
|
-
{% for option in question_options %}
|
4
|
-
{{ loop.index0 }}: {{option}}
|
5
|
-
{% endfor %}
|
6
|
-
Allocate your budget of {{budget_sum}} among the options.
|
7
|
-
|
1
|
+
{{question_text}}
|
2
|
+
The options are
|
3
|
+
{% for option in question_options %}
|
4
|
+
{{ loop.index0 }}: {{option}}
|
5
|
+
{% endfor %}
|
6
|
+
Allocate your budget of {{budget_sum}} among the options.
|
7
|
+
|
@@ -1,10 +1,10 @@
|
|
1
|
-
{# Answering Instructions #}
|
2
|
-
{% if use_code %}
|
3
|
-
Please respond only with a comma-separated list of the code of the options that apply, with square brackets. E.g., [0, 1, 3]
|
4
|
-
{% else %}
|
5
|
-
Please respond only with a comma-separated list of the options that apply, with square brackets. E.g., ['Good', 'Bad', 'Ugly']
|
6
|
-
{% endif %}
|
7
|
-
{% if include_comment %}
|
8
|
-
After the answer, you can put a comment explaining your choice on the next line.
|
9
|
-
{% endif %}
|
10
|
-
|
1
|
+
{# Answering Instructions #}
|
2
|
+
{% if use_code %}
|
3
|
+
Please respond only with a comma-separated list of the code of the options that apply, with square brackets. E.g., [0, 1, 3]
|
4
|
+
{% else %}
|
5
|
+
Please respond only with a comma-separated list of the options that apply, with square brackets. E.g., ['Good', 'Bad', 'Ugly']
|
6
|
+
{% endif %}
|
7
|
+
{% if include_comment %}
|
8
|
+
After the answer, you can put a comment explaining your choice on the next line.
|
9
|
+
{% endif %}
|
10
|
+
|
@@ -1,22 +1,22 @@
|
|
1
|
-
{{question_text}}
|
2
|
-
{% if use_code %}
|
3
|
-
{% for option in question_options %}
|
4
|
-
{{ loop.index0 }}: {{option}}
|
5
|
-
{% endfor %}
|
6
|
-
{% else %}
|
7
|
-
{% for option in question_options %}
|
8
|
-
{{ option }}
|
9
|
-
{% endfor %}
|
10
|
-
{% endif %}
|
11
|
-
|
12
|
-
{# Restrictions #}
|
13
|
-
{% if min_selections != None and max_selections != None and min_selections == max_selections %}
|
14
|
-
You must select exactly {{min_selections}} options.
|
15
|
-
{% elif min_selections != None and max_selections != None %}
|
16
|
-
Minimum number of options that must be selected: {{min_selections}}.
|
17
|
-
Maximum number of options that must be selected: {{max_selections}}.
|
18
|
-
{% elif min_selections != None %}
|
19
|
-
Minimum number of options that must be selected: {{min_selections}}.
|
20
|
-
{% elif max_selections != None %}
|
21
|
-
Maximum number of options that must be selected: {{max_selections}}.
|
22
|
-
{% endif %}
|
1
|
+
{{question_text}}
|
2
|
+
{% if use_code %}
|
3
|
+
{% for option in question_options %}
|
4
|
+
{{ loop.index0 }}: {{option}}
|
5
|
+
{% endfor %}
|
6
|
+
{% else %}
|
7
|
+
{% for option in question_options %}
|
8
|
+
{{ option }}
|
9
|
+
{% endfor %}
|
10
|
+
{% endif %}
|
11
|
+
|
12
|
+
{# Restrictions #}
|
13
|
+
{% if min_selections != None and max_selections != None and min_selections == max_selections %}
|
14
|
+
You must select exactly {{min_selections}} options.
|
15
|
+
{% elif min_selections != None and max_selections != None %}
|
16
|
+
Minimum number of options that must be selected: {{min_selections}}.
|
17
|
+
Maximum number of options that must be selected: {{max_selections}}.
|
18
|
+
{% elif min_selections != None %}
|
19
|
+
Minimum number of options that must be selected: {{min_selections}}.
|
20
|
+
{% elif max_selections != None %}
|
21
|
+
Maximum number of options that must be selected: {{max_selections}}.
|
22
|
+
{% endif %}
|
@@ -1,7 +1,7 @@
|
|
1
|
-
An ANSWER should be formatted like this:
|
2
|
-
|
3
|
-
{{ answer_template }}
|
4
|
-
|
5
|
-
It should have the same keys but values extracted from the input.
|
6
|
-
If the value of a key is not present in the input, fill with "null".
|
7
|
-
Put any comments in the next line after the answer.
|
1
|
+
An ANSWER should be formatted like this:
|
2
|
+
|
3
|
+
{{ answer_template }}
|
4
|
+
|
5
|
+
It should have the same keys but values extracted from the input.
|
6
|
+
If the value of a key is not present in the input, fill with "null".
|
7
|
+
Put any comments in the next line after the answer.
|
@@ -1,10 +1,10 @@
|
|
1
|
-
{# Answering Instructions #}
|
2
|
-
{% if use_code %}
|
3
|
-
Respond only with the code corresponding to one of the options.
|
4
|
-
{% else %}
|
5
|
-
Respond only with a string corresponding to one of the options.
|
6
|
-
{% endif %}
|
7
|
-
{% if include_comment %}
|
8
|
-
After the answer, you can put a comment explaining why you chose that option on the next line.
|
9
|
-
{% endif %}
|
10
|
-
|
1
|
+
{# Answering Instructions #}
|
2
|
+
{% if use_code %}
|
3
|
+
Respond only with the code corresponding to one of the options.
|
4
|
+
{% else %}
|
5
|
+
Respond only with a string corresponding to one of the options.
|
6
|
+
{% endif %}
|
7
|
+
{% if include_comment %}
|
8
|
+
After the answer, you can put a comment explaining why you chose that option on the next line.
|
9
|
+
{% endif %}
|
10
|
+
|
@@ -1,12 +1,12 @@
|
|
1
|
-
{# Question Presention #}
|
2
|
-
{{question_text}}
|
3
|
-
{% if use_code %}
|
4
|
-
{%- for option in question_options %}
|
5
|
-
{{ loop.index0 }}: {{option}}
|
6
|
-
{% endfor %}
|
7
|
-
{% else %}
|
8
|
-
{% for option in question_options %}
|
9
|
-
{{option}}
|
10
|
-
{% endfor %}
|
11
|
-
{% endif %}
|
1
|
+
{# Question Presention #}
|
2
|
+
{{question_text}}
|
3
|
+
{% if use_code %}
|
4
|
+
{%- for option in question_options %}
|
5
|
+
{{ loop.index0 }}: {{option}}
|
6
|
+
{% endfor %}
|
7
|
+
{% else %}
|
8
|
+
{% for option in question_options %}
|
9
|
+
{{option}}
|
10
|
+
{% endfor %}
|
11
|
+
{% endif %}
|
12
12
|
Only 1 option may be selected.
|
@@ -1,5 +1,5 @@
|
|
1
|
-
{# Answering Instructions #}
|
2
|
-
Respond only with the code corresponding to one of the options. E.g., "1" or "5" by itself.
|
3
|
-
{% if include_comment %}
|
4
|
-
After the answer, you can put a comment explaining why you chose that option on the next line.
|
5
|
-
{% endif %}
|
1
|
+
{# Answering Instructions #}
|
2
|
+
Respond only with the code corresponding to one of the options. E.g., "1" or "5" by itself.
|
3
|
+
{% if include_comment %}
|
4
|
+
After the answer, you can put a comment explaining why you chose that option on the next line.
|
5
|
+
{% endif %}
|
@@ -1,5 +1,5 @@
|
|
1
|
-
{{question_text}}
|
2
|
-
{% for option in question_options %}
|
3
|
-
{{option}} : {{ option_labels.get(option, "") }}
|
4
|
-
{% endfor %}
|
5
|
-
Only 1 option may be selected.
|
1
|
+
{{question_text}}
|
2
|
+
{% for option in question_options %}
|
3
|
+
{{option}} : {{ option_labels.get(option, "") }}
|
4
|
+
{% endfor %}
|
5
|
+
Only 1 option may be selected.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Return your answers on one line, in a comma-separated list of your responses, with square brackets and each answer in quotes E.g., ["A", "B", "C"]
|
2
|
-
{% if include_comment %}
|
3
|
-
After the answers, you can put a comment explaining your choice on the next line.
|
1
|
+
Return your answers on one line, in a comma-separated list of your responses, with square brackets and each answer in quotes E.g., ["A", "B", "C"]
|
2
|
+
{% if include_comment %}
|
3
|
+
After the answers, you can put a comment explaining your choice on the next line.
|
4
4
|
{% endif %}
|
@@ -1,5 +1,5 @@
|
|
1
|
-
{{question_text}}
|
2
|
-
|
3
|
-
{% if max_list_items is not none %}
|
4
|
-
The list must not contain more than {{ max_list_items }} items.
|
5
|
-
{% endif %}
|
1
|
+
{{question_text}}
|
2
|
+
|
3
|
+
{% if max_list_items is not none %}
|
4
|
+
The list must not contain more than {{ max_list_items }} items.
|
5
|
+
{% endif %}
|
@@ -0,0 +1 @@
|
|
1
|
+
|