edsl 0.1.47__py3-none-any.whl → 0.1.48__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 (314) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +303 -67
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +313 -167
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +4 -9
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +365 -220
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/{FileStore.py → file_store.py} +275 -189
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +18 -19
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/METADATA +1 -1
  230. edsl-0.1.48.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -493
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/coop/PriceFetcher.py +0 -54
  236. edsl/data/Cache.py +0 -582
  237. edsl/data/CacheEntry.py +0 -238
  238. edsl/data/SQLiteDict.py +0 -292
  239. edsl/data/__init__.py +0 -5
  240. edsl/data/orm.py +0 -10
  241. edsl/exceptions/cache.py +0 -5
  242. edsl/exceptions/coop.py +0 -14
  243. edsl/exceptions/data.py +0 -14
  244. edsl/exceptions/scenarios.py +0 -29
  245. edsl/jobs/Answers.py +0 -43
  246. edsl/jobs/JobsPrompts.py +0 -354
  247. edsl/jobs/buckets/BucketCollection.py +0 -134
  248. edsl/jobs/buckets/ModelBuckets.py +0 -65
  249. edsl/jobs/buckets/TokenBucket.py +0 -283
  250. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  251. edsl/jobs/interviews/Interview.py +0 -395
  252. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  253. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  254. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  255. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  256. edsl/jobs/tasks/TaskCreators.py +0 -64
  257. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  258. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  259. edsl/language_models/LanguageModel.py +0 -635
  260. edsl/language_models/ServiceDataSources.py +0 -0
  261. edsl/language_models/key_management/KeyLookup.py +0 -63
  262. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  263. edsl/language_models/key_management/models.py +0 -137
  264. edsl/questions/QuestionBase.py +0 -544
  265. edsl/questions/QuestionFreeText.py +0 -130
  266. edsl/questions/derived/QuestionLikertFive.py +0 -76
  267. edsl/results/ResultsExportMixin.py +0 -45
  268. edsl/results/TextEditor.py +0 -50
  269. edsl/results/results_fetch_mixin.py +0 -33
  270. edsl/results/results_tools_mixin.py +0 -98
  271. edsl/scenarios/DocumentChunker.py +0 -104
  272. edsl/scenarios/Scenario.py +0 -548
  273. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  274. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  275. edsl/scenarios/handlers/latex.py +0 -5
  276. edsl/shared.py +0 -1
  277. edsl/surveys/Survey.py +0 -1301
  278. edsl/surveys/SurveyQualtricsImport.py +0 -284
  279. edsl/surveys/SurveyToApp.py +0 -141
  280. edsl/surveys/instructions/__init__.py +0 -0
  281. edsl/tools/__init__.py +0 -1
  282. edsl/tools/clusters.py +0 -192
  283. edsl/tools/embeddings.py +0 -27
  284. edsl/tools/embeddings_plotting.py +0 -118
  285. edsl/tools/plotting.py +0 -112
  286. edsl/tools/summarize.py +0 -18
  287. edsl/utilities/data/Registry.py +0 -6
  288. edsl/utilities/data/__init__.py +0 -1
  289. edsl/utilities/data/scooter_results.json +0 -1
  290. edsl-0.1.47.dist-info/RECORD +0 -354
  291. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  292. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  293. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  294. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  295. /edsl/{results → dataset/display}/table_display.css +0 -0
  296. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  297. /edsl/{results → dataset}/tree_explore.py +0 -0
  298. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  299. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  300. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  301. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  302. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  303. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  304. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  305. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  306. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  307. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  308. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  309. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  310. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  311. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  312. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  313. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
  314. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -1,11 +1,47 @@
1
- from typing import List, Optional
1
+ """
2
+ This module provides the TaskHistory class for tracking and analyzing task execution history.
3
+
4
+ The TaskHistory class maintains a record of all interviews conducted by EDSL, including
5
+ their task execution histories, exceptions, and performance metrics. It supports rich
6
+ visualization and reporting to help users understand task execution patterns and diagnose
7
+ issues.
8
+ """
9
+
10
+ from typing import List, Optional, Dict, Any, Union
2
11
  from io import BytesIO
