edsl 0.1.14__py3-none-any.whl → 0.1.40__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 +348 -38
- edsl/BaseDiff.py +260 -0
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +46 -10
- edsl/__version__.py +1 -0
- edsl/agents/Agent.py +842 -144
- edsl/agents/AgentList.py +521 -25
- edsl/agents/Invigilator.py +250 -374
- edsl/agents/InvigilatorBase.py +257 -0
- edsl/agents/PromptConstructor.py +272 -0
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/descriptors.py +43 -13
- edsl/agents/prompt_helpers.py +129 -0
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -0
- edsl/auto/StageBase.py +243 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +74 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +218 -0
- edsl/base/Base.py +279 -0
- edsl/config.py +121 -104
- edsl/conversation/Conversation.py +290 -0
- edsl/conversation/car_buying.py +59 -0
- edsl/conversation/chips.py +95 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -0
- edsl/coop/__init__.py +1 -0
- edsl/coop/coop.py +1029 -134
- edsl/coop/utils.py +131 -0
- edsl/data/Cache.py +560 -89
- edsl/data/CacheEntry.py +230 -0
- edsl/data/CacheHandler.py +168 -0
- edsl/data/RemoteCacheSync.py +186 -0
- edsl/data/SQLiteDict.py +292 -0
- edsl/data/__init__.py +5 -3
- edsl/data/orm.py +6 -33
- edsl/data_transfer_models.py +74 -27
- edsl/enums.py +165 -8
- edsl/exceptions/BaseException.py +21 -0
- edsl/exceptions/__init__.py +52 -46
- edsl/exceptions/agents.py +33 -15
- edsl/exceptions/cache.py +5 -0
- edsl/exceptions/coop.py +8 -0
- edsl/exceptions/general.py +34 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +15 -0
- edsl/exceptions/language_models.py +46 -1
- edsl/exceptions/questions.py +80 -5
- edsl/exceptions/results.py +16 -5
- edsl/exceptions/scenarios.py +29 -0
- edsl/exceptions/surveys.py +13 -10
- edsl/inference_services/AnthropicService.py +106 -0
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -0
- edsl/inference_services/AzureAI.py +215 -0
- edsl/inference_services/DeepInfraService.py +18 -0
- edsl/inference_services/GoogleService.py +143 -0
- edsl/inference_services/GroqService.py +20 -0
- edsl/inference_services/InferenceServiceABC.py +80 -0
- edsl/inference_services/InferenceServicesCollection.py +138 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OllamaService.py +18 -0
- edsl/inference_services/OpenAIService.py +236 -0
- edsl/inference_services/PerplexityService.py +160 -0
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -0
- edsl/inference_services/TogetherAIService.py +172 -0
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/registry.py +41 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +21 -20
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +684 -204
- edsl/jobs/JobsChecks.py +172 -0
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -0
- edsl/jobs/buckets/ModelBuckets.py +65 -0
- edsl/jobs/buckets/TokenBucket.py +283 -0
- 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 +392 -0
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
- edsl/jobs/interviews/InterviewStatistic.py +63 -0
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
- edsl/jobs/interviews/InterviewStatusLog.py +92 -0
- edsl/jobs/interviews/ReportErrors.py +66 -0
- edsl/jobs/interviews/interview_status_enum.py +9 -0
- 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 -110
- edsl/jobs/runners/JobsRunnerStatus.py +298 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
- edsl/jobs/tasks/TaskCreators.py +64 -0
- edsl/jobs/tasks/TaskHistory.py +470 -0
- edsl/jobs/tasks/TaskStatusLog.py +23 -0
- edsl/jobs/tasks/task_status_enum.py +161 -0
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
- edsl/jobs/tokens/TokenUsage.py +34 -0
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +507 -386
- edsl/language_models/ModelList.py +164 -0
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
- edsl/language_models/__init__.py +1 -8
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- 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 +109 -41
- edsl/language_models/utilities.py +65 -0
- edsl/notebooks/Notebook.py +263 -0
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -0
- edsl/prompts/Prompt.py +222 -93
- edsl/prompts/__init__.py +1 -1
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -0
- edsl/questions/QuestionBasePromptsMixin.py +221 -0
- edsl/questions/QuestionBudget.py +164 -67
- edsl/questions/QuestionCheckBox.py +281 -62
- edsl/questions/QuestionDict.py +343 -0
- edsl/questions/QuestionExtract.py +136 -50
- edsl/questions/QuestionFreeText.py +79 -55
- edsl/questions/QuestionFunctional.py +138 -41
- edsl/questions/QuestionList.py +184 -57
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +293 -69
- edsl/questions/QuestionNumerical.py +109 -56
- edsl/questions/QuestionRank.py +244 -49
- edsl/questions/Quick.py +41 -0
- edsl/questions/SimpleAskMixin.py +74 -0
- edsl/questions/__init__.py +9 -6
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
- edsl/questions/compose_questions.py +13 -7
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +28 -26
- edsl/questions/derived/QuestionLinearScale.py +41 -28
- edsl/questions/derived/QuestionTopK.py +34 -26
- edsl/questions/derived/QuestionYesNo.py +40 -27
- edsl/questions/descriptors.py +228 -74
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_base_gen_mixin.py +168 -0
- edsl/questions/question_registry.py +130 -46
- edsl/questions/register_questions_meta.py +71 -0
- edsl/questions/response_validator_abc.py +188 -0
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +5 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/dict/__init__.py +0 -0
- edsl/questions/templates/dict/answering_instructions.jinja +21 -0
- edsl/questions/templates/dict/question_presentation.jinja +1 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- 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/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +550 -19
- edsl/results/DatasetExportMixin.py +594 -0
- edsl/results/DatasetTree.py +295 -0
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +477 -173
- edsl/results/Results.py +987 -269
- edsl/results/ResultsExportMixin.py +28 -125
- edsl/results/ResultsGGMixin.py +83 -15
- edsl/results/TableDisplay.py +125 -0
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/results_fetch_mixin.py +33 -0
- edsl/results/results_selector.py +145 -0
- edsl/results/results_tools_mixin.py +98 -0
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +78 -0
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +543 -0
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +431 -62
- edsl/scenarios/ScenarioHtmlMixin.py +65 -0
- edsl/scenarios/ScenarioList.py +1415 -45
- edsl/scenarios/ScenarioListExportMixin.py +45 -0
- edsl/scenarios/ScenarioListPdfMixin.py +239 -0
- edsl/scenarios/__init__.py +2 -0
- 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 +49 -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/scenario_join.py +131 -0
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +80 -0
- edsl/study/Study.py +521 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +92 -11
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +9 -4
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +156 -35
- edsl/surveys/Rule.py +221 -74
- edsl/surveys/RuleCollection.py +241 -61
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1079 -339
- edsl/surveys/SurveyCSS.py +273 -0
- edsl/surveys/SurveyExportMixin.py +235 -40
- edsl/surveys/SurveyFlowVisualization.py +181 -0
- edsl/surveys/SurveyQualtricsImport.py +284 -0
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/base.py +19 -3
- edsl/surveys/descriptors.py +17 -6
- edsl/surveys/instructions/ChangeInstruction.py +48 -0
- edsl/surveys/instructions/Instruction.py +56 -0
- edsl/surveys/instructions/InstructionCollection.py +82 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +116 -0
- edsl/templates/error_reporting/interviews.html +19 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- edsl/tools/__init__.py +1 -0
- edsl/tools/clusters.py +192 -0
- edsl/tools/embeddings.py +27 -0
- edsl/tools/embeddings_plotting.py +118 -0
- edsl/tools/plotting.py +112 -0
- edsl/tools/summarize.py +18 -0
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +5 -0
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/ast_utilities.py +3 -0
- edsl/utilities/data/Registry.py +2 -0
- edsl/utilities/decorators.py +41 -0
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/interface.py +310 -60
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/restricted_python.py +70 -0
- edsl/utilities/utilities.py +203 -13
- edsl-0.1.40.dist-info/METADATA +111 -0
- edsl-0.1.40.dist-info/RECORD +362 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
- edsl/agents/AgentListExportMixin.py +0 -24
- edsl/coop/old.py +0 -31
- edsl/data/Database.py +0 -141
- edsl/data/crud.py +0 -121
- edsl/jobs/Interview.py +0 -417
- edsl/jobs/JobsRunner.py +0 -63
- edsl/jobs/JobsRunnerStatusMixin.py +0 -115
- edsl/jobs/base.py +0 -47
- edsl/jobs/buckets.py +0 -166
- edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
- edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
- edsl/jobs/task_management.py +0 -218
- edsl/jobs/token_tracking.py +0 -78
- edsl/language_models/DeepInfra.py +0 -69
- edsl/language_models/OpenAI.py +0 -98
- edsl/language_models/model_interfaces/GeminiPro.py +0 -66
- edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
- edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
- edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
- edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
- edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
- edsl/language_models/registry.py +0 -81
- edsl/language_models/schemas.py +0 -15
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/prompts/QuestionInstructionsBase.py +0 -6
- edsl/prompts/library/agent_instructions.py +0 -29
- edsl/prompts/library/agent_persona.py +0 -17
- edsl/prompts/library/question_budget.py +0 -26
- edsl/prompts/library/question_checkbox.py +0 -32
- edsl/prompts/library/question_extract.py +0 -19
- edsl/prompts/library/question_freetext.py +0 -14
- edsl/prompts/library/question_linear_scale.py +0 -20
- edsl/prompts/library/question_list.py +0 -22
- edsl/prompts/library/question_multiple_choice.py +0 -44
- edsl/prompts/library/question_numerical.py +0 -31
- edsl/prompts/library/question_rank.py +0 -21
- edsl/prompts/prompt_config.py +0 -33
- edsl/prompts/registry.py +0 -185
- edsl/questions/Question.py +0 -240
- edsl/report/InputOutputDataTypes.py +0 -134
- edsl/report/RegressionMixin.py +0 -28
- edsl/report/ReportOutputs.py +0 -1228
- edsl/report/ResultsFetchMixin.py +0 -106
- edsl/report/ResultsOutputMixin.py +0 -14
- edsl/report/demo.ipynb +0 -645
- edsl/results/ResultsDBMixin.py +0 -184
- edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
- edsl/trackers/Tracker.py +0 -91
- edsl/trackers/TrackerAPI.py +0 -196
- edsl/trackers/TrackerTasks.py +0 -70
- edsl/utilities/pastebin.py +0 -141
- edsl-0.1.14.dist-info/METADATA +0 -69
- edsl-0.1.14.dist-info/RECORD +0 -141
- /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
- /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
- /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/questions/descriptors.py
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
+
"""This module contains the descriptors used to validate the attributes of the question classes."""
|
2
|
+
|
1
3
|
from abc import ABC, abstractmethod
|
2
4
|
import re
|
3
|
-
from typing import Any, Callable
|
4
|
-
from edsl.exceptions import (
|
5
|
+
from typing import Any, Callable, List, Optional
|
6
|
+
from edsl.exceptions.questions import (
|
5
7
|
QuestionCreationValidationError,
|
6
8
|
QuestionAnswerValidationError,
|
7
9
|
)
|
8
10
|
from edsl.questions.settings import Settings
|
9
|
-
from edsl.utilities.utilities import is_valid_variable_name
|
10
11
|
|
11
|
-
from edsl.prompts import get_classes
|
12
12
|
|
13
13
|
################################
|
14
14
|
# Helper functions
|
@@ -16,19 +16,19 @@ from edsl.prompts import get_classes
|
|
16
16
|
|
17
17
|
|
18
18
|
def contains_single_braced_substring(s: str) -> bool:
|
19
|
-
"""
|
19
|
+
"""Check if the string contains a substring in single braces."""
|
20
20
|
pattern = r"(?<!\{)\{[^{}]+\}(?!\})"
|
21
21
|
match = re.search(pattern, s)
|
22
22
|
return bool(match)
|
23
23
|
|
24
24
|
|
25
25
|
def is_number(value: Any) -> bool:
|
26
|
-
"""
|
26
|
+
"""Check if an object is a number."""
|
27
27
|
return isinstance(value, int) or isinstance(value, float)
|
28
28
|
|
29
29
|
|
30
30
|
def is_number_or_none(value: Any) -> bool:
|
31
|
-
"""
|
31
|
+
"""Check if an object is a number or None."""
|
32
32
|
return value is None or is_number(value)
|
33
33
|
|
34
34
|
|
@@ -42,43 +42,26 @@ class BaseDescriptor(ABC):
|
|
42
42
|
|
43
43
|
@abstractmethod
|
44
44
|
def validate(self, value: Any) -> None:
|
45
|
-
"""
|
45
|
+
"""Validate the value. If it is invalid, raises an exception. If it is valid, does nothing."""
|
46
46
|
pass
|
47
47
|
|
48
48
|
def __get__(self, instance, owner):
|
49
|
-
""""""
|
49
|
+
"""Get the value of the attribute."""
|
50
50
|
if self.name not in instance.__dict__:
|
51
51
|
return {}
|
52
52
|
return instance.__dict__[self.name]
|
53
53
|
|
54
54
|
def __set__(self, instance, value: Any) -> None:
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
else:
|
63
|
-
potential_prompt_classes = get_classes(
|
64
|
-
question_type=instance.question_type
|
65
|
-
)
|
66
|
-
if len(potential_prompt_classes) > 0:
|
67
|
-
instructions = potential_prompt_classes[0]().text
|
68
|
-
instance.__dict__[self.name] = instructions
|
69
|
-
instance.set_instructions = False
|
70
|
-
else:
|
71
|
-
if not hasattr(instance, "default_instructions"):
|
72
|
-
raise Exception(
|
73
|
-
"No default instructions found and no matching prompts!"
|
74
|
-
)
|
75
|
-
instructions = instance.default_instructions
|
76
|
-
instance.__dict__[self.name] = instructions
|
77
|
-
instance.set_instructions = False
|
78
|
-
|
79
|
-
# instance.set_instructions = value != instance.default_instructions
|
55
|
+
"""Set the value of the attribute."""
|
56
|
+
new_value = self.validate(value, instance)
|
57
|
+
|
58
|
+
if new_value is not None:
|
59
|
+
instance.__dict__[self.name] = new_value
|
60
|
+
else:
|
61
|
+
instance.__dict__[self.name] = value
|
80
62
|
|
81
63
|
def __set_name__(self, owner, name: str) -> None:
|
64
|
+
"""Set the name of the attribute."""
|
82
65
|
self.name = "_" + name
|
83
66
|
|
84
67
|
|
@@ -88,8 +71,10 @@ class BaseDescriptor(ABC):
|
|
88
71
|
|
89
72
|
|
90
73
|
class FunctionDescriptor(BaseDescriptor):
|
74
|
+
"""Validate that a value is a function."""
|
75
|
+
|
91
76
|
def validate(self, value: Any, instance) -> Callable:
|
92
|
-
"""
|
77
|
+
"""Validate the value is a function, and if so, returns it."""
|
93
78
|
if not callable(value):
|
94
79
|
raise QuestionCreationValidationError(
|
95
80
|
f"Expected a function (got {value}).)"
|
@@ -99,14 +84,17 @@ class FunctionDescriptor(BaseDescriptor):
|
|
99
84
|
|
100
85
|
class IntegerDescriptor(BaseDescriptor):
|
101
86
|
"""
|
102
|
-
|
87
|
+
Validate that a value is an integer.
|
88
|
+
|
103
89
|
- `none_allowed` is whether None is allowed as a value.
|
104
90
|
"""
|
105
91
|
|
106
92
|
def __init__(self, none_allowed: bool = False):
|
93
|
+
"""Initialize the descriptor."""
|
107
94
|
self.none_allowed = none_allowed
|
108
95
|
|
109
96
|
def validate(self, value, instance):
|
97
|
+
"""Validate the value is an integer."""
|
110
98
|
if self.none_allowed:
|
111
99
|
if not (isinstance(value, int) or value is None):
|
112
100
|
raise QuestionAnswerValidationError(
|
@@ -120,7 +108,10 @@ class IntegerDescriptor(BaseDescriptor):
|
|
120
108
|
|
121
109
|
|
122
110
|
class IntegerOrNoneDescriptor(BaseDescriptor):
|
111
|
+
"""Validate that a value is an integer or None."""
|
112
|
+
|
123
113
|
def validate(self, value, instance):
|
114
|
+
"""Validate the value is an integer or None."""
|
124
115
|
if not (isinstance(value, int) or value is None):
|
125
116
|
raise QuestionCreationValidationError(
|
126
117
|
f"Expected an integer or None (got {value})."
|
@@ -128,7 +119,10 @@ class IntegerOrNoneDescriptor(BaseDescriptor):
|
|
128
119
|
|
129
120
|
|
130
121
|
class NumericalOrNoneDescriptor(BaseDescriptor):
|
122
|
+
"""Validate that a value is a number or None."""
|
123
|
+
|
131
124
|
def validate(self, value, instance):
|
125
|
+
"""Validate the value is a number or None."""
|
132
126
|
if not is_number_or_none(value):
|
133
127
|
raise QuestionAnswerValidationError(
|
134
128
|
f"Expected a number or None (got {value})."
|
@@ -140,20 +134,11 @@ class NumericalOrNoneDescriptor(BaseDescriptor):
|
|
140
134
|
################################
|
141
135
|
|
142
136
|
|
143
|
-
class AllowNonresponseDescriptor(BaseDescriptor):
|
144
|
-
"""Validates that the `allow_nonresponse` attribute is a boolean."""
|
145
|
-
|
146
|
-
def validate(self, value, instance):
|
147
|
-
if not isinstance(value, bool):
|
148
|
-
raise QuestionCreationValidationError(
|
149
|
-
f"`allow_nonresponse` must be a boolean (got {value})."
|
150
|
-
)
|
151
|
-
|
152
|
-
|
153
137
|
class AnswerTemplateDescriptor(BaseDescriptor):
|
154
|
-
"""
|
138
|
+
"""Validate that the answer template is a dictionary with string keys and string values."""
|
155
139
|
|
156
140
|
def validate(self, value: Any, instance) -> None:
|
141
|
+
"""Validate the answer template."""
|
157
142
|
if not isinstance(value, dict):
|
158
143
|
raise QuestionCreationValidationError(
|
159
144
|
f"`answer_template` must be a dictionary (got {value}).)"
|
@@ -165,9 +150,10 @@ class AnswerTemplateDescriptor(BaseDescriptor):
|
|
165
150
|
|
166
151
|
|
167
152
|
class InstructionsDescriptor(BaseDescriptor):
|
168
|
-
"""
|
153
|
+
"""Validate that the `instructions` attribute is a string."""
|
169
154
|
|
170
155
|
def validate(self, value, instance):
|
156
|
+
"""Validate the value is a string."""
|
171
157
|
# if not isinstance(value, str):
|
172
158
|
# raise QuestionCreationValidationError(
|
173
159
|
# f"Question `instructions` must be a string (got {value})."
|
@@ -176,9 +162,10 @@ class InstructionsDescriptor(BaseDescriptor):
|
|
176
162
|
|
177
163
|
|
178
164
|
class NumSelectionsDescriptor(BaseDescriptor):
|
179
|
-
"""
|
165
|
+
"""Validate that `num_selections` is an integer, is less than the number of options, and is positive."""
|
180
166
|
|
181
167
|
def validate(self, value, instance):
|
168
|
+
"""Validate the value is an integer, is less than the number of options, and is positive."""
|
182
169
|
if not (isinstance(value, int)):
|
183
170
|
raise QuestionCreationValidationError(
|
184
171
|
f"`num_selections` must be an integer (got {value})."
|
@@ -194,13 +181,32 @@ class NumSelectionsDescriptor(BaseDescriptor):
|
|
194
181
|
|
195
182
|
|
196
183
|
class OptionLabelDescriptor(BaseDescriptor):
|
184
|
+
"""Validate that the `option_label` attribute is a string.
|
185
|
+
|
186
|
+
>>> class TestQuestion:
|
187
|
+
... option_label = OptionLabelDescriptor()
|
188
|
+
... def __init__(self, option_label: str):
|
189
|
+
... self.option_label = option_label
|
190
|
+
|
191
|
+
>>> _ = TestQuestion("{{Option}}")
|
192
|
+
|
193
|
+
"""
|
194
|
+
|
197
195
|
def validate(self, value, instance):
|
198
|
-
|
199
|
-
|
196
|
+
"""Validate the value is a string."""
|
197
|
+
if isinstance(value, str):
|
198
|
+
if "{{" in value and "}}" in value:
|
199
|
+
# they're trying to use a dynamic question name - let's let this play out
|
200
|
+
return None
|
201
|
+
|
202
|
+
key_values = [int(v) for v in value.keys()]
|
203
|
+
|
204
|
+
if value and (key_values := [float(v) for v in value.keys()]) != []:
|
205
|
+
if min(key_values) != min(instance.question_options):
|
200
206
|
raise QuestionCreationValidationError(
|
201
207
|
f"First option needs a label (got {value})"
|
202
208
|
)
|
203
|
-
if max(
|
209
|
+
if max(key_values) != max(instance.question_options):
|
204
210
|
raise QuestionCreationValidationError(
|
205
211
|
f"Last option needs a label (got {value})"
|
206
212
|
)
|
@@ -208,17 +214,34 @@ class OptionLabelDescriptor(BaseDescriptor):
|
|
208
214
|
raise QuestionCreationValidationError(
|
209
215
|
"Option labels must be strings (got {value})."
|
210
216
|
)
|
211
|
-
for key in
|
217
|
+
for key in key_values:
|
212
218
|
if key not in instance.question_options:
|
213
219
|
raise QuestionCreationValidationError(
|
214
220
|
f"Option label key ({key}) is not in question options ({instance.question_options})."
|
215
221
|
)
|
216
222
|
|
223
|
+
if len(value.values()) != len(set(value.values())):
|
224
|
+
raise QuestionCreationValidationError(
|
225
|
+
f"Option labels must be unique (got {value})."
|
226
|
+
)
|
227
|
+
|
217
228
|
|
218
229
|
class QuestionNameDescriptor(BaseDescriptor):
|
219
|
-
"""
|
230
|
+
"""Validate that the `question_name` attribute is a valid variable name."""
|
220
231
|
|
221
232
|
def validate(self, value, instance):
|
233
|
+
"""Validate the value is a valid variable name."""
|
234
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
235
|
+
|
236
|
+
if "{{" in value and "}}" in value:
|
237
|
+
# they're trying to use a dynamic question name - let's let this play out
|
238
|
+
return None
|
239
|
+
|
240
|
+
if value.endswith("_comment") or value.endswith("_generated_tokens"):
|
241
|
+
raise QuestionCreationValidationError(
|
242
|
+
f"`question_name` cannot end with '_comment' or '_generated_tokens - (got {value})."
|
243
|
+
)
|
244
|
+
|
222
245
|
if not is_valid_variable_name(value):
|
223
246
|
raise QuestionCreationValidationError(
|
224
247
|
f"`question_name` is not a valid variable name (got {value})."
|
@@ -226,13 +249,55 @@ class QuestionNameDescriptor(BaseDescriptor):
|
|
226
249
|
|
227
250
|
|
228
251
|
class QuestionOptionsDescriptor(BaseDescriptor):
|
229
|
-
"""
|
252
|
+
"""Validate that `question_options` is a list, does not exceed the min/max lengths, and has unique items."""
|
253
|
+
|
254
|
+
@classmethod
|
255
|
+
def example(cls):
|
256
|
+
class TestQuestion:
|
257
|
+
question_options = QuestionOptionsDescriptor()
|
230
258
|
|
231
|
-
|
259
|
+
def __init__(self, question_options: List[str]):
|
260
|
+
self.question_options = question_options
|
261
|
+
|
262
|
+
return TestQuestion
|
263
|
+
|
264
|
+
def __init__(
|
265
|
+
self,
|
266
|
+
num_choices: int = None,
|
267
|
+
linear_scale: bool = False,
|
268
|
+
q_budget: bool = False,
|
269
|
+
):
|
270
|
+
"""Initialize the descriptor."""
|
232
271
|
self.num_choices = num_choices
|
233
272
|
self.linear_scale = linear_scale
|
273
|
+
self.q_budget = q_budget
|
234
274
|
|
235
275
|
def validate(self, value: Any, instance) -> None:
|
276
|
+
"""Validate the question options.
|
277
|
+
|
278
|
+
>>> q_class = QuestionOptionsDescriptor.example()
|
279
|
+
>>> _ = q_class(["a", "b", "c"])
|
280
|
+
>>> _ = q_class(["a", "b", "c", "d", "d"])
|
281
|
+
Traceback (most recent call last):
|
282
|
+
...
|
283
|
+
edsl.exceptions.questions.QuestionCreationValidationError: Question options must be unique (got ['a', 'b', 'c', 'd', 'd']).
|
284
|
+
|
285
|
+
We allow dynamic question options, which are strings of the form '{{ question_options }}'.
|
286
|
+
|
287
|
+
>>> _ = q_class("{{dynamic_options}}")
|
288
|
+
>>> _ = q_class("dynamic_options")
|
289
|
+
Traceback (most recent call last):
|
290
|
+
...
|
291
|
+
edsl.exceptions.questions.QuestionCreationValidationError: ...
|
292
|
+
"""
|
293
|
+
if isinstance(value, str):
|
294
|
+
# Check if the string is a dynamic question option
|
295
|
+
if "{{" in value and "}}" in value:
|
296
|
+
return None
|
297
|
+
else:
|
298
|
+
raise QuestionCreationValidationError(
|
299
|
+
f"Dynamic question options must have jinja2 braces - instead received: {value}."
|
300
|
+
)
|
236
301
|
if not isinstance(value, list):
|
237
302
|
raise QuestionCreationValidationError(
|
238
303
|
f"Question options must be a list (got {value})."
|
@@ -245,18 +310,32 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
245
310
|
raise QuestionCreationValidationError(
|
246
311
|
f"Too few question options (got {value})."
|
247
312
|
)
|
248
|
-
|
313
|
+
# handle the case when question_options is a list of lists (a list of list can be converted to set)
|
314
|
+
tmp_value = [str(x) for x in value]
|
315
|
+
if len(tmp_value) != len(set(tmp_value)):
|
249
316
|
raise QuestionCreationValidationError(
|
250
317
|
f"Question options must be unique (got {value})."
|
251
318
|
)
|
252
319
|
if not self.linear_scale:
|
253
|
-
if not
|
254
|
-
|
255
|
-
|
256
|
-
|
320
|
+
if not self.q_budget:
|
321
|
+
pass
|
322
|
+
# if not (
|
323
|
+
# value
|
324
|
+
# and all(type(x) == type(value[0]) for x in value)
|
325
|
+
# and isinstance(value[0], (str, list, int, float))
|
326
|
+
# ):
|
327
|
+
# raise QuestionCreationValidationError(
|
328
|
+
# f"Question options must be all same type (got {value}).)"
|
329
|
+
# )
|
330
|
+
else:
|
331
|
+
if not all(isinstance(x, (str)) for x in value):
|
332
|
+
raise QuestionCreationValidationError(
|
333
|
+
f"Question options must be strings (got {value}).)"
|
334
|
+
)
|
257
335
|
if not all(
|
258
336
|
[
|
259
|
-
|
337
|
+
type(option) != str
|
338
|
+
or (len(option) >= 1 and len(option) < Settings.MAX_OPTION_LENGTH)
|
260
339
|
for option in value
|
261
340
|
]
|
262
341
|
):
|
@@ -287,31 +366,106 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
287
366
|
|
288
367
|
|
289
368
|
class QuestionTextDescriptor(BaseDescriptor):
|
369
|
+
"""Validate that the `question_text` attribute is a string.
|
370
|
+
|
371
|
+
|
372
|
+
>>> class TestQuestion:
|
373
|
+
... question_text = QuestionTextDescriptor()
|
374
|
+
... def __init__(self, question_text: str):
|
375
|
+
... self.question_text = question_text
|
376
|
+
|
377
|
+
>>> _ = TestQuestion("What is the capital of France?")
|
378
|
+
>>> _ = TestQuestion("What is the capital of France? {{variable}}")
|
379
|
+
>>> _ = TestQuestion("What is the capital of France? {{variable name}}")
|
380
|
+
Traceback (most recent call last):
|
381
|
+
...
|
382
|
+
edsl.exceptions.questions.QuestionCreationValidationError: Question text contains an invalid identifier: 'variable name'
|
383
|
+
"""
|
384
|
+
|
290
385
|
def validate(self, value, instance):
|
291
|
-
|
292
|
-
|
386
|
+
"""Validate the value is a string."""
|
387
|
+
# if len(value) > Settings.MAX_QUESTION_LENGTH:
|
388
|
+
# raise Exception("Question is too long!")
|
293
389
|
if len(value) < 1:
|
294
390
|
raise Exception("Question is too short!")
|
295
391
|
if not isinstance(value, str):
|
296
392
|
raise Exception("Question must be a string!")
|
297
393
|
if contains_single_braced_substring(value):
|
298
|
-
|
299
|
-
|
394
|
+
import warnings
|
395
|
+
|
396
|
+
# # warnings.warn(
|
397
|
+
# # f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
|
398
|
+
# # UserWarning,
|
399
|
+
# # )
|
400
|
+
warnings.warn(
|
401
|
+
"WARNING: Question text contains a single-braced substring. "
|
402
|
+
"If you intended to parameterize the question with a Scenario, this will "
|
403
|
+
"be changed to a double-braced substring, e.g. {{variable}}.\n"
|
404
|
+
"See details on constructing Scenarios in the docs: "
|
405
|
+
"https://docs.expectedparrot.com/en/latest/scenarios.html",
|
406
|
+
UserWarning,
|
300
407
|
)
|
408
|
+
# Automatically replace single braces with double braces
|
409
|
+
# This is here because if the user is using an f-string, the double brace will get converted to a single brace.
|
410
|
+
# This undoes that.
|
411
|
+
value = re.sub(r"\{([^\{\}]+)\}", r"{{\1}}", value)
|
412
|
+
return value
|
413
|
+
|
414
|
+
# iterate through all doubles braces and check if they are valid python identifiers
|
415
|
+
for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
|
416
|
+
if " " in match.group(1).strip():
|
417
|
+
raise QuestionCreationValidationError(
|
418
|
+
f"Question text contains an invalid identifier: '{match.group(1)}'"
|
419
|
+
)
|
301
420
|
|
421
|
+
return None
|
302
422
|
|
303
|
-
|
423
|
+
|
424
|
+
class ValueTypesDescriptor(BaseDescriptor):
|
304
425
|
def validate(self, value, instance):
|
305
|
-
"
|
306
|
-
if
|
426
|
+
"""Validate the value is a list of strings or None."""
|
427
|
+
if value is None: # Allow None as a valid value
|
428
|
+
return None
|
429
|
+
if not isinstance(value, list):
|
307
430
|
raise QuestionCreationValidationError(
|
308
|
-
f"
|
431
|
+
f"`value_types` must be a list or None (got {value})."
|
309
432
|
)
|
310
|
-
|
433
|
+
# Convert all items in the list to strings
|
434
|
+
return [str(item) for item in value]
|
435
|
+
|
436
|
+
|
437
|
+
class ValueDescriptionsDescriptor(BaseDescriptor):
|
438
|
+
def validate(self, value, instance):
|
439
|
+
"""Validate the value is a list of strings or None."""
|
440
|
+
if value is None: # Allow None as a valid value
|
441
|
+
return None
|
442
|
+
if not isinstance(value, list):
|
311
443
|
raise QuestionCreationValidationError(
|
312
|
-
f"
|
444
|
+
f"`value_descriptions` must be a list or None (got {value})."
|
313
445
|
)
|
314
|
-
if not all(isinstance(x, str) for x in value
|
446
|
+
if not all(isinstance(x, str) for x in value):
|
315
447
|
raise QuestionCreationValidationError(
|
316
|
-
f"
|
448
|
+
f"`value_descriptions` must be a list of strings (got {value})."
|
317
449
|
)
|
450
|
+
return value
|
451
|
+
|
452
|
+
|
453
|
+
class AnswerKeysDescriptor(BaseDescriptor):
|
454
|
+
"""Validate that the `answer_keys` attribute is a list of strings or integers."""
|
455
|
+
|
456
|
+
def validate(self, value, instance):
|
457
|
+
"""Validate the value is a list of strings or integers."""
|
458
|
+
if not isinstance(value, list):
|
459
|
+
raise QuestionCreationValidationError(
|
460
|
+
f"`answer_keys` must be a list (got {value})."
|
461
|
+
)
|
462
|
+
if not all(isinstance(x, (str, int)) for x in value):
|
463
|
+
raise QuestionCreationValidationError(
|
464
|
+
f"`answer_keys` must be a list of strings or integers (got {value})."
|
465
|
+
)
|
466
|
+
|
467
|
+
|
468
|
+
if __name__ == "__main__":
|
469
|
+
import doctest
|
470
|
+
|
471
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -0,0 +1,149 @@
|
|
1
|
+
from typing import List, Any, Dict, Union
|
2
|
+
from jinja2 import Environment
|
3
|
+
from edsl.questions.QuestionBase import QuestionBase
|
4
|
+
from edsl import ScenarioList
|
5
|
+
|
6
|
+
|
7
|
+
class LoopProcessor:
|
8
|
+
def __init__(self, question: QuestionBase):
|
9
|
+
self.question = question
|
10
|
+
self.env = Environment()
|
11
|
+
|
12
|
+
def process_templates(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
13
|
+
"""Process templates for each scenario and return list of modified questions.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
scenario_list: List of scenarios to process templates against
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
List of QuestionBase objects with rendered templates
|
20
|
+
"""
|
21
|
+
questions = []
|
22
|
+
starting_name = self.question.question_name
|
23
|
+
|
24
|
+
for index, scenario in enumerate(scenario_list):
|
25
|
+
question_data = self.question.to_dict().copy()
|
26
|
+
processed_data = self._process_data(question_data, scenario)
|
27
|
+
|
28
|
+
if processed_data["question_name"] == starting_name:
|
29
|
+
processed_data["question_name"] += f"_{index}"
|
30
|
+
|
31
|
+
questions.append(QuestionBase.from_dict(processed_data))
|
32
|
+
|
33
|
+
return questions
|
34
|
+
|
35
|
+
def _process_data(
|
36
|
+
self, data: Dict[str, Any], scenario: Dict[str, Any]
|
37
|
+
) -> Dict[str, Any]:
|
38
|
+
"""Process all data fields according to their type.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
data: Dictionary of question data
|
42
|
+
scenario: Current scenario to render templates against
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
Processed dictionary with rendered templates
|
46
|
+
"""
|
47
|
+
processed = {}
|
48
|
+
|
49
|
+
for key, value in [(k, v) for k, v in data.items() if v is not None]:
|
50
|
+
processed[key] = self._process_value(key, value, scenario)
|
51
|
+
|
52
|
+
return processed
|
53
|
+
|
54
|
+
def _process_value(self, key: str, value: Any, scenario: Dict[str, Any]) -> Any:
|
55
|
+
"""Process a single value according to its type.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
key: Dictionary key
|
59
|
+
value: Value to process
|
60
|
+
scenario: Current scenario
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
Processed value
|
64
|
+
"""
|
65
|
+
if key == "question_options" and isinstance(value, str):
|
66
|
+
return value
|
67
|
+
|
68
|
+
if key == "option_labels":
|
69
|
+
import json
|
70
|
+
|
71
|
+
return (
|
72
|
+
eval(self._render_template(value, scenario))
|
73
|
+
if isinstance(value, str)
|
74
|
+
else value
|
75
|
+
)
|
76
|
+
|
77
|
+
if isinstance(value, str):
|
78
|
+
return self._render_template(value, scenario)
|
79
|
+
|
80
|
+
if isinstance(value, list):
|
81
|
+
return self._process_list(value, scenario)
|
82
|
+
|
83
|
+
if isinstance(value, dict):
|
84
|
+
return self._process_dict(value, scenario)
|
85
|
+
|
86
|
+
if isinstance(value, (int, float)):
|
87
|
+
return value
|
88
|
+
|
89
|
+
raise ValueError(f"Unexpected value type: {type(value)} for key '{key}'")
|
90
|
+
|
91
|
+
def _render_template(self, template: str, scenario: Dict[str, Any]) -> str:
|
92
|
+
"""Render a single template string.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
template: Template string to render
|
96
|
+
scenario: Current scenario
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Rendered template string
|
100
|
+
"""
|
101
|
+
return self.env.from_string(template).render(scenario)
|
102
|
+
|
103
|
+
def _process_list(self, items: List[Any], scenario: Dict[str, Any]) -> List[Any]:
|
104
|
+
"""Process all items in a list.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
items: List of items to process
|
108
|
+
scenario: Current scenario
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
List of processed items
|
112
|
+
"""
|
113
|
+
return [
|
114
|
+
self._render_template(item, scenario) if isinstance(item, str) else item
|
115
|
+
for item in items
|
116
|
+
]
|
117
|
+
|
118
|
+
def _process_dict(
|
119
|
+
self, data: Dict[str, Any], scenario: Dict[str, Any]
|
120
|
+
) -> Dict[str, Any]:
|
121
|
+
"""Process all keys and values in a dictionary.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
data: Dictionary to process
|
125
|
+
scenario: Current scenario
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Dictionary with processed keys and values
|
129
|
+
"""
|
130
|
+
return {
|
131
|
+
(self._render_template(k, scenario) if isinstance(k, str) else k): (
|
132
|
+
self._render_template(v, scenario) if isinstance(v, str) else v
|
133
|
+
)
|
134
|
+
for k, v in data.items()
|
135
|
+
}
|
136
|
+
|
137
|
+
|
138
|
+
# Usage example:
|
139
|
+
"""
|
140
|
+
from edsl import QuestionFreeText, ScenarioList
|
141
|
+
|
142
|
+
question = QuestionFreeText(
|
143
|
+
question_text="What are your thoughts on: {{subject}}?",
|
144
|
+
question_name="base_{{subject}}"
|
145
|
+
)
|
146
|
+
processor = TemplateProcessor(question)
|
147
|
+
scenarios = ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])
|
148
|
+
processed_questions = processor.process_templates(scenarios)
|
149
|
+
"""
|
@@ -0,0 +1,13 @@
|
|
1
|
+
You are being asked the following question: {{question_text}}
|
2
|
+
The options are:
|
3
|
+
{% for option in question_options %}
|
4
|
+
{{ loop.index0 }}: {{option}}
|
5
|
+
{% endfor %}
|
6
|
+
Return a valid JSON formatted as follows, with a dictionary for your "answer"
|
7
|
+
where the keys are the option numbers and the values are the amounts you want
|
8
|
+
to allocate to the options, and the sum of the values is {{budget_sum}}:
|
9
|
+
|
10
|
+
{"answer": {<put dict of option numbers and allocation amounts here>}, "comment": "<put explanation here>"}
|
11
|
+
Example response for a budget of 100 and 4 options:
|
12
|
+
{"answer": {"0": 25, "1": 25, "2": 25, "3": 25}, "comment": "I allocated 25 to each option."}
|
13
|
+
There must be an allocation listed for each item (including 0).
|
@@ -0,0 +1,32 @@
|
|
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
|
+
|
13
|
+
{# Restrictions #}
|
14
|
+
{% if min_selections != None and max_selections != None and min_selections == max_selections %}
|
15
|
+
You must select exactly {{min_selections}} options.
|
16
|
+
{% elif min_selections != None and max_selections != None %}
|
17
|
+
Minimum number of options that must be selected: {{min_selections}}.
|
18
|
+
Maximum number of options that must be selected: {{max_selections}}.
|
19
|
+
{% elif min_selections != None %}
|
20
|
+
Minimum number of options that must be selected: {{min_selections}}.
|
21
|
+
{% elif max_selections != None %}
|
22
|
+
Maximum number of options that must be selected: {{max_selections}}.
|
23
|
+
{% endif %}
|
24
|
+
|
25
|
+
{# Answering Instructions #}
|
26
|
+
Please respond with valid JSON, formatted like so:
|
27
|
+
{% if include_comment %}
|
28
|
+
{"answer": [<put comma-separated list here>], "comment": "<put explanation here>"}
|
29
|
+
{% else %}
|
30
|
+
{"answer": [<put comma-separated list here>]}
|
31
|
+
{% endif %}
|
32
|
+
|