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,98 +0,0 @@
1
- from __future__ import annotations
2
- import asyncio
3
- from typing import Any, Type, List, Generator, Optional, Union, TYPE_CHECKING
4
-
5
- if TYPE_CHECKING:
6
- from edsl.questions import QuestionBase
7
- from edsl.jobs.tokens.InterviewTokenUsage import InterviewTokenUsage
8
- from edsl.jobs.interviews.InterviewStatusDictionary import InterviewStatusDictionary
9
- from edsl.jobs.interviews.InterviewStatusLog import InterviewStatusLog
10
-
11
-
12
- class InterviewTaskManager:
13
- """Handles creation and management of interview tasks."""
14
-
15
- def __init__(self, survey, iteration=0):
16
- from edsl.jobs.tasks.TaskCreators import TaskCreators
17
- from edsl.jobs.interviews.InterviewStatusLog import InterviewStatusLog
18
-
19
- self.survey = survey
20
- self.iteration = iteration
21
- self.task_creators = TaskCreators()
22
- self.to_index = {
23
- question_name: index
24
- for index, question_name in enumerate(self.survey.question_names)
25
- }
26
- self._task_status_log_dict = InterviewStatusLog()
27
-
28
- def build_question_tasks(
29
- self, answer_func, token_estimator, model_buckets
30
- ) -> list[asyncio.Task]:
31
- """Create tasks for all questions with proper dependencies."""
32
- tasks = []
33
- for question in self.survey.questions:
34
- dependencies = self._get_task_dependencies(tasks, question)
35
- task = self._create_single_task(
36
- question=question,
37
- dependencies=dependencies,
38
- answer_func=answer_func,
39
- token_estimator=token_estimator,
40
- model_buckets=model_buckets,
41
- )
42
- tasks.append(task)
43
- return tuple(tasks)
44
-
45
- def _get_task_dependencies(
46
- self, existing_tasks: list[asyncio.Task], question: "QuestionBase"
47
- ) -> list[asyncio.Task]:
48
- """Get tasks that must be completed before the given question."""
49
- dag = self.survey.dag(textify=True)
50
- parents = dag.get(question.question_name, [])
51
- return [existing_tasks[self.to_index[parent_name]] for parent_name in parents]
52
-
53
- def _create_single_task(
54
- self,
55
- question: "QuestionBase",
56
- dependencies: list[asyncio.Task],
57
- answer_func,
58
- token_estimator,
59
- model_buckets,
60
- ) -> asyncio.Task:
61
- """Create a single question task with its dependencies."""
62
- from edsl.jobs.tasks.QuestionTaskCreator import QuestionTaskCreator
63
-
64
- task_creator = QuestionTaskCreator(
65
- question=question,
66
- answer_question_func=answer_func,
67
- token_estimator=token_estimator,
68
- model_buckets=model_buckets,
69
- iteration=self.iteration,
70
- )
71
-
72
- for dependency in dependencies:
73
- task_creator.add_dependency(dependency)
74
-
75
- self.task_creators[question.question_name] = task_creator
76
- return task_creator.generate_task()
77
-
78
- @property
79
- def task_status_logs(self) -> "InterviewStatusLog":
80
- """Return the task status logs for the interview.
81
-
82
- The keys are the question names; the values are the lists of status log changes for each task.
83
- """
84
- for task_creator in self.task_creators.values():
85
- self._task_status_log_dict[
86
- task_creator.question.question_name
87
- ] = task_creator.status_log
88
- return self._task_status_log_dict
89
-
90
- @property
91
- def token_usage(self) -> "InterviewTokenUsage":
92
- """Determine how many tokens were used for the interview."""
93
- return self.task_creators.token_usage
94
-
95
- @property
96
- def interview_status(self) -> "InterviewStatusDictionary":
97
- """Return a dictionary mapping task status codes to counts."""
98
- return self.task_creators.interview_status
@@ -1,48 +0,0 @@
1
- from typing import Generator, TYPE_CHECKING
2
- from itertools import product
3
-
4
- if TYPE_CHECKING:
5
- from edsl.jobs.interviews.Interview import Interview
6
-
7
-
8
- class InterviewsConstructor:
9
- def __init__(self, jobs):
10
- self.jobs = jobs
11
-
12
- def create_interviews(self) -> Generator["Interview", None, None]:
13
- """
14
- Generate interviews.
15
-
16
- Note that this sets the agents, model and scenarios if they have not been set. This is a side effect of the method.
17
- This is useful because a user can create a job without setting the agents, models, or scenarios, and the job will still run,
18
- with us filling in defaults.
19
-
20
- """
21
- from edsl.jobs.interviews.Interview import Interview
22
-
23
- agent_index = {
24
- hash(agent): index for index, agent in enumerate(self.jobs.agents)
25
- }
26
- model_index = {
27
- hash(model): index for index, model in enumerate(self.jobs.models)
28
- }
29
- scenario_index = {
30
- hash(scenario): index for index, scenario in enumerate(self.jobs.scenarios)
31
- }
32
-
33
- for agent, scenario, model in product(
34
- self.jobs.agents, self.jobs.scenarios, self.jobs.models
35
- ):
36
- yield Interview(
37
- survey=self.jobs.survey,
38
- agent=agent,
39
- scenario=scenario,
40
- model=model,
41
- skip_retry=self.jobs.skip_retry,
42
- raise_validation_errors=self.jobs.raise_validation_errors,
43
- indices={
44
- "agent": agent_index[hash(agent)],
45
- "model": model_index[hash(model)],
46
- "scenario": scenario_index[hash(scenario)],
47
- },
48
- )
@@ -1,189 +0,0 @@
1
- from typing import Union, Sequence, TYPE_CHECKING
2
-
3
- if TYPE_CHECKING:
4
- from edsl.agents.Agent import Agent
5
- from edsl.language_models.LanguageModel import LanguageModel
6
- from edsl.scenarios.Scenario import Scenario
7
- from edsl.jobs.Jobs import Jobs
8
-
9
-
10
- class JobsComponentConstructor:
11
- "Handles the creation of Agents, Scenarios, and LanguageModels in a job."
12
-
13
- def __init__(self, jobs: "Jobs"):
14
- self.jobs = jobs
15
-
16
- def by(
17
- self,
18
- *args: Union[
19
- "Agent",
20
- "Scenario",
21
- "LanguageModel",
22
- Sequence[Union["Agent", "Scenario", "LanguageModel"]],
23
- ],
24
- ) -> "Jobs":
25
- """
26
- Add Agents, Scenarios and LanguageModels to a job.
27
-
28
- :param args: objects or a sequence (list, tuple, ...) of objects of the same type
29
-
30
- If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
31
- Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
32
-
33
- This 'by' is intended to create a fluent interface.
34
-
35
- >>> from edsl.surveys import Survey
36
- >>> from edsl.questions import QuestionFreeText
37
- >>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
38
- >>> from edsl.jobs import Jobs
39
- >>> j = Jobs(survey = Survey(questions=[q]))
40
- >>> j
41
- Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
42
- >>> from edsl import Agent; a = Agent(traits = {"status": "Sad"})
43
- >>> j.by(a).agents
44
- AgentList([Agent(traits = {'status': 'Sad'})])
45
-
46
-
47
- Notes:
48
- - all objects must implement the 'get_value', 'set_value', and `__add__` methods
49
- - agents: traits of new agents are combined with traits of existing agents. New and existing agents should not have overlapping traits, and do not increase the # agents in the instance
50
- - scenarios: traits of new scenarios are combined with traits of old existing. New scenarios will overwrite overlapping traits, and do not increase the number of scenarios in the instance
51
- - models: new models overwrite old models.
52
- """
53
- from edsl.results.Dataset import Dataset
54
-
55
- if isinstance(
56
- args[0], Dataset
57
- ): # let the user use a Dataset as if it were a ScenarioList
58
- args = args[0].to_scenario_list()
59
-
60
- passed_objects = self._turn_args_to_list(
61
- args
62
- ) # objects can also be passed comma-separated
63
-
64
- current_objects, objects_key = self._get_current_objects_of_this_type(
65
- passed_objects[0]
66
- )
67
-
68
- if not current_objects:
69
- new_objects = passed_objects
70
- else:
71
- new_objects = self._merge_objects(passed_objects, current_objects)
72
-
73
- setattr(self.jobs, objects_key, new_objects) # update the job object
74
- return self.jobs
75
-
76
- @staticmethod
77
- def _turn_args_to_list(args):
78
- """Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments.
79
-
80
- Example:
81
-
82
- >>> JobsComponentConstructor._turn_args_to_list([1,2,3])
83
- [1, 2, 3]
84
-
85
- """
86
-
87
- def did_user_pass_a_sequence(args):
88
- """Return True if the user passed a sequence, False otherwise.
89
-
90
- Example:
91
-
92
- >>> did_user_pass_a_sequence([1,2,3])
93
- True
94
-
95
- >>> did_user_pass_a_sequence(1)
96
- False
97
- """
98
- return len(args) == 1 and isinstance(args[0], Sequence)
99
-
100
- if did_user_pass_a_sequence(args):
101
- container_class = JobsComponentConstructor._get_container_class(args[0][0])
102
- return container_class(args[0])
103
- else:
104
- container_class = JobsComponentConstructor._get_container_class(args[0])
105
- return container_class(args)
106
-
107
- def _get_current_objects_of_this_type(
108
- self, object: Union["Agent", "Scenario", "LanguageModel"]
109
- ) -> tuple[list, str]:
110
- from edsl.agents.Agent import Agent
111
- from edsl.scenarios.Scenario import Scenario
112
- from edsl.language_models.LanguageModel import LanguageModel
113
-
114
- """Return the current objects of the same type as the first argument.
115
-
116
- >>> from edsl.jobs import Jobs
117
- >>> j = JobsComponentConstructor(Jobs.example())
118
- >>> j._get_current_objects_of_this_type(j.agents[0])
119
- (AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'})]), 'agents')
120
- """
121
- class_to_key = {
122
- Agent: "agents",
123
- Scenario: "scenarios",
124
- LanguageModel: "models",
125
- }
126
- for class_type in class_to_key:
127
- if isinstance(object, class_type) or issubclass(
128
- object.__class__, class_type
129
- ):
130
- key = class_to_key[class_type]
131
- break
132
- else:
133
- raise ValueError(
134
- f"First argument must be an Agent, Scenario, or LanguageModel, not {object}"
135
- )
136
- current_objects = getattr(self.jobs, key, None)
137
- return current_objects, key
138
-
139
- @staticmethod
140
- def _get_empty_container_object(object):
141
- from edsl.agents.AgentList import AgentList
142
- from edsl.scenarios.ScenarioList import ScenarioList
143
-
144
- return {"Agent": AgentList([]), "Scenario": ScenarioList([])}.get(
145
- object.__class__.__name__, []
146
- )
147
-
148
- @staticmethod
149
- def _merge_objects(passed_objects, current_objects) -> list:
150
- """
151
- Combine all the existing objects with the new objects.
152
-
153
- For example, if the user passes in 3 agents,
154
- and there are 2 existing agents, this will create 6 new agents
155
- >>> from edsl.jobs import Jobs
156
- >>> JobsComponentConstructor(Jobs(survey = []))._merge_objects([1,2,3], [4,5,6])
157
- [5, 6, 7, 6, 7, 8, 7, 8, 9]
158
- """
159
- new_objects = JobsComponentConstructor._get_empty_container_object(
160
- passed_objects[0]
161
- )
162
- for current_object in current_objects:
163
- for new_object in passed_objects:
164
- new_objects.append(current_object + new_object)
165
- return new_objects
166
-
167
- @staticmethod
168
- def _get_container_class(object):
169
- from edsl.agents.AgentList import AgentList
170
- from edsl.agents.Agent import Agent
171
- from edsl.scenarios.Scenario import Scenario
172
- from edsl.scenarios.ScenarioList import ScenarioList
173
- from edsl.language_models.ModelList import ModelList
174
-
175
- if isinstance(object, Agent):
176
- return AgentList
177
- elif isinstance(object, Scenario):
178
- return ScenarioList
179
- elif isinstance(object, ModelList):
180
- return ModelList
181
- else:
182
- return list
183
-
184
-
185
- if __name__ == "__main__":
186
- """Run the module's doctests."""
187
- import doctest
188
-
189
- doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,239 +0,0 @@
1
- import re
2
- import sys
3
- import uuid
4
- from abc import ABC, abstractmethod
5
- from typing import Optional, Union, Literal, TYPE_CHECKING, List, Dict
6
- from datetime import datetime
7
- from dataclasses import dataclass
8
- from edsl.exceptions.coop import CoopServerResponseError
9
-
10
- from edsl.jobs.jobs_status_enums import JobsStatus
11
-
12
- if TYPE_CHECKING:
13
- from edsl.results.Results import Results
14
-
15
-
16
- @dataclass
17
- class LogMessage:
18
- text: str
19
- status: str
20
- timestamp: datetime
21
- status: JobsStatus
22
-
23
-
24
- @dataclass
25
- class JobsInfo:
26
- job_uuid: str = None
27
- progress_bar_url: str = None
28
- error_report_url: str = None
29
- results_uuid: str = None
30
- results_url: str = None
31
-
32
- pretty_names = {
33
- "job_uuid": "Job UUID",
34
- "progress_bar_url": "Progress Bar URL",
35
- "error_report_url": "Error Report URL",
36
- "results_uuid": "Results UUID",
37
- "results_url": "Results URL",
38
- }
39
-
40
-
41
- class JobLogger(ABC):
42
- def __init__(self, verbose: bool = False):
43
- self.verbose = verbose
44
- self.jobs_info = JobsInfo()
45
-
46
- def add_info(
47
- self,
48
- information_type: Literal[
49
- "job_uuid",
50
- "progress_bar_url",
51
- "error_report_url",
52
- "results_uuid",
53
- "results_url",
54
- ],
55
- value: str,
56
- ):
57
- """Add information to the logger
58
-
59
- >>> j = StdOutJobLogger()
60
- >>> j.add_info("job_uuid", "1234")
61
- >>> j.jobs_info.job_uuid
62
- '1234'
63
- """
64
- if information_type not in self.jobs_info.__annotations__:
65
- raise ValueError(f"Information type {information_type} not supported")
66
- setattr(self.jobs_info, information_type, value)
67
-
68
- @abstractmethod
69
- def update(self, message: str, status: str = "running"):
70
- pass
71
-
72
-
73
- class HTMLTableJobLogger(JobLogger):
74
- def __init__(self, verbose=True, **kwargs):
75
- from IPython.display import display, HTML
76
-
77
- super().__init__(verbose=verbose)
78
- self.display_handle = display(HTML(""), display_id=True)
79
- self.current_message = None
80
- self.log_id = str(uuid.uuid4())
81
- self.is_expanded = True
82
- self.spinner_chars = ["◐", "◓", "◑", "◒"] # Rotating spinner characters
83
- self.spinner_idx = 0
84
-
85
- def _get_table_row(self, key: str, value: str) -> str:
86
- """Generate a table row with key-value pair"""
87
- return f"""
88
- <tr>
89
- <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">{key}</td>
90
- <td style="padding: 8px; border: 1px solid #ddd;">{value if value else 'None'}</td>
91
- </tr>
92
- """
93
-
94
- def _linkify(self, text: str) -> str:
95
- """Convert URLs in text to clickable links"""
96
- url_pattern = r'(https?://[^\s<>"]+|www\.[^\s<>"]+)'
97
- return re.sub(
98
- url_pattern,
99
- r'<a href="\1" target="_blank" style="color: #3b82f6; text-decoration: underline;">\1</a>',
100
- text,
101
- )
102
-
103
- def _get_spinner(self, status: JobsStatus) -> str:
104
- """Get the current spinner frame if status is running"""
105
- if status == JobsStatus.RUNNING:
106
- spinner = self.spinner_chars[self.spinner_idx]
107
- self.spinner_idx = (self.spinner_idx + 1) % len(self.spinner_chars)
108
- return f'<span style="margin-right: 8px;">{spinner}</span>'
109
- elif status == JobsStatus.COMPLETED:
110
- return '<span style="margin-right: 8px; color: #22c55e;">✓</span>'
111
- elif status == JobsStatus.FAILED:
112
- return '<span style="margin-right: 8px; color: #ef4444;">✗</span>'
113
- return ""
114
-
115
- def _get_html(self, status: JobsStatus = JobsStatus.RUNNING) -> str:
116
- """Generate the complete HTML display"""
117
- # Generate table rows for each JobsInfo field
118
- info_rows = ""
119
- for field, _ in self.jobs_info.__annotations__.items():
120
- if field != "pretty_names": # Skip the pretty_names dictionary
121
- value = getattr(self.jobs_info, field)
122
- value = self._linkify(str(value)) if value else None
123
- pretty_name = self.jobs_info.pretty_names.get(
124
- field, field.replace("_", " ").title()
125
- )
126
- info_rows += self._get_table_row(pretty_name, value)
127
-
128
- # Add current message section with spinner
129
- message_html = ""
130
- if self.current_message:
131
- spinner = self._get_spinner(status)
132
- message_html = f"""
133
- <div style="margin-top: 10px; padding: 8px; background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 4px;">
134
- {spinner}<strong>Current Status:</strong> {self._linkify(self.current_message)}
135
- </div>
136
- """
137
-
138
- display_style = "block" if self.is_expanded else "none"
139
- arrow = "▼" if self.is_expanded else "▶"
140
-
141
- return f"""
142
- <div style="font-family: system-ui; max-width: 800px; margin: 10px 0;">
143
- <div onclick="document.getElementById('content-{self.log_id}').style.display = document.getElementById('content-{self.log_id}').style.display === 'none' ? 'block' : 'none';
144
- document.getElementById('arrow-{self.log_id}').innerHTML = document.getElementById('content-{self.log_id}').style.display === 'none' ? '▶' : '▼';"
145
- style="padding: 10px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">
146
- <span id="arrow-{self.log_id}">{arrow}</span> Job Status ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
147
- </div>
148
- <div id="content-{self.log_id}" style="display: {display_style};">
149
- <table style="width: 100%; border-collapse: collapse; background: white; border: 1px solid #ddd;">
150
- {info_rows}
151
- </table>
152
- {message_html}
153
- </div>
154
- </div>
155
- """
156
-
157
- def update(self, message: str, status: JobsStatus = JobsStatus.RUNNING):
158
- """Update the display with new message and current JobsInfo state"""
159
- from IPython.display import HTML
160
-
161
- self.current_message = message
162
- if self.verbose:
163
- self.display_handle.update(HTML(self._get_html(status)))
164
- else:
165
- return None
166
-
167
-
168
- class StdOutJobLogger(JobLogger):
169
- def __init__(self, verbose=True, **kwargs):
170
- super().__init__(verbose=verbose) # Properly call parent's __init__
171
- self.messages: List[LogMessage] = []
172
-
173
- def update(self, message: str, status: JobsStatus = JobsStatus.RUNNING):
174
- log_msg = LogMessage(text=message, status=status, timestamp=datetime.now())
175
- self.messages.append(log_msg)
176
- if self.verbose:
177
- sys.stdout.write(f"│ {message}\n")
178
- sys.stdout.flush()
179
- else:
180
- return None
181
-
182
-
183
- class JupyterJobLogger(JobLogger):
184
- def __init__(self, verbose=True, **kwargs):
185
- from IPython.display import display, HTML
186
-
187
- super().__init__(verbose=verbose)
188
- self.messages = []
189
- self.log_id = str(uuid.uuid4())
190
- self.is_expanded = True
191
- self.display_handle = display(HTML(""), display_id=True)
192
-
193
- def _linkify(self, text):
194
- url_pattern = r'(https?://[^\s<>"]+|www\.[^\s<>"]+)'
195
- return re.sub(
196
- url_pattern,
197
- r'<a href="\1" target="_blank" style="color: #3b82f6; text-decoration: underline;">\1</a>',
198
- text,
199
- )
200
-
201
- def _get_html(self):
202
- messages_html = "\n".join(
203
- [
204
- f'<div style="border-left: 3px solid {msg["color"]}; padding: 5px 10px; margin: 5px 0;">{self._linkify(msg["text"])}</div>'
205
- for msg in self.messages
206
- ]
207
- )
208
-
209
- display_style = "block" if self.is_expanded else "none"
210
- arrow = "▼" if self.is_expanded else "▶"
211
-
212
- return f"""
213
- <div style="border: 1px solid #ccc; margin: 10px 0; max-width: 800px;">
214
- <div onclick="document.getElementById('content-{self.log_id}').style.display = document.getElementById('content-{self.log_id}').style.display === 'none' ? 'block' : 'none';
215
- document.getElementById('arrow-{self.log_id}').innerHTML = document.getElementById('content-{self.log_id}').style.display === 'none' ? '▶' : '▼';"
216
- style="padding: 10px; background: #f5f5f5; cursor: pointer;">
217
- <span id="arrow-{self.log_id}">{arrow}</span> Remote Job Log ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
218
- </div>
219
- <div id="content-{self.log_id}" style="padding: 10px; display: {display_style};">
220
- {messages_html}
221
- </div>
222
- </div>
223
- """
224
-
225
- def update(self, message, status: JobsStatus = JobsStatus.RUNNING):
226
- from IPython.display import HTML
227
-
228
- colors = {"running": "#3b82f6", "completed": "#22c55e", "failed": "#ef4444"}
229
- self.messages.append({"text": message, "color": colors.get(status, "#666")})
230
- if self.verbose:
231
- self.display_handle.update(HTML(self._get_html()))
232
- else:
233
- return None
234
-
235
-
236
- if __name__ == "__main__":
237
- import doctest
238
-
239
- doctest.testmod()
@@ -1,30 +0,0 @@
1
- from edsl.jobs.FetchInvigilator import FetchInvigilator
2
-
3
-
4
- class RequestTokenEstimator:
5
- """Estimate the number of tokens that will be required to run the focal task."""
6
-
7
- def __init__(self, interview):
8
- self.interview = interview
9
-
10
- def __call__(self, question) -> float:
11
- """Estimate the number of tokens that will be required to run the focal task."""
12
- from edsl.scenarios.FileStore import FileStore
13
-
14
- invigilator = FetchInvigilator(self.interview)(question=question)
15
-
16
- # TODO: There should be a way to get a more accurate estimate.
17
- combined_text = ""
18
- file_tokens = 0
19
- for prompt in invigilator.get_prompts().values():
20
- if hasattr(prompt, "text"):
21
- combined_text += prompt.text
22
- elif isinstance(prompt, str):
23
- combined_text += prompt
24
- elif isinstance(prompt, list):
25
- for file in prompt:
26
- if isinstance(file, FileStore):
27
- file_tokens += file.size * 0.25
28
- else:
29
- raise ValueError(f"Prompt is of type {type(prompt)}")
30
- return len(combined_text) / 4.0 + file_tokens