edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__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 (344) hide show
  1. edsl/Base.py +413 -332
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +57 -49
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +1071 -867
  7. edsl/agents/AgentList.py +551 -413
  8. edsl/agents/Invigilator.py +284 -233
  9. edsl/agents/InvigilatorBase.py +257 -270
  10. edsl/agents/PromptConstructor.py +272 -354
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/__init__.py +2 -3
  14. edsl/agents/descriptors.py +99 -99
  15. edsl/agents/prompt_helpers.py +129 -129
  16. edsl/agents/question_option_processor.py +172 -0
  17. edsl/auto/AutoStudy.py +130 -117
  18. edsl/auto/StageBase.py +243 -230
  19. edsl/auto/StageGenerateSurvey.py +178 -178
  20. edsl/auto/StageLabelQuestions.py +125 -125
  21. edsl/auto/StagePersona.py +61 -61
  22. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  23. edsl/auto/StagePersonaDimensionValues.py +74 -74
  24. edsl/auto/StagePersonaDimensions.py +69 -69
  25. edsl/auto/StageQuestions.py +74 -73
  26. edsl/auto/SurveyCreatorPipeline.py +21 -21
  27. edsl/auto/utilities.py +218 -224
  28. edsl/base/Base.py +279 -279
  29. edsl/config.py +177 -157
  30. edsl/conversation/Conversation.py +290 -290
  31. edsl/conversation/car_buying.py +59 -58
  32. edsl/conversation/chips.py +95 -95
  33. edsl/conversation/mug_negotiation.py +81 -81
  34. edsl/conversation/next_speaker_utilities.py +93 -93
  35. edsl/coop/CoopFunctionsMixin.py +15 -0
  36. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  37. edsl/coop/PriceFetcher.py +54 -54
  38. edsl/coop/__init__.py +2 -2
  39. edsl/coop/coop.py +1106 -1028
  40. edsl/coop/utils.py +131 -131
  41. edsl/data/Cache.py +573 -555
  42. edsl/data/CacheEntry.py +230 -233
  43. edsl/data/CacheHandler.py +168 -149
  44. edsl/data/RemoteCacheSync.py +186 -78
  45. edsl/data/SQLiteDict.py +292 -292
  46. edsl/data/__init__.py +5 -4
  47. edsl/data/hack.py +10 -0
  48. edsl/data/orm.py +10 -10
  49. edsl/data_transfer_models.py +74 -73
  50. edsl/enums.py +202 -175
  51. edsl/exceptions/BaseException.py +21 -21
  52. edsl/exceptions/__init__.py +54 -54
  53. edsl/exceptions/agents.py +54 -42
  54. edsl/exceptions/cache.py +5 -5
  55. edsl/exceptions/configuration.py +16 -16
  56. edsl/exceptions/coop.py +10 -10
  57. edsl/exceptions/data.py +14 -14
  58. edsl/exceptions/general.py +34 -34
  59. edsl/exceptions/inference_services.py +5 -0
  60. edsl/exceptions/jobs.py +33 -33
  61. edsl/exceptions/language_models.py +63 -63
  62. edsl/exceptions/prompts.py +15 -15
  63. edsl/exceptions/questions.py +109 -91
  64. edsl/exceptions/results.py +29 -29
  65. edsl/exceptions/scenarios.py +29 -22
  66. edsl/exceptions/surveys.py +37 -37
  67. edsl/inference_services/AnthropicService.py +106 -87
  68. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  69. edsl/inference_services/AvailableModelFetcher.py +215 -0
  70. edsl/inference_services/AwsBedrock.py +118 -120
  71. edsl/inference_services/AzureAI.py +215 -217
  72. edsl/inference_services/DeepInfraService.py +18 -18
  73. edsl/inference_services/GoogleService.py +143 -148
  74. edsl/inference_services/GroqService.py +20 -20
  75. edsl/inference_services/InferenceServiceABC.py +80 -147
  76. edsl/inference_services/InferenceServicesCollection.py +138 -97
  77. edsl/inference_services/MistralAIService.py +120 -123
  78. edsl/inference_services/OllamaService.py +18 -18
  79. edsl/inference_services/OpenAIService.py +236 -224
  80. edsl/inference_services/PerplexityService.py +160 -163
  81. edsl/inference_services/ServiceAvailability.py +135 -0
  82. edsl/inference_services/TestService.py +90 -89
  83. edsl/inference_services/TogetherAIService.py +172 -170
  84. edsl/inference_services/data_structures.py +134 -0
  85. edsl/inference_services/models_available_cache.py +118 -118
  86. edsl/inference_services/rate_limits_cache.py +25 -25
  87. edsl/inference_services/registry.py +41 -41
  88. edsl/inference_services/write_available.py +10 -10
  89. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  90. edsl/jobs/Answers.py +43 -56
  91. edsl/jobs/FetchInvigilator.py +47 -0
  92. edsl/jobs/InterviewTaskManager.py +98 -0
  93. edsl/jobs/InterviewsConstructor.py +50 -0
  94. edsl/jobs/Jobs.py +823 -898
  95. edsl/jobs/JobsChecks.py +172 -147
  96. edsl/jobs/JobsComponentConstructor.py +189 -0
  97. edsl/jobs/JobsPrompts.py +270 -268
  98. edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
  99. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  100. edsl/jobs/RequestTokenEstimator.py +30 -0
  101. edsl/jobs/__init__.py +1 -1
  102. edsl/jobs/async_interview_runner.py +138 -0
  103. edsl/jobs/buckets/BucketCollection.py +104 -63
  104. edsl/jobs/buckets/ModelBuckets.py +65 -65
  105. edsl/jobs/buckets/TokenBucket.py +283 -251
  106. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  107. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  108. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  109. edsl/jobs/data_structures.py +120 -0
  110. edsl/jobs/decorators.py +35 -0
  111. edsl/jobs/interviews/Interview.py +396 -661
  112. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  113. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  114. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  115. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  116. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  117. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  118. edsl/jobs/interviews/ReportErrors.py +66 -66
  119. edsl/jobs/interviews/interview_status_enum.py +9 -9
  120. edsl/jobs/jobs_status_enums.py +9 -0
  121. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  122. edsl/jobs/results_exceptions_handler.py +98 -0
  123. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
  124. edsl/jobs/runners/JobsRunnerStatus.py +297 -330
  125. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  126. edsl/jobs/tasks/TaskCreators.py +64 -64
  127. edsl/jobs/tasks/TaskHistory.py +470 -450
  128. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  129. edsl/jobs/tasks/task_status_enum.py +161 -163
  130. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  131. edsl/jobs/tokens/TokenUsage.py +34 -34
  132. edsl/language_models/ComputeCost.py +63 -0
  133. edsl/language_models/LanguageModel.py +626 -668
  134. edsl/language_models/ModelList.py +164 -155
  135. edsl/language_models/PriceManager.py +127 -0
  136. edsl/language_models/RawResponseHandler.py +106 -0
  137. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  138. edsl/language_models/ServiceDataSources.py +0 -0
  139. edsl/language_models/__init__.py +2 -3
  140. edsl/language_models/fake_openai_call.py +15 -15
  141. edsl/language_models/fake_openai_service.py +61 -61
  142. edsl/language_models/key_management/KeyLookup.py +63 -0
  143. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  144. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  145. edsl/language_models/key_management/__init__.py +0 -0
  146. edsl/language_models/key_management/models.py +131 -0
  147. edsl/language_models/model.py +256 -0
  148. edsl/language_models/repair.py +156 -156
  149. edsl/language_models/utilities.py +65 -64
  150. edsl/notebooks/Notebook.py +263 -258
  151. edsl/notebooks/NotebookToLaTeX.py +142 -0
  152. edsl/notebooks/__init__.py +1 -1
  153. edsl/prompts/Prompt.py +352 -362
  154. edsl/prompts/__init__.py +2 -2
  155. edsl/questions/ExceptionExplainer.py +77 -0
  156. edsl/questions/HTMLQuestion.py +103 -0
  157. edsl/questions/QuestionBase.py +518 -664
  158. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  159. edsl/questions/QuestionBudget.py +227 -227
  160. edsl/questions/QuestionCheckBox.py +359 -359
  161. edsl/questions/QuestionExtract.py +180 -182
  162. edsl/questions/QuestionFreeText.py +113 -114
  163. edsl/questions/QuestionFunctional.py +166 -166
  164. edsl/questions/QuestionList.py +223 -231
  165. edsl/questions/QuestionMatrix.py +265 -0
  166. edsl/questions/QuestionMultipleChoice.py +330 -286
  167. edsl/questions/QuestionNumerical.py +151 -153
  168. edsl/questions/QuestionRank.py +314 -324
  169. edsl/questions/Quick.py +41 -41
  170. edsl/questions/SimpleAskMixin.py +74 -73
  171. edsl/questions/__init__.py +27 -26
  172. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  173. edsl/questions/compose_questions.py +98 -98
  174. edsl/questions/data_structures.py +20 -0
  175. edsl/questions/decorators.py +21 -21
  176. edsl/questions/derived/QuestionLikertFive.py +76 -76
  177. edsl/questions/derived/QuestionLinearScale.py +90 -87
  178. edsl/questions/derived/QuestionTopK.py +93 -93
  179. edsl/questions/derived/QuestionYesNo.py +82 -82
  180. edsl/questions/descriptors.py +427 -413
  181. edsl/questions/loop_processor.py +149 -0
  182. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  183. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  184. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  185. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  186. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  187. edsl/questions/prompt_templates/question_list.jinja +17 -17
  188. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  189. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  190. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  191. edsl/questions/question_registry.py +177 -177
  192. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  193. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  194. edsl/questions/response_validator_factory.py +34 -0
  195. edsl/questions/settings.py +12 -12
  196. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  197. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  198. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  199. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  200. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  201. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  202. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  203. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  204. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  205. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  206. edsl/questions/templates/list/question_presentation.jinja +5 -5
  207. edsl/questions/templates/matrix/__init__.py +1 -0
  208. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  209. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  210. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  211. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  212. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  213. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  214. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  215. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  216. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  217. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  218. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  219. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  220. edsl/results/CSSParameterizer.py +108 -108
  221. edsl/results/Dataset.py +587 -424
  222. edsl/results/DatasetExportMixin.py +594 -731
  223. edsl/results/DatasetTree.py +295 -275
  224. edsl/results/MarkdownToDocx.py +122 -0
  225. edsl/results/MarkdownToPDF.py +111 -0
  226. edsl/results/Result.py +557 -465
  227. edsl/results/Results.py +1183 -1165
  228. edsl/results/ResultsExportMixin.py +45 -43
  229. edsl/results/ResultsGGMixin.py +121 -121
  230. edsl/results/TableDisplay.py +125 -198
  231. edsl/results/TextEditor.py +50 -0
  232. edsl/results/__init__.py +2 -2
  233. edsl/results/file_exports.py +252 -0
  234. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  235. edsl/results/{Selector.py → results_selector.py} +145 -135
  236. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  237. edsl/results/smart_objects.py +96 -0
  238. edsl/results/table_data_class.py +12 -0
  239. edsl/results/table_display.css +77 -77
  240. edsl/results/table_renderers.py +118 -0
  241. edsl/results/tree_explore.py +115 -115
  242. edsl/scenarios/ConstructDownloadLink.py +109 -0
  243. edsl/scenarios/DocumentChunker.py +102 -0
  244. edsl/scenarios/DocxScenario.py +16 -0
  245. edsl/scenarios/FileStore.py +511 -632
  246. edsl/scenarios/PdfExtractor.py +40 -0
  247. edsl/scenarios/Scenario.py +498 -601
  248. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  249. edsl/scenarios/ScenarioList.py +1458 -1287
  250. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  251. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  252. edsl/scenarios/__init__.py +3 -4
  253. edsl/scenarios/directory_scanner.py +96 -0
  254. edsl/scenarios/file_methods.py +85 -0
  255. edsl/scenarios/handlers/__init__.py +13 -0
  256. edsl/scenarios/handlers/csv.py +38 -0
  257. edsl/scenarios/handlers/docx.py +76 -0
  258. edsl/scenarios/handlers/html.py +37 -0
  259. edsl/scenarios/handlers/json.py +111 -0
  260. edsl/scenarios/handlers/latex.py +5 -0
  261. edsl/scenarios/handlers/md.py +51 -0
  262. edsl/scenarios/handlers/pdf.py +68 -0
  263. edsl/scenarios/handlers/png.py +39 -0
  264. edsl/scenarios/handlers/pptx.py +105 -0
  265. edsl/scenarios/handlers/py.py +294 -0
  266. edsl/scenarios/handlers/sql.py +313 -0
  267. edsl/scenarios/handlers/sqlite.py +149 -0
  268. edsl/scenarios/handlers/txt.py +33 -0
  269. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
  270. edsl/scenarios/scenario_selector.py +156 -0
  271. edsl/shared.py +1 -1
  272. edsl/study/ObjectEntry.py +173 -173
  273. edsl/study/ProofOfWork.py +113 -113
  274. edsl/study/SnapShot.py +80 -80
  275. edsl/study/Study.py +521 -528
  276. edsl/study/__init__.py +4 -4
  277. edsl/surveys/ConstructDAG.py +92 -0
  278. edsl/surveys/DAG.py +148 -148
  279. edsl/surveys/EditSurvey.py +221 -0
  280. edsl/surveys/InstructionHandler.py +100 -0
  281. edsl/surveys/Memory.py +31 -31
  282. edsl/surveys/MemoryManagement.py +72 -0
  283. edsl/surveys/MemoryPlan.py +244 -244
  284. edsl/surveys/Rule.py +327 -326
  285. edsl/surveys/RuleCollection.py +385 -387
  286. edsl/surveys/RuleManager.py +172 -0
  287. edsl/surveys/Simulator.py +75 -0
  288. edsl/surveys/Survey.py +1280 -1801
  289. edsl/surveys/SurveyCSS.py +273 -261
  290. edsl/surveys/SurveyExportMixin.py +259 -259
  291. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
  292. edsl/surveys/SurveyQualtricsImport.py +284 -284
  293. edsl/surveys/SurveyToApp.py +141 -0
  294. edsl/surveys/__init__.py +5 -3
  295. edsl/surveys/base.py +53 -53
  296. edsl/surveys/descriptors.py +60 -56
  297. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  298. edsl/surveys/instructions/Instruction.py +56 -65
  299. edsl/surveys/instructions/InstructionCollection.py +82 -77
  300. edsl/templates/error_reporting/base.html +23 -23
  301. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  302. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  303. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  304. edsl/templates/error_reporting/interview_details.html +115 -115
  305. edsl/templates/error_reporting/interviews.html +19 -19
  306. edsl/templates/error_reporting/overview.html +4 -4
  307. edsl/templates/error_reporting/performance_plot.html +1 -1
  308. edsl/templates/error_reporting/report.css +73 -73
  309. edsl/templates/error_reporting/report.html +117 -117
  310. edsl/templates/error_reporting/report.js +25 -25
  311. edsl/test_h +1 -0
  312. edsl/tools/__init__.py +1 -1
  313. edsl/tools/clusters.py +192 -192
  314. edsl/tools/embeddings.py +27 -27
  315. edsl/tools/embeddings_plotting.py +118 -118
  316. edsl/tools/plotting.py +112 -112
  317. edsl/tools/summarize.py +18 -18
  318. edsl/utilities/PrettyList.py +56 -0
  319. edsl/utilities/SystemInfo.py +28 -28
  320. edsl/utilities/__init__.py +22 -22
  321. edsl/utilities/ast_utilities.py +25 -25
  322. edsl/utilities/data/Registry.py +6 -6
  323. edsl/utilities/data/__init__.py +1 -1
  324. edsl/utilities/data/scooter_results.json +1 -1
  325. edsl/utilities/decorators.py +77 -77
  326. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  327. edsl/utilities/gcp_bucket/example.py +50 -0
  328. edsl/utilities/interface.py +627 -627
  329. edsl/utilities/is_notebook.py +18 -0
  330. edsl/utilities/is_valid_variable_name.py +11 -0
  331. edsl/utilities/naming_utilities.py +263 -263
  332. edsl/utilities/remove_edsl_version.py +24 -0
  333. edsl/utilities/repair_functions.py +28 -28
  334. edsl/utilities/restricted_python.py +70 -70
  335. edsl/utilities/utilities.py +436 -424
  336. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/LICENSE +21 -21
  337. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
  338. edsl-0.1.39.dev4.dist-info/RECORD +361 -0
  339. edsl/language_models/KeyLookup.py +0 -30
  340. edsl/language_models/registry.py +0 -190
  341. edsl/language_models/unused/ReplicateBase.py +0 -83
  342. edsl/results/ResultsDBMixin.py +0 -238
  343. edsl-0.1.39.dev3.dist-info/RECORD +0 -277
  344. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/WHEEL +0 -0
