edsl 0.1.14__py3-none-any.whl → 0.1.40__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 (407) hide show
  1. edsl/Base.py +348 -38
  2. edsl/BaseDiff.py +260 -0
  3. edsl/TemplateLoader.py +24 -0
  4. edsl/__init__.py +46 -10
  5. edsl/__version__.py +1 -0
  6. edsl/agents/Agent.py +842 -144
  7. edsl/agents/AgentList.py +521 -25
  8. edsl/agents/Invigilator.py +250 -374
  9. edsl/agents/InvigilatorBase.py +257 -0
  10. edsl/agents/PromptConstructor.py +272 -0
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/descriptors.py +43 -13
  14. edsl/agents/prompt_helpers.py +129 -0
  15. edsl/agents/question_option_processor.py +172 -0
  16. edsl/auto/AutoStudy.py +130 -0
  17. edsl/auto/StageBase.py +243 -0
  18. edsl/auto/StageGenerateSurvey.py +178 -0
  19. edsl/auto/StageLabelQuestions.py +125 -0
  20. edsl/auto/StagePersona.py +61 -0
  21. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  22. edsl/auto/StagePersonaDimensionValues.py +74 -0
  23. edsl/auto/StagePersonaDimensions.py +69 -0
  24. edsl/auto/StageQuestions.py +74 -0
  25. edsl/auto/SurveyCreatorPipeline.py +21 -0
  26. edsl/auto/utilities.py +218 -0
  27. edsl/base/Base.py +279 -0
  28. edsl/config.py +121 -104
  29. edsl/conversation/Conversation.py +290 -0
  30. edsl/conversation/car_buying.py +59 -0
  31. edsl/conversation/chips.py +95 -0
  32. edsl/conversation/mug_negotiation.py +81 -0
  33. edsl/conversation/next_speaker_utilities.py +93 -0
  34. edsl/coop/CoopFunctionsMixin.py +15 -0
  35. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  36. edsl/coop/PriceFetcher.py +54 -0
  37. edsl/coop/__init__.py +1 -0
  38. edsl/coop/coop.py +1029 -134
  39. edsl/coop/utils.py +131 -0
  40. edsl/data/Cache.py +560 -89
  41. edsl/data/CacheEntry.py +230 -0
  42. edsl/data/CacheHandler.py +168 -0
  43. edsl/data/RemoteCacheSync.py +186 -0
  44. edsl/data/SQLiteDict.py +292 -0
  45. edsl/data/__init__.py +5 -3
  46. edsl/data/orm.py +6 -33
  47. edsl/data_transfer_models.py +74 -27
  48. edsl/enums.py +165 -8
  49. edsl/exceptions/BaseException.py +21 -0
  50. edsl/exceptions/__init__.py +52 -46
  51. edsl/exceptions/agents.py +33 -15
  52. edsl/exceptions/cache.py +5 -0
  53. edsl/exceptions/coop.py +8 -0
  54. edsl/exceptions/general.py +34 -0
  55. edsl/exceptions/inference_services.py +5 -0
  56. edsl/exceptions/jobs.py +15 -0
  57. edsl/exceptions/language_models.py +46 -1
  58. edsl/exceptions/questions.py +80 -5
  59. edsl/exceptions/results.py +16 -5
  60. edsl/exceptions/scenarios.py +29 -0
  61. edsl/exceptions/surveys.py +13 -10
  62. edsl/inference_services/AnthropicService.py +106 -0
  63. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  64. edsl/inference_services/AvailableModelFetcher.py +215 -0
  65. edsl/inference_services/AwsBedrock.py +118 -0
  66. edsl/inference_services/AzureAI.py +215 -0
  67. edsl/inference_services/DeepInfraService.py +18 -0
  68. edsl/inference_services/GoogleService.py +143 -0
  69. edsl/inference_services/GroqService.py +20 -0
  70. edsl/inference_services/InferenceServiceABC.py +80 -0
  71. edsl/inference_services/InferenceServicesCollection.py +138 -0
  72. edsl/inference_services/MistralAIService.py +120 -0
  73. edsl/inference_services/OllamaService.py +18 -0
  74. edsl/inference_services/OpenAIService.py +236 -0
  75. edsl/inference_services/PerplexityService.py +160 -0
  76. edsl/inference_services/ServiceAvailability.py +135 -0
  77. edsl/inference_services/TestService.py +90 -0
  78. edsl/inference_services/TogetherAIService.py +172 -0
  79. edsl/inference_services/data_structures.py +134 -0
  80. edsl/inference_services/models_available_cache.py +118 -0
  81. edsl/inference_services/rate_limits_cache.py +25 -0
  82. edsl/inference_services/registry.py +41 -0
  83. edsl/inference_services/write_available.py +10 -0
  84. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  85. edsl/jobs/Answers.py +21 -20
  86. edsl/jobs/FetchInvigilator.py +47 -0
  87. edsl/jobs/InterviewTaskManager.py +98 -0
  88. edsl/jobs/InterviewsConstructor.py +50 -0
  89. edsl/jobs/Jobs.py +684 -204
  90. edsl/jobs/JobsChecks.py +172 -0
  91. edsl/jobs/JobsComponentConstructor.py +189 -0
  92. edsl/jobs/JobsPrompts.py +270 -0
  93. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  94. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  95. edsl/jobs/RequestTokenEstimator.py +30 -0
  96. edsl/jobs/async_interview_runner.py +138 -0
  97. edsl/jobs/buckets/BucketCollection.py +104 -0
  98. edsl/jobs/buckets/ModelBuckets.py +65 -0
  99. edsl/jobs/buckets/TokenBucket.py +283 -0
  100. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  101. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  102. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  103. edsl/jobs/data_structures.py +120 -0
  104. edsl/jobs/decorators.py +35 -0
  105. edsl/jobs/interviews/Interview.py +392 -0
  106. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
  107. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
  108. edsl/jobs/interviews/InterviewStatistic.py +63 -0
  109. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
  110. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
  111. edsl/jobs/interviews/InterviewStatusLog.py +92 -0
  112. edsl/jobs/interviews/ReportErrors.py +66 -0
  113. edsl/jobs/interviews/interview_status_enum.py +9 -0
  114. edsl/jobs/jobs_status_enums.py +9 -0
  115. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  116. edsl/jobs/results_exceptions_handler.py +98 -0
  117. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
  118. edsl/jobs/runners/JobsRunnerStatus.py +298 -0
  119. edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
  120. edsl/jobs/tasks/TaskCreators.py +64 -0
  121. edsl/jobs/tasks/TaskHistory.py +470 -0
  122. edsl/jobs/tasks/TaskStatusLog.py +23 -0
  123. edsl/jobs/tasks/task_status_enum.py +161 -0
  124. edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
  125. edsl/jobs/tokens/TokenUsage.py +34 -0
  126. edsl/language_models/ComputeCost.py +63 -0
  127. edsl/language_models/LanguageModel.py +507 -386
  128. edsl/language_models/ModelList.py +164 -0
  129. edsl/language_models/PriceManager.py +127 -0
  130. edsl/language_models/RawResponseHandler.py +106 -0
  131. edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
  132. edsl/language_models/__init__.py +1 -8
  133. edsl/language_models/fake_openai_call.py +15 -0
  134. edsl/language_models/fake_openai_service.py +61 -0
  135. edsl/language_models/key_management/KeyLookup.py +63 -0
  136. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  137. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  138. edsl/language_models/key_management/__init__.py +0 -0
  139. edsl/language_models/key_management/models.py +131 -0
  140. edsl/language_models/model.py +256 -0
  141. edsl/language_models/repair.py +109 -41
  142. edsl/language_models/utilities.py +65 -0
  143. edsl/notebooks/Notebook.py +263 -0
  144. edsl/notebooks/NotebookToLaTeX.py +142 -0
  145. edsl/notebooks/__init__.py +1 -0
  146. edsl/prompts/Prompt.py +222 -93
  147. edsl/prompts/__init__.py +1 -1
  148. edsl/questions/ExceptionExplainer.py +77 -0
  149. edsl/questions/HTMLQuestion.py +103 -0
  150. edsl/questions/QuestionBase.py +518 -0
  151. edsl/questions/QuestionBasePromptsMixin.py +221 -0
  152. edsl/questions/QuestionBudget.py +164 -67
  153. edsl/questions/QuestionCheckBox.py +281 -62
  154. edsl/questions/QuestionDict.py +343 -0
  155. edsl/questions/QuestionExtract.py +136 -50
  156. edsl/questions/QuestionFreeText.py +79 -55
  157. edsl/questions/QuestionFunctional.py +138 -41
  158. edsl/questions/QuestionList.py +184 -57
  159. edsl/questions/QuestionMatrix.py +265 -0
  160. edsl/questions/QuestionMultipleChoice.py +293 -69
  161. edsl/questions/QuestionNumerical.py +109 -56
  162. edsl/questions/QuestionRank.py +244 -49
  163. edsl/questions/Quick.py +41 -0
  164. edsl/questions/SimpleAskMixin.py +74 -0
  165. edsl/questions/__init__.py +9 -6
  166. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
  167. edsl/questions/compose_questions.py +13 -7
  168. edsl/questions/data_structures.py +20 -0
  169. edsl/questions/decorators.py +21 -0
  170. edsl/questions/derived/QuestionLikertFive.py +28 -26
  171. edsl/questions/derived/QuestionLinearScale.py +41 -28
  172. edsl/questions/derived/QuestionTopK.py +34 -26
  173. edsl/questions/derived/QuestionYesNo.py +40 -27
  174. edsl/questions/descriptors.py +228 -74
  175. edsl/questions/loop_processor.py +149 -0
  176. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  177. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  178. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  179. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  180. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  181. edsl/questions/prompt_templates/question_list.jinja +17 -0
  182. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  183. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  184. edsl/questions/question_base_gen_mixin.py +168 -0
  185. edsl/questions/question_registry.py +130 -46
  186. edsl/questions/register_questions_meta.py +71 -0
  187. edsl/questions/response_validator_abc.py +188 -0
  188. edsl/questions/response_validator_factory.py +34 -0
  189. edsl/questions/settings.py +5 -2
  190. edsl/questions/templates/__init__.py +0 -0
  191. edsl/questions/templates/budget/__init__.py +0 -0
  192. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  193. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  194. edsl/questions/templates/checkbox/__init__.py +0 -0
  195. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  196. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  197. edsl/questions/templates/dict/__init__.py +0 -0
  198. edsl/questions/templates/dict/answering_instructions.jinja +21 -0
  199. edsl/questions/templates/dict/question_presentation.jinja +1 -0
  200. edsl/questions/templates/extract/__init__.py +0 -0
  201. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  202. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  203. edsl/questions/templates/free_text/__init__.py +0 -0
  204. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  205. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  206. edsl/questions/templates/likert_five/__init__.py +0 -0
  207. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  208. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  209. edsl/questions/templates/linear_scale/__init__.py +0 -0
  210. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  211. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  212. edsl/questions/templates/list/__init__.py +0 -0
  213. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  214. edsl/questions/templates/list/question_presentation.jinja +5 -0
  215. edsl/questions/templates/matrix/__init__.py +1 -0
  216. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  217. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  218. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  219. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  220. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  221. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  222. edsl/questions/templates/numerical/__init__.py +0 -0
  223. edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
  224. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  225. edsl/questions/templates/rank/__init__.py +0 -0
  226. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  227. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  228. edsl/questions/templates/top_k/__init__.py +0 -0
  229. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  230. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  231. edsl/questions/templates/yes_no/__init__.py +0 -0
  232. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  233. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  234. edsl/results/CSSParameterizer.py +108 -0
  235. edsl/results/Dataset.py +550 -19
  236. edsl/results/DatasetExportMixin.py +594 -0
  237. edsl/results/DatasetTree.py +295 -0
  238. edsl/results/MarkdownToDocx.py +122 -0
  239. edsl/results/MarkdownToPDF.py +111 -0
  240. edsl/results/Result.py +477 -173
  241. edsl/results/Results.py +987 -269
  242. edsl/results/ResultsExportMixin.py +28 -125
  243. edsl/results/ResultsGGMixin.py +83 -15
  244. edsl/results/TableDisplay.py +125 -0
  245. edsl/results/TextEditor.py +50 -0
  246. edsl/results/__init__.py +1 -1
  247. edsl/results/file_exports.py +252 -0
  248. edsl/results/results_fetch_mixin.py +33 -0
  249. edsl/results/results_selector.py +145 -0
  250. edsl/results/results_tools_mixin.py +98 -0
  251. edsl/results/smart_objects.py +96 -0
  252. edsl/results/table_data_class.py +12 -0
  253. edsl/results/table_display.css +78 -0
  254. edsl/results/table_renderers.py +118 -0
  255. edsl/results/tree_explore.py +115 -0
  256. edsl/scenarios/ConstructDownloadLink.py +109 -0
  257. edsl/scenarios/DocumentChunker.py +102 -0
  258. edsl/scenarios/DocxScenario.py +16 -0
  259. edsl/scenarios/FileStore.py +543 -0
  260. edsl/scenarios/PdfExtractor.py +40 -0
  261. edsl/scenarios/Scenario.py +431 -62
  262. edsl/scenarios/ScenarioHtmlMixin.py +65 -0
  263. edsl/scenarios/ScenarioList.py +1415 -45
  264. edsl/scenarios/ScenarioListExportMixin.py +45 -0
  265. edsl/scenarios/ScenarioListPdfMixin.py +239 -0
  266. edsl/scenarios/__init__.py +2 -0
  267. edsl/scenarios/directory_scanner.py +96 -0
  268. edsl/scenarios/file_methods.py +85 -0
  269. edsl/scenarios/handlers/__init__.py +13 -0
  270. edsl/scenarios/handlers/csv.py +49 -0
  271. edsl/scenarios/handlers/docx.py +76 -0
  272. edsl/scenarios/handlers/html.py +37 -0
  273. edsl/scenarios/handlers/json.py +111 -0
  274. edsl/scenarios/handlers/latex.py +5 -0
  275. edsl/scenarios/handlers/md.py +51 -0
  276. edsl/scenarios/handlers/pdf.py +68 -0
  277. edsl/scenarios/handlers/png.py +39 -0
  278. edsl/scenarios/handlers/pptx.py +105 -0
  279. edsl/scenarios/handlers/py.py +294 -0
  280. edsl/scenarios/handlers/sql.py +313 -0
  281. edsl/scenarios/handlers/sqlite.py +149 -0
  282. edsl/scenarios/handlers/txt.py +33 -0
  283. edsl/scenarios/scenario_join.py +131 -0
  284. edsl/scenarios/scenario_selector.py +156 -0
  285. edsl/shared.py +1 -0
  286. edsl/study/ObjectEntry.py +173 -0
  287. edsl/study/ProofOfWork.py +113 -0
  288. edsl/study/SnapShot.py +80 -0
  289. edsl/study/Study.py +521 -0
  290. edsl/study/__init__.py +4 -0
  291. edsl/surveys/ConstructDAG.py +92 -0
  292. edsl/surveys/DAG.py +92 -11
  293. edsl/surveys/EditSurvey.py +221 -0
  294. edsl/surveys/InstructionHandler.py +100 -0
  295. edsl/surveys/Memory.py +9 -4
  296. edsl/surveys/MemoryManagement.py +72 -0
  297. edsl/surveys/MemoryPlan.py +156 -35
  298. edsl/surveys/Rule.py +221 -74
  299. edsl/surveys/RuleCollection.py +241 -61
  300. edsl/surveys/RuleManager.py +172 -0
  301. edsl/surveys/Simulator.py +75 -0
  302. edsl/surveys/Survey.py +1079 -339
  303. edsl/surveys/SurveyCSS.py +273 -0
  304. edsl/surveys/SurveyExportMixin.py +235 -40
  305. edsl/surveys/SurveyFlowVisualization.py +181 -0
  306. edsl/surveys/SurveyQualtricsImport.py +284 -0
  307. edsl/surveys/SurveyToApp.py +141 -0
  308. edsl/surveys/__init__.py +4 -2
  309. edsl/surveys/base.py +19 -3
  310. edsl/surveys/descriptors.py +17 -6
  311. edsl/surveys/instructions/ChangeInstruction.py +48 -0
  312. edsl/surveys/instructions/Instruction.py +56 -0
  313. edsl/surveys/instructions/InstructionCollection.py +82 -0
  314. edsl/surveys/instructions/__init__.py +0 -0
  315. edsl/templates/error_reporting/base.html +24 -0
  316. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  317. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  318. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  319. edsl/templates/error_reporting/interview_details.html +116 -0
  320. edsl/templates/error_reporting/interviews.html +19 -0
  321. edsl/templates/error_reporting/overview.html +5 -0
  322. edsl/templates/error_reporting/performance_plot.html +2 -0
  323. edsl/templates/error_reporting/report.css +74 -0
  324. edsl/templates/error_reporting/report.html +118 -0
  325. edsl/templates/error_reporting/report.js +25 -0
  326. edsl/tools/__init__.py +1 -0
  327. edsl/tools/clusters.py +192 -0
  328. edsl/tools/embeddings.py +27 -0
  329. edsl/tools/embeddings_plotting.py +118 -0
  330. edsl/tools/plotting.py +112 -0
  331. edsl/tools/summarize.py +18 -0
  332. edsl/utilities/PrettyList.py +56 -0
  333. edsl/utilities/SystemInfo.py +5 -0
  334. edsl/utilities/__init__.py +21 -20
  335. edsl/utilities/ast_utilities.py +3 -0
  336. edsl/utilities/data/Registry.py +2 -0
  337. edsl/utilities/decorators.py +41 -0
  338. edsl/utilities/gcp_bucket/__init__.py +0 -0
  339. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  340. edsl/utilities/interface.py +310 -60
  341. edsl/utilities/is_notebook.py +18 -0
  342. edsl/utilities/is_valid_variable_name.py +11 -0
  343. edsl/utilities/naming_utilities.py +263 -0
  344. edsl/utilities/remove_edsl_version.py +24 -0
  345. edsl/utilities/repair_functions.py +28 -0
  346. edsl/utilities/restricted_python.py +70 -0
  347. edsl/utilities/utilities.py +203 -13
  348. edsl-0.1.40.dist-info/METADATA +111 -0
  349. edsl-0.1.40.dist-info/RECORD +362 -0
  350. {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
  351. edsl/agents/AgentListExportMixin.py +0 -24
  352. edsl/coop/old.py +0 -31
  353. edsl/data/Database.py +0 -141
  354. edsl/data/crud.py +0 -121
  355. edsl/jobs/Interview.py +0 -417
  356. edsl/jobs/JobsRunner.py +0 -63
  357. edsl/jobs/JobsRunnerStatusMixin.py +0 -115
  358. edsl/jobs/base.py +0 -47
  359. edsl/jobs/buckets.py +0 -166
  360. edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
  361. edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
  362. edsl/jobs/task_management.py +0 -218
  363. edsl/jobs/token_tracking.py +0 -78
  364. edsl/language_models/DeepInfra.py +0 -69
  365. edsl/language_models/OpenAI.py +0 -98
  366. edsl/language_models/model_interfaces/GeminiPro.py +0 -66
  367. edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
  368. edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
  369. edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
  370. edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
  371. edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
  372. edsl/language_models/registry.py +0 -81
  373. edsl/language_models/schemas.py +0 -15
  374. edsl/language_models/unused/ReplicateBase.py +0 -83
  375. edsl/prompts/QuestionInstructionsBase.py +0 -6
  376. edsl/prompts/library/agent_instructions.py +0 -29
  377. edsl/prompts/library/agent_persona.py +0 -17
  378. edsl/prompts/library/question_budget.py +0 -26
  379. edsl/prompts/library/question_checkbox.py +0 -32
  380. edsl/prompts/library/question_extract.py +0 -19
  381. edsl/prompts/library/question_freetext.py +0 -14
  382. edsl/prompts/library/question_linear_scale.py +0 -20
  383. edsl/prompts/library/question_list.py +0 -22
  384. edsl/prompts/library/question_multiple_choice.py +0 -44
  385. edsl/prompts/library/question_numerical.py +0 -31
  386. edsl/prompts/library/question_rank.py +0 -21
  387. edsl/prompts/prompt_config.py +0 -33
  388. edsl/prompts/registry.py +0 -185
  389. edsl/questions/Question.py +0 -240
  390. edsl/report/InputOutputDataTypes.py +0 -134
  391. edsl/report/RegressionMixin.py +0 -28
  392. edsl/report/ReportOutputs.py +0 -1228
  393. edsl/report/ResultsFetchMixin.py +0 -106
  394. edsl/report/ResultsOutputMixin.py +0 -14
  395. edsl/report/demo.ipynb +0 -645
  396. edsl/results/ResultsDBMixin.py +0 -184
  397. edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
  398. edsl/trackers/Tracker.py +0 -91
  399. edsl/trackers/TrackerAPI.py +0 -196
  400. edsl/trackers/TrackerTasks.py +0 -70
  401. edsl/utilities/pastebin.py +0 -141
  402. edsl-0.1.14.dist-info/METADATA +0 -69
  403. edsl-0.1.14.dist-info/RECORD +0 -141
  404. /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
  405. /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
  406. /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
  407. {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/jobs/Jobs.py CHANGED
@@ -1,52 +1,240 @@
1
+ # """The Jobs class is a collection of agents, scenarios and models and one survey."""
1
2
  from __future__ import annotations
2
- from collections.abc import Sequence
3
- from collections import UserDict
4
- from itertools import product
5
- from typing import Union
3
+ import asyncio
4
+ from inspect import signature
5
+ from typing import (
6
+ Literal,
7
+ Optional,
8
+ Union,
9
+ Sequence,
10
+ Generator,
11
+ TYPE_CHECKING,
12
+ Callable,
13
+ Tuple,
14
+ )
6
15
 
7
- from edsl import CONFIG
8
- from edsl.agents import Agent
9
16
  from edsl.Base import Base
10
- from edsl.data import Database, database
11
- from edsl.language_models import LanguageModel, LanguageModelOpenAIThreeFiveTurbo
12
- from edsl.results import Results
13
- from edsl.scenarios import Scenario
14
- from edsl.surveys import Survey
15
- from edsl.jobs.base import JobsRunnersRegistry, JobsRunnerDescriptor
16
- from edsl.jobs.Interview import Interview
17
- from edsl.coop.old import JobRunnerAPI, ResultsAPI
18
17
 
19
- # from edsl.jobs.ModelBuckets import ModelBuckets, BucketCollection
18
+ from edsl.jobs.buckets.BucketCollection import BucketCollection
19
+ from edsl.jobs.JobsPrompts import JobsPrompts
20
+ from edsl.jobs.interviews.Interview import Interview
21
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
22
+ from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
23
+ from edsl.data.RemoteCacheSync import RemoteCacheSync
24
+ from edsl.exceptions.coop import CoopServerResponseError
25
+
26
+ from edsl.jobs.JobsChecks import JobsChecks
27
+ from edsl.jobs.data_structures import RunEnvironment, RunParameters, RunConfig
28
+
29
+ if TYPE_CHECKING:
30
+ from edsl.agents.Agent import Agent
31
+ from edsl.agents.AgentList import AgentList
32
+ from edsl.language_models.LanguageModel import LanguageModel
33
+ from edsl.scenarios.Scenario import Scenario
34
+ from edsl.scenarios.ScenarioList import ScenarioList
35
+ from edsl.surveys.Survey import Survey
36
+ from edsl.results.Results import Results
37
+ from edsl.results.Dataset import Dataset
38
+ from edsl.language_models.ModelList import ModelList
39
+ from edsl.data.Cache import Cache
40
+ from edsl.language_models.key_management.KeyLookup import KeyLookup
41
+
42
+ VisibilityType = Literal["private", "public", "unlisted"]
43
+
44
+ from dataclasses import dataclass
45
+ from typing import Optional, Union, TypeVar, Callable, cast
46
+ from functools import wraps
47
+
48
+ try:
49
+ from typing import ParamSpec
50
+ except ImportError:
51
+ from typing_extensions import ParamSpec
52
+
53
+
54
+ P = ParamSpec("P")
55
+ T = TypeVar("T")
56
+
57
+
58
+ from edsl.jobs.check_survey_scenario_compatibility import (
59
+ CheckSurveyScenarioCompatibility,
60
+ )
61
+
62
+
63
+ def with_config(f: Callable[P, T]) -> Callable[P, T]:
64
+ "This decorator make it so that the run function parameters match the RunConfig dataclass."
65
+ parameter_fields = {
66
+ name: field.default
67
+ for name, field in RunParameters.__dataclass_fields__.items()
68
+ }
69
+ environment_fields = {
70
+ name: field.default
71
+ for name, field in RunEnvironment.__dataclass_fields__.items()
72
+ }
73
+ combined = {**parameter_fields, **environment_fields}
74
+
75
+ @wraps(f)
76
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
77
+ environment = RunEnvironment(
78
+ **{k: v for k, v in kwargs.items() if k in environment_fields}
79
+ )
80
+ parameters = RunParameters(
81
+ **{k: v for k, v in kwargs.items() if k in parameter_fields}
82
+ )
83
+ config = RunConfig(environment=environment, parameters=parameters)
84
+ return f(*args, config=config)
85
+
86
+ # Update the wrapper's signature to include all RunConfig parameters
87
+ # old_sig = signature(f)
88
+ # wrapper.__signature__ = old_sig.replace(
89
+ # parameters=list(old_sig.parameters.values())[:-1]
90
+ # + [
91
+ # old_sig.parameters["config"].replace(
92
+ # default=parameter_fields[name], name=name
93
+ # )
94
+ # for name in combined
95
+ # ]
96
+ # )
20
97
 
21
- from edsl.jobs.buckets import BucketCollection
98
+ return cast(Callable[P, T], wrapper)
22
99
 
23
100
 
24
101
  class Jobs(Base):
25
102
  """
26
- The Jobs class is a collection of agents, scenarios and models and one survey.
27
-
28
- Methods:
29
- - `by()`: adds agents, scenarios or models to the job. Its a tricksy little method, be careful.
30
- - `interviews()`: creates a collection of interviews
31
- - `run()`: runs a collection of interviews
32
-
103
+ A collection of agents, scenarios and models and one survey that creates 'interviews'
33
104
  """
34
105
 
35
- jobs_runner_name = JobsRunnerDescriptor()
106
+ __documentation__ = "https://docs.expectedparrot.com/en/latest/jobs.html"
36
107
 
37
108
  def __init__(
38
109
  self,
39
- survey: Survey,
40
- agents: list[Agent] = None,
41
- models: list[LanguageModel] = None,
42
- scenarios: list[Scenario] = None,
110
+ survey: "Survey",
111
+ agents: Optional[Union[list[Agent], AgentList]] = None,
112
+ models: Optional[Union[ModelList, list[LanguageModel]]] = None,
113
+ scenarios: Optional[Union[ScenarioList, list[Scenario]]] = None,
43
114
  ):
115
+ """Initialize a Jobs instance.
116
+
117
+ :param survey: the survey to be used in the job
118
+ :param agents: a list of agents
119
+ :param models: a list of models
120
+ :param scenarios: a list of scenarios
121
+ """
122
+ self.run_config = RunConfig(
123
+ environment=RunEnvironment(), parameters=RunParameters()
124
+ )
125
+
44
126
  self.survey = survey
45
- self.agents = agents or []
46
- self.models = models or []
47
- self.scenarios = scenarios or []
127
+ self.agents: AgentList = agents
128
+ self.scenarios: ScenarioList = scenarios
129
+ self.models: ModelList = models
130
+
131
+ def add_running_env(self, running_env: RunEnvironment):
132
+ self.run_config.add_environment(running_env)
133
+ return self
134
+
135
+ def using_cache(self, cache: "Cache") -> Jobs:
136
+ """
137
+ Add a Cache to the job.
138
+
139
+ :param cache: the cache to add
140
+ """
141
+ self.run_config.add_cache(cache)
142
+ return self
143
+
144
+ def using_bucket_collection(self, bucket_collection: BucketCollection) -> Jobs:
145
+ """
146
+ Add a BucketCollection to the job.
147
+
148
+ :param bucket_collection: the bucket collection to add
149
+ """
150
+ self.run_config.add_bucket_collection(bucket_collection)
151
+ return self
152
+
153
+ def using_key_lookup(self, key_lookup: KeyLookup) -> Jobs:
154
+ """
155
+ Add a KeyLookup to the job.
156
+
157
+ :param key_lookup: the key lookup to add
158
+ """
159
+ self.run_config.add_key_lookup(key_lookup)
160
+ return self
161
+
162
+ def using(self, obj: Union[Cache, BucketCollection, KeyLookup]) -> Jobs:
163
+ """
164
+ Add a Cache, BucketCollection, or KeyLookup to the job.
165
+
166
+ :param obj: the object to add
167
+ """
168
+ from edsl.data.Cache import Cache
169
+ from edsl.language_models.key_management.KeyLookup import KeyLookup
170
+
171
+ if isinstance(obj, Cache):
172
+ self.using_cache(obj)
173
+ elif isinstance(obj, BucketCollection):
174
+ self.using_bucket_collection(obj)
175
+ elif isinstance(obj, KeyLookup):
176
+ self.using_key_lookup(obj)
177
+ return self
178
+
179
+ @property
180
+ def models(self):
181
+ return self._models
182
+
183
+ @models.setter
184
+ def models(self, value):
185
+ from edsl.language_models.ModelList import ModelList
186
+
187
+ if value:
188
+ if not isinstance(value, ModelList):
189
+ self._models = ModelList(value)
190
+ else:
191
+ self._models = value
192
+ else:
193
+ self._models = ModelList([])
194
+
195
+ # update the bucket collection if it exists
196
+ if self.run_config.environment.bucket_collection is None:
197
+ self.run_config.environment.bucket_collection = (
198
+ self.create_bucket_collection()
199
+ )
48
200
 
49
- self.__bucket_collection = None
201
+ @property
202
+ def agents(self):
203
+ return self._agents
204
+
205
+ @agents.setter
206
+ def agents(self, value):
207
+ from edsl.agents.AgentList import AgentList
208
+
209
+ if value:
210
+ if not isinstance(value, AgentList):
211
+ self._agents = AgentList(value)
212
+ else:
213
+ self._agents = value
214
+ else:
215
+ self._agents = AgentList([])
216
+
217
+ @property
218
+ def scenarios(self):
219
+ return self._scenarios
220
+
221
+ @scenarios.setter
222
+ def scenarios(self, value):
223
+ from edsl.scenarios.ScenarioList import ScenarioList
224
+ from edsl.results.Dataset import Dataset
225
+
226
+ if value:
227
+ if isinstance(
228
+ value, Dataset
229
+ ): # if the user passes in a Dataset, convert it to a ScenarioList
230
+ value = value.to_scenario_list()
231
+
232
+ if not isinstance(value, ScenarioList):
233
+ self._scenarios = ScenarioList(value)
234
+ else:
235
+ self._scenarios = value
236
+ else:
237
+ self._scenarios = ScenarioList([])
50
238
 
51
239
  def by(
52
240
  self,
@@ -54,14 +242,29 @@ class Jobs(Base):
54
242
  Agent,
55
243
  Scenario,
56
244
  LanguageModel,
57
- Sequence[Union[Agent, Scenario, LanguageModel]],
245
+ Sequence[Union["Agent", "Scenario", "LanguageModel"]],
58
246
  ],
59
247
  ) -> Jobs:
60
248
  """
61
- Adds Agents, Scenarios and LanguageModels to a job. If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute. Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
249
+ Add Agents, Scenarios and LanguageModels to a job.
250
+
251
+ :param args: objects or a sequence (list, tuple, ...) of objects of the same type
252
+
253
+ If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
254
+ Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
255
+
256
+ This 'by' is intended to create a fluent interface.
257
+
258
+ >>> from edsl.surveys.Survey import Survey
259
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
260
+ >>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
261
+ >>> j = Jobs(survey = Survey(questions=[q]))
262
+ >>> j
263
+ Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
264
+ >>> from edsl.agents.Agent import Agent; a = Agent(traits = {"status": "Sad"})
265
+ >>> j.by(a).agents
266
+ AgentList([Agent(traits = {'status': 'Sad'})])
62
267
 
63
- Arguments:
64
- - objects or a sequence (list, tuple, ...) of objects of the same type
65
268
 
66
269
  Notes:
67
270
  - all objects must implement the 'get_value', 'set_value', and `__add__` methods
@@ -69,187 +272,432 @@ class Jobs(Base):
69
272
  - 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
70
273
  - models: new models overwrite old models.
71
274
  """
72
- # if the first argument is a sequence, grab it and ignore other arguments
275
+ from edsl.jobs.JobsComponentConstructor import JobsComponentConstructor
73
276
 
74
- passed_objects = self._turn_args_to_list(args)
277
+ return JobsComponentConstructor(self).by(*args)
75
278
 
76
- current_objects, objects_key = self._get_current_objects_of_this_type(
77
- passed_objects[0]
78
- )
279
+ def prompts(self) -> "Dataset":
280
+ """Return a Dataset of prompts that will be used.
79
281
 
80
- if not current_objects:
81
- new_objects = passed_objects
82
- else:
83
- new_objects = self._merge_objects(passed_objects, current_objects)
84
282
 
85
- setattr(self, objects_key, new_objects) # update the job
86
- return self
87
-
88
- @staticmethod
89
- def _turn_args_to_list(args):
90
- def did_user_pass_a_sequence(args):
91
- """
92
- >>> did_user_pass_a_sequence([1,2,3])
93
- True
94
- >>> did_user_pass_a_sequence(1)
95
- False
96
- """
97
- return len(args) == 1 and isinstance(args[0], Sequence)
98
-
99
- if did_user_pass_a_sequence(args):
100
- return list(args[0])
101
- else:
102
- return list(args)
283
+ >>> from edsl.jobs import Jobs
284
+ >>> Jobs.example().prompts()
285
+ Dataset(...)
286
+ """
287
+ return JobsPrompts(self).prompts()
103
288
 
104
- def _get_current_objects_of_this_type(self, object):
105
- class_to_key = {
106
- Agent: "agents",
107
- Scenario: "scenarios",
108
- LanguageModel: "models",
109
- }
110
- for class_type in class_to_key:
111
- if isinstance(object, class_type) or issubclass(
112
- object.__class__, class_type
113
- ):
114
- key = class_to_key[class_type]
115
- break
289
+ def show_prompts(self, all: bool = False) -> None:
290
+ """Print the prompts."""
291
+ if all:
292
+ return self.prompts().to_scenario_list().table()
116
293
  else:
117
- raise ValueError(
118
- f"First argument must be an Agent, Scenario, or LanguageModel, not {object}"
294
+ return (
295
+ self.prompts().to_scenario_list().table("user_prompt", "system_prompt")
119
296
  )
120
- current_objects = getattr(self, key, None)
121
- return current_objects, key
122
297
 
123
298
  @staticmethod
124
- def _merge_objects(passed_objects, current_objects):
299
+ def estimate_prompt_cost(
300
+ system_prompt: str,
301
+ user_prompt: str,
302
+ price_lookup: dict,
303
+ inference_service: str,
304
+ model: str,
305
+ ) -> dict:
306
+ """
307
+ Estimate the cost of running the prompts.
308
+ :param iterations: the number of iterations to run
309
+ :param system_prompt: the system prompt
310
+ :param user_prompt: the user prompt
311
+ :param price_lookup: the price lookup
312
+ :param inference_service: the inference service
313
+ :param model: the model name
314
+ """
315
+ return JobsPrompts.estimate_prompt_cost(
316
+ system_prompt, user_prompt, price_lookup, inference_service, model
317
+ )
318
+
319
+ def estimate_job_cost(self, iterations: int = 1) -> dict:
125
320
  """
126
- Combines all the existing objects with the new objects
127
- For example, if the user passes in 3 agents,
128
- and there are 2 existing agents, this will create 6 new agents
321
+ Estimate the cost of running the job.
129
322
 
130
- >>> Jobs(survey = [])._merge_objects([1,2,3], [4,5,6])
131
- [5, 6, 7, 6, 7, 8, 7, 8, 9]
323
+ :param iterations: the number of iterations to run
132
324
  """
133
- new_objects = []
134
- for current_object in current_objects:
135
- for new_object in passed_objects:
136
- new_objects.append(current_object + new_object)
137
- return new_objects
325
+ return JobsPrompts(self).estimate_job_cost(iterations)
138
326
 
139
- def interviews(self) -> list[Interview]:
327
+ def estimate_job_cost_from_external_prices(
328
+ self, price_lookup: dict, iterations: int = 1
329
+ ) -> dict:
330
+ return JobsPrompts(self).estimate_job_cost_from_external_prices(
331
+ price_lookup, iterations
332
+ )
333
+
334
+ @staticmethod
335
+ def compute_job_cost(job_results: Results) -> float:
140
336
  """
141
- Returns a list of Interviews, that will eventually be used by the JobRunner.
142
- - Returns one Interview for each combination of Agent, Scenario, and LanguageModel.
143
- - If any of Agents, Scenarios, or LanguageModels are missing, fills in with defaults. Note that this will change the corresponding class attributes.
337
+ Computes the cost of a completed job in USD.
144
338
  """
339
+ return job_results.compute_job_cost()
340
+
341
+ def replace_missing_objects(self) -> None:
342
+ from edsl.agents.Agent import Agent
343
+ from edsl.language_models.model import Model
344
+ from edsl.scenarios.Scenario import Scenario
345
+
145
346
  self.agents = self.agents or [Agent()]
146
- self.models = self.models or [LanguageModelOpenAIThreeFiveTurbo(use_cache=True)]
347
+ self.models = self.models or [Model()]
147
348
  self.scenarios = self.scenarios or [Scenario()]
148
- interviews = []
149
- for agent, scenario, model in product(self.agents, self.scenarios, self.models):
150
- interview = Interview(
151
- survey=self.survey, agent=agent, scenario=scenario, model=model
349
+
350
+ def generate_interviews(self) -> Generator[Interview, None, None]:
351
+ """
352
+ Generate interviews.
353
+
354
+ Note that this sets the agents, model and scenarios if they have not been set. This is a side effect of the method.
355
+ This is useful because a user can create a job without setting the agents, models, or scenarios, and the job will still run,
356
+ with us filling in defaults.
357
+
358
+ """
359
+ from edsl.jobs.InterviewsConstructor import InterviewsConstructor
360
+
361
+ self.replace_missing_objects()
362
+ yield from InterviewsConstructor(
363
+ self, cache=self.run_config.environment.cache
364
+ ).create_interviews()
365
+
366
+ def interviews(self) -> list[Interview]:
367
+ """
368
+ Return a list of :class:`edsl.jobs.interviews.Interview` objects.
369
+
370
+ It returns one Interview for each combination of Agent, Scenario, and LanguageModel.
371
+ If any of Agents, Scenarios, or LanguageModels are missing, it fills in with defaults.
372
+
373
+ >>> from edsl.jobs import Jobs
374
+ >>> j = Jobs.example()
375
+ >>> len(j.interviews())
376
+ 4
377
+ >>> j.interviews()[0]
378
+ Interview(agent = Agent(traits = {'status': 'Joyful'}), survey = Survey(...), scenario = Scenario({'period': 'morning'}), model = Model(...))
379
+ """
380
+ return list(self.generate_interviews())
381
+
382
+ @classmethod
383
+ def from_interviews(cls, interview_list) -> "Jobs":
384
+ """Return a Jobs instance from a list of interviews.
385
+
386
+ This is useful when you have, say, a list of failed interviews and you want to create
387
+ a new job with only those interviews.
388
+ """
389
+ survey = interview_list[0].survey
390
+ # get all the models
391
+ models = list(set([interview.model for interview in interview_list]))
392
+ jobs = cls(survey)
393
+ jobs.models = models
394
+ jobs._interviews = interview_list
395
+ return jobs
396
+
397
+ def create_bucket_collection(self) -> BucketCollection:
398
+ """
399
+ Create a collection of buckets for each model.
400
+
401
+ These buckets are used to track API calls and token usage.
402
+
403
+ >>> from edsl.jobs import Jobs
404
+ >>> from edsl import Model
405
+ >>> j = Jobs.example().by(Model(temperature = 1), Model(temperature = 0.5))
406
+ >>> bc = j.create_bucket_collection()
407
+ >>> bc
408
+ BucketCollection(...)
409
+ """
410
+ return BucketCollection.from_models(self.models)
411
+
412
+ def html(self):
413
+ """Return the HTML representations for each scenario"""
414
+ links = []
415
+ for index, scenario in enumerate(self.scenarios):
416
+ links.append(
417
+ self.survey.html(
418
+ scenario=scenario, return_link=True, cta=f"Scenario {index}"
419
+ )
152
420
  )
153
- interviews.append(interview)
154
- return interviews
421
+ return links
422
+
423
+ def __hash__(self):
424
+ """Allow the model to be used as a key in a dictionary.
425
+
426
+ >>> from edsl.jobs import Jobs
427
+ >>> hash(Jobs.example())
428
+ 846655441787442972
155
429
 
156
- def create_bucket_collection(self):
157
430
  """
158
- Creates a collection of buckets for each model.
431
+ from edsl.utilities.utilities import dict_hash
432
+
433
+ return dict_hash(self.to_dict(add_edsl_version=False))
434
+
435
+ def _output(self, message) -> None:
436
+ """Check if a Job is verbose. If so, print the message."""
437
+ if self.run_config.parameters.verbose:
438
+ print(message)
439
+ # if hasattr(self, "verbose") and self.verbose:
440
+ # print(message)
441
+
442
+ def all_question_parameters(self) -> set:
443
+ """Return all the fields in the questions in the survey.
444
+ >>> from edsl.jobs import Jobs
445
+ >>> Jobs.example().all_question_parameters()
446
+ {'period'}
159
447
  """
160
- bucket_collection = BucketCollection()
161
- for model in self.models:
162
- bucket_collection.add_model(model)
163
- return bucket_collection
448
+ return set.union(*[question.parameters for question in self.survey.questions])
164
449
 
165
- @property
166
- def bucket_collection(self):
167
- if self.__bucket_collection is None:
168
- self.__bucket_collection = self.create_bucket_collection()
169
- return self.__bucket_collection
450
+ def use_remote_cache(self) -> bool:
451
+ import requests
452
+
453
+ if self.run_config.parameters.disable_remote_cache:
454
+ return False
455
+ if not self.run_config.parameters.disable_remote_cache:
456
+ try:
457
+ from edsl.coop.coop import Coop
458
+
459
+ user_edsl_settings = Coop().edsl_settings
460
+ return user_edsl_settings.get("remote_caching", False)
461
+ except requests.ConnectionError:
462
+ pass
463
+ except CoopServerResponseError as e:
464
+ pass
170
465
 
171
- def run(
466
+ return False
467
+
468
+ def _remote_results(
172
469
  self,
173
- n: int = 1,
174
- debug: bool = False,
175
- verbose: bool = False,
176
- progress_bar: bool = False,
177
- dry_run: bool = False,
178
- streaming: bool = False,
179
- db: Database = database,
180
- ) -> Union[Results, ResultsAPI, None]:
181
- """
182
- Runs the Job: conducts Interviews and returns their results.
183
- - `method`: "serial" or "threaded", defaults to "serial"
184
- - `n`: how many times to run each interview
185
- - `debug`: prints debug messages
186
- - `verbose`: prints messages
187
- - `progress_bar`: shows a progress bar
188
- """
189
- # self.job_runner_name = method
190
- if dry_run:
191
- self.job_runner_name = "dry_run"
192
- elif streaming:
193
- self.job_runner_name = "streaming"
194
- else:
195
- self.job_runner_name = "asyncio"
470
+ ) -> Union["Results", None]:
471
+ from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
196
472
 
197
- if (emeritus_api_key := CONFIG.get("EMERITUS_API_KEY")) == "local":
198
- results = self._run_local(
199
- n=n, verbose=verbose, debug=debug, progress_bar=progress_bar, db=db
473
+ jh = JobsRemoteInferenceHandler(
474
+ self, verbose=self.run_config.parameters.verbose
475
+ )
476
+ if jh.use_remote_inference(self.run_config.parameters.disable_remote_inference):
477
+ job_info = jh.create_remote_inference_job(
478
+ iterations=self.run_config.parameters.n,
479
+ remote_inference_description=self.run_config.parameters.remote_inference_description,
480
+ remote_inference_results_visibility=self.run_config.parameters.remote_inference_results_visibility,
200
481
  )
482
+ results = jh.poll_remote_inference_job(job_info)
483
+ return results
484
+ else:
485
+ return None
486
+
487
+ def _prepare_to_run(self) -> None:
488
+ "This makes sure that the job is ready to run and that keys are in place for a remote job."
489
+ CheckSurveyScenarioCompatibility(self.survey, self.scenarios).check()
490
+
491
+ def _check_if_remote_keys_ok(self):
492
+ jc = JobsChecks(self)
493
+ if jc.needs_key_process():
494
+ jc.key_process()
495
+
496
+ def _check_if_local_keys_ok(self):
497
+ jc = JobsChecks(self)
498
+ if self.run_config.parameters.check_api_keys:
499
+ jc.check_api_keys()
500
+
501
+ async def _execute_with_remote_cache(self, run_job_async: bool) -> Results:
502
+ use_remote_cache = self.use_remote_cache()
503
+
504
+ from edsl.coop.coop import Coop
505
+ from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
506
+ from edsl.data.Cache import Cache
507
+
508
+ assert isinstance(self.run_config.environment.cache, Cache)
509
+
510
+ # with RemoteCacheSync(
511
+ # coop=Coop(),
512
+ # cache=self.run_config.environment.cache,
513
+ # output_func=self._output,
514
+ # remote_cache=use_remote_cache,
515
+ # remote_cache_description=self.run_config.parameters.remote_cache_description,
516
+ # ):
517
+ runner = JobsRunnerAsyncio(self, environment=self.run_config.environment)
518
+ if run_job_async:
519
+ results = await runner.run_async(self.run_config.parameters)
520
+ else:
521
+ results = runner.run(self.run_config.parameters)
522
+ return results
523
+
524
+ def _setup_and_check(self) -> Tuple[RunConfig, Optional[Results]]:
525
+ self._prepare_to_run()
526
+ self._check_if_remote_keys_ok()
527
+
528
+ # first try to run the job remotely
529
+ if results := self._remote_results():
530
+ return results
531
+
532
+ self._check_if_local_keys_ok()
533
+ return None
534
+
535
+ @property
536
+ def num_interviews(self):
537
+ if self.run_config.parameters.n is None:
538
+ return len(self)
201
539
  else:
202
- results = self._run_remote(
203
- api_key=emeritus_api_key, job_dict=self.to_dict()
540
+ return len(self) * self.run_config.parameters.n
541
+
542
+ def _run(self, config: RunConfig):
543
+ "Shared code for run and run_async"
544
+ if config.environment.cache is not None:
545
+ self.run_config.environment.cache = config.environment.cache
546
+ if config.environment.jobs_runner_status is not None:
547
+ self.run_config.environment.jobs_runner_status = (
548
+ config.environment.jobs_runner_status
204
549
  )
205
550
 
206
- return results
551
+ if config.environment.bucket_collection is not None:
552
+ self.run_config.environment.bucket_collection = (
553
+ config.environment.bucket_collection
554
+ )
207
555
 
208
- def _run_local(self, *args, db: Database = database, **kwargs):
209
- """Runs the job locally."""
210
- db._health_check_pre_run()
211
- JobRunner = JobsRunnersRegistry[self.job_runner_name](jobs=self)
212
- results = JobRunner.run(*args, **kwargs)
213
- db._health_check_post_run()
214
- return results
556
+ if config.environment.key_lookup is not None:
557
+ self.run_config.environment.key_lookup = config.environment.key_lookup
215
558
 
216
- def _run_remote(self, *args, **kwargs):
217
- """Runs the job remotely."""
218
- results = JobRunnerAPI(*args, **kwargs)
219
- return results
559
+ # replace the parameters with the ones from the config
560
+ self.run_config.parameters = config.parameters
561
+
562
+ self.replace_missing_objects()
563
+
564
+ # try to run remotely first
565
+ self._prepare_to_run()
566
+ self._check_if_remote_keys_ok()
567
+
568
+ if (
569
+ self.run_config.environment.cache is None
570
+ or self.run_config.environment.cache is True
571
+ ):
572
+ from edsl.data.CacheHandler import CacheHandler
573
+
574
+ self.run_config.environment.cache = CacheHandler().get_cache()
575
+
576
+ if self.run_config.environment.cache is False:
577
+ from edsl.data.Cache import Cache
578
+
579
+ self.run_config.environment.cache = Cache(immediate_write=False)
580
+
581
+ # first try to run the job remotely
582
+ if results := self._remote_results():
583
+ return results
584
+
585
+ self._check_if_local_keys_ok()
586
+
587
+ if config.environment.bucket_collection is None:
588
+ self.run_config.environment.bucket_collection = (
589
+ self.create_bucket_collection()
590
+ )
591
+
592
+ @with_config
593
+ def run(self, *, config: RunConfig) -> "Results":
594
+ """
595
+ Runs the Job: conducts Interviews and returns their results.
596
+
597
+ :param n: How many times to run each interview
598
+ :param progress_bar: Whether to show a progress bar
599
+ :param stop_on_exception: Stops the job if an exception is raised
600
+ :param check_api_keys: Raises an error if API keys are invalid
601
+ :param verbose: Prints extra messages
602
+ :param remote_cache_description: Specifies a description for this group of entries in the remote cache
603
+ :param remote_inference_description: Specifies a description for the remote inference job
604
+ :param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
605
+ :param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
606
+ :param disable_remote_inference: If True, the job will not use remote inference
607
+ :param cache: A Cache object to store results
608
+ :param bucket_collection: A BucketCollection object to track API calls
609
+ :param key_lookup: A KeyLookup object to manage API keys
610
+ """
611
+ self._run(config)
612
+
613
+ return asyncio.run(self._execute_with_remote_cache(run_job_async=False))
614
+
615
+ @with_config
616
+ async def run_async(self, *, config: RunConfig) -> "Results":
617
+ """
618
+ Runs the Job: conducts Interviews and returns their results.
619
+
620
+ :param n: How many times to run each interview
621
+ :param progress_bar: Whether to show a progress bar
622
+ :param stop_on_exception: Stops the job if an exception is raised
623
+ :param check_api_keys: Raises an error if API keys are invalid
624
+ :param verbose: Prints extra messages
625
+ :param remote_cache_description: Specifies a description for this group of entries in the remote cache
626
+ :param remote_inference_description: Specifies a description for the remote inference job
627
+ :param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
628
+ :param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
629
+ :param disable_remote_inference: If True, the job will not use remote inference
630
+ :param cache: A Cache object to store results
631
+ :param bucket_collection: A BucketCollection object to track API calls
632
+ :param key_lookup: A KeyLookup object to manage API keys
633
+ """
634
+ self._run(config)
635
+
636
+ return await self._execute_with_remote_cache(run_job_async=True)
220
637
 
221
- #######################
222
- # Dunder methods
223
- #######################
224
638
  def __repr__(self) -> str:
225
- """Returns an eval-able string representation of the Jobs instance."""
639
+ """Return an eval-able string representation of the Jobs instance."""
226
640
  return f"Jobs(survey={repr(self.survey)}, agents={repr(self.agents)}, models={repr(self.models)}, scenarios={repr(self.scenarios)})"
227
641
 
642
+ def _summary(self):
643
+ return {
644
+ "questions": len(self.survey),
645
+ "agents": len(self.agents or [1]),
646
+ "models": len(self.models or [1]),
647
+ "scenarios": len(self.scenarios or [1]),
648
+ }
649
+
228
650
  def __len__(self) -> int:
229
- """Returns the number of questions that will be asked while running this job."""
230
- number_of_questions = (
651
+ """Return the number of interviews that will be conducted for one iteration of this job.
652
+ An interview is the result of one survey, taken by one agent, with one model, with one scenario.
653
+
654
+ >>> from edsl.jobs import Jobs
655
+ >>> len(Jobs.example())
656
+ 4
657
+ """
658
+ number_of_interviews = (
231
659
  len(self.agents or [1])
232
660
  * len(self.scenarios or [1])
233
661
  * len(self.models or [1])
234
- * len(self.survey)
235
662
  )
236
- return number_of_questions
237
-
238
- #######################
239
- # Serialization methods
240
- #######################
241
- def to_dict(self) -> dict:
242
- """Converts the Jobs instance to a dictionary."""
243
- return {
244
- "survey": self.survey.to_dict(),
245
- "agents": [agent.to_dict() for agent in self.agents],
246
- "models": [model.to_dict() for model in self.models],
247
- "scenarios": [scenario.to_dict() for scenario in self.scenarios],
663
+ return number_of_interviews
664
+
665
+ def to_dict(self, add_edsl_version=True):
666
+ d = {
667
+ "survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
668
+ "agents": [
669
+ agent.to_dict(add_edsl_version=add_edsl_version)
670
+ for agent in self.agents
671
+ ],
672
+ "models": [
673
+ model.to_dict(add_edsl_version=add_edsl_version)
674
+ for model in self.models
675
+ ],
676
+ "scenarios": [
677
+ scenario.to_dict(add_edsl_version=add_edsl_version)
678
+ for scenario in self.scenarios
679
+ ],
248
680
  }
681
+ if add_edsl_version:
682
+ from edsl import __version__
683
+
684
+ d["edsl_version"] = __version__
685
+ d["edsl_class_name"] = "Jobs"
686
+
687
+ return d
688
+
689
+ def table(self):
690
+ return self.prompts().to_scenario_list().table()
249
691
 
250
692
  @classmethod
693
+ @remove_edsl_version
251
694
  def from_dict(cls, data: dict) -> Jobs:
252
- """Creates a Jobs instance from a JSON string."""
695
+ """Creates a Jobs instance from a dictionary."""
696
+ from edsl.surveys.Survey import Survey
697
+ from edsl.agents.Agent import Agent
698
+ from edsl.language_models.LanguageModel import LanguageModel
699
+ from edsl.scenarios.Scenario import Scenario
700
+
253
701
  return cls(
254
702
  survey=Survey.from_dict(data["survey"]),
255
703
  agents=[Agent.from_dict(agent) for agent in data["agents"]],
@@ -257,14 +705,45 @@ class Jobs(Base):
257
705
  scenarios=[Scenario.from_dict(scenario) for scenario in data["scenarios"]],
258
706
  )
259
707
 
260
- #######################
261
- # Example methods
262
- #######################
708
+ def __eq__(self, other: Jobs) -> bool:
709
+ """Return True if the Jobs instance is equal to another Jobs instance.
710
+
711
+ >>> from edsl.jobs import Jobs
712
+ >>> Jobs.example() == Jobs.example()
713
+ True
714
+
715
+ """
716
+ return hash(self) == hash(other)
717
+
263
718
  @classmethod
264
- def example(cls) -> Jobs:
265
- from edsl.questions import QuestionMultipleChoice
719
+ def example(
720
+ cls,
721
+ throw_exception_probability: float = 0.0,
722
+ randomize: bool = False,
723
+ test_model=False,
724
+ ) -> Jobs:
725
+ """Return an example Jobs instance.
726
+
727
+ :param throw_exception_probability: the probability that an exception will be thrown when answering a question. This is useful for testing error handling.
728
+ :param randomize: whether to randomize the job by adding a random string to the period
729
+ :param test_model: whether to use a test model
730
+
731
+ >>> Jobs.example()
732
+ Jobs(...)
266
733
 
267
- from edsl import Agent
734
+ """
735
+ import random
736
+ from uuid import uuid4
737
+ from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
738
+ from edsl.agents.Agent import Agent
739
+ from edsl.scenarios.Scenario import Scenario
740
+
741
+ addition = "" if not randomize else str(uuid4())
742
+
743
+ if test_model:
744
+ from edsl.language_models.LanguageModel import LanguageModel
745
+
746
+ m = LanguageModel.example(test_model=True)
268
747
 
269
748
  # (status, question, period)
270
749
  agent_answers = {
@@ -279,6 +758,10 @@ class Jobs(Base):
279
758
  }
280
759
 
281
760
  def answer_question_directly(self, question, scenario):
761
+ """Return the answer to a question. This is a method that can be added to an agent."""
762
+
763
+ if random.random() < throw_exception_probability:
764
+ raise Exception("Error!")
282
765
  return agent_answers[
283
766
  (self.traits["status"], question.question_name, scenario["period"])
284
767
  ]
@@ -299,46 +782,43 @@ class Jobs(Base):
299
782
  question_options=["Good", "Great", "OK", "Terrible"],
300
783
  question_name="how_feeling_yesterday",
301
784
  )
785
+ from edsl.surveys.Survey import Survey
786
+ from edsl.scenarios.ScenarioList import ScenarioList
787
+
302
788
  base_survey = Survey(questions=[q1, q2])
303
789
 
304
- job = base_survey.by(
305
- Scenario({"period": "morning"}), Scenario({"period": "afternoon"})
306
- ).by(joy_agent, sad_agent)
790
+ scenario_list = ScenarioList(
791
+ [
792
+ Scenario({"period": f"morning{addition}"}),
793
+ Scenario({"period": "afternoon"}),
794
+ ]
795
+ )
796
+ if test_model:
797
+ job = base_survey.by(m).by(scenario_list).by(joy_agent, sad_agent)
798
+ else:
799
+ job = base_survey.by(scenario_list).by(joy_agent, sad_agent)
307
800
 
308
801
  return job
309
802
 
310
- def rich_print(self):
311
- """Prints a rich representation of the Jobs instance."""
312
- from rich.table import Table
313
-
314
- table = Table(title="Jobs")
315
- table.add_column("Jobs")
316
- table.add_row(self.survey.rich_print())
317
- return table
318
-
319
803
  def code(self):
804
+ """Return the code to create this instance."""
320
805
  raise NotImplementedError
321
806
 
322
807
 
323
808
  def main():
324
- from edsl.jobs import Jobs
809
+ """Run the module's doctests."""
810
+ from edsl.jobs.Jobs import Jobs
811
+ from edsl.data.Cache import Cache
325
812
 
326
813
  job = Jobs.example()
327
- len(job) == 8
328
- results = job.run(debug=True)
329
- len(results) == 8
814
+ len(job) == 4
815
+ results = job.run(cache=Cache())
816
+ len(results) == 4
330
817
  results
331
818
 
332
819
 
333
820
  if __name__ == "__main__":
821
+ """Run the module's doctests."""
334
822
  import doctest
335
823
 
336
- doctest.testmod()
337
-
338
- from edsl.jobs import Jobs
339
-
340
- job = Jobs.example()
341
- len(job) == 8
342
- results, info = job.run(debug=True)
343
- len(results) == 8
344
- results
824
+ doctest.testmod(optionflags=doctest.ELLIPSIS)