edsl 0.1.38.dev3__py3-none-any.whl → 0.1.39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. edsl/Base.py +413 -303
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +57 -49
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +1071 -858
  7. edsl/agents/AgentList.py +551 -362
  8. edsl/agents/Invigilator.py +284 -222
  9. edsl/agents/InvigilatorBase.py +257 -284
  10. edsl/agents/PromptConstructor.py +272 -353
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/__init__.py +2 -3
  14. edsl/agents/descriptors.py +99 -99
  15. edsl/agents/prompt_helpers.py +129 -129
  16. edsl/agents/question_option_processor.py +172 -0
  17. edsl/auto/AutoStudy.py +130 -117
  18. edsl/auto/StageBase.py +243 -230
  19. edsl/auto/StageGenerateSurvey.py +178 -178
  20. edsl/auto/StageLabelQuestions.py +125 -125
  21. edsl/auto/StagePersona.py +61 -61
  22. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  23. edsl/auto/StagePersonaDimensionValues.py +74 -74
  24. edsl/auto/StagePersonaDimensions.py +69 -69
  25. edsl/auto/StageQuestions.py +74 -73
  26. edsl/auto/SurveyCreatorPipeline.py +21 -21
  27. edsl/auto/utilities.py +218 -224
  28. edsl/base/Base.py +279 -279
  29. edsl/config.py +177 -149
  30. edsl/conversation/Conversation.py +290 -290
  31. edsl/conversation/car_buying.py +59 -58
  32. edsl/conversation/chips.py +95 -95
  33. edsl/conversation/mug_negotiation.py +81 -81
  34. edsl/conversation/next_speaker_utilities.py +93 -93
  35. edsl/coop/CoopFunctionsMixin.py +15 -0
  36. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  37. edsl/coop/PriceFetcher.py +54 -54
  38. edsl/coop/__init__.py +2 -2
  39. edsl/coop/coop.py +1106 -961
  40. edsl/coop/utils.py +131 -131
  41. edsl/data/Cache.py +573 -530
  42. edsl/data/CacheEntry.py +230 -228
  43. edsl/data/CacheHandler.py +168 -149
  44. edsl/data/RemoteCacheSync.py +186 -97
  45. edsl/data/SQLiteDict.py +292 -292
  46. edsl/data/__init__.py +5 -4
  47. edsl/data/orm.py +10 -10
  48. edsl/data_transfer_models.py +74 -73
  49. edsl/enums.py +202 -173
  50. edsl/exceptions/BaseException.py +21 -21
  51. edsl/exceptions/__init__.py +54 -54
  52. edsl/exceptions/agents.py +54 -42
  53. edsl/exceptions/cache.py +5 -5
  54. edsl/exceptions/configuration.py +16 -16
  55. edsl/exceptions/coop.py +10 -10
  56. edsl/exceptions/data.py +14 -14
  57. edsl/exceptions/general.py +34 -34
  58. edsl/exceptions/inference_services.py +5 -0
  59. edsl/exceptions/jobs.py +33 -33
  60. edsl/exceptions/language_models.py +63 -63
  61. edsl/exceptions/prompts.py +15 -15
  62. edsl/exceptions/questions.py +109 -91
  63. edsl/exceptions/results.py +29 -29
  64. edsl/exceptions/scenarios.py +29 -22
  65. edsl/exceptions/surveys.py +37 -37
  66. edsl/inference_services/AnthropicService.py +106 -87
  67. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  68. edsl/inference_services/AvailableModelFetcher.py +215 -0
  69. edsl/inference_services/AwsBedrock.py +118 -120
  70. edsl/inference_services/AzureAI.py +215 -217
  71. edsl/inference_services/DeepInfraService.py +18 -18
  72. edsl/inference_services/GoogleService.py +143 -156
  73. edsl/inference_services/GroqService.py +20 -20
  74. edsl/inference_services/InferenceServiceABC.py +80 -147
  75. edsl/inference_services/InferenceServicesCollection.py +138 -97
  76. edsl/inference_services/MistralAIService.py +120 -123
  77. edsl/inference_services/OllamaService.py +18 -18
  78. edsl/inference_services/OpenAIService.py +236 -224
  79. edsl/inference_services/PerplexityService.py +160 -0
  80. edsl/inference_services/ServiceAvailability.py +135 -0
  81. edsl/inference_services/TestService.py +90 -89
  82. edsl/inference_services/TogetherAIService.py +172 -170
  83. edsl/inference_services/data_structures.py +134 -0
  84. edsl/inference_services/models_available_cache.py +118 -118
  85. edsl/inference_services/rate_limits_cache.py +25 -25
  86. edsl/inference_services/registry.py +41 -39
  87. edsl/inference_services/write_available.py +10 -10
  88. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  89. edsl/jobs/Answers.py +43 -56
  90. edsl/jobs/FetchInvigilator.py +47 -0
  91. edsl/jobs/InterviewTaskManager.py +98 -0
  92. edsl/jobs/InterviewsConstructor.py +50 -0
  93. edsl/jobs/Jobs.py +823 -1358
  94. edsl/jobs/JobsChecks.py +172 -0
  95. edsl/jobs/JobsComponentConstructor.py +189 -0
  96. edsl/jobs/JobsPrompts.py +270 -0
  97. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  98. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  99. edsl/jobs/RequestTokenEstimator.py +30 -0
  100. edsl/jobs/__init__.py +1 -1
  101. edsl/jobs/async_interview_runner.py +138 -0
  102. edsl/jobs/buckets/BucketCollection.py +104 -63
  103. edsl/jobs/buckets/ModelBuckets.py +65 -65
  104. edsl/jobs/buckets/TokenBucket.py +283 -251
  105. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  106. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  107. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  108. edsl/jobs/data_structures.py +120 -0
  109. edsl/jobs/decorators.py +35 -0
  110. edsl/jobs/interviews/Interview.py +396 -661
  111. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  112. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  113. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  114. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  115. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  116. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  117. edsl/jobs/interviews/ReportErrors.py +66 -66
  118. edsl/jobs/interviews/interview_status_enum.py +9 -9
  119. edsl/jobs/jobs_status_enums.py +9 -0
  120. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  121. edsl/jobs/results_exceptions_handler.py +98 -0
  122. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -361
  123. edsl/jobs/runners/JobsRunnerStatus.py +298 -332
  124. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  125. edsl/jobs/tasks/TaskCreators.py +64 -64
  126. edsl/jobs/tasks/TaskHistory.py +470 -451
  127. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  128. edsl/jobs/tasks/task_status_enum.py +161 -163
  129. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  130. edsl/jobs/tokens/TokenUsage.py +34 -34
  131. edsl/language_models/ComputeCost.py +63 -0
  132. edsl/language_models/LanguageModel.py +626 -708
  133. edsl/language_models/ModelList.py +164 -109
  134. edsl/language_models/PriceManager.py +127 -0
  135. edsl/language_models/RawResponseHandler.py +106 -0
  136. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  137. edsl/language_models/ServiceDataSources.py +0 -0
  138. edsl/language_models/__init__.py +2 -3
  139. edsl/language_models/fake_openai_call.py +15 -15
  140. edsl/language_models/fake_openai_service.py +61 -61
  141. edsl/language_models/key_management/KeyLookup.py +63 -0
  142. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  143. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  144. edsl/language_models/key_management/__init__.py +0 -0
  145. edsl/language_models/key_management/models.py +131 -0
  146. edsl/language_models/model.py +256 -0
  147. edsl/language_models/repair.py +156 -156
  148. edsl/language_models/utilities.py +65 -64
  149. edsl/notebooks/Notebook.py +263 -258
  150. edsl/notebooks/NotebookToLaTeX.py +142 -0
  151. edsl/notebooks/__init__.py +1 -1
  152. edsl/prompts/Prompt.py +352 -357
  153. edsl/prompts/__init__.py +2 -2
  154. edsl/questions/ExceptionExplainer.py +77 -0
  155. edsl/questions/HTMLQuestion.py +103 -0
  156. edsl/questions/QuestionBase.py +518 -660
  157. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  158. edsl/questions/QuestionBudget.py +227 -227
  159. edsl/questions/QuestionCheckBox.py +359 -359
  160. edsl/questions/QuestionExtract.py +180 -183
  161. edsl/questions/QuestionFreeText.py +113 -114
  162. edsl/questions/QuestionFunctional.py +166 -166
  163. edsl/questions/QuestionList.py +223 -231
  164. edsl/questions/QuestionMatrix.py +265 -0
  165. edsl/questions/QuestionMultipleChoice.py +330 -286
  166. edsl/questions/QuestionNumerical.py +151 -153
  167. edsl/questions/QuestionRank.py +314 -324
  168. edsl/questions/Quick.py +41 -41
  169. edsl/questions/SimpleAskMixin.py +74 -73
  170. edsl/questions/__init__.py +27 -26
  171. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  172. edsl/questions/compose_questions.py +98 -98
  173. edsl/questions/data_structures.py +20 -0
  174. edsl/questions/decorators.py +21 -21
  175. edsl/questions/derived/QuestionLikertFive.py +76 -76
  176. edsl/questions/derived/QuestionLinearScale.py +90 -87
  177. edsl/questions/derived/QuestionTopK.py +93 -93
  178. edsl/questions/derived/QuestionYesNo.py +82 -82
  179. edsl/questions/descriptors.py +427 -413
  180. edsl/questions/loop_processor.py +149 -0
  181. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  182. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  183. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  184. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  185. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  186. edsl/questions/prompt_templates/question_list.jinja +17 -17
  187. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  188. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  189. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  190. edsl/questions/question_registry.py +177 -147
  191. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  192. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  193. edsl/questions/response_validator_factory.py +34 -0
  194. edsl/questions/settings.py +12 -12
  195. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  196. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  197. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  198. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  199. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  200. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  201. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  202. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  203. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  204. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  205. edsl/questions/templates/list/question_presentation.jinja +5 -5
  206. edsl/questions/templates/matrix/__init__.py +1 -0
  207. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  208. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  209. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  210. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  211. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  212. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  213. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  214. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  215. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  216. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  217. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  218. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  219. edsl/results/CSSParameterizer.py +108 -0
  220. edsl/results/Dataset.py +587 -293
  221. edsl/results/DatasetExportMixin.py +594 -717
  222. edsl/results/DatasetTree.py +295 -145
  223. edsl/results/MarkdownToDocx.py +122 -0
  224. edsl/results/MarkdownToPDF.py +111 -0
  225. edsl/results/Result.py +557 -456
  226. edsl/results/Results.py +1183 -1071
  227. edsl/results/ResultsExportMixin.py +45 -43
  228. edsl/results/ResultsGGMixin.py +121 -121
  229. edsl/results/TableDisplay.py +125 -0
  230. edsl/results/TextEditor.py +50 -0
  231. edsl/results/__init__.py +2 -2
  232. edsl/results/file_exports.py +252 -0
  233. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  234. edsl/results/{Selector.py → results_selector.py} +145 -135
  235. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  236. edsl/results/smart_objects.py +96 -0
  237. edsl/results/table_data_class.py +12 -0
  238. edsl/results/table_display.css +78 -0
  239. edsl/results/table_renderers.py +118 -0
  240. edsl/results/tree_explore.py +115 -115
  241. edsl/scenarios/ConstructDownloadLink.py +109 -0
  242. edsl/scenarios/DocumentChunker.py +102 -0
  243. edsl/scenarios/DocxScenario.py +16 -0
  244. edsl/scenarios/FileStore.py +543 -458
  245. edsl/scenarios/PdfExtractor.py +40 -0
  246. edsl/scenarios/Scenario.py +498 -544
  247. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  248. edsl/scenarios/ScenarioList.py +1458 -1112
  249. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  250. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  251. edsl/scenarios/__init__.py +3 -4
  252. edsl/scenarios/directory_scanner.py +96 -0
  253. edsl/scenarios/file_methods.py +85 -0
  254. edsl/scenarios/handlers/__init__.py +13 -0
  255. edsl/scenarios/handlers/csv.py +49 -0
  256. edsl/scenarios/handlers/docx.py +76 -0
  257. edsl/scenarios/handlers/html.py +37 -0
  258. edsl/scenarios/handlers/json.py +111 -0
  259. edsl/scenarios/handlers/latex.py +5 -0
  260. edsl/scenarios/handlers/md.py +51 -0
  261. edsl/scenarios/handlers/pdf.py +68 -0
  262. edsl/scenarios/handlers/png.py +39 -0
  263. edsl/scenarios/handlers/pptx.py +105 -0
  264. edsl/scenarios/handlers/py.py +294 -0
  265. edsl/scenarios/handlers/sql.py +313 -0
  266. edsl/scenarios/handlers/sqlite.py +149 -0
  267. edsl/scenarios/handlers/txt.py +33 -0
  268. edsl/scenarios/scenario_join.py +131 -0
  269. edsl/scenarios/scenario_selector.py +156 -0
  270. edsl/shared.py +1 -1
  271. edsl/study/ObjectEntry.py +173 -173
  272. edsl/study/ProofOfWork.py +113 -113
  273. edsl/study/SnapShot.py +80 -80
  274. edsl/study/Study.py +521 -528
  275. edsl/study/__init__.py +4 -4
  276. edsl/surveys/ConstructDAG.py +92 -0
  277. edsl/surveys/DAG.py +148 -148
  278. edsl/surveys/EditSurvey.py +221 -0
  279. edsl/surveys/InstructionHandler.py +100 -0
  280. edsl/surveys/Memory.py +31 -31
  281. edsl/surveys/MemoryManagement.py +72 -0
  282. edsl/surveys/MemoryPlan.py +244 -244
  283. edsl/surveys/Rule.py +327 -326
  284. edsl/surveys/RuleCollection.py +385 -387
  285. edsl/surveys/RuleManager.py +172 -0
  286. edsl/surveys/Simulator.py +75 -0
  287. edsl/surveys/Survey.py +1280 -1787
  288. edsl/surveys/SurveyCSS.py +273 -261
  289. edsl/surveys/SurveyExportMixin.py +259 -259
  290. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -121
  291. edsl/surveys/SurveyQualtricsImport.py +284 -284
  292. edsl/surveys/SurveyToApp.py +141 -0
  293. edsl/surveys/__init__.py +5 -3
  294. edsl/surveys/base.py +53 -53
  295. edsl/surveys/descriptors.py +60 -56
  296. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  297. edsl/surveys/instructions/Instruction.py +56 -53
  298. edsl/surveys/instructions/InstructionCollection.py +82 -77
  299. edsl/templates/error_reporting/base.html +23 -23
  300. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  301. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  302. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  303. edsl/templates/error_reporting/interview_details.html +115 -115
  304. edsl/templates/error_reporting/interviews.html +19 -10
  305. edsl/templates/error_reporting/overview.html +4 -4
  306. edsl/templates/error_reporting/performance_plot.html +1 -1
  307. edsl/templates/error_reporting/report.css +73 -73
  308. edsl/templates/error_reporting/report.html +117 -117
  309. edsl/templates/error_reporting/report.js +25 -25
  310. edsl/tools/__init__.py +1 -1
  311. edsl/tools/clusters.py +192 -192
  312. edsl/tools/embeddings.py +27 -27
  313. edsl/tools/embeddings_plotting.py +118 -118
  314. edsl/tools/plotting.py +112 -112
  315. edsl/tools/summarize.py +18 -18
  316. edsl/utilities/PrettyList.py +56 -0
  317. edsl/utilities/SystemInfo.py +28 -28
  318. edsl/utilities/__init__.py +22 -22
  319. edsl/utilities/ast_utilities.py +25 -25
  320. edsl/utilities/data/Registry.py +6 -6
  321. edsl/utilities/data/__init__.py +1 -1
  322. edsl/utilities/data/scooter_results.json +1 -1
  323. edsl/utilities/decorators.py +77 -77
  324. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  325. edsl/utilities/interface.py +627 -627
  326. edsl/utilities/is_notebook.py +18 -0
  327. edsl/utilities/is_valid_variable_name.py +11 -0
  328. edsl/utilities/naming_utilities.py +263 -263
  329. edsl/utilities/remove_edsl_version.py +24 -0
  330. edsl/utilities/repair_functions.py +28 -28
  331. edsl/utilities/restricted_python.py +70 -70
  332. edsl/utilities/utilities.py +436 -409
  333. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/LICENSE +21 -21
  334. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/METADATA +13 -10
  335. edsl-0.1.39.dist-info/RECORD +358 -0
  336. {edsl-0.1.38.dev3.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  337. edsl/language_models/KeyLookup.py +0 -30
  338. edsl/language_models/registry.py +0 -137
  339. edsl/language_models/unused/ReplicateBase.py +0 -83
  340. edsl/results/ResultsDBMixin.py +0 -238
  341. edsl-0.1.38.dev3.dist-info/RECORD +0 -269
@@ -1,361 +1,151 @@
1
- from __future__ import annotations
2
- import time
3
- import asyncio
4
- import threading
5
- import warnings
6
- from typing import Coroutine, List, AsyncGenerator, Optional, Union, Generator, Type
7
- from uuid import UUID
8
- from collections import UserList
9
-
10
- from edsl.results.Results import Results
11
- from edsl.jobs.interviews.Interview import Interview
12
- from edsl.jobs.runners.JobsRunnerStatus import JobsRunnerStatus, JobsRunnerStatusBase
13
-
14
- from edsl.jobs.tasks.TaskHistory import TaskHistory
15
- from edsl.jobs.buckets.BucketCollection import BucketCollection
16
- from edsl.utilities.decorators import jupyter_nb_handler
17
- from edsl.data.Cache import Cache
18
- from edsl.results.Result import Result
19
- from edsl.results.Results import Results
20
- from edsl.language_models.LanguageModel import LanguageModel
21
- from edsl.data.Cache import Cache
22
-
23
-
24
- class StatusTracker(UserList):
25
- def __init__(self, total_tasks: int):
26
- self.total_tasks = total_tasks
27
- super().__init__()
28
-
29
- def current_status(self):
30
- return print(f"Completed: {len(self.data)} of {self.total_tasks}", end="\r")
31
-
32
-
33
- class JobsRunnerAsyncio:
34
- """A class for running a collection of interviews asynchronously.
35
-
36
- It gets instaniated from a Jobs object.
37
- The Jobs object is a collection of interviews that are to be run.
38
- """
39
-
40
- def __init__(self, jobs: "Jobs"):
41
- self.jobs = jobs
42
- self.interviews: List["Interview"] = jobs.interviews()
43
- self.bucket_collection: "BucketCollection" = jobs.bucket_collection
44
- self.total_interviews: List["Interview"] = []
45
- self._initialized = threading.Event()
46
-
47
- async def run_async_generator(
48
- self,
49
- cache: Cache,
50
- n: int = 1,
51
- stop_on_exception: bool = False,
52
- sidecar_model: Optional[LanguageModel] = None,
53
- total_interviews: Optional[List["Interview"]] = None,
54
- raise_validation_errors: bool = False,
55
- ) -> AsyncGenerator["Result", None]:
56
- """Creates the tasks, runs them asynchronously, and returns the results as a Results object.
57
-
58
- Completed tasks are yielded as they are completed.
59
-
60
- :param n: how many times to run each interview
61
- :param stop_on_exception: Whether to stop the interview if an exception is raised
62
- :param sidecar_model: a language model to use in addition to the interview's model
63
- :param total_interviews: A list of interviews to run can be provided instead.
64
- :param raise_validation_errors: Whether to raise validation errors
65
- """
66
- tasks = []
67
- if total_interviews: # was already passed in total interviews
68
- self.total_interviews = total_interviews
69
- else:
70
- self.total_interviews = list(
71
- self._populate_total_interviews(n=n)
72
- ) # Populate self.total_interviews before creating tasks
73
-
74
- self._initialized.set() # Signal that we're ready
75
-
76
- for interview in self.total_interviews:
77
- interviewing_task = self._build_interview_task(
78
- interview=interview,
79
- stop_on_exception=stop_on_exception,
80
- sidecar_model=sidecar_model,
81
- raise_validation_errors=raise_validation_errors,
82
- )
83
- tasks.append(asyncio.create_task(interviewing_task))
84
-
85
- for task in asyncio.as_completed(tasks):
86
- result = await task
87
- self.jobs_runner_status.add_completed_interview(result)
88
- yield result
89
-
90
- def _populate_total_interviews(
91
- self, n: int = 1
92
- ) -> Generator["Interview", None, None]:
93
- """Populates self.total_interviews with n copies of each interview.
94
-
95
- :param n: how many times to run each interview.
96
- """
97
- for interview in self.interviews:
98
- for iteration in range(n):
99
- if iteration > 0:
100
- yield interview.duplicate(iteration=iteration, cache=self.cache)
101
- else:
102
- interview.cache = self.cache
103
- yield interview
104
-
105
- async def run_async(self, cache: Optional[Cache] = None, n: int = 1) -> Results:
106
- """Used for some other modules that have a non-standard way of running interviews."""
107
- self.jobs_runner_status = JobsRunnerStatus(self, n=n)
108
- self.cache = Cache() if cache is None else cache
109
- data = []
110
- async for result in self.run_async_generator(cache=self.cache, n=n):
111
- data.append(result)
112
- return Results(survey=self.jobs.survey, data=data)
113
-
114
- def simple_run(self):
115
- data = asyncio.run(self.run_async())
116
- return Results(survey=self.jobs.survey, data=data)
117
-
118
- async def _build_interview_task(
119
- self,
120
- *,
121
- interview: Interview,
122
- stop_on_exception: bool = False,
123
- sidecar_model: Optional["LanguageModel"] = None,
124
- raise_validation_errors: bool = False,
125
- ) -> "Result":
126
- """Conducts an interview and returns the result.
127
-
128
- :param interview: the interview to conduct
129
- :param stop_on_exception: stops the interview if an exception is raised
130
- :param sidecar_model: a language model to use in addition to the interview's model
131
- """
132
- # the model buckets are used to track usage rates
133
- model_buckets = self.bucket_collection[interview.model]
134
-
135
- # get the results of the interview
136
- answer, valid_results = await interview.async_conduct_interview(
137
- model_buckets=model_buckets,
138
- stop_on_exception=stop_on_exception,
139
- sidecar_model=sidecar_model,
140
- raise_validation_errors=raise_validation_errors,
141
- )
142
-
143
- question_results = {}
144
- for result in valid_results:
145
- question_results[result.question_name] = result
146
-
147
- answer_key_names = list(question_results.keys())
148
-
149
- generated_tokens_dict = {
150
- k + "_generated_tokens": question_results[k].generated_tokens
151
- for k in answer_key_names
152
- }
153
- comments_dict = {
154
- k + "_comment": question_results[k].comment for k in answer_key_names
155
- }
156
-
157
- # we should have a valid result for each question
158
- answer_dict = {k: answer[k] for k in answer_key_names}
159
- assert len(valid_results) == len(answer_key_names)
160
-
161
- # TODO: move this down into Interview
162
- question_name_to_prompts = dict({})
163
- for result in valid_results:
164
- question_name = result.question_name
165
- question_name_to_prompts[question_name] = {
166
- "user_prompt": result.prompts["user_prompt"],
167
- "system_prompt": result.prompts["system_prompt"],
168
- }
169
-
170
- prompt_dictionary = {}
171
- for answer_key_name in answer_key_names:
172
- prompt_dictionary[answer_key_name + "_user_prompt"] = (
173
- question_name_to_prompts[answer_key_name]["user_prompt"]
174
- )
175
- prompt_dictionary[answer_key_name + "_system_prompt"] = (
176
- question_name_to_prompts[answer_key_name]["system_prompt"]
177
- )
178
-
179
- raw_model_results_dictionary = {}
180
- cache_used_dictionary = {}
181
- for result in valid_results:
182
- question_name = result.question_name
183
- raw_model_results_dictionary[question_name + "_raw_model_response"] = (
184
- result.raw_model_response
185
- )
186
- raw_model_results_dictionary[question_name + "_cost"] = result.cost
187
- one_use_buys = (
188
- "NA"
189
- if isinstance(result.cost, str)
190
- or result.cost == 0
191
- or result.cost is None
192
- else 1.0 / result.cost
193
- )
194
- raw_model_results_dictionary[question_name + "_one_usd_buys"] = one_use_buys
195
- cache_used_dictionary[question_name] = result.cache_used
196
-
197
- result = Result(
198
- agent=interview.agent,
199
- scenario=interview.scenario,
200
- model=interview.model,
201
- iteration=interview.iteration,
202
- answer=answer_dict,
203
- prompt=prompt_dictionary,
204
- raw_model_response=raw_model_results_dictionary,
205
- survey=interview.survey,
206
- generated_tokens=generated_tokens_dict,
207
- comments_dict=comments_dict,
208
- cache_used_dict=cache_used_dictionary,
209
- )
210
- result.interview_hash = hash(interview)
211
-
212
- return result
213
-
214
- @property
215
- def elapsed_time(self):
216
- return time.monotonic() - self.start_time
217
-
218
- def process_results(
219
- self, raw_results: Results, cache: Cache, print_exceptions: bool
220
- ):
221
- interview_lookup = {
222
- hash(interview): index
223
- for index, interview in enumerate(self.total_interviews)
224
- }
225
- interview_hashes = list(interview_lookup.keys())
226
-
227
- task_history = TaskHistory(self.total_interviews, include_traceback=False)
228
-
229
- results = Results(
230
- survey=self.jobs.survey,
231
- data=sorted(
232
- raw_results, key=lambda x: interview_hashes.index(x.interview_hash)
233
- ),
234
- task_history=task_history,
235
- cache=cache,
236
- )
237
- results.bucket_collection = self.bucket_collection
238
-
239
- if results.has_unfixed_exceptions and print_exceptions:
240
- from edsl.scenarios.FileStore import HTMLFileStore
241
- from edsl.config import CONFIG
242
- from edsl.coop.coop import Coop
243
-
244
- msg = f"Exceptions were raised in {len(results.task_history.indices)} out of {len(self.total_interviews)} interviews.\n"
245
-
246
- if len(results.task_history.indices) > 5:
247
- msg += f"Exceptions were raised in the following interviews: {results.task_history.indices}.\n"
248
-
249
- print(msg)
250
- # this is where exceptions are opening up
251
- filepath = results.task_history.html(
252
- cta="Open report to see details.",
253
- open_in_browser=True,
254
- return_link=True,
255
- )
256
-
257
- try:
258
- coop = Coop()
259
- user_edsl_settings = coop.edsl_settings
260
- remote_logging = user_edsl_settings["remote_logging"]
261
- except Exception as e:
262
- print(e)
263
- remote_logging = False
264
-
265
- if remote_logging:
266
- filestore = HTMLFileStore(filepath)
267
- coop_details = filestore.push(description="Error report")
268
- print(coop_details)
269
-
270
- print("Also see: https://docs.expectedparrot.com/en/latest/exceptions.html")
271
-
272
- return results
273
-
274
- @jupyter_nb_handler
275
- async def run(
276
- self,
277
- cache: Union[Cache, False, None],
278
- n: int = 1,
279
- stop_on_exception: bool = False,
280
- progress_bar: bool = False,
281
- sidecar_model: Optional[LanguageModel] = None,
282
- jobs_runner_status: Optional[Type[JobsRunnerStatusBase]] = None,
283
- job_uuid: Optional[UUID] = None,
284
- print_exceptions: bool = True,
285
- raise_validation_errors: bool = False,
286
- ) -> "Coroutine":
287
- """Runs a collection of interviews, handling both async and sync contexts."""
288
-
289
- self.results = []
290
- self.start_time = time.monotonic()
291
- self.completed = False
292
- self.cache = cache
293
- self.sidecar_model = sidecar_model
294
-
295
- from edsl.coop import Coop
296
-
297
- coop = Coop()
298
- endpoint_url = coop.get_progress_bar_url()
299
-
300
- if jobs_runner_status is not None:
301
- self.jobs_runner_status = jobs_runner_status(
302
- self, n=n, endpoint_url=endpoint_url, job_uuid=job_uuid
303
- )
304
- else:
305
- self.jobs_runner_status = JobsRunnerStatus(
306
- self, n=n, endpoint_url=endpoint_url, job_uuid=job_uuid
307
- )
308
-
309
- stop_event = threading.Event()
310
-
311
- async def process_results(cache):
312
- """Processes results from interviews."""
313
- async for result in self.run_async_generator(
314
- n=n,
315
- stop_on_exception=stop_on_exception,
316
- cache=cache,
317
- sidecar_model=sidecar_model,
318
- raise_validation_errors=raise_validation_errors,
319
- ):
320
- self.results.append(result)
321
- self.completed = True
322
-
323
- def run_progress_bar(stop_event):
324
- """Runs the progress bar in a separate thread."""
325
- self.jobs_runner_status.update_progress(stop_event)
326
-
327
- if progress_bar and self.jobs_runner_status.has_ep_api_key():
328
- self.jobs_runner_status.setup()
329
- progress_thread = threading.Thread(
330
- target=run_progress_bar, args=(stop_event,)
331
- )
332
- progress_thread.start()
333
- elif progress_bar:
334
- warnings.warn(
335
- "You need an Expected Parrot API key to view job progress bars."
336
- )
337
-
338
- exception_to_raise = None
339
- try:
340
- with cache as c:
341
- await process_results(cache=c)
342
- except KeyboardInterrupt:
343
- print("Keyboard interrupt received. Stopping gracefully...")
344
- stop_event.set()
345
- except Exception as e:
346
- if stop_on_exception:
347
- exception_to_raise = e
348
- stop_event.set()
349
- finally:
350
- stop_event.set()
351
- if progress_bar and self.jobs_runner_status.has_ep_api_key():
352
- # self.jobs_runner_status.stop_event.set()
353
- if progress_thread:
354
- progress_thread.join()
355
-
356
- if exception_to_raise:
357
- raise exception_to_raise
358
-
359
- return self.process_results(
360
- raw_results=self.results, cache=cache, print_exceptions=print_exceptions
361
- )
1
+ from __future__ import annotations
2
+ import time
3
+ import asyncio
4
+ import threading
5
+ import warnings
6
+ from typing import TYPE_CHECKING
7
+
8
+ from edsl.results.Results import Results
9
+ from edsl.jobs.runners.JobsRunnerStatus import JobsRunnerStatus
10
+ from edsl.jobs.tasks.TaskHistory import TaskHistory
11
+ from edsl.utilities.decorators import jupyter_nb_handler
12
+ from edsl.jobs.async_interview_runner import AsyncInterviewRunner
13
+ from edsl.jobs.data_structures import RunEnvironment, RunParameters, RunConfig
14
+
15
+ if TYPE_CHECKING:
16
+ from edsl.jobs.Jobs import Jobs
17
+
18
+
19
+ class JobsRunnerAsyncio:
20
+ """A class for running a collection of interviews asynchronously.
21
+
22
+ It gets instaniated from a Jobs object.
23
+ The Jobs object is a collection of interviews that are to be run.
24
+ """
25
+
26
+ def __init__(self, jobs: "Jobs", environment: RunEnvironment):
27
+ self.jobs = jobs
28
+ self.environment = environment
29
+
30
+ def __len__(self):
31
+ return len(self.jobs)
32
+
33
+ async def run_async(self, parameters: RunParameters) -> Results:
34
+ """Used for some other modules that have a non-standard way of running interviews."""
35
+
36
+ self.environment.jobs_runner_status = JobsRunnerStatus(self, n=parameters.n)
37
+ data = []
38
+ task_history = TaskHistory(include_traceback=False)
39
+
40
+ run_config = RunConfig(parameters=parameters, environment=self.environment)
41
+ result_generator = AsyncInterviewRunner(self.jobs, run_config)
42
+
43
+ async for result, interview in result_generator.run():
44
+ data.append(result)
45
+ task_history.add_interview(interview)
46
+
47
+ return Results(survey=self.jobs.survey, task_history=task_history, data=data)
48
+
49
+ def simple_run(self):
50
+ data = asyncio.run(self.run_async())
51
+ return Results(survey=self.jobs.survey, data=data)
52
+
53
+ @jupyter_nb_handler
54
+ async def run(self, parameters: RunParameters) -> Results:
55
+ """Runs a collection of interviews, handling both async and sync contexts."""
56
+
57
+ run_config = RunConfig(parameters=parameters, environment=self.environment)
58
+
59
+ self.start_time = time.monotonic()
60
+ self.completed = False
61
+
62
+ from edsl.coop import Coop
63
+
64
+ coop = Coop()
65
+ endpoint_url = coop.get_progress_bar_url()
66
+
67
+ def set_up_jobs_runner_status(jobs_runner_status):
68
+ if jobs_runner_status is not None:
69
+ return jobs_runner_status(
70
+ self,
71
+ n=parameters.n,
72
+ endpoint_url=endpoint_url,
73
+ job_uuid=parameters.job_uuid,
74
+ )
75
+ else:
76
+ return JobsRunnerStatus(
77
+ self,
78
+ n=parameters.n,
79
+ endpoint_url=endpoint_url,
80
+ job_uuid=parameters.job_uuid,
81
+ )
82
+
83
+ run_config.environment.jobs_runner_status = set_up_jobs_runner_status(
84
+ self.environment.jobs_runner_status
85
+ )
86
+
87
+ async def get_results(results) -> None:
88
+ """Conducted the interviews and append to the results list."""
89
+ result_generator = AsyncInterviewRunner(self.jobs, run_config)
90
+ async for result, interview in result_generator.run():
91
+ results.append(result)
92
+ results.task_history.add_interview(interview)
93
+
94
+ self.completed = True
95
+
96
+ def run_progress_bar(stop_event, jobs_runner_status) -> None:
97
+ """Runs the progress bar in a separate thread."""
98
+ jobs_runner_status.update_progress(stop_event)
99
+
100
+ def set_up_progress_bar(progress_bar: bool, jobs_runner_status):
101
+ progress_thread = None
102
+ if progress_bar and jobs_runner_status.has_ep_api_key():
103
+ jobs_runner_status.setup()
104
+ progress_thread = threading.Thread(
105
+ target=run_progress_bar, args=(stop_event, jobs_runner_status)
106
+ )
107
+ progress_thread.start()
108
+ elif progress_bar:
109
+ warnings.warn(
110
+ "You need an Expected Parrot API key to view job progress bars."
111
+ )
112
+ return progress_thread
113
+
114
+ results = Results(
115
+ survey=self.jobs.survey,
116
+ data=[],
117
+ task_history=TaskHistory(),
118
+ cache=self.environment.cache.new_entries_cache(),
119
+ )
120
+ stop_event = threading.Event()
121
+ progress_thread = set_up_progress_bar(
122
+ parameters.progress_bar, run_config.environment.jobs_runner_status
123
+ )
124
+
125
+ exception_to_raise = None
126
+ try:
127
+ await get_results(results)
128
+ except KeyboardInterrupt:
129
+ print("Keyboard interrupt received. Stopping gracefully...")
130
+ stop_event.set()
131
+ except Exception as e:
132
+ if parameters.stop_on_exception:
133
+ exception_to_raise = e
134
+ stop_event.set()
135
+ finally:
136
+ stop_event.set()
137
+ if progress_thread is not None:
138
+ progress_thread.join()
139
+
140
+ if exception_to_raise:
141
+ raise exception_to_raise
142
+
143
+ results.cache = self.environment.cache.new_entries_cache()
144
+ results.bucket_collection = self.environment.bucket_collection
145
+
146
+ from edsl.jobs.results_exceptions_handler import ResultsExceptionsHandler
147
+
148
+ results_exceptions_handler = ResultsExceptionsHandler(results, parameters)
149
+
150
+ results_exceptions_handler.handle_exceptions()
151
+ return results