edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev5__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 (341) 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/orm.py +10 -10
  48. edsl/data_transfer_models.py +74 -73
  49. edsl/enums.py +202 -175
  50. edsl/exceptions/BaseException.py +21 -21
  51. edsl/exceptions/__init__.py +54 -54
  52. edsl/exceptions/agents.py +54 -42
  53. edsl/exceptions/cache.py +5 -5
  54. edsl/exceptions/configuration.py +16 -16
  55. edsl/exceptions/coop.py +10 -10
  56. edsl/exceptions/data.py +14 -14
  57. edsl/exceptions/general.py +34 -34
  58. edsl/exceptions/inference_services.py +5 -0
  59. edsl/exceptions/jobs.py +33 -33
  60. edsl/exceptions/language_models.py +63 -63
  61. edsl/exceptions/prompts.py +15 -15
  62. edsl/exceptions/questions.py +109 -91
  63. edsl/exceptions/results.py +29 -29
  64. edsl/exceptions/scenarios.py +29 -22
  65. edsl/exceptions/surveys.py +37 -37
  66. edsl/inference_services/AnthropicService.py +106 -87
  67. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  68. edsl/inference_services/AvailableModelFetcher.py +215 -0
  69. edsl/inference_services/AwsBedrock.py +118 -120
  70. edsl/inference_services/AzureAI.py +215 -217
  71. edsl/inference_services/DeepInfraService.py +18 -18
  72. edsl/inference_services/GoogleService.py +143 -148
  73. edsl/inference_services/GroqService.py +20 -20
  74. edsl/inference_services/InferenceServiceABC.py +80 -147
  75. edsl/inference_services/InferenceServicesCollection.py +138 -97
  76. edsl/inference_services/MistralAIService.py +120 -123
  77. edsl/inference_services/OllamaService.py +18 -18
  78. edsl/inference_services/OpenAIService.py +236 -224
  79. edsl/inference_services/PerplexityService.py +160 -163
  80. edsl/inference_services/ServiceAvailability.py +135 -0
  81. edsl/inference_services/TestService.py +90 -89
  82. edsl/inference_services/TogetherAIService.py +172 -170
  83. edsl/inference_services/data_structures.py +134 -0
  84. edsl/inference_services/models_available_cache.py +118 -118
  85. edsl/inference_services/rate_limits_cache.py +25 -25
  86. edsl/inference_services/registry.py +41 -41
  87. edsl/inference_services/write_available.py +10 -10
  88. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  89. edsl/jobs/Answers.py +43 -56
  90. edsl/jobs/FetchInvigilator.py +47 -0
  91. edsl/jobs/InterviewTaskManager.py +98 -0
  92. edsl/jobs/InterviewsConstructor.py +50 -0
  93. edsl/jobs/Jobs.py +823 -898
  94. edsl/jobs/JobsChecks.py +172 -147
  95. edsl/jobs/JobsComponentConstructor.py +189 -0
  96. edsl/jobs/JobsPrompts.py +270 -268
  97. edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
  98. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  99. edsl/jobs/RequestTokenEstimator.py +30 -0
  100. edsl/jobs/__init__.py +1 -1
  101. edsl/jobs/async_interview_runner.py +138 -0
  102. edsl/jobs/buckets/BucketCollection.py +104 -63
  103. edsl/jobs/buckets/ModelBuckets.py +65 -65
  104. edsl/jobs/buckets/TokenBucket.py +283 -251
  105. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  106. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  107. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  108. edsl/jobs/data_structures.py +120 -0
  109. edsl/jobs/decorators.py +35 -0
  110. edsl/jobs/interviews/Interview.py +396 -661
  111. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  112. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  113. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  114. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  115. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  116. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  117. edsl/jobs/interviews/ReportErrors.py +66 -66
  118. edsl/jobs/interviews/interview_status_enum.py +9 -9
  119. edsl/jobs/jobs_status_enums.py +9 -0
  120. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  121. edsl/jobs/results_exceptions_handler.py +98 -0
  122. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
  123. edsl/jobs/runners/JobsRunnerStatus.py +297 -330
  124. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  125. edsl/jobs/tasks/TaskCreators.py +64 -64
  126. edsl/jobs/tasks/TaskHistory.py +470 -450
  127. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  128. edsl/jobs/tasks/task_status_enum.py +161 -163
  129. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  130. edsl/jobs/tokens/TokenUsage.py +34 -34
  131. edsl/language_models/ComputeCost.py +63 -0
  132. edsl/language_models/LanguageModel.py +626 -668
  133. edsl/language_models/ModelList.py +164 -155
  134. edsl/language_models/PriceManager.py +127 -0
  135. edsl/language_models/RawResponseHandler.py +106 -0
  136. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  137. edsl/language_models/ServiceDataSources.py +0 -0
  138. edsl/language_models/__init__.py +2 -3
  139. edsl/language_models/fake_openai_call.py +15 -15
  140. edsl/language_models/fake_openai_service.py +61 -61
  141. edsl/language_models/key_management/KeyLookup.py +63 -0
  142. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  143. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  144. edsl/language_models/key_management/__init__.py +0 -0
  145. edsl/language_models/key_management/models.py +131 -0
  146. edsl/language_models/model.py +256 -0
  147. edsl/language_models/repair.py +156 -156
  148. edsl/language_models/utilities.py +65 -64
  149. edsl/notebooks/Notebook.py +263 -258
  150. edsl/notebooks/NotebookToLaTeX.py +142 -0
  151. edsl/notebooks/__init__.py +1 -1
  152. edsl/prompts/Prompt.py +352 -362
  153. edsl/prompts/__init__.py +2 -2
  154. edsl/questions/ExceptionExplainer.py +77 -0
  155. edsl/questions/HTMLQuestion.py +103 -0
  156. edsl/questions/QuestionBase.py +518 -664
  157. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  158. edsl/questions/QuestionBudget.py +227 -227
  159. edsl/questions/QuestionCheckBox.py +359 -359
  160. edsl/questions/QuestionExtract.py +180 -182
  161. edsl/questions/QuestionFreeText.py +113 -114
  162. edsl/questions/QuestionFunctional.py +166 -166
  163. edsl/questions/QuestionList.py +223 -231
  164. edsl/questions/QuestionMatrix.py +265 -0
  165. edsl/questions/QuestionMultipleChoice.py +330 -286
  166. edsl/questions/QuestionNumerical.py +151 -153
  167. edsl/questions/QuestionRank.py +314 -324
  168. edsl/questions/Quick.py +41 -41
  169. edsl/questions/SimpleAskMixin.py +74 -73
  170. edsl/questions/__init__.py +27 -26
  171. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  172. edsl/questions/compose_questions.py +98 -98
  173. edsl/questions/data_structures.py +20 -0
  174. edsl/questions/decorators.py +21 -21
  175. edsl/questions/derived/QuestionLikertFive.py +76 -76
  176. edsl/questions/derived/QuestionLinearScale.py +90 -87
  177. edsl/questions/derived/QuestionTopK.py +93 -93
  178. edsl/questions/derived/QuestionYesNo.py +82 -82
  179. edsl/questions/descriptors.py +427 -413
  180. edsl/questions/loop_processor.py +149 -0
  181. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  182. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  183. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  184. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  185. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  186. edsl/questions/prompt_templates/question_list.jinja +17 -17
  187. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  188. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  189. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  190. edsl/questions/question_registry.py +177 -177
  191. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  192. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  193. edsl/questions/response_validator_factory.py +34 -0
  194. edsl/questions/settings.py +12 -12
  195. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  196. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  197. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  198. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  199. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  200. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  201. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  202. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  203. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  204. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  205. edsl/questions/templates/list/question_presentation.jinja +5 -5
  206. edsl/questions/templates/matrix/__init__.py +1 -0
  207. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  208. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  209. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  210. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  211. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  212. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  213. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  214. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  215. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  216. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  217. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  218. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  219. edsl/results/CSSParameterizer.py +108 -108
  220. edsl/results/Dataset.py +587 -424
  221. edsl/results/DatasetExportMixin.py +594 -731
  222. edsl/results/DatasetTree.py +295 -275
  223. edsl/results/MarkdownToDocx.py +122 -0
  224. edsl/results/MarkdownToPDF.py +111 -0
  225. edsl/results/Result.py +557 -465
  226. edsl/results/Results.py +1183 -1165
  227. edsl/results/ResultsExportMixin.py +45 -43
  228. edsl/results/ResultsGGMixin.py +121 -121
  229. edsl/results/TableDisplay.py +125 -198
  230. edsl/results/TextEditor.py +50 -0
  231. edsl/results/__init__.py +2 -2
  232. edsl/results/file_exports.py +252 -0
  233. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  234. edsl/results/{Selector.py → results_selector.py} +145 -135
  235. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  236. edsl/results/smart_objects.py +96 -0
  237. edsl/results/table_data_class.py +12 -0
  238. edsl/results/table_display.css +77 -77
  239. edsl/results/table_renderers.py +118 -0
  240. edsl/results/tree_explore.py +115 -115
  241. edsl/scenarios/ConstructDownloadLink.py +109 -0
  242. edsl/scenarios/DocumentChunker.py +102 -0
  243. edsl/scenarios/DocxScenario.py +16 -0
  244. edsl/scenarios/FileStore.py +511 -632
  245. edsl/scenarios/PdfExtractor.py +40 -0
  246. edsl/scenarios/Scenario.py +498 -601
  247. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  248. edsl/scenarios/ScenarioList.py +1458 -1287
  249. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  250. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  251. edsl/scenarios/__init__.py +3 -4
  252. edsl/scenarios/directory_scanner.py +96 -0
  253. edsl/scenarios/file_methods.py +85 -0
  254. edsl/scenarios/handlers/__init__.py +13 -0
  255. edsl/scenarios/handlers/csv.py +38 -0
  256. edsl/scenarios/handlers/docx.py +76 -0
  257. edsl/scenarios/handlers/html.py +37 -0
  258. edsl/scenarios/handlers/json.py +111 -0
  259. edsl/scenarios/handlers/latex.py +5 -0
  260. edsl/scenarios/handlers/md.py +51 -0
  261. edsl/scenarios/handlers/pdf.py +68 -0
  262. edsl/scenarios/handlers/png.py +39 -0
  263. edsl/scenarios/handlers/pptx.py +105 -0
  264. edsl/scenarios/handlers/py.py +294 -0
  265. edsl/scenarios/handlers/sql.py +313 -0
  266. edsl/scenarios/handlers/sqlite.py +149 -0
  267. edsl/scenarios/handlers/txt.py +33 -0
  268. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
  269. edsl/scenarios/scenario_selector.py +156 -0
  270. edsl/shared.py +1 -1
  271. edsl/study/ObjectEntry.py +173 -173
  272. edsl/study/ProofOfWork.py +113 -113
  273. edsl/study/SnapShot.py +80 -80
  274. edsl/study/Study.py +521 -528
  275. edsl/study/__init__.py +4 -4
  276. edsl/surveys/ConstructDAG.py +92 -0
  277. edsl/surveys/DAG.py +148 -148
  278. edsl/surveys/EditSurvey.py +221 -0
  279. edsl/surveys/InstructionHandler.py +100 -0
  280. edsl/surveys/Memory.py +31 -31
  281. edsl/surveys/MemoryManagement.py +72 -0
  282. edsl/surveys/MemoryPlan.py +244 -244
  283. edsl/surveys/Rule.py +327 -326
  284. edsl/surveys/RuleCollection.py +385 -387
  285. edsl/surveys/RuleManager.py +172 -0
  286. edsl/surveys/Simulator.py +75 -0
  287. edsl/surveys/Survey.py +1280 -1801
  288. edsl/surveys/SurveyCSS.py +273 -261
  289. edsl/surveys/SurveyExportMixin.py +259 -259
  290. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
  291. edsl/surveys/SurveyQualtricsImport.py +284 -284
  292. edsl/surveys/SurveyToApp.py +141 -0
  293. edsl/surveys/__init__.py +5 -3
  294. edsl/surveys/base.py +53 -53
  295. edsl/surveys/descriptors.py +60 -56
  296. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  297. edsl/surveys/instructions/Instruction.py +56 -65
  298. edsl/surveys/instructions/InstructionCollection.py +82 -77
  299. edsl/templates/error_reporting/base.html +23 -23
  300. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  301. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  302. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  303. edsl/templates/error_reporting/interview_details.html +115 -115
  304. edsl/templates/error_reporting/interviews.html +19 -19
  305. edsl/templates/error_reporting/overview.html +4 -4
  306. edsl/templates/error_reporting/performance_plot.html +1 -1
  307. edsl/templates/error_reporting/report.css +73 -73
  308. edsl/templates/error_reporting/report.html +117 -117
  309. edsl/templates/error_reporting/report.js +25 -25
  310. edsl/tools/__init__.py +1 -1
  311. edsl/tools/clusters.py +192 -192
  312. edsl/tools/embeddings.py +27 -27
  313. edsl/tools/embeddings_plotting.py +118 -118
  314. edsl/tools/plotting.py +112 -112
  315. edsl/tools/summarize.py +18 -18
  316. edsl/utilities/PrettyList.py +56 -0
  317. edsl/utilities/SystemInfo.py +28 -28
  318. edsl/utilities/__init__.py +22 -22
  319. edsl/utilities/ast_utilities.py +25 -25
  320. edsl/utilities/data/Registry.py +6 -6
  321. edsl/utilities/data/__init__.py +1 -1
  322. edsl/utilities/data/scooter_results.json +1 -1
  323. edsl/utilities/decorators.py +77 -77
  324. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  325. edsl/utilities/interface.py +627 -627
  326. edsl/utilities/is_notebook.py +18 -0
  327. edsl/utilities/is_valid_variable_name.py +11 -0
  328. edsl/utilities/naming_utilities.py +263 -263
  329. edsl/utilities/remove_edsl_version.py +24 -0
  330. edsl/utilities/repair_functions.py +28 -28
  331. edsl/utilities/restricted_python.py +70 -70
  332. edsl/utilities/utilities.py +436 -424
  333. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/LICENSE +21 -21
  334. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/METADATA +13 -11
  335. edsl-0.1.39.dev5.dist-info/RECORD +358 -0
  336. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/WHEEL +1 -1
  337. edsl/language_models/KeyLookup.py +0 -30
  338. edsl/language_models/registry.py +0 -190
  339. edsl/language_models/unused/ReplicateBase.py +0 -83
  340. edsl/results/ResultsDBMixin.py +0 -238
  341. edsl-0.1.39.dev3.dist-info/RECORD +0 -277
@@ -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]()