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