edsl 0.1.39.dev2__py3-none-any.whl → 0.1.39.dev3__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 (334) hide show
  1. edsl/Base.py +332 -385
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +49 -57
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +867 -1079
  7. edsl/agents/AgentList.py +413 -551
  8. edsl/agents/Invigilator.py +233 -285
  9. edsl/agents/InvigilatorBase.py +270 -254
  10. edsl/agents/PromptConstructor.py +354 -252
  11. edsl/agents/__init__.py +3 -2
  12. edsl/agents/descriptors.py +99 -99
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +279 -279
  26. edsl/config.py +157 -177
  27. edsl/conversation/Conversation.py +290 -290
  28. edsl/conversation/car_buying.py +58 -59
  29. edsl/conversation/chips.py +95 -95
  30. edsl/conversation/mug_negotiation.py +81 -81
  31. edsl/conversation/next_speaker_utilities.py +93 -93
  32. edsl/coop/PriceFetcher.py +54 -54
  33. edsl/coop/__init__.py +2 -2
  34. edsl/coop/coop.py +1028 -1090
  35. edsl/coop/utils.py +131 -131
  36. edsl/data/Cache.py +555 -562
  37. edsl/data/CacheEntry.py +233 -230
  38. edsl/data/CacheHandler.py +149 -170
  39. edsl/data/RemoteCacheSync.py +78 -78
  40. edsl/data/SQLiteDict.py +292 -292
  41. edsl/data/__init__.py +4 -5
  42. edsl/data/orm.py +10 -10
  43. edsl/data_transfer_models.py +73 -74
  44. edsl/enums.py +175 -195
  45. edsl/exceptions/BaseException.py +21 -21
  46. edsl/exceptions/__init__.py +54 -54
  47. edsl/exceptions/agents.py +42 -54
  48. edsl/exceptions/cache.py +5 -5
  49. edsl/exceptions/configuration.py +16 -16
  50. edsl/exceptions/coop.py +10 -10
  51. edsl/exceptions/data.py +14 -14
  52. edsl/exceptions/general.py +34 -34
  53. edsl/exceptions/jobs.py +33 -33
  54. edsl/exceptions/language_models.py +63 -63
  55. edsl/exceptions/prompts.py +15 -15
  56. edsl/exceptions/questions.py +91 -109
  57. edsl/exceptions/results.py +29 -29
  58. edsl/exceptions/scenarios.py +22 -29
  59. edsl/exceptions/surveys.py +37 -37
  60. edsl/inference_services/AnthropicService.py +87 -84
  61. edsl/inference_services/AwsBedrock.py +120 -118
  62. edsl/inference_services/AzureAI.py +217 -215
  63. edsl/inference_services/DeepInfraService.py +18 -18
  64. edsl/inference_services/GoogleService.py +148 -139
  65. edsl/inference_services/GroqService.py +20 -20
  66. edsl/inference_services/InferenceServiceABC.py +147 -80
  67. edsl/inference_services/InferenceServicesCollection.py +97 -122
  68. edsl/inference_services/MistralAIService.py +123 -120
  69. edsl/inference_services/OllamaService.py +18 -18
  70. edsl/inference_services/OpenAIService.py +224 -221
  71. edsl/inference_services/PerplexityService.py +163 -160
  72. edsl/inference_services/TestService.py +89 -92
  73. edsl/inference_services/TogetherAIService.py +170 -170
  74. edsl/inference_services/models_available_cache.py +118 -118
  75. edsl/inference_services/rate_limits_cache.py +25 -25
  76. edsl/inference_services/registry.py +41 -41
  77. edsl/inference_services/write_available.py +10 -10
  78. edsl/jobs/Answers.py +56 -43
  79. edsl/jobs/Jobs.py +898 -757
  80. edsl/jobs/JobsChecks.py +147 -172
  81. edsl/jobs/JobsPrompts.py +268 -270
  82. edsl/jobs/JobsRemoteInferenceHandler.py +239 -287
  83. edsl/jobs/__init__.py +1 -1
  84. edsl/jobs/buckets/BucketCollection.py +63 -104
  85. edsl/jobs/buckets/ModelBuckets.py +65 -65
  86. edsl/jobs/buckets/TokenBucket.py +251 -283
  87. edsl/jobs/interviews/Interview.py +661 -358
  88. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  89. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  90. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  91. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  92. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  93. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  94. edsl/jobs/interviews/ReportErrors.py +66 -66
  95. edsl/jobs/interviews/interview_status_enum.py +9 -9
  96. edsl/jobs/runners/JobsRunnerAsyncio.py +466 -421
  97. edsl/jobs/runners/JobsRunnerStatus.py +330 -330
  98. edsl/jobs/tasks/QuestionTaskCreator.py +242 -244
  99. edsl/jobs/tasks/TaskCreators.py +64 -64
  100. edsl/jobs/tasks/TaskHistory.py +450 -449
  101. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  102. edsl/jobs/tasks/task_status_enum.py +163 -161
  103. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  104. edsl/jobs/tokens/TokenUsage.py +34 -34
  105. edsl/language_models/KeyLookup.py +30 -0
  106. edsl/language_models/LanguageModel.py +668 -571
  107. edsl/language_models/ModelList.py +155 -153
  108. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  109. edsl/language_models/__init__.py +3 -2
  110. edsl/language_models/fake_openai_call.py +15 -15
  111. edsl/language_models/fake_openai_service.py +61 -61
  112. edsl/language_models/registry.py +190 -180
  113. edsl/language_models/repair.py +156 -156
  114. edsl/language_models/unused/ReplicateBase.py +83 -0
  115. edsl/language_models/utilities.py +64 -65
  116. edsl/notebooks/Notebook.py +258 -263
  117. edsl/notebooks/__init__.py +1 -1
  118. edsl/prompts/Prompt.py +362 -352
  119. edsl/prompts/__init__.py +2 -2
  120. edsl/questions/AnswerValidatorMixin.py +289 -334
  121. edsl/questions/QuestionBase.py +664 -509
  122. edsl/questions/QuestionBaseGenMixin.py +161 -165
  123. edsl/questions/QuestionBasePromptsMixin.py +217 -221
  124. edsl/questions/QuestionBudget.py +227 -227
  125. edsl/questions/QuestionCheckBox.py +359 -359
  126. edsl/questions/QuestionExtract.py +182 -182
  127. edsl/questions/QuestionFreeText.py +114 -113
  128. edsl/questions/QuestionFunctional.py +166 -166
  129. edsl/questions/QuestionList.py +231 -229
  130. edsl/questions/QuestionMultipleChoice.py +286 -330
  131. edsl/questions/QuestionNumerical.py +153 -151
  132. edsl/questions/QuestionRank.py +324 -314
  133. edsl/questions/Quick.py +41 -41
  134. edsl/questions/RegisterQuestionsMeta.py +71 -71
  135. edsl/questions/ResponseValidatorABC.py +174 -200
  136. edsl/questions/SimpleAskMixin.py +73 -74
  137. edsl/questions/__init__.py +26 -27
  138. edsl/questions/compose_questions.py +98 -98
  139. edsl/questions/decorators.py +21 -21
  140. edsl/questions/derived/QuestionLikertFive.py +76 -76
  141. edsl/questions/derived/QuestionLinearScale.py +87 -90
  142. edsl/questions/derived/QuestionTopK.py +93 -93
  143. edsl/questions/derived/QuestionYesNo.py +82 -82
  144. edsl/questions/descriptors.py +413 -427
  145. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  146. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  147. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  148. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  149. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  150. edsl/questions/prompt_templates/question_list.jinja +17 -17
  151. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  152. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  153. edsl/questions/question_registry.py +177 -177
  154. edsl/questions/settings.py +12 -12
  155. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  156. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  157. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  158. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  159. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  160. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  161. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  162. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  163. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  164. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  165. edsl/questions/templates/list/question_presentation.jinja +5 -5
  166. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  167. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  168. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  169. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  170. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  171. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  172. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  173. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  174. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  176. edsl/results/CSSParameterizer.py +108 -108
  177. edsl/results/Dataset.py +424 -587
  178. edsl/results/DatasetExportMixin.py +731 -653
  179. edsl/results/DatasetTree.py +275 -295
  180. edsl/results/Result.py +465 -451
  181. edsl/results/Results.py +1165 -1172
  182. edsl/results/ResultsDBMixin.py +238 -0
  183. edsl/results/ResultsExportMixin.py +43 -45
  184. edsl/results/ResultsFetchMixin.py +33 -33
  185. edsl/results/ResultsGGMixin.py +121 -121
  186. edsl/results/ResultsToolsMixin.py +98 -98
  187. edsl/results/Selector.py +135 -145
  188. edsl/results/TableDisplay.py +198 -125
  189. edsl/results/__init__.py +2 -2
  190. edsl/results/table_display.css +77 -77
  191. edsl/results/tree_explore.py +115 -115
  192. edsl/scenarios/FileStore.py +632 -511
  193. edsl/scenarios/Scenario.py +601 -498
  194. edsl/scenarios/ScenarioHtmlMixin.py +64 -65
  195. edsl/scenarios/ScenarioJoin.py +127 -131
  196. edsl/scenarios/ScenarioList.py +1287 -1430
  197. edsl/scenarios/ScenarioListExportMixin.py +52 -45
  198. edsl/scenarios/ScenarioListPdfMixin.py +261 -239
  199. edsl/scenarios/__init__.py +4 -3
  200. edsl/shared.py +1 -1
  201. edsl/study/ObjectEntry.py +173 -173
  202. edsl/study/ProofOfWork.py +113 -113
  203. edsl/study/SnapShot.py +80 -80
  204. edsl/study/Study.py +528 -521
  205. edsl/study/__init__.py +4 -4
  206. edsl/surveys/DAG.py +148 -148
  207. edsl/surveys/Memory.py +31 -31
  208. edsl/surveys/MemoryPlan.py +244 -244
  209. edsl/surveys/Rule.py +326 -327
  210. edsl/surveys/RuleCollection.py +387 -385
  211. edsl/surveys/Survey.py +1801 -1229
  212. edsl/surveys/SurveyCSS.py +261 -273
  213. edsl/surveys/SurveyExportMixin.py +259 -259
  214. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +179 -181
  215. edsl/surveys/SurveyQualtricsImport.py +284 -284
  216. edsl/surveys/__init__.py +3 -5
  217. edsl/surveys/base.py +53 -53
  218. edsl/surveys/descriptors.py +56 -60
  219. edsl/surveys/instructions/ChangeInstruction.py +49 -48
  220. edsl/surveys/instructions/Instruction.py +65 -56
  221. edsl/surveys/instructions/InstructionCollection.py +77 -82
  222. edsl/templates/error_reporting/base.html +23 -23
  223. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  224. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  225. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  226. edsl/templates/error_reporting/interview_details.html +115 -115
  227. edsl/templates/error_reporting/interviews.html +19 -19
  228. edsl/templates/error_reporting/overview.html +4 -4
  229. edsl/templates/error_reporting/performance_plot.html +1 -1
  230. edsl/templates/error_reporting/report.css +73 -73
  231. edsl/templates/error_reporting/report.html +117 -117
  232. edsl/templates/error_reporting/report.js +25 -25
  233. edsl/tools/__init__.py +1 -1
  234. edsl/tools/clusters.py +192 -192
  235. edsl/tools/embeddings.py +27 -27
  236. edsl/tools/embeddings_plotting.py +118 -118
  237. edsl/tools/plotting.py +112 -112
  238. edsl/tools/summarize.py +18 -18
  239. edsl/utilities/SystemInfo.py +28 -28
  240. edsl/utilities/__init__.py +22 -22
  241. edsl/utilities/ast_utilities.py +25 -25
  242. edsl/utilities/data/Registry.py +6 -6
  243. edsl/utilities/data/__init__.py +1 -1
  244. edsl/utilities/data/scooter_results.json +1 -1
  245. edsl/utilities/decorators.py +77 -77
  246. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  247. edsl/utilities/interface.py +627 -627
  248. edsl/utilities/naming_utilities.py +263 -263
  249. edsl/utilities/repair_functions.py +28 -28
  250. edsl/utilities/restricted_python.py +70 -70
  251. edsl/utilities/utilities.py +424 -436
  252. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/LICENSE +21 -21
  253. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/METADATA +10 -12
  254. edsl-0.1.39.dev3.dist-info/RECORD +277 -0
  255. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  256. edsl/agents/QuestionOptionProcessor.py +0 -172
  257. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  258. edsl/coop/CoopFunctionsMixin.py +0 -15
  259. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  260. edsl/exceptions/inference_services.py +0 -5
  261. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  262. edsl/inference_services/AvailableModelFetcher.py +0 -209
  263. edsl/inference_services/ServiceAvailability.py +0 -135
  264. edsl/inference_services/data_structures.py +0 -62
  265. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -188
  266. edsl/jobs/FetchInvigilator.py +0 -40
  267. edsl/jobs/InterviewTaskManager.py +0 -98
  268. edsl/jobs/InterviewsConstructor.py +0 -48
  269. edsl/jobs/JobsComponentConstructor.py +0 -189
  270. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  271. edsl/jobs/RequestTokenEstimator.py +0 -30
  272. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  273. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  274. edsl/jobs/decorators.py +0 -35
  275. edsl/jobs/jobs_status_enums.py +0 -9
  276. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  277. edsl/language_models/ComputeCost.py +0 -63
  278. edsl/language_models/PriceManager.py +0 -127
  279. edsl/language_models/RawResponseHandler.py +0 -106
  280. edsl/language_models/ServiceDataSources.py +0 -0
  281. edsl/language_models/key_management/KeyLookup.py +0 -63
  282. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  283. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  284. edsl/language_models/key_management/__init__.py +0 -0
  285. edsl/language_models/key_management/models.py +0 -131
  286. edsl/notebooks/NotebookToLaTeX.py +0 -142
  287. edsl/questions/ExceptionExplainer.py +0 -77
  288. edsl/questions/HTMLQuestion.py +0 -103
  289. edsl/questions/LoopProcessor.py +0 -149
  290. edsl/questions/QuestionMatrix.py +0 -265
  291. edsl/questions/ResponseValidatorFactory.py +0 -28
  292. edsl/questions/templates/matrix/__init__.py +0 -1
  293. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  294. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  295. edsl/results/MarkdownToDocx.py +0 -122
  296. edsl/results/MarkdownToPDF.py +0 -111
  297. edsl/results/TextEditor.py +0 -50
  298. edsl/results/smart_objects.py +0 -96
  299. edsl/results/table_data_class.py +0 -12
  300. edsl/results/table_renderers.py +0 -118
  301. edsl/scenarios/ConstructDownloadLink.py +0 -109
  302. edsl/scenarios/DirectoryScanner.py +0 -96
  303. edsl/scenarios/DocumentChunker.py +0 -102
  304. edsl/scenarios/DocxScenario.py +0 -16
  305. edsl/scenarios/PdfExtractor.py +0 -40
  306. edsl/scenarios/ScenarioSelector.py +0 -156
  307. edsl/scenarios/file_methods.py +0 -85
  308. edsl/scenarios/handlers/__init__.py +0 -13
  309. edsl/scenarios/handlers/csv.py +0 -38
  310. edsl/scenarios/handlers/docx.py +0 -76
  311. edsl/scenarios/handlers/html.py +0 -37
  312. edsl/scenarios/handlers/json.py +0 -111
  313. edsl/scenarios/handlers/latex.py +0 -5
  314. edsl/scenarios/handlers/md.py +0 -51
  315. edsl/scenarios/handlers/pdf.py +0 -68
  316. edsl/scenarios/handlers/png.py +0 -39
  317. edsl/scenarios/handlers/pptx.py +0 -105
  318. edsl/scenarios/handlers/py.py +0 -294
  319. edsl/scenarios/handlers/sql.py +0 -313
  320. edsl/scenarios/handlers/sqlite.py +0 -149
  321. edsl/scenarios/handlers/txt.py +0 -33
  322. edsl/surveys/ConstructDAG.py +0 -92
  323. edsl/surveys/EditSurvey.py +0 -221
  324. edsl/surveys/InstructionHandler.py +0 -100
  325. edsl/surveys/MemoryManagement.py +0 -72
  326. edsl/surveys/RuleManager.py +0 -172
  327. edsl/surveys/Simulator.py +0 -75
  328. edsl/surveys/SurveyToApp.py +0 -141
  329. edsl/utilities/PrettyList.py +0 -56
  330. edsl/utilities/is_notebook.py +0 -18
  331. edsl/utilities/is_valid_variable_name.py +0 -11
  332. edsl/utilities/remove_edsl_version.py +0 -24
  333. edsl-0.1.39.dev2.dist-info/RECORD +0 -352
  334. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/WHEEL +0 -0
