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
edsl/questions/QuestionRank.py
CHANGED
@@ -1,324 +1,314 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
import
|
3
|
-
|
4
|
-
from
|
5
|
-
|
6
|
-
from edsl.questions.QuestionBase import QuestionBase
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
if
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
if
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
#
|
219
|
-
#
|
220
|
-
#
|
221
|
-
|
222
|
-
#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
q.
|
302
|
-
q.
|
303
|
-
q.
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
q.to_dict()
|
316
|
-
assert q.from_dict(q.to_dict()) == q
|
317
|
-
|
318
|
-
q = QuestionRank.example(use_code=False)
|
319
|
-
answer = {"answer": ["Pizza", "Pasta"], "comment": "I like pizza and pasta."}
|
320
|
-
q._validate_answer(answer)
|
321
|
-
|
322
|
-
import doctest
|
323
|
-
|
324
|
-
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional, Any, List, Annotated, Literal
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field
|
5
|
+
|
6
|
+
from edsl.questions.QuestionBase import QuestionBase
|
7
|
+
from edsl.questions.descriptors import (
|
8
|
+
QuestionOptionsDescriptor,
|
9
|
+
NumSelectionsDescriptor,
|
10
|
+
)
|
11
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
12
|
+
|
13
|
+
|
14
|
+
def create_response_model(
|
15
|
+
choices: list,
|
16
|
+
num_selections: Optional[int] = None,
|
17
|
+
permissive: bool = False,
|
18
|
+
):
|
19
|
+
"""
|
20
|
+
:param choices: A list of allowed values for the answer field.
|
21
|
+
:param include_comment: Whether to include a comment field in the model.
|
22
|
+
:return: A new Pydantic model class.
|
23
|
+
"""
|
24
|
+
# Convert the choices list to a tuple for use with Literal
|
25
|
+
choice_tuple = tuple(choices)
|
26
|
+
|
27
|
+
field_params = {}
|
28
|
+
if num_selections is not None and not permissive:
|
29
|
+
field_params["min_items"] = num_selections
|
30
|
+
field_params["max_items"] = num_selections
|
31
|
+
|
32
|
+
class RankResponse(BaseModel):
|
33
|
+
answer: Annotated[
|
34
|
+
List[Literal[choice_tuple]],
|
35
|
+
Field(..., **field_params),
|
36
|
+
] = Field(..., description="List of selected choices")
|
37
|
+
comment: Optional[str] = Field(None, description="Optional comment field")
|
38
|
+
generated_tokens: Optional[Any] = Field(None)
|
39
|
+
|
40
|
+
class Config:
|
41
|
+
@staticmethod
|
42
|
+
def json_schema_extra(schema: dict, model: BaseModel) -> None:
|
43
|
+
# Add the list of choices to the schema for better documentation
|
44
|
+
for prop in schema.get("properties", {}).values():
|
45
|
+
if prop.get("title") == "answer":
|
46
|
+
prop["items"] = {"enum": choices}
|
47
|
+
|
48
|
+
return RankResponse
|
49
|
+
|
50
|
+
|
51
|
+
class RankResponseValidator(ResponseValidatorABC):
|
52
|
+
required_params = ["num_selections", "permissive", "use_code", "question_options"]
|
53
|
+
valid_examples = []
|
54
|
+
invalid_examples = []
|
55
|
+
|
56
|
+
def fix(self, response, verbose=False):
|
57
|
+
if verbose:
|
58
|
+
print("Invalid response of QuestionRank was: ", False)
|
59
|
+
response_text = response.get("generated_tokens")
|
60
|
+
if response_text is None or response_text == "": # nothing to be done
|
61
|
+
return response
|
62
|
+
# Maybe it's a comma separated list?
|
63
|
+
response_text = str(response.get("answer"))
|
64
|
+
proposed_list = (
|
65
|
+
response_text.replace("[", "").replace("]", "").replace("'", "").split(",")
|
66
|
+
)
|
67
|
+
proposed_list = [item.strip() for item in proposed_list]
|
68
|
+
|
69
|
+
if verbose:
|
70
|
+
print("Using code? ", self.use_code)
|
71
|
+
if self.use_code:
|
72
|
+
try:
|
73
|
+
proposed_list = [int(i) for i in proposed_list]
|
74
|
+
except ValueError:
|
75
|
+
# print("Could not convert to int")
|
76
|
+
pass
|
77
|
+
|
78
|
+
if verbose:
|
79
|
+
print("Proposed solution is: ", proposed_list)
|
80
|
+
|
81
|
+
# print(f"Ivalid generated tokens was was: {response_text}")
|
82
|
+
if "comment" in response:
|
83
|
+
proposed_data = {
|
84
|
+
"answer": proposed_list,
|
85
|
+
"comment": response["comment"],
|
86
|
+
"generated_tokens": response.get("generated_tokens", None),
|
87
|
+
}
|
88
|
+
else:
|
89
|
+
proposed_data = {
|
90
|
+
"answer": proposed_list,
|
91
|
+
"generated_tokens": response.get("generated_tokens", None),
|
92
|
+
}
|
93
|
+
|
94
|
+
try:
|
95
|
+
self.response_model(**proposed_data)
|
96
|
+
return proposed_data
|
97
|
+
except Exception as e:
|
98
|
+
if verbose:
|
99
|
+
print(f"Proposed solution {proposed_data} is invalid. Error: {e}")
|
100
|
+
# return response
|
101
|
+
if verbose:
|
102
|
+
print("Now seeing if responses show up in the answer")
|
103
|
+
matches = []
|
104
|
+
for index, option in enumerate(self.question_options):
|
105
|
+
if self.use_code:
|
106
|
+
if str(index) in response_text:
|
107
|
+
if index not in matches:
|
108
|
+
matches.append(index)
|
109
|
+
else:
|
110
|
+
if option in response_text:
|
111
|
+
if option not in matches:
|
112
|
+
matches.append(option)
|
113
|
+
proposed_data = {
|
114
|
+
"answer": matches,
|
115
|
+
"comment": response.get("comment", None),
|
116
|
+
"generated_tokens": response.get("generated_tokens", None),
|
117
|
+
}
|
118
|
+
try:
|
119
|
+
self.response_model(**proposed_data)
|
120
|
+
return proposed_data
|
121
|
+
except Exception as e:
|
122
|
+
if verbose:
|
123
|
+
print(f"Proposed solution {proposed_data} is invalid. Error: {e}")
|
124
|
+
return response
|
125
|
+
|
126
|
+
|
127
|
+
class QuestionRank(QuestionBase):
|
128
|
+
"""This question prompts the agent to rank options from a list."""
|
129
|
+
|
130
|
+
question_type = "rank"
|
131
|
+
question_options: list[str] = QuestionOptionsDescriptor()
|
132
|
+
num_selections = NumSelectionsDescriptor()
|
133
|
+
|
134
|
+
_response_model = None
|
135
|
+
response_validator_class = RankResponseValidator
|
136
|
+
|
137
|
+
def __init__(
|
138
|
+
self,
|
139
|
+
question_name: str,
|
140
|
+
question_text: str,
|
141
|
+
question_options: list[str],
|
142
|
+
num_selections: Optional[int] = None,
|
143
|
+
question_presentation: Optional[str] = None,
|
144
|
+
answering_instructions: Optional[str] = None,
|
145
|
+
permissive: bool = False,
|
146
|
+
use_code: bool = True,
|
147
|
+
include_comment: bool = True,
|
148
|
+
):
|
149
|
+
"""Initialize the question.
|
150
|
+
|
151
|
+
:param question_name: The name of the question.
|
152
|
+
:param question_text: The text of the question.
|
153
|
+
:param question_options: The options the respondent should select from.
|
154
|
+
:param min_selections: The minimum number of options that must be selected.
|
155
|
+
:param max_selections: The maximum number of options that must be selected.
|
156
|
+
"""
|
157
|
+
self.question_name = question_name
|
158
|
+
self.question_text = question_text
|
159
|
+
self.question_options = question_options
|
160
|
+
self.num_selections = num_selections or len(question_options)
|
161
|
+
self.question_presentation = question_presentation
|
162
|
+
self.answering_instructions = answering_instructions
|
163
|
+
self.permissive = permissive
|
164
|
+
self.use_code = use_code
|
165
|
+
self.include_comment = include_comment
|
166
|
+
|
167
|
+
def create_response_model(self):
|
168
|
+
choices = (
|
169
|
+
self.question_options
|
170
|
+
if not self.use_code
|
171
|
+
else range(len(self.question_options))
|
172
|
+
)
|
173
|
+
return create_response_model(
|
174
|
+
choices=choices,
|
175
|
+
num_selections=self.num_selections,
|
176
|
+
permissive=self.permissive,
|
177
|
+
)
|
178
|
+
|
179
|
+
################
|
180
|
+
# Answer methods
|
181
|
+
################
|
182
|
+
# def _validate_answer(self, answer: Any) -> dict[str, list[int]]:
|
183
|
+
# """Validate the answer."""
|
184
|
+
# self._validate_answer_template_basic(answer)
|
185
|
+
# self._validate_answer_key_value(answer, "answer", list)
|
186
|
+
# self._validate_answer_rank(answer)
|
187
|
+
# return answer
|
188
|
+
|
189
|
+
def _translate_answer_code_to_answer(
|
190
|
+
self, answer_codes, scenario: Scenario = None
|
191
|
+
) -> list[str]:
|
192
|
+
"""Translate the answer code to the actual answer."""
|
193
|
+
from edsl.scenarios.Scenario import Scenario
|
194
|
+
from jinja2 import Template
|
195
|
+
|
196
|
+
scenario = scenario or Scenario()
|
197
|
+
translated_options = [
|
198
|
+
Template(option).render(scenario) for option in self.question_options
|
199
|
+
]
|
200
|
+
translated_codes = []
|
201
|
+
for answer_code in answer_codes:
|
202
|
+
if self._use_code:
|
203
|
+
translated_codes.append(translated_options[int(answer_code)])
|
204
|
+
else:
|
205
|
+
translated_codes.append(answer_code)
|
206
|
+
return translated_codes
|
207
|
+
|
208
|
+
# def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
209
|
+
# """Simulate a valid answer for debugging purposes."""
|
210
|
+
# from edsl.utilities.utilities import random_string
|
211
|
+
|
212
|
+
# if human_readable:
|
213
|
+
# selected = random.sample(self.question_options, self.num_selections)
|
214
|
+
# else:
|
215
|
+
# selected = random.sample(
|
216
|
+
# range(len(self.question_options)), self.num_selections
|
217
|
+
# )
|
218
|
+
# answer = {
|
219
|
+
# "answer": selected,
|
220
|
+
# "comment": random_string(),
|
221
|
+
# }
|
222
|
+
# return answer
|
223
|
+
|
224
|
+
@property
|
225
|
+
def question_html_content(self) -> str:
|
226
|
+
from jinja2 import Template
|
227
|
+
|
228
|
+
question_html_content = Template(
|
229
|
+
"""
|
230
|
+
<form id="rankForm">
|
231
|
+
<p>{{ question_text }}</p>
|
232
|
+
{% for option in question_options %}
|
233
|
+
<div>
|
234
|
+
<label for="{{ option }}">{{ option }}</label>
|
235
|
+
<input type="number" id="{{ option }}" name="{{ question_name }}[{{ option }}]" value="0" min="1" max="{{ question_options|length }}" oninput="updateRankings()">
|
236
|
+
</div>
|
237
|
+
{% endfor %}
|
238
|
+
</form>
|
239
|
+
<script>
|
240
|
+
function updateRankings() {
|
241
|
+
let options = {{ question_options|length }};
|
242
|
+
let values = [];
|
243
|
+
let isValid = true;
|
244
|
+
|
245
|
+
{% for option in question_options %}
|
246
|
+
let value = parseInt(document.getElementById("{{ option }}").value) || 0;
|
247
|
+
if (value > 0 && value <= options && !values.includes(value)) {
|
248
|
+
values.push(value);
|
249
|
+
} else if (value !== 0) {
|
250
|
+
isValid = false;
|
251
|
+
}
|
252
|
+
{% endfor %}
|
253
|
+
|
254
|
+
if (!isValid || values.length !== new Set(values).size) {
|
255
|
+
document.getElementById("error").innerText = "Please enter unique and valid ranks for each option.";
|
256
|
+
} else {
|
257
|
+
document.getElementById("error").innerText = "";
|
258
|
+
}
|
259
|
+
}
|
260
|
+
</script>
|
261
|
+
<p id="error" style="color: red;"></p>
|
262
|
+
"""
|
263
|
+
).render(
|
264
|
+
question_name=self.question_name,
|
265
|
+
question_text=self.question_text,
|
266
|
+
question_options=self.question_options,
|
267
|
+
)
|
268
|
+
return question_html_content
|
269
|
+
|
270
|
+
################
|
271
|
+
# Helpful methods
|
272
|
+
################
|
273
|
+
@classmethod
|
274
|
+
def example(cls, use_code=False, include_comment=True) -> QuestionRank:
|
275
|
+
"""Return an example question."""
|
276
|
+
return cls(
|
277
|
+
question_name="rank_foods",
|
278
|
+
question_text="Rank your favorite foods.",
|
279
|
+
question_options=["Pizza", "Pasta", "Salad", "Soup"],
|
280
|
+
num_selections=2,
|
281
|
+
use_code=use_code,
|
282
|
+
include_comment=include_comment,
|
283
|
+
)
|
284
|
+
|
285
|
+
|
286
|
+
def main():
|
287
|
+
"""Show example usage."""
|
288
|
+
from edsl.questions.QuestionRank import QuestionRank
|
289
|
+
|
290
|
+
q = QuestionRank.example(use_code=True)
|
291
|
+
q.question_text
|
292
|
+
q.question_name
|
293
|
+
q.question_options
|
294
|
+
q.num_selections
|
295
|
+
# validate an answer
|
296
|
+
answer = {"answer": [0, 1], "comment": "I like pizza and pasta."}
|
297
|
+
q._validate_answer(answer)
|
298
|
+
# translate an answer code to an answer
|
299
|
+
# q._translate_answer_code_to_answer([0, 1])
|
300
|
+
# simulate answer
|
301
|
+
q._simulate_answer()
|
302
|
+
q._simulate_answer(human_readable=False)
|
303
|
+
q._validate_answer(q._simulate_answer(human_readable=False))
|
304
|
+
# serialization (inherits from Question)
|
305
|
+
q.to_dict()
|
306
|
+
assert q.from_dict(q.to_dict()) == q
|
307
|
+
|
308
|
+
q = QuestionRank.example(use_code=False)
|
309
|
+
answer = {"answer": ["Pizza", "Pasta"], "comment": "I like pizza and pasta."}
|
310
|
+
q._validate_answer(answer)
|
311
|
+
|
312
|
+
import doctest
|
313
|
+
|
314
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|