3
12
  import base64
4
- from edsl.jobs.tasks.task_status_enum import TaskStatus
5
- from edsl.Base import RepresentationMixin
13
+ import os
14
+ import tempfile
15
+
16
+ from .task_status_enum import TaskStatus
17
+ from ..base import RepresentationMixin
6
18
 
7
19
 
8
20
  class TaskHistory(RepresentationMixin):
21
+ """
22
+ Records and analyzes the execution history of tasks across multiple interviews.
23
+
24
+ The TaskHistory class serves as a central repository for tracking task execution
25
+ across multiple interviews. It provides methods for:
26
+
27
+ 1. Error Analysis - Collecting, categorizing, and reporting exceptions
28
+ 2. Execution Visualization - Generating plots of task status over time
29
+ 3. Performance Metrics - Calculating timing statistics for tasks
30
+ 4. HTML Reports - Creating detailed interactive reports of execution
31
+
32
+ This class is particularly useful for debugging complex interview workflows,
33
+ identifying performance bottlenecks, and understanding patterns in task execution.
34
+ It supports both interactive exploration in notebooks and standalone report
35
+ generation.
36
+
37
+ Key features:
38
+ - Tracks exceptions with optional traceback storage
39
+ - Provides visualizations of task status transitions
40
+ - Generates interactive HTML reports with filtering and drill-down
41
+ - Computes statistics across interviews (by model, question type, etc.)
42
+ - Exports to various formats (HTML, notebook, etc.)
43
+ """
44
+
9
45
  def __init__(
10
46
  self,
11
47
  interviews: List["Interview"] = None,
@@ -14,12 +50,16 @@ class TaskHistory(RepresentationMixin):
14
50
  interviews_with_exceptions_only: bool = False,
15
51
  ):
16
52
  """
17
- The structure of a TaskHistory exception
18
-
19
- [Interview.exceptions, Interview.exceptions, Interview.exceptions, ...]
20
-
21
- >>> _ = TaskHistory.example()
22
- ...
53
+ Initialize a TaskHistory to track execution across multiple interviews.
54
+
55
+ Parameters:
56
+ interviews: List of Interview objects to track
57
+ include_traceback: Whether to include full exception tracebacks
58
+ max_interviews: Maximum number of interviews to display in reports
59
+ interviews_with_exceptions_only: If True, only track interviews with exceptions
60
+
61
+ Example:
62
+ >>> _ = TaskHistory.example() # Create a sample TaskHistory
23
63
  """
24
64
  self.interviews_with_exceptions_only = interviews_with_exceptions_only
25
65
  self._interviews = {}
@@ -34,10 +74,8 @@ class TaskHistory(RepresentationMixin):
34
74
  }
35
75
  self.max_interviews = max_interviews
36
76
 
37
- # self.total_interviews = interviews
38
77
  self.include_traceback = include_traceback
39
78
 
40
- # self._interviews = {index: i for index, i in enumerate(self.total_interviews)}
41
79
  self.max_interviews = max_interviews
42
80
 
43
81
  def add_interview(self, interview: "Interview"):
@@ -51,9 +89,8 @@ class TaskHistory(RepresentationMixin):
51
89
  @classmethod
52
90
  def example(cls):
53
91
  """ """
54
- from edsl.jobs.interviews.Interview import Interview
55
-
56
- from edsl.jobs.Jobs import Jobs
92
+ from ..interviews import Interview
93
+ from ..jobs import Jobs
57
94
 
58
95
  j = Jobs.example(throw_exception_probability=1, test_model=True)
59
96
 
@@ -120,7 +157,7 @@ class TaskHistory(RepresentationMixin):
120
157
  if data is None:
121
158
  return cls([], include_traceback=False)
122
159
 
123
- from edsl.jobs.interviews.Interview import Interview
160
+ from ..interviews import Interview
124
161
 
