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
@@ -1,89 +1,113 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import textwrap
|
3
2
|
from typing import Any, Optional
|
4
|
-
from
|
5
|
-
from edsl.questions.descriptors import AllowNonresponseDescriptor
|
6
|
-
from edsl.scenarios import Scenario
|
7
|
-
from edsl.utilities import random_string
|
3
|
+
from uuid import uuid4
|
8
4
|
|
5
|
+
from pydantic import field_validator
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
from edsl.questions.QuestionBase import QuestionBase
|
8
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
9
|
+
|
10
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
11
|
+
from edsl.questions.decorators import inject_exception
|
12
|
+
|
13
|
+
from pydantic import BaseModel
|
14
|
+
from typing import Optional, Any, List
|
14
15
|
|
15
|
-
|
16
|
-
- `question_name` is the name of the question (string)
|
17
|
-
- `question_text` is the text of the question (string)
|
16
|
+
from edsl.prompts.Prompt import Prompt
|
18
17
|
|
19
|
-
Optional arguments:
|
20
|
-
- `allow_nonresponse` is whether the user can skip the question (boolean). If not provided, the default is False.
|
21
|
-
- `instructions` are the instructions for the question (string). If not provided, the default instructions are used. To view them, run `QuestionFreeText.default_instructions`
|
22
18
|
|
23
|
-
|
19
|
+
class FreeTextResponse(BaseModel):
|
24
20
|
"""
|
21
|
+
Validator for free text response questions.
|
22
|
+
"""
|
23
|
+
|
24
|
+
answer: str
|
25
|
+
generated_tokens: Optional[str] = None
|
26
|
+
|
27
|
+
|
28
|
+
class FreeTextResponseValidator(ResponseValidatorABC):
|
29
|
+
required_params = []
|
30
|
+
valid_examples = [({"answer": "This is great"}, {})]
|
31
|
+
invalid_examples = [
|
32
|
+
(
|
33
|
+
{"answer": None},
|
34
|
+
{},
|
35
|
+
"Answer code must not be missing.",
|
36
|
+
),
|
37
|
+
]
|
38
|
+
|
39
|
+
def fix(self, response, verbose=False):
|
40
|
+
return {
|
41
|
+
"answer": str(response.get("generated_tokens")),
|
42
|
+
"generated_tokens": str(response.get("generated_tokens")),
|
43
|
+
}
|
44
|
+
|
45
|
+
|
46
|
+
class QuestionFreeText(QuestionBase):
|
47
|
+
"""This question prompts the agent to respond with free text."""
|
25
48
|
|
26
49
|
question_type = "free_text"
|
27
|
-
|
28
|
-
|
29
|
-
"""\
|
30
|
-
You are being asked the following question: {{question_text}}
|
31
|
-
Return a valid JSON formatted like this:
|
32
|
-
{"answer": "<put free text answer here>"}
|
33
|
-
"""
|
34
|
-
)
|
50
|
+
_response_model = FreeTextResponse
|
51
|
+
response_validator_class = FreeTextResponseValidator
|
35
52
|
|
36
53
|
def __init__(
|
37
54
|
self,
|
38
|
-
question_text: str,
|
39
55
|
question_name: str,
|
40
|
-
|
41
|
-
|
56
|
+
question_text: str,
|
57
|
+
answering_instructions: Optional[str] = None,
|
58
|
+
question_presentation: Optional[str] = None,
|
42
59
|
):
|
43
|
-
|
44
|
-
self.question_name = question_name
|
45
|
-
self.allow_nonresponse = allow_nonresponse or False
|
46
|
-
self.instructions = instructions
|
60
|
+
"""Instantiate a new QuestionFreeText.
|
47
61
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
self.
|
53
|
-
self.
|
54
|
-
|
62
|
+
:param question_name: The name of the question.
|
63
|
+
:param question_text: The text of the question.
|
64
|
+
"""
|
65
|
+
self.question_name = question_name
|
66
|
+
self.question_text = question_text
|
67
|
+
self.answering_instructions = answering_instructions
|
68
|
+
self.question_presentation = question_presentation
|
55
69
|
|
56
|
-
|
57
|
-
|
58
|
-
|
70
|
+
@property
|
71
|
+
def question_html_content(self) -> str:
|
72
|
+
from jinja2 import Template
|
59
73
|
|
60
|
-
|
61
|
-
|
74
|
+
question_html_content = Template(
|
75
|
+
"""
|
76
|
+
<div>
|
77
|
+
<textarea id="{{ question_name }}" name="{{ question_name }}"></textarea>
|
78
|
+
</div>
|
79
|
+
"""
|
80
|
+
).render(question_name=self.question_name)
|
81
|
+
return question_html_content
|
62
82
|
|
63
83
|
@classmethod
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
)
|
84
|
+
@inject_exception
|
85
|
+
def example(cls, randomize: bool = False) -> QuestionFreeText:
|
86
|
+
"""Return an example instance of a free text question."""
|
87
|
+
addition = "" if not randomize else str(uuid4())
|
88
|
+
return cls(question_name="how_are_you", question_text=f"How are you?{addition}")
|
70
89
|
|
71
90
|
|
72
91
|
def main():
|
92
|
+
"""Create an example question and demonstrate its functionality."""
|
73
93
|
from edsl.questions.QuestionFreeText import QuestionFreeText
|
74
94
|
|
75
95
|
q = QuestionFreeText.example()
|
76
96
|
q.question_text
|
77
97
|
q.question_name
|
78
|
-
q.instructions
|
98
|
+
# q.instructions
|
79
99
|
# validate an answer
|
80
|
-
q.
|
100
|
+
q._validate_answer({"answer": "I like custard"})
|
81
101
|
# translate answer code
|
82
|
-
q.
|
102
|
+
q._translate_answer_code_to_answer({"answer"})
|
83
103
|
# simulate answer
|
84
|
-
q.
|
85
|
-
q.
|
86
|
-
q.
|
104
|
+
q._simulate_answer()
|
105
|
+
q._simulate_answer(human_readable=False)
|
106
|
+
q._validate_answer(q._simulate_answer(human_readable=False))
|
87
107
|
# serialization (inherits from Question)
|
88
108
|
q.to_dict()
|
89
109
|
assert q.from_dict(q.to_dict()) == q
|
110
|
+
|
111
|
+
import doctest
|
112
|
+
|
113
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -1,69 +1,166 @@
|
|
1
1
|
from typing import Optional, Callable
|
2
|
-
|
3
|
-
from edsl.questions.descriptors import FunctionDescriptor
|
2
|
+
import inspect
|
4
3
|
|
4
|
+
from edsl.questions.QuestionBase import QuestionBase
|
5
|
+
|
6
|
+
from edsl.utilities.restricted_python import create_restricted_function
|
7
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
8
|
+
|
9
|
+
|
10
|
+
class QuestionFunctional(QuestionBase):
|
11
|
+
"""A special type of question that is *not* answered by an LLM.
|
12
|
+
|
13
|
+
>>> from edsl import Scenario, Agent
|
14
|
+
|
15
|
+
# Create an instance of QuestionFunctional with the new function
|
16
|
+
>>> question = QuestionFunctional.example()
|
17
|
+
|
18
|
+
# Activate and test the function
|
19
|
+
>>> question.activate()
|
20
|
+
>>> scenario = Scenario({"numbers": [1, 2, 3, 4, 5]})
|
21
|
+
>>> agent = Agent(traits={"multiplier": 10})
|
22
|
+
>>> results = question.by(scenario).by(agent).run(disable_remote_cache = True, disable_remote_inference = True)
|
23
|
+
>>> results.select("answer.*").to_list()[0] == 150
|
24
|
+
True
|
25
|
+
|
26
|
+
# Serialize the question to a dictionary
|
27
|
+
|
28
|
+
>>> from edsl.questions.QuestionBase import QuestionBase
|
29
|
+
>>> new_question = QuestionBase.from_dict(question.to_dict())
|
30
|
+
>>> results = new_question.by(scenario).by(agent).run(disable_remote_cache = True, disable_remote_inference = True)
|
31
|
+
>>> results.select("answer.*").to_list()[0] == 150
|
32
|
+
True
|
5
33
|
|
6
|
-
class QuestionFunctional(Question):
|
7
|
-
"""
|
8
|
-
A special type of question that's *not* answered by an LLM
|
9
|
-
- Instead, it is "answered" by a function that is passed in, `func`.
|
10
|
-
- Useful for questions that require some kind of computation first
|
11
|
-
or are the result of a multi-step process.
|
12
|
-
See `compose_questions` in `compose_functions.py` for an example of how this is used.
|
13
|
-
|
14
|
-
Notes
|
15
|
-
- 'func' is a function that takes in a scenario and agent traits and returns an answer.
|
16
|
-
- QuestionFunctional is not meant to be instantiated directly by end-users, but rather
|
17
|
-
it is meant to be subclassed by us to create new function types.
|
18
|
-
- It is probably *not* safe to allow end-users to have the ability to pass functional-derived questions.
|
19
|
-
They could monkey-patch the function to do something malicious, e.g., to replace our function logic
|
20
|
-
with "os.system('rm -rf /')".
|
21
|
-
- One possible solution is to have interfaces they can pass via the API, like so:
|
22
|
-
QuestionDropdown(question_name = "dropdown", question_options = ["a", "b", "c"]...)
|
23
|
-
which we then translate to the real QuestionFunctional `under the hood.`
|
24
|
-
|
25
|
-
To see how it's used, see `tests/test_QuestionFunctional_construction_from_function`
|
26
34
|
"""
|
27
35
|
|
28
|
-
func: Callable = FunctionDescriptor()
|
29
36
|
question_type = "functional"
|
30
37
|
default_instructions = ""
|
38
|
+
activated = True
|
39
|
+
function_source_code = ""
|
40
|
+
function_name = ""
|
41
|
+
|
42
|
+
_response_model = None
|
43
|
+
response_validator_class = None
|
31
44
|
|
32
45
|
def __init__(
|
33
46
|
self,
|
34
47
|
question_name: str,
|
35
|
-
func: Callable,
|
48
|
+
func: Optional[Callable] = None,
|
36
49
|
question_text: Optional[str] = "Functional question",
|
50
|
+
requires_loop: Optional[bool] = False,
|
51
|
+
function_source_code: Optional[str] = None,
|
52
|
+
function_name: Optional[str] = None,
|
53
|
+
unsafe: Optional[bool] = False,
|
37
54
|
):
|
55
|
+
super().__init__()
|
56
|
+
if func:
|
57
|
+
self.function_source_code = inspect.getsource(func)
|
58
|
+
self.function_name = func.__name__
|
59
|
+
else:
|
60
|
+
self.function_source_code = function_source_code
|
61
|
+
self.function_name = function_name
|
62
|
+
|
63
|
+
self.requires_loop = requires_loop
|
64
|
+
|
65
|
+
if unsafe:
|
66
|
+
self.func = func
|
67
|
+
else:
|
68
|
+
self.func = create_restricted_function(
|
69
|
+
self.function_name, self.function_source_code
|
70
|
+
)
|
71
|
+
|
38
72
|
self.question_name = question_name
|
39
|
-
self.func = func
|
40
73
|
self.question_text = question_text
|
41
74
|
self.instructions = self.default_instructions
|
42
75
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
76
|
+
def activate(self):
|
77
|
+
self.activated = True
|
78
|
+
|
79
|
+
def activate_loop(self):
|
80
|
+
"""Activate the function with loop logic using RestrictedPython."""
|
81
|
+
self.func = create_restricted_function(
|
82
|
+
self.function_name, self.function_source_code, loop_activated=True
|
83
|
+
)
|
46
84
|
|
47
85
|
def answer_question_directly(self, scenario, agent_traits=None):
|
48
|
-
|
86
|
+
"""Return the answer to the question, ensuring the function is activated."""
|
87
|
+
if not self.activated:
|
88
|
+
raise Exception("Function not activated. Please activate it first.")
|
89
|
+
try:
|
90
|
+
return {"answer": self.func(scenario, agent_traits), "comment": None}
|
91
|
+
except Exception as e:
|
92
|
+
print("Function execution error:", e)
|
93
|
+
raise Exception("Error during function execution.")
|
49
94
|
|
50
|
-
def
|
51
|
-
"""Required by Question, but not used by QuestionFunctional"""
|
95
|
+
def _translate_answer_code_to_answer(self, answer, scenario):
|
96
|
+
"""Required by Question, but not used by QuestionFunctional."""
|
52
97
|
return None
|
53
98
|
|
54
|
-
def
|
55
|
-
"""Required by Question, but not used by QuestionFunctional"""
|
99
|
+
def _simulate_answer(self, human_readable=True) -> dict[str, str]:
|
100
|
+
"""Required by Question, but not used by QuestionFunctional."""
|
56
101
|
raise NotImplementedError
|
57
102
|
|
103
|
+
def _validate_answer(self, answer: dict[str, str]):
|
104
|
+
"""Required by Question, but not used by QuestionFunctional."""
|
105
|
+
raise NotImplementedError
|
106
|
+
|
107
|
+
@property
|
108
|
+
def question_html_content(self) -> str:
|
109
|
+
return "NA for QuestionFunctional"
|
110
|
+
|
111
|
+
# @add_edsl_version
|
112
|
+
def to_dict(self, add_edsl_version=True):
|
113
|
+
d = {
|
114
|
+
"question_name": self.question_name,
|
115
|
+
"function_source_code": self.function_source_code,
|
116
|
+
"question_type": "functional",
|
117
|
+
"requires_loop": self.requires_loop,
|
118
|
+
"function_name": self.function_name,
|
119
|
+
}
|
120
|
+
if add_edsl_version:
|
121
|
+
from edsl import __version__
|
122
|
+
|
123
|
+
d["edsl_version"] = __version__
|
124
|
+
d["edsl_class_name"] = self.__class__.__name__
|
125
|
+
|
126
|
+
return d
|
127
|
+
|
58
128
|
@classmethod
|
59
129
|
def example(cls):
|
60
|
-
"""Required by Question, but not used by QuestionFunctional"""
|
61
|
-
|
62
|
-
silly_function = lambda scenario, agent_traits: scenario.get(
|
63
|
-
"a", 1
|
64
|
-
) + scenario.get("b", 2)
|
65
130
|
return cls(
|
66
|
-
question_name="
|
67
|
-
|
68
|
-
|
131
|
+
question_name="sum_and_multiply",
|
132
|
+
func=calculate_sum_and_multiply,
|
133
|
+
question_text="Calculate the sum of the list and multiply it by the agent trait multiplier.",
|
134
|
+
requires_loop=True,
|
69
135
|
)
|
136
|
+
|
137
|
+
|
138
|
+
def calculate_sum_and_multiply(scenario, agent_traits):
|
139
|
+
numbers = scenario.get("numbers", [])
|
140
|
+
multiplier = agent_traits.get("multiplier", 1) if agent_traits else 1
|
141
|
+
sum = 0
|
142
|
+
for num in numbers:
|
143
|
+
sum = sum + num
|
144
|
+
return sum * multiplier
|
145
|
+
|
146
|
+
|
147
|
+
def main():
|
148
|
+
from edsl import Scenario, Agent
|
149
|
+
from edsl.questions.QuestionFunctional import QuestionFunctional
|
150
|
+
|
151
|
+
# Create an instance of QuestionFunctional with the new function
|
152
|
+
question = QuestionFunctional.example()
|
153
|
+
|
154
|
+
# Activate and test the function
|
155
|
+
question.activate()
|
156
|
+
scenario = Scenario({"numbers": [1, 2, 3, 4, 5]})
|
157
|
+
agent = Agent(traits={"multiplier": 10})
|
158
|
+
results = question.by(scenario).by(agent).run()
|
159
|
+
assert results.select("answer.*").to_list()[0] == 150
|
160
|
+
|
161
|
+
|
162
|
+
if __name__ == "__main__":
|
163
|
+
# main()
|
164
|
+
import doctest
|
165
|
+
|
166
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/questions/QuestionList.py
CHANGED
@@ -1,96 +1,223 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import
|
3
|
-
import textwrap
|
2
|
+
import json
|
4
3
|
from typing import Any, Optional, Union
|
5
|
-
from edsl.questions import Question
|
6
|
-
from edsl.questions.descriptors import (
|
7
|
-
AllowNonresponseDescriptor,
|
8
|
-
IntegerOrNoneDescriptor,
|
9
|
-
)
|
10
|
-
from edsl.scenarios import Scenario
|
11
|
-
from edsl.utilities import random_string
|
12
4
|
|
5
|
+
from pydantic import Field
|
6
|
+
from json_repair import repair_json
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
9
|
+
from edsl.questions.QuestionBase import QuestionBase
|
10
|
+
from edsl.questions.descriptors import IntegerOrNoneDescriptor
|
11
|
+
from edsl.questions.decorators import inject_exception
|
12
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
17
13
|
|
18
|
-
Arguments:
|
19
|
-
- `question_name` is the name of the question (string)
|
20
|
-
- `question_text` is the text of the question (string)
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
- `allow_nonresponse` is whether the user can skip the question (boolean). If not provided, the default is False.
|
25
|
-
- `instructions` are the instructions for the question (string). If not provided, the default instructions are used. To view them, run `QuestionList.default_instructions`
|
15
|
+
def convert_string(s: str) -> Union[float, int, str, dict]:
|
16
|
+
"""Convert a string to a more appropriate type if possible.
|
26
17
|
|
27
|
-
|
18
|
+
>>> convert_string("3.14")
|
19
|
+
3.14
|
20
|
+
>>> convert_string("42")
|
21
|
+
42
|
22
|
+
>>> convert_string("hello")
|
23
|
+
'hello'
|
24
|
+
>>> convert_string('{"key": "value"}')
|
25
|
+
{'key': 'value'}
|
26
|
+
>>> convert_string("{'key': 'value'}")
|
27
|
+
{'key': 'value'}
|
28
28
|
"""
|
29
29
|
|
30
|
+
if not isinstance(s, str): # if it's not a string, return it as is
|
31
|
+
return s
|
32
|
+
|
33
|
+
# If the repair returns, continue on; otherwise, try to load it as JSON
|
34
|
+
if (repaired_json := repair_json(s)) == '""':
|
35
|
+
pass
|
36
|
+
else:
|
37
|
+
try:
|
38
|
+
return json.loads(repaired_json)
|
39
|
+
except json.JSONDecodeError:
|
40
|
+
pass
|
41
|
+
|
42
|
+
# Try to convert to float
|
43
|
+
try:
|
44
|
+
return float(s)
|
45
|
+
except ValueError:
|
46
|
+
pass
|
47
|
+
|
48
|
+
# Try to convert to int
|
49
|
+
try:
|
50
|
+
return int(s)
|
51
|
+
except ValueError:
|
52
|
+
pass
|
53
|
+
|
54
|
+
# If all conversions fail, return the original string
|
55
|
+
return s
|
56
|
+
|
57
|
+
|
58
|
+
def create_model(max_list_items: int, permissive: bool) -> "ListResponse":
|
59
|
+
from pydantic import BaseModel
|
60
|
+
|
61
|
+
if permissive or max_list_items is None:
|
62
|
+
|
63
|
+
class ListResponse(BaseModel):
|
64
|
+
answer: list[Any]
|
65
|
+
comment: Optional[str] = None
|
66
|
+
generated_tokens: Optional[str] = None
|
67
|
+
|
68
|
+
else:
|
69
|
+
|
70
|
+
class ListResponse(BaseModel):
|
71
|
+
"""
|
72
|
+
>>> nr = ListResponse(answer=["Apple", "Cherry"])
|
73
|
+
>>> nr.dict()
|
74
|
+
{'answer': ['Apple', 'Cherry'], 'comment': None, 'generated_tokens': None}
|
75
|
+
"""
|
76
|
+
|
77
|
+
answer: list[Any] = Field(..., min_items=0, max_items=max_list_items)
|
78
|
+
comment: Optional[str] = None
|
79
|
+
generated_tokens: Optional[str] = None
|
80
|
+
|
81
|
+
return ListResponse
|
82
|
+
|
83
|
+
|
84
|
+
class ListResponseValidator(ResponseValidatorABC):
|
85
|
+
required_params = ["max_list_items", "permissive"]
|
86
|
+
valid_examples = [({"answer": ["hello", "world"]}, {"max_list_items": 5})]
|
87
|
+
|
88
|
+
invalid_examples = [
|
89
|
+
(
|
90
|
+
{"answer": ["hello", "world", "this", "is", "a", "test"]},
|
91
|
+
{"max_list_items": 5},
|
92
|
+
"Too many items.",
|
93
|
+
),
|
94
|
+
]
|
95
|
+
|
96
|
+
def _check_constraints(self, response) -> None:
|
97
|
+
if (
|
98
|
+
self.max_list_items is not None
|
99
|
+
and len(response.answer) > self.max_list_items
|
100
|
+
):
|
101
|
+
raise QuestionAnswerValidationError("Too many items.")
|
102
|
+
|
103
|
+
def fix(self, response, verbose=False):
|
104
|
+
if verbose:
|
105
|
+
print(f"Fixing list response: {response}")
|
106
|
+
answer = str(response.get("answer") or response.get("generated_tokens", ""))
|
107
|
+
if len(answer.split(",")) > 0:
|
108
|
+
return (
|
109
|
+
{"answer": answer.split(",")} | {"comment": response.get("comment")}
|
110
|
+
if "comment" in response
|
111
|
+
else {}
|
112
|
+
)
|
113
|
+
|
114
|
+
def _post_process(self, edsl_answer_dict):
|
115
|
+
edsl_answer_dict["answer"] = [
|
116
|
+
convert_string(item) for item in edsl_answer_dict["answer"]
|
117
|
+
]
|
118
|
+
return edsl_answer_dict
|
119
|
+
|
120
|
+
|
121
|
+
class QuestionList(QuestionBase):
|
122
|
+
"""This question prompts the agent to answer by providing a list of items as comma-separated strings."""
|
123
|
+
|
30
124
|
question_type = "list"
|
31
125
|
max_list_items: int = IntegerOrNoneDescriptor()
|
32
|
-
|
126
|
+
_response_model = None
|
127
|
+
response_validator_class = ListResponseValidator
|
33
128
|
|
34
129
|
def __init__(
|
35
130
|
self,
|
36
|
-
question_text: str,
|
37
131
|
question_name: str,
|
38
|
-
|
132
|
+
question_text: str,
|
133
|
+
include_comment: bool = True,
|
39
134
|
max_list_items: Optional[int] = None,
|
135
|
+
answering_instructions: Optional[str] = None,
|
136
|
+
question_presentation: Optional[str] = None,
|
137
|
+
permissive: bool = False,
|
40
138
|
):
|
41
|
-
|
139
|
+
"""Instantiate a new QuestionList.
|
140
|
+
|
141
|
+
:param question_name: The name of the question.
|
142
|
+
:param question_text: The text of the question.
|
143
|
+
:param max_list_items: The maximum number of items that can be in the answer list.
|
144
|
+
|
145
|
+
>>> QuestionList.example().self_check()
|
146
|
+
"""
|
42
147
|
self.question_name = question_name
|
43
|
-
self.
|
148
|
+
self.question_text = question_text
|
44
149
|
self.max_list_items = max_list_items
|
150
|
+
self.permissive = permissive
|
151
|
+
|
152
|
+
self.include_comment = include_comment
|
153
|
+
self.answering_instructions = answering_instructions
|
154
|
+
self.question_presentations = question_presentation
|
155
|
+
|
156
|
+
def create_response_model(self):
|
157
|
+
return create_model(self.max_list_items, self.permissive)
|
158
|
+
|
159
|
+
@property
|
160
|
+
def question_html_content(self) -> str:
|
161
|
+
from jinja2 import Template
|
162
|
+
|
163
|
+
question_html_content = Template(
|
164
|
+
"""
|
165
|
+
<div id="question-list-container">
|
166
|
+
<div>
|
167
|
+
<textarea name="{{ question_name }}[]" rows="1" placeholder="Enter item"></textarea>
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
<button type="button" onclick="addNewLine()">Add another line</button>
|
171
|
+
|
172
|
+
<script>
|
173
|
+
function addNewLine() {
|
174
|
+
var container = document.getElementById('question-list-container');
|
175
|
+
var newLine = document.createElement('div');
|
176
|
+
newLine.innerHTML = '<textarea name="{{ question_name }}[]" rows="1" placeholder="Enter item"></textarea>';
|
177
|
+
container.appendChild(newLine);
|
178
|
+
}
|
179
|
+
</script>
|
180
|
+
"""
|
181
|
+
).render(question_name=self.question_name)
|
182
|
+
return question_html_content
|
45
183
|
|
46
|
-
################
|
47
|
-
# Answer methods
|
48
|
-
################
|
49
|
-
def validate_answer(self, answer: Any) -> dict[str, Union[list[str], str]]:
|
50
|
-
"""Validates the answer"""
|
51
|
-
self.validate_answer_template_basic(answer)
|
52
|
-
self.validate_answer_key_value(answer, "answer", list)
|
53
|
-
self.validate_answer_list(answer)
|
54
|
-
return answer
|
55
|
-
|
56
|
-
def translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
57
|
-
"""There is no answer code."""
|
58
|
-
return answer
|
59
|
-
|
60
|
-
def simulate_answer(self, human_readable: bool = True):
|
61
|
-
"Simulates a valid answer for debugging purposes (what the validator expects)"
|
62
|
-
num_items = random.randint(1, self.max_list_items or 2)
|
63
|
-
return {"answer": [random_string() for _ in range(num_items)]}
|
64
|
-
|
65
|
-
################
|
66
|
-
# Helpful methods
|
67
|
-
################
|
68
184
|
@classmethod
|
69
|
-
|
185
|
+
@inject_exception
|
186
|
+
def example(
|
187
|
+
cls, include_comment=True, max_list_items=None, permissive=False
|
188
|
+
) -> QuestionList:
|
189
|
+
"""Return an example of a list question."""
|
70
190
|
return cls(
|
71
191
|
question_name="list_of_foods",
|
72
192
|
question_text="What are your favorite foods?",
|
73
|
-
|
74
|
-
max_list_items=
|
193
|
+
include_comment=include_comment,
|
194
|
+
max_list_items=max_list_items,
|
195
|
+
permissive=permissive,
|
75
196
|
)
|
76
197
|
|
77
198
|
|
78
199
|
def main():
|
200
|
+
"""Create an example of a list question and demonstrate its functionality."""
|
79
201
|
from edsl.questions.QuestionList import QuestionList
|
80
202
|
|
81
|
-
q = QuestionList.example()
|
203
|
+
q = QuestionList.example(max_list_items=5)
|
82
204
|
q.question_text
|
83
205
|
q.question_name
|
84
|
-
q.allow_nonresponse
|
85
206
|
q.max_list_items
|
86
207
|
# validate an answer
|
87
|
-
q.
|
208
|
+
q._validate_answer({"answer": ["pasta", "garlic", "oil", "parmesan"]})
|
88
209
|
# translate answer code
|
89
|
-
q.
|
210
|
+
q._translate_answer_code_to_answer(["pasta", "garlic", "oil", "parmesan"])
|
90
211
|
# simulate answer
|
91
|
-
q.
|
92
|
-
q.
|
93
|
-
q.
|
212
|
+
q._simulate_answer()
|
213
|
+
q._simulate_answer(human_readable=False)
|
214
|
+
q._validate_answer(q._simulate_answer(human_readable=False))
|
94
215
|
# serialization (inherits from Question)
|
95
216
|
q.to_dict()
|
96
217
|
assert q.from_dict(q.to_dict()) == q
|
218
|
+
|
219
|
+
|
220
|
+
if __name__ == "__main__":
|
221
|
+
import doctest
|
222
|
+
|
223
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|