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
@@ -0,0 +1,221 @@
|
|
1
|
+
from importlib import resources
|
2
|
+
from typing import Optional
|
3
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
4
|
+
from functools import lru_cache
|
5
|
+
|
6
|
+
|
7
|
+
class TemplateManager:
|
8
|
+
_instance = None
|
9
|
+
|
10
|
+
def __new__(cls):
|
11
|
+
if cls._instance is None:
|
12
|
+
cls._instance = super().__new__(cls)
|
13
|
+
cls._instance._template_cache = {}
|
14
|
+
return cls._instance
|
15
|
+
|
16
|
+
@lru_cache(maxsize=None)
|
17
|
+
def get_template(self, question_type, template_name):
|
18
|
+
if (question_type, template_name) not in self._template_cache:
|
19
|
+
with resources.open_text(
|
20
|
+
f"edsl.questions.templates.{question_type}", template_name
|
21
|
+
) as file:
|
22
|
+
self._template_cache[(question_type, template_name)] = file.read()
|
23
|
+
return self._template_cache[(question_type, template_name)]
|
24
|
+
|
25
|
+
|
26
|
+
# Global instance
|
27
|
+
template_manager = TemplateManager()
|
28
|
+
|
29
|
+
|
30
|
+
class QuestionBasePromptsMixin:
|
31
|
+
@property
|
32
|
+
def model_instructions(self) -> dict:
|
33
|
+
"""Get the model-specific instructions for the question."""
|
34
|
+
if not hasattr(self, "_model_instructions"):
|
35
|
+
self._model_instructions = {}
|
36
|
+
return self._model_instructions
|
37
|
+
|
38
|
+
def _all_text(self) -> str:
|
39
|
+
"""Return the question text.
|
40
|
+
|
41
|
+
>>> from edsl import QuestionMultipleChoice as Q
|
42
|
+
>>> Q.example()._all_text()
|
43
|
+
"how_feelingHow are you?['Good', 'Great', 'OK', 'Bad']"
|
44
|
+
"""
|
45
|
+
txt = ""
|
46
|
+
for key, value in self.data.items():
|
47
|
+
if isinstance(value, str):
|
48
|
+
txt += value
|
49
|
+
elif isinstance(value, list):
|
50
|
+
txt += "".join(str(value))
|
51
|
+
return txt
|
52
|
+
|
53
|
+
@model_instructions.setter
|
54
|
+
def model_instructions(self, data: dict):
|
55
|
+
"""Set the model-specific instructions for the question."""
|
56
|
+
self._model_instructions = data
|
57
|
+
|
58
|
+
def add_model_instructions(
|
59
|
+
self, *, instructions: str, model: Optional[str] = None
|
60
|
+
) -> None:
|
61
|
+
"""Add model-specific instructions for the question that override the default instructions.
|
62
|
+
|
63
|
+
:param instructions: The instructions to add. This is typically a jinja2 template.
|
64
|
+
:param model: The language model for this instruction.
|
65
|
+
|
66
|
+
>>> from edsl.questions import QuestionFreeText
|
67
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
68
|
+
>>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
|
69
|
+
>>> q.get_instructions(model = "gpt3")
|
70
|
+
Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
|
71
|
+
"""
|
72
|
+
from edsl.language_models.model import Model
|
73
|
+
|
74
|
+
if not hasattr(self, "_model_instructions"):
|
75
|
+
self._model_instructions = {}
|
76
|
+
if model is None:
|
77
|
+
# if not model is passed, all the models are mapped to this instruction, including 'None'
|
78
|
+
self._model_instructions = {
|
79
|
+
model_name: instructions
|
80
|
+
for model_name in Model.available(name_only=True)
|
81
|
+
}
|
82
|
+
self._model_instructions.update({model: instructions})
|
83
|
+
else:
|
84
|
+
self._model_instructions.update({model: instructions})
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def path_to_folder(cls) -> str:
|
88
|
+
return resources.files(f"edsl.questions.templates", cls.question_type)
|
89
|
+
|
90
|
+
@property
|
91
|
+
def response_model(self) -> type["BaseModel"]:
|
92
|
+
if self._response_model is not None:
|
93
|
+
return self._response_model
|
94
|
+
else:
|
95
|
+
return self.create_response_model()
|
96
|
+
|
97
|
+
@property
|
98
|
+
def use_code(self) -> bool:
|
99
|
+
if hasattr(self, "_use_code"):
|
100
|
+
return self._use_code
|
101
|
+
return True
|
102
|
+
|
103
|
+
@use_code.setter
|
104
|
+
def use_code(self, value: bool) -> None:
|
105
|
+
self._use_code = value
|
106
|
+
|
107
|
+
@property
|
108
|
+
def include_comment(self) -> bool:
|
109
|
+
if hasattr(self, "_include_comment"):
|
110
|
+
return self._include_comment
|
111
|
+
return True
|
112
|
+
|
113
|
+
@include_comment.setter
|
114
|
+
def include_comment(self, value: bool) -> None:
|
115
|
+
self._include_comment = value
|
116
|
+
|
117
|
+
@classmethod
|
118
|
+
def default_answering_instructions(cls) -> str:
|
119
|
+
# template_text = cls._read_template("answering_instructions.jinja")
|
120
|
+
template_text = template_manager.get_template(
|
121
|
+
cls.question_type, "answering_instructions.jinja"
|
122
|
+
)
|
123
|
+
from edsl.prompts import Prompt
|
124
|
+
|
125
|
+
return Prompt(text=template_text)
|
126
|
+
|
127
|
+
@classmethod
|
128
|
+
def default_question_presentation(cls):
|
129
|
+
template_text = template_manager.get_template(
|
130
|
+
cls.question_type, "question_presentation.jinja"
|
131
|
+
)
|
132
|
+
from edsl.prompts import Prompt
|
133
|
+
|
134
|
+
return Prompt(text=template_text)
|
135
|
+
|
136
|
+
@property
|
137
|
+
def answering_instructions(self) -> str:
|
138
|
+
if self._answering_instructions is None:
|
139
|
+
return self.default_answering_instructions()
|
140
|
+
return self._answering_instructions
|
141
|
+
|
142
|
+
@answering_instructions.setter
|
143
|
+
def answering_instructions(self, value) -> None:
|
144
|
+
self._answering_instructions = value
|
145
|
+
|
146
|
+
@property
|
147
|
+
def question_presentation(self):
|
148
|
+
if self._question_presentation is None:
|
149
|
+
return self.default_question_presentation()
|
150
|
+
return self._question_presentation
|
151
|
+
|
152
|
+
@question_presentation.setter
|
153
|
+
def question_presentation(self, value):
|
154
|
+
self._question_presentation = value
|
155
|
+
|
156
|
+
def prompt_preview(self, scenario=None, agent=None):
|
157
|
+
return self.new_default_instructions.render(
|
158
|
+
self.data
|
159
|
+
| {
|
160
|
+
"include_comment": getattr(self, "_include_comment", True),
|
161
|
+
"use_code": getattr(self, "_use_code", True),
|
162
|
+
}
|
163
|
+
| ({"scenario": scenario} or {})
|
164
|
+
| ({"agent": agent} or {})
|
165
|
+
)
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
def self_check(cls):
|
169
|
+
q = cls.example()
|
170
|
+
for answer, params in q.response_validator.valid_examples:
|
171
|
+
for key, value in params.items():
|
172
|
+
setattr(q, key, value)
|
173
|
+
q._validate_answer(answer)
|
174
|
+
for answer, params, reason in q.response_validator.invalid_examples:
|
175
|
+
for key, value in params.items():
|
176
|
+
setattr(q, key, value)
|
177
|
+
try:
|
178
|
+
q._validate_answer(answer)
|
179
|
+
except QuestionAnswerValidationError:
|
180
|
+
pass
|
181
|
+
else:
|
182
|
+
raise ValueError(f"Example {answer} should have failed for {reason}.")
|
183
|
+
|
184
|
+
@property
|
185
|
+
def new_default_instructions(self) -> "Prompt":
|
186
|
+
"This is set up as a property because there are mutable question values that determine how it is rendered."
|
187
|
+
from edsl.prompts import Prompt
|
188
|
+
|
189
|
+
return Prompt(self.question_presentation) + Prompt(self.answering_instructions)
|
190
|
+
|
191
|
+
@property
|
192
|
+
def parameters(self) -> set[str]:
|
193
|
+
"""Return the parameters of the question."""
|
194
|
+
from jinja2 import Environment, meta
|
195
|
+
|
196
|
+
env = Environment()
|
197
|
+
# Parse the template
|
198
|
+
txt = self._all_text()
|
199
|
+
# txt = self.question_text
|
200
|
+
# if hasattr(self, "question_options"):
|
201
|
+
# txt += " ".join(self.question_options)
|
202
|
+
parsed_content = env.parse(txt)
|
203
|
+
# Extract undeclared variables
|
204
|
+
variables = meta.find_undeclared_variables(parsed_content)
|
205
|
+
# Return as a list
|
206
|
+
return set(variables)
|
207
|
+
|
208
|
+
def get_instructions(self, model: Optional[str] = None) -> type["PromptBase"]:
|
209
|
+
"""Get the mathcing question-answering instructions for the question.
|
210
|
+
|
211
|
+
:param model: The language model to use.
|
212
|
+
"""
|
213
|
+
from edsl.prompts.Prompt import Prompt
|
214
|
+
|
215
|
+
if model in self.model_instructions:
|
216
|
+
return Prompt(text=self.model_instructions[model])
|
217
|
+
else:
|
218
|
+
if hasattr(self, "new_default_instructions"):
|
219
|
+
return self.new_default_instructions
|
220
|
+
else:
|
221
|
+
return self.applicable_prompts(model)[0]()
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -1,33 +1,70 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import
|
3
|
-
|
4
|
-
from
|
5
|
-
|
2
|
+
from typing import Any, Optional, Union, List
|
3
|
+
|
4
|
+
from pydantic import Field, BaseModel, validator
|
5
|
+
|
6
|
+
from edsl.questions.QuestionBase import QuestionBase
|
6
7
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
7
|
-
from edsl.
|
8
|
-
|
8
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
9
|
+
|
10
|
+
|
11
|
+
class BudgetResponseValidator(ResponseValidatorABC):
|
12
|
+
valid_examples = []
|
13
|
+
|
14
|
+
invalid_examples = []
|
15
|
+
|
16
|
+
def fix(self, response, verbose=False):
|
17
|
+
if verbose:
|
18
|
+
print(f"Fixing list response: {response}")
|
19
|
+
answer = str(response.get("answer") or response.get("generated_tokens", ""))
|
20
|
+
if len(answer.split(",")) > 0:
|
21
|
+
return (
|
22
|
+
{"answer": answer.split(",")} | {"comment": response.get("comment")}
|
23
|
+
if "comment" in response
|
24
|
+
else {}
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def create_budget_model(
|
29
|
+
budget_sum: float, permissive: bool, question_options: List[str]
|
30
|
+
):
|
31
|
+
class BudgetResponse(BaseModel):
|
32
|
+
answer: List[float] = Field(
|
33
|
+
...,
|
34
|
+
description="List of non-negative numbers representing budget allocation",
|
35
|
+
min_items=len(question_options),
|
36
|
+
max_items=len(question_options),
|
37
|
+
)
|
38
|
+
comment: Optional[str] = None
|
39
|
+
generated_tokens: Optional[str] = None
|
9
40
|
|
41
|
+
@validator("answer")
|
42
|
+
def validate_answer(cls, v):
|
43
|
+
if len(v) != len(question_options):
|
44
|
+
raise ValueError(f"Must provide {len(question_options)} values")
|
45
|
+
if any(x < 0 for x in v):
|
46
|
+
raise ValueError("All values must be non-negative")
|
47
|
+
total = sum(v)
|
48
|
+
if not permissive and total != budget_sum:
|
49
|
+
raise ValueError(f"Sum of numbers must equal {budget_sum}")
|
50
|
+
elif permissive and total > budget_sum:
|
51
|
+
raise ValueError(f"Sum of numbers cannot exceed {budget_sum}")
|
52
|
+
return v
|
10
53
|
|
11
|
-
class
|
12
|
-
|
13
|
-
This question asks the respondent to allocate a budget among options.
|
54
|
+
class Config:
|
55
|
+
extra = "forbid"
|
14
56
|
|
15
|
-
|
16
|
-
- `budget_sum` is the total amount of the budget to be allocated (positive integer)
|
17
|
-
- `question_name` is the name of the question (string)
|
18
|
-
- `question_options` are the options the user should allocated the budget to (list of strings)
|
19
|
-
- `question_text` is the text of the question (string)
|
57
|
+
return BudgetResponse
|
20
58
|
|
21
|
-
Optional arguments:
|
22
|
-
- `instructions` are the instructions for the question (string). If not provided, the default instructions are used. To view them, run `QuestionBudget.default_instructions`
|
23
|
-
- `short_names_dict` maps question_options to short names (dictionary mapping strings to strings)
|
24
59
|
|
25
|
-
|
26
|
-
"""
|
60
|
+
class QuestionBudget(QuestionBase):
|
61
|
+
"""This question prompts the agent to allocate a budget among options."""
|
27
62
|
|
28
63
|
question_type = "budget"
|
29
64
|
budget_sum: int = IntegerDescriptor(none_allowed=False)
|
30
|
-
question_options: list[str] = QuestionOptionsDescriptor()
|
65
|
+
question_options: list[str] = QuestionOptionsDescriptor(q_budget=True)
|
66
|
+
_response_model = None
|
67
|
+
response_validator_class = BudgetResponseValidator
|
31
68
|
|
32
69
|
def __init__(
|
33
70
|
self,
|
@@ -35,96 +72,156 @@ class QuestionBudget(Question):
|
|
35
72
|
question_text: str,
|
36
73
|
question_options: list[str],
|
37
74
|
budget_sum: int,
|
38
|
-
|
75
|
+
include_comment: bool = True,
|
76
|
+
question_presentation: Optional[str] = None,
|
77
|
+
answering_instructions: Optional[str] = None,
|
78
|
+
permissive: bool = False,
|
39
79
|
):
|
80
|
+
"""Instantiate a new QuestionBudget.
|
81
|
+
|
82
|
+
:param question_name: The name of the question.
|
83
|
+
:param question_text: The text of the question.
|
84
|
+
:param question_options: The options for allocation of the budget sum.
|
85
|
+
:param budget_sum: The total amount of the budget to be allocated among the options.
|
86
|
+
"""
|
40
87
|
self.question_name = question_name
|
41
88
|
self.question_text = question_text
|
42
89
|
self.question_options = question_options
|
43
90
|
self.budget_sum = budget_sum
|
44
|
-
self.
|
91
|
+
self.question_presentation = question_presentation
|
92
|
+
self.answering_instructions = answering_instructions
|
93
|
+
self.permissive = permissive
|
94
|
+
self.include_comment = include_comment
|
95
|
+
|
96
|
+
def create_response_model(self):
|
97
|
+
return create_budget_model(
|
98
|
+
self.budget_sum, self.permissive, self.question_options
|
99
|
+
)
|
100
|
+
|
101
|
+
def _translate_answer_code_to_answer(
|
102
|
+
self, answer_code, combined_dict
|
103
|
+
) -> list[dict]:
|
104
|
+
"""
|
105
|
+
Translate the answer codes to the actual answers.
|
45
106
|
|
46
|
-
################
|
47
|
-
# Answer methods
|
48
|
-
################
|
49
|
-
def validate_answer(self, answer: dict[str, Any]) -> dict[str, Union[int, str]]:
|
50
|
-
self.validate_answer_template_basic(answer)
|
51
|
-
self.validate_answer_key_value(answer, "answer", dict)
|
52
|
-
self.validate_answer_budget(answer)
|
53
|
-
return answer
|
54
|
-
|
55
|
-
def translate_answer_code_to_answer(
|
56
|
-
self, answer_codes: dict[str, int], scenario: Scenario = None
|
57
|
-
):
|
58
|
-
"""Translates the answer codes to the actual answers.
|
59
107
|
For example, for a budget question with options ["a", "b", "c"],
|
60
108
|
the answer codes are 0, 1, and 2. The LLM will respond with 0.
|
61
109
|
This code will translate that to "a".
|
62
110
|
"""
|
63
111
|
translated_codes = []
|
64
|
-
for answer_code,
|
65
|
-
translated_codes.append({
|
112
|
+
for answer_code, question_option in zip(answer_code, self.question_options):
|
113
|
+
translated_codes.append({question_option: answer_code})
|
66
114
|
|
67
115
|
return translated_codes
|
68
116
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
117
|
+
# def _simulate_answer(self, human_readable=True):
|
118
|
+
# """Simulate a valid answer for debugging purposes (what the validator expects)."""
|
119
|
+
# from edsl.utilities.utilities import random_string
|
120
|
+
|
121
|
+
# if human_readable:
|
122
|
+
# keys = self.question_options
|
123
|
+
# else:
|
124
|
+
# keys = range(len(self.question_options))
|
125
|
+
# remaining_budget = self.budget_sum
|
126
|
+
# values = []
|
127
|
+
# for _ in range(len(self.question_options)):
|
128
|
+
# if _ == len(self.question_options) - 1:
|
129
|
+
# # Assign remaining budget to the last value
|
130
|
+
# values.append(remaining_budget)
|
131
|
+
# else:
|
132
|
+
# # Generate a random value between 0 and remaining budget
|
133
|
+
# value = random.randint(0, remaining_budget)
|
134
|
+
# values.append(value)
|
135
|
+
# remaining_budget -= value
|
136
|
+
# answer = dict(zip(keys, values))
|
137
|
+
# return {
|
138
|
+
# "answer": answer,
|
139
|
+
# "comment": random_string(),
|
140
|
+
# }
|
141
|
+
|
142
|
+
@property
|
143
|
+
def question_html_content(self) -> str:
|
144
|
+
from jinja2 import Template
|
145
|
+
|
146
|
+
question_html_content = Template(
|
147
|
+
"""
|
148
|
+
<form id="budgetForm">
|
149
|
+
<p>Total Budget: {{ budget_sum }}</p>
|
150
|
+
<p>Remaining Budget: <span id="remainingBudget">{{ budget_sum }}</span></p>
|
151
|
+
{% for option in question_options %}
|
152
|
+
<div>
|
153
|
+
<label for="{{ option }}">{{ option }}</label>
|
154
|
+
<input type="number" id="{{ option }}" name="{{ question_name }}[{{ option }}]" value="0" min="0" max="{{ budget_sum }}" oninput="updateRemainingBudget()">
|
155
|
+
</div>
|
156
|
+
{% endfor %}
|
157
|
+
</form>
|
158
|
+
<script>
|
159
|
+
function updateRemainingBudget() {
|
160
|
+
let totalBudget = {{ budget_sum }};
|
161
|
+
let allocated = 0;
|
162
|
+
|
163
|
+
{% for option in question_options %}
|
164
|
+
allocated += parseInt(document.getElementById("{{ option }}").value) || 0;
|
165
|
+
{% endfor %}
|
166
|
+
|
167
|
+
let remaining = totalBudget - allocated;
|
168
|
+
document.getElementById('remainingBudget').innerText = remaining;
|
169
|
+
|
170
|
+
{% for option in question_options %}
|
171
|
+
document.getElementById("{{ option }}").max = remaining + parseInt(document.getElementById("{{ option }}").value);
|
172
|
+
{% endfor %}
|
90
173
|
}
|
174
|
+
</script>
|
175
|
+
"""
|
176
|
+
).render(
|
177
|
+
question_name=self.question_name,
|
178
|
+
budget_sum=self.budget_sum,
|
179
|
+
question_options=self.question_options,
|
180
|
+
)
|
181
|
+
return question_html_content
|
91
182
|
|
92
183
|
################
|
93
184
|
# Helpful methods
|
94
185
|
################
|
95
186
|
@classmethod
|
96
|
-
def example(cls) -> QuestionBudget:
|
187
|
+
def example(cls, include_comment: bool = True) -> QuestionBudget:
|
188
|
+
"""Return an example of a budget question."""
|
97
189
|
return cls(
|
98
190
|
question_name="food_budget",
|
99
191
|
question_text="How would you allocate $100?",
|
100
192
|
question_options=["Pizza", "Ice Cream", "Burgers", "Salad"],
|
101
193
|
budget_sum=100,
|
194
|
+
include_comment=include_comment,
|
102
195
|
)
|
103
196
|
|
104
197
|
|
105
198
|
def main():
|
199
|
+
"""Create an example of a budget question and demonstrate its functionality."""
|
106
200
|
from edsl.questions.QuestionBudget import QuestionBudget
|
107
201
|
|
108
202
|
q = QuestionBudget.example()
|
109
203
|
q.question_text
|
110
204
|
q.question_options
|
111
205
|
q.question_name
|
112
|
-
q.short_names_dict
|
113
206
|
# validate an answer
|
114
|
-
q.
|
207
|
+
q._validate_answer(
|
115
208
|
{"answer": {0: 100, 1: 0, 2: 0, 3: 0}, "comment": "I like custard"}
|
116
209
|
)
|
117
210
|
# translate answer code
|
118
|
-
q.
|
211
|
+
q._translate_answer_code_to_answer({0: 100, 1: 0, 2: 0, 3: 0})
|
119
212
|
# simulate answer
|
120
|
-
q.
|
121
|
-
q.
|
122
|
-
q.
|
213
|
+
q._simulate_answer()
|
214
|
+
q._simulate_answer(human_readable=False)
|
215
|
+
q._validate_answer(q._simulate_answer(human_readable=False))
|
123
216
|
# serialization (inherits from Question)
|
124
217
|
q.to_dict()
|
125
218
|
assert q.from_dict(q.to_dict()) == q
|
126
219
|
|
127
220
|
|
128
221
|
if __name__ == "__main__":
|
129
|
-
q = QuestionBudget.example()
|
130
|
-
results = q.run()
|
222
|
+
# q = QuestionBudget.example()
|
223
|
+
# results = q.run()
|
224
|
+
|
225
|
+
import doctest
|
226
|
+
|
227
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|