125
162
  interviews = [Interview.from_dict(i) for i in data["interviews"]]
126
163
  return cls(interviews, include_traceback=data["include_traceback"])
@@ -273,15 +310,15 @@ class TaskHistory(RepresentationMixin):
273
310
  for exception in exceptions:
274
311
  key = (
275
312
  exception.exception.__class__.__name__, # Exception type
276
- interview.model._inference_service_, # Service
277
- interview.model.model, # Model
278
- question_name # Question name
313
+ interview.model._inference_service_, # Service
314
+ interview.model.model, # Model
315
+ question_name, # Question name
279
316
  )
280
317
  if key not in exceptions_table:
281
318
  exceptions_table[key] = 0
282
319
  exceptions_table[key] += 1
283
320
  return exceptions_table
284
-
321
+
285
322
  @property
286
323
  def exceptions_by_type(self) -> dict:
287
324
  """Return a dictionary of exceptions tallied by type."""
@@ -342,27 +379,6 @@ class TaskHistory(RepresentationMixin):
342
379
  }
343
380
  return sorted_exceptions_by_question_name
344
381
 
345
- # @property
346
- # def exceptions_by_model(self) -> dict:
347
- # """Return a dictionary of exceptions tallied by model and question name."""
348
- # exceptions_by_model = {}
349
- # for interview in self.total_interviews:
350
- # model = interview.model.model
351
- # service = interview.model._inference_service_
352
- # if (service, model) not in exceptions_by_model:
353
- # exceptions_by_model[(service, model)] = 0
354
- # if interview.exceptions != {}:
355
- # exceptions_by_model[(service, model)] += len(interview.exceptions)
356
-
357
- # # sort the exceptions by model
358
- # sorted_exceptions_by_model = {
359
- # k: v
360
- # for k, v in sorted(
361
- # exceptions_by_model.items(), key=lambda item: item[1], reverse=True
362
- # )
363
- # }
364
- # return sorted_exceptions_by_model
365
-
366
382
  @property
367
383
  def exceptions_by_model(self) -> dict:
368
384
  """Return a dictionary of exceptions tallied by model and question name."""
@@ -389,7 +405,7 @@ class TaskHistory(RepresentationMixin):
389
405
  models_used = set([i.model.model for index, i in self._interviews.items()])
390
406
 
391
407
  from jinja2 import Environment, FileSystemLoader
392
- from edsl.TemplateLoader import TemplateLoader
408
+ from edsl.utilities import TemplateLoader
393
409
 
394
410
  env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
395
411
 
@@ -417,12 +433,35 @@ class TaskHistory(RepresentationMixin):
417
433
  def html(
418
434
  self,
419
435
  filename: Optional[str] = None,
420
- return_link=False,
421
- css=None,
422
- cta="<br><span style='font-size: 18px; font-weight: medium-bold; text-decoration: underline;'>Click to open the report in a new tab</span><br><br>",
423
- open_in_browser=False,
424
- ):
425
- """Return an HTML report."""
436
+ return_link: bool = False,
437
+ css: Optional[str] = None,
438
+ cta: str = "<br><span style='font-size: 18px; font-weight: medium-bold; text-decoration: underline;'>Click to open the report in a new tab</span><br><br>",
439
+ open_in_browser: bool = False,
440
+ ) -> Optional[str]:
441
+ """
442
+ Generate and display an interactive HTML report of task execution.
443
+
444
+ This method creates a comprehensive HTML report showing task execution details,
445
+ exceptions, timing information, and statistics across all tracked interviews.
446
+ In notebook environments, it displays an embedded preview with a link to open
447
+ the full report in a new tab.
448
+
449
+ Parameters:
450
+ filename: Path to save the HTML report (if None, a temporary file is created)
451
+ return_link: If True, return the path to the saved HTML file
452
+ css: Custom CSS to apply to the report (if None, uses default styling)
453
+ cta: HTML for the "Call to Action" link text
454
+ open_in_browser: If True, automatically open the report in the default browser
455
+
456
+ Returns:
457
+ If return_link is True, returns the path to the saved HTML file; otherwise None
458
+
459
+ Notes:
460
+ - In Jupyter notebooks, displays an embedded preview with a link
461
+ - In terminal environments, saves the file and prints its location
462
+ - The report includes interactive elements for filtering and drill-down
463
+ - Exception details, status transitions, and timing are all included
464
+ """
426
465
  from IPython.display import display, HTML