@@ -1,217 +1,221 @@
1
- from importlib import resources
2
- from typing import Optional
3
- from edsl.prompts import Prompt
4
- from edsl.exceptions.questions import QuestionAnswerValidationError
5
-
6
- from functools import lru_cache
7
-
8
-
9
- class TemplateManager:
10
- _instance = None
11
-
12
- def __new__(cls):
13
- if cls._instance is None:
14
- cls._instance = super().__new__(cls)
15
- cls._instance._template_cache = {}
16
- return cls._instance
17
-
18
- @lru_cache(maxsize=None)
19
- def get_template(self, question_type, template_name):
20
- if (question_type, template_name) not in self._template_cache:
21
- with resources.open_text(
22
- f"edsl.questions.templates.{question_type}", template_name
23
- ) as file:
24
- self._template_cache[(question_type, template_name)] = file.read()
25
- return self._template_cache[(question_type, template_name)]
26
-
27
-
28
- # Global instance
29
- template_manager = TemplateManager()
30
-
31
-
32
- class QuestionBasePromptsMixin:
33
- @property
34
- def model_instructions(self) -> dict:
35
- """Get the model-specific instructions for the question."""
36
- if not hasattr(self, "_model_instructions"):
37
- self._model_instructions = {}
38
- return self._model_instructions
39
-
40
- def _all_text(self) -> str:
41
- """Return the question text.
42
-
43
- >>> from edsl import QuestionMultipleChoice as Q
44
- >>> Q.example()._all_text()
45
- "how_feelingHow are you?['Good', 'Great', 'OK', 'Bad']"
46
- """
47
- txt = ""
48
- for key, value in self.data.items():
49
- if isinstance(value, str):
50
- txt += value
51
- elif isinstance(value, list):
52
- txt += "".join(str(value))
53
- return txt
54
-
55
- @model_instructions.setter
56
- def model_instructions(self, data: dict):
57
- """Set the model-specific instructions for the question."""
58
- self._model_instructions = data
59
-
60
- def add_model_instructions(
61
- self, *, instructions: str, model: Optional[str] = None
62
- ) -> None:
63
- """Add model-specific instructions for the question that override the default instructions.
64
-
65
- :param instructions: The instructions to add. This is typically a jinja2 template.
66
- :param model: The language model for this instruction.
67
-
68
- >>> from edsl.questions import QuestionFreeText
69
- >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
70
- >>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
71
- >>> q.get_instructions(model = "gpt3")
72
- Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
73
- """
74
- from edsl import Model
75
-
76
- if not hasattr(self, "_model_instructions"):
77
- self._model_instructions = {}
78
- if model is None:
79
- # if not model is passed, all the models are mapped to this instruction, including 'None'
80
- self._model_instructions = {
81
- model_name: instructions
82
- for model_name in Model.available(name_only=True)
83
- }
84
- self._model_instructions.update({model: instructions})
85
- else:
86
- self._model_instructions.update({model: instructions})
87
-
88
- @classmethod
89
- def path_to_folder(cls) -> str:
90
- return resources.files(f"edsl.questions.templates", cls.question_type)
91
-
92
- @property
93
- def response_model(self) -> type["BaseModel"]:
94
- if self._response_model is not None:
95
- return self._response_model
96
- else:
97
- return self.create_response_model()
98
-
99
- @property
100
- def use_code(self) -> bool:
101
- if hasattr(self, "_use_code"):
102
- return self._use_code
103
- return True
104
-
105
- @use_code.setter
106
- def use_code(self, value: bool) -> None:
107
- self._use_code = value
108
-
109
- @property
110
- def include_comment(self) -> bool:
111
- if hasattr(self, "_include_comment"):
112
- return self._include_comment
113
- return True
114
-
115
- @include_comment.setter
116
- def include_comment(self, value: bool) -> None:
117
- self._include_comment = value
118
-
119
- @classmethod
120
- def default_answering_instructions(cls) -> str:
121
- # template_text = cls._read_template("answering_instructions.jinja")
122
- template_text = template_manager.get_template(
123
- cls.question_type, "answering_instructions.jinja"
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
- return Prompt(text=template_text)
133
-
134
- @property
135
- def answering_instructions(self) -> str:
136
- if self._answering_instructions is None:
137
- return self.default_answering_instructions()
138
- return self._answering_instructions
139
-
140
- @answering_instructions.setter
141
- def answering_instructions(self, value) -> None:
142
- self._answering_instructions = value
143
-
144
- @property
145
- def question_presentation(self):
146
- if self._question_presentation is None:
147
- return self.default_question_presentation()
148
- return self._question_presentation
149
-
150
- @question_presentation.setter
151
- def question_presentation(self, value):
152
- self._question_presentation = value
153
-
154
- def prompt_preview(self, scenario=None, agent=None):
155
- return self.new_default_instructions.render(
156
- self.data
157
- | {
158
- "include_comment": getattr(self, "_include_comment", True),
159
- "use_code": getattr(self, "_use_code", True),
160
- }
161
- | ({"scenario": scenario} or {})
162
- | ({"agent": agent} or {})
163
- )
164
-
165
- @classmethod
166
- def self_check(cls):
167
- q = cls.example()
168
- for answer, params in q.response_validator.valid_examples:
169
- for key, value in params.items():
170
- setattr(q, key, value)
171
- q._validate_answer(answer)
172
- for answer, params, reason in q.response_validator.invalid_examples:
173
- for key, value in params.items():
174
- setattr(q, key, value)
175
- try:
176
- q._validate_answer(answer)
177
- except QuestionAnswerValidationError:
178
- pass
179
- else:
180
- raise ValueError(f"Example {answer} should have failed for {reason}.")
181
-
182
- @property
183
- def new_default_instructions(self) -> "Prompt":
184
- "This is set up as a property because there are mutable question values that determine how it is rendered."
185
- return Prompt(self.question_presentation) + Prompt(self.answering_instructions)
186
-
187
- @property
188
- def parameters(self) -> set[str]:
189
- """Return the parameters of the question."""
190
- from jinja2 import Environment, meta
191
-
192
- env = Environment()
193
- # Parse the template
194
- txt = self._all_text()
195
- # txt = self.question_text
196
- # if hasattr(self, "question_options"):
197
- # txt += " ".join(self.question_options)
198
- parsed_content = env.parse(txt)
199
- # Extract undeclared variables
200
- variables = meta.find_undeclared_variables(parsed_content)
201
- # Return as a list
202
- return set(variables)
203
-
204
- def get_instructions(self, model: Optional[str] = None) -> type["PromptBase"]:
205
- """Get the mathcing question-answering instructions for the question.
206
-
207
- :param model: The language model to use.
208
- """
209
- from edsl.prompts.Prompt import Prompt
210
-
211
- if model in self.model_instructions:
212
- return Prompt(text=self.model_instructions[model])
213
- else:
214
- if hasattr(self, "new_default_instructions"):
215
- return self.new_default_instructions
216
- else:
217
- return self.applicable_prompts(model)[0]()
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]()