@@ -1,172 +0,0 @@
1
- from jinja2 import Environment, meta
2
- from typing import List, Optional, Union
3
-
4
-
5
- class QuestionOptionProcessor:
6
- """
7
- Class that manages the processing of question options.
8
- These can be provided directly, as a template string, or fetched from prior answers or the scenario.
9
- """
10
-
11
- def __init__(self, prompt_constructor):
12
- self.prompt_constructor = prompt_constructor
13
-
14
- @staticmethod
15
- def _get_default_options() -> list:
16
- """Return default placeholder options."""
17
- return [f"<< Option {i} - Placeholder >>" for i in range(1, 4)]
18
-
19
- @staticmethod
20
- def _parse_template_variable(template_str: str) -> str:
21
- """
22
- Extract the variable name from a template string.
23
-
24
- Args:
25
- template_str (str): Jinja template string
26
-
27
- Returns:
28
- str: Name of the first undefined variable in the template
29
-
30
- >>> QuestionOptionProcessor._parse_template_variable("Here are some {{ options }}")
31
- 'options'
32
- >>> QuestionOptionProcessor._parse_template_variable("Here are some {{ options }} and {{ other }}")
33
- Traceback (most recent call last):
34
- ...
35
- ValueError: Multiple variables found in template string
36
- >>> QuestionOptionProcessor._parse_template_variable("Here are some")
37
- Traceback (most recent call last):
38
- ...
39
- ValueError: No variables found in template string
40
- """
41
- env = Environment()
42
- parsed_content = env.parse(template_str)
43
- undeclared_variables = list(meta.find_undeclared_variables(parsed_content))
44
- if not undeclared_variables:
45
- raise ValueError("No variables found in template string")
46
- if len(undeclared_variables) > 1:
47
- raise ValueError("Multiple variables found in template string")
48
- return undeclared_variables[0]
49
-
50
- @staticmethod
51
- def _get_options_from_scenario(
52
- scenario: dict, option_key: str
53
- ) -> Union[list, None]:
54
- """
55
- Try to get options from scenario data.
56
-
57
- >>> from edsl import Scenario
58
- >>> scenario = Scenario({"options": ["Option 1", "Option 2"]})
59
- >>> QuestionOptionProcessor._get_options_from_scenario(scenario, "options")
60
- ['Option 1', 'Option 2']
61
-
62
-
63
- Returns:
64
- list | None: List of options if found in scenario, None otherwise
65
- """
66
- scenario_options = scenario.get(option_key)
67
- return scenario_options if isinstance(scenario_options, list) else None
68
-
69
- @staticmethod
70
- def _get_options_from_prior_answers(
71
- prior_answers: dict, option_key: str
72
- ) -> Union[list, None]:
73
- """
74
- Try to get options from prior answers.
75
-
76
- prior_answers (dict): Dictionary of prior answers
77
- option_key (str): Key to look up in prior answers
78
-
79
- >>> from edsl import QuestionList as Q
80
- >>> q = Q.example()
81
- >>> q.answer = ["Option 1", "Option 2"]
82
- >>> prior_answers = {"options": q}
83
- >>> QuestionOptionProcessor._get_options_from_prior_answers(prior_answers, "options")
84
- ['Option 1', 'Option 2']
85
- >>> QuestionOptionProcessor._get_options_from_prior_answers(prior_answers, "wrong_key") is None
86
- True
87
-
88
- Returns:
89
- list | None: List of options if found in prior answers, None otherwise
90
- """
91
- prior_answer = prior_answers.get(option_key)
92
- if prior_answer and hasattr(prior_answer, "answer"):
93
- if isinstance(prior_answer.answer, list):
94
- return prior_answer.answer
95
- return None
96
-
97
- def get_question_options(self, question_data: dict) -> list:
98
- """
99
- Extract and process question options from question data.
100
-
101
- Args:
102
- question_data (dict): Dictionary containing question configuration
103
-
104
- Returns:
105
- list: List of question options. Returns default placeholders if no valid options found.
106
-
107
- >>> class MockPromptConstructor:
108
- ... pass
109
- >>> mpc = MockPromptConstructor()
110
- >>> from edsl import Scenario
111
- >>> mpc.scenario = Scenario({"options": ["Option 1", "Option 2"]})
112
- >>> processor = QuestionOptionProcessor(mpc)
113
-
114
- The basic case where options are directly provided:
115
-
116
- >>> question_data = {"question_options": ["Option 1", "Option 2"]}
117
- >>> processor.get_question_options(question_data)
118
- ['Option 1', 'Option 2']
119
-
120
- The case where options are provided as a template string:
121
-
122
- >>> question_data = {"question_options": "{{ options }}"}
123
- >>> processor.get_question_options(question_data)
124
- ['Option 1', 'Option 2']
125
-
126
- The case where there is a templace string but it's in the prior answers:
127
-
128
- >>> class MockQuestion:
129
- ... pass
130
- >>> q0 = MockQuestion()
131
- >>> q0.answer = ["Option 1", "Option 2"]
132
- >>> mpc.prior_answers_dict = lambda: {'q0': q0}
133
- >>> processor = QuestionOptionProcessor(mpc)
134
- >>> question_data = {"question_options": "{{ q0 }}"}
135
- >>> processor.get_question_options(question_data)
136
- ['Option 1', 'Option 2']
137
-
138
- The case we're no options are found:
139
- >>> processor.get_question_options({"question_options": "{{ poop }}"})
140
- ['<< Option 1 - Placeholder >>', '<< Option 2 - Placeholder >>', '<< Option 3 - Placeholder >>']
141
-
142
- """
143
- options_entry = question_data.get("question_options")
144
-
145
- # If not a template string, return as is or default
146
- if not isinstance(options_entry, str):
147
- return options_entry if options_entry else self._get_default_options()
148
-
149
- # Parse template to get variable name
150
- option_key = self._parse_template_variable(options_entry)
151
-
152
- # Try getting options from scenario
153
- scenario_options = self._get_options_from_scenario(
154
- self.prompt_constructor.scenario, option_key
155
- )
156
- if scenario_options:
157
- return scenario_options
158
-
159
- # Try getting options from prior answers
160
- prior_answer_options = self._get_options_from_prior_answers(
161
- self.prompt_constructor.prior_answers_dict(), option_key
162
- )
163
- if prior_answer_options:
164
- return prior_answer_options
165
-
166
- return self._get_default_options()
167
-
168
-
169
- if __name__ == "__main__":
170
- import doctest
171
-
172
- doctest.testmod()
@@ -1,137 +0,0 @@
1
- from jinja2 import Environment, meta
2
- from typing import Any, Set, TYPE_CHECKING
3
-
4
- if TYPE_CHECKING:
5
- from edsl.agents.PromptConstructor import PromptConstructor
6
- from edsl.scenarios.Scenario import Scenario
7
-
8
-
9
- class QuestionTemplateReplacementsBuilder:
10
- def __init__(self, prompt_constructor: "PromptConstructor"):
11
- self.prompt_constructor = prompt_constructor
12
-
13
- def question_file_keys(self):
14
- question_text = self.prompt_constructor.question.question_text
15
- file_keys = self._find_file_keys(self.prompt_constructor.scenario)
16
- return self._extract_file_keys_from_question_text(question_text, file_keys)
17
-
18
- def scenario_file_keys(self):
19
- return self._find_file_keys(self.prompt_constructor.scenario)
20
-
21
- def get_jinja2_variables(template_str: str) -> Set[str]:
22
- """
23
- Extracts all variable names from a Jinja2 template using Jinja2's built-in parsing.
24
-
25
- Args:
26
- template_str (str): The Jinja2 template string
27
-
28
- Returns:
29
- Set[str]: A set of variable names found in the template
30
- """
31
- env = Environment()
32
- ast = env.parse(template_str)
33
- return meta.find_undeclared_variables(ast)
34
-
35
- @staticmethod
36
- def _find_file_keys(scenario: "Scenario") -> list:
37
- """We need to find all the keys in the scenario that refer to FileStore objects.
38
- These will be used to append to the prompt a list of files that are part of the scenario.
39
-
40
- >>> from edsl import Scenario
41
- >>> from edsl.scenarios.FileStore import FileStore
42
- >>> import tempfile
43
- >>> with tempfile.NamedTemporaryFile() as f:
44
- ... _ = f.write(b"Hello, world!")
45
- ... _ = f.seek(0)
46
- ... fs = FileStore(f.name)
47
- ... scenario = Scenario({"fs_file": fs, 'a': 1})
48
- ... QuestionTemplateReplacementsBuilder._find_file_keys(scenario)
49
- ['fs_file']
50
- """
51
- from edsl.scenarios.FileStore import FileStore
52
-
53
- file_entries = []
54
- for key, value in scenario.items():
55
- if isinstance(value, FileStore):
56
- file_entries.append(key)
57
- return file_entries
58
-
59
- @staticmethod
60
- def _extract_file_keys_from_question_text(
61
- question_text: str, scenario_file_keys: list
62
- ) -> list:
63
- """
64
- Extracts the file keys from a question text.
65
-
66
- >>> from edsl import Scenario
67
- >>> from edsl.scenarios.FileStore import FileStore
68
- >>> import tempfile
69
- >>> with tempfile.NamedTemporaryFile() as f:
70
- ... _ = f.write(b"Hello, world!")
71
- ... _ = f.seek(0)
72
- ... fs = FileStore(f.name)
73
- ... scenario = Scenario({"fs_file": fs, 'a': 1})
74
- ... QuestionTemplateReplacementsBuilder._extract_file_keys_from_question_text("{{ fs_file }}", ['fs_file'])
75
- ['fs_file']
76
- """
77
- variables = QuestionTemplateReplacementsBuilder.get_jinja2_variables(
78
- question_text
79
- )
80
- question_file_keys = []
81
- for var in variables:
82
- if var in scenario_file_keys:
83
- question_file_keys.append(var)
84
- return question_file_keys
85
-
86
- def _scenario_replacements(self) -> dict[str, Any]:
87
- # File references dictionary
88
- file_refs = {key: f"<see file {key}>" for key in self.scenario_file_keys()}
89
-
90
- # Scenario items excluding file keys
91
- scenario_items = {
92
- k: v
93
- for k, v in self.prompt_constructor.scenario.items()
94
- if k not in self.scenario_file_keys()
95
- }
96
- return {**file_refs, **scenario_items}
97
-
98
- @staticmethod
99
- def _question_data_replacements(
100
- question: dict, question_data: dict
101
- ) -> dict[str, Any]:
102
- """Builds a dictionary of replacement values for rendering a prompt by combining multiple data sources.
103
-
104
- >>> from edsl import QuestionMultipleChoice
105
- >>> q = QuestionMultipleChoice(question_text="Do you like school?", question_name = "q0", question_options = ["yes", "no"])
106
- >>> QuestionTemplateReplacementsBuilder._question_data_replacements(q, q.data)
107
- {'use_code': False, 'include_comment': True, 'question_name': 'q0', 'question_text': 'Do you like school?', 'question_options': ['yes', 'no']}
108
-
109
- """
110
- question_settings = {
111
- "use_code": getattr(question, "_use_code", True),
112
- "include_comment": getattr(question, "_include_comment", False),
113
- }
114
- return {**question_settings, **question_data}
115
-
116
- def build_replacement_dict(self, question_data: dict) -> dict[str, Any]:
117
- """Builds a dictionary of replacement values for rendering a prompt by combining multiple data sources."""
118
- rpl = {}
119
- rpl["scenario"] = self._scenario_replacements()
120
- rpl["question"] = self._question_data_replacements(
121
- self.prompt_constructor.question, question_data
122
- )
123
- rpl["prior_answers"] = self.prompt_constructor.prior_answers_dict()
124
- rpl["agent"] = {"agent": self.prompt_constructor.agent}
125
-
126
- # Combine all dictionaries using dict.update() for clarity
127
- replacement_dict = {}
128
- for r in rpl.values():
129
- replacement_dict.update(r)
130
-
131
- return replacement_dict
132
-
133
-
134
- if __name__ == "__main__":
135
- import doctest
136
-
137
- doctest.testmod()
@@ -1,15 +0,0 @@
1
- class CoopFunctionsMixin:
2
- def better_names(self, existing_names):
3
- from edsl import QuestionList, Scenario
4
-
5
- s = Scenario({"existing_names": existing_names})
6
- q = QuestionList(
7
- question_text="""The following colum names are already in use: {{ existing_names }}
8
- Please provide new names for the columns.
9
- They should be short, one or two words, and unique. They should be valid Python idenifiers.
10
- No spaces - use underscores instead.
11
- """,
12
- question_name="better_names",
13
- )
14
- results = q.by(s).run(verbose=False)
15
- return results.select("answer.better_names").first()
@@ -1,125 +0,0 @@
1
- from pathlib import Path
2
- import os
3
- import platformdirs
4
-
5
-
6
- import sys
7
- import select
8
-
9
-
10
- def get_input_with_timeout(prompt, timeout=5, default="y"):
11
- print(prompt, end="", flush=True)
12
- ready, _, _ = select.select([sys.stdin], [], [], timeout)
13
- if ready:
14
- return sys.stdin.readline().strip()
15
- print(f"\nNo input received within {timeout} seconds. Using default: {default}")
16
- return default
17
-
18
-
19
- class ExpectedParrotKeyHandler:
20
- asked_to_store_file_name = "asked_to_store.txt"
21
- ep_key_file_name = "ep_api_key.txt"
22
- application_name = "edsl"
23
-
24
- @property
25
- def config_dir(self):
26
- return platformdirs.user_config_dir(self.application_name)
27
-
28
- def _ep_key_file_exists(self) -> bool:
29
- """Check if the Expected Parrot key file exists."""
30
- return Path(self.config_dir).joinpath(self.ep_key_file_name).exists()
31
-
32
- def ok_to_ask_to_store(self):
33
- """Check if it's okay to ask the user to store the key."""
34
- from edsl.config import CONFIG
35
-
36
- if CONFIG.get("EDSL_RUN_MODE") != "production":
37
- return False
38
-
39
- return (
40
- not Path(self.config_dir).joinpath(self.asked_to_store_file_name).exists()
41
- )
42
-
43
- def reset_asked_to_store(self):
44
- """Reset the flag that indicates whether the user has been asked to store the key."""
45
- asked_to_store_path = Path(self.config_dir).joinpath(
46
- self.asked_to_store_file_name
47
- )
48
- if asked_to_store_path.exists():
49
- os.remove(asked_to_store_path)
50
- print(
51
- "Deleted the file that indicates whether the user has been asked to store the key."
52
- )
53
-
54
- def ask_to_store(self, api_key) -> bool:
55
- """Ask the user if they want to store the Expected Parrot key. If they say "yes", store it."""
56
- if self.ok_to_ask_to_store():
57
- # can_we_store = get_input_with_timeout(
58
- # "Would you like to store your Expected Parrot key for future use? (y/n): ",
59
- # timeout=5,
60
- # default="y",
61
- # )
62
- can_we_store = "y"
63
- if can_we_store.lower() == "y":
64
- Path(self.config_dir).mkdir(parents=True, exist_ok=True)
65
- self.store_ep_api_key(api_key)
66
- # print("Stored Expected Parrot API key at ", self.config_dir)
67
- return True
68
- else:
69
- Path(self.config_dir).mkdir(parents=True, exist_ok=True)
70
- with open(
71
- Path(self.config_dir).joinpath(self.asked_to_store_file_name), "w"
72
- ) as f:
73
- f.write("Yes")
74
- return False
75
-
76
- def get_ep_api_key(self):
77
- # check if the key is stored in the config_dir
78
- api_key = None
79
- api_key_from_cache = None
80
- api_key_from_os = None
81
-
82
- if self._ep_key_file_exists():
83
- with open(Path(self.config_dir).joinpath(self.ep_key_file_name), "r") as f:
84
- api_key_from_cache = f.read().strip()
85
-
86
- api_key_from_os = os.getenv("EXPECTED_PARROT_API_KEY")
87
-
88
- if api_key_from_os and api_key_from_cache:
89
- if api_key_from_os != api_key_from_cache:
90
- import warnings
91
-
92
- warnings.warn(
93
- "WARNING: The Expected Parrot API key from the environment variable "
94
- "differs from the one stored in the config directory. Using the one "
95
- "from the environment variable."
96
- )
97
- api_key = api_key_from_os
98
-
99
- if api_key_from_os and not api_key_from_cache:
100
- api_key = api_key_from_os
101
-
102
- if not api_key_from_os and api_key_from_cache:
103
- api_key = api_key_from_cache
104
-
105
- if api_key is not None:
106
- _ = self.ask_to_store(api_key)
107
- return api_key
108
-
109
- def delete_ep_api_key(self):
110
- key_path = Path(self.config_dir) / self.ep_key_file_name
111
- if key_path.exists():
112
- os.remove(key_path)
113
- print("Deleted Expected Parrot API key at ", key_path)
114
-
115
- def store_ep_api_key(self, api_key):
116
- # Create the directory if it doesn't exist
117
- os.makedirs(self.config_dir, exist_ok=True)
118
-
119
- # Create the path for the key file
120
- key_path = Path(self.config_dir) / self.ep_key_file_name
121
-
122
- # Save the key
123
- with open(key_path, "w") as f:
124
- f.write(api_key)
125
- # print("Stored Expected Parrot API key at ", key_path)
@@ -1,5 +0,0 @@
1
- from edsl.exceptions.BaseException import BaseException
2
-
3
-
4
- class InferenceServiceError(BaseException):
5
- relevant_doc = "https://docs.expectedparrot.com/"
@@ -1,184 +0,0 @@
1
- from typing import List, Optional, get_args, Union
2
- from pathlib import Path
3
- import sqlite3
4
- from datetime import datetime
5
- import tempfile
6
- from platformdirs import user_cache_dir
7
- from dataclasses import dataclass
8
- import os
9
-
10
- from edsl.inference_services.data_structures import LanguageModelInfo, AvailableModels
11
- from edsl.enums import InferenceServiceLiteral
12
-
13
-
14
- class AvailableModelCacheHandler:
15
- MAX_ROWS = 1000
16
- CACHE_VALIDITY_HOURS = 48
17
-
18
- def __init__(
19
- self,
20
- cache_validity_hours: int = 48,
21
- verbose: bool = False,
22
- testing_db_name: str = None,
23
- ):
24
- self.cache_validity_hours = cache_validity_hours
25
- self.verbose = verbose
26
-
27
- if testing_db_name:
28
- self.cache_dir = Path(tempfile.mkdtemp())
29
- self.db_path = self.cache_dir / testing_db_name
30
- else:
31
- self.cache_dir = Path(user_cache_dir("edsl", "model_availability"))
32
- self.db_path = self.cache_dir / "available_models.db"
33
- self.cache_dir.mkdir(parents=True, exist_ok=True)
34
-
35
- if os.path.exists(self.db_path):
36
- if self.verbose:
37
- print(f"Using existing cache DB: {self.db_path}")
38
- else:
39
- self._initialize_db()
40
-
41
- @property
42
- def path_to_db(self):
43
- return self.db_path
44
-
45
- def _initialize_db(self):
46
- """Initialize the SQLite database with the required schema."""
47
- with sqlite3.connect(self.db_path) as conn:
48
- cursor = conn.cursor()
49
- # Drop the old table if it exists (for migration)
50
- cursor.execute("DROP TABLE IF EXISTS model_cache")
51
- cursor.execute(
52
- """
53
- CREATE TABLE IF NOT EXISTS model_cache (
54
- timestamp DATETIME NOT NULL,
55
- model_name TEXT NOT NULL,
56
- service_name TEXT NOT NULL,
57
- UNIQUE(model_name, service_name)
58
- )
59
- """
60
- )
61
- conn.commit()
62
-
63
- def _prune_old_entries(self, conn: sqlite3.Connection):
64
- """Delete oldest entries when MAX_ROWS is exceeded."""
65
- cursor = conn.cursor()
66
- cursor.execute("SELECT COUNT(*) FROM model_cache")
67
- count = cursor.fetchone()[0]
68
-
69
- if count > self.MAX_ROWS:
70
- cursor.execute(
71
- """
72
- DELETE FROM model_cache
73
- WHERE rowid IN (
74
- SELECT rowid
75
- FROM model_cache
76
- ORDER BY timestamp ASC
77
- LIMIT ?
78
- )
79
- """,
80
- (count - self.MAX_ROWS,),
81
- )
82
- conn.commit()
83
-
84
- @classmethod
85
- def example_models(cls) -> List[LanguageModelInfo]:
86
- return [
87
- LanguageModelInfo(
88
- "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", "deep_infra"
89
- ),
90
- LanguageModelInfo("openai/gpt-4", "openai"),
91
- ]
92
-
93
- def add_models_to_cache(self, models_data: List[LanguageModelInfo]):
94
- """Add new models to the cache, updating timestamps for existing entries."""
95
- current_time = datetime.now()
96
-
97
- with sqlite3.connect(self.db_path) as conn:
98
- cursor = conn.cursor()
99
- for model in models_data:
100
- cursor.execute(
101
- """
102
- INSERT INTO model_cache (timestamp, model_name, service_name)
103
- VALUES (?, ?, ?)
104
- ON CONFLICT(model_name, service_name)
105
- DO UPDATE SET timestamp = excluded.timestamp
106
- """,
107
- (current_time, model.model_name, model.service_name),
108
- )
109
-
110
- # self._prune_old_entries(conn)
111
- conn.commit()
112
-
113
- def reset_cache(self):
114
- """Clear all entries from the cache."""
115
- with sqlite3.connect(self.db_path) as conn:
116
- cursor = conn.cursor()
117
- cursor.execute("DELETE FROM model_cache")
118
- conn.commit()
119
-
120
- @property
121
- def num_cache_entries(self):
122
- """Return the number of entries in the cache."""
123
- with sqlite3.connect(self.db_path) as conn:
124
- cursor = conn.cursor()
125
- cursor.execute("SELECT COUNT(*) FROM model_cache")
126
- count = cursor.fetchone()[0]
127
- return count
128
-
129
- def models(
130
- self,
131
- service: Optional[InferenceServiceLiteral],
132
- ) -> Union[None, AvailableModels]:
133
- """Return the available models within the cache validity period."""
134
- # if service is not None:
135
- # assert service in get_args(InferenceServiceLiteral)
136
-
137
- with sqlite3.connect(self.db_path) as conn:
138
- cursor = conn.cursor()
139
- valid_time = datetime.now().timestamp() - (self.cache_validity_hours * 3600)
140
-
141
- if self.verbose:
142
- print(f"Fetching all with timestamp greater than {valid_time}")
143
-
144
- cursor.execute(
145
- """
146
- SELECT DISTINCT model_name, service_name
147
- FROM model_cache
148
- WHERE timestamp > ?
149
- ORDER BY timestamp DESC
150
- """,
151
- (valid_time,),
152
- )
153
-
154
- results = cursor.fetchall()
155
- if not results:
156
- if self.verbose:
157
- print("No results found in cache DB.")
158
- return None
159
-
160
- matching_models = [
161
- LanguageModelInfo(model_name=row[0], service_name=row[1])
162
- for row in results
163
- ]
164
-
165
- if self.verbose:
166
- print(f"Found {len(matching_models)} models in cache DB.")
167
- if service:
168
- matching_models = [
169
- model for model in matching_models if model.service_name == service
170
- ]
171
-
172
- return AvailableModels(matching_models)
173
-
174
-
175
- if __name__ == "__main__":
176
- import doctest
177
-
178
- doctest.testmod()
179
- # cache_handler = AvailableModelCacheHandler(verbose=True)
180
- # models_data = cache_handler.example_models()
181
- # cache_handler.add_models_to_cache(models_data)
182
- # print(cache_handler.models())
183
- # cache_handler.clear_cache()
184
- # print(cache_handler.models())