427
466
  import tempfile
428
467
  import os
@@ -5,7 +5,27 @@ import time
5
5
 
6
6
 
7
7
  class TaskStatus(enum.Enum):
8
- "These are the possible states a task can be in."
8
+ """
9
+ Enumeration of possible states for a task in the EDSL task system.
10
+
11
+ Each task moves through various states during its lifecycle, from creation
12
+ to completion. This enum defines all possible states to track task progress
13
+ and diagnose issues.
14
+
15
+ States:
16
+ NOT_STARTED: Initial state - task has been created but not yet started
17
+ WAITING_FOR_DEPENDENCIES: Task is waiting for prerequisite tasks to complete
18
+ CANCELLED: Task was explicitly cancelled by the user or system
19
+ PARENT_FAILED: Task cannot run because a dependency task failed
20
+ WAITING_FOR_REQUEST_CAPACITY: Task is waiting due to API rate limits
21
+ WAITING_FOR_TOKEN_CAPACITY: Task is waiting due to token usage limits
22
+ API_CALL_IN_PROGRESS: Task is actively executing an API call
23
+ SUCCESS: Task completed successfully
24
+ FAILED: Task encountered an error and failed to complete
25
+
26
+ These states are used throughout EDSL to track task progress, generate
27
+ visualizations, and provide detailed error reports.
28
+ """
9
29
  NOT_STARTED = enum.auto()
10
30
  WAITING_FOR_DEPENDENCIES = enum.auto()
11
31
  CANCELLED = enum.auto()
@@ -18,30 +38,80 @@ class TaskStatus(enum.Enum):
18
38
 
19
39
 
20
40
  class TaskStatusLogEntry(UserDict):
21
- def __init__(self, log_time, value):
41
+ """
42
+ A timestamped record of a task's status change.
43
+
44
+ This class records both the time when a task's status changed and the new status value.
45
+ It uses the UserDict interface for convenient dictionary-like access while maintaining
46
+ the structured nature of status log entries.
47
+
48
+ Attributes:
49
+ log_time: The time (from time.monotonic()) when the status change occurred
50
+ value: The new TaskStatus value
51
+ """
52
+ def __init__(self, log_time: float, value: TaskStatus):
53
+ """
54
+ Create a new task status log entry.
55
+
56
+ Parameters:
57
+ log_time: The time when this status change occurred (from time.monotonic())
58
+ value: The TaskStatus value that the task transitioned to
59
+ """
22
60
  self.data = {"log_time": log_time, "value": value}
23
61
  super().__init__(self.data)
24
62
 
25
63
 
26
64
  class TaskStatusDescriptor:
27
- "The descriptor ensures that the task status is always an instance of the TaskStatus enum."
65
+ """
66
+ A descriptor that enforces TaskStatus type safety and logs status changes.
67
+
68
+ This descriptor is used to create task_status properties in task-related classes.
69
+ It performs two key functions:
70
+
71
+ 1. Type Enforcement: Ensures that task_status is always set to a valid TaskStatus enum
72
+ 2. Logging: Automatically adds entries to the task's status_log when status changes
73
+
74
+ By using this descriptor, EDSL ensures consistent status tracking across all tasks
75
+ while providing a rich history of status changes for debugging and visualization.
76
+ """
28
77
 
29
78
  def __init__(self):
79
+ """Initialize the descriptor with a null status value."""
30
80
  self._task_status = None
31
81
 
32
82
  def __get__(self, instance, owner):
