edsl 0.1.47__py3-none-any.whl → 0.1.48__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/__init__.py +44 -39
- edsl/__version__.py +1 -1
- edsl/agents/__init__.py +4 -2
- edsl/agents/{Agent.py → agent.py} +442 -152
- edsl/agents/{AgentList.py → agent_list.py} +220 -162
- edsl/agents/descriptors.py +46 -7
- edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
- edsl/base/__init__.py +75 -0
- edsl/base/base_class.py +1303 -0
- edsl/base/data_transfer_models.py +114 -0
- edsl/base/enums.py +215 -0
- edsl/base.py +8 -0
- edsl/buckets/__init__.py +25 -0
- edsl/buckets/bucket_collection.py +324 -0
- edsl/buckets/model_buckets.py +206 -0
- edsl/buckets/token_bucket.py +502 -0
- edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
- edsl/buckets/token_bucket_client.py +509 -0
- edsl/caching/__init__.py +20 -0
- edsl/caching/cache.py +814 -0
- edsl/caching/cache_entry.py +427 -0
- edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
- edsl/caching/exceptions.py +24 -0
- edsl/caching/orm.py +30 -0
- edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
- edsl/caching/sql_dict.py +441 -0
- edsl/config/__init__.py +8 -0
- edsl/config/config_class.py +177 -0
- edsl/config.py +4 -176
- edsl/conversation/Conversation.py +7 -7
- edsl/conversation/car_buying.py +4 -4
- edsl/conversation/chips.py +6 -6
- edsl/coop/__init__.py +25 -2
- edsl/coop/coop.py +303 -67
- edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
- edsl/coop/exceptions.py +62 -0
- edsl/coop/price_fetcher.py +126 -0
- edsl/coop/utils.py +89 -24
- edsl/data_transfer_models.py +5 -72
- edsl/dataset/__init__.py +10 -0
- edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
- edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
- edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
- edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
- edsl/{results → dataset/display}/table_renderers.py +58 -2
- edsl/{results → dataset}/file_exports.py +4 -5
- edsl/{results → dataset}/smart_objects.py +2 -2
- edsl/enums.py +5 -205
- edsl/inference_services/__init__.py +5 -0
- edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
- edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
- edsl/inference_services/data_structures.py +3 -2
- edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
- edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
- edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
- edsl/inference_services/registry.py +4 -41
- edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
- edsl/inference_services/services/__init__.py +31 -0
- edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
- edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
- edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
- edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
- edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
- edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
- edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
- edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
- edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
- edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
- edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
- edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
- edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
- edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
- edsl/inference_services/write_available.py +1 -2
- edsl/instructions/__init__.py +6 -0
- edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
- edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
- edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
- edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
- edsl/interviews/__init__.py +4 -0
- edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
- edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
- edsl/interviews/interview.py +638 -0
- edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
- edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
- edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
- edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
- edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
- edsl/invigilators/__init__.py +38 -0
- edsl/invigilators/invigilator_base.py +477 -0
- edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
- edsl/invigilators/prompt_constructor.py +476 -0
- edsl/{agents → invigilators}/prompt_helpers.py +2 -1
- edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
- edsl/{agents → invigilators}/question_option_processor.py +96 -21
- edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
- edsl/jobs/__init__.py +7 -1
- edsl/jobs/async_interview_runner.py +99 -35
- edsl/jobs/check_survey_scenario_compatibility.py +7 -5
- edsl/jobs/data_structures.py +153 -22
- edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
- edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
- edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
- edsl/jobs/{Jobs.py → jobs.py} +313 -167
- edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
- edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
- edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
- edsl/jobs/jobs_pricing_estimation.py +347 -0
- edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
- edsl/jobs/jobs_runner_asyncio.py +282 -0
- edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
- edsl/jobs/results_exceptions_handler.py +2 -2
- edsl/key_management/__init__.py +28 -0
- edsl/key_management/key_lookup.py +161 -0
- edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
- edsl/key_management/key_lookup_collection.py +82 -0
- edsl/key_management/models.py +218 -0
- edsl/language_models/__init__.py +7 -2
- edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
- edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
- edsl/language_models/language_model.py +1080 -0
- edsl/language_models/model.py +10 -25
- edsl/language_models/{ModelList.py → model_list.py} +9 -14
- edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
- edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
- edsl/language_models/repair.py +4 -4
- edsl/language_models/utilities.py +4 -4
- edsl/notebooks/__init__.py +3 -1
- edsl/notebooks/{Notebook.py → notebook.py} +7 -8
- edsl/prompts/__init__.py +1 -1
- edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
- edsl/prompts/{Prompt.py → prompt.py} +101 -95
- edsl/questions/HTMLQuestion.py +1 -1
- edsl/questions/__init__.py +154 -25
- edsl/questions/answer_validator_mixin.py +1 -1
- edsl/questions/compose_questions.py +4 -3
- edsl/questions/derived/question_likert_five.py +166 -0
- edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
- edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
- edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
- edsl/questions/descriptors.py +24 -30
- edsl/questions/loop_processor.py +65 -19
- edsl/questions/question_base.py +881 -0
- edsl/questions/question_base_gen_mixin.py +15 -16
- edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
- edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
- edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
- edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
- edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
- edsl/questions/question_free_text.py +282 -0
- edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
- edsl/questions/{QuestionList.py → question_list.py} +6 -7
- edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
- edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
- edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
- edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
- edsl/questions/question_registry.py +4 -9
- edsl/questions/register_questions_meta.py +8 -4
- edsl/questions/response_validator_abc.py +17 -16
- edsl/results/__init__.py +4 -1
- edsl/{exceptions/results.py → results/exceptions.py} +1 -1
- edsl/results/report.py +197 -0
- edsl/results/{Result.py → result.py} +131 -45
- edsl/results/{Results.py → results.py} +365 -220
- edsl/results/results_selector.py +344 -25
- edsl/scenarios/__init__.py +30 -3
- edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
- edsl/scenarios/directory_scanner.py +156 -13
- edsl/scenarios/document_chunker.py +186 -0
- edsl/scenarios/exceptions.py +101 -0
- edsl/scenarios/file_methods.py +2 -3
- edsl/scenarios/{FileStore.py → file_store.py} +275 -189
- edsl/scenarios/handlers/__init__.py +14 -14
- edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
- edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
- edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
- edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
- edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
- edsl/scenarios/handlers/latex_file_store.py +5 -0
- edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
- edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
- edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
- edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
- edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
- edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
- edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
- edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
- edsl/scenarios/scenario.py +928 -0
- edsl/scenarios/scenario_join.py +18 -5
- edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
- edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
- edsl/scenarios/scenario_selector.py +5 -1
- edsl/study/ObjectEntry.py +2 -2
- edsl/study/SnapShot.py +5 -5
- edsl/study/Study.py +18 -19
- edsl/study/__init__.py +6 -4
- edsl/surveys/__init__.py +7 -4
- edsl/surveys/dag/__init__.py +2 -0
- edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
- edsl/surveys/{DAG.py → dag/dag.py} +13 -10
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
- edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
- edsl/surveys/memory/__init__.py +3 -0
- edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
- edsl/surveys/rules/__init__.py +3 -0
- edsl/surveys/{Rule.py → rules/rule.py} +103 -43
- edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
- edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
- edsl/surveys/survey.py +1743 -0
- edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
- edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
- edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
- edsl/tasks/__init__.py +32 -0
- edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
- edsl/tasks/task_creators.py +135 -0
- edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
- edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
- edsl/tasks/task_status_log.py +85 -0
- edsl/tokens/__init__.py +2 -0
- edsl/tokens/interview_token_usage.py +53 -0
- edsl/utilities/PrettyList.py +1 -1
- edsl/utilities/SystemInfo.py +25 -22
- edsl/utilities/__init__.py +29 -21
- edsl/utilities/gcp_bucket/__init__.py +2 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
- edsl/utilities/interface.py +44 -536
- edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
- edsl/utilities/repair_functions.py +1 -1
- {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/METADATA +1 -1
- edsl-0.1.48.dist-info/RECORD +347 -0
- edsl/Base.py +0 -493
- edsl/BaseDiff.py +0 -260
- edsl/agents/InvigilatorBase.py +0 -260
- edsl/agents/PromptConstructor.py +0 -318
- edsl/coop/PriceFetcher.py +0 -54
- edsl/data/Cache.py +0 -582
- edsl/data/CacheEntry.py +0 -238
- edsl/data/SQLiteDict.py +0 -292
- edsl/data/__init__.py +0 -5
- edsl/data/orm.py +0 -10
- edsl/exceptions/cache.py +0 -5
- edsl/exceptions/coop.py +0 -14
- edsl/exceptions/data.py +0 -14
- edsl/exceptions/scenarios.py +0 -29
- edsl/jobs/Answers.py +0 -43
- edsl/jobs/JobsPrompts.py +0 -354
- edsl/jobs/buckets/BucketCollection.py +0 -134
- edsl/jobs/buckets/ModelBuckets.py +0 -65
- edsl/jobs/buckets/TokenBucket.py +0 -283
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/interviews/Interview.py +0 -395
- edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
- edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
- edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
- edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
- edsl/jobs/tasks/TaskCreators.py +0 -64
- edsl/jobs/tasks/TaskStatusLog.py +0 -23
- edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
- edsl/language_models/LanguageModel.py +0 -635
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/models.py +0 -137
- edsl/questions/QuestionBase.py +0 -544
- edsl/questions/QuestionFreeText.py +0 -130
- edsl/questions/derived/QuestionLikertFive.py +0 -76
- edsl/results/ResultsExportMixin.py +0 -45
- edsl/results/TextEditor.py +0 -50
- edsl/results/results_fetch_mixin.py +0 -33
- edsl/results/results_tools_mixin.py +0 -98
- edsl/scenarios/DocumentChunker.py +0 -104
- edsl/scenarios/Scenario.py +0 -548
- edsl/scenarios/ScenarioHtmlMixin.py +0 -65
- edsl/scenarios/ScenarioListExportMixin.py +0 -45
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/shared.py +0 -1
- edsl/surveys/Survey.py +0 -1301
- edsl/surveys/SurveyQualtricsImport.py +0 -284
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/tools/__init__.py +0 -1
- edsl/tools/clusters.py +0 -192
- edsl/tools/embeddings.py +0 -27
- edsl/tools/embeddings_plotting.py +0 -118
- edsl/tools/plotting.py +0 -112
- edsl/tools/summarize.py +0 -18
- edsl/utilities/data/Registry.py +0 -6
- edsl/utilities/data/__init__.py +0 -1
- edsl/utilities/data/scooter_results.json +0 -1
- edsl-0.1.47.dist-info/RECORD +0 -354
- /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
- /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
- /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
- /edsl/{results → dataset/display}/table_data_class.py +0 -0
- /edsl/{results → dataset/display}/table_display.css +0 -0
- /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
- /edsl/{results → dataset}/tree_explore.py +0 -0
- /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
- /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
- /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
- /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
- /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
- /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
- /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
- /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
- /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
- /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
- /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
- /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
- /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
- /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
- /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
- {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
- {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
edsl/questions/__init__.py
CHANGED
@@ -1,28 +1,157 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from edsl.questions.register_questions_meta import RegisterQuestionsMeta
|
1
|
+
"""
|
2
|
+
EDSL Questions Module: The core system for creating and processing questions.
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
The questions module provides a comprehensive framework for creating, validating,
|
5
|
+
and processing various types of questions that can be asked to language models.
|
6
|
+
It is one of the foundational components of EDSL and enables the creation of
|
7
|
+
surveys, interviews, and other question-based interactions.
|
8
|
+
|
9
|
+
Key Features:
|
10
|
+
-------------
|
11
|
+
- A wide variety of question types including free text, multiple choice, checkbox, etc.
|
12
|
+
- Consistent interface for asking questions to language models
|
13
|
+
- Robust validation of responses
|
14
|
+
- Support for question templates and parameterization with scenarios
|
15
|
+
- Integration with the rest of the EDSL framework
|
16
|
+
- Extensible architecture for creating custom question types
|
17
|
+
|
18
|
+
Question Types:
|
19
|
+
--------------
|
20
|
+
Core Question Types:
|
21
|
+
- QuestionFreeText: Free-form text responses without constraints
|
22
|
+
- QuestionMultipleChoice: Selection from a predefined list of options
|
23
|
+
- QuestionCheckBox: Selection of multiple options from a predefined list
|
24
|
+
- QuestionNumerical: Numeric responses within an optional range
|
25
|
+
- QuestionList: Responses in the form of lists or arrays
|
26
|
+
- QuestionDict: Responses with key-value pairs
|
27
|
+
- QuestionMatrix: Grid-based responses with rows and columns
|
28
|
+
- QuestionBudget: Allocation of a budget across multiple options
|
29
|
+
- QuestionRank: Ordering of items by preference or other criteria
|
30
|
+
- QuestionExtract: Extraction of specific information from text or data
|
31
|
+
|
32
|
+
Derived Question Types:
|
33
|
+
- QuestionLikertFive: Standard 5-point Likert scale (agree/disagree)
|
34
|
+
- QuestionLinearScale: Linear scale with customizable range and labels
|
35
|
+
- QuestionYesNo: Simple binary yes/no response
|
36
|
+
- QuestionTopK: Selection of top K items from a list of options
|
37
|
+
|
38
|
+
Technical Architecture:
|
39
|
+
---------------------
|
40
|
+
1. Base Classes and Mixins:
|
41
|
+
- QuestionBase: Abstract base class for all question types
|
42
|
+
- SimpleAskMixin: Basic asking functionality to models and agents
|
43
|
+
- AnswerValidatorMixin: Validation of responses
|
44
|
+
- QuestionBasePromptsMixin: Template-based prompt generation
|
45
|
+
- QuestionBaseGenMixin: Integration with language models
|
46
|
+
|
47
|
+
2. Validation System:
|
48
|
+
- Response validators ensure answers conform to expected formats
|
49
|
+
- Pydantic models provide schema validation
|
50
|
+
- Repair functionality attempts to fix invalid responses
|
51
|
+
|
52
|
+
3. Template System:
|
53
|
+
- Jinja2 templates for consistent prompt generation
|
54
|
+
- Separate templates for answering instructions and question presentation
|
55
|
+
- Support for dynamic content through scenario variables
|
56
|
+
|
57
|
+
4. Registry System:
|
58
|
+
- RegisterQuestionsMeta metaclass for automatic registration
|
59
|
+
- Question types are automatically available for serialization
|
60
|
+
- Registry enables runtime lookup of question types
|
61
|
+
|
62
|
+
Example Usage:
|
63
|
+
-------------
|
64
|
+
>>> from edsl import QuestionFreeText
|
65
|
+
>>> question = QuestionFreeText(
|
66
|
+
... question_name="greeting",
|
67
|
+
... question_text="Say hello to the user."
|
68
|
+
... )
|
69
|
+
>>> from edsl.language_models import Model
|
70
|
+
>>> model = Model()
|
71
|
+
>>> # result = question.by(model).run()
|
72
|
+
|
73
|
+
>>> from edsl import QuestionMultipleChoice
|
74
|
+
>>> choice_q = QuestionMultipleChoice(
|
75
|
+
... question_name="preference",
|
76
|
+
... question_text="Which color do you prefer?",
|
77
|
+
... question_options=["Red", "Blue", "Green", "Yellow"]
|
78
|
+
... )
|
79
|
+
>>> # result = choice_q.by(model).run()
|
80
|
+
|
81
|
+
Integration with Surveys:
|
82
|
+
-----------------------
|
83
|
+
Questions can be combined into surveys for more complex interactions:
|
84
|
+
|
85
|
+
# Note: Actual survey usage in code
|
86
|
+
# from edsl import Survey
|
87
|
+
# survey = Survey()
|
88
|
+
# survey.add_question(question)
|
89
|
+
# survey.add_question(choice_q)
|
90
|
+
# results = survey.by(model).run()
|
91
|
+
|
92
|
+
Extension Points:
|
93
|
+
---------------
|
94
|
+
The questions module is designed to be extensible:
|
95
|
+
- Create custom question types by subclassing QuestionBase
|
96
|
+
- Implement custom validators for specialized validation
|
97
|
+
- Define custom templates for unique presentation needs
|
98
|
+
- Combine questions in surveys with custom flow logic
|
99
|
+
"""
|
100
|
+
|
101
|
+
# Schemas and metadata
|
102
|
+
from .settings import Settings
|
103
|
+
from .register_questions_meta import RegisterQuestionsMeta
|
104
|
+
|
105
|
+
# Base Class and registry
|
106
|
+
from .question_base import QuestionBase
|
107
|
+
from .question_registry import Question
|
7
108
|
|
8
109
|
# Core Questions
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
|
21
|
-
#
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
25
|
-
from
|
26
|
-
|
27
|
-
|
28
|
-
|
110
|
+
from .question_check_box import QuestionCheckBox
|
111
|
+
from .question_extract import QuestionExtract
|
112
|
+
from .question_free_text import QuestionFreeText
|
113
|
+
from .question_functional import QuestionFunctional
|
114
|
+
from .question_list import QuestionList
|
115
|
+
from .question_matrix import QuestionMatrix
|
116
|
+
from .question_dict import QuestionDict
|
117
|
+
from .question_multiple_choice import QuestionMultipleChoice
|
118
|
+
from .question_numerical import QuestionNumerical
|
119
|
+
from .question_budget import QuestionBudget
|
120
|
+
from .question_rank import QuestionRank
|
121
|
+
|
122
|
+
# Questions derived from core questions
|
123
|
+
from .derived.question_likert_five import QuestionLikertFive
|
124
|
+
from .derived.question_linear_scale import QuestionLinearScale
|
125
|
+
from .derived.question_yes_no import QuestionYesNo
|
126
|
+
from .derived.question_top_k import QuestionTopK
|
127
|
+
|
128
|
+
from .exceptions import QuestionScenarioRenderError
|
129
|
+
|
130
|
+
__all__ = [
|
131
|
+
# Schema and metadata
|
132
|
+
"Settings",
|
133
|
+
"RegisterQuestionsMeta",
|
134
|
+
|
135
|
+
# Base question class and registry
|
136
|
+
"QuestionBase",
|
137
|
+
"Question",
|
138
|
+
|
139
|
+
# Core question types
|
140
|
+
"QuestionFreeText",
|
141
|
+
"QuestionMultipleChoice",
|
142
|
+
"QuestionCheckBox",
|
143
|
+
"QuestionDict",
|
144
|
+
"QuestionExtract",
|
145
|
+
"QuestionFunctional",
|
146
|
+
"QuestionList",
|
147
|
+
"QuestionMatrix",
|
148
|
+
"QuestionNumerical",
|
149
|
+
"QuestionBudget",
|
150
|
+
"QuestionRank",
|
151
|
+
|
152
|
+
# Derived question types
|
153
|
+
"QuestionLinearScale",
|
154
|
+
"QuestionTopK",
|
155
|
+
"QuestionLikertFive",
|
156
|
+
"QuestionYesNo",
|
157
|
+
]
|
@@ -1,8 +1,9 @@
|
|
1
1
|
"""Compose two questions where the answer to q1 is used as an input to q2."""
|
2
2
|
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
3
|
+
from .question_functional import QuestionFunctional
|
4
|
+
from .question_base import QuestionBase
|
5
|
+
from ..scenarios import Scenario
|
6
|
+
from ..agents import Agent
|
6
7
|
|
7
8
|
|
8
9
|
def compose_questions(
|
@@ -0,0 +1,166 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional
|
3
|
+
from ..question_multiple_choice import QuestionMultipleChoice
|
4
|
+
from ..decorators import inject_exception
|
5
|
+
|
6
|
+
|
7
|
+
class QuestionLikertFive(QuestionMultipleChoice):
|
8
|
+
"""
|
9
|
+
A specialized question that prompts the agent to respond on a 5-point Likert scale.
|
10
|
+
|
11
|
+
QuestionLikertFive is a subclass of QuestionMultipleChoice that presents a standard
|
12
|
+
5-point Likert scale ranging from "Strongly disagree" to "Strongly agree". This
|
13
|
+
question type is ideal for measuring attitudes, opinions, and perceptions on a
|
14
|
+
symmetric agree-disagree scale.
|
15
|
+
|
16
|
+
Key Features:
|
17
|
+
- Pre-configured with standard Likert scale options
|
18
|
+
- Simplifies creating consistent Likert scale questions
|
19
|
+
- Inherits all functionality from QuestionMultipleChoice
|
20
|
+
- Can be customized with different options if needed
|
21
|
+
|
22
|
+
Technical Details:
|
23
|
+
- Uses the standard Likert options: ["Strongly disagree", "Disagree", "Neutral", "Agree", "Strongly agree"]
|
24
|
+
- Implements `use_code=False` by default, so responses contain the text labels
|
25
|
+
- Can be connected to language models and agents like any other question
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
Basic usage:
|
29
|
+
|
30
|
+
```python
|
31
|
+
q = QuestionLikertFive(
|
32
|
+
question_name="climate_concern",
|
33
|
+
question_text="I am concerned about climate change."
|
34
|
+
)
|
35
|
+
```
|
36
|
+
|
37
|
+
With custom options:
|
38
|
+
|
39
|
+
```python
|
40
|
+
q = QuestionLikertFive(
|
41
|
+
question_name="satisfaction",
|
42
|
+
question_text="I am satisfied with the product.",
|
43
|
+
question_options=["Strongly Disagree", "Somewhat Disagree",
|
44
|
+
"Neither Agree nor Disagree",
|
45
|
+
"Somewhat Agree", "Strongly Agree"]
|
46
|
+
)
|
47
|
+
```
|
48
|
+
|
49
|
+
Notes:
|
50
|
+
- Likert scales are particularly useful for surveys and opinion research
|
51
|
+
- The default options follow the most common 5-point Likert scale format
|
52
|
+
- For different scales (e.g., 7-point), you can pass custom options
|
53
|
+
"""
|
54
|
+
|
55
|
+
question_type = "likert_five"
|
56
|
+
likert_options: list[str] = [
|
57
|
+
"Strongly disagree",
|
58
|
+
"Disagree",
|
59
|
+
"Neutral",
|
60
|
+
"Agree",
|
61
|
+
"Strongly agree",
|
62
|
+
]
|
63
|
+
|
64
|
+
def __init__(
|
65
|
+
self,
|
66
|
+
question_name: str,
|
67
|
+
question_text: str,
|
68
|
+
question_options: Optional[list[str]] = None,
|
69
|
+
answering_instructions: Optional[str] = None,
|
70
|
+
question_presentation: Optional[str] = None,
|
71
|
+
include_comment: bool = True,
|
72
|
+
):
|
73
|
+
"""
|
74
|
+
Initialize a new 5-point Likert scale question.
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
question_name : str
|
79
|
+
The name of the question, used as an identifier. Must be a valid Python variable name.
|
80
|
+
This name will be used in results, templates, and when referencing the question in surveys.
|
81
|
+
|
82
|
+
question_text : str
|
83
|
+
The statement to which the agent will respond on the Likert scale. This is typically
|
84
|
+
phrased as a statement rather than a question, e.g., "I enjoy using this product."
|
85
|
+
|
86
|
+
question_options : Optional[list[str]], default=None
|
87
|
+
Optional custom Likert scale options. If None, the default 5-point Likert scale options
|
88
|
+
are used: ["Strongly disagree", "Disagree", "Neutral", "Agree", "Strongly agree"].
|
89
|
+
Custom options should follow the same format (typically 5 or 7 points from negative to positive).
|
90
|
+
|
91
|
+
answering_instructions : Optional[str], default=None
|
92
|
+
Custom instructions for how the model should answer the question. If None,
|
93
|
+
default instructions for Likert scale questions will be used.
|
94
|
+
|
95
|
+
question_presentation : Optional[str], default=None
|
96
|
+
Custom template for how the question is presented to the model. If None,
|
97
|
+
the default presentation for Likert scale questions will be used.
|
98
|
+
|
99
|
+
include_comment : bool, default=True
|
100
|
+
Whether to include a comment field in the response, allowing the model to provide
|
101
|
+
additional explanation beyond just selecting an option on the scale.
|
102
|
+
|
103
|
+
Examples
|
104
|
+
--------
|
105
|
+
>>> q = QuestionLikertFive(
|
106
|
+
... question_name="product_satisfaction",
|
107
|
+
... question_text="I am satisfied with the product."
|
108
|
+
... )
|
109
|
+
|
110
|
+
>>> q_custom = QuestionLikertFive(
|
111
|
+
... question_name="service_quality",
|
112
|
+
... question_text="The service quality was excellent.",
|
113
|
+
... question_options=["Completely Disagree", "Somewhat Disagree",
|
114
|
+
... "Neutral", "Somewhat Agree", "Completely Agree"]
|
115
|
+
... )
|
116
|
+
|
117
|
+
Notes
|
118
|
+
-----
|
119
|
+
- The default Likert options can be accessed via QuestionLikertFive.likert_options
|
120
|
+
- Likert scale questions inherently use text labels rather than numeric codes
|
121
|
+
- The statement should be phrased such that agreeing or disagreeing makes sense
|
122
|
+
"""
|
123
|
+
# Use default Likert options if none are provided
|
124
|
+
if question_options is None:
|
125
|
+
question_options = self.likert_options
|
126
|
+
super().__init__(
|
127
|
+
question_name=question_name,
|
128
|
+
question_text=question_text,
|
129
|
+
question_options=question_options,
|
130
|
+
use_code=False,
|
131
|
+
include_comment=include_comment,
|
132
|
+
answering_instructions=answering_instructions,
|
133
|
+
question_presentation=question_presentation,
|
134
|
+
)
|
135
|
+
|
136
|
+
@classmethod
|
137
|
+
@inject_exception
|
138
|
+
def example(cls) -> QuestionLikertFive:
|
139
|
+
"""Return an example question."""
|
140
|
+
return cls(
|
141
|
+
question_name="happy_raining",
|
142
|
+
question_text="I'm only happy when it rains.",
|
143
|
+
)
|
144
|
+
|
145
|
+
|
146
|
+
def main():
|
147
|
+
"""Test QuestionLikertFive."""
|
148
|
+
# Use the class directly since we're already in the module
|
149
|
+
q = QuestionLikertFive.example()
|
150
|
+
q.question_text
|
151
|
+
q.question_options
|
152
|
+
q.question_name
|
153
|
+
# validate an answer
|
154
|
+
q._validate_answer({"answer": 0, "comment": "I like custard"})
|
155
|
+
# translate answer code
|
156
|
+
q._translate_answer_code_to_answer(0, {})
|
157
|
+
q._simulate_answer()
|
158
|
+
q._simulate_answer(human_readable=False)
|
159
|
+
q._validate_answer(q._simulate_answer(human_readable=False))
|
160
|
+
# serialization (inherits from Question)
|
161
|
+
q.to_dict()
|
162
|
+
assert q.from_dict(q.to_dict()) == q
|
163
|
+
|
164
|
+
import doctest
|
165
|
+
|
166
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Optional
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
4
|
+
from ..descriptors import QuestionOptionsDescriptor, OptionLabelDescriptor
|
5
|
+
from ..question_multiple_choice import QuestionMultipleChoice
|
6
6
|
|
7
|
-
from
|
7
|
+
from ..decorators import inject_exception
|
8
8
|
|
9
9
|
|
10
10
|
class QuestionLinearScale(QuestionMultipleChoice):
|
@@ -67,7 +67,7 @@ class QuestionLinearScale(QuestionMultipleChoice):
|
|
67
67
|
|
68
68
|
def main():
|
69
69
|
"""Create an example of a linear scale question and demonstrate its functionality."""
|
70
|
-
from edsl.questions
|
70
|
+
from edsl.questions import QuestionLinearScale
|
71
71
|
|
72
72
|
q = QuestionLinearScale.example()
|
73
73
|
q.question_text
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Optional
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
4
|
+
from ..exceptions import QuestionCreationValidationError
|
5
|
+
from ..question_check_box import QuestionCheckBox
|
6
|
+
from ..decorators import inject_exception
|
7
7
|
|
8
8
|
|
9
9
|
class QuestionTopK(QuestionCheckBox):
|
@@ -70,7 +70,7 @@ class QuestionTopK(QuestionCheckBox):
|
|
70
70
|
|
71
71
|
def main():
|
72
72
|
"""Test QuestionTopK."""
|
73
|
-
from edsl.questions
|
73
|
+
from edsl.questions import QuestionTopK
|
74
74
|
|
75
75
|
q = QuestionTopK.example()
|
76
76
|
q.question_text
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Optional
|
3
|
-
from
|
4
|
-
from
|
3
|
+
from ..descriptors import QuestionOptionsDescriptor
|
4
|
+
from ..question_multiple_choice import QuestionMultipleChoice
|
5
5
|
|
6
|
-
from
|
6
|
+
from ..decorators import inject_exception
|
7
7
|
|
8
8
|
|
9
9
|
class QuestionYesNo(QuestionMultipleChoice):
|
@@ -54,8 +54,7 @@ class QuestionYesNo(QuestionMultipleChoice):
|
|
54
54
|
|
55
55
|
def main():
|
56
56
|
"""Create an example of a yes/no question and demonstrate its functionality."""
|
57
|
-
|
58
|
-
|
57
|
+
# Use the class directly since we're already in the module
|
59
58
|
q = QuestionYesNo.example()
|
60
59
|
q.question_text
|
61
60
|
q.question_options
|
edsl/questions/descriptors.py
CHANGED
@@ -2,14 +2,12 @@
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
import re
|
5
|
-
import textwrap
|
6
5
|
from typing import Any, Callable, List, Optional
|
7
|
-
from
|
6
|
+
from .exceptions import (
|
8
7
|
QuestionCreationValidationError,
|
9
8
|
QuestionAnswerValidationError,
|
10
9
|
)
|
11
|
-
from
|
12
|
-
|
10
|
+
from .settings import Settings
|
13
11
|
|
14
12
|
################################
|
15
13
|
# Helper functions
|
@@ -262,7 +260,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
262
260
|
>>> _ = q_class(["a", "b", "c", "d", "d"])
|
263
261
|
Traceback (most recent call last):
|
264
262
|
...
|
265
|
-
edsl.exceptions.
|
263
|
+
edsl.questions.exceptions.QuestionCreationValidationError: Question options must be unique (got ['a', 'b', 'c', 'd', 'd']).
|
266
264
|
|
267
265
|
We allow dynamic question options, which are strings of the form '{{ question_options }}'.
|
268
266
|
|
@@ -270,7 +268,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
270
268
|
>>> _ = q_class("dynamic_options")
|
271
269
|
Traceback (most recent call last):
|
272
270
|
...
|
273
|
-
edsl.exceptions.
|
271
|
+
edsl.questions.exceptions.QuestionCreationValidationError: ...
|
274
272
|
"""
|
275
273
|
|
276
274
|
@classmethod
|
@@ -391,10 +389,6 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
391
389
|
|
392
390
|
>>> _ = TestQuestion("What is the capital of France?")
|
393
391
|
>>> _ = TestQuestion("What is the capital of France? {{variable}}")
|
394
|
-
>>> _ = TestQuestion("What is the capital of France? {{variable name}}")
|
395
|
-
Traceback (most recent call last):
|
396
|
-
...
|
397
|
-
edsl.exceptions.questions.QuestionCreationValidationError: Question text contains an invalid identifier: 'variable name'
|
398
392
|
"""
|
399
393
|
|
400
394
|
def validate(self, value, instance):
|
@@ -408,21 +402,21 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
408
402
|
|
409
403
|
#value = textwrap.dedent(value).strip()
|
410
404
|
|
411
|
-
if contains_single_braced_substring(value):
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
405
|
+
# if contains_single_braced_substring(value):
|
406
|
+
# import warnings
|
407
|
+
|
408
|
+
# # # warnings.warn(
|
409
|
+
# # # 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",
|
410
|
+
# # # UserWarning,
|
411
|
+
# # # )
|
412
|
+
# warnings.warn(
|
413
|
+
# "WARNING: Question text contains a single-braced substring. "
|
414
|
+
# "If you intended to parameterize the question with a Scenario, this will "
|
415
|
+
# "be changed to a double-braced substring, e.g. {{variable}}.\n"
|
416
|
+
# "See details on constructing Scenarios in the docs: "
|
417
|
+
# "https://docs.expectedparrot.com/en/latest/scenarios.html",
|
418
|
+
# UserWarning,
|
419
|
+
# )
|
426
420
|
# Automatically replace single braces with double braces
|
427
421
|
# This is here because if the user is using an f-string, the double brace will get converted to a single brace.
|
428
422
|
# This undoes that.
|
@@ -430,11 +424,11 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
430
424
|
return value
|
431
425
|
|
432
426
|
# iterate through all doubles braces and check if they are valid python identifiers
|
433
|
-
for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
427
|
+
# for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
|
428
|
+
# if " " in match.group(1).strip():
|
429
|
+
# raise QuestionCreationValidationError(
|
430
|
+
# f"Question text contains an invalid identifier: '{match.group(1)}'"
|
431
|
+
# )
|
438
432
|
|
439
433
|
return None
|
440
434
|
|
edsl/questions/loop_processor.py
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
from typing import List, Any, Dict, Union
|
2
|
-
from jinja2 import Environment
|
3
|
-
from
|
4
|
-
from
|
5
|
-
|
2
|
+
from jinja2 import Environment, Undefined
|
3
|
+
from .question_base import QuestionBase
|
4
|
+
from ..scenarios import ScenarioList
|
6
5
|
|
7
6
|
class LoopProcessor:
|
8
7
|
def __init__(self, question: QuestionBase):
|
9
8
|
self.question = question
|
10
|
-
self.env = Environment()
|
9
|
+
self.env = Environment(undefined=Undefined)
|
11
10
|
|
12
11
|
def process_templates(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
13
12
|
"""Process templates for each scenario and return list of modified questions.
|
@@ -46,8 +45,11 @@ class LoopProcessor:
|
|
46
45
|
"""
|
47
46
|
processed = {}
|
48
47
|
|
48
|
+
extended_scenario = scenario.copy()
|
49
|
+
extended_scenario.update({"scenario": scenario})
|
50
|
+
|
49
51
|
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,
|
52
|
+
processed[key] = self._process_value(key, value, extended_scenario)
|
51
53
|
|
52
54
|
return processed
|
53
55
|
|
@@ -96,9 +98,61 @@ class LoopProcessor:
|
|
96
98
|
scenario: Current scenario
|
97
99
|
|
98
100
|
Returns:
|
99
|
-
Rendered template string
|
101
|
+
Rendered template string, preserving any unmatched template variables
|
102
|
+
|
103
|
+
Examples:
|
104
|
+
>>> from edsl.questions import QuestionBase
|
105
|
+
>>> q = QuestionBase()
|
106
|
+
>>> q.question_text = "test"
|
107
|
+
>>> p = LoopProcessor(q)
|
108
|
+
>>> p._render_template("Hello {{name}}!", {"name": "World"})
|
109
|
+
'Hello World!'
|
110
|
+
|
111
|
+
>>> p._render_template("{{a}} and {{b}}", {"b": 6})
|
112
|
+
'{{ a }} and 6'
|
113
|
+
|
114
|
+
>>> p._render_template("{{x}} + {{y}} = {{z}}", {"x": 2, "y": 3})
|
115
|
+
'2 + 3 = {{ z }}'
|
116
|
+
|
117
|
+
>>> p._render_template("No variables here", {})
|
118
|
+
'No variables here'
|
119
|
+
|
120
|
+
>>> p._render_template("{{item.price}}", {"item": {"price": 9.99}})
|
121
|
+
'9.99'
|
122
|
+
|
123
|
+
>>> p._render_template("{{item.missing}}", {"item": {"price": 9.99}})
|
124
|
+
'{{ item.missing }}'
|
100
125
|
"""
|
101
|
-
|
126
|
+
import re
|
127
|
+
|
128
|
+
# Regular expression to find Jinja2 variables in the template
|
129
|
+
pattern = r'(?P<open>\{\{\s*)(?P<var>[a-zA-Z0-9_.]+)(?P<close>\s*\}\})'
|
130
|
+
|
131
|
+
def replace_var(match):
|
132
|
+
var_name = match.group('var')
|
133
|
+
open_brace = match.group('open')
|
134
|
+
close_brace = match.group('close')
|
135
|
+
|
136
|
+
# Try to evaluate the variable in the context
|
137
|
+
try:
|
138
|
+
# Handle nested attributes (like item.price)
|
139
|
+
parts = var_name.split('.')
|
140
|
+
value = scenario
|
141
|
+
for part in parts:
|
142
|
+
if part in value:
|
143
|
+
value = value[part]
|
144
|
+
else:
|
145
|
+
# If any part doesn't exist, return the original with spacing
|
146
|
+
return f"{{ {var_name} }}".replace("{", "{{").replace("}", "}}")
|
147
|
+
# Return the rendered value if successful
|
148
|
+
return str(value)
|
149
|
+
except (KeyError, TypeError):
|
150
|
+
# Return the original variable name with the expected spacing
|
151
|
+
return f"{{ {var_name} }}".replace("{", "{{").replace("}", "}}")
|
152
|
+
|
153
|
+
# Replace all variables in the template
|
154
|
+
result = re.sub(pattern, replace_var, template)
|
155
|
+
return result
|
102
156
|
|
103
157
|
def _process_list(self, items: List[Any], scenario: Dict[str, Any]) -> List[Any]:
|
104
158
|
"""Process all items in a list.
|
@@ -135,15 +189,7 @@ class LoopProcessor:
|
|
135
189
|
}
|
136
190
|
|
137
191
|
|
138
|
-
|
139
|
-
|
140
|
-
from edsl import QuestionFreeText, ScenarioList
|
192
|
+
if __name__ == "__main__":
|
193
|
+
import doctest
|
141
194
|
|
142
|
-
|
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
|
-
"""
|
195
|
+
doctest.testmod()
|