83
+ """Return the current task status."""
33
84
  return self._task_status
34
85
 
35
86
  def __set__(self, instance, value):
36
- """Ensure that the value is an instance of TaskStatus."""
87
+ """
88
+ Set a new task status and record the change in the status log.
89
+
90
+ This method enforces that the value is a valid TaskStatus enum and
91
+ automatically adds an entry to the instance's status_log (if it exists).
92
+
93
+ Parameters:
94
+ instance: The object instance that owns this descriptor
95
+ value: The new TaskStatus value to set
96
+
97
+ Raises:
98
+ ValueError: If value is not an instance of TaskStatus enum
99
+ """
37
100
  if not isinstance(value, TaskStatus):
38
101
  raise ValueError("Value must be an instance of TaskStatus enum")
102
+
103
+ # Record the current time for the status change
39
104
  t = time.monotonic()
105
+
106
+ # Add an entry to the status log if the instance has one
40
107
  if hasattr(instance, "status_log"):
41
108
  instance.status_log.append(TaskStatusLogEntry(t, value))
109
+
110
+ # Update the actual status value
42
111
  self._task_status = value
43
112
 
44
113
  def __delete__(self, instance):
114
+ """Reset the task status to None when deleted."""
45
115
  self._task_status = None
46
116
 
47
117
 
@@ -69,9 +139,23 @@ def get_enum_from_string(str_key):
69
139
 
70
140
 
71
141
  class InterviewTaskLogDict(UserDict):
72
- """A dictionary of TaskStatusLog objects.
73
-
74
- The key is the name of the task.
142
+ """
143
+ A collection of task status logs for all tasks in an interview.
144
+
145
+ This dictionary-like object maps task names to their individual TaskStatusLog objects,
146
+ providing methods to analyze task execution across an entire interview. It supports
147
+ calculating timing metrics, generating status matrices for visualization, and
148
+ rendering graphical representations of task execution flow.
149
+
150
+ The InterviewTaskLogDict is a key component in EDSL's task monitoring system,
151
+ enabling both debugging of individual interviews and aggregate analysis of
152
+ execution patterns.
153
+
154
+ Key features:
155
+ - Temporal analysis (min/max execution times)
156
+ - Status matrix generation for visualization
157
+ - Visual representation of task status changes over time
158
+ - Color-coded status visualization
75
159
  """
76
160
 
77
161
  @property
@@ -0,0 +1,85 @@
1
+ """
2
+ This module provides the TaskStatusLog class for tracking the status history of tasks.
3
+
4
+ The TaskStatusLog class maintains an ordered list of status changes for a specific task,
5
+ with timestamps and status values. This history allows for detailed analysis of task
6
+ execution, including timing, state transitions, and status at any point in time.
7
+ """
8
+
9
+ from collections import UserList
10
+ from typing import List, Optional, Union
11
+
12
+ from .task_status_enum import TaskStatus, TaskStatusLogEntry
13
+
14
+
15
+ class TaskStatusLog(UserList):
16
+ """
17
+ An ordered history of status changes for a single task.
18
+
19
+ This class extends UserList to provide a chronological record of all status changes
20
+ that a task undergoes during its lifecycle. Each entry in the list is a
21
+ TaskStatusLogEntry object containing a timestamp and status value.
22
+
23
+ The TaskStatusLog provides methods to analyze the timing of task execution and
24
+ determine task status at any point in time. This information is valuable for
25
+ debugging, performance analysis, and visualization of task execution flow.
26
+
27
+ Key features:
28
+ - Records all status transitions with timestamps
29
+ - Provides min/max execution time calculations
30
+ - Supports interpolation to determine status at any given time
31
+ - Used by visualization tools to render task execution timelines
32
+ """
33
+
34
+ @property
35
+ def min_time(self) -> float:
36
+ """
37
+ Get the timestamp of the first status change.
38
+
39
+ Returns:
40
+ The timestamp (from time.monotonic()) of the earliest status entry
41
+
42
+ Note:
43
+ This is typically when the task was first created and set to NOT_STARTED
44
+ """
45
+ return self[0]["log_time"]
46
+
47
+ @property
48
+ def max_time(self) -> float:
49
+ """
50
+ Get the timestamp of the last status change.
51
+
52
+ Returns:
53
+ The timestamp (from time.monotonic()) of the most recent status entry
54
+
55
+ Note:
56
+ This is typically when the task reached its final state (SUCCESS, FAILED, etc.)
57
+ """
58
+ return self[-1]["log_time"]
59
+
60
+ def status_at_time(self, t: float) -> TaskStatus:
61
+ """
62
+ Determine what status the task had at a specific point in time.
63
+
64
+ This method interpolates between status log entries to determine the task's
65
+ status at any arbitrary time point. It searches for the first status change
66
+ that occurred after time t and returns the status that was active at that time.
67
+
68
+ Parameters:
69
+ t: The timestamp to query (from time.monotonic())
70
+
71
+ Returns:
72
+ The TaskStatus that was active at time t
73
+
74
+ Note:
75
+ If t is after the last recorded status change, the final status is returned.
76
+ If t is before the first recorded status change, this method may not behave
77
+ as expected since it assumes ordered traversal through the log.
78
+
79
+ TODO:
80
+ Could re-factor with bisect to make this faster for large logs.
81
+ """
82
+ for entry in self:
83
+ if entry["log_time"] > t:
84
+ return entry["value"]
85
+ return self[-1]["value"]
@@ -0,0 +1,2 @@
1
+ from .token_usage import TokenUsage
2
+ from .interview_token_usage import InterviewTokenUsage
@@ -0,0 +1,53 @@
1
+ from typing import Optional
2
+
3
+ from .token_usage import TokenUsage
4
+ from edsl.enums import TokenPricing
5
+
6
+ class InterviewTokenUsage:
7
+ """A class to represent the token usage of an interview."""
8
+
9
+ def __init__(
10
+ self, new_token_usage: Optional[TokenUsage] = None, cached_token_usage: Optional[TokenUsage] = None
11
+ ):
12
+ """Initialize the InterviewTokenUsage.
13
+
14
+ >>> usage = InterviewTokenUsage()
15
+ """
16
+ self.new_token_usage = new_token_usage or TokenUsage(from_cache=False)
17
+ self.cached_token_usage = cached_token_usage or TokenUsage(from_cache=True)
18
+
19
+ def __add__(self, other: "InterviewTokenUsage") -> "InterviewTokenUsage":
20
+ """Add two InterviewTokenUsage objects together.
21
+
22
+ >>> usage1 = InterviewTokenUsage()
23
+ >>> usage2 = InterviewTokenUsage()
24
+ >>> usage3 = usage1 + usage2
25
+ """
26
+ if not isinstance(other, InterviewTokenUsage):
27
+ raise ValueError(f"Can't add {type(other)} to InterviewTokenSummary")
28
+ return InterviewTokenUsage(
29
+ new_token_usage=self.new_token_usage + other.new_token_usage,
30
+ cached_token_usage=self.cached_token_usage + other.cached_token_usage,
31
+ )
32
+
33
+ def __repr__(self):
34
+ return f"InterviewTokenUsage(new_token_usage={self.new_token_usage}, cached_token_usage={self.cached_token_usage})"
35
+
36
+ def cost(self, prices: TokenPricing) -> float:
37
+ """Return the cost of the new and cached token usage.
38
+
39
+ >>> usage = InterviewTokenUsage()
40
+ >>> usage.cost(TokenPricing.example())
41
+ 0.0
42
+ """
43
+ return self.new_token_usage.cost(prices)
44
+
45
+ def saved(self, prices: TokenPricing) -> float:
46
+ """Return the saved cost of the cached token usage.
47
+ """
48
+ return self.cached_token_usage.cost(prices)
49
+
50
+
51
+ if __name__ == "__main__":
52
+ import doctest
53
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,5 +1,5 @@
1
1
  from collections import UserList
2
- from edsl.results.Dataset import Dataset
2
+ from ..dataset import Dataset
3
3
 
4
4
  class Markkdown:
5
5
 
@@ -1,28 +1,31 @@
1
1
  """Module to store system information."""
2
2
 
3
- from dataclasses import dataclass
4
- import getpass
5
- import platform
6
- import pkg_resources
3
+ # This module is not currently used by any part of the codebase.
4
+ # Keeping it commented out for potential future use.
7
5
 
6
+ # from dataclasses import dataclass
7
+ # import getpass
8
+ # import platform
9
+ # import pkg_resources
8
10
 
9
- @dataclass
10
- class SystemInfo:
11
- """Dataclass to store system information."""
12
11
 
13
- username: str
14
- system_info: str
15
- release_info: str
16
- package_name: str
17
- package_version: str
12
+ # @dataclass
13
+ # class SystemInfo:
14
+ # """Dataclass to store system information."""
18
15
 
19
- def __init__(self, package_name: str):
20
- """Initialize the dataclass with system."""
21
- self.username = getpass.getuser()
22
- self.system_info = platform.system()
23
- self.release_info = platform.release()
24
- self.package_name = package_name
25
- try:
26
- self.package_version = pkg_resources.get_distribution(package_name).version
27
- except pkg_resources.DistributionNotFound:
28
- self.package_version = "Not installed"
16
+ # username: str
17
+ # system_info: str
18
+ # release_info: str
19
+ # package_name: str
20
+ # package_version: str
21
+
22
+ # def __init__(self, package_name: str):
23
+ # """Initialize the dataclass with system."""
24
+ # self.username = getpass.getuser()
25
+ # self.system_info = platform.system()
26
+ # self.release_info = platform.release()
27
+ # self.package_name = package_name
28
+ # try:
29
+ # self.package_version = pkg_resources.get_distribution(package_name).version
30
+ # except pkg_resources.DistributionNotFound:
31
+ # self.package_version = "Not installed"
@@ -1,22 +1,30 @@
1
- # from edsl.utilities.interface import (
2
- # print_dict_as_html_table,
3
- # print_dict_with_rich,
4
- # print_list_of_dicts_as_html_table,
5
- # print_table_with_rich,
6
- # print_public_methods_with_doc,
7
- # print_list_of_dicts_as_markdown_table,
8
- # )
1
+ # Core utilities - used across the codebase
2
+ from .template_loader import TemplateLoader
3
+ from .PrettyList import PrettyList
4
+ from .restricted_python import create_restricted_function
5
+ from .remove_edsl_version import remove_edsl_version
6
+ from .ast_utilities import extract_variable_names
7
+
8
+ # Functions from utilities.py
9
+ from .utilities import (
10
+ clean_json,
11
+ dict_hash,
12
+ hash_value,
13
+ repair_json,
14
+ create_valid_var_name,
15
+ random_string,
16
+ shorten_string,
17
+ is_gzipped
18
+ )
19
+
20
+ # Decorator utilities
21
+ from .decorators import sync_wrapper, jupyter_nb_handler
22
+
23
+ # Standalone utilities
24
+ from .is_notebook import is_notebook
25
+ from .is_valid_variable_name import is_valid_variable_name
26
+ from .naming_utilities import sanitize_string
27
+
28
+ # Interface module - note: print_results_long is imported directly in results.py
29
+ from .interface import print_results_long
9
30
 
10
- # from edsl.utilities.utilities import (
11
- # create_valid_var_name,
12
- # dict_to_html,
13
- # hash_value,
14
- # HTMLSnippet,
15
- # is_notebook,
16
- # is_gzipped,
17
- # is_valid_variable_name,
18
- # random_string,
19
- # repair_json,
20
- # shorten_string,
21
- # time_all_functions,
22
- # )
@@ -0,0 +1,2 @@
1
+ # GCP bucket utilities
2
+ # This package is currently not being used